From 94f4e28605b77c8a5eb9fef19439cf12d94a4538 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Wed, 14 Aug 2024 10:50:57 +0200 Subject: [PATCH] dealing with multiple fonts --- README.md | 10 ++++ __init__.py | 142 ++++++++++++++++++++++++++++++++++++++++++++++------ butils.py | 75 +++++++++++++++++++++------ 3 files changed, 195 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index a89154c..95d5887 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,15 @@ bpy.utils.script_paths() ``` then check it for the `addons` directory +# addons dir: +``` +~/git/tools/blender_git/build_linux_v4.1/bin/4.1/scripts/addons/ +``` + +# addon data: +``` +~/.config/blender/4.1/datafiles +``` + # reload addon in blender: F3 -> "reload scripts" diff --git a/__init__.py b/__init__.py index ccce617..1e14201 100644 --- a/__init__.py +++ b/__init__.py @@ -149,11 +149,32 @@ class FONT3D_glyph_properties(bpy.types.PropertyGroup): ) class FONT3D_text_properties(bpy.types.PropertyGroup): + def font_name_items(self, context): + out = [] + for f in Font.fonts.keys(): + out.append((f, f, "A Font")) + return tuple(out) + def face_name_items(self, context): + out = [] + for ff in Font.fonts[self.font_lol].faces.keys(): + out.append((ff, ff, "A Face")) + return tuple(out) + def font_name_update_callback(self, context): + self.face_lol = Font.fonts[self.font_lol].faces.keys()[0] + update_callback(self, context) def update_callback(self, context): butils.set_text_on_curve(self) text_id: bpy.props.IntProperty() font_name: bpy.props.StringProperty() face_name: bpy.props.StringProperty() + font_lol: bpy.props.EnumProperty( + items=font_name_items, + update=font_name_update_callback + ) + face_lol: bpy.props.EnumProperty( + items=face_name_items, + update=update_callback + ) text_object: bpy.props.PointerProperty(type=bpy.types.Object) text: bpy.props.StringProperty( update=update_callback @@ -550,8 +571,11 @@ class FONT3D_OT_TemporaryHelper(bpy.types.Operator): scene = bpy.context.scene font3d_data = scene.font3d_data - objects = bpy.context.selected_objects - butils.add_default_metrics_to_objects(objects) + # butils.load_font_from_filepath("/home/jrkb/.config/blender/4.1/datafiles/font3d/fonts/NM_Origin.glb") + butils.update_available_fonts() + + # objects = bpy.context.selected_objects + # butils.add_default_metrics_to_objects(objects) # reference_bound_box = None # for o in objects: # bb = o.bound_box @@ -642,6 +666,24 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator): bl_idname = f"{__name__}.save_font_to_file" bl_label = "Save Font" bl_options = {'REGISTER', 'UNDO'} + + save_path: bpy.props.StringProperty(name="save_path", subtype="DIR_PATH") + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + # def draw(self, contex): + # layout = self.layout + # layout.prop(self, 'font_name') + # layout.prop(self, 'face_name') + # layout.prop(self, 'import_infix') + # layout.prop(self, 'fix_common_misspellings') + # for k in Font.known_misspellings: + # character = "" + # if Font.known_misspellings[k] in Font.name_to_glyph_d: + # character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})" + # layout.label(text=f"{k} -> {Font.known_misspellings[k]}{character}") def execute(self, context): global shared @@ -752,6 +794,9 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): import_infix: bpy.props.StringProperty( default="_NM_Origin_Tender", ) + autodetect_names: bpy.props.BoolProperty( + default=True, + ) fix_common_misspellings: bpy.props.BoolProperty( default=True, ) @@ -762,17 +807,50 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): def draw(self, contex): layout = self.layout - layout.prop(self, 'font_name') - layout.prop(self, 'face_name') - layout.prop(self, 'import_infix') + row = layout.row() + row.prop(self, 'autodetect_names') + if self.autodetect_names: + scale_y = 0.5 + row = layout.row(); row.scale_y = scale_y + row.label(text="Watch out, follow convention in naming your meshes:") + row = layout.row(); row.scale_y = scale_y + row.label(text="'__'") + row = layout.row(); row.scale_y = scale_y + row.label(text=" - glyph id: unicode glyph name or raw glyph") + row = layout.row(); row.scale_y = scale_y + row.label(text=" - font name: font name with underscore") + row = layout.row(); row.scale_y = scale_y + row.label(text=" - face name: face name") + row = layout.row(); row.scale_y = scale_y + row.label(text="working examples:") + row = layout.row(); row.scale_y = scale_y + row.label(text="- 'A_NM_Origin_Tender'") + row = layout.row(); row.scale_y = scale_y + row.label(text="- 'B_NM_Origin_Tender'") + row = layout.row(); row.scale_y = scale_y + row.label(text="- 'arrowright_NM_Origin_Tender'") + row = layout.row(); row.scale_y = scale_y + row.label(text="- '→_NM_Origin_Tender' (equal to above)") + row = layout.row(); row.scale_y = scale_y + row.label(text="- 'quotesingle_NM_Origin_Tender.001'") + row = layout.row(); row.scale_y = scale_y + row.label(text="- 'colon_NM_Origin_Tender_2'") + box = layout.box() + box.enabled = not self.autodetect_names + box.prop(self, 'font_name') + box.prop(self, 'face_name') + box.prop(self, 'import_infix') layout.prop(self, 'fix_common_misspellings') - for k in Font.known_misspellings: - character = "" - if Font.known_misspellings[k] in Font.name_to_glyph_d: - character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})" - layout.label(text=f"{k} -> {Font.known_misspellings[k]}{character}") + if self.fix_common_misspellings: + for k in Font.known_misspellings: + character = "" + if Font.known_misspellings[k] in Font.name_to_glyph_d: + character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})" + row = layout.row(); row.scale_y = 0.5 + row.label(text=f"{k} → {Font.known_misspellings[k]}{character}") def execute(self, context): + print(f"executing {self.bl_idname}") global shared scene = bpy.context.scene font3d = scene.font3d @@ -791,20 +869,30 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): font_name = self.font_name face_name = self.face_name - added_font = False - # TODO: do not clear # font3d_data.available_fonts.clear() # Font.fonts = {} currentObjects = [] for o in context.selected_objects: if o.name not in currentObjects: - if font3d.import_infix in o.name and not butils.is_metrics_object(o): + print(f"processing {o.name}") + process_object = True + if self.autodetect_names: + ifxsplit = o.name.split('_') + if len(ifxsplit) < 4: + print(f"whoops name could not be autodetected {o.name}") + continue + font_name = f"{ifxsplit[1]}_{ifxsplit[2]}" + face_name = ifxsplit[3] + + if butils.is_mesh(o) and not butils.is_metrics_object(o): uc = o.users_collection - regex = f"{font3d.import_infix}(.)*" + # regex = f"{font3d.import_infix}(.)*" if self.fix_common_misspellings: o.name = Font.fix_glyph_name_misspellings(o.name) - name = re.sub(regex, "", o.name) + # name = re.sub(regex, "", o.name) + # glyph_id = Font.name_to_glyph(name) + name = o.name.split('_')[0] glyph_id = Font.name_to_glyph(name) if type(glyph_id )!= type(None): @@ -820,7 +908,6 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): face_name, glyph_id, o) - added_font = True #TODO: is there a better way to iterate over a CollectionProperty? found = False @@ -895,6 +982,26 @@ class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel): if is_glyph: layout.row().label(text="Glyph Properties:") +class FONT3D_OT_Reporter(bpy.types.Operator): + bl_idname = f"{__name__}.reporter" + bl_label = "Report" + + label = bpy.props.StringProperty( + name="label", + default="INFO", + ) + message = bpy.props.StringProperty( + name="message", + default="I have nothing to say really", + ) + + def execute(self, context): + #this is where I send the message + self.report({'INFO'}, 'whatever') + print("lalala reporter") + for i in range(0,10): + butils.ShowMessageBox('whatever','INFO','INFO') + return {'FINISHED'} classes = ( FONT3D_addonPreferences, @@ -923,12 +1030,14 @@ classes = ( FONT3D_OT_SaveFontToFile, FONT3D_OT_CreateFontFromObjects, FONT3D_PT_RightPropertiesPanel, + FONT3D_OT_Reporter, ) @persistent def load_handler(self, dummy): if not bpy.app.timers.is_registered(butils.execute_queued_functions): bpy.app.timers.register(butils.execute_queued_functions) + butils.run_in_main_thread(butils.update_available_fonts) def load_handler_unload(): if bpy.app.timers.is_registered(butils.execute_queued_functions): @@ -963,6 +1072,7 @@ def register(): butils.run_in_main_thread(butils.clear_available_fonts) butils.run_in_main_thread(butils.load_available_fonts) + butils.run_in_main_thread(butils.update_available_fonts) Font.name_to_glyph_d = Font.generate_name_to_glyph_d() diff --git a/butils.py b/butils.py index 1f230f4..9a5bba2 100644 --- a/butils.py +++ b/butils.py @@ -4,6 +4,8 @@ import queue import importlib import os import re +from multiprocessing import Process + # import time # for debugging performance # then import dependencies for our addon @@ -381,12 +383,18 @@ def load_font_from_filepath(filepath): return False font3d_data = bpy.context.scene.font3d_data + for f in bpy.context.scene.font3d_data.available_fonts.values(): + print(f"inside available font: {f.font_name} {f.face_name}") allObjectsBefore = [] for ob in bpy.data.objects: allObjectsBefore.append(ob.name) bpy.ops.import_scene.gltf(filepath=filepath) + print(f"after import available fonts:") + for f in bpy.context.scene.font3d_data.available_fonts.values(): + print(f"after import available font: {f.font_name} {f.face_name}") + fontcollection = bpy.data.collections.get("Font3D") if fontcollection is None: fontcollection = bpy.data.collections.new("Font3D") @@ -395,6 +403,8 @@ def load_font_from_filepath(filepath): all_objects = [] for o in bpy.data.objects: all_objects.append(o) + for f in bpy.context.scene.font3d_data.available_fonts.values(): + print(f"before loop available font: {f.font_name} {f.face_name}") for o in all_objects: if o.name not in allObjectsBefore: # must be new @@ -404,9 +414,13 @@ def load_font_from_filepath(filepath): and not ("type" in o.keys() and o["type"] == "metrics") and not is_metrics_object(o) ): + for f in bpy.context.scene.font3d_data.available_fonts.values(): + print(f"super inside available font: {f.font_name} {f.face_name}") glyph_id = o["glyph"] font_name = o["font_name"] face_name = o["face_name"] + ShowMessageBox("Loading Font", "INFO", f"adding glyph {glyph_id} for {font_name} {face_name}") + print(f"adding glyph {glyph_id} for {font_name} {face_name}") glyph_obj = move_in_fontcollection( o, fontcollection) @@ -422,21 +436,47 @@ def load_font_from_filepath(filepath): if glyph_obj != o: remove_list.append(o) - found = False - for f in font3d_data.available_fonts.values(): - if f.font_name == font_name and f.face_name == face_name: - found = True - break - if not found: - f = font3d_data.available_fonts.add() - f.font_name = font_name - f.face_name = face_name - print(f"{__name__} added {font_name} {face_name}") + # found = False + # for f in font3d_data.available_fonts.values(): + # print(f"has in availables {f.font_name} {f.face_name}") + # if f.font_name == font_name and f.face_name == face_name: + # found = True + # break + # if not found: + # f = font3d_data.available_fonts.add() + # f.font_name = font_name + # f.face_name = face_name + # print(f"{__name__} added {font_name} {face_name}") else: remove_list.append(o) for o in remove_list: bpy.data.objects.remove(o, do_unlink=True) - print(f"{__name__}: loaded fonts") + print(f"{__name__}: loaded font from {filepath}") + update_available_fonts() + +def update_available_fonts(): + font3d_data = bpy.context.scene.font3d_data + + for font_name in Font.fonts.keys(): + for face_name in Font.fonts[font_name].faces.keys(): + found = False + for f in font3d_data.available_fonts.values(): + if font_name == f.font_name and face_name == f.face_name: + found = True + if not found: + f = font3d_data.available_fonts.add() + f.font_name = font_name + f.face_name = face_name + print("{__name__} added {font_name} {face_name}") + +def update_available_texts(): + font3d_data = bpy.context.scene.font3d_data + for o in bpy.context.scene.objects: + if "linked_textobject" in o.keys(): + i = o["linked_textobject"] + found = False + if len(font3d_data.available_texts) > i: + if font3d_data.available_texts[i].glyphs def getPreferences(context): preferences = context.preferences @@ -447,17 +487,17 @@ def clear_available_fonts(): bpy.context.scene.font3d_data.available_fonts.clear() def load_available_fonts(): + preferences = getPreferences(bpy.context) - - currentObjects = [] - for ob in bpy.data.objects: - currentObjects.append(ob.name) - print(f"assets folder: {preferences.assets_dir}") font_dir = f"{preferences.assets_dir}/fonts" for file in os.listdir(font_dir): if file.endswith(".glb") or file.endswith(".gltf"): font_path = os.path.join(font_dir, file) + ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}") + print(f"loading font from {font_path}") + for f in bpy.context.scene.font3d_data.available_fonts.values(): + print(f"available font: {f.font_name} {f.face_name}") load_font_from_filepath(font_path) def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""): @@ -560,6 +600,9 @@ def set_text_on_curve(text_properties): regenerate = True if len(text_properties.text) > i and g.glyph_id != text_properties.text[i]: regenerate = True + if len(text_properties.text) > i and (g.font_name != text_properties.text[i].font_name + or g.face_name != text_properties.text[i].face_name): + regenerate = True if len(text_properties.text) != len(text_properties.glyphs): regenerate = True