diff --git a/README.md b/README.md index e38ad46..0b0bdee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ / ___ \| |_) | |___ ___) | |_| | /_/ \_\____/ \____|____/|____/ ``` -v0.0.6 +v0.0.5 Convenience tool to work with 3D typography in Blender and Cinema4D. diff --git a/__init__.py b/__init__.py index 2a71c8f..9d5b8be 100644 --- a/__init__.py +++ b/__init__.py @@ -16,13 +16,13 @@ from .common import Font, utils bl_info = { "name": "ABC3D", "author": "Jakob Schlötter, Studio Pointer*", - "version": (0, 0, 6), + "version": (0, 0, 5), "blender": (4, 1, 0), "location": "VIEW3D", "description": "Convenience addon for 3D fonts", "category": "Typography", } -# NOTE: also change version in common/utils.py and README.md +# NOTE: also change version in common/utils.py # make sure that modules are reloadable # when registering @@ -134,26 +134,11 @@ class ABC3D_available_font(bpy.types.PropertyGroup): class ABC3D_glyph_properties(bpy.types.PropertyGroup): - - def update_callback(self, context): - if self.text_id >= 0: - butils.set_text_on_curve( - context.scene.abc3d_data.available_texts[self.text_id] - ) - glyph_id: bpy.props.StringProperty(maxlen=1) - text_id: bpy.props.IntProperty( - default=-1, - ) - alternate: bpy.props.IntProperty( - default=-1, - update=update_callback, - ) glyph_object: bpy.props.PointerProperty(type=bpy.types.Object) letter_spacing: bpy.props.FloatProperty( name="Letter Spacing", description="Letter Spacing", - update=update_callback, ) class ABC3D_text_properties(bpy.types.PropertyGroup): @@ -579,95 +564,6 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): layout.row().operator(f"{__name__}.remove_text", text="Remove Textobject") -class ABC3D_PG_FontCreation(bpy.types.PropertyGroup): - bl_label = "Font Creation Properties" - # bl_parent_id = "ABC3D_PG_NamingHelper" - # bl_category = "ABC3D" - # bl_space_type = "VIEW_3D" - # bl_region_type = "UI" - # bl_options = {"DEFAULT_CLOSED"} - - def naming_glyph_id_update_callback(self, context): - glyph_name = Font.glyph_to_name(self.naming_glyph_id) - if self.naming_glyph_full: - self.naming_glyph_name = f"{glyph_name}_{self.font_name}_{self.face_name}" - else: - self.naming_glyph_name = glyph_name - - naming_glyph_id: bpy.props.StringProperty( - name="", - description="find proper naming for a glyph", - default="", - maxlen=32, - update=naming_glyph_id_update_callback, - ) - - naming_glyph_name: bpy.props.StringProperty( - name="", - description="find proper naming for a glyph", - default="", - maxlen=1024, - ) - - naming_glyph_full: bpy.props.BoolProperty( - default=True, - description="Generate full name", - update=naming_glyph_id_update_callback, - ) - - font_name: bpy.props.StringProperty( - name="", - description="Font name", - default="NM_Origin", - update=naming_glyph_id_update_callback, - ) - - face_name: bpy.props.StringProperty( - name="", - description="FontFace name", - default="Tender", - update=naming_glyph_id_update_callback, - ) - -class ABC3D_OT_NamingHelper(bpy.types.Operator): - bl_label = "Font Creation Naming Helper Apply To Active Object" - bl_idname = f"{__name__}.apply_naming_helper" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - abc3d_font_creation = context.scene.abc3d_font_creation - name = abc3d_font_creation.naming_glyph_name - context.active_object.name = name - return {"FINISHED"} - - -class ABC3D_PT_NamingHelper(bpy.types.Panel): - bl_label = "Naming Helper" - bl_parent_id = "ABC3D_PT_FontCreation" - bl_category = "ABC3D" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_options = {"DEFAULT_CLOSED"} - - def draw(self, context): - layout = self.layout - scene = context.scene - - abc3d_font_creation = scene.abc3d_font_creation - - box = layout.box() - box.label(text="Glyph Naming Helper") - box.row().prop(abc3d_font_creation, "naming_glyph_full") - box.label(text="Glyph Character Input") - box.row().prop(abc3d_font_creation, "naming_glyph_id") - box.label(text="Font name:") - box.row().prop(abc3d_font_creation, "font_name") - box.label(text="FontFace name:") - box.row().prop(abc3d_font_creation, "face_name") - box.label(text="Glyph Output Name") - box.row().prop(abc3d_font_creation, "naming_glyph_name") - box.row().operator(f"{__name__}.apply_naming_helper", text="Apply name to active object") - class ABC3D_PT_FontCreation(bpy.types.Panel): bl_label = "Font Creation" bl_parent_id = "ABC3D_PT_Panel" @@ -679,6 +575,9 @@ class ABC3D_PT_FontCreation(bpy.types.Panel): def draw(self, context): layout = self.layout wm = context.window_manager + scene = context.scene + + abc3d_data = scene.abc3d_data layout.row().operator( f"{__name__}.toggle_abc3d_collection", text="Toggle Collection" @@ -689,7 +588,6 @@ class ABC3D_PT_FontCreation(bpy.types.Panel): layout.row().operator( f"{__name__}.save_font_to_file", text="Export Font To File" ) - box = layout.box() box.label(text="metrics") box.row().operator( @@ -704,10 +602,6 @@ class ABC3D_PT_FontCreation(bpy.types.Panel): layout.row().operator( f"{__name__}.temporaryhelper", text="Debug Function Do Not Use" ) - box.label(text="origin points") - box.row().operator(f"{__name__}.align_origins_to_active_object", text="Align origins to Active Object") - # box.row().operator(f"{__name__}.align_origins_to_metrics", text="Align origins to Metrics (left)") - # box.row().operator(f"{__name__}.fix_objects_metrics_origins", text="Fix objects metrics origins") class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): @@ -719,38 +613,14 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): def get_active_text_properties(self): # and bpy.context.object.select_get(): - a_o = bpy.context.active_object - if a_o is not None: - if f"{utils.prefix()}_linked_textobject" in a_o: - text_index = a_o[f"{utils.prefix()}_linked_textobject"] - return bpy.context.scene.abc3d_data.available_texts[text_index] - elif f"{utils.prefix()}_linked_textobject" in a_o.parent: - text_index = a_o.parent[f"{utils.prefix()}_linked_textobject"] - return bpy.context.scene.abc3d_data.available_texts[text_index] - else: - for t in bpy.context.scene.abc3d_data.available_texts: - if butils.is_or_has_parent(bpy.context.active_object, t.text_object, max_depth=4): - return t + if type(bpy.context.active_object) != type(None): + for t in bpy.context.scene.abc3d_data.available_texts: + if bpy.context.active_object == t.text_object: + return t + if bpy.context.active_object.parent == t.text_object: + return t return None - # NOTE: HERE - def get_active_glyph_properties(self): - a_o = bpy.context.active_object - if a_o is not None: - if (f"{utils.prefix()}_linked_textobject" in a_o - and f"{utils.prefix()}_glyph_index" in a_o): - text_index = a_o[f"{utils.prefix()}_linked_textobject"] - glyph_index = a_o[f"{utils.prefix()}_glyph_index"] - return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[glyph_index] - else: - for t in bpy.context.scene.abc3d_data.available_texts: - if butils.is_or_has_parent(a_o, t.text_object, if_is_parent=False, max_depth=4): - for g in t.glyphs: - if butils.is_or_has_parent(a_o, g.glyph_object, max_depth=4): - return g - return None - - # def font_items_callback(self, context): # items = [] # fonts = Font.get_loaded_fonts_and_faces() @@ -780,7 +650,7 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): @classmethod def poll(self, context): - return self.get_active_text_properties(self) is not None + return type(self.get_active_text_properties(self)) != type(None) def draw(self, context): layout = self.layout @@ -789,16 +659,11 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): abc3d_data = scene.abc3d_data props = self.get_active_text_properties() - glyph_props = self.get_active_glyph_properties() - if props is None or props.text_object is None: + if type(props) == type(None) or type(props.text_object) == type(None): # this should not happen # as then polling does not work # however, we are paranoid - if props is None: - layout.label(text="props is none") - elif props.text_object is None: - layout.label(text="props.text_object is none") return layout.label(text=f"Mom: {props.text_object.name}") @@ -812,13 +677,6 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): layout.column().prop(props, "translation") layout.column().prop(props, "orientation") - if glyph_props is None: - return - box = layout.box() - box.label(text=f"{glyph_props.glyph_id}") - box.row().prop(glyph_props, "letter_spacing") - - class ABC3D_OT_InstallFont(bpy.types.Operator): """Install or load Fontfile from path above. @@ -1065,72 +923,6 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator): butils.align_metrics_of_objects(objects) return {"FINISHED"} -class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator): - """Align origins of selected objects to origin of active object on one axis.""" - - bl_idname = f"{__name__}.align_origins_to_active_object" - bl_label = "Align origins to Active Object" - bl_options = {"REGISTER", "UNDO"} - - enum_axis = (('0','X',''),('1','Y',''),('2','Z','')) - axis: bpy.props.EnumProperty(items = enum_axis, default='2') - - def execute(self, context): - objects = bpy.context.selected_objects - butils.align_origins_to_active_object(objects, int(self.axis)) - return {"FINISHED"} - -# class ABC3D_OT_AlignOriginsToMetrics(bpy.types.Operator): - # """Align origins of selected objects to their metrics left border. - - # Be aware that shifting the origin will also shift the pivot point around which an object rotates. - - # If an object does not have metrics, it will be ignored.""" - - # bl_idname = f"{__name__}.align_origins_to_metrics" - # bl_label = "Align origins to metrics metrics" - # bl_options = {"REGISTER", "UNDO"} - - # ignore_warning: bpy.props.BoolProperty( - # name="Do not warn in the future", - # description="Do not warn in the future", - # default=False, - # ) - - # def draw(self, context): - # layout = self.layout - # layout.row().label(text="Warning!") - # layout.row().label(text="This also shifts the pivot point around which the glyph rotates.") - # layout.row().label(text="This may not be what you want.") - # layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.") - # layout.row().label(text="If you are sure about what you're doing, please continue.") - # layout.row().prop(self, "ignore_warning") - - # def invoke(self, context, event): - # if not self.ignore_warning: - # wm = context.window_manager - # return wm.invoke_props_dialog(self) - # return self.execute(context) - - # def execute(self, context): - # objects = bpy.context.selected_objects - # butils.align_origins_to_metrics(objects) - # butils.fix_objects_metrics_origins(objects) - # return {"FINISHED"} - -# class ABC3D_OT_FixObjectsMetricsOrigins(bpy.types.Operator): - # """Align metrics origins of selected objects to their metrics bounding box. - - # If an object does not have metrics, it will be ignored.""" - - # bl_idname = f"{__name__}.fix_objects_metrics_origins" - # bl_label = "Fix metrics origin of all selected objects" - # bl_options = {"REGISTER", "UNDO"} - - # def execute(self, context): - # objects = bpy.context.selected_objects - # butils.fix_objects_metrics_origins(objects) - # return {"FINISHED"} class ABC3D_OT_TemporaryHelper(bpy.types.Operator): """Temporary Helper ABC3D\nThis could do anything.\nIt's just there to make random functions available for testing.""" @@ -1725,9 +1517,6 @@ classes = ( ABC3D_PT_TextPlacement, ABC3D_PT_TextManagement, ABC3D_PT_FontCreation, - ABC3D_PG_FontCreation, - ABC3D_OT_NamingHelper, - ABC3D_PT_NamingHelper, ABC3D_PT_TextPropertiesPanel, ABC3D_OT_OpenAssetDirectory, ABC3D_OT_LoadInstalledFonts, @@ -1736,9 +1525,6 @@ classes = ( ABC3D_OT_RemoveMetrics, ABC3D_OT_AlignMetricsToActiveObject, ABC3D_OT_AlignMetrics, - ABC3D_OT_AlignOriginsToActiveObject, - # ABC3D_OT_AlignOriginsToMetrics, - # ABC3D_OT_FixObjectsMetricsOrigins, ABC3D_OT_TemporaryHelper, ABC3D_OT_RemoveText, ABC3D_OT_PlaceText, @@ -1887,7 +1673,6 @@ def register(): addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings. bpy.utils.register_class(cls) bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data) - bpy.types.Scene.abc3d_font_creation = bpy.props.PointerProperty(type=ABC3D_PG_FontCreation) # bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}") # autostart if we load a blend file @@ -1931,7 +1716,6 @@ def unregister(): bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update) del bpy.types.Scene.abc3d_data - del bpy.types.Scene.abc3d_font_creation print(f"UNREGISTER {utils.prefix()}") diff --git a/_vimrc_local.vim b/_vimrc_local.vim new file mode 100644 index 0000000..cac1787 --- /dev/null +++ b/_vimrc_local.vim @@ -0,0 +1,20 @@ +""""""""""""""""""""""""""""""""" JEDI + +let g:jedi#auto_initialization = 1 +let g:jedi#use_tabs_not_buffers = 1 +let g:jedi#environment_path = "venv" + +""""""""""""""""""""""""""""""""" ALE + +"let g:ale_python_pylint_executable = '/home/jrkb/git/pointer/neomatter/font3d/abc3d/venv/bin/pylint' +"let g:ale_python_executable='/home/jrkb/git/pointer/neomatter/font3d/abc3d/venv/bin/python' +"let g:ale_python_pylint_use_global=1 +"let g:ale_use_global_executables=1 +"let g:ale_python_auto_pipenv=1 +"let g:ale_python_auto_virtualenv=1 +"let g:ale_virtualenv_dir_names = ['venv'] + +"let g:ale_linters = { 'javascript': ['eslint', 'tsserver'], 'python': ['jedils', 'pylint', 'flake8'], 'cpp': ['cc', 'clangcheck', 'clangd', 'clangtidy', 'clazy', 'cppcheck', 'cpplint', 'cquery', 'cspell', 'flawfinder'], 'php': ['php_cs_fixer'] } +"let g:ale_fixers = { '*': ['remove_trailing_lines', 'trim_whitespace'], 'python': ['autopep8'], 'cpp': ['uncrustify'], 'javascript': js_fixers, 'css': ['prettier'], 'json': ['prettier'], 'php': ['php_cs_fixer'] } + +"let g:ale_pattern_options = {'\.py$': {'ale_enabled': 0}} diff --git a/butils.py b/butils.py index 94ed804..9ddfba1 100644 --- a/butils.py +++ b/butils.py @@ -485,7 +485,7 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): modified_font_faces = [] all_glyph_os = [] - + all_objects = [] for o in bpy.context.scene.objects: if marker_property in o: if "type" in o and o["type"] == "glyph": @@ -511,17 +511,17 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): modified_font_faces.append({"font_name": font_name, "face_name": face_name}) for mff in modified_font_faces: - mff_glyphs = [] + glyphs = [] face = Font.fonts[mff["font_name"]].faces[mff["face_name"]] # iterate glyphs for g in face.glyphs: # iterate alternates for glyph in face.glyphs[g]: - mff_glyphs.append(get_original(glyph)) - if len(mff_glyphs) > 0: - add_default_metrics_to_objects(mff_glyphs) + glyphs.append(get_original(glyph)) + if len(glyphs) > 0: + add_default_metrics_to_objects(glyphs) # calculate unit factor - h = get_glyph_height(mff_glyphs[0]) + h = get_glyph_height(glyphs[0]) if h != 0: face.unit_factor = 1 / h @@ -733,12 +733,6 @@ def get_glyph_advance(glyph_obj): return abs(c.bound_box[4][0] - c.bound_box[0][0]) return abs(glyph_obj.bound_box[4][0] - glyph_obj.bound_box[0][0]) -def get_glyph_prepost_advances(glyph_obj): - for c in glyph_obj.children: - if is_metrics_object(c): - return -1 * c.bound_box[0][0], c.bound_box[4][0] - return -1 * glyph_obj.bound_box[0][0], glyph_obj.bound_box[4][0] - def get_glyph_height(glyph_obj): for c in glyph_obj.children: @@ -808,41 +802,6 @@ def will_regenerate(text_properties): return False -def update_matrices(obj): - if obj.parent is None: - obj.matrix_world = obj.matrix_basis - - else: - obj.matrix_world = obj.parent.matrix_world * \ - obj.matrix_parent_inverse * \ - obj.matrix_basis - -def is_or_has_parent(o, parent, if_is_parent=True, max_depth=10): - if o == parent and if_is_parent: - return True - for i in range(0, max_depth): - o = o.parent - if o == parent: - return True - if o is None: - return False - return False - -def parent_to_curve(o, c): - o.parent_type = 'OBJECT' - o.parent = c - # o.matrix_parent_inverse = c.matrix_world.inverted() - - if c.data.use_path and len(c.data.splines) > 0: - if c.data.splines[0].type == "BEZIER": - i = -1 if c.data.splines[0].use_cyclic_u else 0 - p = c.data.splines[0].bezier_points[i].co - o.matrix_parent_inverse.translation = p * -1.0 - elif c.data.splines[0].type == 'NURBS': - cm = c.to_mesh() - p = cm.vertices[0].co - o.matrix_parent_inverse.translation = p * -1.0 - def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4): """set_text_on_curve @@ -856,9 +815,8 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) :param reset_depsgraph_n: reset external parameters after n-th depsgraph update. (<= 0) = immediate, (> 0) = reset after n-th depsgraph update, (False) = no depsgraph reset :type reset_depsgraph_n: int """ - # NOTE: depsgraph update not locked - # as we fixed data_path with parent_to_curve trick - # global lock_depsgraph_update_n_times + + global lock_depsgraph_update_n_times # starttime = time.perf_counter_ns() mom = text_properties.text_object @@ -867,44 +825,28 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) distribution_type = "CALCULATE" if is_bezier(mom) else "FOLLOW_PATH" - # NOTE: following not necessary anymore - # as we fixed data_path with parent_to_curve trick - # # use_path messes with parenting # however, we need it for follow_path # https://projects.blender.org/blender/blender/issues/100661 - # previous_use_path = mom.data.use_path - # if distribution_type == "CALCULATE": - # mom.data.use_path = False - # elif distribution_type == "FOLLOW_PATH": - # mom.data.use_path = True + previous_use_path = mom.data.use_path + if distribution_type == "CALCULATE": + mom.data.use_path = False + elif distribution_type == "FOLLOW_PATH": + mom.data.use_path = True regenerate = will_regenerate(text_properties) # if we regenerate.... delete objects if regenerate and text_properties.get("glyphs"): - for g in text_properties.glyphs: - print(dict(g)) glyph_objects = [g["glyph_object"] for g in text_properties["glyphs"]] completely_delete_objects(glyph_objects, True) text_properties.glyphs.clear() - mom[f"{utils.prefix()}_type"] = "textobject" - mom[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id - mom[f"{utils.prefix()}_font_name"] = text_properties.font_name - mom[f"{utils.prefix()}_face_name"] = text_properties.face_name - mom[f"{utils.prefix()}_font_size"] = text_properties.font_size - mom[f"{utils.prefix()}_letter_spacing"] = text_properties.letter_spacing - mom[f"{utils.prefix()}_orientation"] = text_properties.orientation - mom[f"{utils.prefix()}_translation"] = text_properties.translation - curve_length = get_curve_length(mom) advance = text_properties.offset glyph_advance = 0 - glyph_index = 0 is_command = False previous_spline_index = -1 - for i, c in enumerate(text_properties.text): face = Font.fonts[text_properties.font_name].faces[text_properties.face_name] scalor = face.unit_factor * text_properties.font_size @@ -917,6 +859,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) is_newline = True next_line_advance = get_next_line_advance(mom, advance, glyph_advance) if advance == next_line_advance: + # self.report({'INFO'}, f"would like to add new line for {text_properties.text} please") print( f"would like to add new line for {text_properties.text} please" ) @@ -926,14 +869,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) is_command = False glyph_id = c - spline_index = 0 - - ############### GET GLYPH - - glyph_tmp = Font.get_glyph(text_properties.font_name, - text_properties.face_name, - glyph_id, - -1) + glyph_tmp = Font.get_glyph( + text_properties.font_name, text_properties.face_name, glyph_id + ) if glyph_tmp is None: space_width = Font.is_space(glyph_id) if space_width: @@ -948,7 +886,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) text_properties.font_name, text_properties.face_name, possible_replacement, - -1 ) if glyph_tmp is not None: message = message + f" (replaced with '{possible_replacement}')" @@ -962,85 +899,61 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) ) if not replaced: continue - glyph = glyph_tmp.original - ############### GLYPH PROPERTIES - - glyph_properties = text_properties.glyphs[glyph_index] if not regenerate else text_properties.glyphs.add() - + ob = None + obg = None if regenerate: - glyph_properties["glyph_id"] = glyph_id - glyph_properties["text_id"] = text_properties.text_id - glyph_properties["letter_spacing"] = 0 - - ############### NODE SCENE MANAGEMENT - - inner_node = None - outer_node = None - if regenerate: - outer_node = bpy.data.objects.new(f"{glyph_id}", None) - inner_node = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data) - outer_node[f"{utils.prefix()}_type"] = "glyph" - outer_node[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id - outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index - outer_node[f"{utils.prefix()}_font_name"] = text_properties.font_name - outer_node[f"{utils.prefix()}_face_name"] = text_properties.face_name - - # Add into the scene. - mom.users_collection[0].objects.link(outer_node) - mom.users_collection[0].objects.link(inner_node) - # bpy.context.scene.collection.objects.link(inner_node) - - # Parenting is hard. - inner_node.parent_type = 'OBJECT' - inner_node.parent = outer_node - inner_node.matrix_parent_inverse = outer_node.matrix_world.inverted() - parent_to_curve(outer_node, mom) - - glyph_properties["glyph_object"] = outer_node + ob = bpy.data.objects.new(f"{glyph_id}", None) + ob.hide_viewport = True + obg = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data) + ob[f"{utils.prefix()}_type"] = "glyph" + ob[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id + ob[f"{utils.prefix()}_font_name"] = text_properties.font_name + ob[f"{utils.prefix()}_face_name"] = text_properties.face_name else: - outer_node = glyph_properties.glyph_object - outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index - for c in outer_node.children: + ob = text_properties.glyphs[i].glyph_object + for c in ob.children: if c.name.startswith(f"{glyph_id}_mesh"): - inner_node = c - - ############### TRANSFORMS - - # origins could be shifted - # so we need to apply a pre_advance - glyph_pre_advance, glyph_post_advance = get_glyph_prepost_advances(glyph) - advance += glyph_pre_advance * scalor + obg = c if distribution_type == "FOLLOW_PATH": - outer_node.constraints.new(type="FOLLOW_PATH") - outer_node.constraints["Follow Path"].target = mom - outer_node.constraints["Follow Path"].use_fixed_location = True - outer_node.constraints["Follow Path"].offset_factor = advance / curve_length - outer_node.constraints["Follow Path"].use_curve_follow = True - outer_node.constraints["Follow Path"].forward_axis = "FORWARD_X" - outer_node.constraints["Follow Path"].up_axis = "UP_Y" + 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" spline_index = 0 elif distribution_type == "CALCULATE": - previous_outer_node_rotation_mode = None - previous_inner_node_rotation_mode = None - if outer_node.rotation_mode != "QUATERNION": - outer_node.rotation_mode = "QUATERNION" - previous_outer_node_rotation_mode = outer_node.rotation_mode - if inner_node.rotation_mode != "QUATERNION": - inner_node.rotation_mode = "QUATERNION" - previous_inner_node_rotation_mode = inner_node.rotation_mode + previous_ob_rotation_mode = None + previous_obg_rotation_mode = None + if ob.rotation_mode != "QUATERNION": + ob.rotation_mode = "QUATERNION" + previous_ob_rotation_mode = ob.rotation_mode + if obg.rotation_mode != "QUATERNION": + obg.rotation_mode = "QUATERNION" + previous_obg_rotation_mode = obg.rotation_mode - # get info from bezier - location, tangent, spline_index = calc_point_on_bezier_curve(mom, advance, True, True) - - # check if we are on a new line + location, tangent, spline_index = calc_point_on_bezier_curve( + mom, advance, True, True + ) if spline_index != previous_spline_index: is_newline = True - # position - outer_node.location = location + text_properties.translation + if regenerate: + # ob.location = mom.matrix_world @ ( + # location + text_properties.translation + # ) + ob.location = location + text_properties.translation + mom.users_collection[0].objects.link(obg) + mom.users_collection[0].objects.link(ob) + ob.parent = mom + obg.parent = ob + obg.location = mathutils.Vector((0.0, 0.0, 0.0)) + else: + ob.location = location + text_properties.translation # orientation / rotation mask = [0] @@ -1054,28 +967,26 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) q = mathutils.Quaternion() q.rotate(text_properties.orientation) - outer_node.rotation_quaternion = (motor[0].to_3x3() @ q.to_matrix()).to_quaternion() + ob.rotation_quaternion = (motor[0].to_3x3() @ q.to_matrix()).to_quaternion() + # if regenerate: + # obg.rotation_quaternion = q + # ob.rotation_quaternion = ( + # mom.matrix_world @ motor[0] + # ).to_quaternion() + # else: + # ob.rotation_quaternion = motor[0].to_quaternion() - # # NOTE: supercool but out of scope, as we wouldhave to update it everytime the curve object rotates, - # # but this would ignore the curve objects orientation: - # outer_node.rotation_quaternion = (mom.matrix_world.inverted().to_3x3() @ motor[0].to_3x3() @ q.to_matrix()).to_quaternion() + # NOTE: supercool but out of scope, as we wouldhave to update it everytime the curve object rotates, + # but this would ignore the curve objects orientation: + # ob.rotation_quaternion = (mom.matrix_world.inverted().to_3x3() @ motor[0].to_3x3() @ q.to_matrix()).to_quaternion() - # # scale - outer_node.scale = (scalor, scalor, scalor) + if previous_ob_rotation_mode: + ob.rotation_mode = previous_ob_rotation_mode + if previous_obg_rotation_mode: + obg.rotation_mode = previous_obg_rotation_mode - if previous_outer_node_rotation_mode: - outer_node.rotation_mode = previous_outer_node_rotation_mode - if previous_inner_node_rotation_mode: - inner_node.rotation_mode = previous_inner_node_rotation_mode - - # outer_node.hide_viewport = True - outer_node.hide_set(True) - - ############### PREPARE FOR THE NEXT - - print(f"{glyph_id}: {glyph_properties.letter_spacing=}") glyph_advance = ( - glyph_post_advance * scalor + text_properties.letter_spacing + glyph_properties.letter_spacing + get_glyph_advance(glyph) * scalor + text_properties.letter_spacing ) # now we need to compensate for curvature @@ -1084,7 +995,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) curve_compensation = 0 if distribution_type == "CALCULATE" and ( not is_newline or spline_index == 0 - ): + ): # TODO: fix newline hack if text_properties.compensate_curvature and glyph_advance > 0: previous_location, psi = calc_point_on_bezier_curve( mom, advance, False, True @@ -1114,46 +1025,67 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) output_spline_index=True, ) + ob.scale = (scalor, scalor, scalor) + advance = advance + glyph_advance + curve_compensation - glyph_index += 1 previous_spline_index = spline_index - # NOTE: depsgraph update not locked - # as we fixed data_path with parent_to_curve trick - # if lock_depsgraph_update_n_times < 0: - # lock_depsgraph_update_n_times = len( - # bpy.context.selected_objects - # ) - # else: - # lock_depsgraph_update_n_times += len( - # bpy.context.selected_objects - # ) - # # NOTE: we reset with a timeout, as setting and resetting certain things - # # in fast succession will cause visual glitches (e.g. {}.data.use_path). - # def reset(): - # mom.data.use_path = previous_use_path - # if counted_reset in bpy.app.handlers.depsgraph_update_post: - # bpy.app.handlers.depsgraph_update_post.remove(counted_reset) - # if bpy.app.timers.is_registered(reset): - # bpy.app.timers.unregister(reset) - # molotov = reset_depsgraph_n + 0 - # def counted_reset(scene, depsgraph): - # nonlocal molotov - # if molotov == 0: - # reset() - # else: - # molotov -= 1 - # # unregister previous resets to avoid multiple execution - # if bpy.app.timers.is_registered(reset): - # bpy.app.timers.unregister(reset) - # if counted_reset in bpy.app.handlers.depsgraph_update_post: - # bpy.app.handlers.depsgraph_update_post.remove(counted_reset) - # if not isinstance(reset_timeout_s, bool): - # if reset_timeout_s > 0: - # bpy.app.timers.register(reset, first_interval=reset_timeout_s) - # elif reset_timeout <= 0: - # reset() - # bpy.app.handlers.depsgraph_update_post.append(counted_reset) + if regenerate: + glyph_data = text_properties.glyphs.add() + glyph_data.glyph_id = glyph_id + glyph_data.glyph_object = ob + glyph_data.letter_spacing = 0 + + if regenerate: + mom[f"{utils.prefix()}_type"] = "textobject" + mom[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id + mom[f"{utils.prefix()}_font_name"] = text_properties.font_name + mom[f"{utils.prefix()}_face_name"] = text_properties.face_name + mom[f"{utils.prefix()}_font_size"] = text_properties.font_size + mom[f"{utils.prefix()}_letter_spacing"] = text_properties.letter_spacing + mom[f"{utils.prefix()}_orientation"] = text_properties.orientation + mom[f"{utils.prefix()}_translation"] = text_properties.translation + + if lock_depsgraph_update_n_times < 0: + lock_depsgraph_update_n_times = len( + bpy.context.selected_objects + ) + else: + lock_depsgraph_update_n_times += len( + bpy.context.selected_objects + ) + + # NOTE: we reset with a timeout, as setting and resetting certain things + # in fast succession will cause visual glitches (e.g. {}.data.use_path). + def reset(): + mom.data.use_path = previous_use_path + if counted_reset in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(counted_reset) + if bpy.app.timers.is_registered(reset): + bpy.app.timers.unregister(reset) + + molotov = reset_depsgraph_n + 0 + + def counted_reset(scene, depsgraph): + nonlocal molotov + if molotov == 0: + reset() + else: + molotov -= 1 + + # unregister previous resets to avoid multiple execution + if bpy.app.timers.is_registered(reset): + bpy.app.timers.unregister(reset) + if counted_reset in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(counted_reset) + + if not isinstance(reset_timeout_s, bool): + if reset_timeout_s > 0: + bpy.app.timers.register(reset, first_interval=reset_timeout_s) + elif reset_timeout <= 0: + reset() + + bpy.app.handlers.depsgraph_update_post.append(counted_reset) # endtime = time.perf_counter_ns() # elapsedtime = endtime - starttime @@ -1570,109 +1502,3 @@ def align_metrics_of_objects(objects=None): add_metrics_obj_from_bound_box(t, bound_box) return "" - -def align_origins_to_active_object(objects=None, axis=2): - if objects is None: - objects = bpy.context.selected_objects - if len(objects) == 0: - return "no objects selected" - - if bpy.context.active_object is None: - return "no active object selected" - - reference_origin_position = bpy.context.active_object.matrix_world.translation[axis] - - # do it - for o in objects: - is_possibly_glyph = is_glyph(o) - if is_possibly_glyph and o is not bpy.context.active_object: - if is_mesh(o): - diff = reference_origin_position - o.matrix_world.translation[axis] - - for v in o.data.vertices: - v.co[axis] -= diff - - o.matrix_world.translation[axis] = reference_origin_position - - return "" - -# NOTE: -# Following code is not necessary anymore, -# as we derive the advance through metrics -# boundaries - -# def divide_vectors(v1=mathutils.Vector((1.0,1.0,1.0)), v2=mathutils.Vector((1.0,1.0,1.0))): - # return mathutils.Vector([v1[i] / v2[i] for i in range(3)]) - -# def get_origin_shift_metrics(o, axis=0): - # if not is_metrics_object(o): - # return False - # min_value = sys.float_info.max - # for v in o.data.vertices: - # if v.co[axis] < min_value: - # min_value = v.co[axis] - # if min_value == sys.float_info.max: - # return False - # return min_value - -# def fix_origin_shift_metrics(o, axis=0): - # shift = get_origin_shift_metrics(o) - # if not shift: - # print("False") - # return False - # for v in o.data.vertices: - # v.co[axis] -= shift - # shift_vector = mathutils.Vector((0.0, 0.0, 0.0)) - # shift_vector[axis] = shift - # # o.location = o.location - (divide_vectors(v2=o.matrix_world.to_scale()) * (o.matrix_world @ shift_vector)) - # o.matrix_local.translation = o.matrix_local.translation + (shift_vector @ o.matrix_local.inverted()) - # # update_matrices(o) - # return True - - -# def fix_objects_metrics_origins(objects=None, axis=0, handle_metrics_directly=True): - # if objects is None: - # objects = bpy.context.selected_objects - # if len(objects) == 0: - # return "no objects selected" - - # for o in objects: - # is_possibly_glyph = is_glyph(o) - # if is_possibly_glyph: - # for c in o.children: - # if is_metrics_object(c): - # fix_origin_shift_metrics(c, axis) - # elif is_metrics_object(o) and handle_metrics_directly: - # fix_origin_shift_metrics(o, axis) - # return "" - -# def align_origins_to_metrics(objects=None): - # if objects is None: - # objects = bpy.context.selected_objects - # if len(objects) == 0: - # return "no objects selected" - - # for o in objects: - # is_possibly_glyph = is_glyph(o) - # if is_possibly_glyph: - # min_x = 9999999999 - # for c in o.children: - # if is_metrics_object(c): - # for v in c.data.vertices: - # if v.co[0] < min_x: - # min_x = v.co[0] - - # metrics_origin_x = c.matrix_world.translation[0] + min_x - - # diff = metrics_origin_x - o.matrix_world.translation[0] - - # for v in o.data.vertices: - # v.co[0] -= diff - - # o.location += mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted() - - # for c in o.children: - # if is_metrics_object(c): - # c.location -= mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted() - - # return "" diff --git a/common/Font.py b/common/Font.py index bcb7949..f01c91b 100644 --- a/common/Font.py +++ b/common/Font.py @@ -1,4 +1,6 @@ +from typing import TypedDict from typing import Dict +from dataclasses import dataclass from pathlib import Path # convenience dictionary for translating names to glyph ids @@ -43,9 +45,9 @@ known_misspellings = { "overdot": "dotaccent", "diaresis": "dieresis", "diaeresis": "dieresis", - # different conventions - "doubleacute": "hungarumlaut", # character does not exist.. maybe something else + "Odoubleacute": "Ohungarumlaut", + "Udoubleacute": "Uhungarumlaut", "Wcaron": "Wcircumflex", "Neng": "Nlongrightleg", "Lgrave": "Lacute", @@ -75,13 +77,6 @@ def name_to_glyph(name): return None -def glyph_to_name(glyph_id): - for k in name_to_glyph_d: - if glyph_id == name_to_glyph_d[k]: - return k - return glyph_id - - def is_space(character): for name in space_d: if character == space_d[name][0]: diff --git a/common/utils.py b/common/utils.py index f2fe8d0..0c99dfd 100644 --- a/common/utils.py +++ b/common/utils.py @@ -8,7 +8,7 @@ def get_version_minor(): def get_version_patch(): - return 6 + return 5 def get_version_string(): @@ -22,6 +22,7 @@ def prefix(): import datetime import time +from mathutils import Vector def get_timestamp():