"""Opens and closes the pressure hatch from one slider: PressureHatch_Root["open"] (0 shut and locked, 1 open). 0.00 - 0.30 the cam ring on the leaf's back turns 20 deg and the eight dogs slide 0.08 m inward, out of the groove behind the frame's seat lip 0.35 - 1.00 the leaf swings 105 deg about the hinge pin (eased in and out), out toward the front Drivers are simple expressions (this Blender has Python auto-run off; simple expressions run without Python). Timeline: shut at frame 1, open at 90, held to 150, shut again at 240 (30 fps). Action "PressureHatch | OPEN_CLOSE". Each moving part keeps its shut transform in ["closed_location"] / ["closed_rotation"] (the project's convention). """ import bpy, math ROOT = "PressureHatch_Root" D0, D1 = 0.00, 0.30 S0, S1 = 0.35, 1.00 OPEN_DEG, CAM_DEG, DOG_TRAVEL = 105.0, -20.0, 0.08 def ramp(a, b): return "min(max((open-%.4f)/%.4f,0),1)" % (a, b - a) def eased(a, b): r = ramp(a, b) return "%s*%s*(3-2*%s)" % (r, r, r) def drive(ob, path, index, expr, root): ob.driver_remove(path, index) fc = ob.driver_add(path, index) d = fc.driver d.type = 'SCRIPTED' v = d.variables.new() v.name = "open" v.type = 'SINGLE_PROP' v.targets[0].id_type = 'OBJECT' v.targets[0].id = root v.targets[0].data_path = '["open"]' d.expression = expr return d def rig(): root = bpy.data.objects[ROOT] root["open"] = 0.0 ui = root.id_properties_ui("open") ui.update(min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.0, description="0 shut and locked, 1 open") leaf = bpy.data.objects["SM_PressureHatch_Leaf"] cam = bpy.data.objects["SM_PressureHatch_Cam"] report = [] for ob in (leaf, cam): ob.location = ob["closed_location"] ob.rotation_euler = ob["closed_rotation"] d = drive(leaf, "rotation_euler", 2, "%s*%.6f" % (eased(S0, S1), math.radians(OPEN_DEG)), root) report.append((leaf.name, d.is_simple_expression, d.is_valid)) d = drive(cam, "rotation_euler", 1, "%s*%.6f" % (eased(D0, D1), math.radians(CAM_DEG)), root) report.append((cam.name, d.is_simple_expression, d.is_valid)) for ob in sorted((o for o in bpy.data.objects if o.name.startswith("SM_PressureHatch_Dog_")), key=lambda o: o.name): phi = math.radians(ob["phi_deg"]) cx, cy, cz = ob["closed_location"] ob.location = (cx, cy, cz) s = eased(D0, D1) d1 = drive(ob, "location", 0, "%.6f-%.6f*%s" % (cx, DOG_TRAVEL * math.cos(phi), s), root) d2 = drive(ob, "location", 2, "%.6f-%.6f*%s" % (cz, DOG_TRAVEL * math.sin(phi), s), root) report.append((ob.name, d1.is_simple_expression and d2.is_simple_expression, d1.is_valid and d2.is_valid)) sc = bpy.context.scene sc.render.fps = 30 sc.frame_start, sc.frame_end = 1, 240 root.animation_data_clear() for f, v in ((1, 0.0), (90, 1.0), (150, 1.0), (240, 0.0)): root["open"] = v root.keyframe_insert('["open"]', frame=f) act = root.animation_data.action act.name = "PressureHatch | OPEN_CLOSE" try: from bpy_extras.anim_utils import action_get_channelbag_for_slot for fc in action_get_channelbag_for_slot(act, root.animation_data.action_slot).fcurves: for kp in fc.keyframe_points: kp.interpolation = 'LINEAR' except Exception as ex: report.append(("interpolation", str(ex), False)) root["open"] = 0.0 sc.frame_set(1) return report def frame_for(v): """the timeline frame that shows open = v on the way open (frames 1..90)""" return 1.0 + 89.0 * float(v) def set_open(v): """set the state for checks: drop to the frame (the keys drive the slider), then evaluate""" sc = bpy.context.scene f = frame_for(v) sc.frame_set(int(math.floor(f)), subframe=f - math.floor(f)) bpy.context.view_layer.update()