# Tender docking carousel (three arms), game-ready A ceiling-hung turret with three booms 120° apart. Each boom carries a folding two-link hoist (shoulder, elbow, wrist, built like an industrial robot arm) and a clamp head with four claws that closes on the Tender's hatch collar. It is the modelled version of the carousel blockout in the Unreal port (`Vehicles/CarrierCarousel.cpp`, DESIGN_UE.md 3.5). **It is not in the Unreal project yet.** ## Files - `DockArm_Carousel.blend` (Blender 5.2): the assembly in the stowed pose. `DockArm_Parts` (the three shared meshes at their own origin) and `DockArm_Collision` (UCX hulls) are excluded from the view layer; `Studio` is camera and lights only. - `fbx/SM_DockArm_.fbx`: one file per unique mesh, at its own pivot, with its `UCX_` hulls. `fbx/DockArm_Carousel_Assembled.fbx`: the whole thing stowed, for a quick look. - `dockarm_layout.json`: pivots, parents and the hoist formula in Unreal units. `textures/T_DockArm_Decals_BaseColor.png`: decal atlas (arm numbers, hazard chevrons, generic labels; no brand names). `previews/`: renders. `build/`: the scripts. - Ore hose (added 2026-10-13): mesh `SM_DockArm_OreHose` in the .blend, `fbx/SK_DockArm_OreHose.fbx` (one arm's hose with its bones), `textures/T_DockArm_HoseWrinkle_Normal.png` and `T_DockArm_HoseFold_Mask.png`. See "Ore hose" below. ## What comes from the Unreal blockout | Blockout | Model | |---|---| | hub at `ArmsRoot`, bay ceiling at z 1.20 | origin = hub axis at carrier z 0; the mount flange sits on z 1.20, nothing is higher | | three arms 120° apart, cradle radius 6.0 | three booms; tips at radius 6.0007, turned 0.86° so the head is centred on the hatch collar (9 cm ahead of the Tender origin) | | arm 1 over the pool at hub angle 0 (Unreal +Y) | `Arm1` points to Blender −Y | | Tender origin z −1.0 STOWED, −6.3 BOARD | links lie flat beside the boom at STOWED; `lower_m` 5.3 is BOARD | | clamp plate on the hatch collar | head with landing feet on the collar flange and four claws on the collar wall | | carriage moved in z, mast scaled | **changed:** a two-link knee hoist. The blockout leaves 9 cm between beam and clamp when stowed and 0.64 m under the ceiling, so a rigid mast cannot exist there | ## Scale, axes, pivots - 1 unit = 1 m, Z up. Unreal = (x, −y, z) × 100 cm. Span 13.4 m across the heads, 1.0 m tall stowed, reaches 5.3 m down. - Moving parts are separate objects with the origin on their axis, scale 1: | Mesh | Count | Pivot | Motion | |---|---|---|---| | `SM_DockArm_Mount` | 1 | hub axis | fixed to the carrier | | `SM_DockArm_Turret` (drum + three booms) | 1 | hub axis | yaw = carousel angle | | `SM_DockArm_UpperLink` (both side plates) | 3 | shoulder axis at the boom tip (6.0007, 0, 0.94 in the boom frame) | pitch | | `SM_DockArm_LowerLink` (both plates, elbow servos) | 3 | elbow axis, 3.0 m from the shoulder | pitch | | `SM_DockArm_Head` | 3 | wrist axis, 3.0 m from the elbow, 1.94 m above the Tender origin | pitch (stays level) | | `SM_DockArm_Claw` | 12 | its hinge under the head rim, at 45/135/225/315° | swings out 25° to open | - Hoist: `phi = asin((StowedZ − TenderOriginZ) / 6.0)`. In Blender: upper link Y rotation −phi, lower +2·phi, head −phi (Unreal pitch +phi, −2·phi, +phi). 0 at STOWED, 62.05° at BOARD. The wrist stays exactly under the shoulder. - In the .blend the empties `Arm1..3` have two custom properties that drive this: `lower_m` (0..5.3) and `claw_open` (0..1). ## Mesh notes - No booleans. Quads plus triangle fans on the ends of turned parts and a few bevel corners; no n-gons, no open edges, no zero-area faces, outward normals. 7 unique meshes with the hose, 27.0k triangles; 60.2k for the whole assembly (26 mesh objects). The hose is an all-quad tube, open at its two ends (they sit inside the port and the coupling). - UVs on every mesh (smart project per mesh; hand-set on the decal quads and the hose). 9 materials: flat PBR values, the decal atlas, and the hose film with its two textures. **No baked texture sets.** - Collision: `UCX__NN` convex boxes, coarse (the blockout has no collision on the carousel at all). ## Checked - No two parts intersect in 12 poses (0, 0.25, 1.0, 2.5, 4.0, 5.3 m lowered × claws closed/open), all three arms present. - Against the real Kazah mesh (`models/Kazah-stern-cavity-fixedv4.blend`, linked only for the check and the renders): no crossing triangles; smallest gaps 17 mm (landing feet over the flange) and 20 mm (claw pads to the collar wall); links stay 0.24 m or more from the hull; the canopy opened 55° clears the arm at BOARD. - Every FBX re-imported and compared with the .blend (bounds, triangle counts, materials, hulls, placement): `build/verify_fbx.py`. ## Ore hose (added 2026-10-13) A wrinkled, mostly see-through film duct (22 cm) per arm for unloading cargo: the look of the kit's fabric-coated flexible duct (`EP_SM_Ceiling_CoatedPipe`, the pieces named `Truss_A..` in the vessel interior), but its own mesh and its own textures, so it can bend and be see-through. - **Route:** port on the hub's underside pod → up beside the boom root → along the top of the boom → a flat U-turn round the tip → back over the shoulder → down the upper link → U-turn round the elbow → down the lower link → through the wrist → coupling and duct into the housing on the head. - **Beside the arm, not in the middle.** Folded, the boom lies exactly between the link plates and the head sits 4 cm under the boom tip, so a hose on the centre line has nowhere to be. It runs in one plane 0.86 m to the stern side of the arm (away from the Tender's canopy). Nothing of the arm ever enters that plane, so the hose can pass every joint there. - **It bends with the joints.** Every ring of the hose is carried by one bone. Along boom and links it rides rigidly; at shoulder, elbow and wrist it is carried by fan bones (8, 9 and 4 of them) that each take an even share of the joint's angle. Lowered to BOARD the loop round the elbow closes from 180° to 56° (the film bunches up), and the piece over the shoulder stretches by about a third. - **Unrigged model:** each arm has a small armature `ArmN_HoseRig` whose bones copy the arm's objects, so the hose follows `lower_m`. **Rigged model:** the fan bones are part of `DockArm_Rig` (`ArmN_HoseS1..8`, `_HoseE1..9`, `_HoseW1..4`, hidden bone collection). - **Added to the parts:** port stub on the pod, clips and saddles on the boom, a bar at the tip, three stand-offs on the upper link, three hangers on the lower link, flange and duct on the head. - **Material `M_DockArm_OreHose`:** alpha 0.20 in the film to 0.55 in the folds, so mostly see-through. The normal map is OpenGL (flip green for Unreal). Clamp bands are steel. - **Checked:** no contact with any part away from its clamps and fittings, and none of the hose with itself, in the 12 poses; 0.46 m or more from the Tender, also with the canopy open; rigged and unrigged hose agree to 0.006 mm. - **Needs headroom.** The hose tops out at z 1.37: above the old blockout bay's ceiling (1.20). In the vessel interior the roof is far above, but the hull closes in to 6.49 m from the hub at that height; the tip U-turn is kept inside 6.39 m, which leaves about 10 cm when an arm points abeam. - **Putting it into another scene** that holds an appended copy of the unrigged carousel, without rebuilding that scene: `build/scene_add_hose.py` (`add_ore_hose()`, `check_ore_hose()`, `remove_ore_hose()`). ## Rigged version (added 2026-10-12) `DockArm_Carousel_Rigged.blend` is the same model on one armature, `DockArm_Rig`: a bone on every joint, each part skinned to its bone with weight 1 (23 part meshes with one vertex group each, plus the three hoses). `fbx/SK_DockArm_Carousel.fbx` is the skeletal export (86 bones: 23 joints and 63 hose fan bones), `dockarm_rig.json` lists the bones in Unreal units. | Bone | Joint | Turns about | Positive angle | |---|---|---|---| | `Root` | (carries the ceiling mount) | free | | | `Turret` | hub | its local Y (it points up) | counter-clockwise seen from above | | `ArmN_Upper` | shoulder, at the boom tip | local X | lowers the hoist | | `ArmN_Lower` | elbow | local X | lowers the hoist | | `ArmN_Head` | wrist | local X | lowers the hoist (keeps the head level) | | `ArmN_Claw1..4` | claw hinges | local X | opens, 0..25° | - Rest pose = stowed. Every joint bone is locked to its one axis and has a range limit. Hoist: Upper +phi, Lower +2·phi, Head +phi, with `phi = asin(lowered / 6.0)`. - Pose Mode handles (bone collection `Controls`): the square outboard of each head, `CTRL_ArmN_Hoist`, moves down 0..5.3 m and unfolds the arm with the head level; the ring beside it, `CTRL_ArmN_Claws`, turns 0..25° and opens the four claws. Set the square's custom property `auto` to 0 to pose shoulder, elbow and wrist by hand. - The hoist is not an IK chain. Stowed, the wrist lies exactly on the shoulder axis, the one case a two-bone IK cannot solve (tried: 55 mm off at rest, metres off with the turret turned). Six hidden helper bones (`Mechanism`) carry phi and 2·phi from drivers and the joints copy them, so the motion is exact. The drivers are simple expressions (no auto-run needed). - Checked: with the joints set by hand and with the handles, every part lands within 0.006 mm of the unrigged model in 11 poses including a turned turret (the hoses included); the saved file re-opened and posed; the FBX re-imported (86 bones, parents, positions, 26 meshes, weights). `build/verify_rig.py`. - The skeletal FBX holds the deform bones only (no handles), is written in centimetres with no scale on any node, and is turned like the part files. **It has not been imported into Unreal.** ## Rebuild ``` python build\make_decals.py textures blender -b --python build\build_dockarm.py # model, .blend, layout json, fbx blender -b --python build\verify_fbx.py blender -b --python build\render_previews.py # fit check against the Kazah + previews blender -b --python build\rig_dockarm.py # rigged .blend, rig json, skeletal fbx (reads the model file) blender -b --python build\verify_rig.py blender -b --python build\render_rig_preview.py python build\compose_rig_preview.py previews ``` The hose lives in `build\hose.py` (path, brackets, mesh, material) and `build\hose_rig_setup.py` (its bones); both are run by `build_dockarm.py`. ## Open - The ore hose is empty: no ore moves in it yet. In Unreal its film needs a translucent material, and the hose is either the skeletal mesh (bones driven as in `dockarm_layout.json` / `dockarm_rig.json`) or a spline mesh laid along the same path. - In the vessel interior the hose clears the hull by about 10 cm at its top when an arm points abeam. - Putting it into Unreal: `CarrierCarousel.cpp` drives one carriage z and a mast scale per arm; with this model it sets three pitches per arm instead (formula above). The clearance obstacle list there would also get the booms. - No LODs, no baked textures, emissive values are placeholders. Per-part FBX use the project's export pair (forward −Y, up Z, data turned −90° about Z; import with "convert scene" + "force front X axis", as `Tools/ue/assets_12_arm.py` does).