align rotations
This commit is contained in:
parent
a5e45c7439
commit
d081d1d42d
3 changed files with 209 additions and 10 deletions
|
@ -446,6 +446,8 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
|||
ob.constraints["Follow Path"].use_curve_follow = True
|
||||
ob.constraints["Follow Path"].forward_axis = "FORWARD_X"
|
||||
ob.constraints["Follow Path"].up_axis = "UP_Y"
|
||||
# samplecurve = nodes.new(type="GeometryNodeSampleCurve")
|
||||
|
||||
# butils.ShowMessageBox("WHAT","INFO","I don't really know what you mean, lsaidry")
|
||||
else:
|
||||
offset.x = advance
|
||||
|
|
181
butils.py
181
butils.py
|
@ -8,6 +8,11 @@ if "Font" in locals():
|
|||
else:
|
||||
from .common import Font
|
||||
|
||||
if "utils" in locals():
|
||||
importlib.reload(utils)
|
||||
else:
|
||||
from .common import utils
|
||||
|
||||
def apply_all_transforms(obj):
|
||||
mb = obj.matrix_basis
|
||||
if hasattr(obj.data, "transform"):
|
||||
|
@ -25,13 +30,10 @@ def get_parent_collection_names(collection, parent_names):
|
|||
|
||||
# Ensure it's a curve object
|
||||
# TODO: no raising, please
|
||||
def get_curve_length(obj, num_samples = 100):
|
||||
def get_curve_length(curve_obj, num_samples = 100):
|
||||
total_length = 0
|
||||
|
||||
if obj.type != 'CURVE':
|
||||
raise TypeError("The selected object is not a curve")
|
||||
|
||||
curve = obj.data
|
||||
curve = curve_obj.data
|
||||
|
||||
# Loop through all splines in the curve
|
||||
for spline in curve.splines:
|
||||
|
@ -39,6 +41,175 @@ def get_curve_length(obj, num_samples = 100):
|
|||
|
||||
return total_length
|
||||
|
||||
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
|
||||
|
||||
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().col[2]).normalized()
|
||||
|
||||
if rotation_axis.length < 1e-6:
|
||||
# This is now guaranteed to not be zero
|
||||
rotation_axis = (-(old_axis) + mathutils.Matrix().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(0, 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,
|
||||
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:
|
||||
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)]
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
|
||||
import time
|
||||
import datetime
|
||||
from mathutils import (
|
||||
Vector,
|
||||
)
|
||||
|
||||
def get_timestamp():
|
||||
return datetime.datetime \
|
||||
|
@ -16,3 +19,26 @@ def mapRange(in_value, in_min, in_max, out_min, out_max, clamp=False):
|
|||
return max(out_max, min(out_min, output))
|
||||
else:
|
||||
return output
|
||||
|
||||
# # 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
|
||||
|
||||
|
||||
# # Evaluate the unit tangent on a bezier curve for t
|
||||
# def evaluateBezierTangent(p1, h1, h2, p2, t):
|
||||
# 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()
|
||||
|
||||
# def calculateBezierLength(p1, h1, h2, p2, resolution=20):
|
||||
# step = 1/resolution
|
||||
# previous_p = p1
|
||||
# length = 0
|
||||
# for i in range(0, resolution):
|
||||
# t = (i + 1) * step
|
||||
# p = evaluateBezierPoint(p1, h1, h2, p2, t)
|
||||
# length += p.distance(previous_p)
|
||||
# previous_p = p
|
||||
# return length
|
||||
|
|
Loading…
Reference in a new issue