"""Ore hose rig for the UNRIGGED carousel (the one whose arms are plain objects turned by drivers). One small armature per arm. Its bones are 'identity' bones (rest rotation = none), so a bone that copies an object's world transform deforms the hose exactly as that object moves: Hub rigid with the turret (the armature object is a child of the ArmN empty): the port to the boom joint Boom, Upper, Lower, Head copy the arm's four moving objects (the boom pitches on its joint since 2026-10-16) R1..R6 / S1..S8 / E1..E9 / W1..W4 fan bones at the boom joint / shoulder / elbow / wrist: each takes i/n of its joint's rotation, so the hose bends in even steps round the joint instead of kinking Used by build_dockarm.py; rig_dockarm.py makes the same fans as bones of DockArm_Rig. """ import bpy from mathutils import Vector FAN_STEPS = {"R": 7, "S": 9, "E": 10, "W": 5} # steps over the bend; fan bones = steps - 1 HOSE_GROUPS = (["Hub", "Boom", "Upper", "Lower", "Head"] + ["R%d" % i for i in range(1, FAN_STEPS["R"])] + ["S%d" % i for i in range(1, FAN_STEPS["S"])] + ["E%d" % i for i in range(1, FAN_STEPS["E"])] + ["W%d" % i for i in range(1, FAN_STEPS["W"])]) def make_hose_armature(name, J, S, E, W): """Armature data in the boom frame (rest = stowed). J, S, E, W: boom joint, shoulder, elbow, wrist pivots.""" ad = bpy.data.armatures.new(name) tmp = bpy.data.objects.new("_tmp_hose_rig", ad) bpy.context.scene.collection.objects.link(tmp) vl = bpy.context.view_layer for o in vl.objects: o.select_set(False) tmp.select_set(True) vl.objects.active = tmp bpy.ops.object.mode_set(mode='EDIT') eb = ad.edit_bones def ibone(n, head, parent=None, size=0.12): b = eb.new(n) b.head = Vector(head) b.tail = Vector(head) + Vector((0, size, 0)) # along +Y with Z up = identity rest rotation b.align_roll(Vector((0, 0, 1))) if parent: b.parent = eb[parent] return b ibone("Hub", (J[0] - 0.6, 0, J[2] - 0.3), size=0.2) ibone("Boom", J, size=0.2) ibone("Upper", S, size=0.2) ibone("Lower", E, size=0.2) ibone("Head", W, size=0.2) for i in range(1, FAN_STEPS["R"]): ibone("R%d" % i, J, "Hub") for i in range(1, FAN_STEPS["S"]): ibone("S%d" % i, S, "Boom") for i in range(1, FAN_STEPS["E"]): ibone("E%d" % i, E, "Upper") for i in range(1, FAN_STEPS["W"]): ibone("W%d" % i, W, "Lower") bpy.ops.object.mode_set(mode='OBJECT') for b in ad.bones: r = b.matrix_local.to_3x3() assert all(abs(r[i][j] - (1.0 if i == j else 0.0)) < 1e-5 for i in range(3) for j in range(3)), ("not an identity bone", b.name) bpy.data.objects.remove(tmp) ad.display_type = 'STICK' return ad def setup_hose_rig(arm_empty, boom, upper, lower, head, armature_data, hose_mesh, collection, prefix): """Armature object + hose object for one arm. arm_empty: the boom frame; boom/upper/lower/head: its moving objects.""" rig = bpy.data.objects.new(prefix + "_HoseRig", armature_data) collection.objects.link(rig) rig.parent = arm_empty rig.show_in_front = False rig.hide_render = True bpy.context.view_layer.update() # the pose of a new armature object exists only after this P = rig.pose.bones for bone, target in (("Boom", boom), ("Upper", upper), ("Lower", lower), ("Head", head)): c = P[bone].constraints.new('COPY_TRANSFORMS') c.name = "Follow " + target.name c.target = target for key, src in (("R", "Boom"), ("S", "Upper"), ("E", "Lower"), ("W", "Head")): n = FAN_STEPS[key] for i in range(1, n): c = P["%s%d" % (key, i)].constraints.new('COPY_ROTATION') c.name = "%d/%d of the joint" % (i, n) c.target = rig c.subtarget = src c.influence = i / n hose = bpy.data.objects.new(prefix + "_OreHose", hose_mesh) collection.objects.link(hose) hose.parent = rig for g in HOSE_GROUPS: if g not in hose.vertex_groups: hose.vertex_groups.new(name=g) hose.modifiers.new("Armature", 'ARMATURE').object = rig return rig, hose