[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(bimport)
importlib.reload(addon_updater_ops) importlib.reload(addon_updater_ops)
def getPreferences(context): def getPreferences(context):
preferences = context.preferences preferences = context.preferences
return preferences.addons[__name__].preferences return preferences.addons[__name__].preferences
@ -137,7 +138,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
def update_callback(self, context): def update_callback(self, context):
if self.text_id >= 0: if self.text_id >= 0:
# butils.set_text_on_curve( # 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) t = butils.get_text_properties(self.text_id)
if t is not None: if t is not None:
@ -145,8 +146,8 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
glyph_id: bpy.props.StringProperty(maxlen=1) glyph_id: bpy.props.StringProperty(maxlen=1)
text_id: bpy.props.IntProperty( text_id: bpy.props.IntProperty(
default=-1, default=-1,
) )
alternate: bpy.props.IntProperty( alternate: bpy.props.IntProperty(
default=-1, default=-1,
update=update_callback, update=update_callback,
@ -158,6 +159,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
update=update_callback, update=update_callback,
) )
class ABC3D_text_properties(bpy.types.PropertyGroup): class ABC3D_text_properties(bpy.types.PropertyGroup):
def font_items_callback(self, context): def font_items_callback(self, context):
items = [] items = []
@ -165,21 +167,6 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", "")) items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
return items 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): 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, can_regenerate=True) 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): def active_text_index_update(self, context):
if self.active_text_index != -1: 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: if text_properties is not None:
o = text_properties.text_object o = text_properties.text_object
# active_text_index changed. so let's update the selection # active_text_index changed. so let's update the selection
# check if it is already selected # check if it is already selected
# or perhaps one of the glyphs # or perhaps one of the glyphs
if (o is not None if (
o is not None
and not o.select_get() and not o.select_get()
and not len([c for c in o.children if c.select_get()]) > 0 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] available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name font_name = available_font.font_name
face_name = available_font.face_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: if face is not None:
available_glyphs = face.glyphs_in_fontfile available_glyphs = face.glyphs_in_fontfile
loaded_glyphs = sorted(face.loaded_glyphs) loaded_glyphs = sorted(face.loaded_glyphs)
@ -455,10 +445,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel):
@classmethod @classmethod
def poll(self, context): def poll(self, context):
if ( if context.active_object is not None and context.active_object.type == "CURVE":
context.active_object is not None
and context.active_object.type == "CURVE"
):
self.can_place = True self.can_place = True
else: else:
self.can_place = False self.can_place = False
@ -649,6 +636,7 @@ class ABC3D_PG_FontCreation(bpy.types.PropertyGroup):
update=naming_glyph_id_update_callback, update=naming_glyph_id_update_callback,
) )
class ABC3D_OT_NamingHelper(bpy.types.Operator): class ABC3D_OT_NamingHelper(bpy.types.Operator):
bl_label = "Font Creation Naming Helper Apply To Active Object" bl_label = "Font Creation Naming Helper Apply To Active Object"
bl_idname = f"{__name__}.apply_naming_helper" 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.row().prop(abc3d_font_creation, "face_name")
box.label(text="Glyph Output Name") box.label(text="Glyph Output Name")
box.row().prop(abc3d_font_creation, "naming_glyph_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): class ABC3D_PT_FontCreation(bpy.types.Panel):
bl_label = "Font Creation" bl_label = "Font Creation"
@ -725,7 +716,10 @@ class ABC3D_PT_FontCreation(bpy.types.Panel):
f"{__name__}.temporaryhelper", text="Debug Function Do Not Use" f"{__name__}.temporaryhelper", text="Debug Function Do Not Use"
) )
box.label(text="origin points") 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__}.align_origins_to_metrics", text="Align origins to Metrics (left)")
# box.row().operator(f"{__name__}.fix_objects_metrics_origins", text="Fix objects metrics origins") # 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] return bpy.context.scene.abc3d_data.available_texts[text_index]
else: else:
for t in bpy.context.scene.abc3d_data.available_texts: 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 t
return None return None
def get_active_glyph_properties(self): def get_active_glyph_properties(self):
a_o = bpy.context.active_object a_o = bpy.context.active_object
if a_o is not None: if a_o is not None:
if (f"{utils.prefix()}_text_id" in a_o if (
and f"{utils.prefix()}_glyph_index" in a_o): 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"] text_index = a_o[f"{utils.prefix()}_text_id"]
glyph_index = a_o[f"{utils.prefix()}_glyph_index"] 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: else:
for t in bpy.context.scene.abc3d_data.available_texts: 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: 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 g
return None return None
# def font_items_callback(self, context): # def font_items_callback(self, context):
# items = [] # items = []
# fonts = Font.get_loaded_fonts_and_faces() # 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") box.row().prop(glyph_props, "letter_spacing")
class ABC3D_OT_InstallFont(bpy.types.Operator): class ABC3D_OT_InstallFont(bpy.types.Operator):
"""Install or load Fontfile from path above. """Install or load Fontfile from path above.
(Format must be *.glb or *.gltf)""" (Format must be *.glb or *.gltf)"""
@ -1024,9 +1026,16 @@ class ABC3D_OT_LoadFont(bpy.types.Operator):
face_name: bpy.props.StringProperty() face_name: bpy.props.StringProperty()
def execute(self, context): 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: 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"} return {"CANCELLED"}
filepaths = face.filepaths filepaths = face.filepaths
for f in filepaths: for f in filepaths:
@ -1090,6 +1099,7 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator):
butils.align_metrics_of_objects(objects) butils.align_metrics_of_objects(objects)
return {"FINISHED"} return {"FINISHED"}
class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator): class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
"""Align origins of selected objects to origin of active object on one axis.""" """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_label = "Align origins to Active Object"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
enum_axis = (('0','X',''),('1','Y',''),('2','Z','')) enum_axis = (("0", "X", ""), ("1", "Y", ""), ("2", "Z", ""))
axis: bpy.props.EnumProperty(items = enum_axis, default='2') axis: bpy.props.EnumProperty(items=enum_axis, default="2")
def execute(self, context): def execute(self, context):
objects = bpy.context.selected_objects objects = bpy.context.selected_objects
butils.align_origins_to_active_object(objects, int(self.axis)) butils.align_origins_to_active_object(objects, int(self.axis))
return {"FINISHED"} return {"FINISHED"}
# class ABC3D_OT_AlignOriginsToMetrics(bpy.types.Operator): # 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_idname = f"{__name__}.align_origins_to_metrics"
# bl_label = "Align origins to metrics metrics" # bl_label = "Align origins to metrics metrics"
# bl_options = {"REGISTER", "UNDO"} # bl_options = {"REGISTER", "UNDO"}
# ignore_warning: bpy.props.BoolProperty( # ignore_warning: bpy.props.BoolProperty(
# name="Do not warn in the future", # name="Do not warn in the future",
# description="Do not warn in the future", # description="Do not warn in the future",
# default=False, # default=False,
# ) # )
# def draw(self, context): # def draw(self, context):
# layout = self.layout # layout = self.layout
# layout.row().label(text="Warning!") # 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 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="This may not be what you want.")
# layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.") # 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().label(text="If you are sure about what you're doing, please continue.")
# layout.row().prop(self, "ignore_warning") # layout.row().prop(self, "ignore_warning")
# def invoke(self, context, event): # def invoke(self, context, event):
# if not self.ignore_warning: # if not self.ignore_warning:
# wm = context.window_manager # wm = context.window_manager
# return wm.invoke_props_dialog(self) # return wm.invoke_props_dialog(self)
# return self.execute(context) # return self.execute(context)
# def execute(self, context): # def execute(self, context):
# objects = bpy.context.selected_objects # objects = bpy.context.selected_objects
# butils.align_origins_to_metrics(objects) # butils.align_origins_to_metrics(objects)
# butils.fix_objects_metrics_origins(objects) # butils.fix_objects_metrics_origins(objects)
# return {"FINISHED"} # return {"FINISHED"}
# class ABC3D_OT_FixObjectsMetricsOrigins(bpy.types.Operator): # 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_idname = f"{__name__}.fix_objects_metrics_origins"
# bl_label = "Fix metrics origin of all selected objects" # bl_label = "Fix metrics origin of all selected objects"
# bl_options = {"REGISTER", "UNDO"} # 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): 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.""" """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 mom = abc3d_data.available_texts[i].text_object
if self.remove_custom_properties: if self.remove_custom_properties:
def delif(o, p): def delif(o, p):
if p in o: if p in o:
del o[p] del o[p]
@ -1372,7 +1385,8 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator): class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
"""Toggle ABC3D Collection. """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_idname = f"{__name__}.toggle_abc3d_collection"
bl_label = "Toggle Collection visibility" bl_label = "Toggle Collection visibility"
@ -1404,8 +1418,8 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
bl_label = "Save Font" bl_label = "Save Font"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
can_execute : bpy.props.BoolProperty(default=True) can_execute: bpy.props.BoolProperty(default=True)
create_output_directory : bpy.props.BoolProperty(default=False) create_output_directory: bpy.props.BoolProperty(default=False)
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager 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] available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name font_name = available_font.font_name
face_name = available_font.face_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: if face is not None:
loaded_glyphs = sorted(face.loaded_glyphs) loaded_glyphs = sorted(face.loaded_glyphs)
n = 16 n = 16
@ -1463,7 +1477,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.label(text="Please select another directory") row.label(text="Please select another directory")
row = layout.row() row = layout.row()
row.alert = True 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 self.can_execute = False
row.alert = True row.alert = True
row.label(text="Directory does not exist and cannot be created") 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.label(text="Please select another directory")
row = layout.row() row = layout.row()
row.alert = True 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 self.can_execute = True
row.label(text="Directory does not exist") row.label(text="Directory does not exist")
row = layout.row() row = layout.row()
@ -1487,7 +1503,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row.prop(abc3d_data, "export_dir") row.prop(abc3d_data, "export_dir")
else: 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=}") print(f"{utils.prefix()} {Font.fonts=}")
def execute(self, context): def execute(self, context):
@ -1500,10 +1518,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
"ERROR", "ERROR",
[ [
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable", 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)): if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
path = 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", "ERROR",
[ [
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created", 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") fontcollection = bpy.data.collections.get("ABC3D")
@ -1598,9 +1616,11 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
use_selection=True, use_selection=True,
use_active_scene=True, use_active_scene=True,
) )
def delete_scene(): def delete_scene():
bpy.ops.scene.delete() bpy.ops.scene.delete()
return None return None
bpy.app.timers.register(lambda: delete_scene(), first_interval=1) bpy.app.timers.register(lambda: delete_scene(), first_interval=1)
# bpy.ops.scene.delete() # bpy.ops.scene.delete()
@ -1669,7 +1689,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
row.prop(self, "autodetect_names") row.prop(self, "autodetect_names")
first_object_name = context.selected_objects[-1].name first_object_name = context.selected_objects[-1].name
if self.autodetect_names: 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: if self.autodetect_names:
scale_y = 0.5 scale_y = 0.5
row = layout.row() row = layout.row()
@ -1867,7 +1889,8 @@ def compare_text_object_with_object(t, o, strict=False):
# if # if
return True 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) lock_depsgraph_updates(auto_unlock_s=-1)
butils.link_text_object_with_new_text_properties(text_object, scene) butils.link_text_object_with_new_text_properties(text_object, scene)
unlock_depsgraph_updates() unlock_depsgraph_updates()
@ -1878,12 +1901,12 @@ def detect_text():
scene = bpy.context.scene scene = bpy.context.scene
abc3d_data = scene.abc3d_data abc3d_data = scene.abc3d_data
required_keys = [ required_keys = [
"type", "type",
"text_id", "text_id",
"font_name", "font_name",
"face_name", "face_name",
"text", "text",
] ]
objects = scene.objects objects = scene.objects
for o in objects: for o in objects:
valid = True valid = True
@ -1896,10 +1919,7 @@ def detect_text():
if o[butils.get_key("type")] == "textobject": if o[butils.get_key("type")] == "textobject":
current_text_id = int(o[butils.get_key("text_id")]) current_text_id = int(o[butils.get_key("text_id")])
text_properties = butils.get_text_properties(current_text_id) text_properties = butils.get_text_properties(current_text_id)
if ( if text_properties is not None and text_properties.text_object == o:
text_properties is not None
and text_properties.text_object == o
):
# all good # all good
pass pass
else: else:
@ -1972,12 +1992,15 @@ def lock_depsgraph_updates(auto_unlock_s=1):
bpy.app.timers.unregister(unlock_depsgraph_updates) bpy.app.timers.unregister(unlock_depsgraph_updates)
bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s) bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s)
def are_depsgraph_updates_locked(): def are_depsgraph_updates_locked():
global depsgraph_updates_locked global depsgraph_updates_locked
return depsgraph_updates_locked > 0 return depsgraph_updates_locked > 0
import time import time
@persistent @persistent
def on_depsgraph_update(scene, depsgraph): def on_depsgraph_update(scene, depsgraph):
if not bpy.context.mode.startswith("EDIT") and not are_depsgraph_updates_locked(): 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. addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings.
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data) 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}") # bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}")
# autostart if we load a blend file # 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: if on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update) 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.clear_available_fonts)
butils.run_in_main_thread(butils.register_installed_fonts) butils.run_in_main_thread(butils.register_installed_fonts)
butils.run_in_main_thread(butils.update_available_fonts) butils.run_in_main_thread(butils.update_available_fonts)

570
butils.py
View file

@ -4,6 +4,7 @@ import queue
import re import re
import bpy import bpy
import bpy_types
import mathutils import mathutils
# import time # for debugging performance # import time # for debugging performance
@ -45,12 +46,14 @@ def apply_all_transforms(obj):
obj.matrix_basis.identity() obj.matrix_basis.identity()
def get_parent_collection_names(collection, parent_names): # broken
for parent_collection in bpy.data.collections: # def get_parent_collection_names(collection, parent_names):
if collection.name in parent_collection.children.keys(): # for parent_collection in bpy.data.collections:
parent_names.append(parent_collection.name) # if collection.name in parent_collection.children.keys():
get_parent_collection_names(parent_collection, parent_names) # parent_names.append(parent_collection.name)
return # get_parent_collection_names(parent_collection, parent_names)
# return
def get_key(key): def get_key(key):
return f"{utils.prefix()}_{key}" return f"{utils.prefix()}_{key}"
@ -139,15 +142,15 @@ def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t):
# class TestCalcPoint(): # class TestCalcPoint():
# co: mathutils.Vector # co: mathutils.Vector
# handle_left: mathutils.Vector # handle_left: mathutils.Vector
# handle_right: mathutils.Vector # handle_right: mathutils.Vector
# def __init__(self, co, handle_left=None, handle_right=None): # def __init__(self, co, handle_left=None, handle_right=None):
# self.co = co # self.co = co
# if handle_left is not None: # if handle_left is not None:
# self.handle_left = handle_left # self.handle_left = handle_left
# if handle_right is not None: # if handle_right is not None:
# self.handle_right = handle_right # self.handle_right = handle_right
# a = TestCalcPoint(mathutils.Vector((0,0,0)), handle_right=mathutils.Vector((0,1,0))) # a = TestCalcPoint(mathutils.Vector((0,0,0)), handle_right=mathutils.Vector((0,1,0)))
@ -157,7 +160,6 @@ def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t):
# calc_point_on_bezier(a,b,0.5) # calc_point_on_bezier(a,b,0.5)
def align_rotations_auto_pivot( def align_rotations_auto_pivot(
mask, input_rotations, vectors, factors, local_main_axis mask, input_rotations, vectors, factors, local_main_axis
): ):
@ -251,7 +253,7 @@ def calc_point_on_bezier_spline(
# however, maybe let's have it not crash and do this # however, maybe let's have it not crash and do this
if len(bezier_spline_obj.bezier_points) < 1: if len(bezier_spline_obj.bezier_points) < 1:
print( print(
"butils::calc_point_on_bezier_spline: whoops, no points. panicking. return 0,0,0" f"{utils.prefix()}::butils::calc_point_on_bezier_spline: whoops, no points. panicking. return 0,0,0"
) )
if output_tangent: 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))
@ -275,8 +277,9 @@ def calc_point_on_bezier_spline(
# if the bezier points sit on each other we have same issue # if the bezier points sit on each other we have same issue
# but that is then to be fixed in the bezier # but that is then to be fixed in the bezier
if p.handle_left == p.co and len(bezier_spline_obj.bezier_points) > 1: if p.handle_left == p.co and len(bezier_spline_obj.bezier_points) > 1:
beziers, lengths, total_length = get_real_beziers_and_lengths(bezier_spline_obj, beziers, lengths, total_length = get_real_beziers_and_lengths(
resolution_factor) bezier_spline_obj, resolution_factor
)
travel_point = calc_point_on_bezier(beziers[0][1], beziers[0][0], 0.001) travel_point = calc_point_on_bezier(beziers[0][1], beziers[0][0], 0.001)
travel = travel_point.normalized() * distance travel = travel_point.normalized() * distance
@ -288,8 +291,9 @@ def calc_point_on_bezier_spline(
else: else:
return location return location
beziers, lengths, total_length = get_real_beziers_and_lengths(bezier_spline_obj, beziers, lengths, total_length = get_real_beziers_and_lengths(
resolution_factor) bezier_spline_obj, resolution_factor
)
iterated_distance = 0 iterated_distance = 0
for i in range(0, len(beziers)): for i in range(0, len(beziers)):
@ -368,6 +372,7 @@ def calc_point_on_bezier_curve(
# and should not happen usually # and should not happen usually
return bezier_curve_obj.matrix_world @ mathutils.Vector((distance, 0, 0)) return bezier_curve_obj.matrix_world @ mathutils.Vector((distance, 0, 0))
# def get_objects_by_name(name, startswith="", endswith=""): # 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)]
@ -397,13 +402,14 @@ def find_objects_by_custom_property(objects, property_name="", property_value=""
] ]
def turn_collection_hierarchy_into_path(obj): # not verified
parent_collection = obj.users_collection[0] # def turn_collection_hierarchy_into_path(obj):
parent_names = [] # parent_collection = obj.users_collection[0]
parent_names.append(parent_collection.name) # parent_names = []
get_parent_collection_names(parent_collection, parent_names) # parent_names.append(parent_collection.name)
parent_names.reverse() # get_parent_collection_names(parent_collection, parent_names)
return "\\".join(parent_names) # parent_names.reverse()
# return "\\".join(parent_names)
def find_font_object(fontcollection, font_name): def find_font_object(fontcollection, font_name):
@ -458,7 +464,9 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
fontcollection.objects.link(glyphs_obj) fontcollection.objects.link(glyphs_obj)
glyphs_obj.parent = face_obj glyphs_obj.parent = face_obj
elif len(glyphs_objs) > 1: elif len(glyphs_objs) > 1:
print("found more glyphs objects than expected") print(
f"{utils.prefix()}::move_in_fontcollection: found more glyphs objects than expected"
)
# now it must exist # now it must exist
glyphs_obj = find_objects_by_name(face_obj.children, startswith="glyphs")[0] glyphs_obj = find_objects_by_name(face_obj.children, startswith="glyphs")[0]
@ -563,10 +571,14 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
for mff in modified_font_faces: for mff in modified_font_faces:
mff_glyphs = [] mff_glyphs = []
face : Font.FontFace = Font.get_font_face(mff["font_name"], mff["face_name"]) face: Font.FontFace = Font.get_font_face(mff["font_name"], mff["face_name"])
if face is None: if face is None:
print(f"{utils.prefix()}::load_font_from_path({filepath=}, {glyphs=}, {font_name=}, {face_name=}) failed") print(
print(f"modified font face {mff=} could not be accessed.") f"{utils.prefix()}::load_font_from_path({filepath=}, {glyphs=}, {font_name=}, {face_name=}) failed"
)
print(
f"{utils.prefix()}:: modified font face {mff=} could not be accessed."
)
continue continue
# iterate glyphs # iterate glyphs
for g in face.glyphs: for g in face.glyphs:
@ -592,6 +604,143 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
# completely_delete_objects(remove_list) # completely_delete_objects(remove_list)
def is_glyph_used(glyph_alternates):
fontcollection: bpy_types.Collection = bpy.data.collections.get("ABC3D")
glyph = bpy.types.PointerProperty
for glyph in glyph_alternates:
for o in bpy.context.scene.objects:
# only check other glyphs
if is_glyph_object(o):
# then attempt to compare properties
if (
get_key("font_name") in o
and get_key("face_name") in o
and get_key("glyph_id") in o
and o[get_key("font_name")] == glyph["font_name"]
and o[get_key("face_name")] == glyph["face_name"]
and o[get_key("glyph_id")] == glyph["glyph"]
):
# following check is not necessary,
# but we leave it in for backwards compatibility
# properties in the fontcollection start with prefix
# and so they should be caught by previous check
if fontcollection.users == 0 or not (
fontcollection in o.users_collection
and len(o.users_collection) <= 1
):
# it's in the scene and has the correct properties
# it is used
return True
# following check is possibly overkill
# but we also check for objects that use the data
# and are not glyph objects, in that case we don't pull the data
# from under their feet
if is_mesh(o) and o.data == glyph.data:
# in this case, yes we need to check if it is a glyph in the fontcollection
if fontcollection.users == 0 or not (
fontcollection in o.users_collection
and len(o.users_collection) <= 1
):
# bam!
return True
# whoosh!
return False
def clean_fontcollection(fontcollection=None):
if fontcollection is None:
fontcollection = bpy.data.collections.get("ABC3D")
if fontcollection is None:
print(
f"{utils.prefix()}::clean_fontcollection: failed beacause fontcollection is none"
)
return False
collection_fonts = find_objects_by_custom_property(
fontcollection.all_objects, "is_font", True
)
delete_these_fonts = []
delete_these_font_faces = []
delete_these_glyph_moms = []
for font_and_face in Font.get_loaded_fonts_and_faces():
font_name = font_and_face[0]
face_name = font_and_face[1]
collection_font_list = find_objects_by_custom_property(
collection_fonts, "font_name", font_name
)
for collection_font in collection_font_list:
collection_font_face_list = find_objects_by_custom_property(
collection_font.children, "face_name", face_name
)
count_font_faces = 0
for collection_font_face in collection_font_face_list:
glyphs_mom_list = find_objects_by_name(
collection_font_face.children, startswith="glyphs"
)
count_glyphs_moms = 0
for glyphs_mom in glyphs_mom_list:
if len(glyphs_mom.children) == 0:
delete_these_glyph_moms.append(glyphs_mom)
count_glyphs_moms += 1
if len(collection_font_face.children) == count_glyphs_moms:
delete_these_font_faces.append(collection_font_face)
count_font_faces += 1
if len(collection_font.children) == count_font_faces:
delete_these_fonts.append(collection_font)
completely_delete_objects(delete_these_glyph_moms)
completely_delete_objects(delete_these_font_faces)
completely_delete_objects(delete_these_fonts)
def unload_unused_glyph(font_name, face_name, glyph_id, do_clean_fontcollection=True):
fontcollection: bpy_types.Collection = bpy.data.collections.get("ABC3D")
glyph_variations = Font.get_glyphs(font_name, face_name, glyph_id)
if is_glyph_used(glyph_variations):
return False
delete_these = []
for glyph_pointer in glyph_variations:
for o in fontcollection.all_objects:
if (
is_glyph_object(o)
and o["font_name"] == font_name
and o["face_name"] == face_name
and o["glyph"] == glyph_id
):
if len(o.users_collection) <= 1:
delete_these.append(o)
completely_delete_objects(delete_these)
Font.unloaded_glyph(font_name, face_name, glyph_id)
if do_clean_fontcollection:
clean_fontcollection(fontcollection)
return True
def unload_unused_glyphs(do_clean_fontcollection=True):
fontcollection: bpy_types.Collection = bpy.data.collections.get("ABC3D")
if fontcollection is not None:
for font_and_face in Font.get_loaded_fonts_and_faces():
font_name = font_and_face[0]
face_name = font_and_face[1]
face: Font.FontFace | None = Font.get_font_face(font_name, face_name)
if face is None:
print(
f"{utils.prefix()}::unload_unused_glyphs: face is None {font_name=} {face_name=}"
)
continue
unloaded_these = []
for glyph_id in face.loaded_glyphs.copy():
unload_unused_glyph(
font_name, face_name, glyph_id, do_clean_fontcollection=False
)
if do_clean_fontcollection:
clean_fontcollection(fontcollection)
def update_available_fonts(): def update_available_fonts():
abc3d_data = bpy.context.scene.abc3d_data abc3d_data = bpy.context.scene.abc3d_data
@ -606,7 +755,9 @@ def update_available_fonts():
f = abc3d_data.available_fonts.add() f = abc3d_data.available_fonts.add()
f.font_name = font_name f.font_name = font_name
f.face_name = face_name f.face_name = face_name
print(f"{utils.prefix()}::update_available_fonts: {__name__} added {font_name} {face_name}") print(
f"{utils.prefix()}::update_available_fonts: {__name__} added {font_name} {face_name}"
)
# def update_available_texts(): # def update_available_texts():
@ -724,6 +875,8 @@ def completely_delete_objects(objs, recursive=True):
except ReferenceError: except ReferenceError:
# not important # not important
pass pass
except RuntimeError:
pass
def is_mesh(o): def is_mesh(o):
@ -753,7 +906,7 @@ def is_glyph_object(o):
return o[f"{utils.prefix()}_type"] == "glyph" return o[f"{utils.prefix()}_type"] == "glyph"
try: try:
return ( return (
type(o.parent) is not type(None) o.parent is not None
and "glyphs" in o.parent.name and "glyphs" in o.parent.name
and is_mesh(o) and is_mesh(o)
and not is_metrics_object(o) and not is_metrics_object(o)
@ -792,6 +945,7 @@ def get_glyph_advance(glyph_obj):
return abs(c.bound_box[4][0] - c.bound_box[0][0]) 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]) return abs(glyph_obj.bound_box[4][0] - glyph_obj.bound_box[0][0])
def get_glyph_prepost_advances(glyph_obj): def get_glyph_prepost_advances(glyph_obj):
for c in glyph_obj.children: for c in glyph_obj.children:
if is_metrics_object(c): if is_metrics_object(c):
@ -807,14 +961,16 @@ def get_glyph_height(glyph_obj):
def prepare_text(font_name, face_name, text, allow_replacement=True): def prepare_text(font_name, face_name, text, allow_replacement=True):
availability = Font.test_glyphs_availability( availability = Font.test_glyphs_availability(font_name, face_name, text)
font_name, face_name, text
)
if isinstance(availability, int): if isinstance(availability, int):
if availability == Font.MISSING_FONT: if availability == Font.MISSING_FONT:
print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FONT") print(
f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FONT"
)
if availability is Font.MISSING_FACE: if availability is Font.MISSING_FACE:
print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FACE") print(
f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FACE"
)
return False return False
loadable = availability.unloaded loadable = availability.unloaded
# possibly replace upper and lower case letters with each other # possibly replace upper and lower case letters with each other
@ -832,9 +988,16 @@ def prepare_text(font_name, face_name, text, allow_replacement=True):
load_font_from_filepath(filepath, loadable, font_name, face_name) load_font_from_filepath(filepath, loadable, font_name, face_name)
return True return True
def predict_actual_text(text_properties): def predict_actual_text(text_properties):
availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) availability = Font.test_availability(
AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) text_properties.font_name, text_properties.face_name, text_properties.text
)
AVAILABILITY = Font.test_availability(
text_properties.font_name,
text_properties.face_name,
text_properties.text.swapcase(),
)
t_text = text_properties.text t_text = text_properties.text
for c in availability.missing: for c in availability.missing:
t_text = t_text.replace(c, "") t_text = t_text.replace(c, "")
@ -842,6 +1005,7 @@ def predict_actual_text(text_properties):
t_text = t_text.replace(c, "") t_text = t_text.replace(c, "")
return t_text return t_text
def is_bezier(curve): def is_bezier(curve):
if curve.type != "CURVE": if curve.type != "CURVE":
return False return False
@ -906,6 +1070,7 @@ COMPARE_TEXT_OBJECT_SAME = 0
COMPARE_TEXT_OBJECT_DIFFER = 1 COMPARE_TEXT_OBJECT_DIFFER = 1
COMPARE_TEXT_OBJECT_REGENERATE = 2 COMPARE_TEXT_OBJECT_REGENERATE = 2
def find_free_text_id(): def find_free_text_id():
scene = bpy.context.scene scene = bpy.context.scene
abc3d_data = scene.abc3d_data abc3d_data = scene.abc3d_data
@ -922,21 +1087,27 @@ def find_free_text_id():
found_free = True found_free = True
return text_id return text_id
def compare_text_properties_to_text_object(text_properties, o): def compare_text_properties_to_text_object(text_properties, o):
for key in text_object_keys: for key in text_object_keys:
if key in ignore_keys_in_text_object_comparison: if key in ignore_keys_in_text_object_comparison:
continue continue
object_key = get_key(key) object_key = get_key(key)
text_property = text_properties[key] if key in text_properties else getattr(text_properties, key) text_property = (
text_properties[key]
if key in text_properties
else getattr(text_properties, key)
)
text_object_property = o[object_key] if object_key in o else False text_object_property = o[object_key] if object_key in o else False
if text_property != text_object_property: if text_property != text_object_property:
if key in keys_trigger_regeneration: if key in keys_trigger_regeneration:
return COMPARE_TEXT_OBJECT_REGENERATE return COMPARE_TEXT_OBJECT_REGENERATE
elif key in ["translation", "orientation"]: elif key in ["translation", "orientation"]:
if ( if (
text_property[0] != text_object_property[0] or text_property[0] != text_object_property[0]
text_property[1] != text_object_property[1] or or text_property[1] != text_object_property[1]
text_property[2] != text_object_property[2]): or text_property[2] != text_object_property[2]
):
return COMPARE_TEXT_OBJECT_DIFFER return COMPARE_TEXT_OBJECT_DIFFER
# else same # else same
else: else:
@ -950,16 +1121,17 @@ def transfer_text_properties_to_text_object(text_properties, o):
if key in ignore_keys_in_text_object_comparison: if key in ignore_keys_in_text_object_comparison:
continue continue
object_key = get_key(key) object_key = get_key(key)
text_property = text_properties[key] if key in text_properties else getattr(text_properties, key) text_property = (
text_properties[key]
if key in text_properties
else getattr(text_properties, key)
)
o[object_key] = text_property o[object_key] = text_property
o[get_key("type")] = "textobject" o[get_key("type")] = "textobject"
def get_glyph(glyph_id, font_name, face_name, notify_on_replacement=False): def get_glyph(glyph_id, font_name, face_name, notify_on_replacement=False):
glyph_tmp = Font.get_glyph(font_name, glyph_tmp = Font.get_glyph(font_name, face_name, glyph_id, -1)
face_name,
glyph_id,
-1)
if glyph_tmp is None: if glyph_tmp is None:
space_width = Font.is_space(glyph_id) space_width = Font.is_space(glyph_id)
if space_width: if space_width:
@ -973,7 +1145,7 @@ def get_glyph(glyph_id, font_name, face_name, notify_on_replacement=False):
text_properties.font_name, text_properties.font_name,
text_properties.face_name, text_properties.face_name,
possible_replacement, possible_replacement,
-1 -1,
) )
if glyph_tmp is not None: if glyph_tmp is not None:
message = message + f" (replaced with '{possible_replacement}')" message = message + f" (replaced with '{possible_replacement}')"
@ -991,7 +1163,8 @@ def get_glyph(glyph_id, font_name, face_name, notify_on_replacement=False):
return glyph_tmp.original return glyph_tmp.original
def get_text_properties(text_id, scene = None):
def get_text_properties(text_id, scene=None):
if scene is None: if scene is None:
scene = bpy.context.scene scene = bpy.context.scene
abc3d_data = scene.abc3d_data abc3d_data = scene.abc3d_data
@ -1000,7 +1173,15 @@ def get_text_properties(text_id, scene = None):
return t return t
return None return None
def duplicate(obj, data=True, actions=True, add_to_collection=True, collection=None, recursive=True):
def duplicate(
obj,
data=True,
actions=True,
add_to_collection=True,
collection=None,
recursive=True,
):
obj_copy = obj.copy() obj_copy = obj.copy()
if add_to_collection: if add_to_collection:
if collection: if collection:
@ -1019,8 +1200,13 @@ def duplicate(obj, data=True, actions=True, add_to_collection=True, collection=N
# child_copy.matrix_parent_inverse = obj_copy.matrix_world.inverted() # child_copy.matrix_parent_inverse = obj_copy.matrix_world.inverted()
return obj_copy return obj_copy
def transfer_text_object_to_text_properties(text_object, text_properties, id_from_text_properties=True):
possible_brother_text_id = text_object[get_key("text_id")] if get_key("text_id") in text_object else "" def transfer_text_object_to_text_properties(
text_object, text_properties, id_from_text_properties=True
):
possible_brother_text_id = (
text_object[get_key("text_id")] if get_key("text_id") in text_object else ""
)
for key in text_object_keys: for key in text_object_keys:
if key in ignore_keys_in_text_object_comparison: if key in ignore_keys_in_text_object_comparison:
continue continue
@ -1028,12 +1214,17 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro
if id_from_text_properties and key == "text_id": if id_from_text_properties and key == "text_id":
text_object[object_key] = text_properties["text_id"] text_object[object_key] = text_properties["text_id"]
else: else:
text_object_property = text_object[object_key] if object_key in text_object else False text_object_property = (
text_object[object_key] if object_key in text_object else False
)
if text_object_property is not False: if text_object_property is not False:
text_properties[key] = text_object_property text_properties[key] = text_object_property
if len(text_object.children) == 0: if len(text_object.children) == 0:
if possible_brother_text_id != text_properties["text_id"] and possible_brother_text_id != "": if (
possible_brother_text_id != text_properties["text_id"]
and possible_brother_text_id != ""
):
possible_brother_properties = get_text_properties(possible_brother_text_id) possible_brother_properties = get_text_properties(possible_brother_text_id)
possible_brother_object = possible_brother_properties.text_object possible_brother_object = possible_brother_properties.text_object
if possible_brother_object is not None: if possible_brother_object is not None:
@ -1047,11 +1238,7 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro
found_reconstructable_glyphs = False found_reconstructable_glyphs = False
glyph_objects_with_indices = [] glyph_objects_with_indices = []
required_keys = [ required_keys = ["glyph_index", "glyph_id", "type"]
"glyph_index",
"glyph_id",
"type"
]
for glyph_object in text_object.children: for glyph_object in text_object.children:
if is_glyph_object(glyph_object): if is_glyph_object(glyph_object):
has_required_keys = True has_required_keys = True
@ -1087,8 +1274,8 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro
for glyph_index, glyph_object in enumerate(glyph_objects_with_indices): for glyph_index, glyph_object in enumerate(glyph_objects_with_indices):
glyph_id = glyph_object[get_key("glyph_id")] glyph_id = glyph_object[get_key("glyph_id")]
# glyph_tmp = Font.get_glyph(text_properties.font_name, # glyph_tmp = Font.get_glyph(text_properties.font_name,
# text_properties.face_name, # text_properties.face_name,
# glyph_id) # glyph_id)
# glyph = glyph_tmp.original # glyph = glyph_tmp.original
glyph_properties = text_properties.glyphs.add() glyph_properties = text_properties.glyphs.add()
@ -1111,8 +1298,10 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro
text_properties.glyphs.clear() text_properties.glyphs.clear()
unfortunate_children = text_object.children unfortunate_children = text_object.children
completely_delete_objects(unfortunate_children) completely_delete_objects(unfortunate_children)
def kill_children(): def kill_children():
completely_delete_objects(unfortunate_children) completely_delete_objects(unfortunate_children)
run_in_main_thread(kill_children) run_in_main_thread(kill_children)
if "font_name" in text_properties and "face_name" in text_properties: if "font_name" in text_properties and "face_name" in text_properties:
@ -1128,9 +1317,11 @@ def link_text_object_with_new_text_properties(text_object, scene=None):
text_properties = scene.abc3d_data.available_texts.add() text_properties = scene.abc3d_data.available_texts.add()
text_properties["text_id"] = text_id text_properties["text_id"] = text_id
# text_object[get_key("text_id")] = text_id # text_object[get_key("text_id")] = text_id
prepare_text(text_object[get_key("font_name")], prepare_text(
text_object[get_key("face_name")], text_object[get_key("font_name")],
text_object[get_key("text")]) text_object[get_key("face_name")],
text_object[get_key("text")],
)
text_properties.text_object = text_object text_properties.text_object = text_object
transfer_text_object_to_text_properties(text_object, text_properties) transfer_text_object_to_text_properties(text_object, text_properties)
@ -1144,14 +1335,14 @@ def test_finding():
o = bpy.context.active_object o = bpy.context.active_object
transfer_text_object_to_text_properties(o, t) transfer_text_object_to_text_properties(o, t)
# def detect_texts():
# scene = bpy.context.scene
# abc3d_data = scene.abc3d_data
# for o in bpy.data.objects:
# if get_key("type") in o \
# and o[get_key("type") == "textobject" \
# and o[get_key("t
# def detect_texts():
# scene = bpy.context.scene
# abc3d_data = scene.abc3d_data
# for o in bpy.data.objects:
# if get_key("type") in o \
# and o[get_key("type") == "textobject" \
# and o[get_key("t
def link_text_object_and_text_properties(o, text_properties): def link_text_object_and_text_properties(o, text_properties):
@ -1159,22 +1350,33 @@ def link_text_object_and_text_properties(o, text_properties):
o["text_id"] = text_id o["text_id"] = text_id
text_properties.textobject = o text_properties.textobject = o
def get_glyph_object_property(text_properties, glyph_properties, key): def get_glyph_object_property(text_properties, glyph_properties, key):
if key in glyph_properties: if key in glyph_properties:
return glyph_properties[key] return glyph_properties[key]
if hasattr(glyph_properties, key): if hasattr(glyph_properties, key):
return getattr(glyph_properties, key) return getattr(glyph_properties, key)
return text_properties[key] if key in text_properties else getattr(text_properties, key) return (
text_properties[key]
if key in text_properties
else getattr(text_properties, key)
)
def transfer_properties_to_glyph_object(text_properties, glyph_properties, glyph_object):
def transfer_properties_to_glyph_object(
text_properties, glyph_properties, glyph_object
):
for key in glyph_object_keys: for key in glyph_object_keys:
if key in ignore_keys_in_glyph_object_transfer: if key in ignore_keys_in_glyph_object_transfer:
continue continue
object_key = get_key(key) object_key = get_key(key)
glyph_object[object_key] = get_glyph_object_property(text_properties, glyph_properties, key) glyph_object[object_key] = get_glyph_object_property(
text_properties, glyph_properties, key
)
glyph_object[get_key("type")] = "glyph" glyph_object[get_key("type")] = "glyph"
glyph_object[get_key("text_id")] = text_properties["text_id"] glyph_object[get_key("text_id")] = text_properties["text_id"]
def transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties): def transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties):
for key in glyph_object_keys: for key in glyph_object_keys:
if key in ignore_keys_in_glyph_object_transfer: if key in ignore_keys_in_glyph_object_transfer:
@ -1182,6 +1384,7 @@ def transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties):
glyph_properties[key] = glyph_object[get_key(key)] glyph_properties[key] = glyph_object[get_key(key)]
glyph_properties["text_id"] = glyph_object[get_key("text_id")] glyph_properties["text_id"] = glyph_object[get_key("text_id")]
def would_regenerate(text_properties): def would_regenerate(text_properties):
predicted_text = predict_actual_text(text_properties) predicted_text = predict_actual_text(text_properties)
if text_properties.actual_text != predicted_text: if text_properties.actual_text != predicted_text:
@ -1217,10 +1420,11 @@ def update_matrices(obj):
if obj.parent is None: if obj.parent is None:
obj.matrix_world = obj.matrix_basis obj.matrix_world = obj.matrix_basis
# else: # else:
obj.matrix_world = obj.parent.matrix_world * \ obj.matrix_world = (
obj.matrix_parent_inverse * \ obj.parent.matrix_world * obj.matrix_parent_inverse * obj.matrix_basis
obj.matrix_basis )
def is_or_has_parent(o, parent, if_is_parent=True, max_depth=10): def is_or_has_parent(o, parent, if_is_parent=True, max_depth=10):
if o == parent and if_is_parent: if o == parent and if_is_parent:
@ -1234,8 +1438,9 @@ def is_or_has_parent(o, parent, if_is_parent=True, max_depth=10):
return False return False
return False return False
def parent_to_curve(o, c): def parent_to_curve(o, c):
o.parent_type = 'OBJECT' o.parent_type = "OBJECT"
o.parent = c o.parent = c
# o.matrix_parent_inverse = c.matrix_world.inverted() # o.matrix_parent_inverse = c.matrix_world.inverted()
@ -1244,13 +1449,15 @@ def parent_to_curve(o, c):
i = -1 if c.data.splines[0].use_cyclic_u else 0 i = -1 if c.data.splines[0].use_cyclic_u else 0
p = c.data.splines[0].bezier_points[i].co p = c.data.splines[0].bezier_points[i].co
o.matrix_parent_inverse.translation = p * -1.0 o.matrix_parent_inverse.translation = p * -1.0
elif c.data.splines[0].type == 'NURBS': elif c.data.splines[0].type == "NURBS":
cm = c.to_mesh() cm = c.to_mesh()
p = cm.vertices[0].co p = cm.vertices[0].co
o.matrix_parent_inverse.translation = p * -1.0 o.matrix_parent_inverse.translation = p * -1.0
def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, can_regenerate=False): def set_text_on_curve(
text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, can_regenerate=False
):
"""set_text_on_curve """set_text_on_curve
An earlier reset cancels the other. An earlier reset cancels the other.
@ -1274,7 +1481,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
distribution_type = "CALCULATE" if is_bezier(mom) else "FOLLOW_PATH" distribution_type = "CALCULATE" if is_bezier(mom) else "FOLLOW_PATH"
# NOTE: following not necessary anymore # NOTE: following not necessary anymore
# as we fixed data_path with parent_to_curve trick # as we fixed data_path with parent_to_curve trick
# #
@ -1283,9 +1489,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
# https://projects.blender.org/blender/blender/issues/100661 # https://projects.blender.org/blender/blender/issues/100661
# previous_use_path = mom.data.use_path # previous_use_path = mom.data.use_path
# if distribution_type == "CALCULATE": # if distribution_type == "CALCULATE":
# mom.data.use_path = False # mom.data.use_path = False
# elif distribution_type == "FOLLOW_PATH": # elif distribution_type == "FOLLOW_PATH":
# mom.data.use_path = True # mom.data.use_path = True
regenerate = can_regenerate and would_regenerate(text_properties) regenerate = can_regenerate and would_regenerate(text_properties)
@ -1330,10 +1536,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
############### GET GLYPH ############### GET GLYPH
glyph_tmp = Font.get_glyph(text_properties.font_name, glyph_tmp = Font.get_glyph(
text_properties.face_name, text_properties.font_name, text_properties.face_name, glyph_id, -1
glyph_id, )
-1)
if glyph_tmp is None: if glyph_tmp is None:
space_width = Font.is_space(glyph_id) space_width = Font.is_space(glyph_id)
if space_width: if space_width:
@ -1348,7 +1553,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
text_properties.font_name, text_properties.font_name,
text_properties.face_name, text_properties.face_name,
possible_replacement, possible_replacement,
-1 -1,
) )
if glyph_tmp is not None: if glyph_tmp is not None:
message = message + f" (replaced with '{possible_replacement}')" message = message + f" (replaced with '{possible_replacement}')"
@ -1368,7 +1573,11 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
############### GLYPH PROPERTIES ############### GLYPH PROPERTIES
glyph_properties = text_properties.glyphs[glyph_index] if not regenerate else text_properties.glyphs.add() glyph_properties = (
text_properties.glyphs[glyph_index]
if not regenerate
else text_properties.glyphs.add()
)
if regenerate: if regenerate:
glyph_properties["glyph_id"] = glyph_id glyph_properties["glyph_id"] = glyph_id
@ -1383,14 +1592,16 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
if regenerate: if regenerate:
outer_node = bpy.data.objects.new(f"{glyph_id}", None) outer_node = bpy.data.objects.new(f"{glyph_id}", None)
inner_node = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data) inner_node = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data)
transfer_properties_to_glyph_object(text_properties, glyph_properties, outer_node) transfer_properties_to_glyph_object(
text_properties, glyph_properties, outer_node
)
# Add into the scene. # Add into the scene.
mom.users_collection[0].objects.link(outer_node) mom.users_collection[0].objects.link(outer_node)
mom.users_collection[0].objects.link(inner_node) mom.users_collection[0].objects.link(inner_node)
# Parenting is hard. # Parenting is hard.
inner_node.parent_type = 'OBJECT' inner_node.parent_type = "OBJECT"
inner_node.parent = outer_node inner_node.parent = outer_node
inner_node.matrix_parent_inverse = outer_node.matrix_world.inverted() inner_node.matrix_parent_inverse = outer_node.matrix_world.inverted()
parent_to_curve(outer_node, mom) parent_to_curve(outer_node, mom)
@ -1426,7 +1637,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
outer_node.constraints.new(type="FOLLOW_PATH") outer_node.constraints.new(type="FOLLOW_PATH")
outer_node.constraints["Follow Path"].target = mom outer_node.constraints["Follow Path"].target = mom
outer_node.constraints["Follow Path"].use_fixed_location = True outer_node.constraints["Follow Path"].use_fixed_location = True
outer_node.constraints["Follow Path"].offset_factor = applied_advance / curve_length outer_node.constraints["Follow Path"].offset_factor = (
applied_advance / curve_length
)
outer_node.constraints["Follow Path"].use_curve_follow = True outer_node.constraints["Follow Path"].use_curve_follow = True
outer_node.constraints["Follow Path"].forward_axis = "FORWARD_X" outer_node.constraints["Follow Path"].forward_axis = "FORWARD_X"
outer_node.constraints["Follow Path"].up_axis = "UP_Y" outer_node.constraints["Follow Path"].up_axis = "UP_Y"
@ -1442,7 +1655,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
previous_inner_node_rotation_mode = inner_node.rotation_mode previous_inner_node_rotation_mode = inner_node.rotation_mode
# get info from bezier # get info from bezier
location, tangent, spline_index = calc_point_on_bezier_curve(mom, applied_advance, True, True) location, tangent, spline_index = calc_point_on_bezier_curve(
mom, applied_advance, True, True
)
# check if we are on a new line # check if we are on a new line
if spline_index != previous_spline_index: if spline_index != previous_spline_index:
@ -1457,13 +1672,19 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
vectors = [tangent] vectors = [tangent]
factors = [1.0] factors = [1.0]
local_main_axis = mathutils.Vector((1.0, 0.0, 0.0)) local_main_axis = mathutils.Vector((1.0, 0.0, 0.0))
motor = align_rotations_auto_pivot( motor = (
mask, input_rotations, vectors, factors, local_main_axis align_rotations_auto_pivot(
) if not text_properties.ignore_orientation else [mathutils.Matrix()] mask, input_rotations, vectors, factors, local_main_axis
)
if not text_properties.ignore_orientation
else [mathutils.Matrix()]
)
q = mathutils.Quaternion() q = mathutils.Quaternion()
q.rotate(text_properties.orientation) q.rotate(text_properties.orientation)
outer_node.rotation_quaternion = (motor[0].to_3x3() @ q.to_matrix()).to_quaternion() outer_node.rotation_quaternion = (
motor[0].to_3x3() @ q.to_matrix()
).to_quaternion()
# # NOTE: supercool but out of scope, as we wouldhave to update it everytime the curve object rotates, # # NOTE: supercool but out of scope, as we wouldhave to update it everytime the curve object rotates,
# # but this would ignore the curve objects orientation: # # but this would ignore the curve objects orientation:
@ -1482,16 +1703,16 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
############### PREPARE FOR THE NEXT ############### PREPARE FOR THE NEXT
glyph_advance = ( glyph_advance = (
glyph_post_advance * scalor + text_properties.letter_spacing + glyph_properties.letter_spacing glyph_post_advance * scalor
+ text_properties.letter_spacing
+ glyph_properties.letter_spacing
) )
# now we need to compensate for curvature # now we need to compensate for curvature
# otherwise letters will be closer together the curvier the bezier is # otherwise letters will be closer together the curvier the bezier is
# NOTE: this could be done more efficiently # NOTE: this could be done more efficiently
curve_compensation = 0 curve_compensation = 0
if distribution_type == "CALCULATE" and ( if distribution_type == "CALCULATE" and (not is_newline or spline_index == 0):
not is_newline or spline_index == 0
):
if text_properties.compensate_curvature and glyph_advance > 0: if text_properties.compensate_curvature and glyph_advance > 0:
previous_location, psi = calc_point_on_bezier_curve( previous_location, psi = calc_point_on_bezier_curve(
mom, advance, False, True mom, advance, False, True
@ -1503,8 +1724,10 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
n_max = 100 n_max = 100
n = 0 n = 0
while ( while (
previous_location - new_location (previous_location - new_location).length > glyph_advance
).length > glyph_advance and psi == si and n < n_max: and psi == si
and n < n_max
):
curve_compensation = curve_compensation - glyph_advance * 0.01 curve_compensation = curve_compensation - glyph_advance * 0.01
tmp_new_location, si = calc_point_on_bezier_curve( tmp_new_location, si = calc_point_on_bezier_curve(
mom, mom,
@ -1513,14 +1736,18 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
output_spline_index=True, output_spline_index=True,
) )
if tmp_new_location == new_location: if tmp_new_location == new_location:
print(f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome") print(
f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome"
)
break break
new_location = tmp_new_location new_location = tmp_new_location
n += 1 n += 1
n = 0 n = 0
while ( while (
previous_location - new_location (previous_location - new_location).length < glyph_advance
).length < glyph_advance and psi == si and n < n_max: and psi == si
and n < n_max
):
curve_compensation = curve_compensation + glyph_advance * 0.01 curve_compensation = curve_compensation + glyph_advance * 0.01
tmp_new_location, si = calc_point_on_bezier_curve( tmp_new_location, si = calc_point_on_bezier_curve(
mom, mom,
@ -1529,7 +1756,9 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
output_spline_index=True, output_spline_index=True,
) )
if tmp_new_location == new_location: if tmp_new_location == new_location:
print(f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome") print(
f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome"
)
break break
new_location = tmp_new_location new_location = tmp_new_location
n += 1 n += 1
@ -1813,6 +2042,8 @@ def add_default_metrics_to_objects(objects=None, overwrite_existing=False):
targets = [] targets = []
reference_bound_box = None reference_bound_box = None
for o in objects: for o in objects:
if not hasattr(o, "parent"):
print(f"{o.name} has not a PARENTNTNTNTNTNNTNTNTNTNTN")
is_possibly_glyph = is_glyph(o) is_possibly_glyph = is_glyph(o)
if is_possibly_glyph: if is_possibly_glyph:
metrics = [] metrics = []
@ -1932,6 +2163,7 @@ def align_metrics_of_objects(objects=None):
add_metrics_obj_from_bound_box(t, bound_box) add_metrics_obj_from_bound_box(t, bound_box)
return "" return ""
def align_origins_to_active_object(objects=None, axis=2): def align_origins_to_active_object(objects=None, axis=2):
if objects is None: if objects is None:
objects = bpy.context.selected_objects objects = bpy.context.selected_objects
@ -1957,84 +2189,84 @@ def align_origins_to_active_object(objects=None, axis=2):
return "" return ""
# NOTE: # NOTE:
# Following code is not necessary anymore, # Following code is not necessary anymore,
# as we derive the advance through metrics # as we derive the advance through metrics
# boundaries # boundaries
# def divide_vectors(v1=mathutils.Vector((1.0,1.0,1.0)), v2=mathutils.Vector((1.0,1.0,1.0))): # def divide_vectors(v1=mathutils.Vector((1.0,1.0,1.0)), v2=mathutils.Vector((1.0,1.0,1.0))):
# return mathutils.Vector([v1[i] / v2[i] for i in range(3)]) # return mathutils.Vector([v1[i] / v2[i] for i in range(3)])
# def get_origin_shift_metrics(o, axis=0): # def get_origin_shift_metrics(o, axis=0):
# if not is_metrics_object(o): # if not is_metrics_object(o):
# return False # return False
# min_value = sys.float_info.max # min_value = sys.float_info.max
# for v in o.data.vertices: # for v in o.data.vertices:
# if v.co[axis] < min_value: # if v.co[axis] < min_value:
# min_value = v.co[axis] # min_value = v.co[axis]
# if min_value == sys.float_info.max: # if min_value == sys.float_info.max:
# return False # return False
# return min_value # return min_value
# def fix_origin_shift_metrics(o, axis=0): # def fix_origin_shift_metrics(o, axis=0):
# shift = get_origin_shift_metrics(o) # shift = get_origin_shift_metrics(o)
# if not shift: # if not shift:
# print("False") # print("False")
# return False # return False
# for v in o.data.vertices: # for v in o.data.vertices:
# v.co[axis] -= shift # v.co[axis] -= shift
# shift_vector = mathutils.Vector((0.0, 0.0, 0.0)) # shift_vector = mathutils.Vector((0.0, 0.0, 0.0))
# shift_vector[axis] = shift # shift_vector[axis] = shift
# # o.location = o.location - (divide_vectors(v2=o.matrix_world.to_scale()) * (o.matrix_world @ shift_vector)) # # o.location = o.location - (divide_vectors(v2=o.matrix_world.to_scale()) * (o.matrix_world @ shift_vector))
# o.matrix_local.translation = o.matrix_local.translation + (shift_vector @ o.matrix_local.inverted()) # o.matrix_local.translation = o.matrix_local.translation + (shift_vector @ o.matrix_local.inverted())
# # update_matrices(o) # # update_matrices(o)
# return True # return True
# def fix_objects_metrics_origins(objects=None, axis=0, handle_metrics_directly=True): # def fix_objects_metrics_origins(objects=None, axis=0, handle_metrics_directly=True):
# if objects is None: # if objects is None:
# objects = bpy.context.selected_objects # objects = bpy.context.selected_objects
# if len(objects) == 0: # if len(objects) == 0:
# return "no objects selected" # return "no objects selected"
# for o in objects: # for o in objects:
# is_possibly_glyph = is_glyph(o) # is_possibly_glyph = is_glyph(o)
# if is_possibly_glyph: # if is_possibly_glyph:
# for c in o.children: # for c in o.children:
# if is_metrics_object(c): # if is_metrics_object(c):
# fix_origin_shift_metrics(c, axis) # fix_origin_shift_metrics(c, axis)
# elif is_metrics_object(o) and handle_metrics_directly: # elif is_metrics_object(o) and handle_metrics_directly:
# fix_origin_shift_metrics(o, axis) # fix_origin_shift_metrics(o, axis)
# return "" # return ""
# def align_origins_to_metrics(objects=None): # def align_origins_to_metrics(objects=None):
# if objects is None: # if objects is None:
# objects = bpy.context.selected_objects # objects = bpy.context.selected_objects
# if len(objects) == 0: # if len(objects) == 0:
# return "no objects selected" # return "no objects selected"
# for o in objects: # for o in objects:
# is_possibly_glyph = is_glyph(o) # is_possibly_glyph = is_glyph(o)
# if is_possibly_glyph: # if is_possibly_glyph:
# min_x = 9999999999 # min_x = 9999999999
# for c in o.children: # for c in o.children:
# if is_metrics_object(c): # if is_metrics_object(c):
# for v in c.data.vertices: # for v in c.data.vertices:
# if v.co[0] < min_x: # if v.co[0] < min_x:
# min_x = v.co[0] # min_x = v.co[0]
# metrics_origin_x = c.matrix_world.translation[0] + min_x # metrics_origin_x = c.matrix_world.translation[0] + min_x
# diff = metrics_origin_x - o.matrix_world.translation[0] # diff = metrics_origin_x - o.matrix_world.translation[0]
# for v in o.data.vertices: # for v in o.data.vertices:
# v.co[0] -= diff # v.co[0] -= diff
# o.location += mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted() # o.location += mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted()
# for c in o.children: # for c in o.children:
# if is_metrics_object(c): # if is_metrics_object(c):
# c.location -= mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted() # c.location -= mathutils.Vector((diff, 0.0, 0.0)) @ o.matrix_world.inverted()
# return ""
# return ""

View file

@ -1,6 +1,5 @@
from typing import Dict
from pathlib import Path from pathlib import Path
from typing import NamedTuple from typing import Dict, NamedTuple
# convenience dictionary for translating names to glyph ids # convenience dictionary for translating names to glyph ids
# note: overwritten/extended by the content of "glypNamesToUnicode.txt" # note: overwritten/extended by the content of "glypNamesToUnicode.txt"
@ -161,7 +160,6 @@ class Font:
self.faces = faces self.faces = faces
def register_font(font_name, face_name, glyphs_in_fontfile, filepath): def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
if not fonts.keys().__contains__(font_name): if not fonts.keys().__contains__(font_name):
fonts[font_name] = Font({}) fonts[font_name] = Font({})
@ -250,7 +248,10 @@ def get_glyphs(font_name, face_name, glyph_id):
face = get_font_face(font_name, face_name) face = get_font_face(font_name, face_name)
if face is None: if face is None:
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found") print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
print(fonts[font_name].faces.keys()) try:
print(fonts[font_name].faces.keys())
except:
print(fonts.keys())
return [] return []
glyphs_for_id = face.glyphs.get(glyph_id) glyphs_for_id = face.glyphs.get(glyph_id)
@ -291,6 +292,19 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
return glyphs[alternate] return glyphs[alternate]
def unloaded_glyph(font_name, face_name, glyph_id):
face = get_font_face(font_name, face_name)
if face is None:
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
return
while True:
try:
fonts[font_name].faces[face_name].loaded_glyphs.remove(glyph_id)
del fonts[font_name].faces[face_name].glyphs[glyph_id]
except ValueError:
break
class GlyphsAvailability(NamedTuple): class GlyphsAvailability(NamedTuple):
loaded: str loaded: str
missing: str missing: str