maillage 0.1.0b2__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 (34) hide show
  1. maillage-0.1.0b2/LICENSE +21 -0
  2. maillage-0.1.0b2/PKG-INFO +496 -0
  3. maillage-0.1.0b2/README.md +462 -0
  4. maillage-0.1.0b2/pyproject.toml +55 -0
  5. maillage-0.1.0b2/setup.cfg +4 -0
  6. maillage-0.1.0b2/src/maillage/__init__.py +24 -0
  7. maillage-0.1.0b2/src/maillage/__main__.py +3 -0
  8. maillage-0.1.0b2/src/maillage/animate.py +29 -0
  9. maillage-0.1.0b2/src/maillage/blender/__init__.py +6 -0
  10. maillage-0.1.0b2/src/maillage/blender/drop_test.py +611 -0
  11. maillage-0.1.0b2/src/maillage/cli.py +230 -0
  12. maillage-0.1.0b2/src/maillage/fabrics.py +148 -0
  13. maillage-0.1.0b2/src/maillage/formats.py +99 -0
  14. maillage-0.1.0b2/src/maillage/generator.py +292 -0
  15. maillage-0.1.0b2/src/maillage/mesh.py +192 -0
  16. maillage-0.1.0b2/src/maillage/outline.py +204 -0
  17. maillage-0.1.0b2/src/maillage/presets.py +232 -0
  18. maillage-0.1.0b2/src/maillage/render.py +89 -0
  19. maillage-0.1.0b2/src/maillage/texture.py +163 -0
  20. maillage-0.1.0b2/src/maillage/usd_export.py +151 -0
  21. maillage-0.1.0b2/src/maillage.egg-info/PKG-INFO +496 -0
  22. maillage-0.1.0b2/src/maillage.egg-info/SOURCES.txt +32 -0
  23. maillage-0.1.0b2/src/maillage.egg-info/dependency_links.txt +1 -0
  24. maillage-0.1.0b2/src/maillage.egg-info/entry_points.txt +2 -0
  25. maillage-0.1.0b2/src/maillage.egg-info/requires.txt +18 -0
  26. maillage-0.1.0b2/src/maillage.egg-info/top_level.txt +1 -0
  27. maillage-0.1.0b2/tests/test_blender.py +30 -0
  28. maillage-0.1.0b2/tests/test_cli.py +101 -0
  29. maillage-0.1.0b2/tests/test_formats.py +56 -0
  30. maillage-0.1.0b2/tests/test_generator.py +98 -0
  31. maillage-0.1.0b2/tests/test_mesh.py +66 -0
  32. maillage-0.1.0b2/tests/test_outline.py +64 -0
  33. maillage-0.1.0b2/tests/test_texture.py +61 -0
  34. maillage-0.1.0b2/tests/test_usd.py +63 -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,496 @@
1
+ Metadata-Version: 2.4
2
+ Name: maillage
3
+ Version: 0.1.0b2
4
+ Summary: Realistic textile assets generator for Physical AI: procedural cloth pieces (towels, napkins, sheets, flags) with real sizes, fabrics and patterns, exported to OpenUSD and validated by a cloth drop test in Blender.
5
+ Author: Daniel Régis Sarmento Caon
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drscaon/maillage
8
+ Project-URL: Issues, https://github.com/drscaon/maillage/issues
9
+ Project-URL: Changelog, https://github.com/drscaon/maillage/blob/main/CHANGELOG.md
10
+ Project-URL: Suite, https://github.com/drscaon/primvaria
11
+ Keywords: cloth,textile,simulation,openusd,blender,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: usd-core>=24.0
23
+ Provides-Extra: render
24
+ Requires-Dist: matplotlib>=3.7; extra == "render"
25
+ Requires-Dist: pillow>=9; extra == "render"
26
+ Provides-Extra: blender
27
+ Requires-Dist: bpy>=5.1; python_version == "3.13" and extra == "blender"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7; extra == "dev"
30
+ Requires-Dist: ruff>=0.5; extra == "dev"
31
+ Requires-Dist: matplotlib>=3.7; extra == "dev"
32
+ Requires-Dist: pillow>=9; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ <h1 align="center">
36
+ <picture>
37
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/logo-dark.svg">
38
+ <img alt="Maillage" src="https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/logo.svg" width="400">
39
+ </picture>
40
+ </h1>
41
+
42
+ <p align="center"><strong>Realistic textile assets generator for Physical AI.</strong></p>
43
+
44
+ <p align="center">
45
+ <a href="https://github.com/drscaon/maillage/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/drscaon/maillage/actions/workflows/ci.yml/badge.svg"></a>
46
+ <img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-3776AB">
47
+ <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green">
48
+ </p>
49
+
50
+ Maillage generates towels, tea towels, napkins, tablecloths, bed sheets,
51
+ cleaning cloths, bandanas, silk and knitted scarves and flags with real sizes, fabrics, masses and
52
+ patterns, meshed for cloth simulation and exported to **OpenUSD**. Every piece
53
+ is checked in **Blender** by a drape test: it imports, simulates without
54
+ exploding, stretches little, does not go through obstacles and comes to rest.
55
+
56
+ It is built for **Physical AI**: robots that fold laundry, lay a table or hand
57
+ over a towel are trained and tested in simulation, and they need many
58
+ different cloths, not one subdivided square; perception models need synthetic
59
+ images of crumpled and draped fabric with exact ground truth. Everything can run
60
+ on CPU. Maillage is the sister project of
61
+ [Granulaire](https://github.com/drscaon/granulaire) (granular assets) and
62
+ follows the same approach: distributions instead of one shape, measured
63
+ outputs, honest limits.
64
+
65
+ ![Tea towels draped on the clamped pedestal of the Blender drape test](https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/blender_drape.jpg)
66
+
67
+ *Waffle-cotton tea towels generated by Maillage (solid, gingham, striped),
68
+ imported into Blender and draped by the drape test. Render:
69
+ `maillage drop-test output/tea_towel --render`.*
70
+
71
+ ## What it does
72
+
73
+ - Generates *N* distinct pieces of a preset (12 by default): format drawn from
74
+ real retail sizes, size tolerance, corner rounding, an imperfect cut (wavy
75
+ edges, slight skew), fabric and pattern.
76
+ - Meshes each piece for cloth solvers: near-equilateral triangles for any
77
+ outline (rounded corners, round tablecloths), or a structured quad grid for
78
+ rectangles (the topology of a woven fabric).
79
+ - Draws a procedural pattern (solid, end bands, stripes along or across,
80
+ gingham, border, flag bands) as a PNG texture that follows the fabric, not the cut.
81
+ - **Measures** every mesh (area, perimeter, edge lengths, minimum angles) and
82
+ derives the mass from the fabric's areal density.
83
+ - Exports an OpenUSD scene: meshes with UVs, a textured UsdPreviewSurface
84
+ material, `hem` and `pin` primvars, and the fabric's physical properties as
85
+ custom attributes.
86
+ - **Tests every piece in Blender** (`maillage drop-test`): import, cloth
87
+ simulation, automatic checks, Cusick drape coefficient, the draped meshes
88
+ exported back to OpenUSD and an optional Cycles render.
89
+
90
+ ## Installation
91
+
92
+ From PyPI:
93
+
94
+ ```bash
95
+ pip install "maillage[render]"
96
+ ```
97
+
98
+ From a clone, for development:
99
+
100
+ ```bash
101
+ python -m venv .venv && source .venv/bin/activate
102
+ pip install -e ".[render]" # add ",dev" for tests and linting
103
+ ```
104
+
105
+ Requires Python 3.10+. For the Blender drape test, either put Blender on the
106
+ PATH, or install Blender as a Python module (`pip install -e ".[blender]"`,
107
+ Python 3.13 only, as the `bpy` wheels exist for one Python version). Tested
108
+ with Blender 5.2; Blender 4.2 or later should work.
109
+
110
+ ## Usage
111
+
112
+ ```bash
113
+ maillage list
114
+ maillage generate --preset tea_towel # 12 pieces, triangles
115
+ maillage generate --preset tablecloth --count 20 --seed 3
116
+ maillage generate --preset bath_towel --topology grid --vertices 2500
117
+ maillage generate --preset napkin --fabric silk --no-cut --no-color
118
+ maillage generate --preset tablecloth --format "140x180@2,round:150,oval:140x220"
119
+ maillage preview output/tea_towel # flat sheet (matplotlib)
120
+ maillage drop-test output/tea_towel --pieces 4 --render # Blender drape test
121
+ maillage drop-test output/tea_towel --gif # + animated GIF (Pillow)
122
+ maillage drop-test output/flag --wind 4 --render # flags hang from a pole
123
+ ```
124
+
125
+ `python -m maillage ...` works too. Without the `bpy` module or `blender` on
126
+ the PATH, `maillage drop-test` prints the command to run with your Blender:
127
+
128
+ ```bash
129
+ blender -b -P "$(maillage blender-script)" -- output/tea_towel --render
130
+ ```
131
+
132
+ The script does not import Maillage, so Blender's own Python needs nothing
133
+ installed. From Python:
134
+
135
+ ```python
136
+ from maillage import Variations, generate_scene, write_outputs
137
+
138
+ towels = generate_scene("tea_towel", count=12, seed=1)
139
+ write_outputs(towels, "output") # -> output/tea_towel/
140
+
141
+ # 40 cm linen squares with a perfect cut, 2,000 vertices each, quad grid
142
+ napkins = generate_scene(
143
+ "napkin",
144
+ count=6,
145
+ fabric="linen",
146
+ topology="grid",
147
+ vertices=2000,
148
+ variations=Variations(cut=False),
149
+ )
150
+ write_outputs(napkins, "output")
151
+
152
+ # tablecloths in your own sizes: a 140×180 rectangle twice as often as the rest
153
+ tablecloths = generate_scene("tablecloth", count=12, formats="140x180@2, round:150, oval:140x220")
154
+ write_outputs(tablecloths, "output")
155
+ ```
156
+
157
+ ### Presets
158
+
159
+ | Category | Preset | Formats (cm) | Fabrics |
160
+ |---|---|---|---|
161
+ | Bath | `bath_towel` | 70×140, 90×150 | cotton terry |
162
+ | | `hand_towel` | 50×100, 40×60 | cotton terry |
163
+ | | `washcloth` | 30×30 | cotton terry |
164
+ | Kitchen | `tea_towel` | 50×70, 45×65 | waffle cotton, linen, plain cotton |
165
+ | Table | `napkin` | 40×40, 50×50 | linen, plain cotton |
166
+ | | `tablecloth` | 140×180, 140×240, round ⌀150, oval 140×200 | plain cotton, linen |
167
+ | Bedding | `bed_sheet` | 180×290, 240×300, 270×310 | percale, linen |
168
+ | Cleaning | `microfiber_cloth` | 40×40, 30×30 | microfiber |
169
+ | Accessories | `bandana` | 55×55 | plain cotton |
170
+ | | `silk_scarf` | 90×90, 50×160 | silk |
171
+ | | `scarf` | 25×160, 30×180, 20×140 (knitted winter scarf) | wool knit |
172
+ | Outdoor | `flag` | 100×150, 60×90 (hung by the hoist) | polyester knit |
173
+
174
+ ![The 12 presets side by side, one piece each, draped in Blender](https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/all_presets.jpg)
175
+
176
+ *One piece of each preset (seed 2), simulated 5 s by the Blender drape test
177
+ and rendered with Cycles; each image is framed on its own piece, so scales
178
+ differ (30 cm washcloth to 3 m sheet). Same colours for the three terry
179
+ presets: they share a palette and, with one seed, the same colour draw.*
180
+
181
+ Sizes are common retail formats; values are realistic orders of magnitude,
182
+ not measurements of a specific product. Flags use generic two or three band
183
+ designs with random colours on unequal, asymmetric bands, so that real
184
+ national flags (equal or symmetric bands) are unlikely.
185
+
186
+ ### Fabrics
187
+
188
+ | Fabric | Areal density | Thickness | Friction |
189
+ |---|---|---|---|
190
+ | `cotton_terry` | 450 g/m² | 4 mm | 0.8 |
191
+ | `cotton_waffle` | 220 g/m² | 1.5 mm | 0.6 |
192
+ | `cotton_plain` | 140 g/m² | 0.3 mm | 0.5 |
193
+ | `cotton_percale` | 120 g/m² | 0.25 mm | 0.5 |
194
+ | `linen` | 180 g/m² | 0.35 mm | 0.55 |
195
+ | `denim` | 400 g/m² | 0.7 mm | 0.6 |
196
+ | `fleece` | 250 g/m² | 3 mm | 0.8 |
197
+ | `microfiber` | 300 g/m² | 2 mm | 0.9 |
198
+ | `polyester_knit` | 115 g/m² | 0.2 mm | 0.4 |
199
+ | `silk` | 60 g/m² | 0.1 mm | 0.3 |
200
+ | `wool_knit` | 350 g/m² | 3 mm | 0.7 |
201
+
202
+ `--fabric` gives every piece the same fabric, whatever the preset's mix.
203
+
204
+ ### Formats
205
+
206
+ Each preset draws from its own retail formats (table above). `--format`
207
+ (Python: `formats=`) replaces them with your sizes and shapes, in cm, as a
208
+ comma-separated list:
209
+
210
+ | Format | Shape |
211
+ |---|---|
212
+ | `50x70` (or `rect:50x70`, `50×70`) | rectangle, 50 × 70 cm |
213
+ | `oval:140x200` (or `ellipse:140x200`) | ellipse, 140 × 200 cm |
214
+ | `round:150` | circle, 150 cm in diameter |
215
+ | `50x70@3` | any of the above with a weight (here 3; 1 when there is no `@`) |
216
+
217
+ **Weights.** Each piece draws one format at random, with a probability
218
+ proportional to its weight: weight / sum of the weights. With
219
+ `--format "140x180@2,round:150,oval:140x220"` the weights are 2, 1 and 1
220
+ (sum 4), so each tablecloth has a 50 % chance of being 140×180, 25 % of being
221
+ round and 25 % of being oval: about 6, 3 and 3 out of 12 on average, the exact
222
+ counts depending on the seed. Only the ratios matter (`@2`, `@1`, `@1` is the
223
+ same as `@50`, `@25`, `@25`), and without any `@` all formats are equally
224
+ likely. With `--no-size`, nothing is drawn: every piece takes the first format
225
+ of the list, at its exact size. The presets' own formats use the same weights
226
+ (a `bath_towel` is 70×140 with weight 0.7, 90×150 with weight 0.3).
227
+
228
+ The longer side is always the length, so `70x50` is the same as `50x70`. The
229
+ preset's size tolerance, cut, corners, fabric and patterns still apply. In
230
+ Python, a format can also be a dict (`{"width": 50, "length": 70, "shape":
231
+ "rect", "weight": 1}`) or a tuple (`(50, 70)`, `(150, 150, "round")`). With
232
+ `--topology grid`, round and oval formats are skipped (at least one rectangle
233
+ is needed). `summary.json` counts the pieces of each format drawn.
234
+
235
+ ### Variations
236
+
237
+ Each aspect has its own random stream: turning one off leaves the others
238
+ unchanged (same shapes with plain colours, same colours with a perfect cut…).
239
+
240
+ | Option (CLI / Python `Variations`) | On (default) | Off |
241
+ |---|---|---|
242
+ | `--no-size` / `size=False` | format drawn, size within the preset's tolerance | first format, exact size |
243
+ | `--no-cut` / `cut=False` | wavy edges (0.3–6 mm) and skew (up to 1.5°) | perfect cut |
244
+ | `--no-corners` / `corners=False` | corner radius drawn | middle of the range |
245
+ | `--no-color` / `color=False` | pattern and colours drawn | first pattern, first colours |
246
+ | `--fabric NAME` / `fabric="linen"` (`Variations(fabric=False)`: first fabric) | fabric drawn from the preset's mix | one fabric for every piece |
247
+
248
+ ### Mesh
249
+
250
+ | Option | Default | Effect |
251
+ |---|---|---|
252
+ | `--topology tri` | ✓ | near-equilateral triangles (Delaunay on a jittered hexagonal lattice, outline sampled at the same spacing); any outline; minimum angles above ~20° |
253
+ | `--topology grid` | | structured quads stretched over the four cut edges (Coons patch); rectangles only, sharp corners; round formats are skipped |
254
+ | `--vertices N` | 1500 | target vertices per piece, whatever its size |
255
+ | `--count N`, `--seed S` | 12, 1 | number of pieces, random seed; same options and seed give the same pieces |
256
+
257
+ ![Flat preview of nine tea towels, with their mesh](https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/sheet_tea_towel.png)
258
+
259
+ *`maillage preview output/tea_towel` on a grid-topology set: the texture is
260
+ drawn over each piece and the mesh shown on top; the wavy grid lines are the
261
+ imperfect cut, spread across the piece.*
262
+
263
+ ## Outputs (per preset, under `output/<preset>/`)
264
+
265
+ | File | Content |
266
+ |---|---|
267
+ | `maillage_<preset>.usdc` | OpenUSD scene (Blender: File > Import > USD) |
268
+ | `textures/<piece>.png` | pattern texture of each piece |
269
+ | `pieces.csv` | per piece: fabric, shape, pattern, size, corner radius, mass, mesh metrics |
270
+ | `summary.json` | counts by fabric, pattern, shape and format; mass and mesh statistics |
271
+ | `sheet.png` | flat preview (`maillage preview`) |
272
+ | `drop_test/` | `drop_test.json`, `draped.usdc`, `drop_test.png`, `drop_test.gif` and `frames/` (`maillage drop-test`) |
273
+
274
+ ### OpenUSD layout
275
+
276
+ Z up, metres, kilograms. `/World` is the default prim.
277
+
278
+ ```
279
+ /World
280
+ /Looks/<piece> UsdPreviewSurface + UsdUVTexture (textures/<piece>.png)
281
+ /Cloths/<piece> UsdGeom.Mesh, flat on the ground, laid out in rows
282
+ /Ground collision ground (UsdPhysics.CollisionAPI)
283
+ ```
284
+
285
+ Each cloth mesh has `subdivisionScheme = none`, is double sided, and carries:
286
+
287
+ | Property | Content |
288
+ |---|---|
289
+ | `primvars:st` | texture coordinates (vertex) |
290
+ | `primvars:hem` | 1 on the hemmed band along the edges, 0 elsewhere (vertex) |
291
+ | `primvars:pin` | 1 on pinned vertices: the hoist corners of a flag (vertex) |
292
+ | `primvars:displayColor` | mean colour of the texture |
293
+ | `maillage:fabric`, `maillage:pattern`, `maillage:shape` | what the piece is |
294
+ | `maillage:arealDensity`, `maillage:mass`, `maillage:thickness`, `maillage:friction` | physical properties (SI units) |
295
+ | `maillage:nominalSize`, `maillage:size`, `maillage:cornerRadius` | retail and actual size (m) |
296
+ | `maillage:hemBendingFactor` | how much stiffer the hem is in bending |
297
+ | `maillage:blender:*` | stiffness for Blender's cloth solver, for 1 cm edges (see [Blender values](#blender-values)) |
298
+
299
+ OpenUSD has no standard schema for cloth simulation (`UsdPhysics` covers
300
+ rigid bodies and joints), so these properties are custom attributes. Blender's
301
+ USD importer reads the geometry, UVs, material, texture and the `hem` and
302
+ `pin` primvars (as mesh attributes); the drape test reads the `maillage:*`
303
+ attributes with the OpenUSD bindings shipped with Blender.
304
+
305
+ ## Blender drape test
306
+
307
+ `maillage drop-test` imports the scene into Blender and, for each tested
308
+ piece:
309
+
310
+ 1. sets up Blender's cloth solver from the `maillage:*` attributes: stiffness
311
+ rescaled to the mesh and the piece's real mass, stiffer bending on the hem,
312
+ collisions and self-collisions;
313
+ 2. **drape**: lays the piece flat on a round pedestal (diameter 60 % of the
314
+ shorter side), its centre clamped as between the two discs of a Cusick
315
+ drape tester, and lets the rest fall; **hang**: a flag hangs from its two
316
+ hoist corners on a pole, optionally in the wind;
317
+ 3. simulates 5 s (120 frames at 24 fps);
318
+ 4. checks the result and writes `drop_test.json`:
319
+
320
+ | Check | Pass when |
321
+ |---|---|
322
+ | finite | no invalid coordinate |
323
+ | stretch | 99 % of the edges stretched by less than 10 %, none by more than 60 % |
324
+ | penetration | less than 0.5 % of the vertices inside the pedestal or under the floor |
325
+ | at rest | (drape) fastest vertex below 10 cm/s at the end |
326
+
327
+ It also measures the **drape coefficient** of each draped piece, as in the
328
+ Cusick test: (projected area − pedestal area) / (flat area − pedestal area),
329
+ from 1 (stays flat) to 0 (hangs straight down). The draped meshes are exported
330
+ to `draped.usdc` with their `maillage:*` attributes copied back: ready-to-use
331
+ draped assets.
332
+
333
+ ### Results
334
+
335
+ Indicative results of `maillage drop-test` on three pieces of each preset
336
+ (seed 1, ~1,500 vertices per piece, 120 frames, Blender 5.2):
337
+
338
+ | Preset | Mass (g) | Vertices | Scenario | Drape coefficient | Stretch (p99) | Result |
339
+ |---|---|---|---|---|---|---|
340
+ | `bath_towel` | 429–450 | 1563–1582 | drape | 0.22–0.23 | 3.1–3.7 % | 3/3 pass |
341
+ | `hand_towel` | 219–229 | 1563–1585 | drape | 0.22–0.26 | 2.6–3.6 % | 3/3 pass |
342
+ | `washcloth` | 39–41 | 1562–1572 | drape | 0.40–0.49 | 4.2–5.1 % | 3/3 pass |
343
+ | `tea_towel` | 75–78 | 1549–1570 | drape | 0.28 | 2.8–2.9 % | 3/3 pass |
344
+ | `napkin` | 28–29 | 1559–1578 | drape | 0.36–0.38 | 2.2–2.6 % | 3/3 pass |
345
+ | `tablecloth` | 356–470 | 1564–1572 | drape | 0.20–0.25 | 3.1–4.0 % | 3/3 pass |
346
+ | `bed_sheet` | 636–863 | 1553–1573 | drape | 0.28–0.37 | 3.0–3.1 % | 3/3 pass |
347
+ | `microfiber_cloth` | 47–49 | 1554–1575 | drape | 0.37–0.41 | 2.2–2.4 % | 3/3 pass |
348
+ | `bandana` | 42–43 | 1558–1576 | drape | 0.30–0.33 | 2.5–3.1 % | 3/3 pass |
349
+ | `silk_scarf` | 48–49 | 1560–1579 | drape | 0.25–0.27 | 3.1–3.4 % | 3/3 pass |
350
+ | `scarf` | 134–189 | 1608–1621 | drape | 0.10–0.13 | 2.6–2.8 % | 3/3 pass |
351
+ | `flag` | 170–174 | 1551–1567 | hang | — | 1.9–2.4 % | 3/3 pass |
352
+
353
+ No piece went through the pedestal or the floor. The drape coefficient
354
+ depends on the shape of the piece as much as on the fabric (a long towel
355
+ hangs further past a pedestal sized on its short side), so it compares
356
+ pieces of one preset, not presets with each other. To compare fabrics, the
357
+ same 40 cm square was draped in each (`maillage generate --preset napkin
358
+ --no-size --no-cut --fabric silk,polyester_knit,cotton_plain,linen,cotton_terry,denim`):
359
+
360
+ | Fabric | silk | polyester knit | plain cotton | linen | cotton terry | denim |
361
+ |---|---|---|---|---|---|---|
362
+ | Mass of the square | 10 g | 18 g | 22 g | 29 g | 72 g | 64 g |
363
+ | Drape coefficient | 0.37 | 0.35 | 0.42 | 0.40 | 0.38 | 0.42 |
364
+
365
+ In this version the fabrics differ in mass, thickness, friction and look
366
+ much more than in drape: the drape coefficients stay within 0.35–0.42,
367
+ where real silk and denim are far apart (see the [roadmap](#roadmap)).
368
+
369
+ ![Four 40 cm squares (silk, plain cotton, linen, denim) falling onto their pedestals](https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/drape_fabrics.gif)
370
+
371
+ *Silk, plain cotton, linen and denim squares falling onto the clamped
372
+ pedestals, slowed down twice: `maillage generate --preset napkin --count 4 --fabric
373
+ silk,cotton_plain,linen,denim` then `maillage drop-test output/napkin --frames 40
374
+ --gif --gif-step 1`.*
375
+
376
+ ![Two flags hanging from their poles, fluttering in the wind](https://raw.githubusercontent.com/drscaon/maillage/main/docs/images/flags_wind.gif)
377
+
378
+ *Flags hung by their hoist corners in the wind: `maillage drop-test
379
+ output/flag --wind 4 --gif`.*
380
+
381
+ ### Blender values
382
+
383
+ Blender's cloth stiffness acts per edge and is not divided by the vertex mass,
384
+ so the same numbers make a fabric stiffer or softer depending on the mesh
385
+ resolution. Maillage stores the stiffness for a reference mesh with 1 cm edges
386
+ and the real mass, and the drape test rescales it: tension and shear with the
387
+ edge length, bending unchanged, air damping with the area per vertex. The
388
+ solver itself runs with Blender's usual 0.3 kg vertex mass and every stiffness
389
+ and damping multiplied by the same factor, which leaves the motion unchanged
390
+ and keeps Blender's solver in the range its tolerances are tuned for.
391
+
392
+ Tension gives about 3 % stretch when a piece drapes; bending is smaller for
393
+ softer fabrics, and kept low for all of them (see the
394
+ [roadmap](#roadmap)). **The values are not calibrated** against measured
395
+ fabric properties.
396
+
397
+ ## Method in brief
398
+
399
+ 1. **Outline.** A rectangle with rounded corners or an ellipse, parametrised
400
+ by arc length. The cut adds a smooth normal displacement (sum of a few
401
+ sines along the perimeter: long gentle deviations plus a small wobble) and
402
+ a slight shear.
403
+ 2. **Mesh.** *tri*: the outline is sampled at the target edge length (sharp
404
+ corners kept, roundings below the resolution merged into one vertex), the
405
+ inside filled with a jittered hexagonal lattice kept at least half an edge
406
+ away from the outline, then a Delaunay triangulation clipped to the
407
+ outline. *grid*: transfinite (Coons) interpolation of the four cut edges.
408
+ 3. **Fabric and mass.** The fabric is drawn from the preset's mix; the mass is
409
+ the measured area times the areal density.
410
+ 4. **Hem and pins.** Vertices within the hem width of the outline get
411
+ `hem = 1`; for flags, vertices near the two hoist corners get `pin = 1`.
412
+ 5. **Pattern.** Parameters drawn per piece (colours from the preset's
413
+ palettes, periods, band widths), rendered on the nominal rectangle with a
414
+ faint weave noise; texture coordinates follow the fabric, so a skewed or
415
+ wavy cut shows as a pattern not quite parallel to the edge, as on real
416
+ pieces.
417
+
418
+ ## Limits
419
+
420
+ - **Blender only, so far.** The drape test validates the assets in Blender's
421
+ cloth solver. Nothing is exported yet for NVIDIA OmniPhysics deformables
422
+ (Isaac Sim) or MuJoCo flex; the geometry and physical attributes are there,
423
+ the engine-specific schemas are not.
424
+ - **Fabrics drape alike.** Bending stiffness is kept low for every fabric
425
+ (see the [roadmap](#roadmap)), and no value is calibrated against
426
+ measurements (no Kawabata or Cusick data behind them).
427
+ - **Flat pieces only.** No garments (sewn panels), no holes or grommets (a
428
+ flag is pinned at its hoist corners instead), no folded or crumpled starting
429
+ states, no wet cloth.
430
+ - **Textures are procedural.** Patterns are clean and regular; no prints,
431
+ logos, stains or wear.
432
+
433
+ ## Roadmap
434
+
435
+ 1. **Stiff fabrics that drape like real ones.** Blender's angular bending
436
+ model (the only one that works on triangle meshes) becomes unstable when
437
+ the bending stiffness is high relative to the vertex mass, as with stiff
438
+ fabrics on fine meshes: instead of settling, the cloth keeps vibrating,
439
+ and adding damping makes it worse. Every fabric's bending stiffness is
440
+ therefore kept below this limit (about 0.015 × areal density for 1 cm
441
+ edges, measured), so a 40 cm square drapes with a coefficient of
442
+ 0.35–0.42 whether it is silk or denim. Next steps: more solver steps or a
443
+ coarser simulation mesh for stiff fabrics, quad-grid meshes with
444
+ Blender's linear bending model, and other engines.
445
+ 2. Pre-draped and crumpled states as OpenUSD variants (the drape test already
446
+ exports draped meshes), and folded states for laundry-folding tasks.
447
+ 3. Engine-neutral physical stiffness (tensile and bending rigidity in SI
448
+ units) with per-engine mappings, starting with OmniPhysics surface
449
+ deformables.
450
+ 4. Calibration against published fabric measurements.
451
+ 5. Simple garments from sewing patterns.
452
+
453
+ ## Development
454
+
455
+ The project follows **GitHub Flow**:
456
+
457
+ 1. `main` is always releasable and protected: changes reach it only through
458
+ pull requests, nobody pushes to it directly.
459
+ 2. Every change starts on a short-lived branch from `main`, named after its
460
+ type: `feat/<name>`, `fix/<name>`, `docs/<name>`, `ci/<name>`, …
461
+ 3. Push the branch and open a pull request into `main`; CI runs on it.
462
+ 4. Merge when CI is green, then delete the branch.
463
+
464
+ Releasing is a change like any other: on a `chore/release-X.Y.Z` branch, bump
465
+ `version` in `pyproject.toml` and move the `Unreleased` entries of
466
+ `CHANGELOG.md` under the new version; merge the pull request, then tag
467
+ `vX.Y.Z` on `main` (or create the release on the GitHub web page with that new
468
+ tag). The release workflow builds the packages and attaches them.
469
+
470
+ A versioned `pre-push` hook refuses direct pushes to `main`. Enable it once per
471
+ clone with `git config core.hooksPath .githooks` (emergency override:
472
+ `ALLOW_MAIN_PUSH=1 git push`).
473
+
474
+ ### Continuous integration
475
+
476
+ GitHub Actions runs these workflows (`.github/workflows/`):
477
+
478
+ | Workflow | When | What |
479
+ |---|---|---|
480
+ | `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; **the Blender drape test of every preset** with the `bpy` module (Python 3.13, Linux), reports kept as a run artifact; build the wheel and sdist, `twine check`, install the wheel in a clean environment. |
481
+ | `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 attached and the notes taken from `CHANGELOG.md`. Versions with `a`, `b` or `rc` are marked as pre-releases. |
482
+ | Dependabot | monthly | pull requests to update the GitHub Actions and the Python dependencies. |
483
+
484
+ Ruff is pinned in CI so that a new ruff release cannot fail the build on
485
+ unchanged code; update the pin and run `ruff format .` together.
486
+
487
+ ## License
488
+
489
+ [MIT](https://github.com/drscaon/maillage/blob/main/LICENSE) © 2026 Daniel Régis Sarmento Caon
490
+
491
+ The logo is set in IBM Plex Sans (SIL Open Font License 1.1), outlined into
492
+ paths, so it displays without the font installed.
493
+
494
+ ---
495
+
496
+ Maillage is part of [**Primvaria**](https://github.com/drscaon/primvaria), physics-ready variability for Physical AI, in OpenUSD: `pip install primvaria` installs the whole suite.