refactor ensure glyphs + alternates

This commit is contained in:
jrkb 2025-06-04 14:47:09 +02:00
parent 14d1b7a160
commit 7de8fcc5d1
3 changed files with 332 additions and 129 deletions

View file

@ -143,13 +143,40 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
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:
butils.set_text_on_curve(t) 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) 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=0, # also change in alternate_get_callback
get=alternate_get_callback,
set=alternate_set_callback,
update=update_callback, update=update_callback,
) )
glyph_object: bpy.props.PointerProperty(type=bpy.types.Object) glyph_object: bpy.props.PointerProperty(type=bpy.types.Object)
@ -267,6 +294,7 @@ class ABC3D_data(bpy.types.PropertyGroup):
available_texts: bpy.props.CollectionProperty( available_texts: bpy.props.CollectionProperty(
type=ABC3D_text_properties, name="Available texts" type=ABC3D_text_properties, name="Available texts"
) )
texts: bpy.props.CollectionProperty(type=ABC3D_text_properties, name="texts")
def active_text_index_update(self, context): def active_text_index_update(self, context):
lock_depsgraph_updates() lock_depsgraph_updates()
@ -713,6 +741,22 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
layout.label(text="props.text_object is none") layout.label(text="props.text_object is none")
return 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.label(text=f"Mom: {props.text_object.name}")
layout.row().prop(props, "font") layout.row().prop(props, "font")
layout.row().prop(props, "text") layout.row().prop(props, "text")
@ -729,8 +773,26 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
if glyph_props is None: if glyph_props is None:
return return
box = layout.box() box = layout.box()
box.label(text=f"{glyph_props.glyph_id}") box.label(text=f"selected character: {glyph_props.glyph_id}")
box.row().prop(glyph_props, "letter_spacing") 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): class ABC3D_OT_RefreshAvailableFonts(bpy.types.Operator):
@ -1586,9 +1648,10 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
def do_autodetect_names(self, name: str): def do_autodetect_names(self, name: str):
ifxsplit = name.split("_") ifxsplit = name.split("_")
if len(ifxsplit) < 4: if len(ifxsplit) < 4:
print(f"name could not be autodetected {name}") print(
print("split:") f"{utils.prefix()}::CreateFontFromObjects: name could not be autodetected {name}"
print(ifxsplit) )
print(f"{utils.prefix()}::CreateFontFromObjects: split: {ifxsplit=}")
return self.font_name, self.face_name return self.font_name, self.face_name
detected_font_name = f"{ifxsplit[1]}_{ifxsplit[2]}" detected_font_name = f"{ifxsplit[1]}_{ifxsplit[2]}"
detected_face_name = ifxsplit[3] detected_face_name = ifxsplit[3]
@ -1663,9 +1726,11 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
row.label(text=f"{k}{Font.known_misspellings[k]}{character}") row.label(text=f"{k}{Font.known_misspellings[k]}{character}")
def execute(self, context): def execute(self, context):
print(f"executing {self.bl_idname}") print(f"{utils.prefix()}::CreateFontFromObjects: executing {self.bl_idname}")
if len(context.selected_objects) == 0: if len(context.selected_objects) == 0:
print(f"cancelled {self.bl_idname} - no objects selected") print(
f"{utils.prefix()}::CreateFontFromObjects: cancelled {self.bl_idname} - no objects selected"
)
return {"CANCELLED"} return {"CANCELLED"}
global shared global shared
scene = bpy.context.scene scene = bpy.context.scene
@ -1682,7 +1747,7 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
currentObjects = [] currentObjects = []
for o in context.selected_objects: for o in context.selected_objects:
if o.name not in currentObjects: if o.name not in currentObjects:
print(f"processing {o.name}") print(f"{utils.prefix()}::CreateFontFromObjects: processing {o.name}")
process_object = True process_object = True
if self.autodetect_names: if self.autodetect_names:
font_name, face_name = self.do_autodetect_names(o.name) font_name, face_name = self.do_autodetect_names(o.name)
@ -1719,7 +1784,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
f.face_name = face_name f.face_name = face_name
else: else:
print(f"import warning: did not understand glyph {name}") print(
f"{utils.prefix()}::CreateFontFromObjects: import warning: did not understand glyph {name}"
)
self.report({"INFO"}, f"did not understand glyph {name}") self.report({"INFO"}, f"did not understand glyph {name}")
return {"FINISHED"} return {"FINISHED"}

353
butils.py
View file

@ -663,7 +663,7 @@ def clean_fontcollection(fontcollection=None):
fontcollection = bpy.data.collections.get("ABC3D") fontcollection = bpy.data.collections.get("ABC3D")
if fontcollection is None: if fontcollection is None:
print( print(
f"{utils.prefix()}::clean_fontcollection: failed beacause fontcollection is none" f"{utils.prefix()}::clean_fontcollection: failed because fontcollection is none"
) )
return False return False
@ -1011,9 +1011,11 @@ def predict_actual_text(text_properties):
) )
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, "") C = c.swapcase()
for c in AVAILABILITY.missing: if C in AVAILABILITY.missing:
t_text = t_text.replace(c, "") t_text = t_text.replace(c, "")
else:
t_text = t_text.replace(c, C)
return t_text return t_text
@ -1184,6 +1186,7 @@ def get_text_properties(text_id, scene=None):
return t return t
return None return None
def get_text_properties_by_index(text_index, scene=None): def get_text_properties_by_index(text_index, scene=None):
if scene is None: if scene is None:
scene = bpy.context.scene scene = bpy.context.scene
@ -1299,10 +1302,7 @@ def transfer_text_object_to_text_properties(
glyph_properties = text_properties.glyphs.add() glyph_properties = text_properties.glyphs.add()
transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties) transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties)
glyph_properties["glyph_object"] = glyph_object # glyph_properties["glyph_object"] = glyph_object
glyph_properties["glyph_index"] = glyph_index
glyph_properties["text_id"] = text_properties.text_id
glyph_object["text_id"] = text_properties.text_id
inner_node = None inner_node = None
for c in glyph_object.children: for c in glyph_object.children:
if c.name.startswith(f"{glyph_id}_mesh"): if c.name.startswith(f"{glyph_id}_mesh"):
@ -1311,6 +1311,9 @@ def transfer_text_object_to_text_properties(
fail_after_all = True fail_after_all = True
pass pass
glyph_properties["glyph_object"] = glyph_object glyph_properties["glyph_object"] = glyph_object
glyph_properties["glyph_index"] = glyph_index
glyph_properties["text_id"] = text_properties.text_id
glyph_object["text_id"] = text_properties.text_id
if not fail_after_all: if not fail_after_all:
found_reconstructable_glyphs = True found_reconstructable_glyphs = True
@ -1421,10 +1424,21 @@ def transfer_glyph_object_to_glyph_properties(glyph_object, glyph_properties):
glyph_properties["text_id"] = glyph_object[get_key("text_id")] glyph_properties["text_id"] = glyph_object[get_key("text_id")]
def get_text_difference_index(text_a, text_b):
len_a = len(text_a)
len_b = len(text_b)
len_min = min(len_a, len_b)
len_max = max(len_a, len_b)
for i in range(0, len_max):
if i >= len_min or text_a[i] != text_b[i]:
return i
return False
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:
return True return get_text_difference_index(text_properties.actual_text, predicted_text)
if len(text_properties.glyphs) == 0: if len(text_properties.glyphs) == 0:
return True return True
@ -1476,6 +1490,7 @@ def is_or_has_parent(o, parent, if_is_parent=True, max_depth=10):
def parent_to_curve(o, c): def parent_to_curve(o, c):
# https://projects.blender.org/blender/blender/issues/100661
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()
@ -1491,6 +1506,184 @@ def parent_to_curve(o, c):
o.matrix_parent_inverse.translation = p * -1.0 o.matrix_parent_inverse.translation = p * -1.0
def get_original_glyph(text_properties, glyph_properties):
glyph_tmp = Font.get_glyph(
text_properties.font_name,
text_properties.face_name,
glyph_properties.glyph_id,
glyph_properties.alternate,
)
if glyph_tmp is None:
return None
return glyph_tmp.original
def ensure_glyph_object(text_properties, glyph_properties):
glyph_index = glyph_properties["glyph_index"]
# First, let's see if there was ever a glyph object constructed
if (
glyph_properties.glyph_object is None
or not isinstance(glyph_properties.glyph_object, bpy_types.Object)
or not is_glyph_object(glyph_properties.glyph_object)
):
# we do need a text_object though
# if there is not, let's give up for this iteration
if not isinstance(text_properties.text_object, bpy_types.Object):
print(
f"{utils.prefix()}::ensure_glyph_object: failed! text object is not an object"
)
return False
outer_node = bpy.data.objects.new(f"{glyph_properties.glyph_id}", None)
inner_node = bpy.data.objects.new(
f"{glyph_properties.glyph_id}_mesh",
get_original_glyph(text_properties, glyph_properties).data,
)
transfer_properties_to_glyph_object(
text_properties, glyph_properties, outer_node
)
# Add into the scene.
text_properties.text_object.users_collection[0].objects.link(outer_node)
text_properties.text_object.users_collection[0].objects.link(inner_node)
# Parenting is hard.
inner_node.parent_type = "OBJECT"
inner_node.parent = outer_node
inner_node.matrix_parent_inverse = outer_node.matrix_world.inverted()
parent_to_curve(outer_node, text_properties.text_object)
# outer_node["inner_node"] = bpy.types.PointerProperty(inner_node)
# for some funny reason we cannot set 'glyph_object' by key, but need to set the attribute
glyph_properties.glyph_object = outer_node
outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index
else:
outer_node = glyph_properties.glyph_object
outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index
# we might just want to update the data
# imagine a different font, letter or alternate
# this way we keep all manual transforms
if (
glyph_properties.glyph_object[get_key("glyph_id")] != glyph_properties.glyph_id
or glyph_properties.glyph_object[get_key("alternate")]
!= glyph_properties.alternate
or glyph_properties.glyph_object[get_key("font_name")]
!= text_properties.font_name
or glyph_properties.glyph_object[get_key("face_name")]
!= text_properties.face_name
):
inner_node = None
old_font_name = glyph_properties.glyph_object[get_key("font_name")]
old_face_name = glyph_properties.glyph_object[get_key("face_name")]
old_face = Font.get_font_face(old_font_name, old_face_name)
face = Font.get_font_face(text_properties.font_name, text_properties.face_name)
ratio = old_face.unit_factor / face.unit_factor
# try:
# inner_node = glyph_properties["inner_node"].original
# inner_node.location = inner_node.location * ratio
# except KeyError:
old_glyph_id = glyph_properties.glyph_object[get_key("glyph_id")]
for c in glyph_properties.glyph_object.children:
if c.name.startswith(f"{old_glyph_id}_mesh"):
inner_node = c
inner_node.location = inner_node.location * ratio
inner_node.name = f"{glyph_properties.glyph_id}_mesh"
# outer_node["inner_node"] = bpy.types.PointerProperty(inner_node)
if inner_node is None:
print(f"{utils.prefix()}::ensure_glyph_object: failed! no inner_node found")
return False
inner_node.data = get_original_glyph(text_properties, glyph_properties).data
glyph_properties.glyph_object[get_key("glyph_id")] = glyph_properties.glyph_id
glyph_properties.glyph_object[get_key("alternate")] = glyph_properties.alternate
glyph_properties.glyph_object[get_key("font_name")] = text_properties.font_name
glyph_properties.glyph_object[get_key("face_name")] = text_properties.face_name
glyph_properties.glyph_object.hide_set(True)
return True
def ensure_glyphs(text_properties, predicted_text: str):
######### REQUIREMENTS
# turns out this is not a requirement
# and can be a case we want to tackle
#
# if not text_properties.get("glyphs"):
# ShowMessageBox(
# title="text_properties has no glyphs", message="well, what I said"
# )
# return False
######### SETUP
n_glyphs = len(text_properties.glyphs)
n_predicted = len(predicted_text)
########## ENSURE AMOUNT
if n_glyphs == n_predicted:
# same amount of glyphs
# this is the most common case
# don't do anything
pass
elif n_glyphs > n_predicted:
# more glyphs than predicted
# it's a shorter word, or letters were deleted
count = n_glyphs - n_predicted
for i in range(0, count):
reverse_i = n_glyphs - (i + 1)
# let's attempt to remove the glyph_object first
# so we avoid dangling data
if isinstance(
text_properties.glyphs[reverse_i].glyph_object, bpy_types.Object
):
# bam!
completely_delete_objects(
[text_properties.glyphs[reverse_i].glyph_object]
)
# else:
# # nothing to do, if there is no blender object
# # possibly we could do a 'del', but we can also
# # just comment out the whole conditional fork
# pass
# now that blender data is gone, we can remove the glyph
text_properties.glyphs.remove(reverse_i)
elif n_glyphs < n_predicted:
# less glyphs than predicted
# it's a longer word, or letters were added
while n_glyphs < n_predicted:
glyph_id = predicted_text[n_glyphs]
glyph_properties = text_properties.glyphs.add()
glyph_properties["glyph_id"] = predicted_text[n_glyphs]
glyph_properties["glyph_index"] = n_glyphs
glyph_properties["text_id"] = text_properties.text_id
glyph_properties["letter_spacing"] = 0
n_glyphs += 1
######### ENSURE VALUES
for i, glyph_properties in enumerate(text_properties.glyphs):
glyph_properties["glyph_index"] = i
glyph_properties["text_id"] = text_properties.text_id
glyph_properties["glyph_id"] = predicted_text[i]
if not ensure_glyph_object(text_properties, glyph_properties):
print(f"{utils.prefix()}::ensure_glyphs: could not ensure glyph_object")
return True
# C.scene.abc3d_data.available_texts[0]
# import abc3d
# abc3d.butils.ensure_glyphs(C.scene.abc3d_data.available_texts[0], "whatever")
def set_text_on_curve( def set_text_on_curve(
text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, can_regenerate=False text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, can_regenerate=False
): ):
@ -1519,27 +1712,8 @@ def set_text_on_curve(
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 predicted_text = predict_actual_text(text_properties)
# as we fixed data_path with parent_to_curve trick ensure_glyphs(text_properties, predicted_text)
#
# use_path messes with parenting
# however, we need it for follow_path
# https://projects.blender.org/blender/blender/issues/100661
# previous_use_path = mom.data.use_path
# if distribution_type == "CALCULATE":
# mom.data.use_path = False
# elif distribution_type == "FOLLOW_PATH":
# mom.data.use_path = True
regenerate = can_regenerate and would_regenerate(text_properties)
# if we regenerate.... delete objects
if regenerate and text_properties.get("glyphs"):
glyph_objects = [g["glyph_object"] for g in text_properties["glyphs"]]
completely_delete_objects(glyph_objects, True)
text_properties.glyphs.clear()
transfer_text_properties_to_text_object(text_properties, mom)
curve_length = get_curve_length(mom) curve_length = get_curve_length(mom)
advance = text_properties.offset advance = text_properties.offset
@ -1549,6 +1723,9 @@ def set_text_on_curve(
previous_spline_index = -1 previous_spline_index = -1
actual_text = "" actual_text = ""
# we need to iterate over the original text, as we want commands
# however, ideally it could be an array of glyphs, commands and spaces
# now we need to handle non existing characters etc everytime in the loop
for i, c in enumerate(text_properties.text): for i, c in enumerate(text_properties.text):
face = Font.get_font_face(text_properties.font_name, text_properties.face_name) face = Font.get_font_face(text_properties.font_name, text_properties.face_name)
scalor = face.unit_factor * text_properties.font_size scalor = face.unit_factor * text_properties.font_size
@ -1572,90 +1749,31 @@ def set_text_on_curve(
spline_index = 0 spline_index = 0
############### GET GLYPH ############### HANDLE SPACES
glyph_tmp = Font.get_glyph( if glyph_id not in predicted_text:
text_properties.font_name, text_properties.face_name, glyph_id, -1
)
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:
advance = advance + space_width * text_properties.font_size advance = advance + space_width * text_properties.font_size
continue continue
message = f"Glyph not found for font_name='{text_properties.font_name}' face_name='{text_properties.face_name}' glyph_id='{glyph_id}'"
replaced = False
if glyph_id.isalpha():
possible_replacement = glyph_id.swapcase()
glyph_tmp = Font.get_glyph(
text_properties.font_name,
text_properties.face_name,
possible_replacement,
-1,
)
if glyph_tmp is not None:
message = message + f" (replaced with '{possible_replacement}')"
replaced = True
if can_regenerate:
ShowMessageBox(
title="Glyph replaced" if replaced else "Glyph missing",
icon="INFO" if replaced else "ERROR",
message=message,
prevent_repeat=True,
)
if not replaced:
continue
glyph = glyph_tmp.original
############### GLYPH PROPERTIES ############### GLYPH PROPERTIES
glyph_properties = ( glyph_properties = text_properties.glyphs[glyph_index]
text_properties.glyphs[glyph_index] # ensure_glyph_object(text_properties, glyph_properties)
if not regenerate
else text_properties.glyphs.add()
)
if regenerate: ############### ACTUAL TEXT
glyph_properties["glyph_id"] = glyph_id
glyph_properties["text_id"] = text_properties.text_id actual_text += glyph_id
glyph_properties["letter_spacing"] = 0
actual_text += glyph_id
############### NODE SCENE MANAGEMENT ############### NODE SCENE MANAGEMENT
inner_node = None # outsourced to ensure_glyph_object
outer_node = None
if regenerate:
outer_node = bpy.data.objects.new(f"{glyph_id}", None)
inner_node = bpy.data.objects.new(f"{glyph_id}_mesh", glyph.data)
transfer_properties_to_glyph_object(
text_properties, glyph_properties, outer_node
)
# Add into the scene.
mom.users_collection[0].objects.link(outer_node)
mom.users_collection[0].objects.link(inner_node)
# Parenting is hard.
inner_node.parent_type = "OBJECT"
inner_node.parent = outer_node
inner_node.matrix_parent_inverse = outer_node.matrix_world.inverted()
parent_to_curve(outer_node, mom)
outer_node.hide_set(True)
glyph_properties["glyph_object"] = outer_node
outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index
else:
outer_node = glyph_properties.glyph_object
outer_node[f"{utils.prefix()}_glyph_index"] = glyph_index
for c in outer_node.children:
if c.name.startswith(f"{glyph_id}_mesh"):
inner_node = c
############### TRANSFORMS ############### TRANSFORMS
glyph = get_original_glyph(text_properties, glyph_properties)
# origins could be shifted # origins could be shifted
# so we need to apply a pre_advance # so we need to apply a pre_advance
glyph_pre_advance, glyph_post_advance = get_glyph_prepost_advances(glyph) glyph_pre_advance, glyph_post_advance = get_glyph_prepost_advances(glyph)
@ -1683,14 +1801,12 @@ def set_text_on_curve(
outer_node.constraints["Follow Path"].up_axis = "UP_Y" outer_node.constraints["Follow Path"].up_axis = "UP_Y"
spline_index = 0 spline_index = 0
elif distribution_type == "CALCULATE": elif distribution_type == "CALCULATE":
previous_outer_node_rotation_mode = None previous_glyph_object_rotation_mode = None
previous_inner_node_rotation_mode = None if glyph_properties.glyph_object.rotation_mode != "QUATERNION":
if outer_node.rotation_mode != "QUATERNION": previous_glyph_object_rotation_mode = (
outer_node.rotation_mode = "QUATERNION" glyph_properties.glyph_object.rotation_mode
previous_outer_node_rotation_mode = outer_node.rotation_mode )
if inner_node.rotation_mode != "QUATERNION": glyph_properties.glyph_object.rotation_mode = "QUATERNION"
inner_node.rotation_mode = "QUATERNION"
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( location, tangent, spline_index = calc_point_on_bezier_curve(
@ -1702,7 +1818,9 @@ def set_text_on_curve(
is_newline = True is_newline = True
# position # position
outer_node.location = location + text_properties.translation glyph_properties.glyph_object.location = (
location + text_properties.translation
)
# orientation / rotation # orientation / rotation
mask = [0] mask = [0]
@ -1720,21 +1838,21 @@ def set_text_on_curve(
q = mathutils.Quaternion() q = mathutils.Quaternion()
q.rotate(text_properties.orientation) q.rotate(text_properties.orientation)
outer_node.rotation_quaternion = ( glyph_properties.glyph_object.rotation_quaternion = (
motor[0].to_3x3() @ q.to_matrix() motor[0].to_3x3() @ q.to_matrix()
).to_quaternion() ).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:
# outer_node.rotation_quaternion = (mom.matrix_world.inverted().to_3x3() @ motor[0].to_3x3() @ q.to_matrix()).to_quaternion() # glyph_properties.glyph_object.rotation_quaternion = (mom.matrix_world.inverted().to_3x3() @ motor[0].to_3x3() @ q.to_matrix()).to_quaternion()
# # scale # # scale
outer_node.scale = (scalor, scalor, scalor) glyph_properties.glyph_object.scale = (scalor, scalor, scalor)
if previous_outer_node_rotation_mode: if previous_glyph_object_rotation_mode:
outer_node.rotation_mode = previous_outer_node_rotation_mode glyph_properties.glyph_object.rotation_mode = (
if previous_inner_node_rotation_mode: previous_glyph_object_rotation_mode
inner_node.rotation_mode = previous_inner_node_rotation_mode )
# outer_node.hide_viewport = True # outer_node.hide_viewport = True
@ -1805,8 +1923,7 @@ def set_text_on_curve(
glyph_index += 1 glyph_index += 1
previous_spline_index = spline_index previous_spline_index = spline_index
if regenerate: text_properties["actual_text"] = actual_text
text_properties["actual_text"] = actual_text
return True return True

View file

@ -266,7 +266,13 @@ def get_glyphs(font_name, face_name, glyph_id):
return glyphs_for_id return glyphs_for_id
def get_glyph(font_name, face_name, glyph_id, alternate=0): def get_glyph(
font_name: str,
face_name: str,
glyph_id: str,
alternate: int = 0,
alternate_tolerant: bool = True,
):
"""add_glyph adds a glyph to a FontFace """add_glyph adds a glyph to a FontFace
it creates the :class:`Font` and :class:`FontFace` if it does not exist yet it creates the :class:`Font` and :class:`FontFace` if it does not exist yet
@ -276,6 +282,10 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
:type face_name: str :type face_name: str
:param glyph_id: The ``glyph_id`` from the glyph you want :param glyph_id: The ``glyph_id`` from the glyph you want
:type glyph_id: str :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 :return: returns the glyph object, or ``None`` if it does not exist
:rtype: `Object` :rtype: `Object`
@ -283,12 +293,21 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
glyphs = get_glyphs(font_name, face_name, glyph_id) glyphs = get_glyphs(font_name, face_name, glyph_id)
if len(glyphs) <= alternate or len(glyphs) == 0: if len(glyphs) == 0:
print( print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found" f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found"
) )
return None 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] return glyphs[alternate]