257 lines
8.9 KiB
Python
257 lines
8.9 KiB
Python
import bpy
|
|
import mathutils
|
|
import importlib
|
|
|
|
# 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()
|
|
|
|
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
|
|
|
|
# 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]
|
|
|
|
def turn_collection_hierarchy_into_path(obj):
|
|
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):
|
|
# print(turn_collection_hierarchy_into_path(obj))
|
|
# if scene.collection.objects.find(obj.name) >= 0:
|
|
# scene.collection.objects.unlink(obj)
|
|
for c in obj.users_collection:
|
|
c.objects.unlink(obj)
|
|
if fontcollection.objects.find(obj.name) < 0:
|
|
fontcollection.objects.link(obj)
|
|
|
|
# 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
|
|
|
|
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)
|
|
|
|
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
|