urdf-mesh-optimizer 0.2.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omkar Chandrakant Kshirsagar
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,323 @@
1
+ Metadata-Version: 2.4
2
+ Name: urdf-mesh-optimizer
3
+ Version: 0.2.0
4
+ Summary: Mesh optimization toolkit for ROS2, MoveIt, and digital twins: decimation, URDF export, and geometric validation for robot simulation.
5
+ Author: Omkar Chandrakant Kshirsagar
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ookkshirsagar/urdf-mesh-optimizer
8
+ Project-URL: Repository, https://github.com/ookkshirsagar/urdf-mesh-optimizer
9
+ Project-URL: Issues, https://github.com/ookkshirsagar/urdf-mesh-optimizer/issues
10
+ Project-URL: Changelog, https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/main/CHANGELOG.md
11
+ Keywords: ros2,moveit,mesh,decimation,urdf,robotics,simulation,collision-mesh
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: open3d>=0.18.0
25
+ Requires-Dist: numpy>=1.24.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
28
+ Requires-Dist: black>=24.0.0; extra == "dev"
29
+ Requires-Dist: flake8>=7.0.0; extra == "dev"
30
+ Requires-Dist: pre-commit>=3.6.0; extra == "dev"
31
+ Requires-Dist: build>=1.0.0; extra == "dev"
32
+ Provides-Extra: viz
33
+ Requires-Dist: matplotlib>=3.8.0; extra == "viz"
34
+ Requires-Dist: pillow>=10.0.0; extra == "viz"
35
+ Requires-Dist: trimesh>=4.0.0; extra == "viz"
36
+ Requires-Dist: manifold3d>=2.3.0; extra == "viz"
37
+ Requires-Dist: shapely>=2.0.0; extra == "viz"
38
+ Requires-Dist: scipy>=1.10.0; extra == "viz"
39
+ Requires-Dist: networkx>=3.0; extra == "viz"
40
+ Requires-Dist: pyrender>=0.1.45; extra == "viz"
41
+ Dynamic: license-file
42
+
43
+ <div align="center">
44
+
45
+ # urdf-mesh-optimizer
46
+
47
+ **Turn heavy CAD exports into real-time-ready visual and collision meshes for ROS 2, MoveIt and Gazebo.**
48
+
49
+ [![CI](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml/badge.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml)
50
+ [![Python 3.10 to 3.12](https://img.shields.io/badge/python-3.10%20to%203.12-blue.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/pyproject.toml)
51
+ [![ROS 2](https://img.shields.io/badge/ROS%202-URDF%20assets-22314E?logo=ros)](https://docs.ros.org/)
52
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/LICENSE)
53
+
54
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/hero.png" alt="AC servo motor: 370,062-triangle CAD export reduced to a 20,000-triangle visual mesh and a 1,500-triangle collision mesh" width="100%">
55
+
56
+ </div>
57
+
58
+ A 200,000-triangle CAD export dropped straight into MoveIt or Gazebo slows planning to a crawl. This command-line tool turns raw meshes into clean, lightweight visual and collision meshes, and tells you exactly how much shape was lost.
59
+
60
+ ## Highlights
61
+
62
+ - **Whole robot in one command.** Point it at a URDF and every mesh gets optimized, with the URDF rewritten to match.
63
+ - **Measured, not eyeballed.** Every output comes with a Hausdorff deviation report.
64
+ - **Ready for CI.** A MoveIt load check fails the build when a mesh is missing, empty or over budget.
65
+ - **No ROS install needed.** Outputs drop straight into any `*_description` package.
66
+
67
+ <p align="center">
68
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/pipeline.svg" alt="Pipeline: raw mesh, clean, QEM decimation, visual and collision meshes, URDF, MoveIt load check, with metrics report" width="100%">
69
+ </p>
70
+
71
+ ## Quick start
72
+
73
+ ```bash
74
+ git clone https://github.com/ookkshirsagar/urdf-mesh-optimizer.git
75
+ cd urdf-mesh-optimizer
76
+ pip install -e .
77
+ ```
78
+
79
+ ```bash
80
+ # A whole robot: every collision mesh in the URDF, URDF rewritten to match
81
+ urdf-mesh-optimizer --urdf my_robot.urdf --output ./optimized --collision 2000
82
+
83
+ # One mesh
84
+ urdf-mesh-optimizer --input part.stl --output part_small.stl --triangles 5000
85
+ ```
86
+
87
+ Requires Python 3.10 to 3.12. Open3D has no Python 3.13 wheels yet; use a 3.12 virtual environment or the [Docker image](#docker).
88
+
89
+ On minimal Linux systems (servers, WSL, containers), an `ImportError` such as `libEGL.so.1: cannot open shared object file` means Open3D's system libraries are missing:
90
+
91
+ ```bash
92
+ sudo apt install libegl1 libgl1 libx11-6 libudev1 libusb-1.0-0 libgomp1
93
+ ```
94
+
95
+ ## Results on industrial parts
96
+
97
+ Six parametric components built from catalogue dimensions (bores, keyways, bolt circles, involute teeth, T-slots), exported as densely as a CAD "fine" STL, then run through the same code path as the CLI. Left of each divider is the CAD model; right is the collision mesh with every triangle drawn.
98
+
99
+ <p align="center">
100
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/gallery.png" alt="Grid of six industrial components, each split between the CAD render and the decimated collision mesh, with reduction and deviation metrics" width="100%">
101
+ </p>
102
+
103
+ | Component | CAD triangles | Collision triangles | Reduction | Deviation (RMS) | Deviation (max) |
104
+ |---|---:|---:|---:|---:|---:|
105
+ | AC servo motor | 370,062 | 1,500 | 99.59% | 0.75 mm | 2.55 mm |
106
+ | Involute spur gear | 418,512 | 3,000 | 99.28% | 0.50 mm | 1.59 mm |
107
+ | Weld-neck flange | 301,612 | 2,000 | 99.34% | 0.85 mm | 2.90 mm |
108
+ | T-slot extrusion | 673,400 | 1,000 | 99.85% | 0.63 mm | 2.10 mm |
109
+ | Pillow block bearing | 245,408 | 2,000 | 99.19% | 0.47 mm | 3.36 mm |
110
+ | Timing pulley | 765,876 | 2,500 | 99.67% | 0.41 mm | 1.52 mm |
111
+
112
+ ### Where the geometry went
113
+
114
+ Each CAD surface below is coloured by its distance to its collision mesh. Flat faces come through untouched; the small error that remains sits on curved surfaces such as the flange bore and the bearing housing.
115
+
116
+ <p align="center">
117
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/hausdorff_heatmap.png" alt="Spur gear, weld-neck flange and pillow block coloured by per-vertex deviation from their collision meshes" width="100%">
118
+ </p>
119
+
120
+ <details>
121
+ <summary>How these figures are made</summary>
122
+
123
+ <br>
124
+
125
+ Part geometry is in [`docs/figures/parts.py`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/docs/figures/parts.py) and every number shown is in [`docs/figures/results.json`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/docs/figures/results.json). Deviation is the Hausdorff distance, estimated from 50,000 sample points per surface, so values vary slightly between runs. The heatmap measures one direction (CAD to collision); the table's maximum also measures the reverse, which catches collision triangles that cut across concave features.
126
+
127
+ To regenerate everything (about 3 minutes, no GPU needed):
128
+
129
+ ```bash
130
+ pip install -e ".[viz]"
131
+ sudo apt install libegl1 libgl1-mesa-dri
132
+ python docs/generate_figures.py
133
+ ```
134
+
135
+ </details>
136
+
137
+ ## Usage
138
+
139
+ ### Whole robot
140
+
141
+ ```bash
142
+ urdf-mesh-optimizer \
143
+ --urdf my_robot_description/urdf/my_robot.urdf \
144
+ --output ./my_robot_optimized \
145
+ --collision 2000 \
146
+ --mesh-uri-prefix package://my_robot_description/meshes_optimized
147
+ ```
148
+
149
+ This writes a copy of the URDF with updated mesh paths, the new meshes (`meshes/collision/`, plus `meshes/visual/` if you pass `--visual N`), and a `urdf_report.json` with per-mesh triangle counts and deviation.
150
+
151
+ Only mesh references change. Joints, inertials, materials, primitive shapes and `ros2_control` / `gazebo` tags are copied as they are.
152
+
153
+ <details>
154
+ <summary>Details: package lookup, visual meshes, xacro, dry run</summary>
155
+
156
+ <br>
157
+
158
+ - **Package lookup.** `package://` names are found next to the URDF, in a sourced ROS 2 workspace (`AMENT_PREFIX_PATH`), or with `--package-path NAME=DIR`.
159
+ - **Visual meshes** are left alone unless you pass `--visual N`, because they usually carry colours and materials.
160
+ - **Shared meshes** are processed once. Meshes already under budget are copied without decimation.
161
+ - **Unsupported formats** such as COLLADA `.dae` keep their original path and are listed as skipped.
162
+ - **xacro files** must be expanded first: `xacro robot.urdf.xacro > robot.urdf`.
163
+ - **`--dry-run`** prints the plan without writing anything.
164
+
165
+ </details>
166
+
167
+ **Tested on a real robot.** Using the Universal Robots UR5e's visual geometry as collision meshes (the way many CAD-exported robots ship), all seven links were processed in under 2 seconds:
168
+
169
+ | Link mesh | Before | After | Deviation (RMS) |
170
+ |---|---:|---:|---:|
171
+ | base | 4,838 | 1,499 | 1.5 mm |
172
+ | shoulder | 23,290 | 1,500 | 1.6 mm |
173
+ | upper arm | 40,124 | 1,500 | 2.7 mm |
174
+ | forearm | 15,058 | 1,500 | 2.1 mm |
175
+ | wrist 1 | 17,288 | 1,500 | 1.1 mm |
176
+ | wrist 2 | 20,280 | 1,499 | 1.1 mm |
177
+ | wrist 3 | 911 | 911 (already under budget) | 0 mm |
178
+ | **Total** | **121,789** | **9,909** | |
179
+
180
+ <sub>Meshes from [Universal_Robots_ROS2_Description](https://github.com/UniversalRobots/Universal_Robots_ROS2_Description) (BSD-3-Clause), converted from `.dae` to STL. For scale, Universal Robots' hand-made collision meshes for this arm total 7,558 triangles.</sub>
181
+
182
+ ### Single mesh
183
+
184
+ ```bash
185
+ # To a fixed triangle count
186
+ urdf-mesh-optimizer --input model.stl --output model_small.obj --triangles 5000
187
+
188
+ # By percentage (keep 20%)
189
+ urdf-mesh-optimizer --input env.ply --output env_small.ply --reduction 80
190
+ ```
191
+
192
+ ### Visual and collision pair from one mesh
193
+
194
+ ```bash
195
+ urdf-mesh-optimizer --input robot_cell.stl --urdf-output ./output --visual 20000 --collision 3000
196
+ ```
197
+
198
+ This produces `robot_cell_visual.stl`, `robot_cell_collision.stl`, JSON reports, and a `<link>` snippet ready to paste into your URDF.
199
+
200
+ ### A folder of meshes
201
+
202
+ ```bash
203
+ urdf-mesh-optimizer --folder ./cell_assets --output ./optimized --collision 5000 --save-report
204
+ ```
205
+
206
+ <details>
207
+ <summary>More options</summary>
208
+
209
+ <br>
210
+
211
+ | Flag | Effect |
212
+ |---|---|
213
+ | `--save-report` | Write a JSON report next to each output |
214
+ | `--dry-run` | Show what would happen without writing files |
215
+ | `--format obj` | Force the output format regardless of file extension |
216
+ | `--skip-hausdorff` | Skip the deviation measurement (faster on huge meshes) |
217
+ | `-v` | Verbose logging |
218
+
219
+ Run `urdf-mesh-optimizer --help` for the full reference. Exit codes: `0` success, `1` a reported error, `2` invalid arguments.
220
+
221
+ Supported formats for reading and writing: STL, OBJ, PLY, OFF, GLB and glTF. Changing the output extension converts the format.
222
+
223
+ </details>
224
+
225
+ ### Docker
226
+
227
+ ```bash
228
+ docker build -t urdf-mesh-optimizer .
229
+ docker run --rm -v "$PWD:/work" urdf-mesh-optimizer --urdf robot.urdf --output out --collision 2000
230
+ ```
231
+
232
+ ## Validation and benchmarks
233
+
234
+ **MoveIt load check.** `python examples/moveit_cell_demo/run_demo.py` runs the full pipeline and finishes with a static check: the URDF is valid XML, every mesh file exists and loads, and the collision mesh is within budget. It exits `0` on success and `1` otherwise, so it works as a CI gate.
235
+
236
+ **Speed and quality.** Decimating a 389,120-triangle mesh to targets between 500 and 20,000 triangles takes about 4 seconds each, and the deviation stays flat across the whole range:
237
+
238
+ ![Benchmark plots: deviation, output file size and processing time per target triangle count](https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/benchmark_tradeoff.png)
239
+
240
+ <details>
241
+ <summary>Benchmark table and report format</summary>
242
+
243
+ <br>
244
+
245
+ | Target triangles | Reduction | File size | Time | Deviation (max) | Deviation (RMS) |
246
+ |---:|---:|---:|---:|---:|---:|
247
+ | 20,000 | 94.86% | 18.6 MB to 0.95 MB | 4.01 s | 0.0319 | 0.0100 |
248
+ | 10,000 | 97.43% | 18.6 MB to 0.48 MB | 4.11 s | 0.0302 | 0.0100 |
249
+ | 5,000 | 98.72% | 18.6 MB to 0.24 MB | 4.53 s | 0.0314 | 0.0099 |
250
+ | 2,000 | 99.49% | 18.6 MB to 0.10 MB | 4.12 s | 0.0313 | 0.0100 |
251
+ | 1,000 | 99.74% | 18.6 MB to 0.05 MB | 4.05 s | 0.0284 | 0.0100 |
252
+ | 500 | 99.87% | 18.6 MB to 0.02 MB | 4.92 s | 0.0338 | 0.0109 |
253
+
254
+ Deviations are in mesh units. Reproduce with `python benchmarks/generate_report.py`; raw output is in [`benchmarks/performance_results.json`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/benchmarks/performance_results.json).
255
+
256
+ Each run's `--save-report` JSON looks like this:
257
+
258
+ ```json
259
+ {
260
+ "input_file": "robot_cell.stl",
261
+ "output_file": "output/robot_cell_collision.stl",
262
+ "original_triangles": 389120,
263
+ "reduced_triangles": 3000,
264
+ "reduction_percent": 99.23,
265
+ "file_size_before_mb": 18.555,
266
+ "file_size_after_mb": 0.143,
267
+ "processing_time_sec": 3.65,
268
+ "hausdorff_max": 0.032679,
269
+ "hausdorff_mean": 0.008853,
270
+ "hausdorff_rms": 0.009990
271
+ }
272
+ ```
273
+
274
+ </details>
275
+
276
+ ## Why not MeshLab or Blender?
277
+
278
+ They're excellent for one-off manual cleanup. This tool is for the repeatable case: the same settings across a whole robot, every time, scriptable and runnable in CI, with a number for how much each mesh changed.
279
+
280
+ ## Roadmap
281
+
282
+ - [x] Whole-robot URDF mode
283
+ - [x] Docker image and automated PyPI releases
284
+ - [ ] COLLADA (`.dae`) input
285
+ - [ ] Convex decomposition for collision meshes (CoACD / V-HACD)
286
+ - [ ] MuJoCo (MJCF) and Gazebo (SDF) export
287
+ - [ ] MoveIt planning-time benchmark, before and after
288
+
289
+ ## Contributing
290
+
291
+ Pull requests are welcome; for larger changes, please open an issue first. See [CONTRIBUTING.md](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CONTRIBUTING.md) for setup and [CHANGELOG.md](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CHANGELOG.md) for release history.
292
+
293
+ ```bash
294
+ pip install -e ".[dev]"
295
+ pre-commit install
296
+ pytest tests/
297
+ ```
298
+
299
+ <details>
300
+ <summary>Project layout</summary>
301
+
302
+ <br>
303
+
304
+ ```
305
+ optimizer/ the package
306
+ core.py load, clean, simplify, save
307
+ metrics.py Hausdorff deviation and JSON reports
308
+ urdf_robot.py whole-robot URDF mode
309
+ urdf_export.py visual and collision pair with URDF snippet
310
+ moveit_check.py static MoveIt load check
311
+ cli.py command-line interface
312
+ examples/ end-to-end MoveIt demo
313
+ benchmarks/ benchmark script and results
314
+ docs/ figure generator, part models, images
315
+ tests/ 72 tests
316
+ .github/ CI, release workflow, issue templates, Dependabot
317
+ ```
318
+
319
+ </details>
320
+
321
+ ## Citation and license
322
+
323
+ If this tool helps your research or product, please cite it using [`CITATION.cff`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CITATION.cff) (GitHub's "Cite this repository" button). Released under the [MIT License](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/LICENSE). Built on [Open3D](https://www.open3d.org/).
@@ -0,0 +1,281 @@
1
+ <div align="center">
2
+
3
+ # urdf-mesh-optimizer
4
+
5
+ **Turn heavy CAD exports into real-time-ready visual and collision meshes for ROS 2, MoveIt and Gazebo.**
6
+
7
+ [![CI](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml/badge.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml)
8
+ [![Python 3.10 to 3.12](https://img.shields.io/badge/python-3.10%20to%203.12-blue.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/pyproject.toml)
9
+ [![ROS 2](https://img.shields.io/badge/ROS%202-URDF%20assets-22314E?logo=ros)](https://docs.ros.org/)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/LICENSE)
11
+
12
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/hero.png" alt="AC servo motor: 370,062-triangle CAD export reduced to a 20,000-triangle visual mesh and a 1,500-triangle collision mesh" width="100%">
13
+
14
+ </div>
15
+
16
+ A 200,000-triangle CAD export dropped straight into MoveIt or Gazebo slows planning to a crawl. This command-line tool turns raw meshes into clean, lightweight visual and collision meshes, and tells you exactly how much shape was lost.
17
+
18
+ ## Highlights
19
+
20
+ - **Whole robot in one command.** Point it at a URDF and every mesh gets optimized, with the URDF rewritten to match.
21
+ - **Measured, not eyeballed.** Every output comes with a Hausdorff deviation report.
22
+ - **Ready for CI.** A MoveIt load check fails the build when a mesh is missing, empty or over budget.
23
+ - **No ROS install needed.** Outputs drop straight into any `*_description` package.
24
+
25
+ <p align="center">
26
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/pipeline.svg" alt="Pipeline: raw mesh, clean, QEM decimation, visual and collision meshes, URDF, MoveIt load check, with metrics report" width="100%">
27
+ </p>
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ git clone https://github.com/ookkshirsagar/urdf-mesh-optimizer.git
33
+ cd urdf-mesh-optimizer
34
+ pip install -e .
35
+ ```
36
+
37
+ ```bash
38
+ # A whole robot: every collision mesh in the URDF, URDF rewritten to match
39
+ urdf-mesh-optimizer --urdf my_robot.urdf --output ./optimized --collision 2000
40
+
41
+ # One mesh
42
+ urdf-mesh-optimizer --input part.stl --output part_small.stl --triangles 5000
43
+ ```
44
+
45
+ Requires Python 3.10 to 3.12. Open3D has no Python 3.13 wheels yet; use a 3.12 virtual environment or the [Docker image](#docker).
46
+
47
+ On minimal Linux systems (servers, WSL, containers), an `ImportError` such as `libEGL.so.1: cannot open shared object file` means Open3D's system libraries are missing:
48
+
49
+ ```bash
50
+ sudo apt install libegl1 libgl1 libx11-6 libudev1 libusb-1.0-0 libgomp1
51
+ ```
52
+
53
+ ## Results on industrial parts
54
+
55
+ Six parametric components built from catalogue dimensions (bores, keyways, bolt circles, involute teeth, T-slots), exported as densely as a CAD "fine" STL, then run through the same code path as the CLI. Left of each divider is the CAD model; right is the collision mesh with every triangle drawn.
56
+
57
+ <p align="center">
58
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/gallery.png" alt="Grid of six industrial components, each split between the CAD render and the decimated collision mesh, with reduction and deviation metrics" width="100%">
59
+ </p>
60
+
61
+ | Component | CAD triangles | Collision triangles | Reduction | Deviation (RMS) | Deviation (max) |
62
+ |---|---:|---:|---:|---:|---:|
63
+ | AC servo motor | 370,062 | 1,500 | 99.59% | 0.75 mm | 2.55 mm |
64
+ | Involute spur gear | 418,512 | 3,000 | 99.28% | 0.50 mm | 1.59 mm |
65
+ | Weld-neck flange | 301,612 | 2,000 | 99.34% | 0.85 mm | 2.90 mm |
66
+ | T-slot extrusion | 673,400 | 1,000 | 99.85% | 0.63 mm | 2.10 mm |
67
+ | Pillow block bearing | 245,408 | 2,000 | 99.19% | 0.47 mm | 3.36 mm |
68
+ | Timing pulley | 765,876 | 2,500 | 99.67% | 0.41 mm | 1.52 mm |
69
+
70
+ ### Where the geometry went
71
+
72
+ Each CAD surface below is coloured by its distance to its collision mesh. Flat faces come through untouched; the small error that remains sits on curved surfaces such as the flange bore and the bearing housing.
73
+
74
+ <p align="center">
75
+ <img src="https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/hausdorff_heatmap.png" alt="Spur gear, weld-neck flange and pillow block coloured by per-vertex deviation from their collision meshes" width="100%">
76
+ </p>
77
+
78
+ <details>
79
+ <summary>How these figures are made</summary>
80
+
81
+ <br>
82
+
83
+ Part geometry is in [`docs/figures/parts.py`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/docs/figures/parts.py) and every number shown is in [`docs/figures/results.json`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/docs/figures/results.json). Deviation is the Hausdorff distance, estimated from 50,000 sample points per surface, so values vary slightly between runs. The heatmap measures one direction (CAD to collision); the table's maximum also measures the reverse, which catches collision triangles that cut across concave features.
84
+
85
+ To regenerate everything (about 3 minutes, no GPU needed):
86
+
87
+ ```bash
88
+ pip install -e ".[viz]"
89
+ sudo apt install libegl1 libgl1-mesa-dri
90
+ python docs/generate_figures.py
91
+ ```
92
+
93
+ </details>
94
+
95
+ ## Usage
96
+
97
+ ### Whole robot
98
+
99
+ ```bash
100
+ urdf-mesh-optimizer \
101
+ --urdf my_robot_description/urdf/my_robot.urdf \
102
+ --output ./my_robot_optimized \
103
+ --collision 2000 \
104
+ --mesh-uri-prefix package://my_robot_description/meshes_optimized
105
+ ```
106
+
107
+ This writes a copy of the URDF with updated mesh paths, the new meshes (`meshes/collision/`, plus `meshes/visual/` if you pass `--visual N`), and a `urdf_report.json` with per-mesh triangle counts and deviation.
108
+
109
+ Only mesh references change. Joints, inertials, materials, primitive shapes and `ros2_control` / `gazebo` tags are copied as they are.
110
+
111
+ <details>
112
+ <summary>Details: package lookup, visual meshes, xacro, dry run</summary>
113
+
114
+ <br>
115
+
116
+ - **Package lookup.** `package://` names are found next to the URDF, in a sourced ROS 2 workspace (`AMENT_PREFIX_PATH`), or with `--package-path NAME=DIR`.
117
+ - **Visual meshes** are left alone unless you pass `--visual N`, because they usually carry colours and materials.
118
+ - **Shared meshes** are processed once. Meshes already under budget are copied without decimation.
119
+ - **Unsupported formats** such as COLLADA `.dae` keep their original path and are listed as skipped.
120
+ - **xacro files** must be expanded first: `xacro robot.urdf.xacro > robot.urdf`.
121
+ - **`--dry-run`** prints the plan without writing anything.
122
+
123
+ </details>
124
+
125
+ **Tested on a real robot.** Using the Universal Robots UR5e's visual geometry as collision meshes (the way many CAD-exported robots ship), all seven links were processed in under 2 seconds:
126
+
127
+ | Link mesh | Before | After | Deviation (RMS) |
128
+ |---|---:|---:|---:|
129
+ | base | 4,838 | 1,499 | 1.5 mm |
130
+ | shoulder | 23,290 | 1,500 | 1.6 mm |
131
+ | upper arm | 40,124 | 1,500 | 2.7 mm |
132
+ | forearm | 15,058 | 1,500 | 2.1 mm |
133
+ | wrist 1 | 17,288 | 1,500 | 1.1 mm |
134
+ | wrist 2 | 20,280 | 1,499 | 1.1 mm |
135
+ | wrist 3 | 911 | 911 (already under budget) | 0 mm |
136
+ | **Total** | **121,789** | **9,909** | |
137
+
138
+ <sub>Meshes from [Universal_Robots_ROS2_Description](https://github.com/UniversalRobots/Universal_Robots_ROS2_Description) (BSD-3-Clause), converted from `.dae` to STL. For scale, Universal Robots' hand-made collision meshes for this arm total 7,558 triangles.</sub>
139
+
140
+ ### Single mesh
141
+
142
+ ```bash
143
+ # To a fixed triangle count
144
+ urdf-mesh-optimizer --input model.stl --output model_small.obj --triangles 5000
145
+
146
+ # By percentage (keep 20%)
147
+ urdf-mesh-optimizer --input env.ply --output env_small.ply --reduction 80
148
+ ```
149
+
150
+ ### Visual and collision pair from one mesh
151
+
152
+ ```bash
153
+ urdf-mesh-optimizer --input robot_cell.stl --urdf-output ./output --visual 20000 --collision 3000
154
+ ```
155
+
156
+ This produces `robot_cell_visual.stl`, `robot_cell_collision.stl`, JSON reports, and a `<link>` snippet ready to paste into your URDF.
157
+
158
+ ### A folder of meshes
159
+
160
+ ```bash
161
+ urdf-mesh-optimizer --folder ./cell_assets --output ./optimized --collision 5000 --save-report
162
+ ```
163
+
164
+ <details>
165
+ <summary>More options</summary>
166
+
167
+ <br>
168
+
169
+ | Flag | Effect |
170
+ |---|---|
171
+ | `--save-report` | Write a JSON report next to each output |
172
+ | `--dry-run` | Show what would happen without writing files |
173
+ | `--format obj` | Force the output format regardless of file extension |
174
+ | `--skip-hausdorff` | Skip the deviation measurement (faster on huge meshes) |
175
+ | `-v` | Verbose logging |
176
+
177
+ Run `urdf-mesh-optimizer --help` for the full reference. Exit codes: `0` success, `1` a reported error, `2` invalid arguments.
178
+
179
+ Supported formats for reading and writing: STL, OBJ, PLY, OFF, GLB and glTF. Changing the output extension converts the format.
180
+
181
+ </details>
182
+
183
+ ### Docker
184
+
185
+ ```bash
186
+ docker build -t urdf-mesh-optimizer .
187
+ docker run --rm -v "$PWD:/work" urdf-mesh-optimizer --urdf robot.urdf --output out --collision 2000
188
+ ```
189
+
190
+ ## Validation and benchmarks
191
+
192
+ **MoveIt load check.** `python examples/moveit_cell_demo/run_demo.py` runs the full pipeline and finishes with a static check: the URDF is valid XML, every mesh file exists and loads, and the collision mesh is within budget. It exits `0` on success and `1` otherwise, so it works as a CI gate.
193
+
194
+ **Speed and quality.** Decimating a 389,120-triangle mesh to targets between 500 and 20,000 triangles takes about 4 seconds each, and the deviation stays flat across the whole range:
195
+
196
+ ![Benchmark plots: deviation, output file size and processing time per target triangle count](https://raw.githubusercontent.com/ookkshirsagar/urdf-mesh-optimizer/v0.2.0/docs/img/benchmark_tradeoff.png)
197
+
198
+ <details>
199
+ <summary>Benchmark table and report format</summary>
200
+
201
+ <br>
202
+
203
+ | Target triangles | Reduction | File size | Time | Deviation (max) | Deviation (RMS) |
204
+ |---:|---:|---:|---:|---:|---:|
205
+ | 20,000 | 94.86% | 18.6 MB to 0.95 MB | 4.01 s | 0.0319 | 0.0100 |
206
+ | 10,000 | 97.43% | 18.6 MB to 0.48 MB | 4.11 s | 0.0302 | 0.0100 |
207
+ | 5,000 | 98.72% | 18.6 MB to 0.24 MB | 4.53 s | 0.0314 | 0.0099 |
208
+ | 2,000 | 99.49% | 18.6 MB to 0.10 MB | 4.12 s | 0.0313 | 0.0100 |
209
+ | 1,000 | 99.74% | 18.6 MB to 0.05 MB | 4.05 s | 0.0284 | 0.0100 |
210
+ | 500 | 99.87% | 18.6 MB to 0.02 MB | 4.92 s | 0.0338 | 0.0109 |
211
+
212
+ Deviations are in mesh units. Reproduce with `python benchmarks/generate_report.py`; raw output is in [`benchmarks/performance_results.json`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/benchmarks/performance_results.json).
213
+
214
+ Each run's `--save-report` JSON looks like this:
215
+
216
+ ```json
217
+ {
218
+ "input_file": "robot_cell.stl",
219
+ "output_file": "output/robot_cell_collision.stl",
220
+ "original_triangles": 389120,
221
+ "reduced_triangles": 3000,
222
+ "reduction_percent": 99.23,
223
+ "file_size_before_mb": 18.555,
224
+ "file_size_after_mb": 0.143,
225
+ "processing_time_sec": 3.65,
226
+ "hausdorff_max": 0.032679,
227
+ "hausdorff_mean": 0.008853,
228
+ "hausdorff_rms": 0.009990
229
+ }
230
+ ```
231
+
232
+ </details>
233
+
234
+ ## Why not MeshLab or Blender?
235
+
236
+ They're excellent for one-off manual cleanup. This tool is for the repeatable case: the same settings across a whole robot, every time, scriptable and runnable in CI, with a number for how much each mesh changed.
237
+
238
+ ## Roadmap
239
+
240
+ - [x] Whole-robot URDF mode
241
+ - [x] Docker image and automated PyPI releases
242
+ - [ ] COLLADA (`.dae`) input
243
+ - [ ] Convex decomposition for collision meshes (CoACD / V-HACD)
244
+ - [ ] MuJoCo (MJCF) and Gazebo (SDF) export
245
+ - [ ] MoveIt planning-time benchmark, before and after
246
+
247
+ ## Contributing
248
+
249
+ Pull requests are welcome; for larger changes, please open an issue first. See [CONTRIBUTING.md](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CONTRIBUTING.md) for setup and [CHANGELOG.md](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CHANGELOG.md) for release history.
250
+
251
+ ```bash
252
+ pip install -e ".[dev]"
253
+ pre-commit install
254
+ pytest tests/
255
+ ```
256
+
257
+ <details>
258
+ <summary>Project layout</summary>
259
+
260
+ <br>
261
+
262
+ ```
263
+ optimizer/ the package
264
+ core.py load, clean, simplify, save
265
+ metrics.py Hausdorff deviation and JSON reports
266
+ urdf_robot.py whole-robot URDF mode
267
+ urdf_export.py visual and collision pair with URDF snippet
268
+ moveit_check.py static MoveIt load check
269
+ cli.py command-line interface
270
+ examples/ end-to-end MoveIt demo
271
+ benchmarks/ benchmark script and results
272
+ docs/ figure generator, part models, images
273
+ tests/ 72 tests
274
+ .github/ CI, release workflow, issue templates, Dependabot
275
+ ```
276
+
277
+ </details>
278
+
279
+ ## Citation and license
280
+
281
+ If this tool helps your research or product, please cite it using [`CITATION.cff`](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/CITATION.cff) (GitHub's "Cite this repository" button). Released under the [MIT License](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/LICENSE). Built on [Open3D](https://www.open3d.org/).
@@ -0,0 +1,3 @@
1
+ """urdf-mesh-optimizer: lightweight visual and collision meshes for ROS 2 / MoveIt."""
2
+
3
+ __version__ = "0.2.0"