"""
Build a self-contained torso + arm + Shadow-Hand scene for demo2 pick-and-place.

The /demo2 Shadow Hand is fixed-base (forearm bolted to the world), so it can
only grasp in place. This grafts the hand onto a purpose-built torso+arm (kept
independent of the RX1 stack, per the demo2 design) so it can reach, pick from a
table, carry, and place on a pad. Uses MuJoCo's MjSpec.attach to merge the hand
(assets/tendons/actuators) onto a wrist frame at the end of the arm.

Run:  python demo2/build_arm_scene.py   ->  model/scene_arm.xml
"""
from __future__ import annotations

from pathlib import Path
import mujoco

HERE = Path(__file__).resolve().parent
HAND_XML = HERE / "model" / "right_hand.xml"
OUT_MJB = HERE / "model" / "scene_arm.mjb"

# Arm: a self-contained chain. Fixed base column on the floor; waist yaw; a
# shoulder (pitch+roll); upper arm; elbow; forearm; then a wrist mount frame
# oriented so the attached hand points DOWN at the table (palm-down picking).
# The hand model lies along +x in its own frame (forearm root -> fingertips),
# so the mount frame rotates +x_hand -> -z_world (point down).
ARM_XML = """
<mujoco model="demo2_arm">
  <compiler angle="radian" autolimits="true"/>
  <option timestep="0.002" integrator="implicitfast" cone="elliptic" impratio="10"/>

  <visual>
    <rgba haze="0.15 0.25 0.35 1"/>
    <quality shadowsize="4096"/>
    <global azimuth="210" elevation="-25"/>
  </visual>

  <asset>
    <texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
    <texture type="2d" name="grid" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3"
      markrgb="0.8 0.8 0.8" width="300" height="300"/>
    <material name="grid" texture="grid" texuniform="true" texrepeat="6 6" reflectance="0.15"/>
    <material name="metal" rgba="0.5 0.55 0.6 1"/>
    <material name="wood" rgba="0.7 0.55 0.35 1"/>
    <material name="pad" rgba="0.2 0.6 0.9 1"/>
  </asset>

  <default>
    <default class="arm">
      <joint damping="8" armature="0.2" frictionloss="0.2"/>
      <!-- structural arm geoms don't collide (they overlap at joints/base and
           would otherwise lock the chain); the hand's own geoms keep contacts. -->
      <geom type="capsule" material="metal" density="700" contype="0" conaffinity="0"/>
      <position kp="3000" forcerange="-1500 1500"/>
    </default>
  </default>

  <worldbody>
    <light pos="0.4 0 1.6" dir="0 0 -1" directional="true"/>
    <light pos="0 -0.5 1.2"/>
    <geom name="floor" pos="0 0 0" size="0 0 0.05" type="plane" material="grid"/>

    <!-- table the object sits on (top surface at z=0.45), inside arm reach -->
    <body name="table" pos="0.27 0 0">
      <geom name="table_top" type="box" size="0.20 0.26 0.01" pos="0 0 0.49" material="wood"/>
      <geom name="table_leg" type="box" size="0.02 0.02 0.245" pos="0 0 0.245" material="wood"/>
    </body>
    <!-- place pad: the destination for pick-and-place -->
    <geom name="place_pad" type="cylinder" pos="0.27 0.12 0.501" size="0.05 0.002" material="pad"/>

    <!-- objects to pick (free bodies, rest on the table top z=0.50). "object"
         is the default/green target; red & blue enable language selection. -->
    <body name="object" pos="0.27 -0.12 0.55">
      <freejoint/>
      <geom type="ellipsoid" size="0.026 0.026 0.026" rgba="0.4 0.8 0.4 1" condim="6"
        priority="1" friction="0.7 0.02 0.004" mass="0.08"/>
    </body>
    <body name="object_red" pos="0.24 0.0 0.55">
      <freejoint/>
      <geom type="ellipsoid" size="0.026 0.026 0.026" rgba="0.85 0.2 0.2 1" condim="6"
        priority="1" friction="0.7 0.02 0.004" mass="0.08"/>
    </body>
    <body name="object_blue" pos="0.30 -0.06 0.55">
      <freejoint/>
      <geom type="ellipsoid" size="0.026 0.026 0.026" rgba="0.25 0.45 0.9 1" condim="6"
        priority="1" friction="0.7 0.02 0.004" mass="0.08"/>
    </body>

    <!-- ===== torso + arm chain ===== -->
    <body name="base" pos="0 0 0" childclass="arm">
      <geom name="column" type="cylinder" size="0.05 0.30" pos="0 0 0.30"/>
      <body name="torso" pos="0 0 0.60">
        <joint name="waist_yaw" axis="0 0 1" range="-1.57 1.57"/>
        <geom name="torso_g" type="box" size="0.07 0.09 0.10"/>
        <body name="shoulder" pos="0 -0.06 0.06">
          <joint name="shoulder_pitch" axis="0 1 0" range="-2.2 1.2"/>
          <geom name="shoulder_g" type="sphere" size="0.05"/>
          <body name="upper_arm" pos="0 0 0">
            <joint name="shoulder_roll" axis="1 0 0" range="-1.2 1.2"/>
            <geom name="upper_arm_g" type="capsule" fromto="0 0 0 0.24 0 0" size="0.035"/>
            <body name="forearm" pos="0.24 0 0">
              <joint name="elbow" axis="0 1 0" range="-2.4 0.2"/>
              <geom name="forearm_g" type="capsule" fromto="0 0 0 0.18 0 0" size="0.03"/>
              <body name="wrist" pos="0.18 0 0">
                <joint name="wrist_pitch" axis="0 1 0" range="-2.6 2.6"/>
                <geom name="wrist_g" type="sphere" size="0.03"/>
                <!-- mount frame: rotate hand's +x reach axis to point -z (down).
                     quat for -90deg about y maps +x_local -> -z_world. -->
                <frame name="wrist_mount" pos="0.03 0 0" quat="0.7071068 0 -0.7071068 0"/>
              </body>
            </body>
          </body>
        </body>
      </body>
    </body>
  </worldbody>

  <actuator>
    <position class="arm" name="a_waist_yaw"      joint="waist_yaw"      ctrlrange="-1.57 1.57"/>
    <position class="arm" name="a_shoulder_pitch" joint="shoulder_pitch" ctrlrange="-2.2 1.2"/>
    <position class="arm" name="a_shoulder_roll"  joint="shoulder_roll"  ctrlrange="-1.2 1.2"/>
    <position class="arm" name="a_elbow"          joint="elbow"          ctrlrange="-2.4 0.2"/>
    <position class="arm" name="a_wrist_pitch"    joint="wrist_pitch"    ctrlrange="-2.6 2.6"/>
  </actuator>
</mujoco>
"""


def build_model():
    """Compose torso+arm + Shadow Hand into a single compiled MjModel.

    Returns the compiled model. This is the single source of truth — MjSpec's
    XML round-trip mangles the merged <default> blocks (attach artifact), so the
    runtime loads the compiled model (saved as .mjb) rather than re-parsed XML.
    """
    arm = mujoco.MjSpec.from_string(ARM_XML)
    hand = mujoco.MjSpec.from_file(str(HAND_XML))

    # attach the hand at the wrist mount frame (merges hand bodies/assets/
    # tendons/actuators). The hand already uses an rh_ prefix on everything.
    mount = arm.frame("wrist_mount")
    arm.attach(hand, frame=mount, prefix="")

    # Lighten the Shadow-Hand bodies: the stock forearm is 3 kg, far too heavy
    # for this demo arm — the position servos can't hold a bent reach pose under
    # that cantilever load (the arm falls short of commanded angles). Scale all
    # hand body masses/inertias down so the arm can track IK targets.
    for b in arm.bodies:
        if b.name.startswith("rh_") and b.mass > 0:
            b.mass *= 0.15
            b.inertia = [v * 0.15 for v in b.inertia]

    # grasp site at the palm centre + weld for holding the object during carry.
    palm = arm.body("rh_palm")
    # site at the measured closed-finger enclosure centre (palm-local frame), so
    # IK driving the site onto the object seats it where the fingers actually close.
    palm.add_site(name="grasp", pos=[0.009, -0.061, 0.078], size=[0.008, 0.008, 0.008],
                  rgba=[0, 1, 0, 0.4])
    weld = arm.add_equality()
    weld.name = "grasp_weld"
    weld.type = mujoco.mjtEq.mjEQ_WELD
    weld.name1 = "rh_palm"
    weld.name2 = "object"
    weld.objtype = mujoco.mjtObj.mjOBJ_BODY
    weld.active = False

    return arm.compile()


def main():
    model = build_model()
    mujoco.mj_saveModel(model, str(OUT_MJB))
    print(f"[build_arm_scene] compiled nq={model.nq} nu={model.nu} "
          f"nbody={model.nbody}  -> {OUT_MJB.name}")


if __name__ == "__main__":
    main()
