From d081d1d42d752ba9e9f3171749f4b6bc524b0989 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Mon, 1 Jul 2024 10:25:17 +0200 Subject: [PATCH] align rotations --- __init__.py | 2 + butils.py | 181 ++++++++++++++++++++++++++++++++++++++++++++++-- common/utils.py | 36 ++++++++-- 3 files changed, 209 insertions(+), 10 deletions(-) diff --git a/__init__.py b/__init__.py index 23d08cd..88c9685 100644 --- a/__init__.py +++ b/__init__.py @@ -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 diff --git a/butils.py b/butils.py index 42afa54..c3daade 100644 --- a/butils.py +++ b/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)] diff --git a/common/utils.py b/common/utils.py index 7843db9..9ebb409 100644 --- a/common/utils.py +++ b/common/utils.py @@ -1,6 +1,9 @@ import time import datetime +from mathutils import ( + Vector, + ) def get_timestamp(): return datetime.datetime \ @@ -10,9 +13,32 @@ def get_timestamp(): def mapRange(in_value, in_min, in_max, out_min, out_max, clamp=False): output = out_min + ((out_max - out_min) / (in_max - in_min)) * (in_value - in_min) if clamp: - if out_min < out_max: - return min(out_max, max(out_min, output)) - else: - return max(out_max, min(out_min, output)) + if out_min < out_max: + return min(out_max, max(out_min, output)) + else: + return max(out_max, min(out_min, output)) else: - return output + 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