diff --git a/__init__.py b/__init__.py index 0f89da3..ad95aba 100644 --- a/__init__.py +++ b/__init__.py @@ -4,13 +4,14 @@ A 3D font helper """ +import functools +import importlib +import io import os + +import bpy from bpy.app.handlers import persistent from bpy.types import Panel -import functools -import io -import bpy -import importlib bl_info = { "name": "ABC3D", @@ -23,6 +24,9 @@ bl_info = { } # NOTE: also change version in common/utils.py +from . import addon_updater_ops, bimport, butils +from .common import Font, utils + # make sure that modules are reloadable # when registering # handy for development @@ -33,12 +37,6 @@ if "Font" in locals(): importlib.reload(butils) importlib.reload(bimport) importlib.reload(addon_updater_ops) -else: - from .common import Font - from .common import utils - from . import butils - from . import bimport - from . import addon_updater_ops def getPreferences(context): @@ -58,60 +56,68 @@ class ABC3D_addonPreferences(bpy.types.AddonPreferences): auto_check_update = bpy.props.BoolProperty( name="Auto-check for Update", description="If enabled, auto-check for updates using an interval", - default=False) + default=False, + ) updater_interval_months = bpy.props.IntProperty( - name='Months', + name="Months", description="Number of months between checking for updates", default=0, - min=0) + min=0, + ) updater_interval_days = bpy.props.IntProperty( - name='Days', + name="Days", description="Number of days between checking for updates", default=7, min=0, - max=31) + max=31, + ) updater_interval_hours = bpy.props.IntProperty( - name='Hours', + name="Hours", description="Number of hours between checking for updates", default=0, min=0, - max=23) + max=23, + ) updater_interval_minutes = bpy.props.IntProperty( - name='Minutes', + name="Minutes", description="Number of minutes between checking for updates", default=0, min=0, - max=59) + max=59, + ) def get_default_assets_dir(): - return bpy.utils.user_resource( - 'DATAFILES', - path=f"{__name__}", - create=True) + return bpy.utils.user_resource("DATAFILES", path=f"{__name__}", create=True) def on_change_assets_dir(self, context): if not os.path.isdir(self.assets_dir): butils.ShowMessageBox( title=f"{__name__} Warning", icon="ERROR", - message=("Chosen directory does not exist.", - "Please, reset to default, create it or chose another one.")) + message=( + "Chosen directory does not exist.", + "Please, reset to default, create it or chose another one.", + ), + ) elif not os.access(self.assets_dir, os.W_OK): butils.ShowMessageBox( title=f"{__name__} Warning", icon="ERROR", - message=("Chosen directory is not writable.", - "Please reset to default or chose another one.")) + message=( + "Chosen directory is not writable.", + "Please reset to default or chose another one.", + ), + ) print(f"{__name__}: change assets_dir to {self.assets_dir}") assets_dir: bpy.props.StringProperty( name="Assets Folder", - subtype='DIR_PATH', + subtype="DIR_PATH", default=get_default_assets_dir(), update=on_change_assets_dir, ) @@ -168,9 +174,7 @@ class ABC3D_text_properties(bpy.types.PropertyGroup): return 0 # "" def glyphs_update_callback(self, context): - butils.prepare_text(self.font_name, - self.face_name, - self.text) + butils.prepare_text(self.font_name, self.face_name, self.text) butils.set_text_on_curve(self) def update_callback(self, context): @@ -187,46 +191,40 @@ class ABC3D_text_properties(bpy.types.PropertyGroup): items=font_items_callback, update=font_update_callback, ) - font_name: bpy.props.StringProperty( - update=glyphs_update_callback - ) - face_name: bpy.props.StringProperty( - update=glyphs_update_callback - ) + font_name: bpy.props.StringProperty(update=glyphs_update_callback) + face_name: bpy.props.StringProperty(update=glyphs_update_callback) text_object: bpy.props.PointerProperty(type=bpy.types.Object) - text: bpy.props.StringProperty( - update=glyphs_update_callback - ) + text: bpy.props.StringProperty(update=glyphs_update_callback) letter_spacing: bpy.props.FloatProperty( update=update_callback, name="Letter Spacing", description="Letter Spacing", - options={'ANIMATABLE'}, + options={"ANIMATABLE"}, step=0.01, ) orientation: bpy.props.FloatVectorProperty( update=update_callback, name="Orientation", default=(1.5707963267948966, 0.0, 0.0), # 90 degrees in radians - subtype='EULER', + subtype="EULER", ) translation: bpy.props.FloatVectorProperty( update=update_callback, name="Translation", default=(0.0, 0.0, 0.0), - subtype='TRANSLATION', + subtype="TRANSLATION", ) font_size: bpy.props.FloatProperty( update=update_callback, name="Font Size", default=1.0, - subtype='NONE', + subtype="NONE", ) offset: bpy.props.FloatProperty( update=update_callback, name="Offset", default=0.0, - subtype='NONE', + subtype="NONE", ) compensate_curvature: bpy.props.BoolProperty( update=update_callback, @@ -242,9 +240,11 @@ class ABC3D_text_properties(bpy.types.PropertyGroup): distribution_type: bpy.props.StringProperty() glyphs: bpy.props.CollectionProperty(type=ABC3D_glyph_properties) + class ABC3D_data(bpy.types.PropertyGroup): available_fonts: bpy.props.CollectionProperty( - type=ABC3D_available_font, name="Available fonts") + type=ABC3D_available_font, name="Available fonts" + ) def active_font_index_update(self, context): if len(self.available_fonts) <= self.active_font_index: @@ -255,7 +255,8 @@ class ABC3D_data(bpy.types.PropertyGroup): update=active_font_index_update, ) available_texts: bpy.props.CollectionProperty( - type=ABC3D_text_properties, name="Available texts") + type=ABC3D_text_properties, name="Available texts" + ) def active_text_index_update(self, context): if self.active_text_index != -1: @@ -263,12 +264,15 @@ class ABC3D_data(bpy.types.PropertyGroup): # 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: + 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") + # print("already selected") active_text_index: bpy.props.IntProperty(update=active_text_index_update) @@ -283,15 +287,19 @@ class ABC3D_data(bpy.types.PropertyGroup): default="", maxlen=1024, # update=font_path_update_callback, - subtype="FILE_PATH") + 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") + subtype="DIR_PATH", + ) class ABC3D_UL_fonts(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + def draw_item( + self, context, layout, data, item, icon, active_data, active_propname, index + ): # avoids renaming the item by accident layout.label(text=f"{index}: {item.font_name} {item.face_name}") @@ -300,7 +308,9 @@ class ABC3D_UL_fonts(bpy.types.UIList): class ABC3D_UL_texts(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + def draw_item( + self, context, layout, data, item, icon, active_data, active_propname, index + ): split = layout.split(factor=0.3) split.label(text="Id: %d" % (item.text_id)) # avoids renaming the item by accident @@ -309,6 +319,7 @@ class ABC3D_UL_texts(bpy.types.UIList): def invoke(self, context, event): pass + class ABC3D_PT_Panel(bpy.types.Panel): bl_label = f"{__name__} panel" bl_category = "ABC3D" @@ -318,16 +329,20 @@ class ABC3D_PT_Panel(bpy.types.Panel): def draw(self, context): layout = self.layout - icon = 'NONE' + icon = "NONE" if len(context.scene.abc3d_data.available_fonts) == 0: - icon = 'ERROR' - layout.row().label(text='no fonts loaded yet') + icon = "ERROR" + layout.row().label(text="no fonts loaded yet") - layout.operator(f"{__name__}.install_font", text='Install new font') - layout.operator(f"{__name__}.load_installed_fonts", - text="load installed fonts", icon=icon) - layout.operator(f"{__name__}.open_asset_directory", - text="open asset directory", icon='FILEBROWSER') + layout.operator(f"{__name__}.install_font", text="Install new font") + layout.operator( + f"{__name__}.load_installed_fonts", text="load installed fonts", icon=icon + ) + layout.operator( + f"{__name__}.open_asset_directory", + text="open asset directory", + icon="FILEBROWSER", + ) class ABC3D_PT_FontList(bpy.types.Panel): @@ -345,16 +360,22 @@ class ABC3D_PT_FontList(bpy.types.Panel): abc3d_data = scene.abc3d_data layout.label(text="Available Fonts") - layout.template_list("ABC3D_UL_fonts", "", abc3d_data, - "available_fonts", abc3d_data, "active_font_index") + layout.template_list( + "ABC3D_UL_fonts", + "", + abc3d_data, + "available_fonts", + abc3d_data, + "active_font_index", + ) if abc3d_data.active_font_index >= 0: available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] font_name = available_font.font_name face_name = available_font.face_name available_glyphs = sorted( - Font.fonts[font_name].faces[face_name].glyphs_in_fontfile) - loaded_glyphs = sorted( - Font.fonts[font_name].faces[face_name].loaded_glyphs) + Font.fonts[font_name].faces[face_name].glyphs_in_fontfile + ) + loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) box = layout.box() box.row().label(text=f"Font Name: {font_name}") box.row().label(text=f"Face Name: {face_name}") @@ -363,26 +384,37 @@ class ABC3D_PT_FontList(bpy.types.Panel): box.row().label(text=f"Glyphs:") subbox = box.box() for i in range(0, n_rows + 1): - text = ''.join([f"{u}" for ui, u in enumerate( - available_glyphs) if ui < (i+1) * n and ui >= i * n]) + text = "".join( + [ + f"{u}" + for ui, u in enumerate(available_glyphs) + if ui < (i + 1) * n and ui >= i * n + ] + ) scale_y = 0.5 row = subbox.row() row.scale_y = scale_y - row.alignment = 'CENTER' + row.alignment = "CENTER" row.label(text=text) n_rows = int(len(loaded_glyphs) / n) box.row().label(text=f"Loaded/Used Glyphs:") 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]) + 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) row = layout.row() - oper_lf = row.operator(f"{__name__}.load_font", - text='Load all glyphs in memory') + oper_lf = row.operator( + f"{__name__}.load_font", text="Load all glyphs in memory" + ) oper_lf.font_name = font_name oper_lf.face_name = face_name @@ -398,7 +430,10 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel): @classmethod def poll(self, context): - if type(context.active_object) != type(None) and context.active_object.type == 'CURVE': + if ( + type(context.active_object) != type(None) + and context.active_object.type == "CURVE" + ): self.can_place = True else: self.can_place = False @@ -413,7 +448,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel): placerow = layout.row() placerow.enabled = self.can_place - placerow.operator(f"{__name__}.placetext", text='Place Text') + placerow.operator(f"{__name__}.placetext", text="Place Text") if not self.can_place: layout.label(text="Cannot place Text.") layout.label(text="Select a curve as active object.") @@ -445,24 +480,43 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): continue remove_me = True for c in t.text_object.children: - if len(c.users_collection) > 0 and (c.get(f"{utils.prefix()}_linked_textobject")) != type(None) and c.get(f"{utils.prefix()}_linked_textobject") == t.text_id: + if ( + len(c.users_collection) > 0 + and (c.get(f"{utils.prefix()}_linked_textobject")) != type(None) + and c.get(f"{utils.prefix()}_linked_textobject") == t.text_id + ): remove_me = False # not sure how to solve this reliably atm, # we need to reassign the glyph, but also get the proper properties from glyph_properties # these might be there in t.glyphs, but linked to removed objects # or they might be lost - if type(next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)) == type(None): - g = next((g for g in t.glyphs if type( - g.glyph_object) == type(None)), None) + if type( + next( + ( + g + for g in t.glyphs + if type(g.glyph_object) == type(None) + ), + None, + ) + ) == type(None): + g = next( + ( + g + for g in t.glyphs + if type(g.glyph_object) == type(None) + ), + None, + ) # for g in t.glyphs: - # if type(g) == type(None): - # print("IS NONE") - # if type(g.glyph_object) == type(None): - # print("go IS NONE") - # else: - # if g.glyph_object == c: - # # print(g.glyph_object.name) - # pass + # if type(g) == type(None): + # print("IS NONE") + # if type(g.glyph_object) == type(None): + # print("go IS NONE") + # else: + # if g.glyph_object == c: + # # print(g.glyph_object.name) + # pass if remove_me: remove_list.append(i) @@ -474,6 +528,7 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): def delif(o, p): if p in o: del o[p] + delif(mom, f"{utils.prefix()}_linked_textobject") delif(mom, f"{utils.prefix()}_font_name") delif(mom, f"{utils.prefix()}_face_name") @@ -487,8 +542,10 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): for i, t in enumerate(abc3d_data.available_texts): if context.active_object == t.text_object: active_text_index = i - if (hasattr(context.active_object, "parent") and - context.active_object.parent == t.text_object): + if ( + hasattr(context.active_object, "parent") + and context.active_object.parent == t.text_object + ): active_text_index = i if active_text_index != abc3d_data.active_text_index: @@ -506,10 +563,15 @@ class ABC3D_PT_TextManagement(bpy.types.Panel): abc3d_data = scene.abc3d_data layout.label(text="Text Objects") - layout.template_list("ABC3D_UL_texts", "", abc3d_data, - "available_texts", abc3d_data, "active_text_index") - layout.row().operator( - f"{__name__}.remove_text", text="Remove Textobject") + layout.template_list( + "ABC3D_UL_texts", + "", + abc3d_data, + "available_texts", + abc3d_data, + "active_text_index", + ) + layout.row().operator(f"{__name__}.remove_text", text="Remove Textobject") class ABC3D_PT_FontCreation(bpy.types.Panel): @@ -537,13 +599,17 @@ class ABC3D_PT_FontCreation(bpy.types.Panel): box = layout.box() box.label(text="metrics") box.row().operator( - f"{__name__}.add_default_metrics", text='Add Default Metrics') - box.row().operator(f"{__name__}.remove_metrics", text='Remove Metrics') - box.row().operator(f"{__name__}.align_metrics", text='Align Metrics') + f"{__name__}.add_default_metrics", text="Add Default Metrics" + ) + box.row().operator(f"{__name__}.remove_metrics", text="Remove Metrics") + box.row().operator(f"{__name__}.align_metrics", text="Align Metrics") box.row().operator( - f"{__name__}.align_metrics_to_active_object", text='Align Metrics to Active Object') + f"{__name__}.align_metrics_to_active_object", + text="Align Metrics to Active Object", + ) layout.row().operator( - f"{__name__}.temporaryhelper", text='Debug Function Do Not Use') + f"{__name__}.temporaryhelper", text="Debug Function Do Not Use" + ) class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): @@ -564,31 +630,31 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): return None # def font_items_callback(self, context): - # items = [] - # fonts = Font.get_loaded_fonts_and_faces() - # for f in fonts: - # items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", "")) - # return items + # items = [] + # fonts = Font.get_loaded_fonts_and_faces() + # for f in fonts: + # items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", "")) + # return items # def font_default_callback(self, context): - # t = self.get_active_text_properties(self) - # if type(t) != type(None): - # return f"{t.font_name} {t.face_name}" - # else: - # return None + # t = self.get_active_text_properties(self) + # if type(t) != type(None): + # return f"{t.font_name} {t.face_name}" + # else: + # return None # def font_update_callback(self, context): - # font_name, face_name = self.font.split(" ") - # t = self.get_active_text_properties(self) - # t.font_name = font_name - # t.face_name = face_name - # butils.set_text_on_curve(t) + # font_name, face_name = self.font.split(" ") + # t = self.get_active_text_properties(self) + # t.font_name = font_name + # t.face_name = face_name + # butils.set_text_on_curve(t) # font: bpy.props.EnumProperty( - # items=font_items_callback, - # default=font_default_callback, - # update=font_update_callback, - # ) + # items=font_items_callback, + # default=font_default_callback, + # update=font_update_callback, + # ) @classmethod def poll(self, context): @@ -622,16 +688,18 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): class ABC3D_OT_InstallFont(bpy.types.Operator): """Install or load Fontfile from path above. -(Format must be *.glb or *.gltf)""" + (Format must be *.glb or *.gltf)""" + bl_idname = f"{__name__}.install_font" bl_label = "Load Font" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def font_path_update_callback(self, context): - if os.path.exists(self.font_path): - print(f"{self.font_path} does exist") + font_path = butils.bpy_to_abspath(self.font_path) + if os.path.exists(font_path): + print(f"font_path_update: {font_path} does exist") else: - print(f"{self.font_path} does not exist") + print(f"font_path_update: {font_path} does not exist") font_path: bpy.props.StringProperty( name="Font path", @@ -639,7 +707,8 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): default="", maxlen=1024, update=font_path_update_callback, - subtype="FILE_PATH") + subtype="FILE_PATH", + ) install_in_assets: bpy.props.BoolProperty( name="install in assets", @@ -647,9 +716,11 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): default=True, ) - load_into_memory: bpy.props.BoolProperty(name="load font data into memory", - description="if false, it will load font data on demand", - default=False) + load_into_memory: bpy.props.BoolProperty( + name="load font data into memory", + description="if false, it will load font data on demand", + default=False, + ) def draw(self, context): abc3d_data = context.scene.abc3d_data @@ -659,8 +730,7 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): 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,") - layout.label( - text="and the font is not loaded in memory completely,") + layout.label(text="and the font is not loaded in memory completely,") layout.label(text="the fontfile should not be moved.") layout.row().prop(self, "load_into_memory") if self.load_into_memory: @@ -672,14 +742,14 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): def invoke(self, context, event): # self.font_path = butils.bpy_to_abspath(self.font_path) # if not os.path.exists(self.font_path): - # bpy.app.timers.register(lambda: butils.ShowMessageBox( - # title=f"{__name__} Warning", - # icon="ERROR", - # message=[ - # f"We believe the font path ({self.font_path}) does not exist.", - # f"Did you select your fontfile in the field above the 'Install new font'-button?", - # ], - # ), first_interval=0.1) + # bpy.app.timers.register(lambda: butils.ShowMessageBox( + # title=f"{__name__} Warning", + # icon="ERROR", + # message=[ + # f"We believe the font path ({self.font_path}) does not exist.", + # f"Did you select your fontfile in the field above the 'Install new font'-button?", + # ], + # ), first_interval=0.1) return context.window_manager.invoke_props_dialog(self) def execute(self, context): @@ -697,13 +767,14 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): f"If this is an error, please let us know.", ], ) - return {'CANCELLED'} + return {"CANCELLED"} if self.install_in_assets: preferences = getPreferences(context) filename = os.path.basename(font_path) target = os.path.join(preferences.assets_dir, "fonts", filename) import shutil + os.makedirs(os.path.dirname(target), exist_ok=True) shutil.copyfile(font_path, target) # def register_load(target, load=False): @@ -718,40 +789,47 @@ class ABC3D_OT_InstallFont(bpy.types.Operator): if self.load_into_memory: butils.load_font_from_filepath(font_path) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_OpenAssetDirectory(bpy.types.Operator): """Open Asset Directory""" + bl_idname = f"{__name__}.open_asset_directory" bl_label = "Opens asset directory." - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): preferences = getPreferences(context) directory = os.path.realpath(preferences.assets_dir) if os.path.exists(directory): utils.open_file_browser(directory) - return {'FINISHED'} + return {"FINISHED"} else: butils.ShowMessageBox( title=f"{__name__} Warning", icon="ERROR", - message=("Asset directory does not exist.", - f"Command failed trying to access '{directory}'.", - "Please, make sure it exists or chose another directory.")) - return {'CANCELLED'} + message=( + "Asset directory does not exist.", + f"Command failed trying to access '{directory}'.", + "Please, make sure it exists or chose another directory.", + ), + ) + return {"CANCELLED"} class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator): """Load installed fontfiles from datapath.""" + bl_idname = f"{__name__}.load_installed_fonts" bl_label = "Loading installed Fonts." - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} - load_into_memory: bpy.props.BoolProperty(name="load font data into memory", - description="if false, it will load font data on demand", - default=False) + load_into_memory: bpy.props.BoolProperty( + name="load font data into memory", + description="if false, it will load font data on demand", + default=False, + ) def draw(self, context): layout = self.layout @@ -773,22 +851,19 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator): butils.load_installed_fonts() else: butils.register_installed_fonts() - butils.ShowMessageBox("Loading Fonts", - 'INFO', - "Updating Data Structures.") + butils.ShowMessageBox("Loading Fonts", "INFO", "Updating Data Structures.") butils.update_available_fonts() - butils.ShowMessageBox("Loading Fonts", - 'INFO', - "Done loading installed fonts.") + butils.ShowMessageBox("Loading Fonts", "INFO", "Done loading installed fonts.") - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_LoadFont(bpy.types.Operator): """Load all glyphs from a specific font in memory.\nThis can take a while and slow down Blender.""" + bl_idname = f"{__name__}.load_font" bl_label = "Loading Font." - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} font_name: bpy.props.StringProperty() face_name: bpy.props.StringProperty() @@ -797,62 +872,72 @@ class ABC3D_OT_LoadFont(bpy.types.Operator): filepaths = Font.fonts[self.font_name].faces[self.face_name].filepaths for f in filepaths: butils.load_font_from_filepath(f) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_AddDefaultMetrics(bpy.types.Operator): """Add default metrics to selected objects""" + bl_idname = f"{__name__}.add_default_metrics" bl_label = "Add default metrics" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): objects = bpy.context.selected_objects butils.add_default_metrics_to_objects(objects) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_RemoveMetrics(bpy.types.Operator): """Remove metrics from selected objects""" + bl_idname = f"{__name__}.remove_metrics" bl_label = "Remove metrics" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): objects = bpy.context.selected_objects butils.remove_metrics_from_objects(objects) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_AlignMetricsToActiveObject(bpy.types.Operator): - """Align metrics of selected objects to metrics of active object""" + """Align metrics of selected objects to metrics of active object. + + The metrics of the active object are not changed and is taken as a reference for all other objects. + """ + bl_idname = f"{__name__}.align_metrics_to_active_object" bl_label = "Align metrics to active object" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): objects = bpy.context.selected_objects butils.align_metrics_of_objects_to_active_object(objects) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_AlignMetrics(bpy.types.Operator): - """Align metrics of selected objects to each other""" + """Align metrics of selected objects to each other. + + The metrics of all objects are merged and expanded to fit for all objects.""" + bl_idname = f"{__name__}.align_metrics" bl_label = "Align metrics" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): objects = bpy.context.selected_objects butils.align_metrics_of_objects(objects) - return {'FINISHED'} + 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.""" + bl_idname = f"{__name__}.temporaryhelper" bl_label = "Temp Font" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): global shared @@ -873,19 +958,21 @@ class ABC3D_OT_TemporaryHelper(bpy.types.Operator): # butils.add_metrics_obj_from_bound_box(o, metrics) # bpy.app.timers.register(lambda: butils.remove_metrics_from_objects(objects), first_interval=5) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_RemoveText(bpy.types.Operator): """Remove Text 3D""" + bl_idname = f"{__name__}.remove_text" bl_label = "Remove Text" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} remove_objects: bpy.props.BoolProperty( name="Remove Objects", description="Remove both ABC3D text functionality and the objects/meshes", - default=True) + default=True, + ) def invoke(self, context, event): wm = context.window_manager @@ -897,8 +984,9 @@ class ABC3D_OT_RemoveText(bpy.types.Operator): butils.ShowMessageBox( title="No text selected", message=("Please select a text."), - icon='GHOST_ENABLED') - return {'CANCELLED'} + icon="GHOST_ENABLED", + ) + return {"CANCELLED"} i = abc3d_data.active_text_index if type(abc3d_data.available_texts[i].text_object) != type(None): @@ -907,6 +995,7 @@ class ABC3D_OT_RemoveText(bpy.types.Operator): def delif(o, p): if p in o: del o[p] + delif(mom, f"{utils.prefix()}_type") delif(mom, f"{utils.prefix()}_linked_textobject") delif(mom, f"{utils.prefix()}_font_name") @@ -925,14 +1014,15 @@ class ABC3D_OT_RemoveText(bpy.types.Operator): abc3d_data.available_texts.remove(i) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_PlaceText(bpy.types.Operator): """Place Text 3D on active object""" + bl_idname = f"{__name__}.placetext" bl_label = "Place Text" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def font_items_callback(self, context): items = [] @@ -946,15 +1036,9 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): self.font_name = font_name self.face_name = face_name - font_name: bpy.props.StringProperty( - options={'HIDDEN'} - ) - face_name: bpy.props.StringProperty( - options={'HIDDEN'} - ) - font: bpy.props.EnumProperty(items=font_items_callback, - update=font_update_callback - ) + font_name: bpy.props.StringProperty(options={"HIDDEN"}) + face_name: bpy.props.StringProperty(options={"HIDDEN"}) + font: bpy.props.EnumProperty(items=font_items_callback, update=font_update_callback) text: bpy.props.StringProperty( name="Text", description="The text.", @@ -974,22 +1058,22 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): font_size: bpy.props.FloatProperty( name="Font Size", default=1.0, - subtype='NONE', + subtype="NONE", ) offset: bpy.props.FloatProperty( name="Offset", default=0.0, - subtype='NONE', + subtype="NONE", ) translation: bpy.props.FloatVectorProperty( name="Translation", default=(0.0, 0.0, 0.0), - subtype='TRANSLATION', + subtype="TRANSLATION", ) orientation: bpy.props.FloatVectorProperty( name="Orientation", default=(1.5707963267948966, 0.0, 0.0), # 90 degrees in radians - subtype='EULER', + subtype="EULER", ) def invoke(self, context, event): @@ -1010,7 +1094,7 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): # font_name = font.font_name # face_name = font.face_name - distribution_type = 'DEFAULT' + distribution_type = "DEFAULT" text_id = 0 for i, tt in enumerate(abc3d_data.available_texts): @@ -1019,18 +1103,18 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): t = abc3d_data.available_texts.add() # If you wish to set a value and not fire an update, set the id property. # A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default. - t['text_id'] = text_id + t["text_id"] = text_id # t['font'] = self.font # enums want to be set as attribute - t['font_name'] = self.font_name - t['face_name'] = self.face_name + t["font_name"] = self.font_name + t["face_name"] = self.face_name t.text_object = selected - t['text'] = self.text - t['letter_spacing'] = self.letter_spacing - t['font_size'] = self.font_size - t['offset'] = self.offset - t['translation'] = self.translation - t['orientation'] = self.orientation - t['distribution_type'] = distribution_type + t["text"] = self.text + t["letter_spacing"] = self.letter_spacing + t["font_size"] = self.font_size + t["offset"] = self.offset + t["translation"] = self.translation + t["orientation"] = self.orientation + t["distribution_type"] = distribution_type t.font = self.font # enums want to be set as attribute # this also calls the update function # so we don't need to prepare/set again @@ -1046,17 +1130,20 @@ class ABC3D_OT_PlaceText(bpy.types.Operator): message=( "Please select an object.", "It will be used to put the type on.", - "Thank you :)"), - icon='GHOST_ENABLED') + "Thank you :)", + ), + icon="GHOST_ENABLED", + ) - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator): """Toggle ABC3D Collection""" + bl_idname = f"{__name__}.toggle_abc3d_collection" bl_label = "Toggle Collection visibility" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} def execute(self, context): scene = context.scene @@ -1064,56 +1151,68 @@ class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator): if fontcollection is None: self.report( - {'INFO'}, f"{bl_info['name']}: There is no collection. Did you use or create any glyphs yet?") + {"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") + self.report({"INFO"}, f"{bl_info['name']}: show collection") else: scene.collection.children.unlink(fontcollection) - self.report({'INFO'}, f"{bl_info['name']}: hide collection") + self.report({"INFO"}, f"{bl_info['name']}: hide collection") - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_SaveFontToFile(bpy.types.Operator): """Save font to file""" + bl_idname = f"{__name__}.save_font_to_file" bl_label = "Save Font" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} 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") + abc3d_data.export_dir = os.path.join(preferences.assets_dir, "fonts") return wm.invoke_props_dialog(self) 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") + 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) + 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]) + 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') + layout.prop(abc3d_data, "export_dir") def execute(self, context): global shared @@ -1124,22 +1223,21 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): # check if all is good to proceed if fontcollection is None: - self.report({'INFO'}, f"{bl_info['name']}: There is no collection") - return {'CANCELLED'} + self.report({"INFO"}, f"{bl_info['name']}: There is no collection") + return {"CANCELLED"} if abc3d_data.active_font_index < 0: - self.report( - {'INFO'}, f"{bl_info['name']}: There is no active font") - return {'CANCELLED'} + self.report({"INFO"}, f"{bl_info['name']}: There is no active font") + return {"CANCELLED"} if len(abc3d_data.available_fonts) <= abc3d_data.active_font_index: - self.report( - {'INFO'}, f"{bl_info['name']}: Active font is not available") - return {'CANCELLED'} + self.report({"INFO"}, f"{bl_info['name']}: Active font is not available") + return {"CANCELLED"} # save state to restore later - was_fontcollection_linked = scene.collection.children.find( - fontcollection.name) >= 0 + was_fontcollection_linked = ( + scene.collection.children.find(fontcollection.name) >= 0 + ) was_selection = [] for obj in bpy.context.selected_objects: was_selection.append(obj) @@ -1152,11 +1250,13 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): # print(selected_font.font_name) self.report( - {'INFO'}, f"{bl_info['name']}: {selected_font.font_name} {selected_font.face_name}") + {"INFO"}, + f"{bl_info['name']}: {selected_font.font_name} {selected_font.face_name}", + ) preferences = getPreferences(context) print(f"assets folder: {preferences.assets_dir}") - bpy.ops.scene.new(type='FULL_COPY') + bpy.ops.scene.new(type="FULL_COPY") linked_collections = bpy.context.scene.collection.children.values() for c in linked_collections: @@ -1192,13 +1292,12 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): filepath=filepath, check_existing=False, # GLB or GLTF_SEPARATE (also change filepath) - export_format='GLB', + export_format="GLB", export_extras=True, use_selection=True, use_active_scene=True, ) - bpy.app.timers.register( - lambda: bpy.ops.scene.delete(), first_interval=1) + bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1) # bpy.ops.scene.delete() # restore() @@ -1208,20 +1307,24 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): 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'} + bpy.app.timers.register(lambda: remove_faces(), first_interval=2) + self.report({"INFO"}, f"did it") + + return {"FINISHED"} + + # keep = ['io_anim_bvh', 'io_curve_svg', 'io_mesh_stl', 'io_mesh_uv_layout', 'io_scene_fbx', 'io_scene_gltf2', 'io_scene_x3d', 'cycles', 'pose_library', 'abc3d'] # for addon in keep: - # bpy.ops.preferences.addon_enable(module=addon) +# bpy.ops.preferences.addon_enable(module=addon) class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): """Create Font from selected objects""" + bl_idname = f"{__name__}.create_font_from_objects" bl_label = "Create Font" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {"REGISTER", "UNDO"} font_name: bpy.props.StringProperty( default="NM_Origin", @@ -1267,8 +1370,8 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): scale_y = 0.5 row = layout.row() row.scale_y = scale_y - row.label( - text="Watch out, follow convention in naming your meshes:") + row.label(text="Autodetecting names per glyph.") + row.label(text="Watch out, follow convention in naming your meshes:") row = layout.row() row.scale_y = scale_y row.label(text="'__'") @@ -1311,17 +1414,18 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): 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]]})" + 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}") + 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'} + return {"CANCELLED"} global shared scene = bpy.context.scene abc3d_data = scene.abc3d_data @@ -1351,7 +1455,7 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): o.name = Font.fix_glyph_name_misspellings(o.name) # name = re.sub(regex, "", o.name) # glyph_id = Font.name_to_glyph(name) - name = o.name.split('_')[0] + name = o.name.split("_")[0] glyph_id = Font.name_to_glyph(name) if type(glyph_id) != type(None): @@ -1359,20 +1463,15 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): o["font_name"] = font_name o["face_name"] = face_name # butils.apply_all_transforms(o) - butils.move_in_fontcollection( - o, - fontcollection) + butils.move_in_fontcollection(o, fontcollection) Font.add_glyph( - font_name, - face_name, - glyph_id, - bpy.types.PointerProperty(o)) + font_name, face_name, glyph_id, bpy.types.PointerProperty(o) + ) # TODO: is there a better way to iterate over a CollectionProperty? found = False for f in abc3d_data.available_fonts.values(): - if (f.font_name == font_name - and f.face_name == face_name): + if f.font_name == font_name and f.face_name == face_name: found = True break if not found: @@ -1381,12 +1480,10 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): f.face_name = face_name else: - print( - f"import warning: did not understand glyph {name}") - self.report( - {'INFO'}, f"did not understand glyph {name}") + print(f"import warning: did not understand glyph {name}") + self.report({"INFO"}, f"did not understand glyph {name}") - return {'FINISHED'} + return {"FINISHED"} class ABC3D_OT_Reporter(bpy.types.Operator): @@ -1404,10 +1501,10 @@ class ABC3D_OT_Reporter(bpy.types.Operator): def execute(self, context): # this is where I send the message - self.report({'INFO'}, 'whatever') + self.report({"INFO"}, "whatever") for i in range(0, 10): - butils.ShowMessageBox('whatever', 'INFO', 'INFO') - return {'FINISHED'} + butils.ShowMessageBox("whatever", "INFO", "INFO") + return {"FINISHED"} classes = ( @@ -1459,7 +1556,7 @@ def compare_text_object_with_object(t, o, strict=False): if strict: return False # for p in t.keys(): - # if + # if return True @@ -1469,11 +1566,12 @@ def detect_text(): 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: + 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"]) + a = test_availability(o["font_name"], o["face_name"], o["text"]) butils.transfer_blender_object_to_text_properties(o, t) @@ -1481,21 +1579,24 @@ def load_used_glyphs(): scene = bpy.context.scene abc3d_data = scene.abc3d_data for t in abc3d_data.available_texts: - a = Font.test_availability(t.font_name, - t.face_name, - t.text) + a = Font.test_availability(t.font_name, t.face_name, t.text) if type(a) == type(int()): if a == Font.MISSING_FONT: - butils.ShowMessageBox("Missing Font", - "ERROR", - [f"Font {t.font_name} is missing.", - "Do you have it installed?"]) + butils.ShowMessageBox( + "Missing Font", + "ERROR", + [f"Font {t.font_name} is missing.", "Do you have it installed?"], + ) if a is Font.MISSING_FACE: - butils.ShowMessageBox("Missing FontFace", - "ERROR", - [f"Font {t.font_name} is there,", - f"but the FontFace {t.face_name} is missing,", - "Do you have it installed?"]) + butils.ShowMessageBox( + "Missing FontFace", + "ERROR", + [ + f"Font {t.font_name} is there,", + f"but the FontFace {t.face_name} is missing,", + "Do you have it installed?", + ], + ) elif len(a["maybe"]) > 0: for fp in a["filepaths"]: butils.load_font_from_filepath(fp, a["maybe"]) @@ -1521,11 +1622,15 @@ def on_frame_changed(self, dummy): # TODO PERFORMANCE: only on demand butils.set_text_on_curve(t) + depsgraph_updates_locked = False + + def unlock_depsgraph_updates(): global depsgraph_updates_locked depsgraph_updates_locked = False + def lock_depsgraph_updates(): global depsgraph_updates_locked depsgraph_updates_locked = True @@ -1533,25 +1638,38 @@ def lock_depsgraph_updates(): bpy.app.timers.unregister(unlock_depsgraph_updates) bpy.app.timers.register(unlock_depsgraph_updates, first_interval=1) + import time + + @persistent def on_depsgraph_update(scene, depsgraph): global depsgraph_updates_locked if not bpy.context.mode.startswith("EDIT") and not depsgraph_updates_locked: for u in depsgraph.updates: - if 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': + if ( + 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" + ): linked_textobject = u.id[f"{utils.prefix()}_linked_textobject"] - if u.is_updated_geometry and len(scene.abc3d_data.available_texts) > linked_textobject: + if ( + u.is_updated_geometry + and len(scene.abc3d_data.available_texts) > linked_textobject + ): lock_depsgraph_updates() + def later(): - if not "lock_depsgraph_update_ntimes" in scene.abc3d_data \ - or scene.abc3d_data["lock_depsgraph_update_ntimes"] <= 0: + if ( + not "lock_depsgraph_update_ntimes" in scene.abc3d_data + or scene.abc3d_data["lock_depsgraph_update_ntimes"] <= 0 + ): butils.set_text_on_curve( - scene.abc3d_data.available_texts[linked_textobject]) + scene.abc3d_data.available_texts[linked_textobject] + ) elif scene.abc3d_data["lock_depsgraph_update_ntimes"] > 0: - scene.abc3d_data['lock_depsgraph_update_ntimes'] -= 1 + scene.abc3d_data["lock_depsgraph_update_ntimes"] -= 1 + butils.run_in_main_thread(later) @@ -1609,5 +1727,5 @@ def unregister(): print(f"UNREGISTER {utils.prefix()}") -if __name__ == '__main__': +if __name__ == "__main__": register() diff --git a/butils.py b/butils.py index b9488ba..d3b6940 100644 --- a/butils.py +++ b/butils.py @@ -22,17 +22,20 @@ else: execution_queue = queue.Queue() + # This function can safely be called in another thread. # The function will be executed when the timer runs the next time. def run_in_main_thread(function): execution_queue.put(function) + def execute_queued_functions(): while not execution_queue.empty(): function = execution_queue.get() function() return 1.0 + def apply_all_transforms(obj): mb = obj.matrix_basis if hasattr(obj.data, "transform"): @@ -41,16 +44,18 @@ def apply_all_transforms(obj): c.matrix_local = mb @ c.matrix_local obj.matrix_basis.identity() + def get_parent_collection_names(collection, parent_names): - for parent_collection in bpy.data.collections: - if collection.name in parent_collection.children.keys(): - parent_names.append(parent_collection.name) - get_parent_collection_names(parent_collection, parent_names) - return + for parent_collection in bpy.data.collections: + if collection.name in parent_collection.children.keys(): + parent_names.append(parent_collection.name) + get_parent_collection_names(parent_collection, parent_names) + return + # Ensure it's a curve object # TODO: no raising, please -def get_curve_length(curve_obj, resolution = -1): +def get_curve_length(curve_obj, resolution=-1): total_length = 0 curve = curve_obj.data @@ -58,16 +63,20 @@ def get_curve_length(curve_obj, resolution = -1): # Loop through all splines in the curve for spline in curve.splines: total_length = total_length + spline.calc_length(resolution=resolution) - + return total_length -def get_curve_line_lengths(curve_obj, resolution = -1): + +def get_curve_line_lengths(curve_obj, resolution=-1): lengths = [] for spline in curve_obj.data.splines: lengths.append(spline.calc_length(resolution=resolution)) return lengths -def get_next_line_advance(curve_obj, current_advance, previous_glyph_advance, resolution = -1): + +def get_next_line_advance( + curve_obj, current_advance, previous_glyph_advance, resolution=-1 +): curve_line_lengths = get_curve_line_lengths(curve_obj, resolution) total_length = 0 for cll in curve_line_lengths: @@ -76,30 +85,37 @@ def get_next_line_advance(curve_obj, current_advance, previous_glyph_advance, re return total_length return current_advance + def calc_point_on_bezier(bezier_point_1, bezier_point_2, t): p1 = bezier_point_1.co h1 = bezier_point_1.handle_right p2 = bezier_point_2.co h2 = bezier_point_2.handle_left - return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2 + return ( + ((1 - t) ** 3) * p1 + + (3 * t * (1 - t) ** 2) * h1 + + (3 * (t**2) * (1 - t)) * h2 + + (t**3) * p2 + ) + # same in slightly more lines # result is equal, performance minimally better perhaps? # def calc_point_on_bezier(bezier_point_1, bezier_point_2, ratio): - # startPoint = bezier_point_1.co - # controlPoint1 = bezier_point_1.handle_right - # controlPoint2 = bezier_point_2.handle_left - # endPoint = bezier_point_2.co +# startPoint = bezier_point_1.co +# controlPoint1 = bezier_point_1.handle_right +# controlPoint2 = bezier_point_2.handle_left +# endPoint = bezier_point_2.co - # remainder = 1 - ratio - # ratioSquared = ratio * ratio - # remainderSquared = remainder * remainder - # startPointMultiplier = remainderSquared * remainder - # controlPoint1Multiplier = remainderSquared * ratio * 3 - # controlPoint2Multiplier = ratioSquared * remainder * 3 - # endPointMultiplier = ratioSquared * ratio +# remainder = 1 - ratio +# ratioSquared = ratio * ratio +# remainderSquared = remainder * remainder +# startPointMultiplier = remainderSquared * remainder +# controlPoint1Multiplier = remainderSquared * ratio * 3 +# controlPoint2Multiplier = ratioSquared * remainder * 3 +# endPointMultiplier = ratioSquared * ratio - # return startPoint * startPointMultiplier + controlPoint1 * controlPoint1Multiplier + controlPoint2 * controlPoint2Multiplier + endPoint * endPointMultiplier +# return startPoint * startPointMultiplier + controlPoint1 * controlPoint1Multiplier + controlPoint2 * controlPoint2Multiplier + endPoint * endPointMultiplier def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t): @@ -108,15 +124,22 @@ def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t): p2 = bezier_point_2.co h2 = bezier_point_2.handle_left return ( - (-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 + - (-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2 - ).normalized() + (-3 * (1 - t) ** 2) * p1 + + (-6 * t * (1 - t) + 3 * (1 - t) ** 2) * h1 + + (-3 * (t**2) + 6 * t * (1 - t)) * h2 + + (3 * t**2) * p2 + ).normalized() + from math import acos, pi, radians, sqrt -def align_rotations_auto_pivot(mask, input_rotations, vectors, factors, local_main_axis): - output_rotations = [mathutils.Matrix().to_3x3() for _ in range(len(input_rotations))] +def align_rotations_auto_pivot( + mask, input_rotations, vectors, factors, local_main_axis +): + output_rotations = [ + mathutils.Matrix().to_3x3() for _ in range(len(input_rotations)) + ] for i in mask: vector = mathutils.Vector(vectors[i]).normalized() @@ -135,10 +158,12 @@ def align_rotations_auto_pivot(mask, input_rotations, vectors, factors, local_ma if rotation_axis.length < 1e-6: # Vectors are linearly dependent, fallback to another axis rotation_axis = (old_axis + mathutils.Matrix().to_3x3().col[2]).normalized() - + if rotation_axis.length < 1e-6: # This is now guaranteed to not be zero - rotation_axis = (-(old_axis) + mathutils.Matrix().to_3x3().col[1]).normalized() + rotation_axis = ( + -(old_axis) + mathutils.Matrix().to_3x3().col[1] + ).normalized() # full_angle = radians(sqrt((4 * pow(input_rotation.to_quaternion().dot(mathutils.Quaternion(vectors[i].normalized())), 2) - 3))) # dot = old_axis.dot(new_axis) @@ -153,8 +178,9 @@ def align_rotations_auto_pivot(mask, input_rotations, vectors, factors, local_ma return [mat.to_4x4() for mat in output_rotations] + def calc_bezier_length(bezier_point_1, bezier_point_2, resolution=20): - step = 1/resolution + step = 1 / resolution previous_p = bezier_point_1.co length = 0 for i in range(-1, resolution): @@ -164,28 +190,30 @@ def calc_bezier_length(bezier_point_1, bezier_point_2, resolution=20): previous_p = p return length -def calc_point_on_bezier_spline(bezier_spline_obj, - distance, - output_tangent = False, - resolution_factor = 1.0): + +def calc_point_on_bezier_spline( + bezier_spline_obj, distance, output_tangent=False, resolution_factor=1.0 +): # what's the point of just one point # assert len(bezier_spline_obj.bezier_points) >= 2 # however, maybe let's have it not crash and do this if len(bezier_spline_obj.bezier_points) < 1: - print("butils::calc_point_on_bezier_spline: whoops, no points. panicking. return 0,0,0") + print( + "butils::calc_point_on_bezier_spline: whoops, no points. panicking. return 0,0,0" + ) if output_tangent: - return mathutils.Vector((0,0,0)), mathutils.Vector((1,0,0)) + return mathutils.Vector((0, 0, 0)), mathutils.Vector((1, 0, 0)) else: - return mathutils.Vector((0,0,0)) + return mathutils.Vector((0, 0, 0)) if len(bezier_spline_obj.bezier_points) == 1: p = bezier_spline_obj.bezier_points[0] travel = (p.handle_left - p.co).normalized() * distance if output_tangent: - tangent = mathutils.Vector((1,0,0)) + tangent = mathutils.Vector((1, 0, 0)) return travel, tangent else: return travel - + if distance <= 0: p = bezier_spline_obj.bezier_points[0] travel = (p.co - p.handle_left).normalized() * distance @@ -202,30 +230,30 @@ def calc_point_on_bezier_spline(bezier_spline_obj, total_length = 0 n_bezier_points = len(bezier_spline_obj.bezier_points) for i in range(0, len(bezier_spline_obj.bezier_points) - 1): - bezier = [ bezier_spline_obj.bezier_points[i], - bezier_spline_obj.bezier_points[i + 1] ] - length = calc_bezier_length(bezier[0], - bezier[1], - int(bezier_spline_obj.resolution_u * resolution_factor)) - total_length += length - beziers.append(bezier) - lengths.append(length) - # if total_length > distance: - # break + bezier = [ + bezier_spline_obj.bezier_points[i], + bezier_spline_obj.bezier_points[i + 1], + ] + length = calc_bezier_length( + bezier[0], + bezier[1], + int(bezier_spline_obj.resolution_u * resolution_factor), + ) + total_length += length + beziers.append(bezier) + lengths.append(length) + # if total_length > distance: + # break iterated_distance = 0 for i in range(0, len(beziers)): if iterated_distance + lengths[i] > distance: - distance_on_bezier = (distance - iterated_distance) + distance_on_bezier = distance - iterated_distance d = distance_on_bezier / lengths[i] # print(f"i: {i}, d: {d}, distance_on_bezier: {distance_on_bezier}, distance: {distance}") - location = calc_point_on_bezier(beziers[i][0], - beziers[i][1], - d) + location = calc_point_on_bezier(beziers[i][0], beziers[i][1], d) if output_tangent: - tangent = calc_tangent_on_bezier(beziers[i][0], - beziers[i][1], - d) + tangent = calc_tangent_on_bezier(beziers[i][0], beziers[i][1], d) return location, tangent else: return location @@ -236,19 +264,19 @@ def calc_point_on_bezier_spline(bezier_spline_obj, travel = (p.handle_right - p.co).normalized() * (distance - total_length) location = p.co + travel if output_tangent: - tangent = calc_tangent_on_bezier(beziers[last_i][0], - p, - 1) + tangent = calc_tangent_on_bezier(beziers[last_i][0], p, 1) return location, tangent else: return location -def calc_point_on_bezier_curve(bezier_curve_obj, - distance, - output_tangent = False, - output_spline_index = False, - resolution_factor = 1.0): +def calc_point_on_bezier_curve( + bezier_curve_obj, + distance, + output_tangent=False, + output_spline_index=False, + resolution_factor=1.0, +): curve = bezier_curve_obj.data # Loop through all splines in the curve @@ -260,36 +288,35 @@ def calc_point_on_bezier_curve(bezier_curve_obj, if output_spline_index and output_tangent: # return value from c_p_o_b_s is a tuple # so we need to append tuple + tuple - return calc_point_on_bezier_spline(spline, - (distance - total_length), - output_tangent, - resolution_factor) + (i,) + return calc_point_on_bezier_spline( + spline, (distance - total_length), output_tangent, resolution_factor + ) + (i,) if output_spline_index and not output_tangent: # return value from c_p_o_b_s is a location vector # so we need to append with a comma - return calc_point_on_bezier_spline(spline, - (distance - total_length), - output_tangent, - resolution_factor), i + return ( + calc_point_on_bezier_spline( + spline, + (distance - total_length), + output_tangent, + resolution_factor, + ), + i, + ) else: - return calc_point_on_bezier_spline(spline, - (distance - total_length), - output_tangent, - resolution_factor) + return calc_point_on_bezier_spline( + spline, (distance - total_length), output_tangent, resolution_factor + ) total_length += length # TODO: can this fail? # def get_objects_by_name(name, startswith="", endswith=""): - # return [obj for obj in bpy.context.scene.objects if obj.name.startswith(startswith) and if obj.name.endswith(endswith)] +# return [obj for obj in bpy.context.scene.objects if obj.name.startswith(startswith) and if obj.name.endswith(endswith)] -def find_objects_by_name( - objects, - equals="", - contains="", - startswith="", - endswith=""): + +def find_objects_by_name(objects, equals="", contains="", startswith="", endswith=""): # handle equals if equals != "": index = objects.find(equals) @@ -297,48 +324,55 @@ def find_objects_by_name( return [objects[index]] return [] # handle others is more permissive - return [obj for obj in objects if obj.name.startswith(startswith) and obj.name.endswith(endswith) and obj.name.find(contains) >= 0] + return [ + obj + for obj in objects + if obj.name.startswith(startswith) + and obj.name.endswith(endswith) + and obj.name.find(contains) >= 0 + ] + + +def find_objects_by_custom_property(objects, property_name="", property_value=""): + return [ + obj + for obj in objects + if property_name in obj and obj[property_name] == property_value + ] -def find_objects_by_custom_property( - objects, - property_name="", - property_value=""): - return [obj for obj in objects if property_name in obj and obj[property_name] == property_value] def turn_collection_hierarchy_into_path(obj): parent_collection = obj.users_collection[0] - parent_names = [] + parent_names = [] parent_names.append(parent_collection.name) get_parent_collection_names(parent_collection, parent_names) parent_names.reverse() - return '\\'.join(parent_names) + return "\\".join(parent_names) + def find_font_object(fontcollection, font_name): - fonts = find_objects_by_custom_property(fontcollection.objects, - "is_font", - True) + fonts = find_objects_by_custom_property(fontcollection.objects, "is_font", True) for font in fonts: if font["font_name"] == font_name and font.parent == None: return font return None + def find_font_face_object(font_obj, face_name): - faces = find_objects_by_custom_property(font_obj.children, - "is_face", - True) + faces = find_objects_by_custom_property(font_obj.children, "is_face", True) for face in faces: if face["face_name"] == face_name: return face return None + def move_in_fontcollection(obj, fontcollection, allow_duplicates=False): # parent nesting structure # the font object - font_obj = find_font_object(fontcollection, - obj["font_name"]) + font_obj = find_font_object(fontcollection, obj["font_name"]) if font_obj == None: font_obj = bpy.data.objects.new(obj["font_name"], None) - font_obj.empty_display_type = 'PLAIN_AXES' + font_obj.empty_display_type = "PLAIN_AXES" fontcollection.objects.link(font_obj) # ensure custom properties are set @@ -346,11 +380,10 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False): font_obj["is_font"] = True # the face object as a child of font object - face_obj = find_font_face_object(font_obj, - obj["face_name"]) + face_obj = find_font_face_object(font_obj, obj["face_name"]) if face_obj == None: face_obj = bpy.data.objects.new(obj["face_name"], None) - face_obj.empty_display_type = 'PLAIN_AXES' + face_obj.empty_display_type = "PLAIN_AXES" face_obj["is_face"] = True fontcollection.objects.link(face_obj) @@ -365,7 +398,7 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False): glyphs_objs = find_objects_by_name(face_obj.children, startswith="glyphs") if len(glyphs_objs) <= 0: glyphs_obj = bpy.data.objects.new("glyphs", None) - glyphs_obj.empty_display_type = 'PLAIN_AXES' + glyphs_obj.empty_display_type = "PLAIN_AXES" fontcollection.objects.link(glyphs_obj) glyphs_obj.parent = face_obj elif len(glyphs_objs) > 1: @@ -377,9 +410,11 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False): glyphs_obj["font_name"] = obj["font_name"] def get_hash(o): - return hash(tuple(tuple(v.co) for v in o.data.vertices )) + return hash(tuple(tuple(v.co) for v in o.data.vertices)) - for other_obj in find_objects_by_custom_property(glyphs_obj.children, "glyph", obj["glyph"]): + for other_obj in find_objects_by_custom_property( + glyphs_obj.children, "glyph", obj["glyph"] + ): if get_hash(other_obj) == get_hash(obj) and not allow_duplicates: return other_obj @@ -394,14 +429,16 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False): return obj + def bpy_to_abspath(blender_path): return os.path.realpath(bpy.path.abspath(blender_path)) + def register_font_from_filepath(filepath): from .bimport import get_font_faces_in_file availables = get_font_faces_in_file(filepath) - + fonts = {} for a in availables: font_name = a["font_name"] @@ -414,22 +451,28 @@ def register_font_from_filepath(filepath): fonts[font_name][face_name].append(glyph) for font_name in fonts: for face_name in fonts[font_name]: - Font.register_font(font_name, - face_name, - fonts[font_name][face_name], - filepath) + Font.register_font( + font_name, face_name, fonts[font_name][face_name], filepath + ) + def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): if not filepath.endswith(".glb") and not filepath.endswith(".gltf"): - ShowMessageBox(f"Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file") + ShowMessageBox( + f"Font loading error", + "ERROR", + f"Filepath({filepath}) is not a *.glb or *.gltf file", + ) return False marker_property = "font_import" - bpy.ops.abc3d.import_font_gltf(filepath=filepath, - glyphs=glyphs, - marker_property=marker_property, - font_name=font_name, - face_name=face_name) + bpy.ops.abc3d.import_font_gltf( + filepath=filepath, + glyphs=glyphs, + marker_property=marker_property, + font_name=font_name, + face_name=face_name, + ) fontcollection = bpy.data.collections.get("ABC3D") if fontcollection is None: @@ -448,25 +491,19 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): font_name = o["font_name"] face_name = o["face_name"] - glyph_obj = move_in_fontcollection( - o, - fontcollection) + glyph_obj = move_in_fontcollection(o, fontcollection) glyph_obj_pointer = bpy.types.PointerProperty(glyph_obj) if glyph_obj == o: del o[marker_property] - Font.add_glyph( - font_name, - face_name, - glyph_id, - glyph_obj_pointer) + Font.add_glyph(font_name, face_name, glyph_id, glyph_obj_pointer) for c in o.children: if is_metrics_object(c): - add_metrics_obj_from_bound_box(glyph_obj, - bound_box_as_array(c.bound_box)) - modified_font_faces.append({"font_name": font_name, - "face_name": face_name}) + add_metrics_obj_from_bound_box( + glyph_obj, bound_box_as_array(c.bound_box) + ) + modified_font_faces.append({"font_name": font_name, "face_name": face_name}) for mff in modified_font_faces: glyphs = [] @@ -494,6 +531,7 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): # completely_delete_objects(remove_list) + def update_available_fonts(): abc3d_data = bpy.context.scene.abc3d_data @@ -509,26 +547,30 @@ def update_available_fonts(): f.face_name = face_name print(f"{__name__} added {font_name} {face_name}") + # def update_available_texts(): - # abc3d_data = bpy.context.scene.abc3d_data - # for o in bpy.context.scene.objects: - # 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 +# abc3d_data = bpy.context.scene.abc3d_data +# for o in bpy.context.scene.objects: +# 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 + def getPreferences(context): preferences = context.preferences - return preferences.addons['abc3d'].preferences + return preferences.addons["abc3d"].preferences + # clear available fonts def clear_available_fonts(): bpy.context.scene.abc3d_data.available_fonts.clear() + def load_installed_fonts(): preferences = getPreferences(bpy.context) - font_dir = os.path.join(preferences.assets_dir,"fonts") + font_dir = os.path.join(preferences.assets_dir, "fonts") if os.path.exists(font_dir): for file in os.listdir(font_dir): if file.endswith(".glb") or file.endswith(".gltf"): @@ -536,13 +578,14 @@ def load_installed_fonts(): # ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}") # print(f"loading font from {font_path}") # for f in bpy.context.scene.abc3d_data.available_fonts.values(): - # print(f"available font: {f.font_name} {f.face_name}") + # print(f"available font: {f.font_name} {f.face_name}") register_font_from_filepath(font_path) load_font_from_filepath(font_path) + def register_installed_fonts(): preferences = getPreferences(bpy.context) - font_dir = os.path.join(preferences.assets_dir,"fonts") + font_dir = os.path.join(preferences.assets_dir, "fonts") if os.path.exists(font_dir): for file in os.listdir(font_dir): if file.endswith(".glb") or file.endswith(".gltf"): @@ -550,13 +593,14 @@ def register_installed_fonts(): # ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}") # print(f"loading font from {font_path}") # for f in bpy.context.scene.abc3d_data.available_fonts.values(): - # print(f"available font: {f.font_name} {f.face_name}") + # print(f"available font: {f.font_name} {f.face_name}") register_font_from_filepath(font_path) + message_memory = [] -def ShowMessageBox(title = "Message Box", icon = 'INFO', message="", prevent_repeat=False): +def ShowMessageBox(title="Message Box", icon="INFO", message="", prevent_repeat=False): """Show a simple message box taken from `Link here `_ @@ -567,10 +611,10 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message="", prevent_rep :type icon: str :param message: lines of text to display, a.k.a. the message :type message: str or (str, str, ..) - + TIP: Check `Link blender icons `_ for icons you can use TIP: Or even better, check `Link this addons `_ to also see the icons. - + usage: .. code-block:: python myLines=("line 1","line 2","line 3") @@ -579,7 +623,7 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message="", prevent_rep or: .. code-block:: python butils.ShowMessageBox(title="",message=("AAAAAH","NOOOOO"),icon=) - + """ global message_memory if prevent_repeat: @@ -588,18 +632,22 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message="", prevent_rep print("PREVENT PREVENT") return message_memory.append([title, icon, message]) - myLines=message + myLines = message + def draw(self, context): if isinstance(myLines, str): self.layout.label(text=myLines) elif hasattr(myLines, "__iter__"): for n in myLines: self.layout.label(text=n) - bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) + + bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) + def simply_delete_objects(objs): completely_delete_objects(objs) + def completely_delete_objects(objs, recursive=True): for g in objs: if type(g) != type(None): @@ -617,33 +665,43 @@ def completely_delete_objects(objs, recursive=True): # not important pass + def is_mesh(o): return type(o.data) == bpy.types.Mesh + def is_metrics_object(o): if f"{utils.prefix()}_type" in o: - return o[f"{utils.prefix()}_type"] == 'metrics' - return (re.match(".*_metrics$", o.name) != None or re.match(".*_metrics.[\d]{3}$", o.name) != None) and is_mesh(o) + return o[f"{utils.prefix()}_type"] == "metrics" + return ( + re.match(".*_metrics$", o.name) != None + or re.match(".*_metrics.[\d]{3}$", o.name) != None + ) and is_mesh(o) + def is_text_object(o): if f"{utils.prefix()}_type" in o: - return o[f"{utils.prefix()}_type"] == 'textobject' + return o[f"{utils.prefix()}_type"] == "textobject" for t in bpy.context.scene.abc3d_data.available_texts: if o == t.text_object: return True return False + def is_glyph(o): if f"{utils.prefix()}_type" in o: - return o[f"{utils.prefix()}_type"] == 'glyph' + return o[f"{utils.prefix()}_type"] == "glyph" try: - return type(o.parent) is not type(None) \ - and "glyphs" in o.parent.name \ - and is_mesh(o) \ - and not is_metrics_object(o) + return ( + type(o.parent) is not type(None) + and "glyphs" in o.parent.name + and is_mesh(o) + and not is_metrics_object(o) + ) except ReferenceError as e: return False + def update_types(): scene = bpy.context.scene abc3d_data = scene.abc3d_data @@ -652,33 +710,36 @@ def update_types(): for g in t.glyphs: g.glyph_object[f"{utils.prefix()}_type"] = "glyph" + # blender bound_box vertices -# -# 3------7. +# +# 3------7. # |`. | `. +y # | `2------6 | # | | | | | # 0---|--4. | +--- +x -# `. | `.| `. +# `. | `.| `. # `1------5 `+z + def get_glyph_advance(glyph_obj): for c in glyph_obj.children: if is_metrics_object(c): 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_height(glyph_obj): for c in glyph_obj.children: if is_metrics_object(c): return abs(c.bound_box[0][1] - c.bound_box[3][1]) return abs(glyph_obj.bound_box[0][1] - glyph_obj.bound_box[3][1]) + def prepare_text(font_name, face_name, text, allow_replacement=True): loaded, missing, loadable, files = Font.test_glyphs_availability( - font_name, - face_name, - text) + font_name, face_name, text + ) # possibly replace upper and lower case letters with each other if len(missing) > 0 and allow_replacement: replacement_search = "" @@ -694,16 +755,18 @@ def prepare_text(font_name, face_name, text, allow_replacement=True): load_font_from_filepath(filepath, loadable, font_name, face_name) return True + def is_bezier(curve): - if curve.type != 'CURVE': + if curve.type != "CURVE": return False if len(curve.data.splines) < 1: return False for spline in curve.data.splines: - if spline.type != 'BEZIER': + if spline.type != "BEZIER": return False return True + def will_regenerate(text_properties): mom = text_properties.text_object @@ -713,7 +776,7 @@ def will_regenerate(text_properties): for i, g in enumerate(text_properties.glyphs): if not hasattr(g.glyph_object, "type"): return True - elif g.glyph_object.type != 'EMPTY': + elif g.glyph_object.type != "EMPTY": return True # check if perhaps one glyph was deleted elif type(g.glyph_object) == type(None): @@ -724,8 +787,11 @@ def will_regenerate(text_properties): return True elif len(text_properties.text) > i and g.glyph_id != text_properties.text[i]: return True - elif len(text_properties.text) > i and (g.glyph_object[f"{utils.prefix()}_font_name"] != text_properties.font_name - or g.glyph_object[f"{utils.prefix()}_face_name"] != text_properties.face_name): + elif len(text_properties.text) > i and ( + g.glyph_object[f"{utils.prefix()}_font_name"] != text_properties.font_name + or g.glyph_object[f"{utils.prefix()}_face_name"] + != text_properties.face_name + ): return True return False @@ -750,22 +816,22 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) if mom.type != "CURVE": return False - distribution_type = 'CALCULATE' if is_bezier(mom) else 'FOLLOW_PATH' + distribution_type = "CALCULATE" if is_bezier(mom) else "FOLLOW_PATH" # 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': + if distribution_type == "CALCULATE": mom.data.use_path = False - elif distribution_type == 'FOLLOW_PATH': + 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"): - glyph_objects = [ g["glyph_object"] for g in text_properties["glyphs"] ] + glyph_objects = [g["glyph_object"] for g in text_properties["glyphs"]] completely_delete_objects(glyph_objects, True) text_properties.glyphs.clear() @@ -777,47 +843,53 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) 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 - if c == '\\': + if c == "\\": is_command = True continue is_newline = False if is_command: - if c == 'n': + if c == "n": 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") + print( + f"would like to add new line for {text_properties.text} please" + ) # TODO: add a new line advance = next_line_advance + text_properties.offset continue is_command = False glyph_id = c - glyph_tmp = Font.get_glyph(text_properties.font_name, - text_properties.face_name, - glyph_id) + glyph_tmp = Font.get_glyph( + text_properties.font_name, text_properties.face_name, glyph_id + ) if glyph_tmp == None: space_width = Font.is_space(glyph_id) if space_width != False: advance = advance + space_width * text_properties.font_size continue - message=f"Glyph not found for font_name='{text_properties.font_name}' face_name='{text_properties.face_name}' glyph_id='{glyph_id}'" + 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) + glyph_tmp = Font.get_glyph( + text_properties.font_name, + text_properties.face_name, + possible_replacement, + ) if glyph_tmp != None: message = message + f" (replaced with '{possible_replacement}')" replaced = True - - ShowMessageBox(title="Glyph replaced" if replaced else "Glyph missing", - icon='INFO' if replaced else 'ERROR', - message=message, - prevent_repeat=True) + + ShowMessageBox( + title="Glyph replaced" if replaced else "Glyph missing", + icon="INFO" if replaced else "ERROR", + message=message, + prevent_repeat=True, + ) if replaced == False: continue glyph = glyph_tmp.original @@ -837,8 +909,8 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) if c.name.startswith(f"{glyph_id}_mesh"): obg = c - if distribution_type == 'FOLLOW_PATH': - ob.constraints.new(type='FOLLOW_PATH') + if distribution_type == "FOLLOW_PATH": + 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 @@ -846,29 +918,33 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) ob.constraints["Follow Path"].forward_axis = "FORWARD_X" ob.constraints["Follow Path"].up_axis = "UP_Y" spline_index = 0 - elif distribution_type == 'CALCULATE': + elif distribution_type == "CALCULATE": previous_ob_rotation_mode = None previous_obg_rotation_mode = None - if ob.rotation_mode != 'QUATERNION': - ob.rotation_mode = 'QUATERNION' + 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' + if obg.rotation_mode != "QUATERNION": + obg.rotation_mode = "QUATERNION" previous_obg_rotation_mode = obg.rotation_mode - location, tangent, spline_index = calc_point_on_bezier_curve(mom, advance, True, True) + location, tangent, spline_index = calc_point_on_bezier_curve( + mom, advance, True, True + ) if spline_index != previous_spline_index: is_newline = True if regenerate: - ob.location = mom.matrix_world @ (location + text_properties.translation) + ob.location = mom.matrix_world @ ( + 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) + ob.location = location + text_properties.translation if not text_properties.ignore_orientation: mask = [0] @@ -876,24 +952,26 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) vectors = [tangent] factors = [1.0] local_main_axis = mathutils.Vector((1.0, 0.0, 0.0)) - motor = align_rotations_auto_pivot(mask, - input_rotations, - vectors, - factors, - local_main_axis) + motor = align_rotations_auto_pivot( + mask, input_rotations, vectors, factors, local_main_axis + ) q = mathutils.Quaternion() q.rotate(text_properties.orientation) if regenerate: obg.rotation_quaternion = q - ob.rotation_quaternion = (mom.matrix_world @ motor[0]).to_quaternion() + ob.rotation_quaternion = ( + mom.matrix_world @ motor[0] + ).to_quaternion() else: ob.rotation_quaternion = motor[0].to_quaternion() else: q = mathutils.Quaternion() q.rotate(text_properties.orientation) # obg.rotation_quaternion = q - obg.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion() + obg.rotation_quaternion = ( + mom.matrix_world @ q.to_matrix().to_4x4() + ).to_quaternion() # ob.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion() if previous_ob_rotation_mode: @@ -901,29 +979,45 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) if previous_obg_rotation_mode: obg.rotation_mode = previous_obg_rotation_mode - glyph_advance = get_glyph_advance(glyph) * scalor + text_properties.letter_spacing + glyph_advance = ( + get_glyph_advance(glyph) * scalor + text_properties.letter_spacing + ) # now we need to compensate for curvature # otherwise letters will be closer together the curvier the bezier is # this could be done more efficiently, but whatever curve_compensation = 0 - if distribution_type == 'CALCULATE' and (not is_newline or spline_index == 0): # TODO: fix newline hack + 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) - new_location, si = calc_point_on_bezier_curve(mom, advance + glyph_advance, False, True) + previous_location, psi = calc_point_on_bezier_curve( + mom, advance, False, True + ) + new_location, si = calc_point_on_bezier_curve( + mom, advance + glyph_advance, False, True + ) if psi == si: - while (previous_location - new_location).length > glyph_advance and psi == si: + while ( + previous_location - new_location + ).length > glyph_advance and psi == si: curve_compensation = curve_compensation - glyph_advance * 0.01 - new_location, si = calc_point_on_bezier_curve(mom, - advance + glyph_advance + curve_compensation, - output_tangent=False, - output_spline_index=True) - while (previous_location - new_location).length < glyph_advance and psi == si: + new_location, si = calc_point_on_bezier_curve( + mom, + advance + glyph_advance + curve_compensation, + output_tangent=False, + output_spline_index=True, + ) + while ( + previous_location - new_location + ).length < glyph_advance and psi == si: curve_compensation = curve_compensation + glyph_advance * 0.01 - new_location, si = calc_point_on_bezier_curve(mom, - advance + glyph_advance + curve_compensation, - output_tangent=False, - output_spline_index=True) + new_location, si = calc_point_on_bezier_curve( + mom, + advance + glyph_advance + curve_compensation, + output_tangent=False, + output_spline_index=True, + ) ob.scale = (scalor, scalor, scalor) @@ -947,9 +1041,13 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) mom[f"{utils.prefix()}_translation"] = text_properties.translation if "lock_depsgraph_update_ntimes" in bpy.context.scene.abc3d_data: - bpy.context.scene.abc3d_data["lock_depsgraph_update_ntimes"] += len(bpy.context.selected_objects) + bpy.context.scene.abc3d_data["lock_depsgraph_update_ntimes"] += len( + bpy.context.selected_objects + ) else: - bpy.context.scene.abc3d_data["lock_depsgraph_update_ntimes"] = len(bpy.context.selected_objects) + bpy.context.scene.abc3d_data["lock_depsgraph_update_ntimes"] = 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). @@ -961,6 +1059,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) bpy.app.timers.unregister(reset) molotov = reset_depsgraph_n + 0 + def counted_reset(scene, depsgraph): nonlocal molotov if molotov == 0: @@ -987,20 +1086,23 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) 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], - } + 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 @@ -1011,6 +1113,7 @@ def transfer_text_properties_to_text_object(text_properties, o): 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"] @@ -1021,15 +1124,17 @@ def transfer_text_object_to_text_properties(o, text_properties): text_properties["translation"] = o[f"{utils.prefix()}_translation"] text_properties["text"] = o[f"{utils.prefix()}_text"] + # blender bound_box vertices -# -# 3------7. +# +# 3------7. # |`. | `. +y # | `2------6 -z | # | | | | `. | # 0---|--4. | `+--- +x -# `. | `.| -# `1------5 +# `. | `.| +# `1------5 + def add_metrics_obj_from_bound_box(glyph, bound_box=None): mesh = bpy.data.meshes.new(f"{glyph.name}_metrics") # add the new mesh @@ -1054,49 +1159,78 @@ def add_metrics_obj_from_bound_box(glyph, bound_box=None): if type(bound_box) == type(None): bound_box = glyph.bound_box - - verts = [bound_box[0], - bound_box[1], - bound_box[2], - bound_box[3], - bound_box[4], - bound_box[5], - bound_box[6], - bound_box[7], - ] - edges = [[0,1],[1,2],[2,3],[3,0], - [0,4],[1,5],[2,6],[3,7], - [4,5],[5,6],[6,7],[7,4], - ] + + verts = [ + bound_box[0], + bound_box[1], + bound_box[2], + bound_box[3], + bound_box[4], + bound_box[5], + bound_box[6], + bound_box[7], + ] + edges = [ + [0, 1], + [1, 2], + [2, 3], + [3, 0], + [0, 4], + [1, 5], + [2, 6], + [3, 7], + [4, 5], + [5, 6], + [6, 7], + [7, 4], + ] faces = [] - + mesh.from_pydata(verts, edges, faces) + def add_faces_to_metrics(obj): mesh = bpy.data.meshes.new(f"{obj.name}") # add the new mesh print(f"add_faces_to_metrics for {obj.name}") bound_box = bound_box_as_array(obj.bound_box) - verts = [bound_box[0], - bound_box[1], - bound_box[2], - bound_box[3], - bound_box[4], - bound_box[5], - bound_box[6], - bound_box[7], - ] - edges = [[0,1],[1,2],[2,3],[3,0], - [0,4],[1,5],[2,6],[3,7], - [4,5],[5,6],[6,7],[7,4], - ] + verts = [ + bound_box[0], + bound_box[1], + bound_box[2], + bound_box[3], + bound_box[4], + bound_box[5], + bound_box[6], + bound_box[7], + ] + edges = [ + [0, 1], + [1, 2], + [2, 3], + [3, 0], + [0, 4], + [1, 5], + [2, 6], + [3, 7], + [4, 5], + [5, 6], + [6, 7], + [7, 4], + ] faces = [ - [0,1,2], [2,3,0], - [2,6,7], [7,3,2], - [6,5,4], [4,7,6], - [4,5,1], [0,4,1], - [1,5,6], [1,6,2], - [4,0,7], [7,0,3], - ] + [0, 1, 2], + [2, 3, 0], + [2, 6, 7], + [7, 3, 2], + [6, 5, 4], + [4, 7, 6], + [4, 5, 1], + [0, 4, 1], + [1, 5, 6], + [1, 6, 2], + [4, 0, 7], + [7, 0, 3], + ] mesh.from_pydata(verts, edges, faces) @@ -1104,24 +1238,35 @@ def add_faces_to_metrics(obj): obj.data = mesh bpy.data.meshes.remove(old_mesh) + def remove_faces_from_metrics(obj): mesh = bpy.data.meshes.new(f"{obj.name}") # add the new mesh bound_box = bound_box_as_array(obj.bound_box) - verts = [bound_box[0], - bound_box[1], - bound_box[2], - bound_box[3], - bound_box[4], - bound_box[5], - bound_box[6], - bound_box[7], - ] - edges = [[0,1],[1,2],[2,3],[3,0], - [0,4],[1,5],[2,6],[3,7], - [4,5],[5,6],[6,7],[7,4], - ] - faces = [ - ] + verts = [ + bound_box[0], + bound_box[1], + bound_box[2], + bound_box[3], + bound_box[4], + bound_box[5], + bound_box[6], + bound_box[7], + ] + edges = [ + [0, 1], + [1, 2], + [2, 3], + [3, 0], + [0, 4], + [1, 5], + [2, 6], + [3, 7], + [4, 5], + [5, 6], + [6, 7], + [7, 4], + ] + faces = [] mesh.from_pydata(verts, edges, faces) @@ -1129,59 +1274,63 @@ def remove_faces_from_metrics(obj): obj.data = mesh bpy.data.meshes.remove(old_mesh) + # duplicate # def remove_metrics_from_selection(): - # for o in bpy.context.selected_objects: - # is_possibly_glyph = is_mesh(o) - # if is_possibly_glyph: - # metrics = [] - # for c in o.children: - # if is_metrics_object(c): - # metrics.append(c) - # completely_delete_objects(metrics) +# for o in bpy.context.selected_objects: +# is_possibly_glyph = is_mesh(o) +# if is_possibly_glyph: +# metrics = [] +# for c in o.children: +# if is_metrics_object(c): +# metrics.append(c) +# completely_delete_objects(metrics) -def get_max_bound_box(bb_1, bb_2 = None): + +def get_max_bound_box(bb_1, bb_2=None): if type(bb_2) == type(None): bb_2 = bb_1 x_max = max(bb_1[4][0], bb_2[4][0]) x_min = min(bb_1[0][0], bb_2[0][0]) - y_max = max(bb_1[3][1], bb_2[3][1]) - y_min = min(bb_1[0][1], bb_2[0][1]) + y_max = max(bb_1[3][1], bb_2[3][1]) + y_min = min(bb_1[0][1], bb_2[0][1]) z_max = max(bb_1[1][2], bb_2[1][2]) z_min = min(bb_1[0][2], bb_2[0][2]) return [ - mathutils.Vector((x_min, y_min, z_min)), - mathutils.Vector((x_min, y_min, z_max)), - mathutils.Vector((x_min, y_max, z_max)), - mathutils.Vector((x_min, y_max, z_min)), + mathutils.Vector((x_min, y_min, z_min)), + mathutils.Vector((x_min, y_min, z_max)), + mathutils.Vector((x_min, y_max, z_max)), + mathutils.Vector((x_min, y_max, z_min)), + mathutils.Vector((x_max, y_min, z_min)), + mathutils.Vector((x_max, y_min, z_max)), + mathutils.Vector((x_max, y_max, z_max)), + mathutils.Vector((x_max, y_max, z_min)), + ] - mathutils.Vector((x_max, y_min, z_min)), - mathutils.Vector((x_max, y_min, z_max)), - mathutils.Vector((x_max, y_max, z_max)), - mathutils.Vector((x_max, y_max, z_min)), - ] # blender bound_box vertices -# -# 3------7. +# +# 3------7. # |`. | `. +y # | `2------6 | # | | | | | # 0---|--4. | +--- +x -# `. | `.| `. +# `. | `.| `. # `1------5 `+z + # why not [ [0] * 3 ] * 8 # https://stackoverflow.com/questions/2397141/how-to-initialize-a-two-dimensional-array-list-of-lists-if-not-using-numpy-in def bound_box_as_array(bound_box): - array = [ [0] * 3 for i in range(8) ] + array = [[0] * 3 for i in range(8)] for i in range(0, len(bound_box)): for j in range(0, len(bound_box[i])): array[i][j] = bound_box[i][j] return array + ## -# @brief get_metrics_bound_box +# @brief get_metrics_bound_box # generates a metrics bounding box # where x-width comes from bb # and y-height + z-depth from bb_uebermetrics @@ -1207,6 +1356,7 @@ def get_metrics_bound_box(bb, bb_uebermetrics): metrics[7][0] = bb[7][0] return metrics + def get_metrics_object(o): if is_glyph(o): for c in o.children: @@ -1214,15 +1364,17 @@ def get_metrics_object(o): return c return None + def get_original(o): if hasattr(o, "original"): return o.original else: return o + def add_default_metrics_to_objects(objects=None, overwrite_existing=False): if type(objects) == type(None): - objects=bpy.context.selected_objects + objects = bpy.context.selected_objects targets = [] reference_bound_box = None for o in objects: @@ -1235,21 +1387,28 @@ def add_default_metrics_to_objects(objects=None, overwrite_existing=False): if len(metrics) == 0: targets.append(o) - reference_bound_box = get_max_bound_box(o.bound_box, reference_bound_box) + reference_bound_box = get_max_bound_box( + o.bound_box, reference_bound_box + ) elif len(metrics) >= 0 and overwrite_existing: completely_delete_objects(metrics) targets.append(o) - reference_bound_box = get_max_bound_box(o.bound_box, reference_bound_box) + reference_bound_box = get_max_bound_box( + o.bound_box, reference_bound_box + ) else: for m in metrics: - reference_bound_box = get_max_bound_box(m.bound_box, reference_bound_box) + reference_bound_box = get_max_bound_box( + m.bound_box, reference_bound_box + ) for t in targets: bound_box = get_metrics_bound_box(t.bound_box, reference_bound_box) add_metrics_obj_from_bound_box(t, bound_box) + def remove_metrics_from_objects(objects=None): if type(objects) == type(None): - objects=bpy.context.selected_objects + objects = bpy.context.selected_objects metrics = [] for o in objects: for c in o.children: @@ -1257,9 +1416,10 @@ def remove_metrics_from_objects(objects=None): metrics.append(c) completely_delete_objects(metrics) + def align_metrics_of_objects_to_active_object(objects=None): if type(objects) == type(None): - objects=bpy.context.selected_objects + objects = bpy.context.selected_objects if len(objects) == 0: return "no objects selected" @@ -1279,7 +1439,7 @@ def align_metrics_of_objects_to_active_object(objects=None): # do it for o in objects: is_possibly_glyph = is_glyph(o) - if is_possibly_glyph: + if is_possibly_glyph and not o is bpy.context.active_object: metrics = [] for c in o.children: if is_metrics_object(c): @@ -1287,20 +1447,19 @@ def align_metrics_of_objects_to_active_object(objects=None): bb = None if len(metrics) == 0: - bb = get_metrics_bound_box(o.bound_box, - reference_bound_box) + bb = get_metrics_bound_box(o.bound_box, reference_bound_box) else: - bb = get_metrics_bound_box(metrics[0].bound_box, - reference_bound_box) + bb = get_metrics_bound_box(metrics[0].bound_box, reference_bound_box) if len(metrics) > 0: completely_delete_objects(metrics) - + add_metrics_obj_from_bound_box(o, bb) return "" + def align_metrics_of_objects(objects=None): if type(objects) == type(None): - objects=bpy.context.selected_objects + objects = bpy.context.selected_objects if len(objects) == 0: return "no objects selected" @@ -1315,11 +1474,13 @@ def align_metrics_of_objects(objects=None): metrics.append(c) if len(metrics) == 0: - reference_bound_box = get_max_bound_box(o.bound_box, - reference_bound_box) + reference_bound_box = get_max_bound_box( + o.bound_box, reference_bound_box + ) elif len(metrics) > 0: - reference_bound_box = get_max_bound_box(metrics[0].bound_box, - reference_bound_box) + reference_bound_box = get_max_bound_box( + metrics[0].bound_box, reference_bound_box + ) targets.append(o) for t in targets: metrics = [] @@ -1328,13 +1489,10 @@ def align_metrics_of_objects(objects=None): metrics.append(c) bound_box = None if len(metrics) == 0: - bound_box = get_metrics_bound_box(t.bound_box, - reference_bound_box) + bound_box = get_metrics_bound_box(t.bound_box, reference_bound_box) else: - bound_box = get_metrics_bound_box(metrics[0].bound_box, - reference_bound_box) + bound_box = get_metrics_bound_box(metrics[0].bound_box, reference_bound_box) completely_delete_objects(metrics) - + add_metrics_obj_from_bound_box(t, bound_box) return "" - diff --git a/common/utils.py b/common/utils.py index d1eab92..0c99dfd 100644 --- a/common/utils.py +++ b/common/utils.py @@ -1,25 +1,33 @@ # NOTE: also change version in ../__init__.py def get_version_major(): return 0 + + def get_version_minor(): return 0 + + def get_version_patch(): return 5 + + def get_version_string(): return f"{get_version_major()}.{get_version_minor()}.{get_version_patch}" + + def prefix(): return "ABC3D" -import time + import datetime -from mathutils import ( - Vector, - ) +import time + +from mathutils import Vector + def get_timestamp(): - return datetime.datetime \ - .fromtimestamp(time.time()) \ - .strftime('%Y.%m.%d-%H:%M:%S') + return datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d-%H:%M:%S") + def mapRange(in_value, in_min, in_max, out_min, out_max, clamp=False): output = out_min + ((out_max - out_min) / (in_max - in_min)) * (in_value - in_min) @@ -32,36 +40,43 @@ def mapRange(in_value, in_min, in_max, out_min, out_max, clamp=False): return output -import warnings import functools +import warnings + def deprecated(func): """This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used.""" + @functools.wraps(func) def new_func(*args, **kwargs): - warnings.simplefilter('always', DeprecationWarning) # turn off filter - warnings.warn("Call to deprecated function {}.".format(func.__name__), - category=DeprecationWarning, - stacklevel=2) - warnings.simplefilter('default', DeprecationWarning) # reset filter + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + "Call to deprecated function {}.".format(func.__name__), + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter return func(*args, **kwargs) + return new_func import subprocess import sys + + def open_file_browser(directory): - if sys.platform=='win32': + if sys.platform == "win32": os.startfile(directory) - - elif sys.platform=='darwin': - subprocess.Popen(['open', directory]) - + + elif sys.platform == "darwin": + subprocess.Popen(["open", directory]) + else: try: - subprocess.Popen(['xdg-open', directory]) + subprocess.Popen(["xdg-open", directory]) except OSError: pass # er, think of something else to try @@ -73,28 +88,28 @@ def printerr(*args, **kwargs): def removeNonAlphabetic(s): - return ''.join([i for i in s if i.isalpha()]) + return "".join([i for i in s if i.isalpha()]) # # Evaluate a bezier curve for the parameter 0<=t<=1 along its length # def evaluateBezierPoint(p1, h1, h2, p2, t): - # return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2 +# return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2 # # Evaluate the unit tangent on a bezier curve for t # def evaluateBezierTangent(p1, h1, h2, p2, t): - # return ( - # (-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 + - # (-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2 - # ).normalized() +# return ( +# (-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 + +# (-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2 +# ).normalized() # def calculateBezierLength(p1, h1, h2, p2, resolution=20): - # step = 1/resolution - # previous_p = p1 - # length = 0 - # for i in range(0, resolution): - # t = (i + 1) * step - # p = evaluateBezierPoint(p1, h1, h2, p2, t) - # length += p.distance(previous_p) - # previous_p = p - # return length +# step = 1/resolution +# previous_p = p1 +# length = 0 +# for i in range(0, resolution): +# t = (i + 1) * step +# p = evaluateBezierPoint(p1, h1, h2, p2, t) +# length += p.distance(previous_p) +# previous_p = p +# return length