urdf-validator 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. urdf_validator-1.0.0/LICENSE +21 -0
  2. urdf_validator-1.0.0/PKG-INFO +649 -0
  3. urdf_validator-1.0.0/README.md +614 -0
  4. urdf_validator-1.0.0/pyproject.toml +44 -0
  5. urdf_validator-1.0.0/setup.cfg +4 -0
  6. urdf_validator-1.0.0/tests/test_arm_chain.py +268 -0
  7. urdf_validator-1.0.0/tests/test_capability_profile_reference.py +204 -0
  8. urdf_validator-1.0.0/tests/test_capability_wiring.py +245 -0
  9. urdf_validator-1.0.0/tests/test_chain_walker.py +230 -0
  10. urdf_validator-1.0.0/tests/test_cli.py +978 -0
  11. urdf_validator-1.0.0/tests/test_formatter.py +297 -0
  12. urdf_validator-1.0.0/tests/test_geometry_physics.py +139 -0
  13. urdf_validator-1.0.0/tests/test_imports.py +77 -0
  14. urdf_validator-1.0.0/tests/test_install.py +19 -0
  15. urdf_validator-1.0.0/tests/test_models.py +134 -0
  16. urdf_validator-1.0.0/tests/test_mujoco_validation.py +146 -0
  17. urdf_validator-1.0.0/tests/test_orientation.py +129 -0
  18. urdf_validator-1.0.0/tests/test_pending_degradation.py +196 -0
  19. urdf_validator-1.0.0/tests/test_report_derivation.py +167 -0
  20. urdf_validator-1.0.0/tests/test_robot_classifier.py +179 -0
  21. urdf_validator-1.0.0/tests/test_schema_checks.py +375 -0
  22. urdf_validator-1.0.0/tests/test_schema_mesh_check.py +266 -0
  23. urdf_validator-1.0.0/tests/test_schema_new_checks.py +237 -0
  24. urdf_validator-1.0.0/tests/test_self_collision.py +342 -0
  25. urdf_validator-1.0.0/tests/test_stability.py +483 -0
  26. urdf_validator-1.0.0/tests/test_statics.py +684 -0
  27. urdf_validator-1.0.0/tests/test_support_polygon.py +224 -0
  28. urdf_validator-1.0.0/tests/test_task_runner.py +193 -0
  29. urdf_validator-1.0.0/tests/test_task_runner_reference.py +409 -0
  30. urdf_validator-1.0.0/tests/test_task_runner_sweep.py +122 -0
  31. urdf_validator-1.0.0/tests/test_task_runner_toy.py +345 -0
  32. urdf_validator-1.0.0/tests/test_task_schema.py +83 -0
  33. urdf_validator-1.0.0/tests/test_urdf_adapter.py +331 -0
  34. urdf_validator-1.0.0/tests/test_workspace.py +708 -0
  35. urdf_validator-1.0.0/tests/test_xacro_handler.py +81 -0
  36. urdf_validator-1.0.0/urdf_validator.egg-info/PKG-INFO +649 -0
  37. urdf_validator-1.0.0/urdf_validator.egg-info/SOURCES.txt +67 -0
  38. urdf_validator-1.0.0/urdf_validator.egg-info/dependency_links.txt +1 -0
  39. urdf_validator-1.0.0/urdf_validator.egg-info/entry_points.txt +2 -0
  40. urdf_validator-1.0.0/urdf_validator.egg-info/requires.txt +17 -0
  41. urdf_validator-1.0.0/urdf_validator.egg-info/top_level.txt +1 -0
  42. urdf_validator-1.0.0/urdf_validator_main/__init__.py +0 -0
  43. urdf_validator-1.0.0/urdf_validator_main/api/__init__.py +0 -0
  44. urdf_validator-1.0.0/urdf_validator_main/api/task_runner.py +248 -0
  45. urdf_validator-1.0.0/urdf_validator_main/api/task_schema.py +34 -0
  46. urdf_validator-1.0.0/urdf_validator_main/checks/__init__.py +0 -0
  47. urdf_validator-1.0.0/urdf_validator_main/checks/schema.py +347 -0
  48. urdf_validator-1.0.0/urdf_validator_main/checks/stability.py +174 -0
  49. urdf_validator-1.0.0/urdf_validator_main/checks/statics.py +272 -0
  50. urdf_validator-1.0.0/urdf_validator_main/checks/workspace.py +332 -0
  51. urdf_validator-1.0.0/urdf_validator_main/cli.py +396 -0
  52. urdf_validator-1.0.0/urdf_validator_main/integrations/__init__.py +0 -0
  53. urdf_validator-1.0.0/urdf_validator_main/integrations/mujoco_wrapper.py +182 -0
  54. urdf_validator-1.0.0/urdf_validator_main/parser/__init__.py +0 -0
  55. urdf_validator-1.0.0/urdf_validator_main/parser/urdf_adapter.py +293 -0
  56. urdf_validator-1.0.0/urdf_validator_main/parser/xacro_handler.py +48 -0
  57. urdf_validator-1.0.0/urdf_validator_main/physics/__init__.py +0 -0
  58. urdf_validator-1.0.0/urdf_validator_main/physics/arm_chain.py +179 -0
  59. urdf_validator-1.0.0/urdf_validator_main/physics/capability_profiles.py +76 -0
  60. urdf_validator-1.0.0/urdf_validator_main/physics/chain_walker.py +130 -0
  61. urdf_validator-1.0.0/urdf_validator_main/physics/geometry_physics.py +45 -0
  62. urdf_validator-1.0.0/urdf_validator_main/physics/orientation.py +80 -0
  63. urdf_validator-1.0.0/urdf_validator_main/physics/robot_classifier.py +28 -0
  64. urdf_validator-1.0.0/urdf_validator_main/physics/self_collision.py +166 -0
  65. urdf_validator-1.0.0/urdf_validator_main/physics/support_polygon.py +178 -0
  66. urdf_validator-1.0.0/urdf_validator_main/report/__init__.py +0 -0
  67. urdf_validator-1.0.0/urdf_validator_main/report/formatter.py +284 -0
  68. urdf_validator-1.0.0/urdf_validator_main/report/json_export.py +22 -0
  69. urdf_validator-1.0.0/urdf_validator_main/report/models.py +122 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Notlord69
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,649 @@
1
+ Metadata-Version: 2.4
2
+ Name: urdf-validator
3
+ Version: 1.0.0
4
+ Summary: Physics-aware URDF validation for the ROS 2 community
5
+ License: MIT
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.8
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Scientific/Engineering
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Operating System :: OS Independent
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: urdf_parser_py
22
+ Requires-Dist: numpy
23
+ Requires-Dist: shapely
24
+ Requires-Dist: ikpy
25
+ Provides-Extra: xacro
26
+ Requires-Dist: xacro; extra == "xacro"
27
+ Provides-Extra: mujoco
28
+ Requires-Dist: mujoco; extra == "mujoco"
29
+ Provides-Extra: full
30
+ Requires-Dist: xacro; extra == "full"
31
+ Requires-Dist: mujoco; extra == "full"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # urdf_validator
37
+
38
+ A physics-aware URDF validation tool for the ROS 2 community.
39
+
40
+ ## The problem
41
+
42
+ `check_urdf`, the only official ROS 2 validation tool, checks syntax only. A URDF that passes `check_urdf` can still silently fail in any physics-based simulator — collapsing robots, undersized motors, unstable configurations. `urdf_validator` catches this entire class of errors before you ever launch a simulation.
43
+
44
+ The tool is designed to work on **any** URDF, not just a curated set of reference robots. When the link-name heuristics cannot reliably classify your robot, you can declare what the tool cannot infer — robot type, ground-contact links, arm chain boundaries — and the heuristics continue running as a cross-check rather than the sole decision-maker.
45
+
46
+ ## What it checks
47
+
48
+ | Phase | What it analyses |
49
+ |---|---|
50
+ | **Schema** | Broken joint references, kinematic loops, duplicate names, zero/missing inertia, non-positive-definite inertia, inverted joint limits, missing effort/velocity limits, missing mesh files |
51
+ | **Statics** | Full-body centre of mass, gravity torque per actuated joint, motor effort margins (PASS / WARN / FAIL), weakest joint identification |
52
+ | **Stability** | Support polygon from wheel/caster contacts, COM-over-polygon containment, signed margin in mm, tip direction, COM height ratio, tipping angle |
53
+ | **Workspace** | Monte Carlo FK reach envelope (max / vertical / horizontal), task-specific reachability, COM stability during reach |
54
+ | **User overrides** | `--robot-type`, `--contact-links`, `--arm-root`/`--arm-tip` let you declare what heuristics cannot reliably infer; declared values are labeled `exact` and heuristics run as a cross-check |
55
+ | **Deep (optional)** | MuJoCo simulation cross-validation of gravity torques and COM; `SIMULATED` confidence badge |
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install urdf-validator
61
+ ```
62
+
63
+ Optional extras:
64
+
65
+ ```bash
66
+ pip install "urdf-validator[xacro]" # adds xacro preprocessing
67
+ pip install "urdf-validator[mujoco]" # adds MuJoCo deep-validation mode
68
+ pip install "urdf-validator[full]" # adds both xacro and mujoco
69
+ ```
70
+
71
+ No ROS installation required.
72
+
73
+ ## Quick start
74
+
75
+ ```bash
76
+ urdf_validate my_robot.urdf
77
+ ```
78
+
79
+ Writes `my_robot_validation.json` alongside the URDF. Exits non-zero on any WARN or FAIL finding — directly usable as a CI gate.
80
+
81
+ ## CLI reference
82
+
83
+ ```
84
+ urdf_validate <urdf_file> [options]
85
+ ```
86
+
87
+ | Flag | Values | Default | Description |
88
+ |---|---|---|---|
89
+ | `--pose` | `zero` \| `limits` \| `custom` \| `home` | `zero` | Joint configuration for statics/stability/workspace. `limits` sets each joint to its declared upper limit (worst-case torques). `custom` requires `--joint-angles`. `home` warns and falls back to zero (no URDF standard for home configs). |
90
+ | `--joint-angles ANGLES` | `"j1=0.5,j2=1.2"` | — | Joint angles for `--pose custom` in radians/metres. |
91
+ | `--task TASK` | `pick_from_ground` \| `pick_from_table` \| `push_button` \| `custom` | — | Task reachability check. Reports whether the arm can reach the target height and whether the COM remains stable during reach. |
92
+ | `--height M` | float | — | Target height in metres. Required with `--task custom`. |
93
+ | `--output-dir DIR` | path | alongside input | Directory for the JSON validation report. |
94
+ | `--deep` | flag | off | Run MuJoCo simulation pass to cross-validate gravity torques and COM. Auto-triggers when stability margin is negative. Requires `pip install mujoco`. |
95
+ | `--robot-type TYPE` | `wheeled` \| `ground_vehicle` \| `legged` \| `humanoid` \| `arm_only` \| `aerial` \| `unknown` | — | Declare the robot category explicitly. The link-name heuristic still runs as a cross-check; a mismatch is reported as a warning but does not override this declaration. |
96
+ | `--contact-links LINKS` | `"l1,l2,l3"` | — | Comma-separated list of ground-contact link names. Bypasses the geometry heuristic for stability polygon construction; useful for legged robots or non-standard wheel naming. |
97
+ | `--arm-root LINK` | link name | — | Root link of the arm chain. Bypasses the DOF-heuristic arm detection. Must be used together with `--arm-tip`. |
98
+ | `--arm-tip LINK` | link name | — | End-effector link of the arm chain. Bypasses the DOF-heuristic arm detection. Must be used together with `--arm-root`. |
99
+
100
+ ### Exit codes
101
+
102
+ | Code | Meaning |
103
+ |---|---|
104
+ | `0` | PASS — all checks passed |
105
+ | `1` | WARN — no failures, at least one warning |
106
+ | `2` | FAIL or UNKNOWN — at least one check failed or a critical check could not run |
107
+
108
+ ## Output — reference robot examples
109
+
110
+ The acceptance standard is correct, non-crashing output on six well-known public robots plus two capability-profile examples added in v0.7. Output below is captured directly from the tool; ANSI colours are stripped.
111
+
112
+ ---
113
+
114
+ ### TurtleBot3 — differential-drive mobile robot
115
+
116
+ ```
117
+ ╔════════════════════════════════════════════════════╗
118
+ ║ urdf_validate — TurtleBot3.urdf ║
119
+ ╚════════════════════════════════════════════════════╝
120
+ [SCHEMA] ✓ PASS (6 infos)
121
+ [INFO] Joint 'wheel_left_joint' (continuous) has no effort or velocity limit declared
122
+ [INFO] Joint 'wheel_right_joint' (continuous) has no effort or velocity limit declared
123
+ [INFO] Link 'base_link': mesh 'package://turtlebot3_description/meshes/bases/burger_base.stl' not found — package:// resolution requires ROS workspace to be sourced
124
+ [INFO] Link 'wheel_left_link': mesh 'package://turtlebot3_description/meshes/wheels/left_tire.stl' not found — package:// resolution requires ROS workspace to be sourced
125
+ [INFO] Link 'wheel_right_link': mesh 'package://turtlebot3_description/meshes/wheels/right_tire.stl' not found — package:// resolution requires ROS workspace to be sourced
126
+ [INFO] Link 'base_scan': mesh 'package://turtlebot3_description/meshes/sensors/lds.stl' not found — package:// resolution requires ROS workspace to be sourced
127
+ [PHYSICS] 7 links — mass: 5 exact, 2 missing · inertia: 5 exact, 2 missing
128
+ [STATICS] COM [-0.004, 0.000, 0.031] m height 0.031 m total mass 1.002 kg (estimated)
129
+ Heaviest: base_link (82.4%)
130
+ [STATICS] joints: PASS
131
+ [STABILITY] ✓ STABLE margin 4.0 mm
132
+ COM height ratio 0.96 — stable tips at 27.4°
133
+ [WORKSPACE] UNKNOWN — No arm chain detected (robot may be wheeled or legged only)
134
+ [OVERALL] PASS confidence: MEDIUM
135
+ Full report: TurtleBot3_validation.json
136
+ ```
137
+
138
+ Exit 0.
139
+
140
+ ---
141
+
142
+ ### Fetch — wheeled mobile manipulator
143
+
144
+ ```
145
+ ╔════════════════════════════════════════════════════╗
146
+ ║ urdf_validate — fetch.urdf ║
147
+ ╚════════════════════════════════════════════════════╝
148
+ [SCHEMA] ⚠ WARN — 42 issues
149
+ [WARN] Link 'r_gripper_finger_link' has non-positive-definite inertia tensor (min eigenvalue: 0.000000) — physically impossible
150
+ [WARN] Link 'l_gripper_finger_link' has non-positive-definite inertia tensor (min eigenvalue: 0.000000) — physically impossible
151
+ [INFO] (40 mesh resolution notices — package:// resolution requires ROS workspace)
152
+ [PHYSICS] 28 links — mass: 22 exact, 6 missing · inertia: 22 exact, 6 missing
153
+ [STATICS] COM [0.045, 0.001, 0.260] m height 0.260 m total mass 121.538 kg (estimated)
154
+ Heaviest: base_link (57.7%)
155
+ [STATICS] joints: PASS weakest: torso_lift_joint
156
+ [STABILITY] ✓ STABLE margin 43.7 mm
157
+ COM height ratio 0.69 — stable tips at 35.8°
158
+ [WORKSPACE] max reach 2.182 m vertical 2.158 m horizontal 1.165 m (estimated)
159
+ from base 2.182 m
160
+ [OVERALL] WARN confidence: MEDIUM
161
+ Full report: fetch_validation.json
162
+ ```
163
+
164
+ Exit 1. The two gripper finger links have singular inertia tensors in the upstream URDF — a known issue with this file. All statics and stability checks proceed and pass.
165
+
166
+ ---
167
+
168
+ ### PR2 — dual-arm wheeled robot (88 links)
169
+
170
+ ```
171
+ ╔════════════════════════════════════════════════════╗
172
+ ║ urdf_validate — PR2.urdf ║
173
+ ╚════════════════════════════════════════════════════╝
174
+ [SCHEMA] ⚠ WARN — 88 issues
175
+ [WARN] Link 'r_gripper_l_finger_tip_frame' has no inertial block (mass unknown) — will cause physics engine instability
176
+ [WARN] Link 'l_gripper_l_finger_tip_frame' has no inertial block (mass unknown) — will cause physics engine instability
177
+ [INFO] Joint 'torso_lift_motor_screw_joint' (continuous) has no effort or velocity limit declared
178
+ [INFO] Joint 'r_gripper_motor_screw_joint' (continuous) has no effort or velocity limit declared
179
+ [INFO] Joint 'l_gripper_motor_screw_joint' (continuous) has no effort or velocity limit declared
180
+ [INFO] Robot has 88 links (>50) — may be slow to simulate or validate
181
+ [INFO] (82 mesh resolution notices — package:// resolution requires ROS workspace)
182
+ [PHYSICS] 88 links — mass: 73 exact, 15 missing · inertia: 73 exact, 15 missing
183
+ [STATICS] COM [-0.016, 0.005, 0.477] m height 0.477 m total mass 265.732 kg (estimated)
184
+ Heaviest: base_link (43.7%)
185
+ [STATICS] joints: FAIL weakest: l_shoulder_lift_joint
186
+ [STABILITY] ✓ STABLE margin 208.5 mm
187
+ COM height ratio 1.06 — manageable tips at 25.2°
188
+ [WORKSPACE] max reach 1.887 m vertical 1.777 m horizontal 1.177 m (estimated)
189
+ from base 1.931 m
190
+ [OVERALL] FAIL confidence: MEDIUM
191
+ Full report: PR2_validation.json
192
+ ```
193
+
194
+ Exit 2. The shoulder lift joints on both arms are undersized: the URDF declares 30 Nm but the gravity torque at zero pose requires ~49 Nm. The real PR2 compensates with passive counterbalance springs not modelled in the URDF.
195
+
196
+ ---
197
+
198
+ ### ANYmal — legged quadruped
199
+
200
+ ```
201
+ ╔════════════════════════════════════════════════════╗
202
+ ║ urdf_validate — ANYmal.urdf ║
203
+ ╚════════════════════════════════════════════════════╝
204
+ [SCHEMA] ✓ PASS (17 infos)
205
+ [INFO] (17 mesh resolution notices — package:// resolution requires ROS workspace)
206
+ [PHYSICS] 22 links — mass: 17 exact, 5 missing · inertia: 17 exact, 5 missing
207
+ [STATICS] COM [-0.001, -0.001, -0.034] m height -0.034 m total mass 30.421 kg (estimated)
208
+ Heaviest: base_inertia (55.2%)
209
+ [STATICS] joints: PASS weakest: RF_HAA
210
+ [STABILITY] UNKNOWN — robot type 'quadruped' — stability only computed for wheeled robots
211
+ [WORKSPACE] N/A — not applicable — robot type 'quadruped' has no manipulator
212
+ [OVERALL] PASS confidence: MEDIUM
213
+ Full report: ANYmal_validation.json
214
+ ```
215
+
216
+ Exit 0. Stability is UNKNOWN for legged robots — support polygon computation requires declared foot contacts, which are not a standard URDF field. Use `--contact-links` to supply foot contact link names explicitly and get a real stability margin. Workspace is N/A — the capability profile for `quadruped` classifies this as a locomotion platform with no manipulator; v0.6 incorrectly reported leg reach as workspace reach.
217
+
218
+ ---
219
+
220
+ ### Spot — legged quadruped (unofficial URDF, no masses)
221
+
222
+ ```
223
+ ╔════════════════════════════════════════════════════╗
224
+ ║ urdf_validate — Spot.urdf ║
225
+ ╚════════════════════════════════════════════════════╝
226
+ [SCHEMA] ⚠ WARN — 37 issues
227
+ [WARN] Link 'fl.hip' has no inertial block (mass unknown) — will cause physics engine instability
228
+ [WARN] Link 'fl.uleg' has no inertial block (mass unknown) — will cause physics engine instability
229
+ [WARN] Link 'fl.lleg' has no inertial block (mass unknown) — will cause physics engine instability
230
+ [WARN] Link 'fr.hip' has no inertial block (mass unknown) — will cause physics engine instability
231
+ [WARN] Link 'fr.uleg' has no inertial block (mass unknown) — will cause physics engine instability
232
+ [WARN] Link 'fr.lleg' has no inertial block (mass unknown) — will cause physics engine instability
233
+ [WARN] Link 'hl.hip' has no inertial block (mass unknown) — will cause physics engine instability
234
+ [WARN] Link 'hl.uleg' has no inertial block (mass unknown) — will cause physics engine instability
235
+ [WARN] Link 'hl.lleg' has no inertial block (mass unknown) — will cause physics engine instability
236
+ [WARN] Link 'hr.hip' has no inertial block (mass unknown) — will cause physics engine instability
237
+ [WARN] Link 'hr.uleg' has no inertial block (mass unknown) — will cause physics engine instability
238
+ [WARN] Link 'hr.lleg' has no inertial block (mass unknown) — will cause physics engine instability
239
+ [INFO] Link 'fl.hip' has visual geometry (mesh) but no collision geometry — will be invisible to physics engines
240
+ [INFO] (11 more visual-without-collision notices + 13 mesh resolution notices)
241
+ [PHYSICS] 13 links — mass: 0 exact, 13 missing · inertia: 0 exact, 13 missing
242
+ [STATICS] COM unknown (missing)
243
+ [STATICS] joints: PASS
244
+ [STABILITY] UNKNOWN — robot type 'quadruped' — stability only computed for wheeled robots
245
+ [WORKSPACE] N/A — not applicable — robot type 'quadruped' has no manipulator
246
+ [OVERALL] WARN confidence: LOW
247
+ Full report: Spot_validation.json
248
+ ```
249
+
250
+ Exit 1. This unofficial URDF omits all link masses. The tool degrades gracefully: schema warns on every link, statics and stability correctly report missing data. Workspace is N/A — the capability profile for `quadruped` classifies this as a locomotion platform with no manipulator.
251
+
252
+ ---
253
+
254
+ ### Franka Panda — fixed-base arm (no masses declared)
255
+
256
+ ```
257
+ ╔════════════════════════════════════════════════════╗
258
+ ║ urdf_validate — Franka_Panda.urdf ║
259
+ ╚════════════════════════════════════════════════════╝
260
+ [SCHEMA] ⚠ WARN — 25 issues
261
+ [WARN] Link 'panda_base1' has no inertial block (mass unknown) — will cause physics engine instability
262
+ [WARN] Link 'panda_base_arm' has no inertial block (mass unknown) — will cause physics engine instability
263
+ [WARN] Link 'panda_link1' has no inertial block (mass unknown) — will cause physics engine instability
264
+ [WARN] Link 'panda_link2' has no inertial block (mass unknown) — will cause physics engine instability
265
+ [WARN] Link 'panda_link3' has no inertial block (mass unknown) — will cause physics engine instability
266
+ [WARN] Link 'panda_link4' has no inertial block (mass unknown) — will cause physics engine instability
267
+ [WARN] Link 'panda_link5' has no inertial block (mass unknown) — will cause physics engine instability
268
+ [WARN] Link 'panda_link6' has no inertial block (mass unknown) — will cause physics engine instability
269
+ [WARN] Link 'panda_link7' has no inertial block (mass unknown) — will cause physics engine instability
270
+ [WARN] Link 'frankie_leftfinger' has no inertial block (mass unknown) — will cause physics engine instability
271
+ [WARN] Link 'frankie_rightfinger' has no inertial block (mass unknown) — will cause physics engine instability
272
+ [INFO] (2 visual-without-collision notices + 12 mesh resolution notices)
273
+ [PHYSICS] 15 links — mass: 0 exact, 15 missing · inertia: 0 exact, 15 missing
274
+ [STATICS] COM unknown (missing)
275
+ [STATICS] joints: PASS
276
+ [STABILITY] UNKNOWN — robot type 'unknown' — stability only computed for wheeled robots
277
+ [WORKSPACE] max reach 1.255 m vertical 1.626 m horizontal 1.062 m (estimated)
278
+ from base 1.643 m
279
+ [OVERALL] WARN confidence: LOW
280
+ Full report: Franka_Panda_validation.json
281
+ ```
282
+
283
+ Exit 1. No masses declared in this public URDF variant — a common issue with arm-only files intended for kinematic use only. Workspace is still computed from joint limits alone.
284
+
285
+ ---
286
+
287
+ ### ground_vehicle — wheeled vehicle without manipulator
288
+
289
+ ```
290
+ ╔════════════════════════════════════════════════════╗
291
+ ║ urdf_validate — ground_vehicle.urdf ║
292
+ ╚════════════════════════════════════════════════════╝
293
+ [SCHEMA] ✓ PASS
294
+ [PHYSICS] ✓ 5 links — all mass & inertia declared
295
+ [STATICS] COM [0.000, 0.000, -0.025] m height -0.025 m total mass 120.000 kg (estimated)
296
+ Heaviest: chassis (83.3%)
297
+ [STATICS] joints: PASS
298
+ j_wheel_fl req 0.0 Nm declared 50.0 Nm no margin PASS — OK — torque negligible
299
+ j_wheel_fr req 0.0 Nm declared 50.0 Nm no margin PASS — OK — torque negligible
300
+ j_wheel_rl req 0.0 Nm declared 50.0 Nm no margin PASS — OK — torque negligible
301
+ j_wheel_rr req 0.0 Nm declared 50.0 Nm no margin PASS — OK — torque negligible
302
+ [STABILITY] ✓ STABLE margin 300.0 mm
303
+ [WORKSPACE] N/A — not applicable — robot type 'ground_vehicle' has no manipulator
304
+ [WARN] User declared --robot-type=ground_vehicle, but link-name heuristic suggests wheeled
305
+ [OVERALL] PASS confidence: HIGH
306
+ Full report: ground_vehicle_validation.json
307
+ ```
308
+
309
+ Exit 0. Stability runs the wheel-contact heuristic (ground_vehicle profile: locomotion_model="wheeled"). Workspace is N/A — this robot category has no manipulator. `--robot-type ground_vehicle` was supplied explicitly. The heuristic mismatch warning (ground_vehicle vs wheeled) is expected: both share the same wheel-contact algorithm but ground_vehicle disables workspace checks.
310
+
311
+ ---
312
+
313
+ ### aerial_drone — airborne robot (4 fixed rotors)
314
+
315
+ ```
316
+ ╔════════════════════════════════════════════════════╗
317
+ ║ urdf_validate — aerial_drone.urdf ║
318
+ ╚════════════════════════════════════════════════════╝
319
+ [SCHEMA] ✓ PASS
320
+ [PHYSICS] ✓ 5 links — all mass & inertia declared
321
+ [STATICS] COM [0.000, 0.000, 0.000] m height 0.000 m total mass 1.900 kg (estimated)
322
+ Heaviest: body (78.9%)
323
+ [STABILITY] N/A — not applicable — robot type 'aerial' has no ground contact
324
+ [WORKSPACE] N/A — not applicable — robot type 'aerial' has no manipulator
325
+ [WARN] User declared --robot-type=aerial, but link-name heuristic suggests unknown
326
+ [OVERALL] PASS confidence: HIGH
327
+ Full report: aerial_drone_validation.json
328
+ ```
329
+
330
+ Exit 0. Both stability (no ground contact) and workspace (no manipulator) are N/A. `--robot-type aerial` was supplied explicitly. Schema and statics still run.
331
+
332
+ ---
333
+
334
+ ## User-declared robot info
335
+
336
+ When heuristics cannot reliably classify your robot — non-English link names, non-standard wheel geometry, hexapod naming conventions — you can declare the information directly:
337
+
338
+ ```bash
339
+ # Legged robot with known foot contact links
340
+ urdf_validate my_hexapod.urdf \
341
+ --robot-type legged \
342
+ --contact-links foot_fl,foot_fr,foot_ml,foot_mr,foot_rl,foot_rr
343
+
344
+ # Robot with a non-obvious arm chain
345
+ urdf_validate my_robot.urdf \
346
+ --arm-root arm_base_link \
347
+ --arm-tip tool_flange
348
+ ```
349
+
350
+ Declared values are used directly and labeled `exact` in the JSON output. The corresponding heuristic still runs in the background: if it disagrees, a warning is added to the report naming the mismatch. The declared value always wins — the disagreement is surfaced, never silently resolved.
351
+
352
+ If you omit these flags, heuristic output is used and labeled `estimated` to make clear it is an unverified inference, not a verified value.
353
+
354
+ ## Capability profiles
355
+
356
+ The tool uses a built-in profile table to decide which checks apply to each robot category. Consult this table when the default output shows N/A or UNKNOWN and you want to understand why:
357
+
358
+ | Robot type | Stability check | Workspace check |
359
+ |---|---|---|
360
+ | `wheeled` | Runs (wheel-contact heuristic) | Runs if arm chain detected |
361
+ | `ground_vehicle` | Runs (wheel-contact heuristic) | N/A — ground vehicles have no manipulator |
362
+ | `arm_only` | N/A — fixed-base arm has no ground contact | Runs |
363
+ | `legged` / `quadruped` | UNKNOWN — foot contacts must be declared via `--contact-links` | N/A — legged robots have no manipulator |
364
+ | `humanoid` | UNKNOWN — foot-contact algorithm not yet implemented | Runs if arm chain detected |
365
+ | `aerial` | N/A — airborne robot has no ground contact | N/A — aerial vehicles have no manipulator |
366
+ | `unknown` | UNKNOWN — cannot determine contact geometry | Runs |
367
+
368
+ **N/A** means the check does not apply to this robot category and is excluded from the overall status derivation. **UNKNOWN** means the check was attempted but could not produce a result — either the required data is unavailable or the algorithm does not yet cover this case.
369
+
370
+ Use `--robot-type` to force the category. Without it, the link-name heuristic runs and the result is labeled `estimated`.
371
+
372
+ ## Payload-augmented statics
373
+
374
+ Pass `--payload-mass <kg>` to recompute gravity torques with a carried load included. The `[STATICS]` section shows the payload contribution and re-evaluates PASS/WARN/FAIL per joint:
375
+
376
+ ```bash
377
+ urdf_validate fetch.urdf --payload-mass 5.0
378
+ urdf_validate fetch.urdf --payload-mass 5.0 --payload-link gripper_link
379
+ ```
380
+
381
+ `--payload-link` sets which link the load is attached to. It defaults to the automatically-detected end-effector. `payload_mass` and `payload_link` are included in the JSON output.
382
+
383
+ Payload torque is computed as the cross-product of the moment arm (payload link world position relative to each joint) × gravity force. The same PASS/WARN/FAIL thresholds apply: PASS ≥ 1.5×, WARN 1.0–1.5×, FAIL < 1.0×.
384
+
385
+ ## CI integration
386
+
387
+ ### GitHub Actions
388
+
389
+ ```yaml
390
+ name: URDF validation
391
+
392
+ on:
393
+ push:
394
+ paths: ['**.urdf', '**.xacro']
395
+ pull_request:
396
+ paths: ['**.urdf', '**.xacro']
397
+
398
+ jobs:
399
+ validate:
400
+ runs-on: ubuntu-latest
401
+ steps:
402
+ - uses: actions/checkout@v4
403
+
404
+ - uses: actions/setup-python@v5
405
+ with:
406
+ python-version: '3.11'
407
+
408
+ - name: Install urdf-validator
409
+ run: pip install "urdf-validator[full]"
410
+
411
+ - name: Validate URDF
412
+ run: urdf_validate robot/my_robot.urdf --output-dir /tmp/reports
413
+
414
+ - name: Upload validation report
415
+ if: always()
416
+ uses: actions/upload-artifact@v4
417
+ with:
418
+ name: urdf-validation-report
419
+ path: /tmp/reports/*.json
420
+ ```
421
+
422
+ The `urdf_validate` step exits non-zero on any WARN or FAIL finding, failing the CI job. Remove the `paths:` filter if you want to run on every push regardless of which files changed.
423
+
424
+ ### Validating multiple URDFs
425
+
426
+ ```yaml
427
+ - name: Validate all URDFs
428
+ run: |
429
+ find . -name '*.urdf' | while read f; do
430
+ echo "=== $f ==="
431
+ urdf_validate "$f" || exit 1
432
+ done
433
+ ```
434
+
435
+ ### Allowing warnings but blocking failures
436
+
437
+ ```yaml
438
+ - name: Validate URDF (block on FAIL only)
439
+ run: |
440
+ urdf_validate robot/my_robot.urdf
441
+ code=$?
442
+ if [ $code -eq 2 ]; then exit 1; fi
443
+ ```
444
+
445
+ Exit code 1 (WARN) passes; only exit code 2 (FAIL / UNKNOWN) fails the job.
446
+
447
+ ---
448
+
449
+ ## Output sections
450
+
451
+ ### `[SCHEMA]`
452
+
453
+ Structural checks. Severities:
454
+
455
+ | Severity | Example |
456
+ |---|---|
457
+ | `CRITICAL` | Joint references a link that does not exist |
458
+ | `CRITICAL` | Kinematic loop detected |
459
+ | `WARN` | Non-positive-definite inertia tensor |
460
+ | `WARN` | Missing `<inertial>` block on a non-fixed link |
461
+ | `WARN` | Inverted joint limits (lower > upper) |
462
+ | `INFO` | Missing effort or velocity limit |
463
+ | `INFO` | Mesh file not found (`package://` paths require a sourced ROS workspace) |
464
+ | `INFO` | Robot has more than 50 links |
465
+
466
+ ### `[STATICS]`
467
+
468
+ Computed at the declared `--pose` (default: zero pose).
469
+
470
+ - **COM** — full-body centre of mass `[x, y, z]` in metres, total mass in kg.
471
+ - **Per joint** — gravity torque required (`req`), declared effort limit (`declared`), margin = `declared / req`. `PASS` ≥ 1.5×, `WARN` 1.0–1.5×, `FAIL` < 1.0×.
472
+
473
+ ### `[STABILITY]`
474
+
475
+ Available for wheeled robots by default. Contact point detection runs three passes in priority order:
476
+
477
+ 1. Links named `*wheel*`
478
+ 2. Cylindrical links with a wheel-like radius-to-length ratio (r/L > 0.3)
479
+ 3. Links named `*caster*` with cylinder or sphere geometry
480
+
481
+ Use `--contact-links` to supply contact link names directly and bypass this heuristic entirely — required for legged robots and any configuration where link naming does not follow these conventions.
482
+
483
+ Reports signed margin in mm (positive = stable, negative = tipping), tip direction, COM height ratio, and tipping angle.
484
+
485
+ | `com_height_ratio_class` | Ratio | Meaning |
486
+ |---|---|---|
487
+ | `very_stable` | < 0.5 | Passive tip resistance |
488
+ | `stable` | 0.5 – 1.0 | Normal for wheeled mobile robots |
489
+ | `manageable` | 1.0 – 2.0 | Typical humanoid standing |
490
+ | `requires_active_balancing` | 2.0 – 3.0 | Needs active balance control |
491
+ | `will_fall` | > 3.0 | Will fall without fast active control |
492
+
493
+ ### `[WORKSPACE]`
494
+
495
+ Monte Carlo FK sampling over joint limits. The arm chain is detected automatically via a BFS + DOF heuristic. Use `--arm-root` / `--arm-tip` to specify the chain boundary explicitly when auto-detection picks the wrong chain (e.g. a gripper chain instead of the full arm, or an arm on a non-standard robot).
496
+
497
+ Computes `max_reach`, `vertical_reach`, `horizontal_reach`, and `reach_from_base`. With `--task`, also reports whether the arm can reach the target height and whether the COM stays over the support polygon during full extension.
498
+
499
+ ### `[OVERALL]`
500
+
501
+ Worst status across all sections. Confidence level:
502
+
503
+ | Level | Condition |
504
+ |---|---|
505
+ | `HIGH` | All link masses and inertia tensors declared |
506
+ | `MEDIUM` | ≥ 50% of link masses declared |
507
+ | `LOW` | Sparse physics data |
508
+
509
+ ---
510
+
511
+ ## Confidence labels
512
+
513
+ Every physics estimate carries an explicit label:
514
+
515
+ | Label | Meaning |
516
+ |---|---|
517
+ | `exact` | Value read directly from a declared URDF field, or supplied via a user override flag |
518
+ | `estimated` | Derived from declared masses and geometry via analytical formula, or from a heuristic that ran without a user declaration to cross-check against |
519
+ | `guessed` | Heuristic estimate (e.g. mesh geometry — no explicit dims available) |
520
+ | `missing` | No data available |
521
+ | `simulated` | Cross-validated against MuJoCo simulation (`--deep` mode) |
522
+
523
+ The tool never presents an estimated value as ground truth.
524
+
525
+ ---
526
+
527
+ ## JSON output
528
+
529
+ Every run writes `<robot>_validation.json` containing the full `ValidationReport`. The schema is stable across minor versions and documented in [`docs/json_schema.md`](docs/json_schema.md).
530
+
531
+ ```json
532
+ {
533
+ "overall_status": "WARN",
534
+ "confidence_level": "MEDIUM",
535
+ "robot_type": "wheeled",
536
+ "robot_type_confidence": "exact",
537
+ "statics": {
538
+ "full_body_com": [0.045, 0.001, 0.260],
539
+ "total_mass": 121.538,
540
+ "com_height_above_ground": 0.260,
541
+ "weakest_joint_name": "torso_lift_joint",
542
+ "status": "PASS"
543
+ },
544
+ "stability": {
545
+ "stable": true,
546
+ "margin_mm": 43.7,
547
+ "com_height_ratio": 0.69,
548
+ "com_height_ratio_class": "stable",
549
+ "tipping_angle_deg": 35.8,
550
+ "contact_confidence": "estimated",
551
+ "status": "PASS"
552
+ }
553
+ }
554
+ ```
555
+
556
+ `robot_type_confidence` and `contact_confidence` are `"exact"` when the value was supplied via `--robot-type` or `--contact-links`, and `"estimated"` when derived from the heuristic alone.
557
+
558
+ ---
559
+
560
+ ## Task-query API
561
+
562
+ For AI agents and programmatic callers, `urdf_validator` exposes a structured Python interface. It runs the full physics pipeline and returns per-sub-check results without spawning a subprocess:
563
+
564
+ ```python
565
+ from urdf_validator_main.api.task_runner import run_pick_task, run_pick_sweep
566
+ from urdf_validator_main.api.task_schema import TaskQueryRequest
567
+
568
+ req = TaskQueryRequest(
569
+ urdf_path="fetch.urdf",
570
+ task_type="pick",
571
+ target_position=[0.5, 0.0, 0.8], # [x, y, z] metres, robot frame
572
+ target_orientation="top_down", # or "side", [r,p,y], [qw,qx,qy,qz]
573
+ object_mass_kg=0.5,
574
+ terrain_angle_deg=0.0,
575
+ )
576
+ resp = run_pick_task(req)
577
+ print(resp.overall_status) # "PASS" | "FAIL" | "UNKNOWN"
578
+ for check in resp.sub_checks:
579
+ print(check.name, check.status, check.reason)
580
+ ```
581
+
582
+ Sub-checks returned per request:
583
+
584
+ | Sub-check | Passes when |
585
+ |---|---|
586
+ | `reach` | `reach_from_base >= dist(target_position)` |
587
+ | `reach_orientation` | ≥ 5% of workspace samples satisfy `target_orientation` within tolerance |
588
+ | `payload_strength` | all joint effort margins ≥ 1.0× with `object_mass_kg` included |
589
+ | `stability_during_reach` | COM XY shift at maximum reach stays within the stability polygon margin |
590
+ | `self_collision` | ≥ 95% of sampled arm poses are collision-free |
591
+
592
+ To sweep over a list of parameter variations (different heights, payloads, target positions):
593
+
594
+ ```python
595
+ responses = run_pick_sweep([req1, req2, req3])
596
+ # Returns List[TaskQueryResponse], order preserved, failures isolated
597
+ ```
598
+
599
+ Full schema: [`urdf_validator_main/api/task_schema.py`](urdf_validator_main/api/task_schema.py) and [`urdf_validator_main/api/task_runner.py`](urdf_validator_main/api/task_runner.py). Documented in [`docs/json_schema.md`](docs/json_schema.md).
600
+
601
+ ---
602
+
603
+ ## Status
604
+
605
+ | Version | Month | Status | Delivered |
606
+ |---|---|---|---|
607
+ | v0.1 | 1 | **Complete** | Parser, schema checks, physics confidence labels, no-crash on all 6 reference URDFs |
608
+ | v0.2 | 2 | **Complete** | Chain walker, full-body COM, gravity torques, MuJoCo ground-truth validation (0% error) |
609
+ | v0.3 | 3 | **Complete** | Robot type detection, support polygon, COM projection stability check |
610
+ | v0.4 | 4 | **Complete** | Workspace FK, task reachability, full report pipeline, JSON export |
611
+ | v0.5 | 5 | **Complete** | Pose flags, geometry contact detection, COM height ratio, `--deep` MuJoCo wiring, JSON schema docs, performance (PR2: 12.5s → 4.1s) |
612
+ | v0.6 | 6 | **Complete** | `--robot-type`, `--contact-links`, `--arm-root`/`--arm-tip` user override flags; heuristic cross-check and mismatch warnings; `exact` vs `estimated` confidence labeling |
613
+ | v0.7 | 7 | **Complete** | Capability profiles (N/A vs UNKNOWN); `--payload-mass` payload-augmented statics |
614
+ | v0.8 | 8 | **Complete** | Orientation-aware reachability (position + orientation, not position alone) |
615
+ | v0.9 | 9 | **Complete** | Real-pose COM stability during reach; self-collision/clearance checks |
616
+ | v0.10 | 10 | **Complete** | Structured task-query API for AI agents and programmatic callers |
617
+ | v0.11 | 11 | **Complete** | Hardening — full regression across all 6 reference robots + capability-profile URDFs |
618
+ | v1.0 | 12 | **Complete** | Public release — ROS Discourse announcement, pip package |
619
+
620
+ ---
621
+
622
+ ## Dependencies
623
+
624
+ **Core** (`pip install urdf-validator`): `urdf_parser_py`, `numpy`, `shapely`, `ikpy`
625
+
626
+ **Xacro** (`pip install "urdf-validator[xacro]"`): adds `xacro` (`.xacro` / `.xacro` preprocessing)
627
+
628
+ **MuJoCo** (`pip install "urdf-validator[mujoco]"`): adds `mujoco` (`--deep` simulation mode)
629
+
630
+ **Full** (`pip install "urdf-validator[full]"`): both `xacro` and `mujoco`
631
+
632
+ No ROS installation required.
633
+
634
+ ## Known limitations
635
+
636
+ | Limitation | Status |
637
+ |---|---|
638
+ | Humanoid foot-contact stability | `PENDING` — stability is always `UNKNOWN` for `humanoid` robots. Supply foot positions with `--contact-links` to get a real margin. |
639
+ | Unknown-type contact fallback | `PENDING` — for `--robot-type unknown`, stability is `UNKNOWN`. Lowest-link fallback is not yet implemented. |
640
+ | Mimic joints | `OPEN` — mimic followers (common in parallel grippers) are treated as fixed. Joint angles and torques for mimic joints are not computed. |
641
+ | SDF format | `DEFERRED` — only URDF (`.urdf`, `.xacro`) is supported. |
642
+ | `--pose home` | Falls back silently to zero pose. URDF has no standard home-configuration field (that lives in SRDF, outside this tool's scope). |
643
+ | Per-arm workspace breakdown | Aggregated — `max_reach` is the maximum across all detected arm chains, not per-arm. A `--detailed` flag for per-arm envelopes is a planned future feature. |
644
+ | Payload capacity estimate | `PENDING` — `payload_capacity_kg` field exists in the report model but is not populated. |
645
+ | `--deep` drop test | `PENDING` — the 2-second MuJoCo drop test is not implemented. Only the static cross-validation pass runs under `--deep`. |
646
+
647
+ ## License
648
+
649
+ MIT