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.
- maillage-0.1.0b2/LICENSE +21 -0
- maillage-0.1.0b2/PKG-INFO +496 -0
- maillage-0.1.0b2/README.md +462 -0
- maillage-0.1.0b2/pyproject.toml +55 -0
- maillage-0.1.0b2/setup.cfg +4 -0
- maillage-0.1.0b2/src/maillage/__init__.py +24 -0
- maillage-0.1.0b2/src/maillage/__main__.py +3 -0
- maillage-0.1.0b2/src/maillage/animate.py +29 -0
- maillage-0.1.0b2/src/maillage/blender/__init__.py +6 -0
- maillage-0.1.0b2/src/maillage/blender/drop_test.py +611 -0
- maillage-0.1.0b2/src/maillage/cli.py +230 -0
- maillage-0.1.0b2/src/maillage/fabrics.py +148 -0
- maillage-0.1.0b2/src/maillage/formats.py +99 -0
- maillage-0.1.0b2/src/maillage/generator.py +292 -0
- maillage-0.1.0b2/src/maillage/mesh.py +192 -0
- maillage-0.1.0b2/src/maillage/outline.py +204 -0
- maillage-0.1.0b2/src/maillage/presets.py +232 -0
- maillage-0.1.0b2/src/maillage/render.py +89 -0
- maillage-0.1.0b2/src/maillage/texture.py +163 -0
- maillage-0.1.0b2/src/maillage/usd_export.py +151 -0
- maillage-0.1.0b2/src/maillage.egg-info/PKG-INFO +496 -0
- maillage-0.1.0b2/src/maillage.egg-info/SOURCES.txt +32 -0
- maillage-0.1.0b2/src/maillage.egg-info/dependency_links.txt +1 -0
- maillage-0.1.0b2/src/maillage.egg-info/entry_points.txt +2 -0
- maillage-0.1.0b2/src/maillage.egg-info/requires.txt +18 -0
- maillage-0.1.0b2/src/maillage.egg-info/top_level.txt +1 -0
- maillage-0.1.0b2/tests/test_blender.py +30 -0
- maillage-0.1.0b2/tests/test_cli.py +101 -0
- maillage-0.1.0b2/tests/test_formats.py +56 -0
- maillage-0.1.0b2/tests/test_generator.py +98 -0
- maillage-0.1.0b2/tests/test_mesh.py +66 -0
- maillage-0.1.0b2/tests/test_outline.py +64 -0
- maillage-0.1.0b2/tests/test_texture.py +61 -0
- maillage-0.1.0b2/tests/test_usd.py +63 -0
maillage-0.1.0b2/LICENSE
ADDED
|
@@ -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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

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