vcad Assembly & Physics Simulation

SkillDev tools

Build multi-part assemblies with joints and run physics simulations. Use when the user asks about robot arms, mechanisms, hinges, joints, physics simulation, reinforcement learning environments, or assembly of multiple parts.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the vcad Assembly & Physics Simulation skill

What this skill tells your AI

The instructions your AI receives, as published by ecto/vcad in plugins/claude-code/skills/assembly-physics/SKILL.md and read by ahel’s review.

Create assemblies from parts, connect them with joints, and run physics simulations via the vcad MCP tools.

Assembly Structure

An assembly is defined inside create_cad_document alongside the parts:

{
  "parts": [...],
  "assembly": {
    "instances": [...],
    "joints": [...],
    "ground": "base-1"
  }
}

Instances

Each instance places a part in the assembly:

{
  "id": "arm-1",
  "part": "arm",
  "name": "Lower Arm",
  "position": {"x": 0, "y": 0, "z": 50},
  "rotation": {"x": 0, "y": 0, "z": 0}
}
  • id — unique identifier (referenced by joints)
  • part — name of a part in the parts array
  • position — initial position in mm (optional)
  • rotation — initial rotation in degrees (optional)

Joints

Joints connect two instances and define degrees of freedom:

{
  "id": "shoulder",
  "name": "Shoulder Joint",
  "parent": "base-1",
  "child": "arm-1",
  "type": "revolute",
  "axis": "y",
  "parent_anchor": {"x": 0, "y": 0, "z": 50},
  "child_anchor": {"x": 0, "y": 0, "z": 0},
  "limits": [-90, 90],
  "state": 0
}
  • parent — instance ID, or null for world/ground
  • child — instance ID
  • type — joint type (see below)
  • axis"x", "y", "z", or custom {x, y, z} vector
  • parent_anchor / child_anchor — connection points in local coordinates
  • limits[min, max] (degrees for revolute, mm for slider)
  • state — initial angle or position

Ground

Set "ground" to the instance ID of the fixed/base part. This anchors the kinematic chain.

Joint Types

TypeDOFDescriptionState Unit
fixed0Rigid attachment, no motionn/a
revolute1Rotation around axis (hinge)degrees
slider1Translation along axis (piston)mm
cylindrical2Rotation + translation on same axisdegrees
ball3Omnidirectional rotationn/a

Physics Simulation Tools

The gym-style tools simulate assembly dynamics using phyz:

ToolPurpose
create_robot_envInitialize simulation from assembly document
gym_resetReset to initial state, returns observation
gym_stepApply action, advance physics, returns observation + reward + done
gym_observeGet current state without stepping
gym_closeClean up simulation

Simulation Workflow

create_cad_document (with assembly)
  → create_robot_env (document, end_effector_ids)
    → gym_reset (env_id)
      → gym_step (env_id, action_type, values) [loop]
        → gym_close (env_id)

create_robot_env

{
  "document": "<IR document from create_cad_document>",
  "end_effector_ids": ["gripper-1"],
  "dt": 0.004167,
  "substeps": 4,
  "max_steps": 1000
}

Returns: env_id, num_joints, action_dim, observation_dim.

gym_step

{
  "env_id": "sim_1",
  "action_type": "torque",
  "values": [0.5, -0.3]
}

Action types:

  • "torque" — apply torque in Nm to each joint
  • "position" — set target position (degrees for revolute, mm for slider)
  • "velocity" — set target velocity (deg/s or mm/s)

The values array must have one entry per joint.

Returns:

  • observation — joint positions, velocities, end effector poses
  • reward — scalar reward signal
  • done — true if episode ended (max steps or termination)

gym_reset / gym_observe

Both return the observation object:

{
  "joint_positions": [0.0, 0.0],
  "joint_velocities": [0.0, 0.0],
  "end_effector_poses": [
    {"position": {"x": 0, "y": 0, "z": 100}, "orientation": {"x": 0, "y": 0, "z": 0, "w": 1}}
  ]
}

Complete Example: 2-DOF Robot Arm

{
  "parts": [
    {
      "name": "base",
      "primitive": {"type": "cylinder", "radius": 25, "height": 10},
      "material": "steel"
    },
    {
      "name": "arm",
      "primitive": {"type": "cube", "size": {"x": 10, "y": 10, "z": 80}},
      "material": "aluminum"
    }
  ],
  "assembly": {
    "instances": [
      {"id": "base-1", "part": "base"},
      {"id": "lower-arm", "part": "arm", "position": {"x": 0, "y": 0, "z": 10}},
      {"id": "upper-arm", "part": "arm", "position": {"x": 0, "y": 0, "z": 90}}
    ],
    "joints": [
      {
        "id": "shoulder",
        "parent": "base-1",
        "child": "lower-arm",
        "type": "revolute",
        "axis": "y",
        "parent_anchor": {"x": 0, "y": 0, "z": 10},
        "child_anchor": {"x": 5, "y": 5, "z": 0},
        "limits": [-90, 90]
      },
      {
        "id": "elbow",
        "parent": "lower-arm",
        "child": "upper-arm",
        "type": "revolute",
        "axis": "y",
        "parent_anchor": {"x": 5, "y": 5, "z": 80},
        "child_anchor": {"x": 5, "y": 5, "z": 0},
        "limits": [0, 135]
      }
    ],
    "ground": "base-1"
  }
}

After creating the document, run a simulation:

  1. create_robot_env with end_effector_ids: ["upper-arm"]
  2. gym_reset to get initial observation
  3. gym_step with action_type: "torque" and values per joint
  4. Repeat step 3 for your control loop
  5. gym_close when done

Tips

  • Every assembly needs a ground instance — this is the fixed reference
  • Joint anchors are in the instance's local coordinate frame
  • Revolute limits are in degrees, slider limits in mm
  • State outside limits is clamped automatically
  • Use position action type for precise pose control, torque for dynamic simulation
  • end_effector_ids must reference valid instance IDs in the assembly
  • Forward kinematics propagates transforms from ground through the joint chain

Signals

GitHub stars
419
Forks
24
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
vcad-assembly
Source
github.com/ecto/vcad