font3d_blender_addon/butils.py

258 lines
8.9 KiB
Python
Raw Normal View History

2024-05-21 18:00:49 +02:00
import bpy
2024-06-27 14:56:43 +02:00
import mathutils
import importlib
2024-05-21 18:00:49 +02:00
2024-06-27 14:56:43 +02:00
# then import dependencies for our addon
if "Font" in locals():
importlib.reload(Font)
else:
from .common import Font
def apply_all_transforms(obj):
mb = obj.matrix_basis
if hasattr(obj.data, "transform"):
obj.data.transform(mb)
for c in obj.children:
c.matrix_local = mb @ c.matrix_local
obj.matrix_basis.identity()
2024-05-21 18:00:49 +02:00
def get_parent_collection_names(collection, parent_names):
for parent_collection in bpy.data.collections:
if collection.name in parent_collection.children.keys():
parent_names.append(parent_collection.name)
get_parent_collection_names(parent_collection, parent_names)
return
2024-06-27 14:56:43 +02:00
# Ensure it's a curve object
# TODO: no raising, please
def get_curve_length(obj, num_samples = 100):
total_length = 0
if obj.type != 'CURVE':
raise TypeError("The selected object is not a curve")
curve = obj.data
# Loop through all splines in the curve
for spline in curve.splines:
total_length = total_length + spline.calc_length()
return total_length
# 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)]
def find_objects_by_name(
objects,
equals="",
contains="",
startswith="",
endswith=""):
# handle equals
if equals != "":
index = objects.find(equals)
if index >= 0:
return [objects[index]]
return []
# handle others is more permissive
return [obj for obj in objects if obj.name.startswith(startswith) and obj.name.endswith(endswith) and obj.name.find(contains) >= 0]
def find_objects_by_custom_property(
objects,
property_name="",
property_value=""):
return [obj for obj in objects if property_name in obj and obj[property_name] == property_value]
2024-05-21 18:00:49 +02:00
def turn_collection_hierarchy_into_path(obj):
2024-06-27 14:56:43 +02:00
parent_collection = obj.users_collection[0]
parent_names = []
parent_names.append(parent_collection.name)
get_parent_collection_names(parent_collection, parent_names)
parent_names.reverse()
return '\\'.join(parent_names)
def find_font_object(fontcollection, font_name):
fonts = find_objects_by_custom_property(fontcollection.objects,
"is_font",
True)
for font in fonts:
if font["font_name"] == font_name and font.parent == None:
return font
return None
def find_font_face_object(font_obj, face_name):
faces = find_objects_by_custom_property(font_obj.children,
"is_face",
True)
for face in faces:
if face["face_name"] == face_name:
return face
return None
def move_in_fontcollection(obj, fontcollection):
2024-05-21 18:00:49 +02:00
# print(turn_collection_hierarchy_into_path(obj))
2024-06-27 14:56:43 +02:00
# if scene.collection.objects.find(obj.name) >= 0:
# scene.collection.objects.unlink(obj)
for c in obj.users_collection:
c.objects.unlink(obj)
2024-05-21 18:00:49 +02:00
if fontcollection.objects.find(obj.name) < 0:
fontcollection.objects.link(obj)
2024-06-27 14:56:43 +02:00
# parent nesting structure
# the font object
font_obj = find_font_object(fontcollection,
obj["font_name"])
if font_obj == None:
font_obj = bpy.data.objects.new(obj["font_name"], None)
font_obj.empty_display_type = 'PLAIN_AXES'
fontcollection.objects.link(font_obj)
# ensure custom properties are set
font_obj["font_name"] = obj["font_name"]
font_obj["is_font"] = True
# the face object as a child of font object
face_obj = find_font_face_object(font_obj,
obj["face_name"])
if face_obj == None:
face_obj = bpy.data.objects.new(obj["face_name"], None)
face_obj.empty_display_type = 'PLAIN_AXES'
face_obj["is_face"] = True
fontcollection.objects.link(face_obj)
# ensure custom properties are set
face_obj["face_name"] = obj["face_name"]
face_obj["font_name"] = obj["font_name"]
if face_obj.parent != font_obj:
face_obj.parent = font_obj
# create glyphs if it does not exist
glyphs_objs = find_objects_by_name(face_obj.children, startswith="glyphs")
if len(glyphs_objs) <= 0:
glyphs_obj = bpy.data.objects.new("glyphs", None)
glyphs_obj.empty_display_type = 'PLAIN_AXES'
fontcollection.objects.link(glyphs_obj)
glyphs_obj.parent = face_obj
elif len(glyphs_objs) > 1:
print(f"found more glyphs objects than expected")
# now it must exist
glyphs_obj = find_objects_by_name(face_obj.children, startswith="glyphs")[0]
glyphs_obj["face_name"] = obj["face_name"]
glyphs_obj["font_name"] = obj["font_name"]
# and now parent it!
if obj.parent != glyphs_obj:
obj.parent = glyphs_obj
2024-05-28 14:11:32 +02:00
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
"""Show a simple message box
taken from `Link here <https://blender.stackexchange.com/questions/169844/multi-line-text-box-with-popup-menu>`_
:param title: The title shown in the message top bar
:type title: str
:param icon: The icon to be shown in the message top bar
:type icon: str
:param message: lines of text to display, a.k.a. the message
:type message: str or (str, str, ..)
TIP: Check `Link blender icons <https://docs.blender.org/api/current/bpy_types_enum_items/icon_items.html>`_ for icons you can use
TIP: Or even better, check `Link this addons <https://docs.blender.org/manual/en/latest/addons/development/icon_viewer.html>`_ to also see the icons.
usage:
.. code-block:: python
myLines=("line 1","line 2","line 3")
butils.ShowMessageBox(message=myLines)
or:
.. code-block:: python
butils.ShowMessageBox(title="",message=("AAAAAH","NOOOOO"),icon=)
"""
myLines=message
def draw(self, context):
if isinstance(myLines, str):
self.layout.label(text=myLines)
elif hasattr(myLines, "__iter__"):
for n in myLines:
self.layout.label(text=n)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
2024-06-27 14:56:43 +02:00
def set_text_on_curve(text_properties):
mom = text_properties.text_object
if mom.type != "CURVE":
return False
glyph_objects = []
for g in text_properties.glyphs:
glyph_objects.append(g.glyph_object)
context_override = bpy.context.copy()
context_override["selected_objects"] = list(glyph_objects)
with bpy.context.temp_override(**context_override):
bpy.ops.object.delete()
# bpy.ops.object.delete({"selected_objects": glyph_objects})
text_properties.glyphs.clear()
#TODO: fix selection with context_override
previous_selection = bpy.context.selected_objects
bpy.ops.object.select_all(action='DESELECT')
selected_objects = []
curve_length = get_curve_length(mom)
advance = 0
for i, c in enumerate(text_properties.text):
glyph_id = c
glyph = Font.get_glyph(text_properties.font_name,
text_properties.font_face,
glyph_id)
if glyph == None:
self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}")
continue
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
ob.constraints.new(type='FOLLOW_PATH')
ob.constraints["Follow Path"].target = mom
ob.constraints["Follow Path"].use_fixed_location = True
ob.constraints["Follow Path"].offset_factor = advance / curve_length
ob.constraints["Follow Path"].use_curve_follow = True
ob.constraints["Follow Path"].forward_axis = "FORWARD_X"
ob.constraints["Follow Path"].up_axis = "UP_Y"
scalor = 0.001
glyph_advance = (-1 * glyph.bound_box[0][0] + glyph.bound_box[4][0]) * scalor + text_properties.letter_spacing
ob.scale = (scalor, scalor, scalor)
mom.users_collection[0].objects.link(ob)
advance = advance + glyph_advance
glyph_data = text_properties.glyphs.add()
glyph_data.glyph_id = glyph_id
glyph_data.glyph_object = ob
glyph_data.letter_spacing = 0
ob.select_set(True)
# selected_objects.append(ob)
# selected_objects.append(mom)
mom.select_set(True)
bpy.context.view_layer.objects.active = mom
bpy.ops.object.parent_set(type='OBJECT')
# bpy.ops.object.select_all(action='DESELECT')
# for o in previous_selection:
# o.select_set(True)
# context_override = bpy.context.copy()
# context_override["selected_objects"] = selected_objects
# context_override["active_object"] = mom
# with bpy.context.temp_override(**context_override):
# bpy.ops.object.parent_set(type='OBJECT')
return True