f6b1649c71
when we set a text, we don't want our manual depsgraph update, because this would trigger another setting of the text, which would trigger the manual depsgraph update, which would trigger another setting of the text, which would trigger the manual depsgraph update, which will eventually be too much. So, we ignore the depsgraph update n-times, where "n = n-letters + 1". worst side effect: might not update the text automatically in edge cases.
1215 lines
45 KiB
Python
1215 lines
45 KiB
Python
import bpy
|
|
import mathutils
|
|
import queue
|
|
import importlib
|
|
import os
|
|
import re
|
|
from multiprocessing import Process
|
|
|
|
# import time # for debugging performance
|
|
|
|
# then import dependencies for our addon
|
|
if "Font" in locals():
|
|
importlib.reload(Font)
|
|
else:
|
|
from .common import Font
|
|
|
|
if "utils" in locals():
|
|
importlib.reload(utils)
|
|
else:
|
|
from .common import utils
|
|
|
|
execution_queue = queue.Queue()
|
|
|
|
# This function can safely be called in another thread.
|
|
# The function will be executed when the timer runs the next time.
|
|
def run_in_main_thread(function):
|
|
execution_queue.put(function)
|
|
|
|
def execute_queued_functions():
|
|
while not execution_queue.empty():
|
|
function = execution_queue.get()
|
|
function()
|
|
return 1.0
|
|
|
|
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(curve_obj, resolution = -1):
|
|
total_length = 0
|
|
|
|
curve = curve_obj.data
|
|
|
|
# Loop through all splines in the curve
|
|
for spline in curve.splines:
|
|
total_length = total_length + spline.calc_length(resolution=resolution)
|
|
|
|
return total_length
|
|
|
|
def get_curve_line_lengths(curve_obj, resolution = -1):
|
|
lengths = []
|
|
for spline in curve_obj.data.splines:
|
|
lengths.append(spline.calc_length(resolution=resolution))
|
|
return lengths
|
|
|
|
def get_next_line_advance(curve_obj, current_advance, previous_glyph_advance, resolution = -1):
|
|
curve_line_lengths = get_curve_line_lengths(curve_obj, resolution)
|
|
total_length = 0
|
|
for cll in curve_line_lengths:
|
|
total_length += cll
|
|
if current_advance - previous_glyph_advance < total_length:
|
|
return total_length
|
|
return current_advance
|
|
|
|
def calc_point_on_bezier(bezier_point_1, bezier_point_2, t):
|
|
p1 = bezier_point_1.co
|
|
h1 = bezier_point_1.handle_right
|
|
p2 = bezier_point_2.co
|
|
h2 = bezier_point_2.handle_left
|
|
return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2
|
|
|
|
# same in slightly more lines
|
|
# result is equal, performance minimally better perhaps?
|
|
# def calc_point_on_bezier(bezier_point_1, bezier_point_2, ratio):
|
|
# startPoint = bezier_point_1.co
|
|
# controlPoint1 = bezier_point_1.handle_right
|
|
# controlPoint2 = bezier_point_2.handle_left
|
|
# endPoint = bezier_point_2.co
|
|
|
|
# remainder = 1 - ratio
|
|
# ratioSquared = ratio * ratio
|
|
# remainderSquared = remainder * remainder
|
|
# startPointMultiplier = remainderSquared * remainder
|
|
# controlPoint1Multiplier = remainderSquared * ratio * 3
|
|
# controlPoint2Multiplier = ratioSquared * remainder * 3
|
|
# endPointMultiplier = ratioSquared * ratio
|
|
|
|
# return startPoint * startPointMultiplier + controlPoint1 * controlPoint1Multiplier + controlPoint2 * controlPoint2Multiplier + endPoint * endPointMultiplier
|
|
|
|
|
|
def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t):
|
|
p1 = bezier_point_1.co
|
|
h1 = bezier_point_1.handle_right
|
|
p2 = bezier_point_2.co
|
|
h2 = bezier_point_2.handle_left
|
|
return (
|
|
(-3 * (1 - t)**2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t)**2) * h1 +
|
|
(-3 * (t**2) + 6 * t * (1 - t)) * h2 + (3 * t**2) * p2
|
|
).normalized()
|
|
|
|
from math import radians, sqrt, pi, acos
|
|
|
|
def align_rotations_auto_pivot(mask, input_rotations, vectors, factors, local_main_axis):
|
|
output_rotations = [mathutils.Matrix().to_3x3() for _ in range(len(input_rotations))]
|
|
|
|
for i in mask:
|
|
vector = mathutils.Vector(vectors[i]).normalized()
|
|
input_rotation = mathutils.Euler(input_rotations[i])
|
|
|
|
if vector.length < 1e-6:
|
|
output_rotations[i] = input_rotation.to_matrix()
|
|
continue
|
|
|
|
old_rotation = input_rotation.to_matrix()
|
|
old_axis = (old_rotation @ local_main_axis).normalized()
|
|
new_axis = vector
|
|
# rotation_axis = (-(old_axis) + new_axis).normalized()
|
|
rotation_axis = old_axis.cross(new_axis).normalized()
|
|
|
|
if rotation_axis.length < 1e-6:
|
|
# Vectors are linearly dependent, fallback to another axis
|
|
rotation_axis = (old_axis + mathutils.Matrix().to_3x3().col[2]).normalized()
|
|
|
|
if rotation_axis.length < 1e-6:
|
|
# This is now guaranteed to not be zero
|
|
rotation_axis = (-(old_axis) + mathutils.Matrix().to_3x3().col[1]).normalized()
|
|
|
|
# full_angle = radians(sqrt((4 * pow(input_rotation.to_quaternion().dot(mathutils.Quaternion(vectors[i].normalized())), 2) - 3)))
|
|
# dot = old_axis.dot(new_axis)
|
|
# normalized_diff = (old_axis - new_axis).normalized()
|
|
# full_angle = acos(min((old_axis * new_axis + normalized_diff.dot(2)).length, 1))
|
|
full_angle = old_axis.angle(new_axis)
|
|
angle = factors[i] * full_angle
|
|
|
|
rotation = mathutils.Quaternion(rotation_axis, angle).to_matrix()
|
|
new_rotation_matrix = old_rotation @ rotation
|
|
output_rotations[i] = new_rotation_matrix
|
|
|
|
return [mat.to_4x4() for mat in output_rotations]
|
|
|
|
def calc_bezier_length(bezier_point_1, bezier_point_2, resolution=20):
|
|
step = 1/resolution
|
|
previous_p = bezier_point_1.co
|
|
length = 0
|
|
for i in range(-1, resolution):
|
|
t = (i + 1) * step
|
|
p = calc_point_on_bezier(bezier_point_1, bezier_point_2, t)
|
|
length += (p - previous_p).length
|
|
previous_p = p
|
|
return length
|
|
|
|
def calc_point_on_bezier_spline(bezier_spline_obj,
|
|
distance,
|
|
output_tangent = False,
|
|
resolution_factor = 1.0):
|
|
# what's the point of just one point
|
|
# assert len(bezier_spline_obj.bezier_points) >= 2
|
|
# however, maybe let's have it not crash and do this
|
|
if len(bezier_spline_obj.bezier_points) < 1:
|
|
print("butils::calc_point_on_bezier_spline: whoops, no points. panicking. return 0,0,0")
|
|
if output_tangent:
|
|
return mathutils.Vector((0,0,0)), mathutils.Vector((1,0,0))
|
|
else:
|
|
return mathutils.Vector((0,0,0))
|
|
if len(bezier_spline_obj.bezier_points) == 1:
|
|
p = bezier_spline_obj.bezier_points[0]
|
|
travel = (p.handle_left - p.co).normalized() * distance
|
|
if output_tangent:
|
|
tangent = mathutils.Vector((1,0,0))
|
|
return travel, tangent
|
|
else:
|
|
return travel
|
|
|
|
if distance <= 0:
|
|
p = bezier_spline_obj.bezier_points[0]
|
|
travel = (p.co - p.handle_left).normalized() * distance
|
|
location = p.co + travel
|
|
if output_tangent:
|
|
p2 = bezier_spline_obj.bezier_points[1]
|
|
tangent = calc_tangent_on_bezier(p, p2, 0)
|
|
return location, tangent
|
|
else:
|
|
return location
|
|
|
|
beziers = []
|
|
lengths = []
|
|
total_length = 0
|
|
n_bezier_points = len(bezier_spline_obj.bezier_points)
|
|
for i in range(0, len(bezier_spline_obj.bezier_points) - 1):
|
|
bezier = [ bezier_spline_obj.bezier_points[i],
|
|
bezier_spline_obj.bezier_points[i + 1] ]
|
|
length = calc_bezier_length(bezier[0],
|
|
bezier[1],
|
|
int(bezier_spline_obj.resolution_u * resolution_factor))
|
|
total_length += length
|
|
beziers.append(bezier)
|
|
lengths.append(length)
|
|
# if total_length > distance:
|
|
# break
|
|
|
|
iterated_distance = 0
|
|
for i in range(0, len(beziers)):
|
|
if iterated_distance + lengths[i] > distance:
|
|
distance_on_bezier = (distance - iterated_distance)
|
|
d = distance_on_bezier / lengths[i]
|
|
# print(f"i: {i}, d: {d}, distance_on_bezier: {distance_on_bezier}, distance: {distance}")
|
|
location = calc_point_on_bezier(beziers[i][0],
|
|
beziers[i][1],
|
|
d)
|
|
if output_tangent:
|
|
tangent = calc_tangent_on_bezier(beziers[i][0],
|
|
beziers[i][1],
|
|
d)
|
|
return location, tangent
|
|
else:
|
|
return location
|
|
iterated_distance += lengths[i]
|
|
# if we are here, the point is outside the spline
|
|
last_i = len(beziers) - 1
|
|
p = beziers[last_i][1]
|
|
travel = (p.handle_right - p.co).normalized() * (distance - total_length)
|
|
location = p.co + travel
|
|
if output_tangent:
|
|
tangent = calc_tangent_on_bezier(beziers[last_i][0],
|
|
p,
|
|
1)
|
|
return location, tangent
|
|
else:
|
|
return location
|
|
|
|
|
|
def calc_point_on_bezier_curve(bezier_curve_obj,
|
|
distance,
|
|
output_tangent = False,
|
|
output_spline_index = False,
|
|
resolution_factor = 1.0):
|
|
curve = bezier_curve_obj.data
|
|
|
|
# Loop through all splines in the curve
|
|
total_length = 0
|
|
for i, spline in enumerate(curve.splines):
|
|
resolution = int(spline.resolution_u * resolution_factor)
|
|
length = spline.calc_length(resolution=resolution)
|
|
if total_length + length > distance or i == len(curve.splines) - 1:
|
|
if output_spline_index and output_tangent:
|
|
# return value from c_p_o_b_s is a tuple
|
|
# so we need to append tuple + tuple
|
|
return calc_point_on_bezier_spline(spline,
|
|
(distance - total_length),
|
|
output_tangent,
|
|
resolution_factor) + (i,)
|
|
if output_spline_index and not output_tangent:
|
|
# return value from c_p_o_b_s is a location vector
|
|
# so we need to append with a comma
|
|
return calc_point_on_bezier_spline(spline,
|
|
(distance - total_length),
|
|
output_tangent,
|
|
resolution_factor), i
|
|
else:
|
|
return calc_point_on_bezier_spline(spline,
|
|
(distance - total_length),
|
|
output_tangent,
|
|
resolution_factor)
|
|
total_length += length
|
|
|
|
# TODO: can this fail?
|
|
|
|
|
|
# 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, allow_duplicates=False):
|
|
# 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"]
|
|
|
|
def get_hash(o):
|
|
return hash(tuple(tuple(v.co) for v in o.data.vertices ))
|
|
|
|
for other_obj in find_objects_by_custom_property(glyphs_obj.children, "glyph", obj["glyph"]):
|
|
if get_hash(other_obj) == get_hash(obj) and not allow_duplicates:
|
|
return other_obj
|
|
|
|
# and now parent it!
|
|
if obj.parent != glyphs_obj:
|
|
obj.parent = glyphs_obj
|
|
|
|
for c in obj.users_collection:
|
|
c.objects.unlink(obj)
|
|
if fontcollection.objects.find(obj.name) < 0:
|
|
fontcollection.objects.link(obj)
|
|
|
|
return obj
|
|
|
|
def bpy_to_abspath(blender_path):
|
|
return os.path.realpath(bpy.path.abspath(blender_path))
|
|
|
|
def register_font_from_filepath(filepath):
|
|
from .bimport import get_font_faces_in_file
|
|
|
|
availables = get_font_faces_in_file(filepath)
|
|
|
|
fonts = {}
|
|
for a in availables:
|
|
font_name = a["font_name"]
|
|
face_name = a["face_name"]
|
|
glyph = a["glyph"]
|
|
if not font_name in fonts:
|
|
fonts[font_name] = {}
|
|
if not face_name in fonts[font_name]:
|
|
fonts[font_name][face_name] = []
|
|
fonts[font_name][face_name].append(glyph)
|
|
for font_name in fonts:
|
|
for face_name in fonts[font_name]:
|
|
Font.register_font(font_name,
|
|
face_name,
|
|
fonts[font_name][face_name],
|
|
filepath)
|
|
|
|
def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
|
|
if not filepath.endswith(".glb") and not filepath.endswith(".gltf"):
|
|
ShowMessageBox(f"Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file")
|
|
return False
|
|
|
|
marker_property = "font_import"
|
|
bpy.ops.abc3d.import_font_gltf(filepath=filepath,
|
|
glyphs=glyphs,
|
|
marker_property=marker_property,
|
|
font_name=font_name,
|
|
face_name=face_name)
|
|
|
|
fontcollection = bpy.data.collections.get("ABC3D")
|
|
if fontcollection is None:
|
|
fontcollection = bpy.data.collections.new("ABC3D")
|
|
|
|
modified_font_faces = []
|
|
all_glyph_os = []
|
|
all_objects = []
|
|
for o in bpy.context.scene.objects:
|
|
if marker_property in o:
|
|
if "type" in o and o["type"] == "glyph":
|
|
all_glyph_os.append(o)
|
|
|
|
for o in all_glyph_os:
|
|
glyph_id = o["glyph"]
|
|
font_name = o["font_name"]
|
|
face_name = o["face_name"]
|
|
|
|
glyph_obj = move_in_fontcollection(
|
|
o,
|
|
fontcollection)
|
|
|
|
if glyph_obj == o:
|
|
del o[marker_property]
|
|
|
|
Font.add_glyph(
|
|
font_name,
|
|
face_name,
|
|
glyph_id,
|
|
glyph_obj)
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
add_metrics_obj_from_bound_box(glyph_obj,
|
|
bound_box_as_array(c.bound_box))
|
|
modified_font_faces.append({"font_name": font_name,
|
|
"face_name": face_name})
|
|
|
|
for mff in modified_font_faces:
|
|
glyphs = []
|
|
face = Font.fonts[mff["font_name"]].faces[mff["face_name"]]
|
|
# iterate glyphs
|
|
for g in face.glyphs:
|
|
# iterate alternates
|
|
for glyph in face.glyphs[g]:
|
|
glyphs.append(glyph)
|
|
if len(glyphs) > 0:
|
|
add_default_metrics_to_objects(glyphs)
|
|
# calculate unit factor
|
|
h = get_glyph_height(glyphs[0])
|
|
if h != 0:
|
|
face.unit_factor = 1 / h
|
|
|
|
update_available_fonts()
|
|
remove_list = []
|
|
for o in bpy.context.scene.collection.all_objects:
|
|
if not o.name in fontcollection.all_objects:
|
|
if marker_property in o and o[marker_property] == True:
|
|
remove_list.append(o)
|
|
|
|
simply_delete_objects(remove_list)
|
|
|
|
# completely_delete_objects(remove_list)
|
|
|
|
def update_available_fonts():
|
|
abc3d_data = bpy.context.scene.abc3d_data
|
|
|
|
for font_name in Font.fonts.keys():
|
|
for face_name in Font.fonts[font_name].faces.keys():
|
|
found = False
|
|
for f in abc3d_data.available_fonts.values():
|
|
if font_name == f.font_name and face_name == f.face_name:
|
|
found = True
|
|
if not found:
|
|
f = abc3d_data.available_fonts.add()
|
|
f.font_name = font_name
|
|
f.face_name = face_name
|
|
print(f"{__name__} added {font_name} {face_name}")
|
|
|
|
# def update_available_texts():
|
|
# abc3d_data = bpy.context.scene.abc3d_data
|
|
# for o in bpy.context.scene.objects:
|
|
# if "linked_textobject" in o.keys():
|
|
# i = o["linked_textobject"]
|
|
# found = False
|
|
# if len(abc3d_data.available_texts) > i:
|
|
# if abc3d_data.available_texts[i].glyphs
|
|
|
|
def getPreferences(context):
|
|
preferences = context.preferences
|
|
return preferences.addons['abc3d'].preferences
|
|
|
|
# clear available fonts
|
|
def clear_available_fonts():
|
|
bpy.context.scene.abc3d_data.available_fonts.clear()
|
|
|
|
def load_installed_fonts():
|
|
preferences = getPreferences(bpy.context)
|
|
font_dir = os.path.join(preferences.assets_dir,"fonts")
|
|
if os.path.exists(font_dir):
|
|
for file in os.listdir(font_dir):
|
|
if file.endswith(".glb") or file.endswith(".gltf"):
|
|
font_path = os.path.join(font_dir, file)
|
|
# ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
|
# print(f"loading font from {font_path}")
|
|
# for f in bpy.context.scene.abc3d_data.available_fonts.values():
|
|
# print(f"available font: {f.font_name} {f.face_name}")
|
|
register_font_from_filepath(font_path)
|
|
load_font_from_filepath(font_path)
|
|
|
|
def register_installed_fonts():
|
|
preferences = getPreferences(bpy.context)
|
|
font_dir = os.path.join(preferences.assets_dir,"fonts")
|
|
if os.path.exists(font_dir):
|
|
for file in os.listdir(font_dir):
|
|
if file.endswith(".glb") or file.endswith(".gltf"):
|
|
font_path = os.path.join(font_dir, file)
|
|
# ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
|
# print(f"loading font from {font_path}")
|
|
# for f in bpy.context.scene.abc3d_data.available_fonts.values():
|
|
# print(f"available font: {f.font_name} {f.face_name}")
|
|
register_font_from_filepath(font_path)
|
|
|
|
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 simply_delete_objects(objs):
|
|
context_override = bpy.context.copy()
|
|
context_override["selected_objects"] = list(objs)
|
|
with bpy.context.temp_override(**context_override):
|
|
bpy.ops.object.delete()
|
|
|
|
def completely_delete_objects(objs):
|
|
simply_delete_objects(objs)
|
|
|
|
# remove deleted objects
|
|
# this is necessary
|
|
for g in objs:
|
|
if type(g) != type(None):
|
|
try:
|
|
bpy.data.objects.remove(g, do_unlink=True)
|
|
except ReferenceError as e:
|
|
# not important
|
|
pass
|
|
|
|
def is_mesh(o):
|
|
return type(o.data) == bpy.types.Mesh
|
|
|
|
def is_metrics_object(o):
|
|
if f"{utils.prefix()}_type" in o:
|
|
return o[f"{utils.prefix()}_type"] == 'metrics'
|
|
return (re.match(".*_metrics$", o.name) != None or re.match(".*_metrics.[\d]{3}$", o.name) != None) and is_mesh(o)
|
|
|
|
def is_text_object(o):
|
|
if f"{utils.prefix()}_type" in o:
|
|
return o[f"{utils.prefix()}_type"] == 'textobject'
|
|
for t in bpy.context.scene.abc3d_data.available_texts:
|
|
if o == t.text_object:
|
|
return True
|
|
return False
|
|
|
|
def is_glyph(o):
|
|
if f"{utils.prefix()}_type" in o:
|
|
return o[f"{utils.prefix()}_type"] == 'glyph'
|
|
try:
|
|
return type(o.parent) is not type(None) \
|
|
and "glyphs" in o.parent.name \
|
|
and is_mesh(o) \
|
|
and not is_metrics_object(o)
|
|
except ReferenceError as e:
|
|
return False
|
|
|
|
def update_types():
|
|
scene = bpy.context.scene
|
|
abc3d_data = scene.abc3d_data
|
|
for t in abc3d_data.available_texts:
|
|
t.text_object[f"{utils.prefix()}_type"] = "textobject"
|
|
for g in t.glyphs:
|
|
g.glyph_object[f"{utils.prefix()}_type"] = "glyph"
|
|
|
|
# blender bound_box vertices
|
|
#
|
|
# 3------7.
|
|
# |`. | `. +y
|
|
# | `2------6 |
|
|
# | | | | |
|
|
# 0---|--4. | +--- +x
|
|
# `. | `.| `.
|
|
# `1------5 `+z
|
|
|
|
def get_glyph_advance(glyph_obj):
|
|
for c in glyph_obj.children:
|
|
if is_metrics_object(c):
|
|
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])
|
|
|
|
def get_glyph_height(glyph_obj):
|
|
for c in glyph_obj.children:
|
|
if is_metrics_object(c):
|
|
return abs(c.bound_box[0][1] - c.bound_box[3][1])
|
|
return abs(glyph_obj.bound_box[0][1] - glyph_obj.bound_box[3][1])
|
|
|
|
def prepare_text(font_name, face_name, text):
|
|
loaded, missing, loadable, files = Font.test_glyphs_availability(
|
|
font_name,
|
|
face_name,
|
|
text)
|
|
if len(loadable) > 0:
|
|
for filepath in files:
|
|
load_font_from_filepath(filepath, loadable, font_name, face_name)
|
|
return True
|
|
|
|
def is_bezier(curve):
|
|
if len(curve.data.splines) < 1:
|
|
return False
|
|
return curve.data.splines[0].type == 'BEZIER'
|
|
|
|
def set_text_on_curve(text_properties, recursive=True):
|
|
# starttime = time.perf_counter_ns()
|
|
mom = text_properties.text_object
|
|
if mom.type != "CURVE":
|
|
return False
|
|
|
|
regenerate = False
|
|
glyph_objects = []
|
|
for i, g in enumerate(text_properties.glyphs):
|
|
glyph_objects.append(g.glyph_object)
|
|
|
|
# check if perhaps one glyph was deleted
|
|
if (type(g.glyph_object) == type(None)
|
|
or type(g.glyph_object.parent) == type(None)
|
|
or g.glyph_object.parent.users_collection != g.glyph_object.users_collection):
|
|
regenerate = True
|
|
elif len(text_properties.text) > i and g.glyph_id != text_properties.text[i]:
|
|
regenerate = True
|
|
elif len(text_properties.text) > i and (g.glyph_object[f"{utils.prefix()}_font_name"] != text_properties.font_name
|
|
or g.glyph_object[f"{utils.prefix()}_face_name"] != text_properties.face_name):
|
|
regenerate = True
|
|
|
|
if len(text_properties.text) != len(text_properties.glyphs):
|
|
regenerate = True
|
|
|
|
# blender bug
|
|
# https://projects.blender.org/blender/blender/issues/100661
|
|
if mom.data.use_path:
|
|
regenerate = True
|
|
|
|
# if we regenerate.... delete objects
|
|
if regenerate:
|
|
completely_delete_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 = text_properties.offset
|
|
glyph_advance = 0
|
|
is_command = False
|
|
previous_spline_index = -1
|
|
for i, c in enumerate(text_properties.text):
|
|
face = Font.fonts[text_properties.font_name].faces[text_properties.face_name]
|
|
scalor = face.unit_factor * text_properties.font_size
|
|
if c == '\\':
|
|
is_command = True
|
|
continue
|
|
if c == ' ':
|
|
advance = advance + scalor
|
|
continue
|
|
is_newline = False
|
|
if is_command:
|
|
if c == 'n':
|
|
is_newline = True
|
|
next_line_advance = get_next_line_advance(mom, advance, glyph_advance)
|
|
if advance == next_line_advance:
|
|
# self.report({'INFO'}, f"would like to add new line for {text_properties.text} please")
|
|
print(f"would like to add new line for {text_properties.text} please")
|
|
# TODO: add a new line
|
|
advance = next_line_advance + text_properties.offset
|
|
continue
|
|
is_command = False
|
|
glyph_id = c
|
|
|
|
glyph = Font.get_glyph(text_properties.font_name,
|
|
text_properties.face_name,
|
|
glyph_id)
|
|
|
|
if glyph == None:
|
|
# self.report({'ERROR'}, f"Glyph not found for {font_name} {face_name} {glyph_id}")
|
|
print(f"Glyph not found for {text_properties.font_name} {text_properties.face_name} {glyph_id}")
|
|
continue
|
|
|
|
ob = None
|
|
if regenerate:
|
|
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
|
ob[f"{utils.prefix()}_type"] = "glyph"
|
|
ob[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id
|
|
ob[f"{utils.prefix()}_font_name"] = text_properties.font_name
|
|
ob[f"{utils.prefix()}_face_name"] = text_properties.face_name
|
|
else:
|
|
ob = text_properties.glyphs[i].glyph_object
|
|
|
|
distribution_type = 'CALCULATE' if is_bezier(mom) else 'FOLLOW_PATH'
|
|
if distribution_type == 'FOLLOW_PATH':
|
|
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"
|
|
elif distribution_type == 'CALCULATE':
|
|
location, tangent, spline_index = calc_point_on_bezier_curve(mom, advance, True, True)
|
|
if spline_index != previous_spline_index:
|
|
is_newline = True
|
|
|
|
if regenerate:
|
|
ob.location = mom.matrix_world @ (location + text_properties.translation)
|
|
else:
|
|
ob.location = (location + text_properties.translation)
|
|
|
|
if not text_properties.ignore_orientation:
|
|
mask = [0]
|
|
input_rotations = [mathutils.Vector((0.0, 0.0, 0.0))]
|
|
vectors = [tangent]
|
|
factors = [1.0]
|
|
local_main_axis = mathutils.Vector((1.0, 0.0, 0.0))
|
|
motor = align_rotations_auto_pivot(mask,
|
|
input_rotations,
|
|
vectors,
|
|
factors,
|
|
local_main_axis)
|
|
if ob.rotation_mode != 'QUATERNION':
|
|
ob.rotation_mode = 'QUATERNION'
|
|
q = mathutils.Quaternion()
|
|
q.rotate(text_properties.orientation)
|
|
if regenerate:
|
|
ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
|
|
else:
|
|
ob.rotation_quaternion = (motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
|
|
else:
|
|
q = mathutils.Quaternion()
|
|
q.rotate(text_properties.orientation)
|
|
ob.rotation_quaternion = q
|
|
# ob.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion()
|
|
|
|
|
|
glyph_advance = get_glyph_advance(glyph) * scalor + text_properties.letter_spacing
|
|
|
|
# now we need to compensate for curvature
|
|
# otherwise letters will be closer together the curvier the bezier is
|
|
# this could be done more efficiently, but whatever
|
|
curve_compensation = 0
|
|
if distribution_type == 'CALCULATE' and (not is_newline or spline_index == 0): # TODO: fix newline hack
|
|
if text_properties.compensate_curvature and glyph_advance > 0:
|
|
previous_location, psi = calc_point_on_bezier_curve(mom, advance, False, True)
|
|
new_location, si = calc_point_on_bezier_curve(mom, advance + glyph_advance, False, True)
|
|
if psi == si:
|
|
while (previous_location - new_location).length > glyph_advance and psi == si:
|
|
curve_compensation = curve_compensation - glyph_advance * 0.01
|
|
new_location, si = calc_point_on_bezier_curve(mom,
|
|
advance + glyph_advance + curve_compensation,
|
|
output_tangent=False,
|
|
output_spline_index=True)
|
|
while (previous_location - new_location).length < glyph_advance and psi == si:
|
|
curve_compensation = curve_compensation + glyph_advance * 0.01
|
|
new_location, si = calc_point_on_bezier_curve(mom,
|
|
advance + glyph_advance + curve_compensation,
|
|
output_tangent=False,
|
|
output_spline_index=True)
|
|
|
|
ob.scale = (scalor, scalor, scalor)
|
|
|
|
advance = advance + glyph_advance + curve_compensation
|
|
previous_spline_index = spline_index
|
|
|
|
if regenerate:
|
|
mom.users_collection[0].objects.link(ob)
|
|
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)
|
|
|
|
if regenerate:
|
|
mom.select_set(True)
|
|
# https://projects.blender.org/blender/blender/issues/100661
|
|
mom.data.use_path = False
|
|
mom[f"{utils.prefix()}_type"] = "textobject"
|
|
mom[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id
|
|
mom[f"{utils.prefix()}_font_name"] = text_properties.font_name
|
|
mom[f"{utils.prefix()}_face_name"] = text_properties.face_name
|
|
mom[f"{utils.prefix()}_font_size"] = text_properties.font_size
|
|
mom[f"{utils.prefix()}_letter_spacing"] = text_properties.letter_spacing
|
|
mom[f"{utils.prefix()}_orientation"] = text_properties.orientation
|
|
mom[f"{utils.prefix()}_translation"] = text_properties.translation
|
|
bpy.context.view_layer.objects.active = mom
|
|
bpy.ops.object.parent_set(type='OBJECT')
|
|
bpy.context.scene.abc3d_data["lock_depsgraph_update_ntimes"] = len(bpy.context.selected_objects)
|
|
mom["lock_depsgraph_update_ntimes"] = len(bpy.context.selected_objects)
|
|
|
|
# endtime = time.perf_counter_ns()
|
|
# elapsedtime = endtime - starttime
|
|
|
|
return True
|
|
|
|
verification_object = {
|
|
f"{utils.prefix()}_type": "textobject",
|
|
f"{utils.prefix()}_linked_textobject": 0,
|
|
f"{utils.prefix()}_font_name": "font_name",
|
|
f"{utils.prefix()}_face_name": "face_name",
|
|
f"{utils.prefix()}_font_size": 42,
|
|
f"{utils.prefix()}_letter_spacing": 42,
|
|
f"{utils.prefix()}_orientation": [0,0,0],
|
|
f"{utils.prefix()}_translation": [0,0,0],
|
|
}
|
|
|
|
def verify_text_object(o):
|
|
pass
|
|
|
|
def transfer_text_properties_to_text_object(text_properties, o):
|
|
o[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id
|
|
o[f"{utils.prefix()}_font_name"] = text_properties.font_name
|
|
o[f"{utils.prefix()}_face_name"] = text_properties.face_name
|
|
o[f"{utils.prefix()}_font_size"] = text_properties.font_size
|
|
o[f"{utils.prefix()}_letter_spacing"] = text_properties.letter_spacing
|
|
o[f"{utils.prefix()}_orientation"] = text_properties.orientation
|
|
o[f"{utils.prefix()}_translation"] = text_properties.translation
|
|
o[f"{utils.prefix()}_text"] = text_properties["text"]
|
|
|
|
def transfer_text_object_to_text_properties(o, text_properties):
|
|
text_properties["text_id"] = o[f"{utils.prefix()}_linked_textobject"]
|
|
text_properties["font_name"] = o[f"{utils.prefix()}_font_name"]
|
|
text_properties["face_name"] = o[f"{utils.prefix()}_face_name"]
|
|
text_properties["font_size"] = o[f"{utils.prefix()}_font_size"]
|
|
text_properties["letter_spacing"] = o[f"{utils.prefix()}_letter_spacing"]
|
|
text_properties["orientation"] = o[f"{utils.prefix()}_orientation"]
|
|
text_properties["translation"] = o[f"{utils.prefix()}_translation"]
|
|
text_properties["text"] = o[f"{utils.prefix()}_text"]
|
|
|
|
# blender bound_box vertices
|
|
#
|
|
# 3------7.
|
|
# |`. | `. +y
|
|
# | `2------6 -z |
|
|
# | | | | `. |
|
|
# 0---|--4. | `+--- +x
|
|
# `. | `.|
|
|
# `1------5
|
|
|
|
def add_metrics_obj_from_bound_box(glyph, bound_box=None):
|
|
mesh = bpy.data.meshes.new(f"{glyph.name}_metrics") # add the new mesh
|
|
obj = bpy.data.objects.new(mesh.name, mesh)
|
|
obj["font_name"] = glyph["font_name"]
|
|
obj["face_name"] = glyph["face_name"]
|
|
obj["glyph"] = glyph["glyph"]
|
|
obj[f"{utils.prefix()}_type"] = "metrics"
|
|
|
|
# remove already existing metrics
|
|
remove_metrics = []
|
|
for c in glyph.children:
|
|
if is_metrics_object(c):
|
|
remove_metrics.append(c)
|
|
if len(remove_metrics) > 0:
|
|
completely_delete_objects(remove_metrics)
|
|
|
|
col = glyph.users_collection[0]
|
|
col.objects.link(obj)
|
|
# bpy.context.view_layer.objects.active = obj
|
|
obj.parent = glyph
|
|
|
|
if type(bound_box) == type(None):
|
|
bound_box = glyph.bound_box
|
|
|
|
verts = [bound_box[0],
|
|
bound_box[1],
|
|
bound_box[2],
|
|
bound_box[3],
|
|
bound_box[4],
|
|
bound_box[5],
|
|
bound_box[6],
|
|
bound_box[7],
|
|
]
|
|
edges = [[0,1],[1,2],[2,3],[3,0],
|
|
[0,4],[1,5],[2,6],[3,7],
|
|
[4,5],[5,6],[6,7],[7,4],
|
|
]
|
|
faces = []
|
|
|
|
mesh.from_pydata(verts, edges, faces)
|
|
|
|
def add_faces_to_metrics(obj):
|
|
mesh = bpy.data.meshes.new(f"{obj.name}") # add the new mesh
|
|
print(f"add_faces_to_metrics for {obj.name}")
|
|
bound_box = bound_box_as_array(obj.bound_box)
|
|
verts = [bound_box[0],
|
|
bound_box[1],
|
|
bound_box[2],
|
|
bound_box[3],
|
|
bound_box[4],
|
|
bound_box[5],
|
|
bound_box[6],
|
|
bound_box[7],
|
|
]
|
|
edges = [[0,1],[1,2],[2,3],[3,0],
|
|
[0,4],[1,5],[2,6],[3,7],
|
|
[4,5],[5,6],[6,7],[7,4],
|
|
]
|
|
faces = [
|
|
[0,1,2], [2,3,0],
|
|
[2,6,7], [7,3,2],
|
|
[6,5,4], [4,7,6],
|
|
[4,5,1], [0,4,1],
|
|
[1,5,6], [1,6,2],
|
|
[4,0,7], [7,0,3],
|
|
]
|
|
|
|
mesh.from_pydata(verts, edges, faces)
|
|
|
|
old_mesh = obj.data
|
|
obj.data = mesh
|
|
bpy.data.meshes.remove(old_mesh)
|
|
|
|
def remove_faces_from_metrics(obj):
|
|
mesh = bpy.data.meshes.new(f"{obj.name}") # add the new mesh
|
|
bound_box = bound_box_as_array(obj.bound_box)
|
|
verts = [bound_box[0],
|
|
bound_box[1],
|
|
bound_box[2],
|
|
bound_box[3],
|
|
bound_box[4],
|
|
bound_box[5],
|
|
bound_box[6],
|
|
bound_box[7],
|
|
]
|
|
edges = [[0,1],[1,2],[2,3],[3,0],
|
|
[0,4],[1,5],[2,6],[3,7],
|
|
[4,5],[5,6],[6,7],[7,4],
|
|
]
|
|
faces = [
|
|
]
|
|
|
|
mesh.from_pydata(verts, edges, faces)
|
|
|
|
old_mesh = obj.data
|
|
obj.data = mesh
|
|
bpy.data.meshes.remove(old_mesh)
|
|
|
|
# duplicate
|
|
# def remove_metrics_from_selection():
|
|
# for o in bpy.context.selected_objects:
|
|
# is_possibly_glyph = is_mesh(o)
|
|
# if is_possibly_glyph:
|
|
# metrics = []
|
|
# for c in o.children:
|
|
# if is_metrics_object(c):
|
|
# metrics.append(c)
|
|
# completely_delete_objects(metrics)
|
|
|
|
def get_max_bound_box(bb_1, bb_2 = None):
|
|
if type(bb_2) == type(None):
|
|
bb_2 = bb_1
|
|
x_max = max(bb_1[4][0], bb_2[4][0])
|
|
x_min = min(bb_1[0][0], bb_2[0][0])
|
|
y_max = max(bb_1[3][1], bb_2[3][1])
|
|
y_min = min(bb_1[0][1], bb_2[0][1])
|
|
z_max = max(bb_1[1][2], bb_2[1][2])
|
|
z_min = min(bb_1[0][2], bb_2[0][2])
|
|
return [
|
|
mathutils.Vector((x_min, y_min, z_min)),
|
|
mathutils.Vector((x_min, y_min, z_max)),
|
|
mathutils.Vector((x_min, y_max, z_max)),
|
|
mathutils.Vector((x_min, y_max, z_min)),
|
|
|
|
mathutils.Vector((x_max, y_min, z_min)),
|
|
mathutils.Vector((x_max, y_min, z_max)),
|
|
mathutils.Vector((x_max, y_max, z_max)),
|
|
mathutils.Vector((x_max, y_max, z_min)),
|
|
]
|
|
|
|
# blender bound_box vertices
|
|
#
|
|
# 3------7.
|
|
# |`. | `. +y
|
|
# | `2------6 |
|
|
# | | | | |
|
|
# 0---|--4. | +--- +x
|
|
# `. | `.| `.
|
|
# `1------5 `+z
|
|
|
|
# why not [ [0] * 3 ] * 8
|
|
# https://stackoverflow.com/questions/2397141/how-to-initialize-a-two-dimensional-array-list-of-lists-if-not-using-numpy-in
|
|
def bound_box_as_array(bound_box):
|
|
array = [ [0] * 3 for i in range(8) ]
|
|
for i in range(0, len(bound_box)):
|
|
for j in range(0, len(bound_box[i])):
|
|
array[i][j] = bound_box[i][j]
|
|
return array
|
|
|
|
##
|
|
# @brief get_metrics_bound_box
|
|
# generates a metrics bounding box
|
|
# where x-width comes from bb
|
|
# and y-height + z-depth from bb_uebermetrics
|
|
#
|
|
# @param bb
|
|
# @param bb_uebermetrics
|
|
#
|
|
# @return metrics
|
|
def get_metrics_bound_box(bb, bb_uebermetrics):
|
|
metrics = [[0] * 3] * 8
|
|
# hurrays
|
|
if type(bb_uebermetrics) == bpy.types.bpy_prop_array:
|
|
metrics = bound_box_as_array(bb_uebermetrics)
|
|
else:
|
|
metrics = bb_uebermetrics.copy()
|
|
metrics[0][0] = bb[0][0]
|
|
metrics[1][0] = bb[1][0]
|
|
metrics[2][0] = bb[2][0]
|
|
metrics[3][0] = bb[3][0]
|
|
metrics[4][0] = bb[4][0]
|
|
metrics[5][0] = bb[5][0]
|
|
metrics[6][0] = bb[6][0]
|
|
metrics[7][0] = bb[7][0]
|
|
return metrics
|
|
|
|
def get_metrics_object(o):
|
|
if is_glyph(o):
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
return c
|
|
return None
|
|
|
|
def add_default_metrics_to_objects(objects=None, overwrite_existing=False):
|
|
if type(objects) == type(None):
|
|
objects=bpy.context.selected_objects
|
|
targets = []
|
|
reference_bound_box = None
|
|
for o in objects:
|
|
is_possibly_glyph = is_glyph(o)
|
|
if is_possibly_glyph:
|
|
metrics = []
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
metrics.append(c)
|
|
|
|
if len(metrics) == 0:
|
|
targets.append(o)
|
|
reference_bound_box = get_max_bound_box(o.bound_box, reference_bound_box)
|
|
elif len(metrics) >= 0 and overwrite_existing:
|
|
completely_delete_objects(metrics)
|
|
targets.append(o)
|
|
reference_bound_box = get_max_bound_box(o.bound_box, reference_bound_box)
|
|
else:
|
|
for m in metrics:
|
|
reference_bound_box = get_max_bound_box(m.bound_box, reference_bound_box)
|
|
for t in targets:
|
|
bound_box = get_metrics_bound_box(t.bound_box, reference_bound_box)
|
|
add_metrics_obj_from_bound_box(t, bound_box)
|
|
|
|
def remove_metrics_from_objects(objects=None):
|
|
if type(objects) == type(None):
|
|
objects=bpy.context.selected_objects
|
|
metrics = []
|
|
for o in objects:
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
metrics.append(c)
|
|
completely_delete_objects(metrics)
|
|
|
|
def align_metrics_of_objects_to_active_object(objects=None):
|
|
if type(objects) == type(None):
|
|
objects=bpy.context.selected_objects
|
|
if len(objects) == 0:
|
|
return "no objects selected"
|
|
|
|
# define the reference_bound_box
|
|
reference_bound_box = None
|
|
if type(bpy.context.active_object) == type(None):
|
|
return "no active_object, but align_to_active_object is True"
|
|
for c in bpy.context.active_object.children:
|
|
if is_metrics_object(c):
|
|
reference_bound_box = bound_box_as_array(c.bound_box)
|
|
break
|
|
if type(reference_bound_box) == type(None):
|
|
if not is_mesh(bpy.context.active_object):
|
|
return "active_object is not a mesh and does not have a metrics child"
|
|
reference_bound_box = bound_box_as_array(bpy.context.active_object.bound_box)
|
|
|
|
# do it
|
|
for o in objects:
|
|
is_possibly_glyph = is_glyph(o)
|
|
if is_possibly_glyph:
|
|
metrics = []
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
metrics.append(c)
|
|
|
|
bb = None
|
|
if len(metrics) == 0:
|
|
bb = get_metrics_bound_box(o.bound_box,
|
|
reference_bound_box)
|
|
else:
|
|
bb = get_metrics_bound_box(metrics[0].bound_box,
|
|
reference_bound_box)
|
|
if len(metrics) > 0:
|
|
completely_delete_objects(metrics)
|
|
|
|
add_metrics_obj_from_bound_box(o, bb)
|
|
return ""
|
|
|
|
def align_metrics_of_objects(objects=None):
|
|
if type(objects) == type(None):
|
|
objects=bpy.context.selected_objects
|
|
if len(objects) == 0:
|
|
return "no objects selected"
|
|
|
|
targets = []
|
|
reference_bound_box = None
|
|
for o in objects:
|
|
is_possibly_glyph = is_glyph(o)
|
|
if is_possibly_glyph:
|
|
metrics = []
|
|
for c in o.children:
|
|
if is_metrics_object(c):
|
|
metrics.append(c)
|
|
|
|
if len(metrics) == 0:
|
|
reference_bound_box = get_max_bound_box(o.bound_box,
|
|
reference_bound_box)
|
|
elif len(metrics) > 0:
|
|
reference_bound_box = get_max_bound_box(metrics[0].bound_box,
|
|
reference_bound_box)
|
|
targets.append(o)
|
|
for t in targets:
|
|
metrics = []
|
|
for c in t.children:
|
|
if is_metrics_object(c):
|
|
metrics.append(c)
|
|
bound_box = None
|
|
if len(metrics) == 0:
|
|
bound_box = get_metrics_bound_box(t.bound_box,
|
|
reference_bound_box)
|
|
else:
|
|
bound_box = get_metrics_bound_box(metrics[0].bound_box,
|
|
reference_bound_box)
|
|
completely_delete_objects(metrics)
|
|
|
|
add_metrics_obj_from_bound_box(t, bound_box)
|
|
return ""
|
|
|