diff --git a/.gitignore b/.gitignore index 19bc9a2..220a137 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # python __pycache__ venv -venv* # vim *.swo diff --git a/__init__.py b/__init__.py index be82bb0..9dc72d6 100644 --- a/__init__.py +++ b/__init__.py @@ -137,12 +137,9 @@ 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] - # ) - t = butils.get_text_properties(self.text_id) - if t is not None: - butils.set_text_on_curve(t) + 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( @@ -262,7 +259,6 @@ class ABC3D_text_properties(bpy.types.PropertyGroup): ) distribution_type: bpy.props.StringProperty() glyphs: bpy.props.CollectionProperty(type=ABC3D_glyph_properties) - actual_text: bpy.props.StringProperty() class ABC3D_data(bpy.types.PropertyGroup): @@ -284,19 +280,17 @@ class ABC3D_data(bpy.types.PropertyGroup): def active_text_index_update(self, context): if self.active_text_index != -1: - text_properties = butils.get_text_properties(self.active_text_index, context.scene) - if text_properties is not None: - o = text_properties.text_object - # active_text_index changed. so let's update the selection - # check if it is already selected - # or perhaps one of the glyphs - if (o is not None - and not o.select_get() - and not len([c for c in o.children if c.select_get()]) > 0 - ): - bpy.ops.object.select_all(action="DESELECT") - o.select_set(True) - context.view_layer.objects.active = o + o = self.available_texts[self.active_text_index].text_object + # active_text_index changed. so let's update the selection + # check if it is already selected + # or perhaps one of the glyphs + if ( + not o.select_get() + and not len([c for c in o.children if c.select_get()]) > 0 + ): + bpy.ops.object.select_all(action="DESELECT") + o.select_set(True) + bpy.context.view_layer.objects.active = o # else: # print("already selected") @@ -457,7 +451,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel): @classmethod def poll(self, context): if ( - context.active_object is not None + type(context.active_object) != type(None) and context.active_object.type == "CURVE" ): self.can_place = True @@ -508,8 +502,8 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): for c in t.text_object.children: if ( len(c.users_collection) > 0 - and not isinstance(c.get(f"{utils.prefix()}_text_id"), None) - and c.get(f"{utils.prefix()}_text_id") == t.text_id + and not isinstance(c.get(f"{utils.prefix()}_linked_textobject"), None) + and c.get(f"{utils.prefix()}_linked_textobject") == t.text_id ): remove_me = False # not sure how to solve this reliably atm, @@ -548,14 +542,14 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): remove_list.append(i) for i in remove_list: - if abc3d_data.available_texts[i].text_object is not None: + if type(abc3d_data.available_texts[i].text_object) != type(None): mom = abc3d_data.available_texts[i].text_object def delif(o, p): if p in o: del o[p] - delif(mom, f"{utils.prefix()}_text_id") + delif(mom, f"{utils.prefix()}_linked_textobject") delif(mom, f"{utils.prefix()}_font_name") delif(mom, f"{utils.prefix()}_face_name") delif(mom, f"{utils.prefix()}_font_size") @@ -742,11 +736,11 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): # and bpy.context.object.select_get(): a_o = bpy.context.active_object if a_o is not None: - if f"{utils.prefix()}_text_id" in a_o: - text_index = a_o[f"{utils.prefix()}_text_id"] + 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 a_o.parent is not None and f"{utils.prefix()}_text_id" in a_o.parent: - text_index = a_o.parent[f"{utils.prefix()}_text_id"] + elif a_o.parent is not None and 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: @@ -758,9 +752,9 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): def get_active_glyph_properties(self): a_o = bpy.context.active_object if a_o is not None: - if (f"{utils.prefix()}_text_id" in a_o + 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()}_text_id"] + 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: @@ -801,13 +795,13 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): @classmethod def poll(self, context): - try: - return self.get_active_text_properties(self) is not None - except IndexError: - return False + return self.get_active_text_properties(self) is not None def draw(self, context): layout = self.layout + wm = context.window_manager + scene = context.scene + abc3d_data = scene.abc3d_data props = self.get_active_text_properties() glyph_props = self.get_active_glyph_properties() @@ -1196,11 +1190,6 @@ class ABC3D_OT_RemoveText(bpy.types.Operator): description="Remove both ABC3D text functionality and the objects/meshes", default=True, ) - remove_custom_properties: bpy.props.BoolProperty( - name="Remove Custom Properties", - description="Remove ABC3D custom properties of objects", - default=True, - ) def invoke(self, context, event): wm = context.window_manager @@ -1217,27 +1206,26 @@ class ABC3D_OT_RemoveText(bpy.types.Operator): return {"CANCELLED"} i = abc3d_data.active_text_index - if abc3d_data.available_texts[i].text_object is not None: + if type(abc3d_data.available_texts[i].text_object) != type(None): mom = abc3d_data.available_texts[i].text_object - if self.remove_custom_properties: - def delif(o, p): - if p in o: - del o[p] + def delif(o, p): + if p in o: + del o[p] - delif(mom, f"{utils.prefix()}_type") - delif(mom, f"{utils.prefix()}_text_id") - delif(mom, f"{utils.prefix()}_font_name") - delif(mom, f"{utils.prefix()}_face_name") - delif(mom, f"{utils.prefix()}_font_size") - delif(mom, f"{utils.prefix()}_letter_spacing") - delif(mom, f"{utils.prefix()}_orientation") - delif(mom, f"{utils.prefix()}_translation") - delif(mom, f"{utils.prefix()}_offset") + delif(mom, f"{utils.prefix()}_type") + delif(mom, f"{utils.prefix()}_linked_textobject") + delif(mom, f"{utils.prefix()}_font_name") + delif(mom, f"{utils.prefix()}_face_name") + delif(mom, f"{utils.prefix()}_font_size") + delif(mom, f"{utils.prefix()}_letter_spacing") + delif(mom, f"{utils.prefix()}_orientation") + delif(mom, f"{utils.prefix()}_translation") + delif(mom, f"{utils.prefix()}_offset") if self.remove_objects: remove_list = [] for g in abc3d_data.available_texts[i].glyphs: - if g is not None: + if type(g) != type(None): remove_list.append(g.glyph_object) butils.simply_delete_objects(remove_list) @@ -1797,44 +1785,20 @@ def compare_text_object_with_object(t, o, strict=False): # if return True -def link_text_object_with_new_text_properties(text_object, scene = None): - lock_depsgraph_updates(auto_unlock_s=-1) - butils.link_text_object_with_new_text_properties(text_object, scene) - unlock_depsgraph_updates() - def detect_text(): - lock_depsgraph_updates(auto_unlock_s=-1) scene = bpy.context.scene abc3d_data = scene.abc3d_data - required_keys = [ - "type", - "text_id", - "font_name", - "face_name", - "text", - ] - objects = scene.objects - for o in objects: - valid = True - for key in required_keys: - if butils.get_key(key) not in o: - valid = False - break - if not valid: - continue - if o[butils.get_key("type")] == "textobject": - current_text_id = int(o[butils.get_key("text_id")]) - text_properties = butils.get_text_properties(current_text_id) + for o in scene.objects: + if o[f"{utils.prefix()}_type"] == "textobject": + linked_textobject = int(o[f"{utils.prefix()}_linked_textobject"]) if ( - text_properties is not None - and text_properties.text_object == o + len(abc3d_data.available_texts) > linked_textobject + and abc3d_data.available_texts[linked_textobject].text_object == o ): - # all good - pass - else: - butils.link_text_object_with_new_text_properties(o, scene) - unlock_depsgraph_updates() + t = abc3d_data.available_texts[linked_textobject] + a = test_availability(o["font_name"], o["face_name"], o["text"]) + butils.transfer_blender_object_to_text_properties(o, t) def load_used_glyphs(): @@ -1886,50 +1850,51 @@ def on_frame_changed(self, dummy): butils.set_text_on_curve(t) -depsgraph_updates_locked = 0 +depsgraph_updates_locked = False def unlock_depsgraph_updates(): global depsgraph_updates_locked - depsgraph_updates_locked -= 1 + depsgraph_updates_locked = False -def lock_depsgraph_updates(auto_unlock_s=1): +def lock_depsgraph_updates(): global depsgraph_updates_locked - depsgraph_updates_locked += 1 - if auto_unlock_s >= 0: - if bpy.app.timers.is_registered(unlock_depsgraph_updates): - bpy.app.timers.unregister(unlock_depsgraph_updates) - bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s) + depsgraph_updates_locked = True + if bpy.app.timers.is_registered(unlock_depsgraph_updates): + bpy.app.timers.unregister(unlock_depsgraph_updates) + bpy.app.timers.register(unlock_depsgraph_updates, first_interval=1) -def are_depsgraph_updates_locked(): - global depsgraph_updates_locked - return depsgraph_updates_locked > 0 import time + @persistent def on_depsgraph_update(scene, depsgraph): - if not bpy.context.mode.startswith("EDIT") and not are_depsgraph_updates_locked(): + global depsgraph_updates_locked + if not bpy.context.mode.startswith("EDIT") and not depsgraph_updates_locked: for u in depsgraph.updates: if ( - butils.get_key("text_id") in u.id.keys() - and butils.get_key("type") in u.id.keys() - and u.id[butils.get_key("type")] == "textobject" + f"{utils.prefix()}_linked_textobject" in u.id.keys() + and f"{utils.prefix()}_type" in u.id.keys() + and u.id[f"{utils.prefix()}_type"] == "textobject" ): - text_id = u.id[butils.get_key("text_id")] - # if u.is_updated_geometry: - text_properties = butils.get_text_properties(text_id) - if text_properties is not None: - if text_properties.text_object == u.id.original: - # nothing to do - pass - else: - # must be duplicate - link_text_object_with_new_text_properties(u.id.original, scene) - else: - # must be new thing - link_text_object_with_new_text_properties(u.id.original, scene) + linked_textobject = u.id[f"{utils.prefix()}_linked_textobject"] + if ( + u.is_updated_geometry + and len(scene.abc3d_data.available_texts) > linked_textobject + ): + lock_depsgraph_updates() + + def later(): + if butils.lock_depsgraph_update_n_times <= 0: + butils.set_text_on_curve( + scene.abc3d_data.available_texts[linked_textobject] + ) + elif butils.lock_depsgraph_update_n_times <= 0: + butils.lock_depsgraph_update_n_times -= 1 + + butils.run_in_main_thread(later) def register(): @@ -1960,7 +1925,6 @@ def register(): butils.run_in_main_thread(butils.update_available_fonts) butils.run_in_main_thread(load_used_glyphs) butils.run_in_main_thread(butils.update_types) - butils.run_in_main_thread(detect_text) Font.init() diff --git a/butils.py b/butils.py index 138ed9f..6663a1a 100644 --- a/butils.py +++ b/butils.py @@ -52,9 +52,6 @@ def get_parent_collection_names(collection, parent_names): get_parent_collection_names(parent_collection, parent_names) return -def get_key(key): - return f"{utils.prefix()}_{key}" - # Ensure it's a curve object # TODO: no raising, please @@ -559,8 +556,8 @@ def update_available_fonts(): # def update_available_texts(): # abc3d_data = bpy.context.scene.abc3d_data # for o in bpy.context.scene.objects: -# if "text_id" in o.keys(): -# i = o["text_id"] +# if "linked_textobject" in o.keys(): +# i = o["linked_textobject"] # found = False # if len(abc3d_data.available_texts) > i: # if abc3d_data.available_texts[i].glyphs @@ -695,7 +692,7 @@ def is_text_object(o): return False -def is_glyph_object(o): +def is_glyph(o): if f"{utils.prefix()}_type" in o: return o[f"{utils.prefix()}_type"] == "glyph" try: @@ -709,10 +706,6 @@ def is_glyph_object(o): return False -def is_glyph(o): - return is_glyph_object(o) - - def update_types(): scene = bpy.context.scene abc3d_data = scene.abc3d_data @@ -772,15 +765,6 @@ def prepare_text(font_name, face_name, text, allow_replacement=True): load_font_from_filepath(filepath, loadable, font_name, face_name) return True -def predict_actual_text(text_properties): - availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) - AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) - t_text = text_properties.text - for c in availability["missing"]: - t_text = t_text.replace(c, "") - for c in AVAILABILITY["missing"]: - t_text = t_text.replace(c, "") - return t_text def is_bezier(curve): if curve.type != "CURVE": @@ -793,346 +777,10 @@ def is_bezier(curve): return True -text_object_keys = [ - "font_name", - "face_name", - "type", - "text_id", - "font_size", - "letter_spacing", - "distribution_type", - "orientation", - "translation", - "offset", - "text", -] - -glyph_object_keys = [ - "type", - "glyph_index", - "glyph_id", - "text_id", - "font_name", - "face_name", - "font_size", - "letter_spacing", - "alternate", -] - -ignore_keys_in_text_object_comparison = [ - "type", -] - -ignore_keys_in_glyph_object_comparison = [ - "type", - "glyph_index", - "font_name", - "face_name", - "text_id", -] - -ignore_keys_in_glyph_object_transfer = [ - "type", - "text_id", - "glyph_index", -] - -keys_trigger_regeneration = [ - "font_name", - "face_name", -] - -COMPARE_TEXT_OBJECT_SAME = 0 -COMPARE_TEXT_OBJECT_DIFFER = 1 -COMPARE_TEXT_OBJECT_REGENERATE = 2 - -def find_free_text_id(): - scene = bpy.context.scene - abc3d_data = scene.abc3d_data - text_id = 0 - found_free = False - while not found_free: - occupied = False - for t in abc3d_data.available_texts: - if text_id == t.text_id: - occupied = True - if occupied: - text_id += 1 - else: - found_free = True - return text_id - -def compare_text_properties_to_text_object(text_properties, o): - for key in text_object_keys: - if key in ignore_keys_in_text_object_comparison: - continue - object_key = get_key(key) - text_property = text_properties[key] if key in text_properties else getattr(text_properties, key) - text_object_property = o[object_key] if object_key in o else False - if text_property != text_object_property: - if key in keys_trigger_regeneration: - return COMPARE_TEXT_OBJECT_REGENERATE - elif key in ["translation", "orientation"]: - if ( - text_property[0] != text_object_property[0] or - text_property[1] != text_object_property[1] or - text_property[2] != text_object_property[2]): - return COMPARE_TEXT_OBJECT_DIFFER - # else same - else: - return COMPARE_TEXT_OBJECT_DIFFER - # else same - return COMPARE_TEXT_OBJECT_SAME - - -def transfer_text_properties_to_text_object(text_properties, o): - for key in text_object_keys: - if key in ignore_keys_in_text_object_comparison: - continue - object_key = get_key(key) - text_property = text_properties[key] if key in text_properties else getattr(text_properties, key) - o[object_key] = text_property - o[get_key("type")] = "textobject" - - -def get_glyph(glyph_id, font_name, face_name, notify_on_replacement=False): - glyph_tmp = Font.get_glyph(font_name, - face_name, - glyph_id, - -1) - if glyph_tmp is None: - space_width = Font.is_space(glyph_id) - if space_width: - return space_width - - message = f"Glyph not found for font_name='{text_properties.font_name}' face_name='{text_properties.face_name}' glyph_id='{glyph_id}'" - replaced = False - if glyph_id.isalpha(): - possible_replacement = glyph_id.swapcase() - glyph_tmp = Font.get_glyph( - text_properties.font_name, - text_properties.face_name, - possible_replacement, - -1 - ) - if glyph_tmp is not None: - message = message + f" (replaced with '{possible_replacement}')" - replaced = True - - if notify_on_replacement: - ShowMessageBox( - title="Glyph replaced" if replaced else "Glyph missing", - icon="INFO" if replaced else "ERROR", - message=message, - prevent_repeat=True, - ) - if not replaced: - return None - - return glyph_tmp.original - -def get_text_properties(text_id, scene = None): - if scene is None: - scene = bpy.context.scene - abc3d_data = scene.abc3d_data - for t in abc3d_data.available_texts: - if text_id == t.text_id: - return t - return None - -def duplicate(obj, data=True, actions=True, add_to_collection=True, collection=None, recursive=True): - obj_copy = obj.copy() - if add_to_collection: - if collection: - collection.objects.link(obj_copy) - elif len(obj.users_collection) > 0: - obj.users_collection[0].objects.link(obj_copy) - if data and obj.data: - obj_copy.data = obj.data.copy() - if actions and obj.animation_data: - obj_copy.animation_data.action = obj.animation_data.action.copy() - if recursive and hasattr(obj, "children"): - for child in obj.children: - child_copy = duplicate(child) - child_copy.parent_type = child.parent_type - child_copy.parent = obj_copy - # child_copy.matrix_parent_inverse = obj_copy.matrix_world.inverted() - return obj_copy - -def transfer_text_object_to_text_properties(text_object, text_properties, id_from_text_properties=True): - possible_brother_text_id = text_object[get_key("text_id")] if get_key("text_id") in text_object else "" - for key in text_object_keys: - if key in ignore_keys_in_text_object_comparison: - continue - object_key = get_key(key) - if id_from_text_properties and key == "text_id": - text_object[object_key] = text_properties["text_id"] - else: - text_object_property = text_object[object_key] if object_key in text_object else False - if text_object_property is not False: - text_properties[key] = text_object_property - - if len(text_object.children) == 0: - if possible_brother_text_id != text_properties["text_id"] and possible_brother_text_id != "": - possible_brother_properties = get_text_properties(possible_brother_text_id) - possible_brother_object = possible_brother_properties.text_object - if possible_brother_object is not None: - for child in possible_brother_object.children: - if is_glyph_object(child): - child_copy = duplicate(child) - child_copy.parent_type = child.parent_type - child_copy.parent = text_object - parent_to_curve(child_copy, text_object) - # child_copy.matrix_parent_inverse = text_object.matrix_world.inverted() - - found_reconstructable_glyphs = False - glyph_objects_with_indices = [] - required_keys = [ - "glyph_index", - "glyph_id", - "type" - ] - for glyph_object in text_object.children: - if is_glyph_object(glyph_object): - has_required_keys = True - for key in required_keys: - if get_key(key) not in glyph_object: - has_required_keys = False - if has_required_keys: - inner_node = None - glyph_id = glyph_object[get_key("glyph_id")] - for c in glyph_object.children: - if c.name.startswith(f"{glyph_id}_mesh"): - inner_node = c - if inner_node is not None: - glyph_objects_with_indices.append(glyph_object) - - glyph_objects_with_indices.sort(key=lambda g: g[get_key("glyph_index")]) - text = "" - for g in glyph_objects_with_indices: - text += g[get_key("glyph_id")] - is_good_text = False - if len(text) > 0: - if text == text_properties.text: - is_good_text = True - else: - availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) - AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) - t_text = text_properties.text - for c in availability["missing"]: - t_text = t_text.replace(c, "") - for c in AVAILABILITY["missing"]: - t_text = t_text.replace(c, "") - if len(t_text) == len(text): - is_good_text = True - if is_good_text: - text_properties.actual_text = text - text_properties.glyphs.clear() - prepare_text(text_properties.font_name, text_properties.face_name, text) - fail_after_all = False - for glyph_index, glyph_object in enumerate(glyph_objects_with_indices): - glyph_id = glyph_object[get_key("glyph_id")] - # glyph_tmp = Font.get_glyph(text_properties.font_name, - # text_properties.face_name, - # glyph_id) - # glyph = glyph_tmp.original - glyph_properties = text_properties.glyphs.add() - - transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties) - glyph_properties["glyph_object"] = glyph_object - glyph_properties["glyph_index"] = glyph_index - inner_node = None - for c in glyph_object.children: - if c.name.startswith(f"{glyph_id}_mesh"): - inner_node = c - if inner_node is None: - fail_after_all = True - pass - glyph_properties["glyph_object"] = glyph_object - if not fail_after_all: - found_reconstructable_glyphs = True - - if not found_reconstructable_glyphs: - text_properties.actual_text = "" - text_properties.glyphs.clear() - unfortunate_children = text_object.children - completely_delete_objects(unfortunate_children) - def kill_children(): - completely_delete_objects(unfortunate_children) - run_in_main_thread(kill_children) - - if "font_name" in text_properties and "face_name" in text_properties: - font_name = text_properties["font_name"] - face_name = text_properties["face_name"] - text_properties.font = f"{font_name} {face_name}" - - -def link_text_object_with_new_text_properties(text_object, scene=None): - if scene is None: - scene = bpy.context.scene - text_id = find_free_text_id() - text_properties = scene.abc3d_data.available_texts.add() - text_properties["text_id"] = text_id - # text_object[get_key("text_id")] = text_id - prepare_text(text_object[get_key("font_name")], - text_object[get_key("face_name")], - text_object[get_key("text")]) - text_properties.text_object = text_object - transfer_text_object_to_text_properties(text_object, text_properties) - - -def test_finding(): - scene = bpy.context.scene - abc3d_data = scene.abc3d_data - text_id = find_free_text_id() - t = abc3d_data.available_texts.add() - t["text_id"] = text_id - o = bpy.context.active_object - transfer_text_object_to_text_properties(o, t) - -# def detect_texts(): - # scene = bpy.context.scene - # abc3d_data = scene.abc3d_data - # for o in bpy.data.objects: - # if get_key("type") in o \ - # and o[get_key("type") == "textobject" \ - # and o[get_key("t - - - -def link_text_object_and_text_properties(o, text_properties): - text_id = text_properties.text_id - o["text_id"] = text_id - text_properties.textobject = o - -def get_glyph_object_property(text_properties, glyph_properties, key): - if key in glyph_properties: - return glyph_properties[key] - if hasattr(glyph_properties, key): - return getattr(glyph_properties, key) - return text_properties[key] if key in text_properties else getattr(text_properties, key) - -def transfer_properties_to_glyph_object(text_properties, glyph_properties, glyph_object): - for key in glyph_object_keys: - if key in ignore_keys_in_glyph_object_transfer: - continue - object_key = get_key(key) - glyph_object[object_key] = get_glyph_object_property(text_properties, glyph_properties, key) - glyph_object[get_key("type")] = "glyph" - glyph_object[get_key("text_id")] = text_properties["text_id"] - -def transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties): - for key in glyph_object_keys: - if key in ignore_keys_in_glyph_object_transfer: - continue - glyph_properties[key] = glyph_object[get_key(key)] - glyph_properties["text_id"] = glyph_object[get_key("text_id")] - def would_regenerate(text_properties): - predicted_text = predict_actual_text(text_properties) - if text_properties.actual_text != predicted_text: - return True - if len(text_properties.glyphs) == 0: + mom = text_properties.text_object + + if len(text_properties.text) != len(text_properties.glyphs): return True for i, g in enumerate(text_properties.glyphs): @@ -1141,9 +789,9 @@ def would_regenerate(text_properties): elif g.glyph_object.type != "EMPTY": return True # check if perhaps one glyph was deleted - elif g.glyph_object is None: + elif type(g.glyph_object) == type(None): return True - elif g.glyph_object.parent is None: + elif type(g.glyph_object.parent) == type(None): return True elif g.glyph_object.parent.users_collection != g.glyph_object.users_collection: return True @@ -1163,7 +811,7 @@ def update_matrices(obj): if obj.parent is None: obj.matrix_world = obj.matrix_basis - # else: + else: obj.matrix_world = obj.parent.matrix_world * \ obj.matrix_parent_inverse * \ obj.matrix_basis @@ -1195,7 +843,6 @@ def parent_to_curve(o, c): 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, can_regenerate=False): """set_text_on_curve @@ -1219,7 +866,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, return False 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 @@ -1237,11 +883,20 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, # 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() - transfer_text_properties_to_text_object(text_properties, mom) + 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 @@ -1250,7 +905,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, is_command = False previous_spline_index = -1 - actual_text = "" 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 @@ -1320,7 +974,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, glyph_properties["glyph_id"] = glyph_id glyph_properties["text_id"] = text_properties.text_id glyph_properties["letter_spacing"] = 0 - actual_text += glyph_id ############### NODE SCENE MANAGEMENT @@ -1329,11 +982,16 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, if regenerate: outer_node = bpy.data.objects.new(f"{glyph_id}", None) inner_node = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data) - transfer_properties_to_glyph_object(text_properties, glyph_properties, outer_node) + 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' @@ -1343,7 +1001,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, outer_node.hide_set(True) glyph_properties["glyph_object"] = outer_node - outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index else: outer_node = glyph_properties.glyph_object outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index @@ -1471,15 +1128,52 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, glyph_index += 1 previous_spline_index = spline_index - if regenerate: - text_properties["actual_text"] = actual_text + # 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) + + # endtime = time.perf_counter_ns() + # elapsedtime = endtime - starttime return True verification_object = { f"{utils.prefix()}_type": "textobject", - f"{utils.prefix()}_text_id": 0, + f"{utils.prefix()}_linked_textobject": 0, f"{utils.prefix()}_font_name": "font_name", f"{utils.prefix()}_face_name": "face_name", f"{utils.prefix()}_font_size": 42, @@ -1493,6 +1187,28 @@ def verify_text_object(o): pass +def transfer_text_properties_to_text_object(text_properties, o): + o[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id + o[f"{utils.prefix()}_font_name"] = text_properties.font_name + o[f"{utils.prefix()}_face_name"] = text_properties.face_name + o[f"{utils.prefix()}_font_size"] = text_properties.font_size + o[f"{utils.prefix()}_letter_spacing"] = text_properties.letter_spacing + o[f"{utils.prefix()}_orientation"] = text_properties.orientation + o[f"{utils.prefix()}_translation"] = text_properties.translation + o[f"{utils.prefix()}_text"] = text_properties["text"] + + +def transfer_text_object_to_text_properties(o, text_properties): + text_properties["text_id"] = o[f"{utils.prefix()}_linked_textobject"] + text_properties["font_name"] = o[f"{utils.prefix()}_font_name"] + text_properties["face_name"] = o[f"{utils.prefix()}_face_name"] + text_properties["font_size"] = o[f"{utils.prefix()}_font_size"] + text_properties["letter_spacing"] = o[f"{utils.prefix()}_letter_spacing"] + text_properties["orientation"] = o[f"{utils.prefix()}_orientation"] + text_properties["translation"] = o[f"{utils.prefix()}_translation"] + text_properties["text"] = o[f"{utils.prefix()}_text"] + + # blender bound_box vertices # # 3------7. @@ -1970,4 +1686,3 @@ def align_origins_to_active_object(objects=None, axis=2): # c.location -= mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted() # return "" - diff --git a/common/spacesUnicode.txt b/common/spacesUnicode.txt index b7270e0..da6a7c9 100644 --- a/common/spacesUnicode.txt +++ b/common/spacesUnicode.txt @@ -4,20 +4,20 @@ space 0020 0.25 nbspace 00A0 0.25 # ethi:wordspace 1361 # NOTE: has shape enquad 2000 0.5 -emquad 2001 1.0 +emquad 2001 1 enspace 2002 0.5 -emspace 2003 1.0 -threeperemspace 2004 3.0 -fourperemspace 2005 4.0 -sixperemspace 2006 6.0 -figurespace 2007 1.0 -punctuationspace 2008 1.0 +emspace 2003 1 +threeperemspace 2004 3 +fourperemspace 2005 4 +sixperemspace 2006 6 +figurespace 2007 1 +punctuationspace 2008 1 thinspace 2009 0.1 hairspace 200A 0.05 -zerowidthspace 200B 0.0 +zerowidthspace 200B 0 narrownobreakspace 202F 0.1 -mediummathematicalspace 205F 1.0 +mediummathematicalspace 205F 1 cntr:space 2420 0.25 -ideographicspace 3000 1.0 +ideographicspace 3000 1 # ideographichalffillspace 303F # NOTE: has shape -zerowidthnobreakspace FEFF 0.0 +zerowidthnobreakspace FEFF 0