From 97ca4f5d23b4ad1af3dd813a42e9cce967dd9f20 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Mon, 26 Aug 2024 18:48:43 +0200 Subject: [PATCH] revamp loading and panels --- __init__.py | 235 ++++++++++++++++++++++++++++++------------------- bimport.py | 21 ++++- butils.py | 38 +++++++- common/Font.py | 18 ++++ 4 files changed, 219 insertions(+), 93 deletions(-) diff --git a/__init__.py b/__init__.py index 3b86d86..94533ab 100644 --- a/__init__.py +++ b/__init__.py @@ -245,6 +245,10 @@ class ABC3D_data(bpy.types.PropertyGroup): maxlen=1024, # update=font_path_update_callback, subtype="FILE_PATH") + export_dir: bpy.props.StringProperty( + name="Export Directory", + description=f"The directory in which we will export fonts.\nIf it is blank, we will export to the addon assets path.\nThis is where the fonts are installed.", + subtype="DIR_PATH") class ABC3D_UL_fonts(bpy.types.UIList): @@ -278,25 +282,27 @@ class ABC3D_PT_Panel(bpy.types.Panel): layout.row().label(text='no fonts loaded yet') layout.operator(f"{__name__}.load_installed_fonts", text="load installed fonts", icon=icon) - layout.row().prop(context.scene.abc3d_data, "font_path") - layout.row().operator(f"{__name__}.install_font", text='Install new font') -# class ABC3D_PT_LoadFontPanel(bpy.types.Panel): - # bl_label = "Install a new font" - # bl_parent_id = "ABC3D_PT_Panel" - # bl_category = "ABC3D" - # bl_space_type = "VIEW_3D" - # bl_region_type = "UI" +class ABC3D_PT_LoadFontPanel(bpy.types.Panel): + bl_label = "Install a new font" + bl_parent_id = "ABC3D_PT_Panel" + bl_category = "ABC3D" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" - # def draw(self, context): - # layout = self.layout - # wm = context.window_manager - # scene = context.scene + def draw(self, context): + layout = self.layout + wm = context.window_manager + scene = context.scene - # abc3d_data = scene.abc3d_data + abc3d_data = scene.abc3d_data - # layout.row().operator(f"{__name__}.install_font", text='Install new font') + box = layout.box() + box.row().label(text="1. Select fontfile") + box.row().prop(context.scene.abc3d_data, "font_path") + box.row().label(text="2. Install it:") + box.row().operator(f"{__name__}.install_font", text='Install new font') class ABC3D_PT_FontList(bpy.types.Panel): @@ -477,7 +483,13 @@ class ABC3D_PT_FontCreation(bpy.types.Panel): abc3d_data = scene.abc3d_data layout.row().operator(f"{__name__}.create_font_from_objects", text='Create/Extend Font') - layout.row().operator(f"{__name__}.save_font_to_file", text='Save Font To File') + box = layout.box() + box.row().label(text="Exporting a fontfile") + box.row().label(text="1. Select export directory:") + box.prop(abc3d_data, 'export_dir') + box.row().label(text="2. More options and export:") + + box.row().operator(f"{__name__}.save_font_to_file", text='Export Font To File') layout.row().operator(f"{__name__}.toggle_abc3d_collection", text='Toggle Collection') box = layout.box() box.label(text="metrics") @@ -594,6 +606,7 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): abc3d_data = context.scene.abc3d_data layout = self.layout # layout.row().prop(self, "font_path") # crashes on Mac OS? + # layout.row().prop(abc3d_data, "font_path") # closes the stupid panel on Mac OS.. layout.row().prop(self, "install_in_assets") if not self.install_in_assets and not self.load_into_memory: layout.label(text="If the fontfile is not installed,") @@ -742,7 +755,7 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator): return {'FINISHED'} class ABC3D_OT_TemporaryHelper(bpy.types.Operator): - """Temp Font 3D""" + """Temporary Helper ABC3D\nThis could do anything.\nIt's just there to make random functions available for testing.""" bl_idname = f"{__name__}.temporaryhelper" bl_label = "Temp Font" bl_options = {'REGISTER', 'UNDO'} @@ -848,7 +861,7 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): text: bpy.props.StringProperty( name="Text", description="The text.", - default="HELLO", + default="ABC3D", maxlen=1024, ) # target_object: bpy.props.PointerProperty( @@ -891,7 +904,6 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): global shared scene = bpy.context.scene - abc3d = scene.abc3d abc3d_data = scene.abc3d_data selected = bpy.context.view_layer.objects.active @@ -953,7 +965,7 @@ class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator): fontcollection = bpy.data.collections.get("ABC3D") if fontcollection is None: - self.report({'INFO'}, f"{bl_info['name']}: There is no collection") + self.report({'INFO'}, f"{bl_info['name']}: There is no collection. Did you use or create any glyphs yet?") elif scene.collection.children.find(fontcollection.name) < 0: scene.collection.children.link(fontcollection) self.report({'INFO'}, f"{bl_info['name']}: show collection") @@ -969,30 +981,40 @@ class ABC3D_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 + preferences = getPreferences(context) + abc3d_data = context.scene.abc3d_data + if abc3d_data.export_dir == "": + abc3d_data.export_dir = os.path.join(preferences.assets_dir, "fonts") 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 draw(self, context): + abc3d_data = context.scene.abc3d_data + layout = self.layout + layout.label(text="Available Fonts") + layout.template_list("ABC3D_UL_fonts", "", abc3d_data, "available_fonts", abc3d_data, "active_font_index") + available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] + font_name = available_font.font_name + face_name = available_font.face_name + loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) + n = 16 + n_rows = int(len(loaded_glyphs) / n) + box = layout.box() + box.row().label(text=f"Glyphs to be exported:") + subbox = box.box() + for i in range(0, n_rows + 1): + text = ''.join([f"{u}" for ui, u in enumerate(loaded_glyphs) if ui < (i+1) * n and ui >= i * n]) + scale_y = 0.5 + row = subbox.row(); row.scale_y = scale_y + row.label(text=text) + layout.prop(abc3d_data, 'export_dir') def execute(self, context): global shared scene = bpy.context.scene abc3d_data = scene.abc3d_data - abc3d = scene.abc3d fontcollection = bpy.data.collections.get("ABC3D") @@ -1049,7 +1071,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): context_override["selected_objects"] = list(export_objects) # context_override["scene"] = bpy.context.scene.copy() with bpy.context.temp_override(**context_override): - filepath = f"{preferences.assets_dir}/fonts/{selected_font.font_name}.gltf" + filepath = f"{abc3d_data.export_dir}/{selected_font.font_name}.glb" # get rid of scene extra data before export scene_keys = [] for k in bpy.context.scene.keys(): @@ -1061,22 +1083,22 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): bpy.ops.export_scene.gltf( filepath=filepath, check_existing=False, - export_format='GLB', # GLB or GLTF_SEPARATE + export_format='GLB', # GLB or GLTF_SEPARATE (also change filepath) export_extras=True, - # export_hierarchy_full_collections=True, - # use_active_collection_with_nested=True, use_selection=True, use_active_scene=True, ) - # bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=5) + bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1) - bpy.ops.scene.delete() + # bpy.ops.scene.delete() # restore() - for obj in fontcollection.objects: - if obj["font_name"] == selected_font.font_name: - if butils.is_metrics_object(obj): - butils.remove_faces_from_metrics(obj) + def remove_faces(): + for obj in fontcollection.objects: + if obj["font_name"] == selected_font.font_name: + if butils.is_metrics_object(obj): + butils.remove_faces_from_metrics(obj) + bpy.app.timers.register(lambda: remove_faces(), first_interval=2) self.report({'INFO'}, f"did it") return {'FINISHED'} @@ -1111,55 +1133,61 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): wm = context.window_manager return wm.invoke_props_dialog(self) - def draw(self, contex): + def draw(self, context): layout = self.layout - 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') - 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}") + if len(context.selected_objects) == 0: + layout.row().label(text="No objects selected.", icon="ERROR") + layout.row().label(text="Please select your glyphs first.", icon="INFO") + else: + 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') + 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}") + if len(context.selected_objects) == 0: + print(f"cancelled {self.bl_idname} - no objects selected") + return {'CANCELLED'} global shared scene = bpy.context.scene - abc3d = scene.abc3d abc3d_data = scene.abc3d_data fontcollection = bpy.data.collections.get("ABC3D") @@ -1250,7 +1278,6 @@ class ABC3D_PT_RightPropertiesPanel(bpy.types.Panel): def draw(self, context): layout = self.layout scene = context.scene - abc3d = scene.abc3d abc3d_data = scene.abc3d_data obj = context.active_object @@ -1304,7 +1331,6 @@ class ABC3D_OT_Reporter(bpy.types.Operator): 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'} @@ -1320,7 +1346,7 @@ classes = ( ABC3D_UL_fonts, ABC3D_UL_texts, ABC3D_PT_Panel, - # ABC3D_PT_LoadFontPanel, + ABC3D_PT_LoadFontPanel, ABC3D_PT_FontList, ABC3D_PT_TextPlacement, ABC3D_PT_TextManagement, @@ -1342,6 +1368,37 @@ classes = ( ABC3D_OT_Reporter, ) +def compare_text_object_with_object(t, o, strict=False): + for k in o.keys(): + if k == f"{utils.prefix()}_type": + if o[k] != "textobject": + return False + elif k.startswith(f"{utils.prefix()}_"): + p = k.replace(f"{utils.prefix()}_","") + if p in t.keys(): + if t[p] != o[k]: + return False + else: + print(f"{__name__} set_text_object: did not find key ({p})") + if strict: + return False + # for p in t.keys(): + # if + return True + +def detect_text(): + scene = bpy.context.scene + abc3d_data = scene.abc3d_data + for o in scene.objects: + if o[f"{utils.prefix()}_type"] == "textobject": + linked_textobject = int(o[f"{utils.prefix()}_linked_textobject"]) + if len(abc3d_data.available_texts) > linked_textobject \ + and abc3d_data.available_texts[linked_textobject].text_object == o: + 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) + + @persistent def load_handler(self, dummy): if not bpy.app.timers.is_registered(butils.execute_queued_functions): diff --git a/bimport.py b/bimport.py index e105768..a1c88b7 100644 --- a/bimport.py +++ b/bimport.py @@ -8,7 +8,19 @@ from bpy.props import (StringProperty, from bpy.types import Operator from bpy_extras.io_utils import ImportHelper, ExportHelper from io_scene_gltf2 import ConvertGLTF2_Base -from .common import Font +import importlib + +# then import dependencies for our addon +if "Font" in locals(): + importlib.reload(Font) +else: + from .common import Font + +if "utils" in locals(): + importlib.reload(utils) +else: + from .common import utils + # taken from blender_git/blender/scripts/addons/io_scene_gltf2/__init__.py @@ -25,7 +37,8 @@ def get_font_faces_in_file(filepath): for node in gltf_importer.data.nodes: if type(node.extras) != type(None) \ and "glyph" in node.extras \ - and not ("type" in node.extras and node.extras["type"] == "metrics"): + and not ("type" in node.extras and node.extras["type"] == "metrics") \ + and not (f"{utils.prefix()}_type" in node.extras and node.extras[f"{utils.prefix()}_type"] == "metrics"): out.append(node.extras) return out @@ -400,7 +413,9 @@ class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper): set_extras(obj, n_vars["extras"]) if "glyph" in n_vars["extras"] and \ not ("type" in n_vars["extras"] and \ - n_vars["extras"]["type"] == "metrics"): + n_vars["extras"]["type"] == "metrics") and \ + not (f"{utils.prefix()}_type" in n_vars["extras"] and \ + n_vars["extras"][f"{utils.prefix()}_type"] == "metrics"): obj["type"] = "glyph" for vi, vnode in gltf.vnodes.items(): diff --git a/butils.py b/butils.py index 66a929a..2694f1e 100644 --- a/butils.py +++ b/butils.py @@ -724,6 +724,7 @@ def set_text_on_curve(text_properties, recursive=True): ob = None if regenerate: ob = bpy.data.objects.new(f"{glyph_id}", 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 @@ -808,6 +809,7 @@ def set_text_on_curve(text_properties, recursive=True): if regenerate: mom.select_set(True) + 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 @@ -823,6 +825,40 @@ def set_text_on_curve(text_properties, recursive=True): return True +verification_object = { + f"{utils.prefix()}_type": "textobject", + 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, + f"{utils.prefix()}_letter_spacing": 42, + f"{utils.prefix()}_orientation": [0,0,0], + f"{utils.prefix()}_translation": [0,0,0], + } + +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. @@ -839,7 +875,7 @@ def add_metrics_obj_from_bound_box(glyph, bound_box=None): obj["font_name"] = glyph["font_name"] obj["face_name"] = glyph["face_name"] obj["glyph"] = glyph["glyph"] - obj["type"] = "metrics" + obj[f"{utils.pefix()}_type"] = "metrics" # remove already existing metrics remove_metrics = [] diff --git a/common/Font.py b/common/Font.py index 8e467dd..312d5c6 100644 --- a/common/Font.py +++ b/common/Font.py @@ -226,5 +226,23 @@ def get_loaded_fonts_and_faces(): out.append([f,ff]) return out +MISSING_FONT = 0 +MISSING_FACE = 1 + +def test_availability(font_name, face_name, text): + if not fonts.keys().__contains__(font_name): + return MISSING_FONT + if fonts[font_name].faces.get(face_name) == None: + return MISSING_FACE + loaded, missing, maybe = test_glyphs_availability(font_name, + face_name, + text) + return { + "loaded": loaded, + "missing": missing, + "maybe": maybe, + } + + # holds all fonts fonts = {}