From cd99362bb1c9b3ceef9f334dff224e18d516120c Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Thu, 5 Jun 2025 10:59:14 +0200 Subject: [PATCH] [feature] spline animation --- butils.py | 105 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/butils.py b/butils.py index bb3bc1d..30b0a47 100644 --- a/butils.py +++ b/butils.py @@ -221,26 +221,26 @@ def get_hook_modifiers(blender_object: bpy.types.Object): return [m for m in blender_object.modifiers if m.type == "HOOK"] -class BezierSplinePoint: +class HookBezierSplinePoint: def __init__( self, - co: mathutils.Vector, handle_left: mathutils.Vector, + co: mathutils.Vector, handle_right: mathutils.Vector, ): - self.co: mathutils.Vector = co - self.handle_left: mathutils.Vector = handle_left - self.handle_right: mathutils.Vector = handle_right + self.handle_left: mathutils.Vector = mathutils.Vector(handle_left) + self.co: mathutils.Vector = mathutils.Vector(co) + self.handle_right: mathutils.Vector = mathutils.Vector(handle_right) -class BezierSpline: +class HookBezierSpline: def __init__( self, n: int, use_cyclic_u: bool, resolution_u: int, ): - self.bezier_points = [BezierSplinePoint] * n + self.bezier_points = [HookBezierSplinePoint] * n self.use_cyclic_u: int = use_cyclic_u self.resolution_u: int = resolution_u self.beziers: [] @@ -249,34 +249,31 @@ class BezierSpline: def calc_length(self, resolution) -> float: # ignore resolution when accessing length to imitate blender function - print(f"{self.total_length=}") return self.total_length -class BezierData: +class HookBezierData: def __init__(self, n): - self.splines = [BezierSpline] * n + self.splines = [HookBezierSpline] * n -class BezierCurve: +class HookBezierCurve: def __init__(self, blender_curve: bpy.types.Object, resolution_factor=1.0): - self.data = BezierData(len(blender_curve.data.splines)) + self.data = HookBezierData(len(blender_curve.data.splines)) i = 0 hooks = get_hook_modifiers(blender_curve) - print(f"{blender_curve.name=} =============================================") for si, blender_spline in enumerate(blender_curve.data.splines): - self.data.splines[si] = BezierSpline( + self.data.splines[si] = HookBezierSpline( len(blender_spline.bezier_points), blender_spline.use_cyclic_u, blender_spline.resolution_u, ) for pi, blender_bezier_point in enumerate(blender_spline.bezier_points): - self.data.splines[si].bezier_points[pi] = BezierSplinePoint( - blender_bezier_point.co, + self.data.splines[si].bezier_points[pi] = HookBezierSplinePoint( blender_bezier_point.handle_left, + blender_bezier_point.co, blender_bezier_point.handle_right, ) - print(pi) for hook in hooks: hook_co = False hook_handle_left = False @@ -293,40 +290,57 @@ class BezierCurve: blender_curve.matrix_world.inverted() @ hook.object.matrix_world.translation ) - print(f"co {location=}") self.data.splines[si].bezier_points[pi].co = ( self.data.splines[si] .bezier_points[pi] .co.lerp(location, hook.strength) ) - # if hook_handle_left: - # location = ( - # hook.object.matrix_world.translation - # - blender_curve.matrix_world.translation - # ) + mathutils.Vector((-1, 0, 0)) - # print(f"handle_left {location=}") - # self.data.splines[si].bezier_points[pi].handle_left = ( - # self.data.splines[si] - # .bezier_points[pi] - # .handle_left.lerp(location, hook.strength) - # ) - # if hook_handle_right: - # location = ( - # hook.object.matrix_world.translation - # - blender_curve.matrix_world.translation - # ) - # self.data.splines[si].bezier_points[pi].handle_right = ( - # self.data.splines[si] - # .bezier_points[pi] - # .handle_right.lerp(location, hook.strength) - # ) + if hook_handle_left: + location_left = location + ( + self.data.splines[si].bezier_points[pi].co + - self.data.splines[si].bezier_points[pi].handle_left + ) + self.data.splines[si].bezier_points[pi].handle_left = ( + self.data.splines[si] + .bezier_points[pi] + .co.lerp(location_left, hook.strength) + ) + if hook_handle_right: + location_right = location + ( + self.data.splines[si].bezier_points[pi].co + - self.data.splines[si].bezier_points[pi].handle_right + ) + self.data.splines[si].bezier_points[pi].handle_right = ( + self.data.splines[si] + .bezier_points[pi] + .co.lerp(location, hook.strength) + ) + elif hook_handle_left: + location = ( + blender_curve.matrix_world.inverted() + @ hook.object.matrix_world.translation + ) + self.data.splines[si].bezier_points[pi].handle_left = ( + self.data.splines[si] + .bezier_points[pi] + .handle_left.lerp(location, hook.strength) + ) + elif hook_handle_right: + location = ( + blender_curve.matrix_world.inverted() + @ hook.object.matrix_world.translation + ) + self.data.splines[si].bezier_points[pi].handle_right = ( + self.data.splines[si] + .bezier_points[pi] + .handle_right.lerp(location, hook.strength) + ) i += 1 ( self.data.splines[si].beziers, self.data.splines[si].lengths, self.data.splines[si].total_length, ) = get_real_beziers_and_lengths(self.data.splines[si], resolution_factor) - print(f"total length {self.data.splines[si].total_length}") def get_real_beziers_and_lengths(bezier_spline_obj, resolution_factor): @@ -391,7 +405,7 @@ def calc_point_on_bezier_spline( if p.handle_left == p.co and len(bezier_spline_obj.bezier_points) > 1: beziers, lengths, total_length = ( get_real_beziers_and_lengths(bezier_spline_obj, resolution_factor) - if not isinstance(bezier_spline_obj, BezierSpline) + if not isinstance(bezier_spline_obj, HookBezierSpline) else ( bezier_spline_obj.beziers, bezier_spline_obj.lengths, @@ -411,7 +425,7 @@ def calc_point_on_bezier_spline( beziers, lengths, total_length = ( get_real_beziers_and_lengths(bezier_spline_obj, resolution_factor) - if not isinstance(bezier_spline_obj, BezierSpline) + if not isinstance(bezier_spline_obj, HookBezierSpline) else ( bezier_spline_obj.beziers, bezier_spline_obj.lengths, @@ -460,16 +474,15 @@ def calc_point_on_bezier_curve( output_spline_index=False, resolution_factor=1.0, ): - # bezier_curve = BezierCurve(bezier_curve_obj) - # curve = bezier_curve.data - curve = bezier_curve_obj.data + bezier_curve = HookBezierCurve(bezier_curve_obj) + curve = bezier_curve.data + # 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) - print(f"{utils.LINE()} {length=}") 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