Skip to content

Instantly share code, notes, and snippets.

@ncthbrt
Created October 7, 2025 11:46
Show Gist options
  • Select an option

  • Save ncthbrt/171301f16773f303157a519f88e487b8 to your computer and use it in GitHub Desktop.

Select an option

Save ncthbrt/171301f16773f303157a519f88e487b8 to your computer and use it in GitHub Desktop.
Helpers for Snake Rig
import bpy
import mathutils
import mathutils.geometry
import math
from bpy.app.handlers import persistent
from math import cos
def q_sl(a, b, t, axis = (0, 0, 1), parent = None):
bb = b.matrix.to_quaternion()
aa = a.matrix.to_quaternion()
tt = t
if parent == None:
parent = b.parent
(cross, _dummy) = aa.cross(bb).to_axis_angle()
if cross.dot((parent.matrix * mathutils.Matrix.Translation(axis)).translation - parent.matrix.translation) > 0:
bb.negate()
dot = aa.dot(bb)
theta = math.acos(dot)
sin_theta = math.sin(theta)
result = aa * (math.sin((1-tt)*theta) / sin_theta) + bb * (math.sin(tt*theta)/sin_theta)
result.normalize()
return result
def spline_eval_lerp(obj, slf, curve_world_matrix, start_p, end_p, frac):
result = math.pow(1.0 - frac, 3) * start_p.co
result = result + (3 * math.pow(1.0 - frac, 2) * frac * start_p.handle_right)
result = result + (3 * (1-frac) * math.pow(frac, 2))*end_p.handle_left
result = result + math.pow(frac, 3) * end_p.co
result = obj.convert_space(pose_bone = slf, matrix = curve_world_matrix, from_space = 'WORLD', to_space = 'LOCAL_WITH_PARENT') @ result
return result
def spline_eval(obj, slf, curve_obj, interpolant, depsgraph):
curve = curve_obj.`(depsgraph, apply_modifiers=True)
spline = curve.splines[0]
is_iterator = False
iterator = None
count = len(spline.bezier_points)
points = list(spline.bezier_points)
try:
iterator = iter(interpolant)
is_iterator = True
except TypeError:
iterator = iter([interpolant])
results = list()
for interpolant in iterator:
frac = interpolant * count
start_idx = math.floor(frac)
frac = frac - start_idx
end_idx = min(count - 1, start_idx + 1)
start_p = points[start_idx]
end_p = points[end_idx]
results.append(spline_eval_lerp(obj, slf, curve_obj.matrix_world, start_p, end_p, frac))
curve = None
start_p = None
end_p = None
points = None
spline = None
depsgraph = None
curve_obj.to_curve_clear()
if is_iterator:
return results
else:
return results[0]
def tube_bone(obj, slf, depsgraph):
angle = slf["angle"]
z_curve = slf["z_curve"]
y_curve = slf["y_curve"]
interpolant = slf["interpolant"]
x_reference = z_curve["reference"]
z_reference = y_curve["reference"]
y_reference = x_reference.cross(z_reference)
reference = mathutils.Vector((math.cos(angle), 0, math.sin(angle)))
reference_linear_transform = mathutils.Matrix((x_reference, y_reference, z_reference))
reference_0_1 = reference_linear_transform.transposed().inverted() @ reference
# top_curve, bottom_curve, left_curve, right_curve,
# if angle < (math.pi / 2.0):
# z_curve = right_curve
# y_curve = top_curve
# elif angle < math.pi:
# z_curve = left_curve
# y_curve = top_curve
# elif angle < math.pi + (math.pi/2.0):
# z_curve = left_curve
# y_curve = bottom_curve
# else:
# z_curve = right_curve
# y_curve = bottom_curve
x_current = spline_eval(obj, slf, z_curve, interpolant[2], depsgraph)
z_current = spline_eval(obj, slf, y_curve, interpolant[1], depsgraph)
y_current = x_current.cross(z_current)
current_linear_transform = mathutils.Matrix((x_current, y_current, z_current))
current_vector = current_linear_transform.transposed() @ reference_0_1
resultant_rotation = current_vector.to_track_quat('Y', 'Z')
rest_matrix = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix(), from_space = 'LOCAL_WITH_PARENT', to_space = 'POSE')
scale = current_vector.length / ((rest_matrix @ (slf.matrix.inverted() @ slf.tail)) - slf.head).length
result = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix.LocRotScale(slf.head, resultant_rotation, mathutils.Vector((1, scale, 1))), from_space = 'POSE', to_space = 'LOCAL_WITH_PARENT')
return result
def project_bone(obj, slf, depsgraph):
z_curve = slf["z_curve"]
y_curve = slf["y_curve"]
x_curve = slf["x_curve"]
interpolant = slf["interpolant"]
x_current = spline_eval(obj, slf, z_curve, interpolant[2], depsgraph)
z_current = spline_eval(obj, slf, y_curve, interpolant[1], depsgraph)
y_current = x_current.cross(z_current)
reference, x_reference, z_reference = spline_eval(obj, slf.parent, x_curve, [interpolant[0], round(interpolant[0]), 0.5, 1.0], depsgraph)
y_reference = x_reference.cross(z_reference)
reference_linear_transform = mathutils.Matrix((x_reference, y_reference, z_reference))
reference_0_1 = reference_linear_transform.transposed().inverted() @ reference
current_linear_transform = mathutils.Matrix((x_current, y_current, z_current))
current_vector = current_linear_transform.transposed() @ reference_0_1
resultant_rotation = current_vector.to_track_quat('Y', 'Z')
rest_matrix = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix(), from_space = 'LOCAL_WITH_PARENT', to_space = 'POSE')
scale = current_vector.length / ((rest_matrix @ (slf.matrix.inverted() @ slf.tail)) - slf.head).length
result = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix.LocRotScale(slf.head, resultant_rotation, mathutils.Vector((1, scale, 1))), from_space = 'POSE', to_space = 'LOCAL_WITH_PARENT')
return result
def point_at_bezier(obj, slf, depsgraph):
interpolant = slf["interpolant"]
curve = slf["curve"]
tail = spline_eval(obj, slf, curve, interpolant, depsgraph)
resultant_rotation = tail.to_track_quat('Y', 'Z')
rest_matrix = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix(), from_space = 'LOCAL_WITH_PARENT', to_space = 'POSE')
scale = tail.length / ((rest_matrix @ (slf.matrix.inverted() @ slf.tail)) - slf.head).length
result = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix.LocRotScale(slf.head, resultant_rotation, mathutils.Vector((1, scale, 1))), from_space = 'POSE', to_space = 'LOCAL_WITH_PARENT')
return result
def location_at_bezier(obj, slf, depsgraph):
interpolant = slf["interpolant"]
curve = slf["curve"]
location = spline_eval(obj, slf, curve, interpolant, depsgraph)
rest_matrix = obj.convert_space(pose_bone = slf, matrix = mathutils.Matrix(), from_space = 'LOCAL_WITH_PARENT', to_space = 'POSE')
result = mathutils.Matrix.Translation(location)
return result
@persistent
def load_handler(dummy):
bpy.app.driver_namespace['q_sl'] = q_sl
bpy.app.driver_namespace['project_bone'] = project_bone
bpy.app.driver_namespace['tube_bone'] = tube_bone
bpy.app.driver_namespace['point_at_bezier'] = point_at_bezier
bpy.app.driver_namespace['location_at_bezier'] = location_at_bezier
def register():
print("--", "register:", __file__)
load_handler(None)
bpy.app.handlers.load_post.append(load_handler)
def unregister():
bpy.app.handlers.load_post.remove(load_handler)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment