filaire 0.2.0b1__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 (39) hide show
  1. filaire-0.2.0b1/LICENSE +21 -0
  2. filaire-0.2.0b1/PKG-INFO +580 -0
  3. filaire-0.2.0b1/README.md +544 -0
  4. filaire-0.2.0b1/pyproject.toml +57 -0
  5. filaire-0.2.0b1/setup.cfg +4 -0
  6. filaire-0.2.0b1/src/filaire/__init__.py +24 -0
  7. filaire-0.2.0b1/src/filaire/__main__.py +3 -0
  8. filaire-0.2.0b1/src/filaire/blender/__init__.py +38 -0
  9. filaire-0.2.0b1/src/filaire/blender/checks.py +177 -0
  10. filaire-0.2.0b1/src/filaire/blender/physics.py +445 -0
  11. filaire-0.2.0b1/src/filaire/blender/run.py +125 -0
  12. filaire-0.2.0b1/src/filaire/cable.py +486 -0
  13. filaire-0.2.0b1/src/filaire/catalog.py +280 -0
  14. filaire-0.2.0b1/src/filaire/centerline.py +110 -0
  15. filaire-0.2.0b1/src/filaire/cli.py +275 -0
  16. filaire-0.2.0b1/src/filaire/connectors.py +554 -0
  17. filaire-0.2.0b1/src/filaire/generator.py +301 -0
  18. filaire-0.2.0b1/src/filaire/geometry2d.py +172 -0
  19. filaire-0.2.0b1/src/filaire/mesh3d.py +196 -0
  20. filaire-0.2.0b1/src/filaire/render.py +147 -0
  21. filaire-0.2.0b1/src/filaire/sim/__init__.py +0 -0
  22. filaire-0.2.0b1/src/filaire/sim/checks.py +295 -0
  23. filaire-0.2.0b1/src/filaire/sim/mujoco_model.py +342 -0
  24. filaire-0.2.0b1/src/filaire/sim/usd_reader.py +151 -0
  25. filaire-0.2.0b1/src/filaire/usd_export.py +546 -0
  26. filaire-0.2.0b1/src/filaire/validate.py +58 -0
  27. filaire-0.2.0b1/src/filaire.egg-info/PKG-INFO +580 -0
  28. filaire-0.2.0b1/src/filaire.egg-info/SOURCES.txt +37 -0
  29. filaire-0.2.0b1/src/filaire.egg-info/dependency_links.txt +1 -0
  30. filaire-0.2.0b1/src/filaire.egg-info/entry_points.txt +2 -0
  31. filaire-0.2.0b1/src/filaire.egg-info/requires.txt +21 -0
  32. filaire-0.2.0b1/src/filaire.egg-info/top_level.txt +1 -0
  33. filaire-0.2.0b1/tests/test_blender.py +28 -0
  34. filaire-0.2.0b1/tests/test_cli.py +78 -0
  35. filaire-0.2.0b1/tests/test_connectors.py +64 -0
  36. filaire-0.2.0b1/tests/test_generator.py +69 -0
  37. filaire-0.2.0b1/tests/test_geometry.py +59 -0
  38. filaire-0.2.0b1/tests/test_sim.py +87 -0
  39. filaire-0.2.0b1/tests/test_usd.py +144 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Régis Sarmento Caon
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,580 @@
1
+ Metadata-Version: 2.4
2
+ Name: filaire
3
+ Version: 0.2.0b1
4
+ Summary: Standard cable assets generator for Physical AI: cables of standard types (power, USB, Ethernet, HDMI, audio, industrial, ribbon, coiled) with male and female connectors, cut or stripped ends, exported to OpenUSD as UsdPhysics rigid-segment chains and checked in MuJoCo.
5
+ Author: Daniel Régis Sarmento Caon
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drscaon/filaire
8
+ Project-URL: Issues, https://github.com/drscaon/filaire/issues
9
+ Project-URL: Changelog, https://github.com/drscaon/filaire/blob/main/CHANGELOG.md
10
+ Project-URL: Suite, https://github.com/drscaon/primvaria
11
+ Keywords: cable,connector,simulation,openusd,mujoco,procedural,robotics,physical-ai,synthetic-data,robot-learning,primvaria
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.24
21
+ Requires-Dist: scipy>=1.10
22
+ Requires-Dist: shapely>=2.1
23
+ Requires-Dist: usd-core>=24.0
24
+ Provides-Extra: render
25
+ Requires-Dist: matplotlib>=3.7; extra == "render"
26
+ Provides-Extra: sim
27
+ Requires-Dist: mujoco>=3.2; extra == "sim"
28
+ Provides-Extra: blender
29
+ Requires-Dist: bpy>=5.1; python_version == "3.13" and extra == "blender"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Requires-Dist: ruff>=0.5; extra == "dev"
33
+ Requires-Dist: matplotlib>=3.7; extra == "dev"
34
+ Requires-Dist: mujoco>=3.2; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ <h1 align="center">
38
+ <picture>
39
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/logo-dark.svg">
40
+ <img alt="Filaire" src="https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/logo.svg" width="360">
41
+ </picture>
42
+ </h1>
43
+
44
+ <p align="center"><strong>Standard cable assets generator for Physical AI.</strong></p>
45
+
46
+ <p align="center">
47
+ <a href="https://github.com/drscaon/filaire/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/drscaon/filaire/actions/workflows/ci.yml/badge.svg"></a>
48
+ <img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-3776AB">
49
+ <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green">
50
+ </p>
51
+
52
+ Filaire generates cables of standard types (power cords, USB, Ethernet,
53
+ HDMI, audio, microphone, speaker wire, sensor, ribbon and coiled handset
54
+ cords, DC barrel and Micro-USB) with their real connectors, **male and female**, or with cut or
55
+ stripped ends, and exports them to **OpenUSD** as physics-ready assets: a
56
+ chain of rigid segments with `UsdPhysics` joints whose springs come from
57
+ the cable's bending and torsional stiffness. Every connector comes with the
58
+ receptacle it plugs into (device port, inlet, wall socket, panel connector,
59
+ or the female end of an extension), derived from the plug so that the two
60
+ always mate. The assets are checked in **MuJoCo** (dropped on the ground,
61
+ sagging like a beam, plugged in at the right and wrong angles) and in
62
+ **Blender**, where an add-on sets the rigid bodies and joints up by itself
63
+ when a file is imported. Every file passes OpenUSD's own validators.
64
+
65
+ It is built for **Physical AI**: robots that route, plug and unplug cables
66
+ are trained and tested in simulation, and they need many different cables
67
+ with exact ground truth, not one capsule chain. Everything can run on CPU.
68
+ Filaire belongs to a series with
69
+ [Granulaire](https://github.com/drscaon/granulaire) (granular assets),
70
+ [Maillage](https://github.com/drscaon/maillage) (textiles) and
71
+ [Quincaillerie](https://github.com/drscaon/quincaillerie) (fasteners, cells
72
+ and holders), and follows the same approach: distributions instead of one
73
+ shape, measured outputs, honest limits.
74
+
75
+ ![Cables generated by Filaire, rendered in Blender](https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/hero.jpg)
76
+
77
+ *One cable of each of the 14 presets, with its usual ends (left: power cords
78
+ C13 and C7, DC barrel, USB-C, USB-A to C, USB-A to Micro-B, Ethernet; right:
79
+ HDMI, 3.5 mm jack, XLR microphone cable, speaker wire, M12 sensor cable, IDC
80
+ ribbon, coiled handset cord), imported from the OpenUSD files into Blender
81
+ and rendered with Cycles.*
82
+
83
+ ## What it does
84
+
85
+ - Generates *N* cables of a preset, **1 m long by default** (`--length` for
86
+ another length, or the preset's retail lengths), lying straight, as a
87
+ slack random curve or coiled on the floor.
88
+ - Puts on **each end, independently**, a connector plug, a receptacle on a
89
+ cable (the female end of an extension or a coupler), a clean cut or a
90
+ stripped end with the cores out: 17 connector families, 28 end variants,
91
+ any of them on any cable.
92
+ - Models the cable as a **Kirchhoff rod**: rigid segments of 1.5 cable
93
+ widths, joints with twist and bending springs `G·J / L` and `E·I / L`
94
+ computed from the cross-section (round, figure-8 zip cord, flat ribbon;
95
+ coiled cords as a helical spring), real masses.
96
+ - Derives every **receptacle from its plug**, layer by layer, with a
97
+ clearance; the keying (any angle, reversible, one orientation) is found
98
+ from the geometry and checked against the standard.
99
+ - Exports **OpenUSD**: one rigid body per segment and per connector, each
100
+ with *one* visual mesh (so every DCC imports one object per body), mass,
101
+ centre of mass and inertia, convex collision parts, joints with drives,
102
+ PhysX attributes for Isaac Sim, a variant set per end (all the end
103
+ variants of a cable in one asset), a `MateFrame` on each connector with
104
+ its insertion axis, depth, clearance, keying and contact gender; and a
105
+ `sockets.usdc` file with the fixed receptacles. `--no-physics` writes
106
+ visual assets only. Every file passes the OpenUSD validators
107
+ (`UsdValidation`).
108
+ - **Checks the assets in MuJoCo** (`filaire sim-test`), read back from the
109
+ files: drop on the ground, cantilever sag against beam theory, coil
110
+ extension against spring theory, insertion at 0°, 90° and 180°.
111
+ - **A Blender add-on** (`filaire blender-addon`): rigid bodies and
112
+ constraints set up on every USD import, and the same drop check in
113
+ Blender (`filaire blender-test`).
114
+
115
+ ## Installation
116
+
117
+ From PyPI:
118
+
119
+ ```bash
120
+ pip install "filaire[render,sim]"
121
+ ```
122
+
123
+ From a clone, for development:
124
+
125
+ ```bash
126
+ python -m venv .venv && source .venv/bin/activate
127
+ pip install -e ".[render,sim]" # add ",dev" for tests and linting
128
+ ```
129
+
130
+ Requires Python 3.10+. Dependencies: NumPy, SciPy, Shapely 2.1+ and the
131
+ OpenUSD bindings (`usd-core`); matplotlib for the previews, MuJoCo for the
132
+ checks. The Blender checks need Blender as a Python module
133
+ (`pip install -e ".[blender]"`, Python 3.13 only) or a Blender executable.
134
+
135
+ ## Usage
136
+
137
+ ```bash
138
+ filaire list # presets
139
+ filaire ends # every end variant
140
+ filaire generate --preset usb_c # 6 cables, 1 m each
141
+ filaire generate --preset ethernet --length 3 --layout coil
142
+ filaire generate --preset power_c13 --end-a c13_plug --end-b stripped
143
+ filaire generate --preset usb_c --end-a usb_c_plug --end-b usb_c_inline # an extension cord
144
+ filaire generate --preset hdmi --length preset --count 12 --seed 4 # retail lengths
145
+ filaire generate --preset audio_jack --color "#c0392b" --modulus 45
146
+ filaire preview output/usb_c # top view (matplotlib)
147
+ filaire connectors --out connectors.png # plug faces and receptacles
148
+ filaire generate --preset hdmi --no-physics # visual assets only
149
+ filaire sim-test output/usb_c # MuJoCo checks
150
+ filaire blender-test output/usb_c # the drop check in Blender (add-on)
151
+ filaire blender-addon # filaire_physics.zip, for Blender 4.4+
152
+ ```
153
+
154
+ `python -m filaire ...` works too. From Python:
155
+
156
+ ```python
157
+ from filaire import generate_scene, write_outputs
158
+
159
+ cables = generate_scene("usb_c", count=6, seed=1) # 1 m, ends drawn from the preset
160
+ write_outputs(cables, "output") # -> output/usb_c/
161
+
162
+ # 2.5 m power cords coiled on the floor, C13 on one end and a stripped end on the other
163
+ cords = generate_scene(
164
+ "power_c13", count=4, length=2.5, layout="coil", ends={"a": "c13_plug", "b": "stripped"}
165
+ )
166
+ write_outputs(cords, "output")
167
+ ```
168
+
169
+ ### Options of `filaire generate`
170
+
171
+ | Option | Default | Effect |
172
+ |---|---|---|
173
+ | `--preset` | `usb_c` | cable type (table below) |
174
+ | `--count N`, `--seed S` | 6, 1 | number of cables, random seed; same options and seed give the same cables |
175
+ | `--length L` | `1` | length in m of every cable; `preset` draws the preset's retail lengths (± tolerance) |
176
+ | `--end-a V`, `--end-b V` | preset's draw | end variant of each end, independently: any `filaire ends` entry, on any cable |
177
+ | `--layout` | preset's draw | `straight`, `slack` (random smooth curve) or `coil` (flat spiral on the floor) |
178
+ | `--color "#rrggbb"` | preset's draw | jacket colour of every cable |
179
+ | `--modulus E` | preset's draw | effective Young's modulus of the cable in MPa (sets the stiffness) |
180
+ | `--max-segments N` | 400 | upper bound on the rigid segments per cable (longer segments beyond it) |
181
+ | `--no-physics` | | visual assets only: no rigid bodies, colliders, joints, physics scene or ground |
182
+ | `--no-vary-…` | | turn one random aspect off, the others unchanged: `layout`, `ends`, `color`, `stiffness`, `density`, `length` |
183
+
184
+ Each random aspect has its own stream: turning one off or forcing it leaves
185
+ the others as they were (same layouts with plain colours, same colours with
186
+ other ends…).
187
+
188
+ ### Presets
189
+
190
+ | Category | Preset | Cross-section | End A | End B (default draw) |
191
+ |---|---|---|---|---|
192
+ | Power | `power_c13` | round Ø6.4 mm, 3 cores | C13 | CEE 7/7 plug, C14 (extension), stripped |
193
+ | | `power_c7` | figure-8, 2 × Ø3.4 mm | C7 | Europlug, stripped |
194
+ | | `dc_barrel` | figure-8, 2 × Ø2.4 mm | DC plug 5.5 × 2.1, 5.5 × 2.5 or 4.0 × 1.7 | stripped, cut, DC jack (extension) |
195
+ | Data | `usb_c` | round Ø4.0 mm | USB-C plug | USB-C plug, USB-C receptacle (extension) |
196
+ | | `usb_a_to_c` | round Ø3.8 mm | USB-A plug | USB-C plug |
197
+ | | `usb_micro` | round Ø3.5 mm | USB-A plug | Micro-B plug |
198
+ | | `ethernet` | round Ø6.0 mm, 4 pairs | RJ45 plug | RJ45 plug, RJ45 coupler |
199
+ | | `hdmi` | round Ø7.0 mm | HDMI plug | HDMI plug, HDMI receptacle (extension) |
200
+ | Audio | `audio_jack` | round Ø3.5 mm | 3.5 mm plug | 3.5 mm plug, 3.5 mm socket (extension) |
201
+ | | `xlr_mic` | round Ø6.0 mm | XLR3 female | XLR3 male |
202
+ | | `speaker_wire` | figure-8, 2 × Ø3.2 mm | stripped, cut | stripped, cut |
203
+ | Industrial | `m12_sensor` | round Ø5.0 mm, PUR | M12 female | stripped, cut |
204
+ | | `ribbon_idc` | flat, 10 × 1.27 mm | IDC socket 2 × 5 | IDC socket, cut |
205
+ | Telephone | `coiled_handset` | coil Ø8.5 mm of Ø2.8 mm wire | RJ9 plug | RJ9 plug |
206
+
207
+ Every cable also carries `cut` and `stripped` variants on both ends, and any
208
+ other variant forced with `--end-a` / `--end-b`. Diameters, densities
209
+ (8–90 g/m) and moduli (20–300 MPa, drawn per cable) are realistic orders of
210
+ magnitude, not measurements of a specific product.
211
+
212
+ ![The 14 presets, one cable each](https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/presets.jpg)
213
+
214
+ *Each preset with its usual ends (the first variant of each end), 40 cm
215
+ long (25 cm for the ribbon and the coiled cord), rendered in Blender.*
216
+
217
+ ## Connectors
218
+
219
+ ![Every plug on its cable, 15 mm in front of its receptacle](https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/plugs.jpg)
220
+
221
+ *The 17 connector families: each plug on a short piece of its cable, 15 mm
222
+ in front of the fixed receptacle derived from it (`sockets.usdc`), rendered
223
+ in Blender.*
224
+
225
+ | Family | Cable side (plug) | Contacts | Receptacle on a cable | Fixed receptacle | Keying | Depth |
226
+ |---|---|---|---|---|---|---|
227
+ | `usb_c` | USB-C plug | male | USB-C receptacle | USB-C port | reversible | 6.65 mm |
228
+ | `usb_a` | USB-A plug | male | | USB-A port | keyed | 12 mm |
229
+ | `usb_micro` | Micro-B plug | male | | Micro-B port | keyed | 5.6 mm |
230
+ | `hdmi_a` | HDMI plug | male | HDMI receptacle | HDMI port | keyed | 9.5 mm |
231
+ | `rj45` | RJ45 plug | male | RJ45 coupler | RJ45 jack | keyed | 14 mm |
232
+ | `rj9` | RJ9 plug | male | | RJ9 handset jack | keyed | 10 mm |
233
+ | `jack35` | 3.5 mm plug | male | 3.5 mm socket | 3.5 mm socket | none | 15 mm |
234
+ | `c13` | C13 connector | **female** | C14 plug | C14 inlet | keyed | 13 mm |
235
+ | `c7` | C7 connector | **female** | | C8 inlet | reversible | 10 mm |
236
+ | `cee77` | CEE 7/7 plug | male | | wall socket (simplified) | reversible | 34 mm |
237
+ | `cee716` | Europlug | male | | Europlug socket (simplified) | reversible | 25 mm |
238
+ | `xlr3` | XLR3 female | **female** | XLR3 male | XLR3 male (panel) | keyed | 11 mm |
239
+ | `m12a` | M12 A-coded female | **female** | | M12 male (panel) | keyed | 8 mm |
240
+ | `idc10` | IDC socket 2 × 5 | **female** | | box header 2 × 5 | keyed | 6 mm |
241
+ | `dc5521`, `dc5525`, `dc4017` | DC plug 5.5 × 2.1, 5.5 × 2.5, 4.0 × 1.7 mm | male | DC jack | DC jack | none | 9.5 mm |
242
+
243
+ **Gender and role are kept apart.** *Gender* is the contact gender of the
244
+ standard; *role* is geometric: the plug enters, the receptacle receives. An
245
+ IEC C13 has female contacts but enters the C14 inlet; an XLR3 female enters
246
+ the shell of the male. `filaire:contactGender` and `filaire:role` say both.
247
+
248
+ **The receptacle is derived from the plug.** Each plug is described by its
249
+ cross-sections, layer by layer from the tip (a CEE 7/7 plug: two pins, then
250
+ the round face that enters the socket's recess). In each layer, the
251
+ receptacle's solid is its outline minus the plug's cross-section grown by
252
+ the clearance (0.05–0.3 mm). The plug's holes become the receptacle's pins
253
+ or tongue, the plug's key becomes the keyway, and the two always mate. The
254
+ keying is then *found* by turning the plug in 15° steps and testing for
255
+ overlap, and a test checks it against the standard's.
256
+
257
+ The same test answers which plug goes into which receptacle across families
258
+ (`fits(plug, angle, socket=other)`). For the DC barrels it gives the real
259
+ behaviour: the derived jacks get a centre pin from the plug's bore, so a
260
+ 5.5 × 2.5 mm plug goes loosely onto a 5.5 × 2.1 mm jack, while a 5.5 × 2.1 mm
261
+ plug is stopped by the thicker pin of a 5.5 × 2.5 mm jack.
262
+
263
+ ![Plug faces and the receptacles derived from them](https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/connectors.png)
264
+
265
+ *`filaire connectors`: the plug seen from its tip (left) and the fixed
266
+ receptacle seen from the front (right): opening in dark, pins and tongues in
267
+ light grey.*
268
+
269
+ Dimensions come from published figures where they could be found (USB-C
270
+ receptacle opening and insertion length, HDMI plug and receptacle, RJ45 and
271
+ RJ9 widths and heights, 3.5 mm diameter and length, CEE 7 pins and recess
272
+ depth, IEC 60320 pin spacing, Micro-B receptacle opening, DC barrel diameters
273
+ and the 4.0 × 1.7 mm barrel length); the others (XLR, M12, IDC, C13 face,
274
+ Micro-B chamfers, 5.5 mm barrel length, inner cavities, overmolds) are **approximate** and marked so in
275
+ [`connectors.py`](https://github.com/drscaon/filaire/blob/main/src/filaire/connectors.py), each with its sources. The
276
+ standards themselves were not consulted. Connectors are simplified: no
277
+ latches that move, no threads, no contacts inside the holes, no logos.
278
+
279
+ ## Outputs (per preset, under `output/<preset>/`)
280
+
281
+ | File | Content |
282
+ |---|---|
283
+ | `filaire_<preset>.usdc` | the cables (OpenUSD, Blender: File > Import > USD) |
284
+ | `sockets.usdc` | one fixed receptacle per connector family used by the cables |
285
+ | `cables.csv` | per cable: layout, length, ends, colour, modulus, densities, stiffness, segments, masses |
286
+ | `summary.json` | counts by layout, end and colour; length, mass, stiffness and segment ranges |
287
+ | `sheet.png` | top view (`filaire preview`) |
288
+ | `sim_test/sim_test.json` | MuJoCo report (`filaire sim-test`) |
289
+ | `sim_test/blender_setup.json`, `sim_test/blender_naive.json` | Blender reports and their scenes (`filaire blender-test`) |
290
+
291
+ ### OpenUSD layout
292
+
293
+ Z up, metres, kilograms, `/World` the default prim.
294
+
295
+ ```
296
+ /World
297
+ /PhysicsScene gravity
298
+ /Looks, /PhysicsMaterials UsdPreviewSurface materials; friction and restitution
299
+ /Cables/<cable> Xform, PhysicsArticulationRootAPI, filaire:* attributes,
300
+ variant sets "end_a" and "end_b"
301
+ /Segments/seg_000 … rigid bodies (RigidBodyAPI, MassAPI, PhysxRigidBodyAPI), x along the cable
302
+ /Visual ONE mesh: the piece of tube this segment carries
303
+ /Collision Capsule along x (round, coiled) or Cube (flat, figure-8), purpose guide
304
+ /Joints/joint_000 … PhysicsJoint: translations locked (coiled: transX free),
305
+ drives rotX (twist), rotY, rotZ (bending) [, transX (coil)]
306
+ /End_A, /End_B filled by the selected variant:
307
+ /Body rigid body of the connector or stripped end
308
+ /Visual ONE mesh, a GeomSubset per material (shell, body, cores, copper)
309
+ /Collision/hull_00 … convex parts (MeshCollisionAPI convexHull, purpose guide)
310
+ /MateFrame origin on the mating plane, +Z = insertion direction
311
+ /Attach PhysicsFixedJoint to the end segment
312
+ /Ground collision box, its top face at z = 0
313
+ ```
314
+
315
+ Each rigid body has exactly one visual mesh, as in Quincaillerie: Blender
316
+ and other DCCs import one object per body, so a rigid body set on it moves
317
+ the whole part. Each segment spans the chord between its two joints, so
318
+ neighbouring segments meet exactly at their joint even on a tight curve.
319
+ Every collider carries `PhysxCollisionAPI` offsets and every body
320
+ `PhysxRigidBodyAPI` iterations, read by Isaac Sim and ignored elsewhere.
321
+
322
+ **Without physics** (`--no-physics`, `write_outputs(…, physics=False)`):
323
+ each cable is one mesh, `/World/Cables/<cable>/Visual`, each end a `Body`
324
+ with its `Visual` and `MateFrame`; no rigid bodies, colliders, joints,
325
+ physics materials, physics scene or ground. The `filaire:*` attributes stay.
326
+
327
+ **OpenUSD compliance.** `filaire.validate.validate(file)` runs every
328
+ validator of OpenUSD's `UsdValidation` registry (composition, stage
329
+ metadata, geometry and subsets, material bindings, shaders, UsdPhysics
330
+ bodies, colliders, joints and articulations) on each end variant of each
331
+ cable; the tests and CI require no error and no warning, with and without
332
+ physics.
333
+
334
+ Selecting a variant (`end_b = "usb_c_inline"`, `"cut"`…) swaps the end, its
335
+ collision parts and its joint. Angular drives use UsdPhysics units
336
+ (stiffness per degree); the same values per radian and the rod's stiffness
337
+ are kept as engine-neutral attributes:
338
+
339
+ | Attribute (on the cable) | Content |
340
+ |---|---|
341
+ | `filaire:bendingStiffness` | E·I about the width axis and about the height axis (N·m²) |
342
+ | `filaire:torsionalStiffness` | G·J (N·m²) |
343
+ | `filaire:jointStiffness` | per joint: twist, bend y, bend z (N·m/rad) |
344
+ | `filaire:youngsModulus`, `filaire:shearModulus`, `filaire:linearDensity` | material (Pa, kg/m) |
345
+ | `filaire:segmentLength`, `filaire:segmentCount`, `filaire:restShape` | discretisation; rest shape is straight |
346
+ | `filaire:dampingTime` | joint damping = stiffness × 0.01 s |
347
+ | `filaire:coil:*`, `filaire:axialStiffness` | coiled cords: coil diameter, pitch, turns, wire length; spring k (N/m) |
348
+ | `filaire:length`, `filaire:layout`, `filaire:profile`, `filaire:jacket`, … | what the cable is |
349
+
350
+ | Attribute (on a `MateFrame`) | Content |
351
+ |---|---|
352
+ | `filaire:connector`, `filaire:name`, `filaire:standard` | family and name (`usb_c`, "USB-C plug") |
353
+ | `filaire:role`, `filaire:mount`, `filaire:contactGender` | plug / receptacle, cable / panel, male / female |
354
+ | `filaire:insertionDepth`, `filaire:clearance`, `filaire:keying` | for insertion tasks (m) |
355
+
356
+ ## Physics model
357
+
358
+ - **Rod.** The cable is a chain of N rigid segments (length ≈ 1.5 cable
359
+ widths, at most 400 per cable). Consecutive segments share a joint at
360
+ their common end point with three rotational springs: twist `G·J / L`,
361
+ bending `E·I_y / L` and `E·I_z / L`, the discrete Kirchhoff rod. `E·I` and
362
+ `G·J` come from the cross-section (polygon second moments, so a flat
363
+ ribbon is a hundred times softer across its thickness than across its
364
+ width) and an effective modulus drawn per cable; `G = E / (2 (1 + ν))`,
365
+ ν = 0.45. The rest shape is straight; the authored pose (slack, coiled) is
366
+ a deformed state.
367
+ - **Coiled cords** are modelled along their axis, as a helical spring seen
368
+ as a rod (Wahl's close-coiled formulas): bending `2 E I p / (π D (2 + ν))`,
369
+ twist `E I p / (π D)`, and an axial spring `4 G J / (π D³ n)` shared by
370
+ free `transX` joints. The coil itself is in the visual mesh.
371
+ - **Masses** from the linear density; connectors from the volume of their
372
+ parts and an effective density; inertia tensors from the meshes.
373
+
374
+ ## Blender
375
+
376
+ Blender's USD importer reads geometry and materials but ignores UsdPhysics:
377
+ imported as is, a cable is a heap of loose tube pieces and its connectors
378
+ fall off (see the comparison below). The add-on, adapted from
379
+ Quincaillerie's, sets the physics up once and for all:
380
+
381
+ 1. `filaire blender-addon` writes `filaire_physics.zip`.
382
+ 2. In Blender (4.4 or later): Edit > Preferences > Get Extensions > Install
383
+ from Disk, pick the zip.
384
+ 3. File > Import > Universal Scene Description, as usual: every segment and
385
+ connector gets its rigid body, every joint its constraint. Press Play.
386
+
387
+ On import it moves each body's origin to its centre of mass (Blender uses
388
+ the origin as centre of mass), makes its collision shape a compound of the
389
+ USD shapes (capsules, boxes and convex hulls rebuilt from the file), sets
390
+ mass, friction and small collision margins, turns the cable joints into
391
+ Generic Spring constraints with the USD drives' stiffness and damping
392
+ (translations locked; a coiled cord's axial spring kept) and the connectors'
393
+ fixed joints into Fixed constraints, and runs the rigid body world at 4000
394
+ substeps per second for the light segments. The same file runs as a script
395
+ (`blender -b -P filaire_physics.py -- file.usdc`) without installing.
396
+
397
+ Bullet takes a spring's rest position from the first frame, so in Blender a
398
+ cable keeps the shape it was generated in (coiled, slack) as its rest shape,
399
+ where the file and MuJoCo say straight: a coiled cable stays coiled.
400
+
401
+ ## MuJoCo checks
402
+
403
+ `filaire sim-test output/<preset>` reads the OpenUSD files back (not the
404
+ generator's objects) and runs, on CPU:
405
+
406
+ | Check | What | Pass when |
407
+ |---|---|---|
408
+ | drop | the first cables, with their selected ends, lifted 1 cm and dropped for 3 s in their authored layout | finite, ground penetration below half the cable radius after the impact, all bodies slower than 5 cm/s at the end |
409
+ | sag | a sample of the cable clamped at one end, sagging ≈ 10 % of its length under its own weight; `E·I` read back from the joint drives | tip deflection within 5 % of `w L⁴ / (8 E I)` |
410
+ | coil | coiled cords: the coil hanging from its top | extension within 5 % of `W / (2 k)` |
411
+ | insertion | each plug pushed along its axis (5 N max) into the fixed receptacle and the receptacle on a cable, at 0°, 90° and 180° | goes fully in at the angles its keying allows, stopped before 60 % of the depth at the others |
412
+
413
+ For speed, the drop test simulates the cable with at most 60 links (groups
414
+ of segments merged, springs in series, same `E·I`) and without the twist
415
+ hinge: in generalised coordinates a chain's mass matrix is dense and a step
416
+ costs the cube of its joints. Each joint gets an armature (added inertia)
417
+ and critical damping so that stiff, light links stay stable with a 1 ms
418
+ step; neither changes a static equilibrium, which is what the sag and coil
419
+ checks compare.
420
+
421
+ ![Five cables dropped onto a bar on the floor, simulated in MuJoCo](https://raw.githubusercontent.com/drscaon/filaire/main/docs/images/drop.gif)
422
+
423
+ *Power cord, Cat 6, USB-C, 3.5 mm jack and M12 sensor cable (70 cm),
424
+ steeply tilted and dropped together onto a bar: one end hits first, the
425
+ cables fold over the bar and over each other, connectors attached, and come
426
+ to rest. Simulated in MuJoCo from the OpenUSD files (rest shape straight,
427
+ 60 links per cable, 0.5 ms step), rendered in Blender:
428
+ [`scripts/drop_gif.py`](https://github.com/drscaon/filaire/blob/main/scripts/drop_gif.py).*
429
+
430
+ ### Results
431
+
432
+ `filaire generate --preset <p> --count 3` (1 m cables, seed 1) then
433
+ `filaire sim-test output/<p> --cables 3`, MuJoCo 3.17, one CPU core:
434
+
435
+ | Preset | Segments | `E·I` (N·m²) | Mass with ends (g) | Drop (max speed, penetration) | Stiffness check | Insertion |
436
+ |---|---|---|---|---|---|---|
437
+ | `power_c13` | 104 | 0.0049–0.0073 | 119–187 | 3/3 (0.004 m/s, 0.10 mm) | sag −0.8 % | C13 panel + inline, CEE 7/7 ✓ |
438
+ | `power_c7` | 98 | 0.00066–0.00098 | 49–67 | 3/3 (0.002 m/s, 0.06 mm) | sag −0.8 % | C7, Europlug ✓ |
439
+ | `dc_barrel` | 139 | 0.00016–0.00026 | 30–33 | 3/3 (0.012 m/s, 0.09 mm) | sag −0.8 % | three DC jacks ✓ |
440
+ | `usb_c` | 167 | 0.00050–0.00080 | 46–50 | 3/3 (0.013 m/s, 0.18 mm) | sag −0.8 % | USB-C panel + inline ✓ |
441
+ | `usb_a_to_c` | 175 | 0.00041–0.00065 | 43–46 | 3/3 (0.004 m/s, 0.18 mm) | sag −0.8 % | USB-A, USB-C ✓ |
442
+ | `usb_micro` | 190 | 0.00029–0.00051 | 38–40 | 3/3 (0.004 m/s, 0.10 mm) | sag −0.8 % | USB-A, Micro-B ✓ |
443
+ | `ethernet` | 111 | 0.0050–0.0072 | 61–70 | 3/3 (0.005 m/s, 0.07 mm) | sag −0.8 % | RJ45 panel + inline ✓ |
444
+ | `hdmi` | 95 | 0.0094–0.0139 | 99–107 | 3/3 (0.005 m/s, 0.09 mm) | sag −0.8 % | HDMI panel + inline ✓ |
445
+ | `audio_jack` | 190 | 0.00022–0.00036 | 20–22 | 3/3 (0.003 m/s, 0.13 mm) | sag −0.8 % | 3.5 mm panel + inline ✓ |
446
+ | `xlr_mic` | 111 | 0.0013–0.0022 | 147–151 | 3/3 (0.007 m/s, 0.06 mm) | sag −0.8 % | XLR3 panel + inline ✓ |
447
+ | `speaker_wire` | 104 | 0.00042–0.00067 | 52–55 | 3/3 (0.024 m/s, 0.07 mm) | sag −0.8 % | (stripped / cut ends) |
448
+ | `m12_sensor` | 133 | 0.0015–0.0023 | 65–68 | 3/3 (0.004 m/s, 0.03 mm) | sag −0.8 % | M12 ✓ |
449
+ | `ribbon_idc` | 52 | 0.00011–0.00017 | 14–16 | 3/3 (0.007 m/s, 0.06 mm) | sag −0.8 % | IDC ✓ |
450
+ | `coiled_handset` | 59 | (coil) | 130–139 | 3/3 (0.004 m/s, 0.07 mm) | coil −0.0 % | RJ9 ✓ |
451
+
452
+ Every keyed connector was stopped at 90° and 180° (at most 12 % of its
453
+ depth, the IDC socket's flat face entering a little before its key meets the
454
+ header), every reversible one went in at 180°, the 3.5 mm jack at every
455
+ angle, like the DC barrels. The sag is 0.8 % under the linear beam formula for every cable: the
456
+ geometric nonlinearity of a 10 % deflection, the same for all since the
457
+ sample length is scaled to the stiffness. Generation takes 1–4 s per
458
+ preset (3 cables), the checks 15–30 s (a minute for the coiled cords).
459
+
460
+ ### Blender checks
461
+
462
+ `filaire blender-test output/<preset>` builds, for each tested cable, a scene
463
+ that references the cable file, keeps that cable (with its selected ends),
464
+ lifts it 2 cm and drops it on the ground, then simulates it in Blender
465
+ (Bullet) through the add-on, in its own process:
466
+
467
+ | Check | Pass when |
468
+ |---|---|
469
+ | joints hold | the two bodies of every joint meet within 0.5 mm at its anchor, the connectors' fixed joints included |
470
+ | at rest on the ground | no point more than 0.5 mm under the ground, every body slower than 5 cm/s after 1.5 s |
471
+
472
+ `--mode naive` imports the same scene the way one would by hand (File >
473
+ Import > USD, every mesh an active rigid body, the ground passive) and
474
+ reports how far neighbouring pieces drift apart.
475
+
476
+ `filaire generate --preset <p> --count 2` (1 m cables, seed 1), then
477
+ `filaire blender-test output/<p> --cables 2` and `--mode naive` on the
478
+ first cable, Blender 5.2 (`bpy`), one CPU core:
479
+
480
+ | Preset | Through the add-on | Bodies | Widest joint gap (mm) | Lowest point (mm) | Rigid bodies by hand: pieces drifted apart (mm) | Time per cable (s) |
481
+ |---|---|---|---|---|---|---|
482
+ | `power_c13` | 2/2 | 106 | 0.005 | −0.09 | 145 | 18 |
483
+ | `power_c7` | 2/2 | 100 | 0.006 | +0.02 | 180 | 17 |
484
+ | `dc_barrel` | 2/2 | 141 | 0.009 | +0.05 | 174 | 30 |
485
+ | `usb_c` | 2/2 | 169 | 0.005 | +0.04 | 58 | 42 |
486
+ | `usb_a_to_c` | 2/2 | 177 | 0.005 | +0.04 | 32 | 44 |
487
+ | `usb_micro` | 2/2 | 192 | 0.006 | +0.04 | 35 | 47 |
488
+ | `ethernet` | 2/2 | 113 | 0.005 | +0.01 | 40 | 21 |
489
+ | `hdmi` | 2/2 | 97 | 0.002 | +0.03 | 40 | 16 |
490
+ | `audio_jack` | 2/2 | 192 | 0.006 | +0.04 | 162 | 48 |
491
+ | `xlr_mic` | 2/2 | 113 | 0.008 | −0.03 | 1634 | 21 |
492
+ | `speaker_wire` | 2/2 | 105 | < 0.001 | +0.05 | 10 | 18 |
493
+ | `m12_sensor` | 2/2 | 135 | 0.007 | +0.02 | 818 | 25 |
494
+ | `ribbon_idc` | 2/2 | 54 | < 0.001 | −0.05 | 1240 | 7 |
495
+ | `coiled_handset` | 2/2 | 61 | 0.104 (axial spring) | −0.10 | 2193 | 10 |
496
+
497
+ Through the add-on every cable holds together and comes to rest (a coiled
498
+ cord's joints stretch along its axial spring, by design). Set up by hand,
499
+ the pieces are independent: they spread over the floor, and where a heavy
500
+ connector or a coil's overlapping pieces push on them, they are thrown
501
+ metres away. The checks found a real defect of the first beta: on tight
502
+ curves the rigid segments, as long as the arc they stand for, overlapped
503
+ their neighbours by up to 0.4 mm at the joints; each segment now spans the
504
+ chord between its joints.
505
+
506
+ ## Limits
507
+
508
+ - **Not calibrated.** Moduli and densities are orders of magnitude; no
509
+ cable's `E·I` was measured. The sag check proves the export is consistent
510
+ with the model, not that the model matches a real cable.
511
+ - **Approximate connectors.** See [Connectors](#connectors): simplified
512
+ outlines, no moving latches, threads or contacts; several dimensions not
513
+ from a source.
514
+ - **Elastic, straight rest shape.** No plastic set (a real cable keeps its
515
+ coils), no initial tension in coiled cords, no knots.
516
+ - **Isaac Sim not tested here.** The files follow the standard `UsdPhysics`
517
+ schemas (rigid bodies, D6 joints with drives, articulation root, convex
518
+ hulls) and carry PhysX attributes; they are checked in MuJoCo and Blender
519
+ but have not been loaded in Isaac Sim / PhysX (no NVIDIA GPU), where
520
+ drive units and articulation behaviour may need adjusting.
521
+ - **Rest shape differs in Blender.** See [Blender](#blender): Bullet's
522
+ springs rest in the first frame's pose, the generated one.
523
+ - **Visual per segment.** Each segment carries its piece of tube, so a
524
+ simulated cable shows small creases at the joints when it bends.
525
+
526
+ ## Roadmap
527
+
528
+ 1. Calibration against measured bending stiffness (three-point bending or
529
+ cantilever tests) for a few real cables per preset.
530
+ 2. Plastic set and pre-coiled rest shapes; tied bundles and hanks.
531
+ 3. A PhysX test in Isaac Sim, and MJCF export with MuJoCo's cable plugin.
532
+ 4. More connectors (Schuko and French sockets with their earth contacts,
533
+ D-sub with its screws, DisplayPort, M8) and snap-fit latches.
534
+ 5. Insertion tasks: forces, misalignment tolerances, success labels.
535
+
536
+ ## Development
537
+
538
+ The project follows **GitHub Flow**:
539
+
540
+ 1. `main` is always releasable and protected: changes reach it only through
541
+ pull requests, nobody pushes to it directly.
542
+ 2. Every change starts on a short-lived branch from `main`, named after its
543
+ type: `feat/<name>`, `fix/<name>`, `docs/<name>`, `ci/<name>`, …
544
+ 3. Push the branch and open a pull request into `main`; CI runs on it.
545
+ 4. Merge when CI is green, then delete the branch.
546
+
547
+ Releasing is a change like any other: on a `chore/release-X.Y.Z` branch, bump
548
+ `version` in `pyproject.toml` and move the `Unreleased` entries of
549
+ `CHANGELOG.md` under the new version; merge the pull request, then tag
550
+ `vX.Y.Z` on `main`. The release workflow builds the packages and attaches
551
+ them to the GitHub release.
552
+
553
+ A versioned `pre-push` hook refuses direct pushes to `main`. Enable it once per
554
+ clone with `git config core.hooksPath .githooks` (emergency override:
555
+ `ALLOW_MAIN_PUSH=1 git push`).
556
+
557
+ The README images are made by [`scripts/`](https://github.com/drscaon/filaire/blob/main/scripts/): `make_logo.py` (logo),
558
+ `readme_images.py` (Blender renders through `bpy` of every preset and every
559
+ connector family, connector sheet, GitHub social preview
560
+ `docs/images/social_preview.png`, to upload in the repository's Settings >
561
+ General > Social preview), `drop_gif.py` (the MuJoCo drop animation).
562
+
563
+ ### Continuous integration
564
+
565
+ | Workflow | When | What |
566
+ |---|---|---|
567
+ | `ci.yml` | every push to `main`, every pull request | `ruff check` and `ruff format --check`; `pytest` on Python 3.10–3.13 (Linux) and 3.12 (macOS, Windows); a command-line smoke test with the OpenUSD validators; **the MuJoCo checks of every preset**; **the Blender checks of every preset** through the add-on (with the `bpy` module, Python 3.13), and for comparison the same with rigid bodies set by hand; reports kept as run artifacts; build the wheel and sdist, `twine check`, install the wheel in a clean environment. |
568
+ | `release.yml` | a pushed tag `v*` | checks the tag matches the version in `pyproject.toml`, tests, builds, then creates the GitHub release with the packages and the Blender add-on attached and the notes taken from `CHANGELOG.md`. Versions with `a`, `b` or `rc` are marked as pre-releases. |
569
+ | Dependabot | monthly | pull requests to update the GitHub Actions and the Python dependencies. |
570
+
571
+ ## License
572
+
573
+ [MIT](https://github.com/drscaon/filaire/blob/main/LICENSE) © 2026 Daniel Régis Sarmento Caon
574
+
575
+ The logo is drawn from DejaVu Sans Bold outlines (Bitstream Vera license),
576
+ converted to paths, so it displays without the font installed.
577
+
578
+ ---
579
+
580
+ Filaire is part of [**Primvaria**](https://github.com/drscaon/primvaria), physics-ready variability for Physical AI, in OpenUSD: `pip install primvaria` installs the whole suite.