# SPDX-License-Identifier: GPL-2.0-only """ A 3D font helper """ bl_info = { "name": "Font3D", "author": "Jakob Schlötter, Studio Pointer*", "version": (0, 0, 1), "blender": (4, 1, 0), "location": "wherever it may be", "description": "Does Font3D stuff", "category": "Typography", } # make sure that modules are reloadable # when registering # handy for development # first import dependencies for the method import importlib # then import dependencies for our addon if "bpy" in locals(): importlib.reload(Font) importlib.reload(utils) importlib.reload(butils) else: from .common import Font from .common import utils from . import butils import bpy import math import io import functools from bpy.types import Panel from bpy.app.handlers import persistent from random import uniform import time import datetime import re class SharedVariables(): fonts = Font.fonts def __init__(self, **kv): self.__dict__.update(kv) shared = SharedVariables() class FONT3D_OT_Font3D(bpy.types.Operator): """Font 3D""" bl_idname = "font3d.font3d" bl_label = "Font 3D" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): global shared print("Font3d execute()") scene = bpy.context.scene file_dir = scene.font3d.file_dir print(f"file_dir: {file_dir}") return {'FINISHED'} class FONT3D_settings(bpy.types.PropertyGroup): font_path: bpy.props.StringProperty(name="Font path", description="Where is the font", default="", maxlen=1024, subtype="FILE_PATH") import_infix: bpy.props.StringProperty(name="Font name import infix", description="The infix which all font objects to import have", default="_NM_", maxlen=1024, ) class FONT3D_available_font(bpy.types.PropertyGroup): font_name: bpy.props.StringProperty(name="whatever") #TODO: simply, merge, cut cut cut class FONT3D_data(bpy.types.PropertyGroup): available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection poporotery") active_font_index: bpy.props.IntProperty() class FONT3D_UL_fonts(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): split = layout.split(factor=0.3) split.label(text="Index: %d" % (index)) # custom_icon = "OUTLINER_OB_%s" % item.obj_type # split.prop(item, "name", text="", emboss=False, translate=False) split.label(text=f"{item.font_name}") # avoids renaming the item by accident def invoke(self, context, event): pass class FONT3D_PT_panel(bpy.types.Panel): bl_label = "Panel for Font3D" bl_category = "Font3D" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): global shared layout = self.layout wm = context.window_manager scene = context.scene font3d = scene.font3d font3d_data = scene.font3d_data layout.label(text="Load FontFile:") layout.row().prop(font3d, "font_path") layout.row().operator('font3d.loadfont', text='Load Font') layout.label(text="Available Fonts") layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index") layout.label(text='DEBUG') layout.row().operator('font3d.testfont', text='Test Font') layout.row().prop(font3d, "import_infix") layout.row().operator('font3d.create_font_from_objects', text='Create Font') layout.row().operator('font3d.toggle_font3d_collection', text='Toggle Collection') layout.label(text='DEBUG END') class FONT3D_OT_LoadFont(bpy.types.Operator): """Load Font 3D""" bl_idname = "font3d.loadfont" bl_label = "Load Font" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): global shared scene = bpy.context.scene # print(f"loading da font at path {scene.font3d.font_path}") currentObjects = [] for ob in bpy.data.objects: currentObjects.append(ob.name) bpy.ops.import_scene.gltf(filepath=scene.font3d.font_path) newObjects = [] fontcollection = bpy.data.collections.new("Font3D") scene.collection.children.link(fontcollection) font = { "name": "", "glyphs": [] } for o in bpy.data.objects: if o.name not in currentObjects: if (o.parent == None): font['name'] = o.name elif o.parent.name.startswith("glyphs"): font['glyphs'].append(o) newObjects.append(o.name) scene.collection.objects.unlink(o) fontcollection.objects.link(o) try: shared.fonts except: shared.fonts = {} shared.fonts[font['name']] = Font.Font() shared.fonts[font['name']]['faces']['regular'] = font return {'FINISHED'} class FONT3D_OT_TestFont(bpy.types.Operator): """Test Font 3D""" bl_idname = "font3d.testfont" bl_label = "Test Font" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): global shared scene = bpy.context.scene selected = bpy.context.view_layer.objects.active glyph = Font.get_glyph("NM_Origin", "Tender", "A").data ob = bpy.data.objects.new('Duplicate_Linked', glyph) ob.location = selected.location ob.scale = (0.01, 0.01, 0.01) selected.users_collection[0].objects.link(ob) return {'FINISHED'} class FONT3D_OT_ToggleFont3DCollection(bpy.types.Operator): """Toggle Font3D Collection""" bl_idname = "font3d.toggle_font3d_collection" bl_label = "Toggle Collection visibility" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): scene = context.scene fontcollection = bpy.data.collections.get("Font3D") if fontcollection is None: self.report({'INFO'}, f"{bl_info['name']}: There is no collection") elif scene.collection.children.find(fontcollection.name) < 0: scene.collection.children.link(fontcollection) self.report({'INFO'}, f"{bl_info['name']}: show collection") else: scene.collection.children.unlink(fontcollection) self.report({'INFO'}, f"{bl_info['name']}: hide collection") return {'FINISHED'} class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): """Create Font from open objects""" bl_idname = "font3d.create_font_from_objects" bl_label = "Create Font" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): global shared scene = bpy.context.scene font3d = scene.font3d font3d_data = scene.font3d_data fontcollection = bpy.data.collections.get("Font3D") if fontcollection is None: fontcollection = bpy.data.collections.new("Font3D") ifxsplit = font3d.import_infix.split('_') font_name = f"{ifxsplit[1]}_{ifxsplit[2]}" face_name = ifxsplit[3] added_font = False font3d_data.available_fonts.clear() currentObjects = [] for o in bpy.data.objects: if o.name not in currentObjects: if font3d.import_infix in o.name: uc = o.users_collection regex = f"{font3d.import_infix}(.)*" name = re.sub(regex, "", o.name) glyph_id = "unknown" if len(name) == 1: glyph_id = name elif name in Font.name_to_glyph_d: glyph_id = Font.name_to_glyph_d[name] if glyph_id != "unknown": bpy.data.objects[o.name]["glyph"] = glyph_id butils.move_in_fontcollection( o, fontcollection, scene) Font.add_glyph( font_name, face_name, glyph_id, o) added_font = True #TODO: is there a better way to iterate over a CollectionProperty? found = False for f in font3d_data.available_fonts.values(): if f.font_name == font_name: found = True break if not found: f = font3d_data.available_fonts.add() f.font_name = font_name else: self.report({'INFO'}, f"did not understand glyph {name}") return {'FINISHED'} classes = ( FONT3D_OT_Font3D, FONT3D_available_font, FONT3D_data, FONT3D_settings, FONT3D_UL_fonts, FONT3D_PT_panel, FONT3D_OT_TestFont, FONT3D_OT_LoadFont, FONT3D_OT_ToggleFont3DCollection, FONT3D_OT_CreateFontFromObjects, ) def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.font3d = bpy.props.PointerProperty(type=FONT3D_settings) bpy.types.Scene.font3d_data = bpy.props.PointerProperty(type=FONT3D_data) print(f"REGISTER {bl_info['name']}") print(utils.get_timestamp()) print(shared.fonts) # would love to properly auto start this, but IT DOES NOT WORK # if load_handler not in bpy.app.handlers.load_post: # bpy.app.handlers.load_post.append(load_handler) def unregister(): # would love to properly auto start this, but IT DOES NOT WORK # if load_handler in bpy.app.handlers.load_post: # bpy.app.handlers.load_post.remove(load_handler) for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.font3d del bpy.types.Scene.font3d_data print(f"UNREGISTER {bl_info['name']}") if __name__ == '__main__': register()