Compare commits

..

No commits in common. "main" and "v0.0.9" have entirely different histories.
main ... v0.0.9

8 changed files with 691 additions and 1681 deletions

View file

@ -5,8 +5,10 @@
/ ___ \| |_) | |___ ___) | |_| |
/_/ \_\____/ \____|____/|____/
```
v0.0.12
v0.0.9
Convenience addon to work with 3D typography in Blender and Cinema4D.
Convenience tool to work with 3D typography in Blender and Cinema4D.
Install as you would normally install an addon.
Instructions for development in [CONTRIBUTING,md](./CONTRIBUTING.md).

View file

@ -16,7 +16,7 @@ from .common import Font, utils
bl_info = {
"name": "ABC3D",
"author": "Jakob Schlötter, Studio Pointer*",
"version": (0, 0, 12),
"version": (0, 0, 9),
"blender": (4, 1, 0),
"location": "VIEW3D",
"description": "Convenience addon for 3D fonts",
@ -143,40 +143,13 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
t = butils.get_text_properties(self.text_id)
if t is not None:
butils.set_text_on_curve(t)
return None
def alternate_get_callback(self):
return self["alternate"] if "alternate" in self else 0
def alternate_set_callback(self, value):
min_value = 0
new_value = max(value, min_value)
if self.text_id >= 0:
text_properties = butils.get_text_properties(self.text_id)
max_value = (
len(
Font.get_glyphs(
text_properties.font_name,
text_properties.face_name,
self.glyph_id,
)
)
- 1
)
new_value = min(new_value, max_value)
self["alternate"] = new_value
return None
glyph_id: bpy.props.StringProperty(maxlen=1)
text_id: bpy.props.IntProperty(
default=-1,
)
alternate: bpy.props.IntProperty(
default=0, # also change in alternate_get_callback
get=alternate_get_callback,
set=alternate_set_callback,
default=-1,
update=update_callback,
)
glyph_object: bpy.props.PointerProperty(type=bpy.types.Object)
@ -186,7 +159,6 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
update=update_callback,
)
class ABC3D_text_properties(bpy.types.PropertyGroup):
def font_items_callback(self, context):
items = []
@ -194,6 +166,21 @@ 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)
@ -294,31 +281,22 @@ class ABC3D_data(bpy.types.PropertyGroup):
available_texts: bpy.props.CollectionProperty(
type=ABC3D_text_properties, name="Available texts"
)
texts: bpy.props.CollectionProperty(type=ABC3D_text_properties, name="texts")
def active_text_index_update(self, context):
lock_depsgraph_updates()
if self.active_text_index != -1:
text_properties = butils.get_text_properties_by_index(
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 os is not None and not butils.is_or_has_parent(
context.active_object, o
if (o is not None
and not o.select_get()
and not len([c for c in o.children if c.select_get()]) > 0
):
# if (
# o is not None
# and 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)
context.view_layer.objects.active = o
unlock_depsgraph_updates()
# else:
# print("already selected")
@ -369,7 +347,7 @@ class ABC3D_UL_texts(bpy.types.UIList):
class ABC3D_PT_Panel(bpy.types.Panel):
bl_label = f"{utils.prefix()} Panel"
bl_label = f"{__name__} panel"
bl_category = "ABC3D"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
@ -377,9 +355,6 @@ class ABC3D_PT_Panel(bpy.types.Panel):
def draw(self, context):
layout = self.layout
row = layout.row()
row.label(text=f"{utils.prefix()} v{utils.get_version_string()}")
icon = "NONE"
if len(context.scene.abc3d_data.available_fonts) == 0:
icon = "ERROR"
@ -419,17 +394,14 @@ class ABC3D_PT_FontList(bpy.types.Panel):
abc3d_data,
"active_font_index",
)
if (
abc3d_data.active_font_index >= 0
and len(abc3d_data.available_fonts) > abc3d_data.active_font_index
):
if abc3d_data.active_font_index >= 0:
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name
face_name = available_font.face_name
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)
available_glyphs = sorted(
Font.fonts[font_name].faces[face_name].glyphs_in_fontfile
)
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs)
box = layout.box()
box.row().label(text=f"Font Name: {font_name}")
box.row().label(text=f"Face Name: {face_name}")
@ -471,13 +443,6 @@ class ABC3D_PT_FontList(bpy.types.Panel):
)
oper_lf.font_name = font_name
oper_lf.face_name = face_name
box = layout.box()
row = box.row()
row.label(text="File and Memory optimization")
row = box.row()
row.operator(f"{__name__}.refresh_fonts", text="Refresh font list from disk")
row = box.row()
row.operator(f"{__name__}.unload_unused_glyphs", text="Unload unused glyphs")
class ABC3D_PT_TextPlacement(bpy.types.Panel):
@ -491,7 +456,10 @@ 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
@ -520,6 +488,99 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
bl_region_type = "UI"
bl_options = {"DEFAULT_CLOSED"}
# TODO: perhaps this should be done in a periodic timer
@classmethod
def poll(self, context):
scene = context.scene
abc3d_data = scene.abc3d_data
# TODO: update available_texts
def update():
if bpy.context.screen.is_animation_playing:
return
active_text_index = -1
remove_list = []
for i, t in enumerate(abc3d_data.available_texts):
if type(t.text_object) == type(None):
remove_list.append(i)
continue
remove_me = True
for c in t.text_object.children:
if (
len(c.users_collection) > 0
and not isinstance(c.get(f"{utils.prefix()}_text_id"), None)
and c.get(f"{utils.prefix()}_text_id") == t.text_id
):
remove_me = False
# not sure how to solve this reliably atm,
# we need to reassign the glyph, but also get the proper properties from glyph_properties
# these might be there in t.glyphs, but linked to removed objects
# or they might be lost
if type(
next(
(
g
for g in t.glyphs
if type(g.glyph_object) == type(None)
),
None,
)
) == type(None):
g = next(
(
g
for g in t.glyphs
if type(g.glyph_object) == type(None)
),
None,
)
# for g in t.glyphs:
# if type(g) == type(None):
# print("IS NONE")
# if type(g.glyph_object) == type(None):
# print("go IS NONE")
# else:
# if g.glyph_object == c:
# # print(g.glyph_object.name)
# pass
if remove_me:
remove_list.append(i)
for i in remove_list:
if abc3d_data.available_texts[i].text_object is not None:
mom = abc3d_data.available_texts[i].text_object
def delif(o, p):
if p in o:
del o[p]
delif(mom, f"{utils.prefix()}_text_id")
delif(mom, f"{utils.prefix()}_font_name")
delif(mom, f"{utils.prefix()}_face_name")
delif(mom, f"{utils.prefix()}_font_size")
delif(mom, f"{utils.prefix()}_letter_spacing")
delif(mom, f"{utils.prefix()}_orientation")
delif(mom, f"{utils.prefix()}_translation")
delif(mom, f"{utils.prefix()}_offset")
abc3d_data.available_texts.remove(i)
for i, t in enumerate(abc3d_data.available_texts):
if context.active_object == t.text_object:
active_text_index = i
if (
hasattr(context.active_object, "parent")
and context.active_object.parent == t.text_object
):
active_text_index = i
if active_text_index != abc3d_data.active_text_index:
abc3d_data.active_text_index = active_text_index
# butils.run_in_main_thread(update)
return True
def draw(self, context):
layout = self.layout
wm = context.window_manager
@ -589,7 +650,6 @@ 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"
@ -627,10 +687,7 @@ 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"
@ -669,10 +726,7 @@ 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")
@ -688,36 +742,63 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
# and bpy.context.object.select_get():
a_o = bpy.context.active_object
if a_o is not None:
# if f"{utils.prefix()}_text_id" in a_o:
# text_id = a_o[f"{utils.prefix()}_text_id"]
# return butils.get_text_properties(text_id)
# # elif a_o.parent is not None and f"{utils.prefix()}_text_id" in a_o.parent:
# # text_id = a_o.parent[f"{utils.prefix()}_text_id"]
# # return butils.get_text_properties(text_id)
# else:
if f"{utils.prefix()}_text_id" in a_o:
text_index = a_o[f"{utils.prefix()}_text_id"]
return bpy.context.scene.abc3d_data.available_texts[text_index]
elif a_o.parent is not None and f"{utils.prefix()}_text_id" in a_o.parent:
text_index = a_o.parent[f"{utils.prefix()}_text_id"]
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, text_properties):
if text_properties is None:
return None
# NOTE: HERE
def get_active_glyph_properties(self):
a_o = bpy.context.active_object
if a_o is not None:
if 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"]
if len(text_properties.glyphs) <= glyph_index:
return None
return text_properties.glyphs[glyph_index]
return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[glyph_index]
else:
for g in text_properties.glyphs:
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):
for g in t.glyphs:
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()
# for f in fonts:
# items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
# return items
# def font_default_callback(self, context):
# t = self.get_active_text_properties(self)
# if type(t) != type(None):
# return f"{t.font_name} {t.face_name}"
# else:
# return None
# def font_update_callback(self, context):
# font_name, face_name = self.font.split(" ")
# t = self.get_active_text_properties(self)
# t.font_name = font_name
# t.face_name = face_name
# butils.set_text_on_curve(t)
# font: bpy.props.EnumProperty(
# items=font_items_callback,
# default=font_default_callback,
# update=font_update_callback,
# )
@classmethod
def poll(self, context):
try:
@ -729,7 +810,7 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
layout = self.layout
props = self.get_active_text_properties()
glyph_props = self.get_active_glyph_properties(props)
glyph_props = self.get_active_glyph_properties()
if props is None or props.text_object is None:
# this should not happen
@ -741,22 +822,6 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
layout.label(text="props.text_object is none")
return
# TODO: put this at a better place
# here we set the font if it is not correct
# this is a fix for a UI glitch, perhaps it could be fixed
# rather where it is not set properly
# if (
# butils.get_key("font_name") in props.text_object
# and butils.get_key("face_name") in props.text_object
# ):
# font = f"{props.text_object[butils.get_key('font_name')]} {props.text_object[butils.get_key('face_name')]}"
# if font != props.font:
#
# def setfont():
# props.font = font
#
# butils.run_in_main_thread(setfont)
#
layout.label(text=f"Mom: {props.text_object.name}")
layout.row().prop(props, "font")
layout.row().prop(props, "text")
@ -773,54 +838,10 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
if glyph_props is None:
return
box = layout.box()
box.label(text=f"selected character: {glyph_props.glyph_id}")
box.label(text=f"{glyph_props.glyph_id}")
box.row().prop(glyph_props, "letter_spacing")
# if True:
# font_name = props.font_name
# face_name = props.face_name
# glyph_id = glyph_props.glyph_id
# glyphs_n = len(Font.get_glyphs(font_name, face_name, glyph_id))
# glyph_props.alternate.hard_min = -1
# glyph_props.alternate.hard_max = glyphs_n - 1
n_alternates = len(
Font.get_glyphs(
props.font_name,
props.face_name,
glyph_props.glyph_id,
)
)
if n_alternates > 1:
box.row().prop(glyph_props, "alternate", text=f"alternate ({n_alternates})")
# if glyph_props.glyph_object.preview is not None:
# box.row().template_preview(glyph_props.glyph_object.preview.icon_id)
class ABC3D_OT_RefreshAvailableFonts(bpy.types.Operator):
"""Refreshes available font list from disk.
This also removes all fonts which are not saved in the asset directory.
Can be useful when creating fonts or manually installing fonts."""
bl_idname = f"{__name__}.refresh_fonts"
bl_label = "Refresh Available Fonts"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
refresh_fonts()
return {"FINISHED"}
class ABC3D_OT_UnloadUnusedGlyphs(bpy.types.Operator):
"""Unload all glyphs which are not actively used in this project from memory.
They will still be normally loaded when you use them again."""
bl_idname = f"{__name__}.unload_unused_glyphs"
bl_label = "Unload Unused Glyphs"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
butils.unload_unused_glyphs()
return {"FINISHED"}
class ABC3D_OT_InstallFont(bpy.types.Operator):
"""Install or load Fontfile from path above.
@ -980,6 +1001,7 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
print("EXECUTE LOAD INSTALLED FONTS")
scene = bpy.context.scene
if self.load_into_memory:
@ -1004,18 +1026,7 @@ 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)
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=}",
],
)
return {"CANCELLED"}
filepaths = face.filepaths
filepaths = Font.fonts[self.font_name].faces[self.face_name].filepaths
for f in filepaths:
butils.load_font_from_filepath(f)
return {"FINISHED"}
@ -1077,7 +1088,6 @@ 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."""
@ -1085,67 +1095,65 @@ 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"}
# def execute(self, context):
# objects = bpy.context.selected_objects
# butils.fix_objects_metrics_origins(objects)
# return {"FINISHED"}
# 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"}
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."""
@ -1200,7 +1208,6 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
def execute(self, context):
abc3d_data = context.scene.abc3d_data
lock_depsgraph_updates()
if abc3d_data.active_text_index < 0:
butils.ShowMessageBox(
title="No text selected",
@ -1214,7 +1221,6 @@ 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]
@ -1236,7 +1242,6 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
butils.simply_delete_objects(remove_list)
abc3d_data.available_texts.remove(i)
unlock_depsgraph_updates()
return {"FINISHED"}
@ -1320,7 +1325,10 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
distribution_type = "DEFAULT"
text_id = butils.find_free_text_id()
text_id = 0
for i, tt in enumerate(abc3d_data.available_texts):
while text_id == tt.text_id:
text_id = text_id + 1
t = abc3d_data.available_texts.add()
# If you wish to set a value and not fire an update, set the id property.
# A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default.
@ -1362,8 +1370,7 @@ 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"
@ -1395,9 +1402,6 @@ 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)
def invoke(self, context, event):
wm = context.window_manager
preferences = getPreferences(context)
@ -1421,9 +1425,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)
if face is not None:
loaded_glyphs = sorted(face.loaded_glyphs)
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs)
n = 16
n_rows = int(len(loaded_glyphs) / n)
box = layout.box()
@ -1441,79 +1443,12 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
row = subbox.row()
row.scale_y = scale_y
row.label(text=text)
row = layout.row()
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):
self.can_execute = False
row.alert = True
row.label(text="Export directory exists but is not writable")
row = layout.row()
row.alert = True
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
self.can_execute = False
row.alert = True
row.label(text="Directory does not exist and cannot be created")
row = layout.row()
row.alert = True
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
self.can_execute = True
row.label(text="Directory does not exist")
row = layout.row()
row.label(text="But can and will be created on export")
row = layout.row()
else:
self.can_execute = False
row.alert = True
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
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()} {Font.fonts=}")
layout.prop(abc3d_data, "export_dir")
def execute(self, context):
global shared
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
if not self.can_execute:
butils.ShowMessageBox(
"Cannot export font",
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable",
"try setting another path",
],
)
return {"CANCELLED"}
if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
path = butils.bpy_to_abspath(abc3d_data.export_dir)
if utils.can_create_path(path):
os.makedirs(path, exist_ok=True)
else:
butils.ShowMessageBox(
"Cannot export font",
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created",
"try setting another path",
],
)
return {"CANCELLED"}
fontcollection = bpy.data.collections.get("ABC3D")
@ -1593,12 +1528,7 @@ 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.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1)
# bpy.ops.scene.delete()
# restore()
@ -1611,7 +1541,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
return None
bpy.app.timers.register(lambda: remove_faces(), first_interval=2)
self.report({"INFO"}, f"{utils.prefix()}::save_font_to_file done")
self.report({"INFO"}, "did it")
return {"FINISHED"}
@ -1648,10 +1578,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
def do_autodetect_names(self, name: str):
ifxsplit = name.split("_")
if len(ifxsplit) < 4:
print(
f"{utils.prefix()}::CreateFontFromObjects: name could not be autodetected {name}"
)
print(f"{utils.prefix()}::CreateFontFromObjects: split: {ifxsplit=}")
print(f"name could not be autodetected {name}")
print("split:")
print(ifxsplit)
return self.font_name, self.face_name
detected_font_name = f"{ifxsplit[1]}_{ifxsplit[2]}"
detected_face_name = ifxsplit[3]
@ -1667,9 +1596,7 @@ 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()
@ -1726,11 +1653,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
row.label(text=f"{k}{Font.known_misspellings[k]}{character}")
def execute(self, context):
print(f"{utils.prefix()}::CreateFontFromObjects: executing {self.bl_idname}")
print(f"executing {self.bl_idname}")
if len(context.selected_objects) == 0:
print(
f"{utils.prefix()}::CreateFontFromObjects: cancelled {self.bl_idname} - no objects selected"
)
print(f"cancelled {self.bl_idname} - no objects selected")
return {"CANCELLED"}
global shared
scene = bpy.context.scene
@ -1744,10 +1669,13 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
font_name = self.font_name
face_name = self.face_name
# TODO: do not clear
# abc3d_data.available_fonts.clear()
# Font.fonts = {}
currentObjects = []
for o in context.selected_objects:
if o.name not in currentObjects:
print(f"{utils.prefix()}::CreateFontFromObjects: processing {o.name}")
print(f"processing {o.name}")
process_object = True
if self.autodetect_names:
font_name, face_name = self.do_autodetect_names(o.name)
@ -1784,9 +1712,7 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
f.face_name = face_name
else:
print(
f"{utils.prefix()}::CreateFontFromObjects: import warning: did not understand glyph {name}"
)
print(f"import warning: did not understand glyph {name}")
self.report({"INFO"}, f"did not understand glyph {name}")
return {"FINISHED"}
@ -1833,8 +1759,6 @@ classes = (
ABC3D_PT_NamingHelper,
ABC3D_PT_TextPropertiesPanel,
ABC3D_OT_OpenAssetDirectory,
ABC3D_OT_RefreshAvailableFonts,
ABC3D_OT_UnloadUnusedGlyphs,
ABC3D_OT_LoadInstalledFonts,
ABC3D_OT_LoadFont,
ABC3D_OT_AddDefaultMetrics,
@ -1873,35 +1797,14 @@ 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):
lock_depsgraph_updates()
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()
def determine_active_text_index_from_selection():
if bpy.context.active_object is None:
return -1
for text_index, text_properties in enumerate(
bpy.context.scene.abc3d_data.available_texts
):
if butils.is_text_object_legit(text_properties.text_object):
if butils.is_or_has_parent(
bpy.context.active_object, text_properties.text_object
):
return text_index
return -1
def update_active_text_index():
text_index = determine_active_text_index_from_selection()
if text_index != bpy.context.scene.abc3d_data.active_text_index:
bpy.context.scene.abc3d_data.active_text_index = text_index
def detect_text():
lock_depsgraph_updates()
lock_depsgraph_updates(auto_unlock_s=-1)
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
required_keys = [
@ -1923,7 +1826,10 @@ 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:
@ -1936,7 +1842,7 @@ def load_used_glyphs():
abc3d_data = scene.abc3d_data
for t in abc3d_data.available_texts:
a = Font.test_availability(t.font_name, t.face_name, t.text)
if isinstance(a, int):
if type(a) == type(int()):
if a == Font.MISSING_FONT:
butils.ShowMessageBox(
"Missing Font",
@ -1953,24 +1859,9 @@ def load_used_glyphs():
"Do you have it installed?",
],
)
elif len(a.unloaded) > 0:
for fp in a.filepaths:
butils.load_font_from_filepath(fp, a.unloaded)
def refresh_fonts():
fontcollection: bpy_types.Collection = bpy.data.collections.get("ABC3D")
if fontcollection is not None:
objs = [o for o in fontcollection.objects if o.parent == None]
butils.completely_delete_objects(objs)
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)
butils.run_in_main_thread(load_used_glyphs)
butils.run_in_main_thread(butils.update_types)
butils.run_in_main_thread(detect_text)
butils.run_in_main_thread(butils.unload_unused_glyphs)
elif len(a["maybe"]) > 0:
for fp in a["filepaths"]:
butils.load_font_from_filepath(fp, a["maybe"])
@persistent
@ -2003,7 +1894,7 @@ def unlock_depsgraph_updates():
depsgraph_updates_locked -= 1
def lock_depsgraph_updates(auto_unlock_s=-1):
def lock_depsgraph_updates(auto_unlock_s=1):
global depsgraph_updates_locked
depsgraph_updates_locked += 1
if auto_unlock_s >= 0:
@ -2011,19 +1902,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():
lock_depsgraph_updates(auto_unlock_s=-1)
for u in depsgraph.updates:
if (
butils.get_key("text_id") in u.id.keys()
@ -2036,23 +1923,13 @@ def on_depsgraph_update(scene, depsgraph):
if text_properties is not None:
if text_properties.text_object == u.id.original:
# nothing to do
try:
butils.set_text_on_curve(text_properties)
except:
pass
pass
elif butils.is_text_object_legit(u.id.original):
else:
# must be duplicate
link_text_object_with_new_text_properties(u.id.original, scene)
elif (
butils.is_text_object_legit(u.id.original)
and len(u.id.original.users_collection) > 0
):
# must be a new thing, maybe manually created or so
else:
# must be new thing
link_text_object_with_new_text_properties(u.id.original, scene)
butils.clean_text_properties()
update_active_text_index()
unlock_depsgraph_updates()
def register():
@ -2063,9 +1940,7 @@ 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
@ -2080,7 +1955,6 @@ 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)

1178
butils.py

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
from typing import Dict
from pathlib import Path
from typing import Dict, NamedTuple
# convenience dictionary for translating names to glyph ids
# note: overwritten/extended by the content of "glypNamesToUnicode.txt"
@ -177,34 +177,6 @@ def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
fonts[font_name].faces[face_name].filepaths.append(filepath)
def get_font(font_name):
if not fonts.keys().__contains__(font_name):
print(f"ABC3D::get_font: font name({font_name}) not found")
print(fonts.keys())
return None
return fonts[font_name]
def get_font_face(font_name, face_name):
font = get_font(font_name)
if font is None:
return None
if not font.faces.keys().__contains__(face_name):
print(
f"ABC3D::get_font_face (font: {font_name}): face name({face_name}) not found"
)
print(font.faces.keys())
return None
return font.faces[face_name]
def get_font_face_filepaths(font_name, face_name):
face = get_font_face(font_name, face_name)
if not face:
return None
return face.filepaths
def add_glyph(font_name, face_name, glyph_id, glyph_object):
"""add_glyph adds a glyph to a FontFace
it creates the :class:`Font` and :class:`FontFace` if it does not exist yet
@ -231,48 +203,7 @@ def add_glyph(font_name, face_name, glyph_id, glyph_object):
fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id)
def get_glyphs(font_name, face_name, glyph_id):
"""get_glyphs returns an array of glyphs of a FontFace
:param font_name: The :class:`Font` you want to get the glyph from
:type font_name: str
:param face_name: The :class:`FontFace` you want to get the glyph from
:type face_name: str
:param glyph_id: The ``glyph_id`` from the glyph you want
:type glyph_id: str
...
:return: returns a list of the glyph objects, or an empty list if none exists
:rtype: `List`
"""
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")
try:
print(fonts[font_name].faces.keys())
except:
print(fonts.keys())
return []
glyphs_for_id = face.glyphs.get(glyph_id)
if glyphs_for_id is None:
print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id}) not found"
)
if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs:
fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id)
return []
return glyphs_for_id
def get_glyph(
font_name: str,
face_name: str,
glyph_id: str,
alternate: int = 0,
alternate_tolerant: bool = True,
):
def get_glyph(font_name, face_name, glyph_id, alternate=0):
"""add_glyph adds a glyph to a FontFace
it creates the :class:`Font` and :class:`FontFace` if it does not exist yet
@ -282,53 +213,30 @@ def get_glyph(
:type face_name: str
:param glyph_id: The ``glyph_id`` from the glyph you want
:type glyph_id: str
:param alternate: The ``alternate`` from the glyph you want
:type alternate: int
:param alternate_tolerant: Fetch an existing alternate if requested is out of bounds
:type glyph_id: bool
...
:return: returns the glyph object, or ``None`` if it does not exist
:rtype: `Object`
"""
glyphs = get_glyphs(font_name, face_name, glyph_id)
if len(glyphs) == 0:
print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found"
)
if not fonts.keys().__contains__(font_name):
# print(f"ABC3D::get_glyph: font name({font_name}) not found")
# print(fonts.keys())
return None
if len(glyphs) <= alternate:
if alternate_tolerant:
alternate = 0
else:
print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found"
)
return None
return glyphs[alternate]
def unloaded_glyph(font_name, face_name, glyph_id):
face = get_font_face(font_name, face_name)
face = fonts[font_name].faces.get(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
# print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
# print(fonts[font_name].faces.keys())
return None
glyphs_for_id = face.glyphs.get(glyph_id)
if glyphs_for_id is None or len(glyphs_for_id) <= alternate:
# print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found")
if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs:
fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id)
return None
class GlyphsAvailability(NamedTuple):
loaded: str
missing: str
unloaded: str
filepaths: list[str]
return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate]
def test_glyphs_availability(font_name, face_name, text):
@ -337,24 +245,24 @@ def test_glyphs_availability(font_name, face_name, text):
not fonts.keys().__contains__(font_name)
or fonts[font_name].faces.get(face_name) is None
):
return GlyphsAvailability("", "", "", [])
return "", "", text # <loaded>, <missing>, <maybe>
loaded = []
missing = []
unloaded = []
maybe = []
for c in text:
if c in fonts[font_name].faces[face_name].loaded_glyphs:
loaded.append(c)
elif c in fonts[font_name].faces[face_name].glyphs_in_fontfile:
unloaded.append(c)
maybe.append(c)
else:
if c not in fonts[font_name].faces[face_name].missing_glyphs:
fonts[font_name].faces[face_name].missing_glyphs.append(c)
missing.append(c)
return GlyphsAvailability(
return (
"".join(loaded),
"".join(missing),
"".join(unloaded),
"".join(maybe),
fonts[font_name].faces[face_name].filepaths,
)
@ -380,10 +288,15 @@ def test_availability(font_name, face_name, text):
return MISSING_FONT
if fonts[font_name].faces.get(face_name) is None:
return MISSING_FACE
availability: GlyphsAvailability = test_glyphs_availability(
loaded, missing, maybe, filepaths = test_glyphs_availability(
font_name, face_name, text
)
return availability
return {
"loaded": loaded,
"missing": missing,
"maybe": maybe,
"filepaths": filepaths,
}
# holds all fonts

View file

@ -8,11 +8,11 @@ def get_version_minor():
def get_version_patch():
return 12
return 9
def get_version_string():
return f"{get_version_major()}.{get_version_minor()}.{get_version_patch()}"
return f"{get_version_major()}.{get_version_minor()}.{get_version_patch}"
def prefix():
@ -23,6 +23,7 @@ import datetime
import time
def get_timestamp():
return datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d-%H:%M:%S")
@ -81,10 +82,6 @@ def open_file_browser(directory):
# xdg-open *should* be supported by recent Gnome, KDE, Xfce
def LINE():
return sys._getframe(1).f_lineno
def printerr(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
@ -93,34 +90,6 @@ def removeNonAlphabetic(s):
return "".join([i for i in s if i.isalpha()])
import os
import pathlib
def can_create_path(path_str: str):
path = pathlib.Path(path_str).absolute().resolve()
tries = 0
maximum_tries = 1000
while True:
if path.exists():
if os.access(path, os.W_OK):
return True
else:
return False
elif path == path.parent:
# should never be reached, because root exists
# but if it doesn't.. well then we can't
return False
path = path.parent
tries += 1
if tries > maximum_tries:
# always, always break out of while loops eventually
# IF you don't want to be here forever
break
# # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
# def evaluateBezierPoint(p1, h1, h2, p2, t):
# return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2

View file

@ -1,39 +1,33 @@
asttokens==3.0.0
attrs==25.3.0
bpy==4.4.0
cattrs==24.1.3
certifi==2025.4.26
charset-normalizer==3.4.2
Cython==3.1.1
decorator==5.2.1
docstring-to-markdown==0.17
executing==2.2.0
astroid==3.3.5
attrs==24.2.0
black==24.10.0
bpy==4.2.0
cattrs==24.1.2
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
Cython==3.0.11
dill==0.3.9
docstring-to-markdown==0.15
flake8==7.1.1
idna==3.10
importlib_metadata==8.7.0
ipython==9.2.0
ipython_pygments_lexers==1.1.1
jedi==0.19.2
jedi-language-server==0.45.1
isort==5.13.2
jedi==0.19.1
jedi-language-server==0.41.4
lsprotocol==2023.0.1
mathutils==3.3.0
matplotlib-inline==0.1.7
numpy==1.26.4
mccabe==0.7.0
mypy-extensions==1.0.0
numpy==2.1.3
packaging==24.1
parso==0.8.4
pexpect==4.9.0
pluggy==1.6.0
prompt_toolkit==3.0.51
ptyprocess==0.7.0
pure_eval==0.2.3
pathspec==0.12.1
platformdirs==4.3.6
pycodestyle==2.12.1
pyflakes==3.2.0
pygls==1.3.1
Pygments==2.19.1
python-jsonrpc-server==0.4.0
python-lsp-jsonrpc==1.1.2
pylint==3.3.1
requests==2.32.3
stack-data==0.6.3
traitlets==5.14.3
typing_extensions==4.13.2
ujson==5.10.0
urllib3==2.4.0
wcwidth==0.2.13
zipp==3.22.0
tomlkit==0.13.2
urllib3==2.2.3
zstandard==0.23.0

View file

@ -1,25 +0,0 @@
import bpy
from mathutils import *
from math import *
import abc3d.butils
v = 0
goal = 5.0
step = 0.1
speed = 1.0
C = bpy.context
obj = C.scene.objects['Cube']
curve = C.scene.objects['BézierCurve']
m = curve.matrix
def fun(distance):
obj.location = m @ abc3d.butils.calc_point_on_bezier_curve(curve,
distance,
output_tangent=True)
print(f"executed {distance}")
while v < goal:
bpy.app.timers.register(lambda: fun(v), first_interval=(v * speed))
v += step

View file

@ -1,115 +0,0 @@
import bpy
import abc3d
from abc3d import butils
from abc3d.common import Font
def get_text_properties_by_mom(mom):
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
for text_properties in abc3d_data.available_texts:
if mom == text_properties.text_object:
return text_properties
return None
def isolate_objects(objects):
for area in bpy.context.window.screen.areas:
if area.type == "VIEW_3D":
with bpy.context.temp_override(
selected_objects=list(objects),
area=area,
refgion=[region for region in area.regions if region.type == "WINDOW"][
0
],
screen=bpy.context.window.screen,
):
# bpy.ops.view3d.view_selected()
bpy.ops.view3d.localview(frame_selected=True)
break
def main():
# create a curve
bpy.ops.curve.primitive_bezier_curve_add(
radius=1,
enter_editmode=False,
align="WORLD",
location=(0, 0, 0),
scale=(1, 1, 1),
)
# new curve is active object
mom = bpy.context.active_object
# make sure
print(f"MOM: {mom.name}")
fonts = Font.get_loaded_fonts_and_faces()
if len(fonts) == 0:
print("no fonts! what?")
return
font_name = fonts[0][0]
face_name = fonts[0][1]
font = f"{font_name} {face_name}"
isolate_objects([mom])
bpy.ops.abc3d.placetext(
font_name=font_name,
face_name=face_name,
font=font,
text="SOMETHING SOMETHING BROKEN ARMS",
letter_spacing=0,
font_size=1,
offset=0,
translation=(0, 0, 0),
orientation=(1.5708, 0, 0),
)
def change_text(font_name="", face_name="", text=""):
print(f"change_text to '{text}'")
text_properties = get_text_properties_by_mom(mom)
if font_name != "":
text_properties["font_name"] = font_name
if face_name != "":
text_properties["face_name"] = face_name
if text != "":
text_properties.text = text
else:
text_properties.text = text_properties.text
return None
def unload(glyph_id):
print(f"unload glyph '{glyph_id}'")
butils.unload_unused_glyph(font_name, face_name, glyph_id)
return None
def unload_all():
print(f"unload glyph all unused glyphs")
butils.unload_unused_glyphs()
return None
bpy.app.timers.register(lambda: change_text(text="SOMETHING"), first_interval=0)
bpy.app.timers.register(lambda: change_text(text="LOLSS"), first_interval=2)
bpy.app.timers.register(lambda: change_text(text="LOLAA"), first_interval=3)
bpy.app.timers.register(lambda: change_text(text="WHAT"), first_interval=4)
bpy.app.timers.register(lambda: change_text(text="LOL"), first_interval=5)
bpy.app.timers.register(lambda: unload("A"), first_interval=10)
bpy.app.timers.register(lambda: unload_all(), first_interval=12)
bpy.app.timers.register(lambda: change_text(text="LOLM"), first_interval=16)
bpy.app.timers.register(lambda: change_text(text="ZHE END"), first_interval=20)
bpy.app.timers.register(
lambda: change_text(font_name="NM_Origin", face_name="Tender"),
first_interval=30,
)
bpy.app.timers.register(lambda: unload_all(), first_interval=42)
main()