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.
- urdf_mesh_optimizer-0.2.0/LICENSE +21 -0
- urdf_mesh_optimizer-0.2.0/PKG-INFO +323 -0
- urdf_mesh_optimizer-0.2.0/README.md +281 -0
- urdf_mesh_optimizer-0.2.0/optimizer/__init__.py +3 -0
- urdf_mesh_optimizer-0.2.0/optimizer/cli.py +404 -0
- urdf_mesh_optimizer-0.2.0/optimizer/core.py +111 -0
- urdf_mesh_optimizer-0.2.0/optimizer/metrics.py +122 -0
- urdf_mesh_optimizer-0.2.0/optimizer/moveit_check.py +153 -0
- urdf_mesh_optimizer-0.2.0/optimizer/urdf_export.py +117 -0
- urdf_mesh_optimizer-0.2.0/optimizer/urdf_robot.py +331 -0
- urdf_mesh_optimizer-0.2.0/pyproject.toml +82 -0
- urdf_mesh_optimizer-0.2.0/setup.cfg +4 -0
- urdf_mesh_optimizer-0.2.0/tests/test_cli.py +156 -0
- urdf_mesh_optimizer-0.2.0/tests/test_core.py +221 -0
- urdf_mesh_optimizer-0.2.0/tests/test_moveit_check.py +77 -0
- urdf_mesh_optimizer-0.2.0/tests/test_urdf_export.py +92 -0
- urdf_mesh_optimizer-0.2.0/tests/test_urdf_robot.py +270 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/PKG-INFO +323 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/SOURCES.txt +21 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/dependency_links.txt +1 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/entry_points.txt +2 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/requires.txt +19 -0
- urdf_mesh_optimizer-0.2.0/urdf_mesh_optimizer.egg-info/top_level.txt +1 -0
|
@@ -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
|
+
[](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml)
|
|
50
|
+
[](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/pyproject.toml)
|
|
51
|
+
[](https://docs.ros.org/)
|
|
52
|
+
[](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
|
+

|
|
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
|
+
[](https://github.com/ookkshirsagar/urdf-mesh-optimizer/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/ookkshirsagar/urdf-mesh-optimizer/blob/v0.2.0/pyproject.toml)
|
|
9
|
+
[](https://docs.ros.org/)
|
|
10
|
+
[](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
|
+

|
|
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/).
|