[feature] unload glyphs

This commit is contained in:
jrkb 2025-05-31 16:13:16 +02:00
parent 9423659153
commit 2dcd4e7a2c
3 changed files with 552 additions and 280 deletions

View file

@ -35,6 +35,7 @@ if "Font" in locals():
importlib.reload(bimport)
importlib.reload(addon_updater_ops)
def getPreferences(context):
preferences = context.preferences
return preferences.addons[__name__].preferences
@ -137,7 +138,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
def update_callback(self, context):
if self.text_id >= 0:
# butils.set_text_on_curve(
# context.scene.abc3d_data.available_texts[self.text_id]
# context.scene.abc3d_data.available_texts[self.text_id]
# )
t = butils.get_text_properties(self.text_id)
if t is not None:
@ -145,8 +146,8 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
glyph_id: bpy.props.StringProperty(maxlen=1)
text_id: bpy.props.IntProperty(
default=-1,
)
default=-1,
)
alternate: bpy.props.IntProperty(
default=-1,
update=update_callback,
@ -158,6 +159,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
update=update_callback,
)
class ABC3D_text_properties(bpy.types.PropertyGroup):
def font_items_callback(self, context):
items = []
@ -165,21 +167,6 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
return items
def font_default_callback(self, context):
d = context.scene.abc3d_data
if len(d.available_fonts) > 0:
if len(d.available_fonts) > d.active_text_index:
f = d.available_fonts[d.active_text_index]
return 0 # f"{f.font_name} {f.face_name}"
else:
f = d.available_fonts[0]
return 0 # f"{f.font_name} {f.face_name}"
if not isinstance(self.font_name, None) and not isinstance(self.face_name, None):
return 0 # f"{self.font_name} {self.face_name}"
else:
return 0 # ""
def glyphs_update_callback(self, context):
butils.prepare_text(self.font_name, self.face_name, self.text)
butils.set_text_on_curve(self, can_regenerate=True)
@ -283,13 +270,16 @@ class ABC3D_data(bpy.types.PropertyGroup):
def active_text_index_update(self, context):
if self.active_text_index != -1:
text_properties = butils.get_text_properties(self.active_text_index, context.scene)
text_properties = butils.get_text_properties(
self.active_text_index, context.scene
)
if text_properties is not None:
o = text_properties.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 (o is not None
if (
o is not None
and not o.select_get()
and not len([c for c in o.children if c.select_get()]) > 0
):
@ -397,7 +387,7 @@ class ABC3D_PT_FontList(bpy.types.Panel):
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name
face_name = available_font.face_name
face : Font.FontFace = Font.get_font_face(font_name, face_name)
face: Font.FontFace = Font.get_font_face(font_name, face_name)
if face is not None:
available_glyphs = face.glyphs_in_fontfile
loaded_glyphs = sorted(face.loaded_glyphs)
@ -455,10 +445,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel):
@classmethod
def poll(self, context):
if (
context.active_object is not None
and context.active_object.type == "CURVE"
):
if context.active_object is not None and context.active_object.type == "CURVE":
self.can_place = True
else:
self.can_place = False
@ -649,6 +636,7 @@ class ABC3D_PG_FontCreation(bpy.types.PropertyGroup):
update=naming_glyph_id_update_callback,
)
class ABC3D_OT_NamingHelper(bpy.types.Operator):
bl_label = "Font Creation Naming Helper Apply To Active Object"
bl_idname = f"{__name__}.apply_naming_helper"
@ -686,7 +674,10 @@ class ABC3D_PT_NamingHelper(bpy.types.Panel):
box.row().prop(abc3d_font_creation, "face_name")
box.label(text="Glyph Output Name")
box.row().prop(abc3d_font_creation, "naming_glyph_name")
box.row().operator(f"{__name__}.apply_naming_helper", text="Apply name to active object")
box.row().operator(
f"{__name__}.apply_naming_helper", text="Apply name to active object"
)
class ABC3D_PT_FontCreation(bpy.types.Panel):
bl_label = "Font Creation"
@ -725,7 +716,10 @@ class ABC3D_PT_FontCreation(bpy.types.Panel):
f"{__name__}.temporaryhelper", text="Debug Function Do Not Use"
)
box.label(text="origin points")
box.row().operator(f"{__name__}.align_origins_to_active_object", text="Align origins to Active Object")
box.row().operator(
f"{__name__}.align_origins_to_active_object",
text="Align origins to Active Object",
)
# box.row().operator(f"{__name__}.align_origins_to_metrics", text="Align origins to Metrics (left)")
# box.row().operator(f"{__name__}.fix_objects_metrics_origins", text="Fix objects metrics origins")
@ -749,27 +743,36 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
return bpy.context.scene.abc3d_data.available_texts[text_index]
else:
for t in bpy.context.scene.abc3d_data.available_texts:
if butils.is_or_has_parent(bpy.context.active_object, t.text_object, max_depth=4):
if butils.is_or_has_parent(
bpy.context.active_object, t.text_object, max_depth=4
):
return t
return None
def get_active_glyph_properties(self):
a_o = bpy.context.active_object
if a_o is not None:
if (f"{utils.prefix()}_text_id" in a_o
and f"{utils.prefix()}_glyph_index" in a_o):
if (
f"{utils.prefix()}_text_id" in a_o
and f"{utils.prefix()}_glyph_index" in a_o
):
text_index = a_o[f"{utils.prefix()}_text_id"]
glyph_index = a_o[f"{utils.prefix()}_glyph_index"]
return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[glyph_index]
return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[
glyph_index
]
else:
for t in bpy.context.scene.abc3d_data.available_texts:
if butils.is_or_has_parent(a_o, t.text_object, if_is_parent=False, max_depth=4):
if butils.is_or_has_parent(
a_o, t.text_object, if_is_parent=False, max_depth=4
):
for g in t.glyphs:
if butils.is_or_has_parent(a_o, g.glyph_object, max_depth=4):
if butils.is_or_has_parent(
a_o, g.glyph_object, max_depth=4
):
return g
return None
# def font_items_callback(self, context):
# items = []
# fonts = Font.get_loaded_fonts_and_faces()
@ -840,7 +843,6 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
box.row().prop(glyph_props, "letter_spacing")
class ABC3D_OT_InstallFont(bpy.types.Operator):
"""Install or load Fontfile from path above.
(Format must be *.glb or *.gltf)"""
@ -1024,9 +1026,16 @@ class ABC3D_OT_LoadFont(bpy.types.Operator):
face_name: bpy.props.StringProperty()
def execute(self, context):
face : Font.FontFace = Font.get_font_face(self.font_name, self.face_name)
face: Font.FontFace = Font.get_font_face(self.font_name, self.face_name)
if face is None:
butils.ShowMessageBox(f"{utils.prefix()} Load Font", icon="ERROR", message=["Could not load font, sorry!", f"{self.font_name=} {self.face_name=}"])
butils.ShowMessageBox(
f"{utils.prefix()} Load Font",
icon="ERROR",
message=[
"Could not load font, sorry!",
f"{self.font_name=} {self.face_name=}",
],
)
return {"CANCELLED"}
filepaths = face.filepaths
for f in filepaths:
@ -1090,6 +1099,7 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator):
butils.align_metrics_of_objects(objects)
return {"FINISHED"}
class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
"""Align origins of selected objects to origin of active object on one axis."""
@ -1097,65 +1107,67 @@ class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
bl_label = "Align origins to Active Object"
bl_options = {"REGISTER", "UNDO"}
enum_axis = (('0','X',''),('1','Y',''),('2','Z',''))
axis: bpy.props.EnumProperty(items = enum_axis, default='2')
enum_axis = (("0", "X", ""), ("1", "Y", ""), ("2", "Z", ""))
axis: bpy.props.EnumProperty(items=enum_axis, default="2")
def execute(self, context):
objects = bpy.context.selected_objects
butils.align_origins_to_active_object(objects, int(self.axis))
return {"FINISHED"}
# class ABC3D_OT_AlignOriginsToMetrics(bpy.types.Operator):
# """Align origins of selected objects to their metrics left border.
# """Align origins of selected objects to their metrics left border.
# Be aware that shifting the origin will also shift the pivot point around which an object rotates.
# Be aware that shifting the origin will also shift the pivot point around which an object rotates.
# If an object does not have metrics, it will be ignored."""
# If an object does not have metrics, it will be ignored."""
# bl_idname = f"{__name__}.align_origins_to_metrics"
# bl_label = "Align origins to metrics metrics"
# bl_options = {"REGISTER", "UNDO"}
# bl_idname = f"{__name__}.align_origins_to_metrics"
# bl_label = "Align origins to metrics metrics"
# bl_options = {"REGISTER", "UNDO"}
# ignore_warning: bpy.props.BoolProperty(
# name="Do not warn in the future",
# description="Do not warn in the future",
# default=False,
# )
# ignore_warning: bpy.props.BoolProperty(
# name="Do not warn in the future",
# description="Do not warn in the future",
# default=False,
# )
# def draw(self, context):
# layout = self.layout
# layout.row().label(text="Warning!")
# layout.row().label(text="This also shifts the pivot point around which the glyph rotates.")
# layout.row().label(text="This may not be what you want.")
# layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.")
# layout.row().label(text="If you are sure about what you're doing, please continue.")
# layout.row().prop(self, "ignore_warning")
# def draw(self, context):
# layout = self.layout
# layout.row().label(text="Warning!")
# layout.row().label(text="This also shifts the pivot point around which the glyph rotates.")
# layout.row().label(text="This may not be what you want.")
# layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.")
# layout.row().label(text="If you are sure about what you're doing, please continue.")
# layout.row().prop(self, "ignore_warning")
# def invoke(self, context, event):
# if not self.ignore_warning:
# wm = context.window_manager
# return wm.invoke_props_dialog(self)
# return self.execute(context)
# def invoke(self, context, event):
# if not self.ignore_warning:
# wm = context.window_manager
# return wm.invoke_props_dialog(self)
# return self.execute(context)
# def execute(self, context):
# objects = bpy.context.selected_objects
# butils.align_origins_to_metrics(objects)
# butils.fix_objects_metrics_origins(objects)
# return {"FINISHED"}
# def execute(self, context):
# objects = bpy.context.selected_objects
# butils.align_origins_to_metrics(objects)
# butils.fix_objects_metrics_origins(objects)
# return {"FINISHED"}
# class ABC3D_OT_FixObjectsMetricsOrigins(bpy.types.Operator):
# """Align metrics origins of selected objects to their metrics bounding box.
# """Align metrics origins of selected objects to their metrics bounding box.
# If an object does not have metrics, it will be ignored."""
# If an object does not have metrics, it will be ignored."""
# bl_idname = f"{__name__}.fix_objects_metrics_origins"
# bl_label = "Fix metrics origin of all selected objects"
# bl_options = {"REGISTER", "UNDO"}
# bl_idname = f"{__name__}.fix_objects_metrics_origins"
# bl_label = "Fix metrics origin of all selected objects"
# bl_options = {"REGISTER", "UNDO"}
# def execute(self, context):
# objects = bpy.context.selected_objects
# butils.fix_objects_metrics_origins(objects)
# return {"FINISHED"}
# def execute(self, context):
# objects = bpy.context.selected_objects
# butils.fix_objects_metrics_origins(objects)
# 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."""
@ -1223,6 +1235,7 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
mom = abc3d_data.available_texts[i].text_object
if self.remove_custom_properties:
def delif(o, p):
if p in o:
del o[p]
@ -1372,7 +1385,8 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
"""Toggle ABC3D Collection.
This will show the Fonts and Glyphs currently loaded by ABC3D. Useful for font creation, debugging and inspection."""
This will show the Fonts and Glyphs currently loaded by ABC3D. Useful for font creation, debugging and inspection.
"""
bl_idname = f"{__name__}.toggle_abc3d_collection"
bl_label = "Toggle Collection visibility"
@ -1404,8 +1418,8 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
bl_label = "Save Font"
bl_options = {"REGISTER", "UNDO"}
can_execute : bpy.props.BoolProperty(default=True)
create_output_directory : bpy.props.BoolProperty(default=False)
can_execute: bpy.props.BoolProperty(default=True)
create_output_directory: bpy.props.BoolProperty(default=False)
def invoke(self, context, event):
wm = context.window_manager
@ -1430,7 +1444,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name
face_name = available_font.face_name
face : Font.FontFace = Font.get_font_face(font_name, face_name)
face: Font.FontFace = Font.get_font_face(font_name, face_name)
if face is not None:
loaded_glyphs = sorted(face.loaded_glyphs)
n = 16
@ -1451,7 +1465,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.scale_y = scale_y
row.label(text=text)
row = layout.row()
export_dir = butils.bpy_to_abspath(abc3d_data.export_dir)
export_dir = butils.bpy_to_abspath(abc3d_data.export_dir)
if os.access(export_dir, os.W_OK):
self.can_execute = True
elif os.path.exists(export_dir):
@ -1463,7 +1477,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
elif not utils.can_create_path(export_dir): # does not exist and cannot be created
elif not utils.can_create_path(
export_dir
): # does not exist and cannot be created
self.can_execute = False
row.alert = True
row.label(text="Directory does not exist and cannot be created")
@ -1472,7 +1488,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
elif utils.can_create_path(export_dir): # does not exist and can be created
elif utils.can_create_path(export_dir): # does not exist and can be created
self.can_execute = True
row.label(text="Directory does not exist")
row = layout.row()
@ -1487,7 +1503,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.prop(abc3d_data, "export_dir")
else:
print(f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}")
print(
f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}"
)
print(f"{utils.prefix()} {Font.fonts=}")
def execute(self, context):
@ -1500,10 +1518,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable",
"try setting another path"
]
"try setting another path",
],
)
return {'CANCELLED'}
return {"CANCELLED"}
if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
path = butils.bpy_to_abspath(abc3d_data.export_dir)
@ -1515,10 +1533,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created",
"try setting another path"
]
"try setting another path",
],
)
return {'CANCELLED'}
return {"CANCELLED"}
fontcollection = bpy.data.collections.get("ABC3D")
@ -1598,9 +1616,11 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
use_selection=True,
use_active_scene=True,
)
def delete_scene():
bpy.ops.scene.delete()
return None
bpy.app.timers.register(lambda: delete_scene(), first_interval=1)
# bpy.ops.scene.delete()
@ -1669,7 +1689,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
row.prop(self, "autodetect_names")
first_object_name = context.selected_objects[-1].name
if self.autodetect_names:
self.font_name, self.face_name = self.do_autodetect_names(first_object_name)
self.font_name, self.face_name = self.do_autodetect_names(
first_object_name
)
if self.autodetect_names:
scale_y = 0.5
row = layout.row()
@ -1867,7 +1889,8 @@ def compare_text_object_with_object(t, o, strict=False):
# if
return True
def link_text_object_with_new_text_properties(text_object, scene = None):
def link_text_object_with_new_text_properties(text_object, scene=None):
lock_depsgraph_updates(auto_unlock_s=-1)
butils.link_text_object_with_new_text_properties(text_object, scene)
unlock_depsgraph_updates()
@ -1878,12 +1901,12 @@ def detect_text():
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
required_keys = [
"type",
"text_id",
"font_name",
"face_name",
"text",
]
"type",
"text_id",
"font_name",
"face_name",
"text",
]
objects = scene.objects
for o in objects:
valid = True
@ -1896,10 +1919,7 @@ def detect_text():
if o[butils.get_key("type")] == "textobject":
current_text_id = int(o[butils.get_key("text_id")])
text_properties = butils.get_text_properties(current_text_id)
if (
text_properties is not None
and text_properties.text_object == o
):
if text_properties is not None and text_properties.text_object == o:
# all good
pass
else:
@ -1972,12 +1992,15 @@ def lock_depsgraph_updates(auto_unlock_s=1):
bpy.app.timers.unregister(unlock_depsgraph_updates)
bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s)
def are_depsgraph_updates_locked():
global depsgraph_updates_locked
return depsgraph_updates_locked > 0
import time
@persistent
def on_depsgraph_update(scene, depsgraph):
if not bpy.context.mode.startswith("EDIT") and not are_depsgraph_updates_locked():
@ -2010,7 +2033,9 @@ def register():
addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings.
bpy.utils.register_class(cls)
bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data)
bpy.types.Scene.abc3d_font_creation = bpy.props.PointerProperty(type=ABC3D_PG_FontCreation)
bpy.types.Scene.abc3d_font_creation = bpy.props.PointerProperty(
type=ABC3D_PG_FontCreation
)
# bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}")
# autostart if we load a blend file
@ -2025,6 +2050,7 @@ def register():
if on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update)
butils.run_in_main_thread(Font.fonts.clear)
butils.run_in_main_thread(butils.clear_available_fonts)
butils.run_in_main_thread(butils.register_installed_fonts)
butils.run_in_main_thread(butils.update_available_fonts)