Context
Flexiv Rizon 4 is a 7-DOF cobot with non-spherical kinematics — the wrist joints have 13 cm of offset between them, which means EAIK has no analytical decomposition (discovered during personalrobotics/mj_manipulator#127). With mink numerical IK now shipping as the automatic fallback in mj_manipulator v2.0 (personalrobotics/mj_manipulator#136), the Rizon 4 is the first arm that exercises the "EAIK fails, mink takes over" path end-to-end.
Source data: flexivrobotics/flexiv_description (Apache 2.0) — xacro-based URDFs, STL meshes, YAML configs with joint limits and kinematics for the full Rizon family.
Scope: Rizon 4 (P0)
1. URDF → MuJoCo conversion
scripts/convert_urdf.py — loads the Rizon 4 URDF via mujoco.MjSpec.from_file, then:
- Fixes mesh paths (URDF uses
package:// ROS convention → local relative paths)
- Adds position actuators with per-joint PD gains (URDF has none — joints are just kinematic)
- Adds a
grasp_site on the flange (link7_to_flange offset: [0, 0, 0.081] from their kinematics YAML)
- Adds a home keyframe
- Saves as
src/flexiv_mj/models/rizon4/rizon4.xml + scene.xml
The meshes directory from flexiv_description is ~50 MB of STL per variant. Open question: redistribute the converted meshes in this repo (simple but bulky), or have the user clone flexiv_description and convert locally (like mj_manipulator does with mujoco_menagerie via find_menagerie())?
Recommendation: start with a find_flexiv_description() resolver + conversion script (no mesh redistribution). Add a setup.sh that clones the upstream repo if absent.
2. Constants from Flexiv datasheets
Already extracted from their YAML configs:
Joint limits (from config/Rizon4/joint_limits.yaml):
RIZON4_JOINT_NAMES = [f"joint{i}" for i in range(1, 8)]
# Position limits (rad)
RIZON4_LOWER = [-2.8798, -2.3562, -3.0543, -1.9548, -3.0543, -1.4835, -3.0543]
RIZON4_UPPER = [ 2.8798, 2.3562, 3.0543, 2.7751, 3.0543, 4.6251, 3.0543]
# Velocity limits (rad/s) — halved for conservative planning
RIZON4_VELOCITY_LIMITS = np.array([2.0944, 2.0944, 2.4435, 2.4435, 4.8869, 4.8869, 4.8869]) * 0.5
# Acceleration limits — not published by Flexiv; derive from v_max / 0.1s
RIZON4_ACCELERATION_LIMITS = np.array([20.9, 20.9, 24.4, 24.4, 48.9, 48.9, 48.9]) * 0.5
Kinematics (from config/Rizon4/default_kinematics.yaml):
joint2: y=+30mm ← the offsets that make this arm non-spherical
joint3: y=+35mm
joint4: x=-20mm, y=-30mm
joint5: x=-20mm, y=+25mm
joint6: y=+30mm
joint7: x=-15mm, y=+73mm ← big offset, confirmed non-spherical wrist
These offsets are why EAIK returned None — 73 mm between wrist joints is too far from co-spherical for the exact-equality checks.
3. Arm factory
src/flexiv_mj/rizon4.py:
def create_rizon4_arm(env, *, ee_site="grasp_site", with_ik="auto", ...):
config = ArmConfig(
name="rizon4",
joint_names=RIZON4_JOINT_NAMES,
kinematic_limits=KinematicLimits(
velocity=RIZON4_VELOCITY_LIMITS.copy(),
acceleration=RIZON4_ACCELERATION_LIMITS.copy(),
),
ee_site=ee_site,
)
arm = Arm(env, config)
ik_solver = resolve_ik_solver(arm, with_ik=with_ik)
# with_ik="auto": EAIK fails (non-spherical) → mink takes over
return Arm(env, config, ik_solver=ik_solver, ...)
4. Actuator tuning
The URDF provides joint effort limits but no actuator model. We need:
- PD gains — start with the "reach max velocity in 100 ms" rule of thumb that worked for iiwa14, then tune from
scripts/trajectory_tracking_study.py
- Gravity compensation — add a
add_rizon4_gravcomp(spec) helper (same add_subtree_gravcomp pattern)
- Contact/friction — default MuJoCo values for the collision meshes; tune if grasping is unreliable
5. Validation
scripts/validate_rizon4.py — FK-IK round-trip check, confirm mink produces valid plans
- A simple demo scene (arm + table + can) with
robot.pickup() to exercise the full pipeline
scripts/benchmark_ik.py extended (or local copy) to include Rizon 4 — the first arm where mink is the only IK option
6. Documentation
- README with setup instructions (clone flexiv_description, convert, run demo)
- Constants sourced and cited
Future variants (P1/P2)
| Model |
Priority |
Notes |
| Rizon 4s |
P1 |
Same kinematics as Rizon 4 + integrated F/T sensor |
| Rizon 10 |
P1 |
Different link dimensions, 10 kg payload |
| Rizon 10s |
P2 |
Rizon 10 + F/T |
| Rizon 4M/4R |
P2 |
Mobile/ruggedized variants |
Each variant follows the same pattern: convert URDF, add constants from their YAML, write factory. The kinematics offsets differ per variant so mink handles them all the same way.
License
flexiv_description is Apache 2.0. Our repo is MIT. If we redistribute converted meshes, we need to include Flexiv's copyright notice. If we use the "clone + convert locally" pattern (recommended), no redistribution issue.
Dependencies
mj-manipulator >= 2.0.0 (for resolve_ik_solver with mink fallback)
flexivrobotics/flexiv_description (cloned locally for meshes/URDFs, not a pip dep)
Context
Flexiv Rizon 4 is a 7-DOF cobot with non-spherical kinematics — the wrist joints have 13 cm of offset between them, which means EAIK has no analytical decomposition (discovered during personalrobotics/mj_manipulator#127). With mink numerical IK now shipping as the automatic fallback in mj_manipulator v2.0 (personalrobotics/mj_manipulator#136), the Rizon 4 is the first arm that exercises the "EAIK fails, mink takes over" path end-to-end.
Source data: flexivrobotics/flexiv_description (Apache 2.0) — xacro-based URDFs, STL meshes, YAML configs with joint limits and kinematics for the full Rizon family.
Scope: Rizon 4 (P0)
1. URDF → MuJoCo conversion
scripts/convert_urdf.py— loads the Rizon 4 URDF viamujoco.MjSpec.from_file, then:package://ROS convention → local relative paths)grasp_siteon the flange (link7_to_flangeoffset:[0, 0, 0.081]from their kinematics YAML)src/flexiv_mj/models/rizon4/rizon4.xml+scene.xmlThe meshes directory from
flexiv_descriptionis ~50 MB of STL per variant. Open question: redistribute the converted meshes in this repo (simple but bulky), or have the user cloneflexiv_descriptionand convert locally (like mj_manipulator does withmujoco_menagerieviafind_menagerie())?Recommendation: start with a
find_flexiv_description()resolver + conversion script (no mesh redistribution). Add a setup.sh that clones the upstream repo if absent.2. Constants from Flexiv datasheets
Already extracted from their YAML configs:
Joint limits (from
config/Rizon4/joint_limits.yaml):Kinematics (from
config/Rizon4/default_kinematics.yaml):These offsets are why EAIK returned None — 73 mm between wrist joints is too far from co-spherical for the exact-equality checks.
3. Arm factory
src/flexiv_mj/rizon4.py:4. Actuator tuning
The URDF provides joint effort limits but no actuator model. We need:
scripts/trajectory_tracking_study.pyadd_rizon4_gravcomp(spec)helper (sameadd_subtree_gravcomppattern)5. Validation
scripts/validate_rizon4.py— FK-IK round-trip check, confirm mink produces valid plansrobot.pickup()to exercise the full pipelinescripts/benchmark_ik.pyextended (or local copy) to include Rizon 4 — the first arm where mink is the only IK option6. Documentation
Future variants (P1/P2)
Each variant follows the same pattern: convert URDF, add constants from their YAML, write factory. The kinematics offsets differ per variant so mink handles them all the same way.
License
flexiv_description is Apache 2.0. Our repo is MIT. If we redistribute converted meshes, we need to include Flexiv's copyright notice. If we use the "clone + convert locally" pattern (recommended), no redistribution issue.
Dependencies
mj-manipulator >= 2.0.0(forresolve_ik_solverwith mink fallback)flexivrobotics/flexiv_description(cloned locally for meshes/URDFs, not a pip dep)