Illustrative rail-mounted UR5e#

This example adds a one-metre X rail to the shipped nominal UR5e. It exercises a seven-joint, mixed-unit chain without changing the packaged Panda or registering a new production preset. It is an illustrative assembly, not a manufacturer-approved robot or a model validated against physical hardware.

From a source checkout with the build extra installed (Python 3.10+, nvcc for the first build):

python examples/rail_ur5e.py --cache /tmp/hjcd-rail-example
python examples/rail_ur5e.py --cache /tmp/hjcd-rail-example --load-only --precision fp64

An absolute script path works from another directory. The first command assembles the two input URDFs from the installed UR5e assets and compiles through GRiD’s store. Subsequent builds reuse unchanged inputs; --load-only needs grid-rbd, but neither a compiler nor the build step. Keep the example’s rail_ur5e_targets.json beside it.

Frames, coordinates and geometry#

The fixed root is world. rail_joint translates base_link along world +X over [0, 1] metres. The remaining six coordinates are the original UR5e joints in radians, in solver.info.joint_names order. Tool poses refer to tool0, selected by the fixed joint flange-tool0, and use [x,y,z,qw,qx,qy,qz] in the world/root frame.

The generator changes only base_joint from fixed to prismatic (renamed rail_joint) and preserves all supplied sphere geometry, including base_link_inertia. Those base spheres now move and participate in collision checking. GRiD excludes pairs on the same or adjacent movable anchors; the example adds no exclusions. The fit can reject configurations a more detailed physical model would accept.

Carriage, rail, supports, cabling and end-effector hardware are not modeled. The obstacle scene contains one explicitly placed sphere, not a factory workspace. The URDF is a kinematics example: the source’s helper links have no inertials, and the assembly does not claim a validated dynamics model. Parser inertia warnings do not establish a kinematic failure; independent FK and collision placement are tested.

UR5e assets retain BSD-3-Clause attribution to Universal Robots ROS2 Description at 22f055da2fa7e2158254426107d1f257fd56aebb. The generated directory includes its license. The shipped csrc/collision/ur5e_provenance.json reports sampled fidelity: the sphere fit is not a mesh-level or physical-safety certificate. See Custom robot for the nominal-calibration and geometry limitations.

Workflow and acceptance#

The example loads two frozen 32-target sets, installs a world, solves, and reuses only candidates satisfying both 5 mm and 0.001 rad for neighboring target poses. It then installs a fully blocking sphere, handles zero output without indexing an absent row, restores the earlier world, and checks that old results remain valid. A miss is reported as no qualifying solution found within the budget, not proof of infeasibility. count/valid describe returned slots; ok tests pose accuracy. Hard collision validity applies to the supplied sphere model only, not a trajectory.

Witness configurations and nearby poses come from fixed-seed independent NumPy FK and sphere checks, with a 3 mm selection clearance, not selection by IK success. Regenerate them with python scripts/codegen/make_rail_targets.py in the development environment. The tests require at least 31/32 accurate targets per scene/precision for cold and neighboring warm starts at B=2000/S=4; every hard result is independently checked. There is no speed or identical-configuration requirement.

For the full installed-package check, separate from routine pytest:

.venv/bin/python scripts/setup/check_installed.py --out /tmp/hjcd-installed-check

Use a new output directory each time. It retains the source archive, wheel, hashes, commands and logs. It tests packaged Panda without build extras, then the rail journey in an isolated environment outside the checkout, and fresh-process loading with build calls forbidden. This check needs pip access or cached dependencies; it performs no performance measurements. The smaller mixed_joint_warm_start.py remains the minimal two-joint introduction.