cleanup, little fixes and improvements

This commit is contained in:
themancalledjakob 2024-08-04 12:52:37 +02:00
parent d7a73116fd
commit 9e910239ac
2 changed files with 301 additions and 188 deletions

View file

@ -9,7 +9,7 @@ bl_info = {
"author": "Jakob Schlötter, Studio Pointer*", "author": "Jakob Schlötter, Studio Pointer*",
"version": (0, 0, 1), "version": (0, 0, 1),
"blender": (4, 1, 0), "blender": (4, 1, 0),
"location": "wherever it may be", "location": "VIEW3D",
"description": "Does Font3D stuff", "description": "Does Font3D stuff",
"category": "Typography", "category": "Typography",
} }
@ -93,7 +93,7 @@ class FONT3D_addonPreferences(bpy.types.AddonPreferences):
class FONT3D_OT_Font3D(bpy.types.Operator): class FONT3D_OT_Font3D(bpy.types.Operator):
"""Font 3D""" """Font 3D"""
bl_idname = "font3d.font3d" bl_idname = f"{__name__}.font3d"
bl_label = "Font 3D" bl_label = "Font 3D"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -112,7 +112,7 @@ class FONT3D_OT_Font3D(bpy.types.Operator):
class FONT3D_settings(bpy.types.PropertyGroup): class FONT3D_settings(bpy.types.PropertyGroup):
font_path: bpy.props.StringProperty( font_path: bpy.props.StringProperty(
name="Font path", name="Font path",
description="Where is the font", description="Load a *.glb or *.gltf fontfile from disk",
default="", default="",
maxlen=1024, maxlen=1024,
subtype="FILE_PATH") subtype="FILE_PATH")
@ -122,14 +122,14 @@ class FONT3D_settings(bpy.types.PropertyGroup):
default="_NM_", default="_NM_",
maxlen=1024, maxlen=1024,
) )
test_text: bpy.props.StringProperty( text: bpy.props.StringProperty(
name="Test Text", name="Text",
description="the text to test with", description="The text.",
default="HELLO", default="HELLO",
maxlen=1024, maxlen=1024,
) )
the_mother_of_typography: bpy.props.PointerProperty( target_object: bpy.props.PointerProperty(
name="The Mother Of Typography", name="The Target Object",
description="The target, which will be populated by character children of text.", description="The target, which will be populated by character children of text.",
type=bpy.types.Object, type=bpy.types.Object,
) )
@ -137,7 +137,11 @@ class FONT3D_settings(bpy.types.PropertyGroup):
name="Letter Spacing", name="Letter Spacing",
description="Letter Spacing", description="Letter Spacing",
default=0.0, default=0.0,
options={'ANIMATABLE'}, )
orientation: bpy.props.FloatVectorProperty(
name="Orientation",
default=(1.5707963267948966, 0.0, 0.0), # 90 degrees in radians
subtype='EULER',
) )
class FONT3D_available_font(bpy.types.PropertyGroup): class FONT3D_available_font(bpy.types.PropertyGroup):
@ -154,20 +158,6 @@ class FONT3D_glyph_properties(bpy.types.PropertyGroup):
class FONT3D_text_properties(bpy.types.PropertyGroup): class FONT3D_text_properties(bpy.types.PropertyGroup):
def update_callback(self, context): def update_callback(self, context):
butils.set_text_on_curve(self) butils.set_text_on_curve(self)
# TODO: update when animate
# does not work like this, somehow it does not run in main thread when the text is actually being set
# def get_float(self):
# return self["letter_spacingor"]
# def set_float(self, value):
# print(f"{utils.get_timestamp()} setting float to {value}")
# self["letter_spacingor"] = value
# def fun(text_properties : FONT3D_text_properties):
# # print(text_properties)
# # print(type(text_properties))
# # print(text_properties.letter_spacing)
# print(f"is running ---------------------------------->>>>>>> {text_properties.letter_spacing} and {text_properties.get('letter_spacingor')}")
# # butils.set_text_on_curve(text_properties)
# butils.run_in_main_thread(lambda: fun(self))
text_id: bpy.props.IntProperty() text_id: bpy.props.IntProperty()
font_name: bpy.props.StringProperty() font_name: bpy.props.StringProperty()
font_face: bpy.props.StringProperty() font_face: bpy.props.StringProperty()
@ -176,13 +166,18 @@ class FONT3D_text_properties(bpy.types.PropertyGroup):
update=update_callback update=update_callback
) )
letter_spacing: bpy.props.FloatProperty( letter_spacing: bpy.props.FloatProperty(
# get=get_float,
# set=set_float,
update=update_callback, update=update_callback,
name="Letter Spacing", name="Letter Spacing",
description="Letter Spacing", description="Letter Spacing",
options={'ANIMATABLE'},
step=0.01, 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',
)
distribution_type: bpy.props.StringProperty() distribution_type: bpy.props.StringProperty()
glyphs: bpy.props.CollectionProperty(type=FONT3D_glyph_properties) glyphs: bpy.props.CollectionProperty(type=FONT3D_glyph_properties)
@ -191,14 +186,25 @@ class FONT3D_data(bpy.types.PropertyGroup):
available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection property") available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection property")
active_font_index: bpy.props.IntProperty() active_font_index: bpy.props.IntProperty()
available_texts: bpy.props.CollectionProperty(type=FONT3D_text_properties, name="") available_texts: bpy.props.CollectionProperty(type=FONT3D_text_properties, name="")
active_text_index: bpy.props.IntProperty() def active_text_index_update(self, context):
if self.active_text_index != -1:
o = self.available_texts[self.active_text_index].text_object
# active_text_index changed. so let's update the selection
# check if it is already selected
# or perhaps one of the glyphs
if not o.select_get() and not len([ c for c in o.children if c.select_get() ]) > 0:
bpy.ops.object.select_all(action="DESELECT")
o.select_set(True)
bpy.context.view_layer.objects.active = o
# else:
# print("already selected")
active_text_index: bpy.props.IntProperty(update=active_text_index_update)
class FONT3D_UL_fonts(bpy.types.UIList): class FONT3D_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):
split = layout.split(factor=0.3) split = layout.split(factor=0.3)
split.label(text="Index: %d" % (index)) 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 split.label(text=f"{item.font_name}") # avoids renaming the item by accident
def invoke(self, context, event): def invoke(self, context, event):
@ -208,28 +214,101 @@ class FONT3D_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 = layout.split(factor=0.3)
split.label(text="Id: %d" % (item.text_id)) split.label(text="Id: %d" % (item.text_id))
# custom_icon = "OUTLINER_OB_%s" % item.obj_type
# split.prop(item, "name", text="", emboss=False, translate=False)
split.label(text=f"{item.text}") # avoids renaming the item by accident split.label(text=f"{item.text}") # avoids renaming the item by accident
def invoke(self, context, event): def invoke(self, context, event):
pass pass
class FONT3D_PT_Panel(bpy.types.Panel):
class FONT3D_PT_panel(bpy.types.Panel): bl_label = f"{__name__} panel"
bl_label = "Panel for Font3D"
bl_category = "Font3D" bl_category = "Font3D"
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
def draw(self, context):
layout = self.layout
layout.label(text=f"{__name__} panel")
class FONT3D_PT_LoadFontPanel(bpy.types.Panel):
bl_label = "Load a new font"
bl_parent_id = "FONT3D_PT_Panel"
bl_category = "Font3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
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')
class FONT3D_PT_FontList(bpy.types.Panel):
bl_label = "Font List"
bl_parent_id = "FONT3D_PT_Panel"
bl_category = "Font3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
wm = context.window_manager
scene = context.scene
font3d = scene.font3d
font3d_data = scene.font3d_data
layout.label(text="Loaded Fonts")
layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index")
class FONT3D_PT_TextPlacement(bpy.types.Panel):
bl_label = "Place Text"
bl_parent_id = "FONT3D_PT_Panel"
bl_category = "Font3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
wm = context.window_manager
scene = context.scene
font3d = scene.font3d
font3d_data = scene.font3d_data
layout.label(text="Set Properties Objects")
layout.row().prop(font3d, "text")
layout.row().prop(font3d, "letter_spacing")
layout.column().prop(font3d, "orientation")
layout.row().operator('font3d.placetext', text='Place Text')
class FONT3D_PT_TextManagement(bpy.types.Panel):
bl_label = "Text Management"
bl_parent_id = "FONT3D_PT_Panel"
bl_category = "Font3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
# TODO: perhaps this should be done in a periodic timer
@classmethod @classmethod
def poll(self, context): def poll(self, context):
scene = context.scene scene = context.scene
font3d = scene.font3d font3d = scene.font3d
font3d_data = scene.font3d_data font3d_data = scene.font3d_data
# TODO: properly include this # TODO: update available_texts
def update(): def update():
font3d_data.active_text_index = -1 if bpy.context.screen.is_animation_playing:
return
active_text_index = -1
remove_list = [] remove_list = []
for i, t in enumerate(font3d_data.available_texts): for i, t in enumerate(font3d_data.available_texts):
if type(t.text_object) == type(None): if type(t.text_object) == type(None):
@ -245,15 +324,15 @@ class FONT3D_PT_panel(bpy.types.Panel):
# or they might be lost # or they might be lost
if type(next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)) == type(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) g = next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)
for g in t.glyphs: # for g in t.glyphs:
if type(g) == type(None): # if type(g) == type(None):
print("IS NONE") # print("IS NONE")
if type(g.glyph_object) == type(None): # if type(g.glyph_object) == type(None):
print("go IS NONE") # print("go IS NONE")
else: # else:
if g.glyph_object == c: # if g.glyph_object == c:
# print(g.glyph_object.name) # # print(g.glyph_object.name)
pass # pass
if remove_me: if remove_me:
remove_list.append(i) remove_list.append(i)
@ -263,10 +342,13 @@ class FONT3D_PT_panel(bpy.types.Panel):
for i, t in enumerate(font3d_data.available_texts): for i, t in enumerate(font3d_data.available_texts):
if context.active_object == t.text_object: if context.active_object == t.text_object:
font3d_data.active_text_index = i active_text_index = i
if (hasattr(context.active_object, "parent") and if (hasattr(context.active_object, "parent") and
context.active_object.parent == t.text_object): context.active_object.parent == t.text_object):
font3d_data.active_text_index = i active_text_index = i
if active_text_index != font3d_data.active_text_index:
font3d_data.active_text_index = active_text_index
butils.run_in_main_thread(update) butils.run_in_main_thread(update)
@ -280,23 +362,28 @@ class FONT3D_PT_panel(bpy.types.Panel):
font3d = scene.font3d font3d = scene.font3d
font3d_data = scene.font3d_data 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().prop(font3d, "test_text")
layout.row().prop(font3d, "the_mother_of_typography")
layout.row().operator('font3d.testfont', text='Distribute')
layout.label(text="Text Objects") layout.label(text="Text Objects")
layout.template_list("FONT3D_UL_texts", "", font3d_data, "available_texts", font3d_data, "active_text_index") layout.template_list("FONT3D_UL_texts", "", font3d_data, "available_texts", font3d_data, "active_text_index")
layout.label(text="font properties")
layout.row().prop(font3d, "letter_spacing") class FONT3D_PT_FontCreation(bpy.types.Panel):
bl_label = "Font Creation"
bl_parent_id = "FONT3D_PT_Panel"
bl_category = "Font3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
layout = self.layout
wm = context.window_manager
scene = context.scene
font3d = scene.font3d
font3d_data = scene.font3d_data
layout.label(text="font creation") layout.label(text="font creation")
layout.row().prop(font3d, "import_infix") layout.row().prop(font3d, "import_infix")
layout.row().operator('font3d.create_font_from_objects', text='Create Font') layout.row().operator('font3d.create_font_from_objects', text='Create Font')
layout.row().separator()
layout.row().operator('font3d.save_font_to_file', text='Save Font To File') layout.row().operator('font3d.save_font_to_file', text='Save Font To File')
layout.row().operator('font3d.toggle_font3d_collection', text='Toggle Collection') layout.row().operator('font3d.toggle_font3d_collection', text='Toggle Collection')
layout.row().operator('font3d.temporaryhelper', text='Temporary Helper') layout.row().operator('font3d.temporaryhelper', text='Temporary Helper')
@ -304,7 +391,7 @@ class FONT3D_PT_panel(bpy.types.Panel):
class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel): class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
bl_label = "Text Properties" bl_label = "Text Properties"
bl_parent_id = "FONT3D_PT_panel" bl_parent_id = "FONT3D_PT_TextManagement"
bl_category = "Font3D" bl_category = "Font3D"
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
@ -333,16 +420,19 @@ class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
if type(props) == type(None) or type(props.text_object) == type(None): if type(props) == type(None) or type(props.text_object) == type(None):
# this should not happen # this should not happen
print("debug: this should not happen") # as then polling does not work
layout.label(text="AAAAH") # however, we are paranoid
return return
layout.label(text=f"Mom: {props.text_object.name}") layout.label(text=f"Mom: {props.text_object.name}")
layout.row().prop(props, "text")
layout.row().prop(props, "letter_spacing") layout.row().prop(props, "letter_spacing")
layout.column().prop(props, "orientation")
class FONT3D_OT_LoadFont(bpy.types.Operator): class FONT3D_OT_LoadFont(bpy.types.Operator):
"""Load Font 3D""" """Load Fontfile from path above.
bl_idname = "font3d.loadfont" (Format must be *.glb or *.gltf)"""
bl_idname = f"{__name__}.loadfont"
bl_label = "Load Font" bl_label = "Load Font"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -356,52 +446,21 @@ class FONT3D_OT_LoadFont(bpy.types.Operator):
) )
return {'CANCELLED'} return {'CANCELLED'}
# print(f"loading da font at path {scene.font3d.font_path}")
if not os.path.exists(scene.font3d.font_path): if not os.path.exists(scene.font3d.font_path):
butils.ShowMessageBox( butils.ShowMessageBox(
title=f"{__name__} Warning", title=f"{__name__} Warning",
icon="ERROR", icon="ERROR",
message=f"We believe the font path ({scene.font3d.font_path}) does not exist.", message=f"We believe the font path ({scene[__name__].font_path}) does not exist.",
) )
return {'CANCELLED'} return {'CANCELLED'}
currentObjects = [] butils.load_font_from_filepath(scene.font3d.font_path)
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'} return {'FINISHED'}
class FONT3D_OT_TemporaryHelper(bpy.types.Operator): class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
"""Temp Font 3D""" """Temp Font 3D"""
bl_idname = "font3d.temporaryhelper" bl_idname = f"{__name__}.temporaryhelper"
bl_label = "Temp Font" bl_label = "Temp Font"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -414,10 +473,10 @@ class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class FONT3D_OT_TestFont(bpy.types.Operator): class FONT3D_OT_PlaceText(bpy.types.Operator):
"""Test Font 3D""" """Place Text 3D"""
bl_idname = "font3d.testfont" bl_idname = f"{__name__}.placetext"
bl_label = "Test Font" bl_label = "Place Text"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
def execute(self, context): def execute(self, context):
@ -429,8 +488,8 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
font3d = scene.font3d font3d = scene.font3d
font3d_data = scene.font3d_data font3d_data = scene.font3d_data
if font3d.the_mother_of_typography: if font3d.target_object:
selected = font3d.the_mother_of_typography selected = font3d.target_object
if selected: if selected:
font_name = "NM_Origin" font_name = "NM_Origin"
@ -448,8 +507,9 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
t.font_name = font_name t.font_name = font_name
t.font_face = font_face t.font_face = font_face
t.text_object = selected t.text_object = selected
t.text = scene.font3d.test_text t.text = scene.font3d.text
t.letter_spacing = 0.0 t.letter_spacing = scene.font3d.letter_spacing
t.orientation = scene.font3d.orientation
t.distribution_type = distribution_type t.distribution_type = distribution_type
else: else:
butils.ShowMessageBox( butils.ShowMessageBox(
@ -457,14 +517,14 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
message=( message=(
"Please select an object.", "Please select an object.",
"It will be used to put the type on.", "It will be used to put the type on.",
"You little piece of shit :)"), "Thank you :)"),
icon='GHOST_ENABLED') icon='GHOST_ENABLED')
return {'FINISHED'} return {'FINISHED'}
class FONT3D_OT_ToggleFont3DCollection(bpy.types.Operator): class FONT3D_OT_ToggleFont3DCollection(bpy.types.Operator):
"""Toggle Font3D Collection""" """Toggle Font3D Collection"""
bl_idname = "font3d.toggle_font3d_collection" bl_idname = f"{__name__}.toggle_font3d_collection"
bl_label = "Toggle Collection visibility" bl_label = "Toggle Collection visibility"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -490,8 +550,6 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
bl_label = "Save Font" bl_label = "Save Font"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
file_path = "whoopwhoop"
def execute(self, context): def execute(self, context):
global shared global shared
scene = bpy.context.scene scene = bpy.context.scene
@ -577,7 +635,7 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator): class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
"""Create Font from open objects""" """Create Font from open objects"""
bl_idname = "font3d.create_font_from_objects" bl_idname = f"{__name__}.create_font_from_objects"
bl_label = "Create Font" bl_label = "Create Font"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@ -646,7 +704,7 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel): class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window""" """Creates a Panel in the Object properties window"""
bl_label = f"{__name__}" bl_label = f"{bl_info['name']}"
bl_idname = "FONT3D_PT_RightPropertiesPanel" bl_idname = "FONT3D_PT_RightPropertiesPanel"
bl_space_type = 'PROPERTIES' bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
@ -684,11 +742,9 @@ class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
row.label(text="text object is: " + textobject.name) row.label(text="text object is: " + textobject.name)
row = layout.row() row = layout.row()
row.label(text=f"active text index is: {font3d_data.active_text_index}") row.label(text=f"active text index is: {font3d_data.active_text_index}")
row = layout.row() layout.row().prop(available_text, "text")
row.prop(available_text, "text") layout.row().prop(available_text, "letter_spacing")
row = layout.row() layout.column().prop(available_text, "orientation")
row.prop(available_text, "letter_spacing")
classes = ( classes = (
@ -701,10 +757,15 @@ classes = (
FONT3D_settings, FONT3D_settings,
FONT3D_UL_fonts, FONT3D_UL_fonts,
FONT3D_UL_texts, FONT3D_UL_texts,
FONT3D_PT_panel, FONT3D_PT_Panel,
FONT3D_PT_LoadFontPanel,
FONT3D_PT_FontList,
FONT3D_PT_TextPlacement,
FONT3D_PT_TextManagement,
FONT3D_PT_FontCreation,
FONT3D_PT_TextPropertiesPanel, FONT3D_PT_TextPropertiesPanel,
FONT3D_OT_TemporaryHelper, FONT3D_OT_TemporaryHelper,
FONT3D_OT_TestFont, FONT3D_OT_PlaceText,
FONT3D_OT_LoadFont, FONT3D_OT_LoadFont,
FONT3D_OT_ToggleFont3DCollection, FONT3D_OT_ToggleFont3DCollection,
FONT3D_OT_SaveFontToFile, FONT3D_OT_SaveFontToFile,
@ -721,6 +782,16 @@ def load_handler_unload():
if bpy.app.timers.is_registered(butils.execute_queued_functions): if bpy.app.timers.is_registered(butils.execute_queued_functions):
bpy.app.timers.unregister(butils.execute_queued_functions) bpy.app.timers.unregister(butils.execute_queued_functions)
@persistent
def on_frame_changed(self, dummy):
for t in bpy.context.scene.font3d_data.available_texts:
# TODO PERFORMANCE: only on demand
butils.set_text_on_curve(t)
# for i, t in enumerate(bpy.context.scene.font3d_data.available_texts):
# # TODO PERFORMANCE: only on demand
# # butils.set_text_on_curve(t)
# pass
def register(): def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
@ -735,68 +806,11 @@ def register():
# and autostart if we reload script # and autostart if we reload script
load_handler(None, None) load_handler(None, None)
# clear available fonts if on_frame_changed not in bpy.app.handlers.frame_change_post:
def clear_available_fonts(): bpy.app.handlers.frame_change_post.append(on_frame_changed)
bpy.context.scene.font3d_data.available_fonts.clear()
def load_available_fonts(): butils.run_in_main_thread(butils.clear_available_fonts)
global shared butils.run_in_main_thread(butils.load_available_fonts)
preferences = getPreferences(bpy.context)
currentObjects = []
for ob in bpy.data.objects:
currentObjects.append(ob.name)
print(f"assets folder: {preferences.assets_dir}")
font_dir = f"{preferences.assets_dir}/fonts"
for file in os.listdir(font_dir):
if file.endswith(".glb"):
font_path = os.path.join(font_dir, file)
bpy.ops.import_scene.gltf(filepath=font_path)
fontcollection = bpy.data.collections.get("Font3D")
if fontcollection is None:
fontcollection = bpy.data.collections.new("Font3D")
remove_list = []
all_objects = []
for o in bpy.data.objects:
all_objects.append(o)
for o in all_objects:
if o.name not in currentObjects:
# must be new
if ("glyph" in o.keys()
and "face_name" in o.keys()
and "font_name" in o.keys()):
glyph_id = o["glyph"]
font_name = o["font_name"]
face_name = o["face_name"]
butils.move_in_fontcollection(
o,
fontcollection)
Font.add_glyph(
font_name,
face_name,
glyph_id,
o)
font3d_data = bpy.context.scene.font3d_data
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
print(f"font3d added {font_name}")
else:
remove_list.append(o)
for o in remove_list:
bpy.data.objects.remove(o, do_unlink=True)
butils.run_in_main_thread(clear_available_fonts)
butils.run_in_main_thread(load_available_fonts)
def unregister(): def unregister():
for cls in classes: for cls in classes:
@ -808,6 +822,9 @@ def unregister():
# and when reload script # and when reload script
load_handler_unload() load_handler_unload()
if on_frame_changed in bpy.app.handlers.frame_change_post:
bpy.app.handlers.frame_change_post.remove(on_frame_changed)
del bpy.types.Scene.font3d del bpy.types.Scene.font3d
del bpy.types.Scene.font3d_data del bpy.types.Scene.font3d_data
print(f"UNREGISTER {bl_info['name']}") print(f"UNREGISTER {bl_info['name']}")

114
butils.py
View file

@ -2,6 +2,7 @@ import bpy
import mathutils import mathutils
import queue import queue
import importlib import importlib
import os
# then import dependencies for our addon # then import dependencies for our addon
if "Font" in locals(): if "Font" in locals():
@ -289,11 +290,7 @@ def find_font_face_object(font_obj, face_name):
return face return face
return None return None
def move_in_fontcollection(obj, fontcollection): def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
for c in obj.users_collection:
c.objects.unlink(obj)
if fontcollection.objects.find(obj.name) < 0:
fontcollection.objects.link(obj)
# parent nesting structure # parent nesting structure
# the font object # the font object
@ -339,10 +336,105 @@ def move_in_fontcollection(obj, fontcollection):
glyphs_obj["face_name"] = obj["face_name"] glyphs_obj["face_name"] = obj["face_name"]
glyphs_obj["font_name"] = obj["font_name"] glyphs_obj["font_name"] = obj["font_name"]
def get_hash(o):
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"]):
if get_hash(other_obj) == get_hash(obj) and not allow_duplicates:
return other_obj
# and now parent it! # and now parent it!
if obj.parent != glyphs_obj: if obj.parent != glyphs_obj:
obj.parent = glyphs_obj obj.parent = glyphs_obj
for c in obj.users_collection:
c.objects.unlink(obj)
if fontcollection.objects.find(obj.name) < 0:
fontcollection.objects.link(obj)
return obj
def load_font_from_filepath(filepath):
if not filepath.endswith(".glb") and not filepath.endswith(".gltf"):
ShowMessageBox(f"{bl_info['name']} Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file")
return False
font3d_data = bpy.context.scene.font3d_data
allObjectsBefore = []
for ob in bpy.data.objects:
allObjectsBefore.append(ob.name)
bpy.ops.import_scene.gltf(filepath=filepath)
fontcollection = bpy.data.collections.get("Font3D")
if fontcollection is None:
fontcollection = bpy.data.collections.new("Font3D")
remove_list = []
all_objects = []
for o in bpy.data.objects:
all_objects.append(o)
print("all objects", len(all_objects))
for o in all_objects:
if o.name not in allObjectsBefore:
# must be new
if ("glyph" in o.keys()
and "face_name" in o.keys()
and "font_name" in o.keys()):
print("adding glyph", o)
glyph_id = o["glyph"]
font_name = o["font_name"]
face_name = o["face_name"]
glyph_obj = move_in_fontcollection(
o,
fontcollection)
Font.add_glyph(
font_name,
face_name,
glyph_id,
glyph_obj)
if glyph_obj != o:
remove_list.append(o)
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
print(f"{__name__} added {font_name}")
else:
remove_list.append(o)
print("add to remove list", o)
for o in remove_list:
bpy.data.objects.remove(o, do_unlink=True)
print("removed",o)
print("butils:: should have been doing loading the fonts")
def getPreferences(context):
preferences = context.preferences
return preferences.addons['font3d'].preferences
# clear available fonts
def clear_available_fonts():
bpy.context.scene.font3d_data.available_fonts.clear()
def load_available_fonts():
preferences = getPreferences(bpy.context)
currentObjects = []
for ob in bpy.data.objects:
currentObjects.append(ob.name)
print(f"assets folder: {preferences.assets_dir}")
font_dir = f"{preferences.assets_dir}/fonts"
for file in os.listdir(font_dir):
if file.endswith(".glb") or file.endswith(".gltf"):
font_path = os.path.join(font_dir, file)
load_font_from_filepath(font_path)
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""): def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
"""Show a simple message box """Show a simple message box
@ -379,7 +471,6 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def set_text_on_curve(text_properties): def set_text_on_curve(text_properties):
print(f"set text on curve {utils.get_timestamp()} with {text_properties.letter_spacing} and {text_properties.get('letter_spacingor')}")
mom = text_properties.text_object mom = text_properties.text_object
if mom.type != "CURVE": if mom.type != "CURVE":
return False return False
@ -430,7 +521,8 @@ def set_text_on_curve(text_properties):
if c == 'n': if c == 'n':
next_line_advance = get_next_line_advance(mom, advance, glyph_advance) next_line_advance = get_next_line_advance(mom, advance, glyph_advance)
if advance == next_line_advance: if advance == next_line_advance:
self.report({'INFO'}, f"would like to add new line for {text_properties.text} please") # 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")
advance = next_line_advance advance = next_line_advance
continue continue
is_command = False is_command = False
@ -443,7 +535,8 @@ def set_text_on_curve(text_properties):
ob = None ob = None
if regenerate: if regenerate:
if glyph == None: if glyph == None:
self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}") # self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}")
print(f"Glyph not found for {font_name} {font_face} {glyph_id}")
continue continue
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data) ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
@ -476,7 +569,10 @@ def set_text_on_curve(text_properties):
if ob.rotation_mode != 'QUATERNION': if ob.rotation_mode != 'QUATERNION':
ob.rotation_mode = 'QUATERNION' ob.rotation_mode = 'QUATERNION'
q = mathutils.Quaternion() q = mathutils.Quaternion()
q.rotate(mathutils.Euler((radians(90),0,0))) q.rotate(text_properties.orientation)
# mathutils.Euler((radians(text_properties.orientation.x),
# radians(text_properties.orientation.y),
# radians(text_properties.orientation.z))))
ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion() ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
scalor = 0.001 scalor = 0.001