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.
Files changed (96) hide show
  1. {cgmath-1.0.2/cgmath.egg-info → cgmath-1.0.4}/PKG-INFO +35 -16
  2. {cgmath-1.0.2 → cgmath-1.0.4}/README.md +18 -9
  3. {cgmath-1.0.2 → cgmath-1.0.4}/__init__.py +1 -1
  4. {cgmath-1.0.2 → cgmath-1.0.4/cgmath.egg-info}/PKG-INFO +35 -16
  5. cgmath-1.0.4/cgmath.egg-info/requires.txt +23 -0
  6. {cgmath-1.0.2 → cgmath-1.0.4}/formats/fbx.py +1 -15
  7. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_cdt.py +4 -2
  8. {cgmath-1.0.2 → cgmath-1.0.4}/hierarchy/hierarchy.py +405 -1
  9. {cgmath-1.0.2 → cgmath-1.0.4}/pyproject.toml +29 -14
  10. {cgmath-1.0.2 → cgmath-1.0.4}/render/scene.py +12 -0
  11. cgmath-1.0.2/cgmath.egg-info/requires.txt +0 -9
  12. {cgmath-1.0.2 → cgmath-1.0.4}/LICENSE +0 -0
  13. {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/SOURCES.txt +0 -0
  14. {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/dependency_links.txt +0 -0
  15. {cgmath-1.0.2 → cgmath-1.0.4}/cgmath.egg-info/top_level.txt +0 -0
  16. {cgmath-1.0.2 → cgmath-1.0.4}/constraints/__init__.py +0 -0
  17. {cgmath-1.0.2 → cgmath-1.0.4}/constraints/procrustes.py +0 -0
  18. {cgmath-1.0.2 → cgmath-1.0.4}/formats/__init__.py +0 -0
  19. {cgmath-1.0.2 → cgmath-1.0.4}/formats/glb.py +0 -0
  20. {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/__init__.py +0 -0
  21. {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/prim.py +0 -0
  22. {cgmath-1.0.2 → cgmath-1.0.4}/formats/usd/stage.py +0 -0
  23. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/__init__.py +0 -0
  24. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_base.py +0 -0
  25. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/_saddle_surface.py +0 -0
  26. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/bspline.py +0 -0
  27. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/bspline_patch.py +0 -0
  28. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/camera.py +0 -0
  29. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/__init__.py +0 -0
  30. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/delta_mush.py +0 -0
  31. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/ffd.py +0 -0
  32. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/patch_relax.py +0 -0
  33. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/skin_deform.py +0 -0
  34. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/deform/wrap.py +0 -0
  35. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/delta_mush.py +0 -0
  36. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/ffd.py +0 -0
  37. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/map.py +0 -0
  38. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/mesh.py +0 -0
  39. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/morph_target.py +0 -0
  40. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/pack.py +0 -0
  41. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/patch_relax.py +0 -0
  42. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/raytracer.py +0 -0
  43. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/resample.py +0 -0
  44. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/robust_skinweights_transfer_bilinear.py +0 -0
  45. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/sdf.py +0 -0
  46. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/skin_weights.py +0 -0
  47. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/surface_plotting.py +0 -0
  48. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/texture.py +0 -0
  49. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/__init__.py +0 -0
  50. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/__init__.py +0 -0
  51. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bilinear.py +0 -0
  52. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_blur.py +0 -0
  53. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bspline.py +0 -0
  54. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_bvh.py +0 -0
  55. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_cdt.py +0 -0
  56. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_connectivity.py +0 -0
  57. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_delta_mush.py +0 -0
  58. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_ffd.py +0 -0
  59. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_main.py +0 -0
  60. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_normals.py +0 -0
  61. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_pack.py +0 -0
  62. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_patch_relax.py +0 -0
  63. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_rasterize.py +0 -0
  64. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_sdf.py +0 -0
  65. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_skin_deform.py +0 -0
  66. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_skin_weights.py +0 -0
  67. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_subdivide.py +0 -0
  68. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_subdivision.py +0 -0
  69. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_tangent_space.py +0 -0
  70. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_topology.py +0 -0
  71. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/_numba/_wrap.py +0 -0
  72. {cgmath-1.0.2 → cgmath-1.0.4}/geometry/utils/main.py +0 -0
  73. {cgmath-1.0.2 → cgmath-1.0.4}/hierarchy/__init__.py +0 -0
  74. {cgmath-1.0.2 → cgmath-1.0.4}/rbf/__init__.py +0 -0
  75. {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_kernels.py +0 -0
  76. {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/__init__.py +0 -0
  77. {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/_kernels.py +0 -0
  78. {cgmath-1.0.2 → cgmath-1.0.4}/rbf/_numba/_lu.py +0 -0
  79. {cgmath-1.0.2 → cgmath-1.0.4}/render/__init__.py +0 -0
  80. {cgmath-1.0.2 → cgmath-1.0.4}/render/_numba/__init__.py +0 -0
  81. {cgmath-1.0.2 → cgmath-1.0.4}/render/_numba/_texture.py +0 -0
  82. {cgmath-1.0.2 → cgmath-1.0.4}/render/camera.py +0 -0
  83. {cgmath-1.0.2 → cgmath-1.0.4}/render/frame.py +0 -0
  84. {cgmath-1.0.2 → cgmath-1.0.4}/render/raytracer.py +0 -0
  85. {cgmath-1.0.2 → cgmath-1.0.4}/render/texture.py +0 -0
  86. {cgmath-1.0.2 → cgmath-1.0.4}/setup.cfg +0 -0
  87. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/__init__.py +0 -0
  88. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/__init__.py +0 -0
  89. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_axis.py +0 -0
  90. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_euler.py +0 -0
  91. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_matrix.py +0 -0
  92. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_quaternion.py +0 -0
  93. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/_numba/_vector.py +0 -0
  94. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/main.py +0 -0
  95. {cgmath-1.0.2 → cgmath-1.0.4}/transforms/utils.py +0 -0
  96. {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.2
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
- Requires-Dist: pillow
56
- Requires-Dist: opencv-python
57
- Requires-Dist: scikit-image
58
- Requires-Dist: pygltflib
59
- Requires-Dist: trimesh
60
- Requires-Dist: usd-core
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` — installed with the wheel | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | always present after `pip install`. Run off `sys.path` without it, `import cgmath.formats.usd.prim` itself raises `ImportError` and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
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
- Optional, per feature (see the table above for how each degrades):
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 texture I/O and rendered frames
377
- pygltflib, trimesh glTF / GLB read and write
378
- Autodesk FBX SDK FBX read and write
379
- pxr (USD) USD stage read and write
380
- scikit-image or OpenCV UV rasterization
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` — installed with the wheel | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | always present after `pip install`. Run off `sys.path` without it, `import cgmath.formats.usd.prim` itself raises `ImportError` and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
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
- Optional, per feature (see the table above for how each degrades):
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 texture I/O and rendered frames
315
- pygltflib, trimesh glTF / GLB read and write
316
- Autodesk FBX SDK FBX read and write
317
- pxr (USD) USD stage read and write
318
- scikit-image or OpenCV UV rasterization
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
@@ -4,4 +4,4 @@ The underlying matrix, quaternion, euler and vector math lives in
4
4
  :mod:`cgmath.transforms`.
5
5
  """
6
6
 
7
- __version__ = "1.0.2"
7
+ __version__ = "1.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgmath
3
- Version: 1.0.2
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
- Requires-Dist: pillow
56
- Requires-Dist: opencv-python
57
- Requires-Dist: scikit-image
58
- Requires-Dist: pygltflib
59
- Requires-Dist: trimesh
60
- Requires-Dist: usd-core
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` — installed with the wheel | `cgmath.formats.usd` stage / prim helpers; `from_prim` / `to_prim` / `load_usd` in `geometry` | always present after `pip install`. Run off `sys.path` without it, `import cgmath.formats.usd.prim` itself raises `ImportError` and the `geometry` paths raise `ImportError` at call time (`geometry.utils.pxr()` is the accessor) |
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
- Optional, per feature (see the table above for how each degrades):
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 texture I/O and rendered frames
377
- pygltflib, trimesh glTF / GLB read and write
378
- Autodesk FBX SDK FBX read and write
379
- pxr (USD) USD stage read and write
380
- scikit-image or OpenCV UV rasterization
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
@@ -0,0 +1,23 @@
1
+ numpy
2
+ scipy
3
+ numba
4
+
5
+ [all]
6
+ pillow
7
+ opencv-python
8
+ scikit-image
9
+ pygltflib
10
+ trimesh
11
+ usd-core
12
+
13
+ [gltf]
14
+ pygltflib
15
+ trimesh
16
+
17
+ [image]
18
+ pillow
19
+ opencv-python
20
+ scikit-image
21
+
22
+ [usd]
23
+ usd-core
@@ -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 strictly longer.
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.2"
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
- # THREE of these are the real dependencies -- numpy, scipy and numba.
36
- # Without them cgmath does not import. Everything after them is
37
- # OPTIONAL to the library: each is imported under a guard that degrades to
38
- # None, so cgmath runs without it and only the feature named beside it goes
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
- # They are declared as requirements anyway, deliberately. A plain
42
- # `pip install` should hand somebody a complete tool rather than a core that
43
- # silently lacks half its file formats, and every one of them is a wheel on
44
- # PyPI that costs nothing but disk. Anyone who wants the three alone can
45
- # install them and drop the package on sys.path, which is how this library
46
- # was used before it was packaged.
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
- # --- optional: installed for convenience, guarded in the source -----
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",
@@ -1,9 +0,0 @@
1
- numpy
2
- scipy
3
- numba
4
- pillow
5
- opencv-python
6
- scikit-image
7
- pygltflib
8
- trimesh
9
- usd-core
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