There is no official URDF, USD or MJCF for the MasterPi. Hiwonder ships a STEP file on request (support@hiwonder.com, quote your order number) and nothing else.
This MJCF is built from first party numbers rather than eyeballed dimensions:
| source | what it gave |
|---|---|
ArmIK/InverseKinematics.py on the robot image |
l1 8.00, l2 6.50, l3 6.20, l4 10.00 cm |
ArmIK/ArmMoveIK.py |
l1 gets +1.3 cm, so 9.30 cm effective; servo channels 3,4,5,6 |
HiwonderSDK/mecanum.py |
a 67 mm, b 59 mm, wheel diameter 65 mm |
| product page | 185 x 162 x 343 mm, 1100 g, LD-1501MG + LFD-01M servos |
CLAUDE_CODE_PROMPT.md: paste this into Claude Code and it sets the whole thing upbuild_model.py: generatesmasterpi.xmlfrom a table of measured numbersmasterpi.xml: the model, includes floor and lighting, loads standalonemasterpi_simple.xml: the earlier primitive only version, kept for diffingview.py: the model in a viewer with nothing driving itverify_fk.py: runs Hiwonder's own IK and checks the MJCF reaches the same pointdemo.py: scripted pick and place plus a mecanum base driverender.py: offscreen still, no window neededrun.sh: venv, install, verify, launch, in one commandrequirements.txt
masterpi.xml is generated, not hand written. Edit build_model.py and re-run
it. Sources, in order of authority:
- Hiwonder's dimensional drawing on the product page. Scaled off the published 343 mm height (805 px in the 1200 px render, 0.4261 mm/px) and cross checked against the 162 mm width, which lands at 162.3 mm. The drawing is where the chassis layout, the wheel positions and the shoulder height come from.
ArmIK/InverseKinematics.py+ArmIK/ArmMoveIK.pyon the robot image, for the four link lengths. These are what the robot's own software believes, so they define the chain andverify_fk.pychecks against them.- Servo datasheets: LDX-218 and LD-1501MG are both 40 x 20 x 40.5 mm.
Measured against the published envelope:
| model | spec | |
|---|---|---|
| length, chassis and wheels | 185.8 mm | 185 |
| width, across the wheel faces | 163.0 mm | 162 |
| ground to top of rear deck | 101.8 mm | 101 |
| wheel diameter x width | 65 x 30 mm | 65 x 30 |
| mass | 1.105 kg | 1.100 kg |
| shoulder axis height | 129 mm | 129 (measured) |
| gripper tip, arm up | 356 mm | 343 with the jaws open |
One conflict is worth knowing about. The drawing puts the shoulder axis 129 mm
up and the arm's base plate at about 95 mm, 34 mm below it, but Hiwonder's IK
calls that distance l1 = 93 mm. So their l1 is not measured from the arm's base
plate. The chain is what has to stay true, so the IK frame origin is placed
exactly l1 below the shoulder, 36 mm above the ground, and the visible base
hardware is drawn where the drawing puts it. verify_fk.py still reads 0.00 mm.
Every visible part is modelled from the product photography rather than blocked out, and the colours are the real anodising:
| part | how it is built | colour |
|---|---|---|
| chassis box, decks, bumper, side plate | boxes, deck perforated with 30 countersunk holes | silver anodised aluminium |
| arm brackets, base U bracket | slotted U channel: two rails a side with the slot between them, a spine down the back, a rounded ear nesting at each pivot | orange anodised |
| shoulder / elbow / wrist / gripper servos | LD-1501MG and LDX-218 cases with their silver labels | matte black |
| servo horns and axle bosses | discs on both sides of every joint | bright steel |
| mecanum wheels | grey hub, six spokes, ten roller barrels at 45 deg | grey hub, orange rollers |
| gearmotors | barrel plus gearbox collar, inboard of each wheel | black, steel collar |
| dual ultrasonic | two lit lenses in the front housing | emissive blue |
| Raspberry Pi + expansion board + heatsinks + fan | boards between the decks, on copper standoffs | green, black, blue |
| wide angle camera | body, mount plate and lens barrel on the wrist | black, dark lens |
| gripper | jaw rail, guide pins, two jaws | gunmetal jaws, orange tips |
The real MasterPi carries exactly two sensing devices, and both are in the model:
- HD wide angle camera, 480P, mounted on the gripper itself, standing above
and behind the jaws on a plate held off by two brass standoffs, lens looking
straight down the tool axis so the jaw tips sit at the edge of frame. It is a
real MuJoCo
<camera name="wrist_cam">, so you can render the robot's own FPV feed:Renderer.update_scene(d, "wrist_cam"). This is the camera Hiwonder's demos use for colour sorting, block tracking and visual line following. - The glowy ultrasonic module in the front housing, the two blue lit eyes.
Modelled as two
<rangefinder>sensors,sonar_leftandsonar_right, reading metres along +x and -1 when nothing is in range.
There is no infrared sensor on the stock MasterPi. Line following is done in OpenCV through the wrist camera, not with a floor mounted IR array, and the spec sheet lists no IR part. The row of small holes in the lower bumper plate is bracket mounting, and it is modelled as such.
Geometry is split into two sets, so the render can be detailed without making the solver pay for it:
- group 1 : visual only,
contype 0,mass 0. 200 odd geoms of skin. - group 3 : the collision proxies, hidden by default. A box for the chassis, a cylinder per wheel, a capsule per arm link.
In the viewer press 1 to hide the skin and 3 to reveal the proxies, or run
python render.py col.png --collision. Total mass comes out at 1.105 kg against
the 1100 g on the product page.
The kinematic chain, joint names, actuators, sensors and site positions are
untouched by all of this. verify_fk.py still reports 0.00 mm.
verify_fk.py feeds the waypoints from Hiwonder's control_by_kinematics.py demo
through the stock IK, maps the servo angles into MuJoCo joints, and reads the TCP
back. Current error on all five waypoints: 0.00 mm.
| MJCF joint | robot servo | note |
|---|---|---|
joint6_base_yaw |
servo 6 | theta6 = atan2(y, x), model zero is +x forward |
joint5_shoulder |
servo 5 | model zero = arm straight up, q5 = 90 - theta5 |
joint4_elbow |
servo 4 | q4 = theta4 |
joint3_wrist |
servo 3 | q3 = -theta3 |
finger_left_j / finger_right_j |
servo 1 | coupled by equality constraint |
base_x, base_y, base_yaw |
mecanum chassis | velocity actuators, body frame |
Servo pulse 500..2500 us maps to 0..180 deg, so every arm joint range is +/- 90 deg.
MuJoCo's sliding friction is isotropic, so a mecanum wheel cannot be faked with friction parameters alone. Two honest options:
- Planar omni base (what this file does). Three joints on the chassis driven by
velocity actuators that take vx, vy, wz in the body frame, exactly like
MecanumChassis.set_velocity. Correct for anything where the arm is the subject. - Real rollers. Replace the three joints with
<freejoint/>and give each wheel 9 to 12 roller bodies, each a free spinning capsule with its axis at 45 deg to the hub. Needed only if wheel slip itself is what you are studying.
./run.shor by hand:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python verify_fk.py # expect 0.00 mm on all five rows
python demo.py --headless # printed TCP and base positions
python render.py # writes preview.png
mjpython demo.py # interactive viewer (macOS needs mjpython, not python)On macOS mujoco.viewer.launch_passive has to own the main thread, so the
viewer must be started with mjpython, which pip installs alongside mujoco.
run.sh ends with plain python demo.py and will fail on that last step on a
Mac; run the mjpython line by hand instead.
