cgmath 1.0.2__tar.gz → 1.0.4__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.
- {cgmath-1.0.2/cgmath.egg-info → cgmath-1.0.4}/PKG-INFO +35 -16
- {cgmath-1.0.2 → cgmath-1.0.4}/README.md +18 -9
- {cgmath-1.0.2 → cgmath-1.0.4}/__init__.py +1 -1
- {cgmath-1.0.2 → cgmath-1.0.4/cgmath.egg-info}/PKG-INFO +35 -16
- cgmath-1.0.4/cgmath.egg-info/requires.txt +23 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/fbx.py +1 -15
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_cdt.py +4 -2
- {cgmath-1.0.2 → cgmath-1.0.4}/hierarchy/hierarchy.py +405 -1
- {cgmath-1.0.2 → cgmath-1.0.4}/pyproject.toml +29 -14
- {cgmath-1.0.2 → cgmath-1.0.4}/render/scene.py +12 -0
- cgmath-1.0.2/cgmath.egg-info/requires.txt +0 -9
- {cgmath-1.0.2 → cgmath-1.0.4}/LICENSE +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/SOURCES.txt +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/dependency_links.txt +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/top_level.txt +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/constraints/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/constraints/procrustes.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/glb.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/prim.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/stage.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_base.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_saddle_surface.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/bspline.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/bspline_patch.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/camera.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/delta_mush.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/ffd.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/patch_relax.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/skin_deform.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/wrap.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/delta_mush.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/ffd.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/map.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/mesh.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/morph_target.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/pack.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/patch_relax.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/raytracer.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/resample.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/robust_skinweights_transfer_bilinear.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/sdf.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/skin_weights.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/surface_plotting.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/texture.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bilinear.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_blur.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bspline.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bvh.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_cdt.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_connectivity.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_delta_mush.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_ffd.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_main.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_normals.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_pack.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_patch_relax.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_rasterize.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_sdf.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_skin_deform.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_skin_weights.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_subdivide.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_subdivision.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_tangent_space.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_topology.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_wrap.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/main.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/hierarchy/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/rbf/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_kernels.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/_kernels.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/_lu.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/_numba/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/_numba/_texture.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/camera.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/frame.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/raytracer.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/render/texture.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/setup.cfg +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/__init__.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_axis.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_euler.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_matrix.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_quaternion.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_vector.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/main.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/transforms/utils.py +0 -0
- {cgmath-1.0.2 → cgmath-1.0.4}/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cgmath
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: A DCC-agnostic CG math library: batched matrix, quaternion and euler math, mesh topology, deformers, resampling, skeletal hierarchies, SDFs, a software raytracer and asset IO, all numpy-native.
|
|
5
5
|
Author-email: Eric Vignola <eric.vignola@gmail.com>
|
|
6
6
|
License: ## License
|
|
@@ -52,12 +52,22 @@ License-File: LICENSE
|
|
|
52
52
|
Requires-Dist: numpy
|
|
53
53
|
Requires-Dist: scipy
|
|
54
54
|
Requires-Dist: numba
|
|
55
|
-
|
|
56
|
-
Requires-Dist:
|
|
57
|
-
Requires-Dist:
|
|
58
|
-
Requires-Dist:
|
|
59
|
-
|
|
60
|
-
Requires-Dist:
|
|
55
|
+
Provides-Extra: image
|
|
56
|
+
Requires-Dist: pillow; extra == "image"
|
|
57
|
+
Requires-Dist: opencv-python; extra == "image"
|
|
58
|
+
Requires-Dist: scikit-image; extra == "image"
|
|
59
|
+
Provides-Extra: gltf
|
|
60
|
+
Requires-Dist: pygltflib; extra == "gltf"
|
|
61
|
+
Requires-Dist: trimesh; extra == "gltf"
|
|
62
|
+
Provides-Extra: usd
|
|
63
|
+
Requires-Dist: usd-core; extra == "usd"
|
|
64
|
+
Provides-Extra: all
|
|
65
|
+
Requires-Dist: pillow; extra == "all"
|
|
66
|
+
Requires-Dist: opencv-python; extra == "all"
|
|
67
|
+
Requires-Dist: scikit-image; extra == "all"
|
|
68
|
+
Requires-Dist: pygltflib; extra == "all"
|
|
69
|
+
Requires-Dist: trimesh; extra == "all"
|
|
70
|
+
Requires-Dist: usd-core; extra == "all"
|
|
61
71
|
Dynamic: license-file
|
|
62
72
|
|
|
63
73
|
# `cgmath` — a DCC-agnostic CG math library
|
|
@@ -234,13 +244,15 @@ follows — full detail in its [README](https://github.com/Eric-Vignola/cgmath/b
|
|
|
234
244
|
### Optional dependencies
|
|
235
245
|
|
|
236
246
|
Imported lazily behind `try / except ImportError`, so the package loads
|
|
237
|
-
without them and only the paths that need them complain.
|
|
247
|
+
without them and only the paths that need them complain. A plain
|
|
248
|
+
`pip install cgmath` brings in none of them; the PyPI ones come as extras,
|
|
249
|
+
see [Requirements](#requirements).
|
|
238
250
|
|
|
239
251
|
| Dependency | Used for | Missing behaviour |
|
|
240
252
|
|---|---|---|
|
|
241
253
|
| `pygltflib` | GLB / glTF read | `RuntimeError("pygltflib is not installed")` at call time |
|
|
242
254
|
| Autodesk FBX SDK — not on PyPI, a manual install | FBX read / write | imports fine, with one `UserWarning` naming the SDK and where to get it; every FBX call raises `RuntimeError` with that same message |
|
|
243
|
-
| `pxr`, from `usd-core`
|
|
255
|
+
| `pxr`, from `usd-core` or the host DCC's own USD | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | `import cgmath.formats.usd.prim` itself raises `ImportError`, and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
|
|
244
256
|
| `trimesh` | `GlbData.mesh_list` | that attribute is `None` |
|
|
245
257
|
| `PIL` (Pillow) | `Frame.image` / `.save` / `.wireframe` / `.encode_gif`, `to_image`, texture I/O | `RuntimeError("... install Pillow.")` at call time |
|
|
246
258
|
| `cv2` | `imshow()` windows; UV rasterization in `geometry.mesh` | `imshow` raises `RuntimeError`; rasterization falls back to `skimage` |
|
|
@@ -369,15 +381,22 @@ Start with [`CHEATSHEET.md`](https://github.com/Eric-Vignola/cgmath/blob/main/CH
|
|
|
369
381
|
|
|
370
382
|
## Requirements
|
|
371
383
|
|
|
372
|
-
Numpy, Scipy and Numba python modules
|
|
384
|
+
Numpy, Scipy and Numba python modules, the only packages a plain
|
|
385
|
+
`pip install cgmath` brings in.
|
|
373
386
|
|
|
374
|
-
|
|
387
|
+
Everything else is optional, per feature (see the table above for how each
|
|
388
|
+
degrades), and comes as a pip extra:
|
|
375
389
|
|
|
376
|
-
Pillow
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
390
|
+
pip install "cgmath[image]" Pillow, OpenCV, scikit-image: texture I/O,
|
|
391
|
+
rendered frames, UV rasterization
|
|
392
|
+
pip install "cgmath[gltf]" pygltflib, trimesh: glTF / GLB read and write
|
|
393
|
+
pip install "cgmath[usd]" usd-core (pxr): USD stage read and write
|
|
394
|
+
pip install "cgmath[all]" all of the above
|
|
395
|
+
|
|
396
|
+
Inside a DCC that ships its own USD (Maya, Houdini), leave `usd` out: a
|
|
397
|
+
second `pxr` on `sys.path` conflicts with the one the host has loaded.
|
|
398
|
+
|
|
399
|
+
The Autodesk FBX SDK (FBX read and write) is not on PyPI; install it by hand.
|
|
381
400
|
|
|
382
401
|
|
|
383
402
|
## Author
|
|
@@ -172,13 +172,15 @@ follows — full detail in its [README](https://github.com/Eric-Vignola/cgmath/b
|
|
|
172
172
|
### Optional dependencies
|
|
173
173
|
|
|
174
174
|
Imported lazily behind `try / except ImportError`, so the package loads
|
|
175
|
-
without them and only the paths that need them complain.
|
|
175
|
+
without them and only the paths that need them complain. A plain
|
|
176
|
+
`pip install cgmath` brings in none of them; the PyPI ones come as extras,
|
|
177
|
+
see [Requirements](#requirements).
|
|
176
178
|
|
|
177
179
|
| Dependency | Used for | Missing behaviour |
|
|
178
180
|
|---|---|---|
|
|
179
181
|
| `pygltflib` | GLB / glTF read | `RuntimeError("pygltflib is not installed")` at call time |
|
|
180
182
|
| Autodesk FBX SDK — not on PyPI, a manual install | FBX read / write | imports fine, with one `UserWarning` naming the SDK and where to get it; every FBX call raises `RuntimeError` with that same message |
|
|
181
|
-
| `pxr`, from `usd-core`
|
|
183
|
+
| `pxr`, from `usd-core` or the host DCC's own USD | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | `import cgmath.formats.usd.prim` itself raises `ImportError`, and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
|
|
182
184
|
| `trimesh` | `GlbData.mesh_list` | that attribute is `None` |
|
|
183
185
|
| `PIL` (Pillow) | `Frame.image` / `.save` / `.wireframe` / `.encode_gif`, `to_image`, texture I/O | `RuntimeError("... install Pillow.")` at call time |
|
|
184
186
|
| `cv2` | `imshow()` windows; UV rasterization in `geometry.mesh` | `imshow` raises `RuntimeError`; rasterization falls back to `skimage` |
|
|
@@ -307,15 +309,22 @@ Start with [`CHEATSHEET.md`](https://github.com/Eric-Vignola/cgmath/blob/main/CH
|
|
|
307
309
|
|
|
308
310
|
## Requirements
|
|
309
311
|
|
|
310
|
-
Numpy, Scipy and Numba python modules
|
|
312
|
+
Numpy, Scipy and Numba python modules, the only packages a plain
|
|
313
|
+
`pip install cgmath` brings in.
|
|
311
314
|
|
|
312
|
-
|
|
315
|
+
Everything else is optional, per feature (see the table above for how each
|
|
316
|
+
degrades), and comes as a pip extra:
|
|
313
317
|
|
|
314
|
-
Pillow
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
pip install "cgmath[image]" Pillow, OpenCV, scikit-image: texture I/O,
|
|
319
|
+
rendered frames, UV rasterization
|
|
320
|
+
pip install "cgmath[gltf]" pygltflib, trimesh: glTF / GLB read and write
|
|
321
|
+
pip install "cgmath[usd]" usd-core (pxr): USD stage read and write
|
|
322
|
+
pip install "cgmath[all]" all of the above
|
|
323
|
+
|
|
324
|
+
Inside a DCC that ships its own USD (Maya, Houdini), leave `usd` out: a
|
|
325
|
+
second `pxr` on `sys.path` conflicts with the one the host has loaded.
|
|
326
|
+
|
|
327
|
+
The Autodesk FBX SDK (FBX read and write) is not on PyPI; install it by hand.
|
|
319
328
|
|
|
320
329
|
|
|
321
330
|
## Author
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cgmath
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: A DCC-agnostic CG math library: batched matrix, quaternion and euler math, mesh topology, deformers, resampling, skeletal hierarchies, SDFs, a software raytracer and asset IO, all numpy-native.
|
|
5
5
|
Author-email: Eric Vignola <eric.vignola@gmail.com>
|
|
6
6
|
License: ## License
|
|
@@ -52,12 +52,22 @@ License-File: LICENSE
|
|
|
52
52
|
Requires-Dist: numpy
|
|
53
53
|
Requires-Dist: scipy
|
|
54
54
|
Requires-Dist: numba
|
|
55
|
-
|
|
56
|
-
Requires-Dist:
|
|
57
|
-
Requires-Dist:
|
|
58
|
-
Requires-Dist:
|
|
59
|
-
|
|
60
|
-
Requires-Dist:
|
|
55
|
+
Provides-Extra: image
|
|
56
|
+
Requires-Dist: pillow; extra == "image"
|
|
57
|
+
Requires-Dist: opencv-python; extra == "image"
|
|
58
|
+
Requires-Dist: scikit-image; extra == "image"
|
|
59
|
+
Provides-Extra: gltf
|
|
60
|
+
Requires-Dist: pygltflib; extra == "gltf"
|
|
61
|
+
Requires-Dist: trimesh; extra == "gltf"
|
|
62
|
+
Provides-Extra: usd
|
|
63
|
+
Requires-Dist: usd-core; extra == "usd"
|
|
64
|
+
Provides-Extra: all
|
|
65
|
+
Requires-Dist: pillow; extra == "all"
|
|
66
|
+
Requires-Dist: opencv-python; extra == "all"
|
|
67
|
+
Requires-Dist: scikit-image; extra == "all"
|
|
68
|
+
Requires-Dist: pygltflib; extra == "all"
|
|
69
|
+
Requires-Dist: trimesh; extra == "all"
|
|
70
|
+
Requires-Dist: usd-core; extra == "all"
|
|
61
71
|
Dynamic: license-file
|
|
62
72
|
|
|
63
73
|
# `cgmath` — a DCC-agnostic CG math library
|
|
@@ -234,13 +244,15 @@ follows — full detail in its [README](https://github.com/Eric-Vignola/cgmath/b
|
|
|
234
244
|
### Optional dependencies
|
|
235
245
|
|
|
236
246
|
Imported lazily behind `try / except ImportError`, so the package loads
|
|
237
|
-
without them and only the paths that need them complain.
|
|
247
|
+
without them and only the paths that need them complain. A plain
|
|
248
|
+
`pip install cgmath` brings in none of them; the PyPI ones come as extras,
|
|
249
|
+
see [Requirements](#requirements).
|
|
238
250
|
|
|
239
251
|
| Dependency | Used for | Missing behaviour |
|
|
240
252
|
|---|---|---|
|
|
241
253
|
| `pygltflib` | GLB / glTF read | `RuntimeError("pygltflib is not installed")` at call time |
|
|
242
254
|
| Autodesk FBX SDK — not on PyPI, a manual install | FBX read / write | imports fine, with one `UserWarning` naming the SDK and where to get it; every FBX call raises `RuntimeError` with that same message |
|
|
243
|
-
| `pxr`, from `usd-core`
|
|
255
|
+
| `pxr`, from `usd-core` or the host DCC's own USD | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | `import cgmath.formats.usd.prim` itself raises `ImportError`, and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
|
|
244
256
|
| `trimesh` | `GlbData.mesh_list` | that attribute is `None` |
|
|
245
257
|
| `PIL` (Pillow) | `Frame.image` / `.save` / `.wireframe` / `.encode_gif`, `to_image`, texture I/O | `RuntimeError("... install Pillow.")` at call time |
|
|
246
258
|
| `cv2` | `imshow()` windows; UV rasterization in `geometry.mesh` | `imshow` raises `RuntimeError`; rasterization falls back to `skimage` |
|
|
@@ -369,15 +381,22 @@ Start with [`CHEATSHEET.md`](https://github.com/Eric-Vignola/cgmath/blob/main/CH
|
|
|
369
381
|
|
|
370
382
|
## Requirements
|
|
371
383
|
|
|
372
|
-
Numpy, Scipy and Numba python modules
|
|
384
|
+
Numpy, Scipy and Numba python modules, the only packages a plain
|
|
385
|
+
`pip install cgmath` brings in.
|
|
373
386
|
|
|
374
|
-
|
|
387
|
+
Everything else is optional, per feature (see the table above for how each
|
|
388
|
+
degrades), and comes as a pip extra:
|
|
375
389
|
|
|
376
|
-
Pillow
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
390
|
+
pip install "cgmath[image]" Pillow, OpenCV, scikit-image: texture I/O,
|
|
391
|
+
rendered frames, UV rasterization
|
|
392
|
+
pip install "cgmath[gltf]" pygltflib, trimesh: glTF / GLB read and write
|
|
393
|
+
pip install "cgmath[usd]" usd-core (pxr): USD stage read and write
|
|
394
|
+
pip install "cgmath[all]" all of the above
|
|
395
|
+
|
|
396
|
+
Inside a DCC that ships its own USD (Maya, Houdini), leave `usd` out: a
|
|
397
|
+
second `pxr` on `sys.path` conflicts with the one the host has loaded.
|
|
398
|
+
|
|
399
|
+
The Autodesk FBX SDK (FBX read and write) is not on PyPI; install it by hand.
|
|
381
400
|
|
|
382
401
|
|
|
383
402
|
## Author
|
|
@@ -2363,21 +2363,6 @@ class SceneData(BaseData):
|
|
|
2363
2363
|
self._filename = None
|
|
2364
2364
|
|
|
2365
2365
|
|
|
2366
|
-
def _enum_fields(enum_name: str) -> list:
|
|
2367
|
-
"""Splits a Maya enumName ("a=1:b=5:c") into (field, index) pairs.
|
|
2368
|
-
|
|
2369
|
-
A field without an explicit index follows the previous one, the way
|
|
2370
|
-
Maya numbers them.
|
|
2371
|
-
"""
|
|
2372
|
-
fields = []
|
|
2373
|
-
index = -1
|
|
2374
|
-
for token in enum_name.split(":"):
|
|
2375
|
-
name, _, given = token.partition("=")
|
|
2376
|
-
index = int(given) if given else index + 1
|
|
2377
|
-
fields.append((name, index))
|
|
2378
|
-
return fields
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
2366
|
class FbxExporter:
|
|
2382
2367
|
"""Export fbx files from pipeline components"""
|
|
2383
2368
|
|
|
@@ -2461,6 +2446,7 @@ class FbxExporter:
|
|
|
2461
2446
|
# never writes them as scene nodes. parents come first.
|
|
2462
2447
|
# imported here, cgmath.hierarchy imports this module.
|
|
2463
2448
|
from cgmath.hierarchy import SUPPORTED_NODE_TYPES
|
|
2449
|
+
from cgmath.hierarchy.hierarchy import _enum_fields
|
|
2464
2450
|
|
|
2465
2451
|
skipped_nodes = {}
|
|
2466
2452
|
dropped = set()
|
|
@@ -317,7 +317,7 @@ def maya_tiebreak(mesh):
|
|
|
317
317
|
Only swaps when ALL conditions are met:
|
|
318
318
|
|
|
319
319
|
1. The quad is convex (required for a valid flip).
|
|
320
|
-
2. The alternative diagonal is
|
|
320
|
+
2. The alternative diagonal is longer, by more than rounding noise.
|
|
321
321
|
3. Min angles of both triangulations are nearly equal (< 0.005 rad).
|
|
322
322
|
4. Circumcircle violation is small relative to quad geometry.
|
|
323
323
|
"""
|
|
@@ -353,9 +353,11 @@ def maya_tiebreak(mesh):
|
|
|
353
353
|
if not check_convex(pts, c, d, a, b):
|
|
354
354
|
continue
|
|
355
355
|
|
|
356
|
+
# An exact tie (cocircular points, equal diagonals) must not flip:
|
|
357
|
+
# its last bits come from the SVD flattening and vary by numpy build.
|
|
356
358
|
len_ab_sq = (pax - pbx) ** 2 + (pay - pby) ** 2
|
|
357
359
|
len_cd_sq = (pcx - pdx) ** 2 + (pcy - pdy) ** 2
|
|
358
|
-
if len_cd_sq <= len_ab_sq:
|
|
360
|
+
if len_cd_sq <= len_ab_sq * (1.0 + 1e-9):
|
|
359
361
|
continue
|
|
360
362
|
|
|
361
363
|
curr_min = min(
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
import uuid
|
|
5
5
|
from collections.abc import Iterable
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import List, Optional, Union
|
|
7
|
+
from typing import Any, List, Optional, Union
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from cgmath.formats.glb import load_gltf, load_model
|
|
@@ -288,6 +288,262 @@ def _strip_namespace(name: str, namespace: Optional[str] = None) -> str:
|
|
|
288
288
|
return ":".join(target[:-1] + spaces[depth:] + [short])
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
# user defined attribute types, by the cmds.addAttr() name, grouped by how a
|
|
292
|
+
# value converts to them
|
|
293
|
+
_FLOAT_TYPES = ("double", "float", "doubleAngle", "doubleLinear")
|
|
294
|
+
_INT_TYPES = ("long", "short", "byte", "char")
|
|
295
|
+
_NUMBER_TYPES = _FLOAT_TYPES + _INT_TYPES
|
|
296
|
+
|
|
297
|
+
# the types add_user_attribute() creates, as attributeType then as dataType
|
|
298
|
+
_NEW_ATTRIBUTE_TYPES = _NUMBER_TYPES + ("bool", "enum")
|
|
299
|
+
_NEW_DATA_TYPES = (
|
|
300
|
+
"string",
|
|
301
|
+
"stringArray",
|
|
302
|
+
"doubleArray",
|
|
303
|
+
"floatArray",
|
|
304
|
+
"Int32Array",
|
|
305
|
+
"vectorArray",
|
|
306
|
+
"pointArray",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _enum_fields(enum_name: str) -> list:
|
|
311
|
+
"""Splits a Maya enumName ("a=1:b=5:c") into (field, index) pairs.
|
|
312
|
+
|
|
313
|
+
A field without an explicit index follows the previous one, the way
|
|
314
|
+
Maya numbers them.
|
|
315
|
+
"""
|
|
316
|
+
fields = []
|
|
317
|
+
index = -1
|
|
318
|
+
for token in enum_name.split(":"):
|
|
319
|
+
name, _, given = token.partition("=")
|
|
320
|
+
index = int(given) if given else index + 1
|
|
321
|
+
fields.append((name, index))
|
|
322
|
+
return fields
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _user_attr_type(spec: dict) -> str:
|
|
326
|
+
"""the cmds.addAttr() type name of a user defined attribute spec"""
|
|
327
|
+
return spec.get("attributeType", spec.get("dataType"))
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _to_float(value, label: str) -> float:
|
|
331
|
+
if isinstance(value, (bool, int, float, np.bool_, np.integer, np.floating)):
|
|
332
|
+
return float(value)
|
|
333
|
+
raise TypeError(f"{label} takes a number, got {value!r}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _to_int(value, label: str) -> int:
|
|
337
|
+
if isinstance(value, (bool, int, np.bool_, np.integer)):
|
|
338
|
+
return int(value)
|
|
339
|
+
if isinstance(value, (float, np.floating)):
|
|
340
|
+
if float(value).is_integer():
|
|
341
|
+
return int(value)
|
|
342
|
+
raise ValueError(f"{label} takes a whole number, got {value!r}")
|
|
343
|
+
raise TypeError(f"{label} takes a whole number, got {value!r}")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _to_bool(value, label: str) -> bool:
|
|
347
|
+
if isinstance(value, (bool, np.bool_)):
|
|
348
|
+
return bool(value)
|
|
349
|
+
if isinstance(value, (int, float, np.integer, np.floating)):
|
|
350
|
+
if value in (0, 1):
|
|
351
|
+
return bool(value)
|
|
352
|
+
raise ValueError(f"{label} takes True, False, 0 or 1, got {value!r}")
|
|
353
|
+
raise TypeError(f"{label} takes True, False, 0 or 1, got {value!r}")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _to_enum(value, enum_name: Optional[str], label: str) -> int:
|
|
357
|
+
fields = _enum_fields(enum_name) if enum_name else []
|
|
358
|
+
if isinstance(value, str):
|
|
359
|
+
for field, index in fields:
|
|
360
|
+
if field == value:
|
|
361
|
+
return index
|
|
362
|
+
raise ValueError(
|
|
363
|
+
f"{label} has no field {value!r}, its fields are {enum_name!r}"
|
|
364
|
+
)
|
|
365
|
+
index = _to_int(value, label)
|
|
366
|
+
if fields and index not in {i for _, i in fields}:
|
|
367
|
+
raise ValueError(f"{label} has no field {index}, its fields are {enum_name!r}")
|
|
368
|
+
return index
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _to_list(value, label: str) -> list:
|
|
372
|
+
if isinstance(value, (list, tuple, np.ndarray)):
|
|
373
|
+
return list(value)
|
|
374
|
+
raise TypeError(f"{label} takes a list, got {value!r}")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _to_rows(value, size: int, label: str) -> list:
|
|
378
|
+
rows = []
|
|
379
|
+
for item in _to_list(value, label):
|
|
380
|
+
row = [_to_float(x, label) for x in _to_list(item, label)]
|
|
381
|
+
if len(row) != size:
|
|
382
|
+
raise ValueError(f"{label} takes rows of {size} numbers, got {item!r}")
|
|
383
|
+
rows.append(row)
|
|
384
|
+
return rows
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _convert_one(kind: str, spec: dict, value, label: str):
|
|
388
|
+
"""``value`` converted to one value of a user defined attribute of ``kind``;
|
|
389
|
+
a kind without a rule is stored as given"""
|
|
390
|
+
if kind in _FLOAT_TYPES:
|
|
391
|
+
return _to_float(value, label)
|
|
392
|
+
if kind in _INT_TYPES:
|
|
393
|
+
return _to_int(value, label)
|
|
394
|
+
if kind == "bool":
|
|
395
|
+
return _to_bool(value, label)
|
|
396
|
+
if kind == "enum":
|
|
397
|
+
return _to_enum(value, spec.get("enumName"), label)
|
|
398
|
+
if kind == "string":
|
|
399
|
+
if isinstance(value, str):
|
|
400
|
+
return str(value)
|
|
401
|
+
raise TypeError(f"{label} takes a string, got {value!r}")
|
|
402
|
+
if kind == "stringArray":
|
|
403
|
+
items = _to_list(value, label)
|
|
404
|
+
if all(isinstance(x, str) for x in items):
|
|
405
|
+
return [str(x) for x in items]
|
|
406
|
+
raise TypeError(f"{label} takes a list of strings, got {value!r}")
|
|
407
|
+
if kind in ("doubleArray", "floatArray"):
|
|
408
|
+
return [_to_float(x, label) for x in _to_list(value, label)]
|
|
409
|
+
if kind == "Int32Array":
|
|
410
|
+
return [_to_int(x, label) for x in _to_list(value, label)]
|
|
411
|
+
if kind == "vectorArray":
|
|
412
|
+
return _to_rows(value, 3, label)
|
|
413
|
+
if kind == "pointArray":
|
|
414
|
+
return _to_rows(value, 4, label)
|
|
415
|
+
if kind == "matrix":
|
|
416
|
+
rows = _to_list(value, label)
|
|
417
|
+
if len(rows) == 4:
|
|
418
|
+
rows = [x for row in _to_rows(rows, 4, label) for x in row]
|
|
419
|
+
if len(rows) != 16:
|
|
420
|
+
raise ValueError(f"{label} takes 16 numbers or 4 rows of 4, got {value!r}")
|
|
421
|
+
return [_to_float(x, label) for x in rows]
|
|
422
|
+
return value
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _convert_user_value(spec: dict, value, label: str):
|
|
426
|
+
"""``value`` converted to what the user defined attribute ``spec`` stores; a
|
|
427
|
+
multi attribute stores ``[[index, value], ...]``"""
|
|
428
|
+
kind = _user_attr_type(spec)
|
|
429
|
+
if not spec.get("multi"):
|
|
430
|
+
return _convert_one(kind, spec, value, label)
|
|
431
|
+
|
|
432
|
+
pairs = []
|
|
433
|
+
for pair in _to_list(value, label):
|
|
434
|
+
if not isinstance(pair, (list, tuple, np.ndarray)) or len(pair) != 2:
|
|
435
|
+
raise TypeError(f"{label} takes [[index, value], ...], got {value!r}")
|
|
436
|
+
index = _to_int(pair[0], label)
|
|
437
|
+
if index < 0:
|
|
438
|
+
raise ValueError(f"{label} takes indices from 0, got {index}")
|
|
439
|
+
pairs.append([index, _convert_one(kind, spec, pair[1], label)])
|
|
440
|
+
return pairs
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _compound_children(attrs: dict, name: str) -> list:
|
|
444
|
+
"""the names of a compound user attribute's children, in order"""
|
|
445
|
+
return [child for child, spec in attrs.items() if spec.get("parent") == name]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _user_value(attrs: dict, name: str):
|
|
449
|
+
"""a user defined attribute's value; a compound parent's comes from its
|
|
450
|
+
children, as an array when they are all numbers, else as a tuple"""
|
|
451
|
+
spec = attrs[name]
|
|
452
|
+
if "numberOfChildren" not in spec:
|
|
453
|
+
return spec.get("value")
|
|
454
|
+
|
|
455
|
+
children = _compound_children(attrs, name)
|
|
456
|
+
values = [_user_value(attrs, child) for child in children]
|
|
457
|
+
numeric = all(
|
|
458
|
+
_user_attr_type(attrs[child]) in _NUMBER_TYPES
|
|
459
|
+
and "numberOfChildren" not in attrs[child]
|
|
460
|
+
and not attrs[child].get("multi")
|
|
461
|
+
for child in children
|
|
462
|
+
)
|
|
463
|
+
return np.array(values) if children and numeric else tuple(values)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _user_writes(attrs: dict, name: str, value, owner: str) -> list:
|
|
467
|
+
"""the (spec, converted value) pairs that setting ``name`` to ``value``
|
|
468
|
+
stores, all converted before anything is written; a compound parent takes
|
|
469
|
+
one value per child"""
|
|
470
|
+
spec = attrs[name]
|
|
471
|
+
label = f"{owner}.{name}"
|
|
472
|
+
if "numberOfChildren" not in spec:
|
|
473
|
+
return [(spec, _convert_user_value(spec, value, label))]
|
|
474
|
+
|
|
475
|
+
children = _compound_children(attrs, name)
|
|
476
|
+
items = _to_list(value, label)
|
|
477
|
+
if len(items) != len(children):
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"{label} takes {len(children)} values, one per child, got {len(items)}"
|
|
480
|
+
)
|
|
481
|
+
writes = []
|
|
482
|
+
for child, item in zip(children, items):
|
|
483
|
+
writes.extend(_user_writes(attrs, child, item, owner))
|
|
484
|
+
return writes
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _declares(cls: type, name: str) -> bool:
|
|
488
|
+
"""whether ``cls`` or one of its parents annotates ``name`` at class level"""
|
|
489
|
+
return any(name in vars(c).get("__annotations__", {}) for c in cls.__mro__)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _has_user_attr(node: "TransformData", name: str) -> bool:
|
|
493
|
+
"""whether ``node`` has a user attribute ``name`` a built-in does not hide"""
|
|
494
|
+
attrs = node.user_defined_attributes or {}
|
|
495
|
+
return name in attrs and not node._is_builtin_attr(name)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _infer_user_attr_type(value) -> str:
|
|
499
|
+
"""the type a new user attribute takes from its first value"""
|
|
500
|
+
if isinstance(value, (bool, np.bool_)):
|
|
501
|
+
return "bool"
|
|
502
|
+
if isinstance(value, (int, np.integer)):
|
|
503
|
+
return "long"
|
|
504
|
+
if isinstance(value, (float, np.floating)):
|
|
505
|
+
return "double"
|
|
506
|
+
if isinstance(value, str):
|
|
507
|
+
return "string"
|
|
508
|
+
if isinstance(value, (list, tuple, np.ndarray)) and len(value):
|
|
509
|
+
items = list(value)
|
|
510
|
+
numbers = (int, float, np.integer, np.floating)
|
|
511
|
+
flags = (bool, np.bool_)
|
|
512
|
+
if all(isinstance(x, str) for x in items):
|
|
513
|
+
return "stringArray"
|
|
514
|
+
if all(isinstance(x, numbers) and not isinstance(x, flags) for x in items):
|
|
515
|
+
return "doubleArray"
|
|
516
|
+
raise TypeError(
|
|
517
|
+
f"cannot tell a user attribute type from {value!r}, pass attribute_type"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _new_user_attr(
|
|
522
|
+
value, attribute_type: Optional[str], keyable: bool, enum_names, label: str
|
|
523
|
+
) -> dict:
|
|
524
|
+
"""the spec of a new user defined attribute, in the form rig serializes"""
|
|
525
|
+
kind = attribute_type or _infer_user_attr_type(value)
|
|
526
|
+
if kind in _NEW_ATTRIBUTE_TYPES:
|
|
527
|
+
spec = {"attributeType": kind}
|
|
528
|
+
elif kind in _NEW_DATA_TYPES:
|
|
529
|
+
spec = {"dataType": kind}
|
|
530
|
+
else:
|
|
531
|
+
raise ValueError(f"add_user_attribute cannot create a {kind!r} attribute")
|
|
532
|
+
|
|
533
|
+
if kind == "enum":
|
|
534
|
+
if not enum_names:
|
|
535
|
+
raise ValueError(
|
|
536
|
+
"an enum user attribute needs enum_names, e.g. 'a=1:b=5:c'"
|
|
537
|
+
)
|
|
538
|
+
spec["enumName"] = enum_names
|
|
539
|
+
|
|
540
|
+
# Maya can not key strings or arrays
|
|
541
|
+
spec["value"] = _convert_user_value(spec, value, label)
|
|
542
|
+
spec["keyable"] = bool(keyable) and "attributeType" in spec
|
|
543
|
+
spec["channel_box"] = False
|
|
544
|
+
return spec
|
|
545
|
+
|
|
546
|
+
|
|
291
547
|
@dataclass(repr=False, eq=False)
|
|
292
548
|
class TransformData(Data):
|
|
293
549
|
# name, node_type, uuid, and parent_node
|
|
@@ -1158,6 +1414,88 @@ class TransformData(Data):
|
|
|
1158
1414
|
)
|
|
1159
1415
|
self.name = new
|
|
1160
1416
|
|
|
1417
|
+
# --- user defined attributes, as properties --- #
|
|
1418
|
+
|
|
1419
|
+
def _is_builtin_attr(self, name: str) -> bool:
|
|
1420
|
+
"""whether ``name`` is a field, property or method of the class"""
|
|
1421
|
+
return hasattr(type(self), name) or name in self._dataclass_field_names()
|
|
1422
|
+
|
|
1423
|
+
def __getattr__(self, name: str):
|
|
1424
|
+
"""a user defined attribute's value, read as ``node.heroHeight``
|
|
1425
|
+
|
|
1426
|
+
Only reached when normal lookup fails, so built-in names always win.
|
|
1427
|
+
"""
|
|
1428
|
+
attrs = self.__dict__.get("user_defined_attributes")
|
|
1429
|
+
if attrs and not name.startswith("_") and name in attrs:
|
|
1430
|
+
return _user_value(attrs, name)
|
|
1431
|
+
raise AttributeError(
|
|
1432
|
+
f"{type(self).__name__!r} object has no attribute {name!r}"
|
|
1433
|
+
)
|
|
1434
|
+
|
|
1435
|
+
def __setattr__(self, name: str, value) -> None:
|
|
1436
|
+
"""sets a user defined attribute the node has, as ``node.heroHeight =
|
|
1437
|
+
3.1416``, converted to its type; anything else goes to Data, which
|
|
1438
|
+
raises on names the class does not declare"""
|
|
1439
|
+
if not name.startswith("_"):
|
|
1440
|
+
attrs = self.__dict__.get("user_defined_attributes")
|
|
1441
|
+
if attrs and name in attrs and not self._is_builtin_attr(name):
|
|
1442
|
+
for spec, converted in _user_writes(attrs, name, value, self.name):
|
|
1443
|
+
spec["value"] = converted
|
|
1444
|
+
return
|
|
1445
|
+
|
|
1446
|
+
try:
|
|
1447
|
+
super().__setattr__(name, value)
|
|
1448
|
+
except AttributeError as error:
|
|
1449
|
+
if self._is_builtin_attr(name):
|
|
1450
|
+
raise
|
|
1451
|
+
raise AttributeError(
|
|
1452
|
+
f"{error}; add_user_attribute() creates a user attribute"
|
|
1453
|
+
) from None
|
|
1454
|
+
|
|
1455
|
+
def __dir__(self):
|
|
1456
|
+
attrs = self.__dict__.get("user_defined_attributes") or {}
|
|
1457
|
+
return sorted(set(super().__dir__()) | set(attrs))
|
|
1458
|
+
|
|
1459
|
+
def _check_new_user_attr(self, name: str) -> None:
|
|
1460
|
+
"""raises ValueError when ``name`` can not be a new user attribute"""
|
|
1461
|
+
if not isinstance(name, str) or not name.isidentifier() or name.startswith("_"):
|
|
1462
|
+
raise ValueError(f"{name!r} is not a valid user attribute name")
|
|
1463
|
+
if self._is_builtin_attr(name):
|
|
1464
|
+
raise ValueError(
|
|
1465
|
+
f"{name!r} is a built-in attribute of {type(self).__name__}"
|
|
1466
|
+
)
|
|
1467
|
+
if name in (self.user_defined_attributes or {}):
|
|
1468
|
+
raise ValueError(f"{self.name} already has a user attribute {name!r}")
|
|
1469
|
+
|
|
1470
|
+
def add_user_attribute(
|
|
1471
|
+
self,
|
|
1472
|
+
name: str,
|
|
1473
|
+
value: Any,
|
|
1474
|
+
attribute_type: Optional[str] = None,
|
|
1475
|
+
keyable: bool = True,
|
|
1476
|
+
enum_names: Optional[str] = None,
|
|
1477
|
+
) -> None:
|
|
1478
|
+
"""creates a user defined attribute, then read and set as ``node.name``
|
|
1479
|
+
|
|
1480
|
+
``attribute_type`` is a cmds.addAttr() type: double, float, doubleAngle,
|
|
1481
|
+
doubleLinear, long, short, byte, char, bool, enum, string, stringArray,
|
|
1482
|
+
doubleArray, floatArray, Int32Array, vectorArray or pointArray. Without
|
|
1483
|
+
one it comes from ``value``: bool, long, double, string, stringArray or
|
|
1484
|
+
doubleArray. An enum needs ``enum_names``, e.g. ``"a=1:b=5:c"``. String
|
|
1485
|
+
and array attributes are never keyable, as in Maya.
|
|
1486
|
+
|
|
1487
|
+
Raises ValueError when the name exists, is built in or is not a valid
|
|
1488
|
+
attribute name, and TypeError or ValueError when the value does not
|
|
1489
|
+
fit the type.
|
|
1490
|
+
"""
|
|
1491
|
+
self._check_new_user_attr(name)
|
|
1492
|
+
spec = _new_user_attr(
|
|
1493
|
+
value, attribute_type, keyable, enum_names, f"{self.name}.{name}"
|
|
1494
|
+
)
|
|
1495
|
+
if self.user_defined_attributes is None:
|
|
1496
|
+
self.user_defined_attributes = {}
|
|
1497
|
+
self.user_defined_attributes[name] = spec
|
|
1498
|
+
|
|
1161
1499
|
|
|
1162
1500
|
def _read_fbx(filename: str, scale_factor: float):
|
|
1163
1501
|
"""opens an fbx and walks its joints into TransformData dicts
|
|
@@ -1625,6 +1963,72 @@ class TransformList(DataList):
|
|
|
1625
1963
|
for node in self:
|
|
1626
1964
|
node.name = renamed[id(node)]
|
|
1627
1965
|
|
|
1966
|
+
# --- user defined attributes, as properties --- #
|
|
1967
|
+
|
|
1968
|
+
def __getattr__(self, name: str):
|
|
1969
|
+
"""a user defined attribute read on every node of the view, as
|
|
1970
|
+
``h.heroHeight``: one value per node, None where a node has none"""
|
|
1971
|
+
nodes = None if name.startswith("_") else self.__dict__.get("list")
|
|
1972
|
+
if nodes and any(_has_user_attr(node, name) for node in nodes):
|
|
1973
|
+
return [
|
|
1974
|
+
_user_value(node.user_defined_attributes, name)
|
|
1975
|
+
if _has_user_attr(node, name)
|
|
1976
|
+
else None
|
|
1977
|
+
for node in nodes
|
|
1978
|
+
]
|
|
1979
|
+
raise AttributeError(
|
|
1980
|
+
f"{type(self).__name__!r} object has no attribute {name!r}"
|
|
1981
|
+
)
|
|
1982
|
+
|
|
1983
|
+
def __setattr__(self, name: str, value) -> None:
|
|
1984
|
+
"""sets a user defined attribute on every node of the view that has it,
|
|
1985
|
+
as ``h.heroHeight = 1.8``: every value is converted before any is
|
|
1986
|
+
written. A name no node has raises instead of landing on the list,
|
|
1987
|
+
unless the class declares it."""
|
|
1988
|
+
own = name.startswith("_") or name == "list" or name in self.__dict__
|
|
1989
|
+
if own or hasattr(type(self), name) or _declares(type(self), name):
|
|
1990
|
+
super().__setattr__(name, value)
|
|
1991
|
+
return
|
|
1992
|
+
|
|
1993
|
+
nodes = [n for n in self.__dict__.get("list", []) if _has_user_attr(n, name)]
|
|
1994
|
+
if not nodes:
|
|
1995
|
+
raise AttributeError(
|
|
1996
|
+
f"{type(self).__name__!r} has no attribute {name!r} and no node has "
|
|
1997
|
+
f"a user attribute of that name; add_user_attribute() creates one"
|
|
1998
|
+
)
|
|
1999
|
+
writes = []
|
|
2000
|
+
for node in nodes:
|
|
2001
|
+
attrs = node.user_defined_attributes
|
|
2002
|
+
writes.extend(_user_writes(attrs, name, value, node.name))
|
|
2003
|
+
for spec, converted in writes:
|
|
2004
|
+
spec["value"] = converted
|
|
2005
|
+
|
|
2006
|
+
def __dir__(self):
|
|
2007
|
+
names = set(super().__dir__())
|
|
2008
|
+
for node in self.__dict__.get("list", []):
|
|
2009
|
+
names.update(node.user_defined_attributes or {})
|
|
2010
|
+
return sorted(names)
|
|
2011
|
+
|
|
2012
|
+
def add_user_attribute(
|
|
2013
|
+
self,
|
|
2014
|
+
name: str,
|
|
2015
|
+
value: Any,
|
|
2016
|
+
attribute_type: Optional[str] = None,
|
|
2017
|
+
keyable: bool = True,
|
|
2018
|
+
enum_names: Optional[str] = None,
|
|
2019
|
+
) -> None:
|
|
2020
|
+
"""add_user_attribute() on every node of the view, all or nothing"""
|
|
2021
|
+
specs = []
|
|
2022
|
+
for node in self:
|
|
2023
|
+
node._check_new_user_attr(name)
|
|
2024
|
+
label = f"{node.name}.{name}"
|
|
2025
|
+
spec = _new_user_attr(value, attribute_type, keyable, enum_names, label)
|
|
2026
|
+
specs.append((node, spec))
|
|
2027
|
+
for node, spec in specs:
|
|
2028
|
+
if node.user_defined_attributes is None:
|
|
2029
|
+
node.user_defined_attributes = {}
|
|
2030
|
+
node.user_defined_attributes[name] = spec
|
|
2031
|
+
|
|
1628
2032
|
def _parent_first(self) -> List[int]:
|
|
1629
2033
|
"""view indices ordered so an ancestor is always written before a child
|
|
1630
2034
|
|
|
@@ -14,7 +14,7 @@ build-backend = "setuptools.build_meta"
|
|
|
14
14
|
|
|
15
15
|
[project]
|
|
16
16
|
name = "cgmath"
|
|
17
|
-
version = "1.0.
|
|
17
|
+
version = "1.0.4"
|
|
18
18
|
description = "A DCC-agnostic CG math library: batched matrix, quaternion and euler math, mesh topology, deformers, resampling, skeletal hierarchies, SDFs, a software raytracer and asset IO, all numpy-native."
|
|
19
19
|
readme = "README.md"
|
|
20
20
|
requires-python = ">=3.9"
|
|
@@ -32,32 +32,47 @@ classifiers = [
|
|
|
32
32
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# quiet.
|
|
35
|
+
# Only numpy, scipy and numba are required: without them cgmath does not
|
|
36
|
+
# import. Everything else is OPTIONAL to the library -- each is imported under
|
|
37
|
+
# a guard, so cgmath runs without it and only the feature named beside it
|
|
38
|
+
# complains -- and comes as an extra:
|
|
40
39
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
40
|
+
# pip install cgmath numpy, scipy and numba only
|
|
41
|
+
# pip install "cgmath[gltf,usd]" plus the chosen features
|
|
42
|
+
# pip install "cgmath[all]" everything on PyPI
|
|
43
|
+
#
|
|
44
|
+
# Up to 1.0.3 the extras were plain requirements, so that a bare
|
|
45
|
+
# `pip install` handed over the complete tool. That broke DCC hosts: usd-core
|
|
46
|
+
# brings its own `pxr`, and a second USD build on sys.path collides with the
|
|
47
|
+
# one Maya or Houdini has already loaded. So inside a DCC, leave `usd` out.
|
|
48
|
+
# The Autodesk FBX SDK is not on PyPI and is installed by hand.
|
|
47
49
|
dependencies = [
|
|
48
|
-
# --- core ---------------------------------------------------------
|
|
49
50
|
"numpy",
|
|
50
51
|
"scipy",
|
|
51
52
|
"numba",
|
|
53
|
+
]
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
[project.optional-dependencies]
|
|
56
|
+
image = [
|
|
54
57
|
"pillow", # image read/write behind textures, frames and scenes
|
|
55
58
|
"opencv-python", # image ops in geometry.mesh and render.scene
|
|
56
59
|
"scikit-image", # image measurement helpers in geometry.mesh
|
|
60
|
+
]
|
|
61
|
+
gltf = [
|
|
57
62
|
"pygltflib", # GLB/glTF read and write, and the hierarchy importer
|
|
58
63
|
"trimesh", # mesh interchange behind the GLB path
|
|
64
|
+
]
|
|
65
|
+
usd = [
|
|
59
66
|
"usd-core", # USD stage and prim reading
|
|
60
67
|
]
|
|
68
|
+
all = [
|
|
69
|
+
"pillow",
|
|
70
|
+
"opencv-python",
|
|
71
|
+
"scikit-image",
|
|
72
|
+
"pygltflib",
|
|
73
|
+
"trimesh",
|
|
74
|
+
"usd-core",
|
|
75
|
+
]
|
|
61
76
|
|
|
62
77
|
[project.urls]
|
|
63
78
|
Homepage = "https://github.com/Eric-Vignola/cgmath"
|
|
@@ -1714,6 +1714,18 @@ class Scene(HierarchyData):
|
|
|
1714
1714
|
signature spec.
|
|
1715
1715
|
"""
|
|
1716
1716
|
|
|
1717
|
+
# render settings __init__ and configure() set on the instance, declared so
|
|
1718
|
+
# the hierarchy lists' strict setter accepts them
|
|
1719
|
+
aspect_ratio: Optional[float]
|
|
1720
|
+
default_camera_name: Optional[str]
|
|
1721
|
+
resolution: Optional[Tuple[int, int]]
|
|
1722
|
+
samples_per_pixel: Optional[int]
|
|
1723
|
+
autofit: Optional[bool]
|
|
1724
|
+
default_light: Optional[bool]
|
|
1725
|
+
return_depth: Optional[bool]
|
|
1726
|
+
angle_of_view: Optional[float]
|
|
1727
|
+
fit_padding: Optional[float]
|
|
1728
|
+
|
|
1717
1729
|
def __init__(
|
|
1718
1730
|
self,
|
|
1719
1731
|
name: str = "scene",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|