import bpy import mathutils import importlib # then import dependencies for our addon if "Font" in locals(): importlib.reload(Font) else: from .common import Font def apply_all_transforms(obj): mb = obj.matrix_basis if hasattr(obj.data, "transform"): obj.data.transform(mb) for c in obj.children: c.matrix_local = mb @ c.matrix_local obj.matrix_basis.identity() def get_parent_collection_names(collection, parent_names): for parent_collection in bpy.data.collections: if collection.name in parent_collection.children.keys(): parent_names.append(parent_collection.name) get_parent_collection_names(parent_collection, parent_names) return # Ensure it's a curve object # TODO: no raising, please def get_curve_length(obj, num_samples = 100): total_length = 0 if obj.type != 'CURVE': raise TypeError("The selected object is not a curve") curve = obj.data # Loop through all splines in the curve for spline in curve.splines: total_length = total_length + spline.calc_length() return total_length # def get_objects_by_name(name, startswith="", endswith=""): # return [obj for obj in bpy.context.scene.objects if obj.name.startswith(startswith) and if obj.name.endswith(endswith)] def find_objects_by_name( objects, equals="", contains="", startswith="", endswith=""): # handle equals if equals != "": index = objects.find(equals) if index >= 0: return [objects[index]] return [] # handle others is more permissive return [obj for obj in objects if obj.name.startswith(startswith) and obj.name.endswith(endswith) and obj.name.find(contains) >= 0] def find_objects_by_custom_property( objects, property_name="", property_value=""): return [obj for obj in objects if property_name in obj and obj[property_name] == property_value] def turn_collection_hierarchy_into_path(obj): parent_collection = obj.users_collection[0] parent_names = [] parent_names.append(parent_collection.name) get_parent_collection_names(parent_collection, parent_names) parent_names.reverse() return '\\'.join(parent_names) def find_font_object(fontcollection, font_name): fonts = find_objects_by_custom_property(fontcollection.objects, "is_font", True) for font in fonts: if font["font_name"] == font_name and font.parent == None: return font return None def find_font_face_object(font_obj, face_name): faces = find_objects_by_custom_property(font_obj.children, "is_face", True) for face in faces: if face["face_name"] == face_name: return face return None def move_in_fontcollection(obj, fontcollection): # print(turn_collection_hierarchy_into_path(obj)) # if scene.collection.objects.find(obj.name) >= 0: # scene.collection.objects.unlink(obj) for c in obj.users_collection: c.objects.unlink(obj) if fontcollection.objects.find(obj.name) < 0: fontcollection.objects.link(obj) # parent nesting structure # the font object font_obj = find_font_object(fontcollection, obj["font_name"]) if font_obj == None: font_obj = bpy.data.objects.new(obj["font_name"], None) font_obj.empty_display_type = 'PLAIN_AXES' fontcollection.objects.link(font_obj) # ensure custom properties are set font_obj["font_name"] = obj["font_name"] font_obj["is_font"] = True # the face object as a child of font object face_obj = find_font_face_object(font_obj, obj["face_name"]) if face_obj == None: face_obj = bpy.data.objects.new(obj["face_name"], None) face_obj.empty_display_type = 'PLAIN_AXES' face_obj["is_face"] = True fontcollection.objects.link(face_obj) # ensure custom properties are set face_obj["face_name"] = obj["face_name"] face_obj["font_name"] = obj["font_name"] if face_obj.parent != font_obj: face_obj.parent = font_obj # create glyphs if it does not exist glyphs_objs = find_objects_by_name(face_obj.children, startswith="glyphs") if len(glyphs_objs) <= 0: glyphs_obj = bpy.data.objects.new("glyphs", None) glyphs_obj.empty_display_type = 'PLAIN_AXES' fontcollection.objects.link(glyphs_obj) glyphs_obj.parent = face_obj elif len(glyphs_objs) > 1: print(f"found more glyphs objects than expected") # now it must exist glyphs_obj = find_objects_by_name(face_obj.children, startswith="glyphs")[0] glyphs_obj["face_name"] = obj["face_name"] glyphs_obj["font_name"] = obj["font_name"] # and now parent it! if obj.parent != glyphs_obj: obj.parent = glyphs_obj def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""): """Show a simple message box taken from `Link here `_ :param title: The title shown in the message top bar :type title: str :param icon: The icon to be shown in the message top bar :type icon: str :param message: lines of text to display, a.k.a. the message :type message: str or (str, str, ..) TIP: Check `Link blender icons `_ for icons you can use TIP: Or even better, check `Link this addons `_ to also see the icons. usage: .. code-block:: python myLines=("line 1","line 2","line 3") butils.ShowMessageBox(message=myLines) or: .. code-block:: python butils.ShowMessageBox(title="",message=("AAAAAH","NOOOOO"),icon=) """ myLines=message def draw(self, context): if isinstance(myLines, str): self.layout.label(text=myLines) elif hasattr(myLines, "__iter__"): for n in myLines: self.layout.label(text=n) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) def set_text_on_curve(text_properties): mom = text_properties.text_object if mom.type != "CURVE": return False glyph_objects = [] for g in text_properties.glyphs: glyph_objects.append(g.glyph_object) context_override = bpy.context.copy() context_override["selected_objects"] = list(glyph_objects) with bpy.context.temp_override(**context_override): bpy.ops.object.delete() # bpy.ops.object.delete({"selected_objects": glyph_objects}) text_properties.glyphs.clear() #TODO: fix selection with context_override previous_selection = bpy.context.selected_objects bpy.ops.object.select_all(action='DESELECT') selected_objects = [] curve_length = get_curve_length(mom) advance = 0 for i, c in enumerate(text_properties.text): glyph_id = c glyph = Font.get_glyph(text_properties.font_name, text_properties.font_face, glyph_id) if glyph == None: self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}") continue ob = bpy.data.objects.new(f"{glyph_id}", glyph.data) ob.constraints.new(type='FOLLOW_PATH') ob.constraints["Follow Path"].target = mom ob.constraints["Follow Path"].use_fixed_location = True ob.constraints["Follow Path"].offset_factor = advance / curve_length ob.constraints["Follow Path"].use_curve_follow = True ob.constraints["Follow Path"].forward_axis = "FORWARD_X" ob.constraints["Follow Path"].up_axis = "UP_Y" scalor = 0.001 glyph_advance = (-1 * glyph.bound_box[0][0] + glyph.bound_box[4][0]) * scalor + text_properties.letter_spacing ob.scale = (scalor, scalor, scalor) mom.users_collection[0].objects.link(ob) advance = advance + glyph_advance glyph_data = text_properties.glyphs.add() glyph_data.glyph_id = glyph_id glyph_data.glyph_object = ob glyph_data.letter_spacing = 0 ob.select_set(True) # selected_objects.append(ob) # selected_objects.append(mom) mom.select_set(True) bpy.context.view_layer.objects.active = mom bpy.ops.object.parent_set(type='OBJECT') # bpy.ops.object.select_all(action='DESELECT') # for o in previous_selection: # o.select_set(True) # context_override = bpy.context.copy() # context_override["selected_objects"] = selected_objects # context_override["active_object"] = mom # with bpy.context.temp_override(**context_override): # bpy.ops.object.parent_set(type='OBJECT') return True