cgmath 3.0.2__tar.gz → 3.0.3__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 (211) hide show
  1. {cgmath-3.0.2/cgmath.egg-info → cgmath-3.0.3}/PKG-INFO +1 -1
  2. {cgmath-3.0.2 → cgmath-3.0.3}/__init__.py +1 -1
  3. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_export.py +250 -0
  4. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_labels.py +192 -2
  5. {cgmath-3.0.2 → cgmath-3.0.3/cgmath.egg-info}/PKG-INFO +1 -1
  6. {cgmath-3.0.2 → cgmath-3.0.3}/formats/fbx.py +35 -10
  7. {cgmath-3.0.2 → cgmath-3.0.3}/hierarchy/hierarchy.py +273 -101
  8. {cgmath-3.0.2 → cgmath-3.0.3}/pyproject.toml +1 -1
  9. {cgmath-3.0.2 → cgmath-3.0.3}/LICENSE +0 -0
  10. {cgmath-3.0.2 → cgmath-3.0.3}/README.md +0 -0
  11. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/__init__.py +0 -0
  12. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_curves.py +0 -0
  13. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_deform.py +0 -0
  14. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_geometry.py +0 -0
  15. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_hierarchy.py +0 -0
  16. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_render.py +0 -0
  17. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_maya_rotate_order.py +0 -0
  18. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/maya_euler.npz +0 -0
  19. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/mikktspace_deformed_data.json +0 -0
  20. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/mikktspace_test_data.json +0 -0
  21. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.curve.pkl +0 -0
  22. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.mesh.pkl +0 -0
  23. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.patch.pkl +0 -0
  24. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.saved_by_2_0_0.zip +0 -0
  25. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_geometry.bspline_best_fit_params.npz +0 -0
  26. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_geometry.bspline_maya_curves.json +0 -0
  27. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.json +0 -0
  28. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.npz +0 -0
  29. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.pkl +0 -0
  30. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node_clip.json +0 -0
  31. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.results.npz +0 -0
  32. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.skeleton.json +0 -0
  33. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.bitmap_buffer.npz +0 -0
  34. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.source_mesh_data.json +0 -0
  35. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.source_uv_list.json +0 -0
  36. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data.npz +0 -0
  37. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_ngons_only.npz +0 -0
  38. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_preserve_order.npz +0 -0
  39. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_preserve_order_ngons_only.npz +0 -0
  40. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list.npz +0 -0
  41. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_ngons_only.npz +0 -0
  42. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_preserve_order.npz +0 -0
  43. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_preserve_order_ngons_only.npz +0 -0
  44. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_bs_data.npz +0 -0
  45. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_mesh_data.npz +0 -0
  46. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_skin_data.npz +0 -0
  47. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_uv_data.npz +0 -0
  48. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.overlap.pkl +0 -0
  49. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_bs_data.npz +0 -0
  50. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_mesh_data.npz +0 -0
  51. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_skin_data.npz +0 -0
  52. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_uv_data.npz +0 -0
  53. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_sdf.igloo.npz +0 -0
  54. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_base.py +0 -0
  55. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_base_setattr.py +0 -0
  56. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_bspline_kernels.py +0 -0
  57. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_clip.py +0 -0
  58. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_constraints.py +0 -0
  59. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_curves_empty.py +0 -0
  60. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_curves_types.py +0 -0
  61. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_data_module.py +0 -0
  62. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_deform_files.py +0 -0
  63. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_deform_utils.py +0 -0
  64. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_delta_mush.py +0 -0
  65. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_euler_maya.py +0 -0
  66. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_import.py +0 -0
  67. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_lifetime.py +0 -0
  68. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_ffd.py +0 -0
  69. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_file_routes.py +0 -0
  70. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry.py +0 -0
  71. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry_io.py +0 -0
  72. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry_utils.py +0 -0
  73. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_glb.py +0 -0
  74. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_glb_materials.py +0 -0
  75. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_hierarchy.py +0 -0
  76. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_hierarchy_io.py +0 -0
  77. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_import_order.py +0 -0
  78. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_imports.py +0 -0
  79. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_list_item_class.py +0 -0
  80. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_map.py +0 -0
  81. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_mesh_usd.py +0 -0
  82. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_morph_target.py +0 -0
  83. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_namespaces.py +0 -0
  84. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_ngon.py +0 -0
  85. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_normals_quadmatch_utils.py +0 -0
  86. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_object_skin.py +0 -0
  87. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_pack.py +0 -0
  88. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_parent_lookups.py +0 -0
  89. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_patch_relax.py +0 -0
  90. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_procrustes_rotation.py +0 -0
  91. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_quadrangulate.py +0 -0
  92. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_raycast.py +0 -0
  93. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_raycast_bvh.py +0 -0
  94. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_rbf.py +0 -0
  95. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_cache.py +0 -0
  96. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_fit_padding.py +0 -0
  97. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_roundtrip_extra.py +0 -0
  98. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_serialization.py +0 -0
  99. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_threads.py +0 -0
  100. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_resample.py +0 -0
  101. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_resample_bezier.py +0 -0
  102. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_robust_skinweights_transfer_bilinear.py +0 -0
  103. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_roundtrip.py +0 -0
  104. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_saddle_surface.py +0 -0
  105. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_scene.py +0 -0
  106. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_scene_animate.py +0 -0
  107. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_sdf.py +0 -0
  108. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_short_names.py +0 -0
  109. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_deform.py +0 -0
  110. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_fbx.py +0 -0
  111. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_glb.py +0 -0
  112. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_kernels.py +0 -0
  113. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_weights.py +0 -0
  114. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_symmetry.py +0 -0
  115. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_triangulation.py +0 -0
  116. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_usd_prim.py +0 -0
  117. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_utils.py +0 -0
  118. {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_wrap.py +0 -0
  119. {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/SOURCES.txt +0 -0
  120. {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/dependency_links.txt +0 -0
  121. {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/requires.txt +0 -0
  122. {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/top_level.txt +0 -0
  123. {cgmath-3.0.2 → cgmath-3.0.3}/constraints/__init__.py +0 -0
  124. {cgmath-3.0.2 → cgmath-3.0.3}/constraints/procrustes.py +0 -0
  125. {cgmath-3.0.2 → cgmath-3.0.3}/data.py +0 -0
  126. {cgmath-3.0.2 → cgmath-3.0.3}/formats/__init__.py +0 -0
  127. {cgmath-3.0.2 → cgmath-3.0.3}/formats/_fbx_io.py +0 -0
  128. {cgmath-3.0.2 → cgmath-3.0.3}/formats/glb.py +0 -0
  129. {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/__init__.py +0 -0
  130. {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/prim.py +0 -0
  131. {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/stage.py +0 -0
  132. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/__init__.py +0 -0
  133. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/_cdt.py +0 -0
  134. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/_saddle_surface.py +0 -0
  135. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/bspline.py +0 -0
  136. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/bspline_patch.py +0 -0
  137. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/__init__.py +0 -0
  138. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/delta_mush.py +0 -0
  139. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/ffd.py +0 -0
  140. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/patch_relax.py +0 -0
  141. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/skin_deform.py +0 -0
  142. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/wrap.py +0 -0
  143. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/map.py +0 -0
  144. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/mesh.py +0 -0
  145. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/morph_target.py +0 -0
  146. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/pack.py +0 -0
  147. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/resample.py +0 -0
  148. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/robust_skinweights_transfer_bilinear.py +0 -0
  149. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/sdf.py +0 -0
  150. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/skin_weights.py +0 -0
  151. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/__init__.py +0 -0
  152. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/__init__.py +0 -0
  153. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bilinear.py +0 -0
  154. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_blur.py +0 -0
  155. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bspline.py +0 -0
  156. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bvh.py +0 -0
  157. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_cdt.py +0 -0
  158. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_connectivity.py +0 -0
  159. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_delta_mush.py +0 -0
  160. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_ffd.py +0 -0
  161. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_main.py +0 -0
  162. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_normals.py +0 -0
  163. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_pack.py +0 -0
  164. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_patch_relax.py +0 -0
  165. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_rasterize.py +0 -0
  166. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_sdf.py +0 -0
  167. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_skin_deform.py +0 -0
  168. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_skin_weights.py +0 -0
  169. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_subdivide.py +0 -0
  170. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_subdivision.py +0 -0
  171. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_tangent_space.py +0 -0
  172. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_topology.py +0 -0
  173. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_wrap.py +0 -0
  174. {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/main.py +0 -0
  175. {cgmath-3.0.2 → cgmath-3.0.3}/hierarchy/__init__.py +0 -0
  176. {cgmath-3.0.2 → cgmath-3.0.3}/rbf/__init__.py +0 -0
  177. {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_kernels.py +0 -0
  178. {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/__init__.py +0 -0
  179. {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/_kernels.py +0 -0
  180. {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/_lu.py +0 -0
  181. {cgmath-3.0.2 → cgmath-3.0.3}/render/__init__.py +0 -0
  182. {cgmath-3.0.2 → cgmath-3.0.3}/render/_numba/__init__.py +0 -0
  183. {cgmath-3.0.2 → cgmath-3.0.3}/render/_numba/_texture.py +0 -0
  184. {cgmath-3.0.2 → cgmath-3.0.3}/render/_tiles.py +0 -0
  185. {cgmath-3.0.2 → cgmath-3.0.3}/render/camera.py +0 -0
  186. {cgmath-3.0.2 → cgmath-3.0.3}/render/frame.py +0 -0
  187. {cgmath-3.0.2 → cgmath-3.0.3}/render/raytracer.py +0 -0
  188. {cgmath-3.0.2 → cgmath-3.0.3}/render/scene.py +0 -0
  189. {cgmath-3.0.2 → cgmath-3.0.3}/render/texture.py +0 -0
  190. {cgmath-3.0.2 → cgmath-3.0.3}/setup.cfg +0 -0
  191. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/__init__.py +0 -0
  192. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/__init__.py +0 -0
  193. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_axis.py +0 -0
  194. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_euler.py +0 -0
  195. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_matrix.py +0 -0
  196. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_quaternion.py +0 -0
  197. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_vector.py +0 -0
  198. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/__init__.py +0 -0
  199. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_axis.py +0 -0
  200. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_broadcasting.py +0 -0
  201. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_euler.py +0 -0
  202. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_exports.py +0 -0
  203. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_matrix.py +0 -0
  204. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_quaternion.py +0 -0
  205. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_rotation_conversions.py +0 -0
  206. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_slerp.py +0 -0
  207. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_threads.py +0 -0
  208. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_vector.py +0 -0
  209. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/main.py +0 -0
  210. {cgmath-3.0.2 → cgmath-3.0.3}/transforms/utils.py +0 -0
  211. {cgmath-3.0.2 → cgmath-3.0.3}/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgmath
3
- Version: 3.0.2
3
+ Version: 3.0.3
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
@@ -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__ = "3.0.2"
7
+ __version__ = "3.0.3"
@@ -38,6 +38,117 @@ def _joint(name, parent=None, **kwargs):
38
38
  )
39
39
 
40
40
 
41
+ def _node(name, parent=None, node_type="transform", **kwargs):
42
+ """Returns a TransformData of ``node_type`` with a uuid, parented by uuid."""
43
+ return TransformData(
44
+ name = name,
45
+ node_type = node_type,
46
+ uuid = str(uuid.uuid4()).upper(),
47
+ parent_uuid = parent.uuid if parent is not None else None,
48
+ **kwargs,
49
+ )
50
+
51
+
52
+ def scaled_rig():
53
+ """probe_save.py's scaled rig: a group scaled (1, 3, 0.5) turned 30 over
54
+ a turned group with a locator under it, a joint and a turned
55
+ space_transform, and beside them a group, a locator and a joint with
56
+ segment_scale_compensate on"""
57
+ top = _node("sc", rotate=(0.0, 0.0, 30.0), scale=(1.0, 3.0, 0.5))
58
+ kid = _node("sc_kid", top, rotate=(40.0, 10.0, -20.0), translate=(1.0, 1.0, 0.0))
59
+ return HierarchyData([
60
+ top,
61
+ kid,
62
+ _node("sc_loc", kid, "locator", translate=(0.0, 2.0, 1.0)),
63
+ _node("sc_jnt", top, "joint", rotate=(40.0, 10.0, -20.0), translate=(-1.0, 1.0, 0.0)),
64
+ _node("sc_spc", top, "space_transform", rotate=(40.0, 10.0, -20.0), translate=(-1.0, -1.0, 0.0)),
65
+ _node("sc_grp", top, rotate=(10.0, 20.0, 30.0), translate=(1.0, 0.0, 1.0), scale=(1.0, 1.5, 0.75),
66
+ segment_scale_compensate=True),
67
+ _node("sc_lcf", top, "locator", translate=(0.5, 1.0, -2.0), segment_scale_compensate=True),
68
+ _node("sc_cmp", top, "joint", translate=(0.0, 1.0, 1.0), segment_scale_compensate=True),
69
+ ])
70
+
71
+
72
+ # the parents a group with segment_scale_compensate on is saved under:
73
+ # (label, P's scale, P's rotate, G's scale or None for no G); the first three
74
+ # are not mirrored, the last three are
75
+ FLAGGED_CASES = (
76
+ ("(1, 2, 3)", (1.0, 2.0, 3.0), (0.0, 30.0, 0.0), None),
77
+ ("(2, 2, 2)", (2.0, 2.0, 2.0), (0.0, 30.0, 0.0), None),
78
+ ("(2, 1, 0.5) under (1, 2, 3)", (2.0, 1.0, 0.5), (0.0, 30.0, 15.0), (1.0, 2.0, 3.0)),
79
+ ("(-1, 2, 3)", (-1.0, 2.0, 3.0), (0.0, 30.0, 0.0), None),
80
+ ("(-2, -2, -2)", (-2.0, -2.0, -2.0), (0.0, 30.0, 0.0), None),
81
+ ("(-1, 1, 1) under (1, 2, 3)", (-1.0, 1.0, 1.0), (0.0, 30.0, 0.0), (1.0, 2.0, 3.0)),
82
+ )
83
+
84
+
85
+ # the parents a group with segment_scale_compensate on and its own scale
86
+ # mirrored is saved under: (label, P's scale or None for no P); the sdk places
87
+ # it under each, Maya's importer under the last two alone, which mirror too
88
+ MIRRORED_CASES = (
89
+ ("no parent", None),
90
+ ("(1, 1, 1)", (1.0, 1.0, 1.0)),
91
+ ("(2, 2, 2)", (2.0, 2.0, 2.0)),
92
+ ("(1, 2, 3)", (1.0, 2.0, 3.0)),
93
+ ("(-1, 2, 3)", (-1.0, 2.0, 3.0)),
94
+ ("(-2, -2, -2)", (-2.0, -2.0, -2.0)),
95
+ )
96
+
97
+
98
+ def flagged_rig(scale, rotate, grand=None, own=(1.0, 1.5, 0.75)):
99
+ """a group gRrs and a locator xRrs with segment_scale_compensate on under
100
+ a group P at (0, 1, 0) turned ``rotate`` and scaled ``scale``, when given,
101
+ itself under a group G at (0, 0.5, 0) turned (10, 0, 20) and scaled
102
+ ``grand`` when given: gRrs at (2, 0.5, -0.5) turned (20, 40, 60) and
103
+ scaled ``own``, over a locator lRrs, a turned group cRrs over a locator
104
+ dRrs, and a joint jRrs over a joint kRrs, both compensating their
105
+ parent's scale (flag_mirror.py's rig); xRrs scaled the signs of ``own``"""
106
+ nodes = []
107
+ top = None
108
+ if grand is not None:
109
+ top = _node("G", translate=(0.0, 0.5, 0.0), rotate=(10.0, 0.0, 20.0), scale=grand)
110
+ nodes.append(top)
111
+ P = top
112
+ if scale is not None:
113
+ P = _node("P", top, translate=(0.0, 1.0, 0.0), rotate=rotate, scale=scale)
114
+ nodes.append(P)
115
+ g = _node("gRrs", P, translate=(2.0, 0.5, -0.5), rotate=(20.0, 40.0, 60.0), scale=own,
116
+ segment_scale_compensate=True)
117
+ c = _node("cRrs", g, translate=(1.0, 0.0, 0.0), rotate=(10.0, 20.0, 30.0))
118
+ j = _node("jRrs", g, "joint", translate=(0.0, 0.0, 1.0), segment_scale_compensate=True)
119
+ nodes += [
120
+ g,
121
+ _node("lRrs", g, "locator", translate=(0.5, 1.0, -2.0)),
122
+ c,
123
+ _node("dRrs", c, "locator", translate=(0.0, 1.0, 0.0)),
124
+ j,
125
+ _node("kRrs", j, "joint", translate=(0.0, 1.0, 0.0), segment_scale_compensate=True),
126
+ _node("xRrs", P, "locator", translate=(-1.0, 0.5, 0.5), rotate=(0.0, 0.0, 45.0), scale=np.sign(own),
127
+ segment_scale_compensate=True),
128
+ ]
129
+ return HierarchyData(nodes)
130
+
131
+
132
+ def tiny_rig():
133
+ """probe_l1b.py's rig, and -2e-12 beside its scales: for each value v a
134
+ group K<i> scaled (1.32, 1.8, v) over a joint KJ<i> and a locator KL<i>"""
135
+ nodes = []
136
+ for i, v in enumerate((0.0, 1e-13, -1e-13, -2e-12)):
137
+ top = _node(f"K{i}", translate=(0.67 + 3 * i, 2.84, 3.5), rotate=(27.5, 5.9, 85.6), scale=(1.32, 1.8, v))
138
+ nodes += [
139
+ top,
140
+ _node(f"KJ{i}", top, "joint", translate=(0.3, 0.4, 0.2), rotate=(10.0, 0.0, 30.0)),
141
+ _node(f"KL{i}", top, "locator", translate=(0.3, 1.0, 0.25), rotate=(20.0, 0.0, 0.0)),
142
+ ]
143
+ return HierarchyData(nodes)
144
+
145
+
146
+ def gaps(got, want):
147
+ """(3x3, position) largest gaps between two world matrices"""
148
+ got, want = np.asarray(got), np.asarray(want)
149
+ return float(np.abs(got[:3, :3] - want[:3, :3]).max()), float(np.abs(got[3, :3] - want[3, :3]).max())
150
+
151
+
41
152
  @contextlib.contextmanager
42
153
  def _no_fbx_warnings(test):
43
154
  """
@@ -174,6 +285,16 @@ class TestFbxExportSkeleton(unittest.TestCase):
174
285
 
175
286
  return self._walk(_attribute)
176
287
 
288
+ def _placed(self):
289
+ """Returns {node name: (inherit type name, sdk world matrix)}."""
290
+
291
+ def _place(node):
292
+ world = node.EvaluateGlobalTransform()
293
+ matrix = np.array([[world.Get(r, c) for c in range(4)] for r in range(4)])
294
+ return node.GetTransformationInheritType().name, matrix
295
+
296
+ return {path.rsplit("|", 1)[-1]: placed for path, placed in self._walk(_place).items()}
297
+
177
298
  def test_duplicate_names_keep_their_parent(self):
178
299
  root = _joint("root")
179
300
  nodes = [root]
@@ -335,6 +456,135 @@ class TestFbxExportSkeleton(unittest.TestCase):
335
456
  self.assertTrue(np.allclose(loaded[name].world_matrix, want, rtol=0.0, atol=1e-9), name)
336
457
  self.assertEqual(loaded[name].rotate_order, rig[name].rotate_order, name)
337
458
 
459
+ def test_every_node_is_written_with_its_inherit_type(self):
460
+ """eInheritRrs on a node that compensates its parent's scale,
461
+ eInheritRSrs on any other, a group or a locator too, as Maya's
462
+ exporter writes a group and a locator. Written with the sdk's
463
+ default, eInheritRrSs, which scales a node along its own axes by its
464
+ parent's world scale, a turned group under a group scaled (1, 3, 0.5)
465
+ sat 1.58 off the rig in its 3x3 in the sdk, and a locator under it
466
+ 3.17 in position (probe_save.py); the sdk now puts every node where
467
+ the rig has it"""
468
+ rig = scaled_rig()
469
+ self._export(rig)
470
+ placed = self._placed()
471
+ for node in rig:
472
+ with self.subTest(node.name):
473
+ inherit, world = placed[node.name]
474
+ axes, position = gaps(world, node.world_matrix)
475
+ self.assertLessEqual(axes, 1e-9)
476
+ # the largest measured on this file
477
+ self.assertLessEqual(position, 8.881784197001252e-16)
478
+ self.assertEqual(inherit, "eInheritRrs" if node.segment_scale_compensate else "eInheritRSrs")
479
+
480
+ def test_a_mirrored_group_compensating_its_parents_scale_is_written_eInheritRrs(self):
481
+ """a group or locator with segment_scale_compensate on and its own
482
+ scale mirrored is written eInheritRrs and the sdk puts it and every
483
+ node under it where the rig has it, with no parent, under a mirrored
484
+ parent and under one that is not (TransformList.save_fbx says where
485
+ Maya's importer does not). Written eInheritRrSs, its axes sat off
486
+ under any parent scaled other than 1"""
487
+ # the largest position gap measured on each file
488
+ bounds = {
489
+ "no parent": 6.661338147750939e-16,
490
+ "(1, 1, 1)": 4.440892098500626e-16,
491
+ "(2, 2, 2)": 4.440892098500626e-16,
492
+ "(1, 2, 3)": 4.440892098500626e-16,
493
+ "(-1, 2, 3)": 4.440892098500626e-16,
494
+ "(-2, -2, -2)": 4.440892098500626e-16,
495
+ }
496
+ for label, scale in MIRRORED_CASES:
497
+ with self.subTest(label):
498
+ rig = flagged_rig(scale, (0.0, 30.0, 0.0), own=(-1.0, 1.5, 0.75))
499
+ self._export(rig)
500
+ placed = self._placed()
501
+ for node in rig:
502
+ axes, position = gaps(placed[node.name][1], node.world_matrix)
503
+ self.assertLessEqual(axes, 1e-9, node.name)
504
+ self.assertLessEqual(position, bounds[label], node.name)
505
+ inherit = "eInheritRrs" if node.segment_scale_compensate else "eInheritRSrs"
506
+ self.assertEqual(placed[node.name][0], inherit, node.name)
507
+
508
+ def test_a_group_compensating_its_parents_scale_is_written_eInheritRrs(self):
509
+ """a group or locator with segment_scale_compensate on, which a rig
510
+ built or edited by hand can hold, is written eInheritRrs, as a joint
511
+ is, so the sdk puts it and every node under it where the rig has it,
512
+ under mirrored and sheared parents too; written eInheritRrSs, a
513
+ group's axes sat 1.32 off under a parent scaled (1, 2, 3). Read back
514
+ its flag is off, as this release reads every node that is no joint,
515
+ its channels the ones saved"""
516
+ # the largest position gap measured on each file
517
+ bounds = {
518
+ "(1, 2, 3)": 4.440892098500626e-16,
519
+ "(2, 2, 2)": 4.440892098500626e-16,
520
+ "(2, 1, 0.5) under (1, 2, 3)": 3.552713678800501e-15,
521
+ "(-1, 2, 3)": 4.440892098500626e-16,
522
+ "(-2, -2, -2)": 8.881784197001252e-16,
523
+ "(-1, 1, 1) under (1, 2, 3)": 1.7763568394002505e-15,
524
+ }
525
+ for label, scale, rotate, grand in FLAGGED_CASES:
526
+ with self.subTest(label):
527
+ rig = flagged_rig(scale, rotate, grand)
528
+ self._export(rig)
529
+ placed = self._placed()
530
+ for node in rig:
531
+ axes, position = gaps(placed[node.name][1], node.world_matrix)
532
+ self.assertLessEqual(axes, 1e-9, node.name)
533
+ self.assertLessEqual(position, bounds[label], node.name)
534
+ for node in rig:
535
+ inherit = "eInheritRrs" if node.segment_scale_compensate else "eInheritRSrs"
536
+ self.assertEqual(placed[node.name][0], inherit, node.name)
537
+
538
+ back = HierarchyData.load_fbx(self.path)
539
+ for node in rig:
540
+ read = back[node.name]
541
+ flag = node.segment_scale_compensate and node.node_type == "joint"
542
+ self.assertEqual(read.segment_scale_compensate, flag, node.name)
543
+ for channel in ("translate", "rotate", "scale"):
544
+ np.testing.assert_allclose(
545
+ getattr(read, channel), getattr(node, channel), rtol=0.0, atol=1e-12, err_msg=node.name
546
+ )
547
+
548
+ def test_a_scale_under_1e_12_is_written_as_maya_writes_it(self):
549
+ """a scale under 1e-12 in size, 0 and -0 among them, is written as
550
+ 1e-12, positive, as Maya 2025's FBXExport writes a static one; one of
551
+ 1e-12 or more in size as it is (probe_l1.py)"""
552
+ values = (0.0, -0.0, 1e-13, -1e-13, 5e-13, 1e-12, -1e-12, 2e-12)
553
+ rig = HierarchyData([_node(f"k{i}", scale=(1.32, 1.8, v)) for i, v in enumerate(values)])
554
+ rig.append(_node("joint", node_type="joint", scale=(0.0, 1.0, -1e-13)))
555
+ rig.append(_node("locator", node_type="locator", scale=(1e-13, -0.0, 3.0)))
556
+ self._export(rig)
557
+
558
+ written = {name: tuple(scale) for name, scale in self._walk(lambda node: node.LclScaling.Get()).items()}
559
+ expected = (1e-12, 1e-12, 1e-12, 1e-12, 1e-12, 1e-12, -1e-12, 2e-12)
560
+ for i, z in enumerate(expected):
561
+ self.assertEqual(repr(written[f"|k{i}"]), repr((1.32, 1.8, z)), values[i])
562
+ self.assertEqual(repr(written["|joint"]), repr((1e-12, 1.0, 1e-12)))
563
+ self.assertEqual(repr(written["|locator"]), repr((1e-12, 1e-12, 3.0)))
564
+
565
+ def test_a_scale_under_1e_12_reads_back_as_1e_12(self):
566
+ """probe_l1b.py's rig, a group scaled (1.32, 1.8, v) over a joint and
567
+ a locator, v 0, 1e-13 and -1e-13, and -2e-12 beside them, read back
568
+ from its save: v as 1e-12, -2e-12 as it is, every world equal to the
569
+ rig's with 1e-12 in place of each v, and within 1e-12, the size of
570
+ the clamp, of the rig's own (l1c.py). Written as given, the read gave
571
+ v back"""
572
+ rig = tiny_rig()
573
+ self._export(rig)
574
+ back = HierarchyData.load_fbx(self.path)
575
+
576
+ clamped = tiny_rig()
577
+ for i, z in enumerate((1e-12, 1e-12, 1e-12, -2e-12)):
578
+ self.assertEqual(back[f"K{i}"].scale.tolist(), [1.32, 1.8, z])
579
+ clamped[f"K{i}"].scale = (1.32, 1.8, z)
580
+ for node in rig:
581
+ read = back[node.name].world_matrix
582
+ axes, position = gaps(read, clamped[node.name].world_matrix)
583
+ self.assertLessEqual(axes, 1e-9, node.name)
584
+ # the largest measured on this file
585
+ self.assertLessEqual(position, 0.0, node.name)
586
+ self.assertLessEqual(max(gaps(read, node.world_matrix)), 1e-12, node.name)
587
+
338
588
  def test_unsupported_node_types_are_skipped(self):
339
589
  # a constraint can't be exported, it is skipped along with what is under it
340
590
  weight = {"w0": {"attributeType": "double", "value": 1.0, "keyable": True}}
@@ -1191,12 +1191,47 @@ def _maya():
1191
1191
  return cmds, mel
1192
1192
 
1193
1193
 
1194
+ def _stored(path, inherit) -> str:
1195
+ """load_fbx's rig, written with the sdk: a group P at (0, 1, 0) turned
1196
+ (0, 30, 0) and scaled (1, 2, 3) over a group g at (2, 0.5, -0.5) turned
1197
+ (20, 40, 60), scaled (1, 1.5, 0.75) and stored ``inherit``, over a locator
1198
+ l at (0.5, 1, -2)"""
1199
+ manager = FBX.FbxManager.Create()
1200
+ manager.SetIOSettings(FBX.FbxIOSettings.Create(manager, FBX.IOSROOT))
1201
+ scene = FBX.FbxScene.Create(manager, "scene")
1202
+ scene.GetGlobalSettings().SetAxisSystem(FBX.FbxAxisSystem.MayaYUp)
1203
+
1204
+ def node(name, parent, translate, rotate=(0, 0, 0), scale=(1, 1, 1), stored=None, look="eNone"):
1205
+ made = FBX.FbxNode.Create(scene, name)
1206
+ attr = FBX.FbxNull.Create(scene, name)
1207
+ attr.Look.Set(getattr(FBX.FbxNull.ELook, look).value)
1208
+ made.SetNodeAttribute(attr)
1209
+ made.LclTranslation.Set(FBX.FbxDouble3(*translate))
1210
+ made.LclRotation.Set(FBX.FbxDouble3(*rotate))
1211
+ made.LclScaling.Set(FBX.FbxDouble3(*scale))
1212
+ made.SetRotationActive(True)
1213
+ made.SetTransformationInheritType(stored or FBX.FbxTransform.EInheritType.eInheritRSrs)
1214
+ parent.AddChild(made)
1215
+ return made
1216
+
1217
+ top = node("P", scene.GetRootNode(), (0, 1, 0), (0, 30, 0), (1, 2, 3))
1218
+ grp = node("g", top, (2, 0.5, -0.5), (20, 40, 60), (1, 1.5, 0.75), inherit)
1219
+ node("l", grp, (0.5, 1, -2), look="eCross")
1220
+ exporter = FBX.FbxExporter.Create(manager, "")
1221
+ exporter.Initialize(path, -1, manager.GetIOSettings())
1222
+ exporter.Export(scene)
1223
+ exporter.Destroy()
1224
+ manager.Destroy()
1225
+ return path
1226
+
1227
+
1194
1228
  @unittest.skipIf(FBX is None, "the Autodesk FBX SDK is required")
1195
1229
  class TestSaveFbxInMaya(unittest.TestCase):
1196
1230
  """Maya's FBXImport of a save puts every node where it puts the file the
1197
1231
  values were read from, and a mesh, placed under the group its node is
1198
- saved as, where Maya puts the file's mesh. Needs mayapy with the fbxmaya
1199
- plug-in"""
1232
+ saved as, where Maya puts the file's mesh, but for a node that is no joint
1233
+ the read puts off the file, which it puts where the read has it. Needs
1234
+ mayapy with the fbxmaya plug-in"""
1200
1235
 
1201
1236
  @classmethod
1202
1237
  def setUpClass(cls):
@@ -1279,6 +1314,161 @@ class TestSaveFbxInMaya(unittest.TestCase):
1279
1314
  self.assertTrue(np.allclose(self.cmds.getAttr("hip.jointOrient")[0], (0.0, 0.0, 90.0)))
1280
1315
  self.assertLess(float(np.abs(worlds["hip"] - np.asarray(rig["hip"].world_matrix)).max()), 1e-9)
1281
1316
 
1317
+ def test_groups_and_locators_import_where_the_rig_has_them(self):
1318
+ """every node written with its inherit type, eInheritRSrs on a group
1319
+ or a locator as Maya's exporter writes them: written eInheritRrSs, a
1320
+ turned group under a group scaled (1, 3, 0.5) imported 1.58 off the
1321
+ rig in its 3x3, and a locator under it 3.17 in position
1322
+ (probe_save.py)"""
1323
+ from cgmath._tests.test_fbx_export import gaps, scaled_rig
1324
+
1325
+ rig = scaled_rig()
1326
+ out = os.path.join(self.out, "scaled.fbx")
1327
+ rig.save_fbx(out)
1328
+ worlds, _ = self.imported(out)
1329
+ for node in rig:
1330
+ axes, position = gaps(worlds[node.name], node.world_matrix)
1331
+ self.assertLessEqual(axes, 1e-9, node.name)
1332
+ # the largest measured on this file
1333
+ self.assertLessEqual(position, 8.881784197001252e-16, node.name)
1334
+
1335
+ def test_a_node_the_read_puts_off_its_file_imports_where_the_read_has_it(self):
1336
+ """load_fbx reads a node that is no joint as eInheritRSrs whatever the
1337
+ file stores and save_fbx writes that, so a load then a save of a file
1338
+ storing one eInheritRrSs or eInheritRrs puts it, in the sdk and in
1339
+ Maya, where the read has it, not where the file does: on load_fbx's
1340
+ rig 1.11 off the file in the 3x3, a locator 2.34 or 1.75 in position.
1341
+ An earlier save_fbx wrote it eInheritRrSs: one stored so imported
1342
+ where the file does, 1.11 off the read (TransformList.save_fbx)"""
1343
+ from cgmath._tests.test_fbx_export import gaps
1344
+
1345
+ # how far the locator's position lands off the file's
1346
+ for inherit, moved in (("eInheritRrSs", 2.34), ("eInheritRrs", 1.75)):
1347
+ with self.subTest(inherit):
1348
+ stored = getattr(FBX.FbxTransform.EInheritType, inherit)
1349
+ path = _stored(os.path.join(self.out, f"{inherit}.fbx"), stored)
1350
+ rig = HierarchyData.load_fbx(path)
1351
+ out = os.path.join(self.out, f"{inherit}_saved.fbx")
1352
+ rig.save_fbx(out)
1353
+ sdk = _sdk_worlds(out)
1354
+ worlds, _ = self.imported(out)
1355
+ file, _ = self.imported(path)
1356
+ for node in rig:
1357
+ for placed in (sdk, worlds):
1358
+ axes, position = gaps(placed[node.name], node.world_matrix)
1359
+ self.assertLessEqual(axes, 1e-9, node.name)
1360
+ # the largest measured on these two files
1361
+ self.assertLessEqual(position, 8.881784197001252e-16, node.name)
1362
+ axes, position = gaps(worlds["l"], file["l"])
1363
+ self.assertAlmostEqual(axes, 1.11, places=2)
1364
+ self.assertAlmostEqual(position, moved, places=2)
1365
+
1366
+ def test_a_mirrored_group_under_a_mirrored_parent_imports_where_the_rig_has_it(self):
1367
+ """a group or locator with segment_scale_compensate on whose own scale
1368
+ and its parent's both mirror: Maya's FBXImport puts it and the groups
1369
+ and locators under it where the rig has them (written eInheritRrSs,
1370
+ off under either parent); it parts from the sdk and the rig where one
1371
+ of the two mirrors alone (TransformList.save_fbx). Not the
1372
+ compensating joints directly under it (flag_mirror.py)"""
1373
+ from cgmath._tests.test_fbx_export import MIRRORED_CASES, flagged_rig, gaps
1374
+
1375
+ # the largest position gap measured on each file
1376
+ bounds = {
1377
+ "(-1, 2, 3)": 8.881784197001252e-16,
1378
+ "(-2, -2, -2)": 8.881784197001252e-16,
1379
+ }
1380
+ for label, scale in MIRRORED_CASES[-2:]:
1381
+ with self.subTest(label):
1382
+ rig = flagged_rig(scale, (0.0, 30.0, 0.0), own=(-1.0, 1.5, 0.75))
1383
+ out = os.path.join(self.out, "mirrored.fbx")
1384
+ rig.save_fbx(out)
1385
+ worlds, _ = self.imported(out)
1386
+ for node in rig:
1387
+ if node.name in ("jRrs", "kRrs"):
1388
+ continue
1389
+ axes, position = gaps(worlds[node.name], node.world_matrix)
1390
+ self.assertLessEqual(axes, 1e-9, node.name)
1391
+ self.assertLessEqual(position, bounds[label], node.name)
1392
+
1393
+ def test_a_group_compensating_its_parents_scale_imports_where_the_rig_has_it(self):
1394
+ """a group or locator with segment_scale_compensate on, written
1395
+ eInheritRrs, under a parent whose own scale is not mirrored: Maya's
1396
+ FBXImport puts it and the groups and locators under it where the rig
1397
+ has them (written eInheritRrSs, the group's axes 1.32 off under a
1398
+ parent scaled (1, 2, 3)). Not the compensating joints directly under
1399
+ it, which Maya's import puts elsewhere than the sdk and the rig, nor
1400
+ any node of its branch under a mirrored parent (flag_mirror.py)"""
1401
+ from cgmath._tests.test_fbx_export import FLAGGED_CASES, flagged_rig, gaps
1402
+
1403
+ # the largest position gap measured on each file
1404
+ bounds = {
1405
+ "(1, 2, 3)": 4.440892098500626e-16,
1406
+ "(2, 2, 2)": 4.440892098500626e-16,
1407
+ "(2, 1, 0.5) under (1, 2, 3)": 2.6645352591003757e-15,
1408
+ }
1409
+ for label, scale, rotate, grand in FLAGGED_CASES[:3]:
1410
+ with self.subTest(label):
1411
+ rig = flagged_rig(scale, rotate, grand)
1412
+ out = os.path.join(self.out, "flagged.fbx")
1413
+ rig.save_fbx(out)
1414
+ worlds, _ = self.imported(out)
1415
+ for node in rig:
1416
+ if node.name in ("jRrs", "kRrs"):
1417
+ continue
1418
+ axes, position = gaps(worlds[node.name], node.world_matrix)
1419
+ self.assertLessEqual(axes, 1e-9, node.name)
1420
+ self.assertLessEqual(position, bounds[label], node.name)
1421
+
1422
+ def test_a_joint_under_a_compensating_group_imports_where_the_rig_has_it(self):
1423
+ """a joint compensating its scale directly under a group with
1424
+ segment_scale_compensate on, and the joint under it, import where the
1425
+ rig has them when the group's scale is positive and it has no parent,
1426
+ or its parent's scale is 1, or 1 in size with two negative axes
1427
+ (TransformList.save_fbx's second case). Written eInheritRrSs, cRrs
1428
+ imported 0.230 off in its 3x3 with no parent, and under (-1, 1, -1)
1429
+ gRrs 1.74, jRrs 1.76 and lRrs 4.06 in position (flag_mirror.py's rig)"""
1430
+ from cgmath._tests.test_fbx_export import flagged_rig, gaps
1431
+
1432
+ # P's scale, or None for no parent; the largest position gap measured
1433
+ # on each file
1434
+ cases = {
1435
+ "no parent": (None, 6.661338147750939e-16),
1436
+ "(1, 1, 1)": ((1.0, 1.0, 1.0), 4.440892098500626e-16),
1437
+ "(-1, 1, -1)": ((-1.0, 1.0, -1.0), 4.440892098500626e-16),
1438
+ "(1, -1, -1)": ((1.0, -1.0, -1.0), 4.440892098500626e-16),
1439
+ "(-1, -1, 1)": ((-1.0, -1.0, 1.0), 4.440892098500626e-16),
1440
+ }
1441
+ for label, (scale, bound) in cases.items():
1442
+ with self.subTest(label):
1443
+ rig = flagged_rig(scale, (0.0, 30.0, 0.0))
1444
+ out = os.path.join(self.out, "joints.fbx")
1445
+ rig.save_fbx(out)
1446
+ worlds, _ = self.imported(out)
1447
+ for node in rig:
1448
+ axes, position = gaps(worlds[node.name], node.world_matrix)
1449
+ self.assertLessEqual(axes, 1e-9, node.name)
1450
+ self.assertLessEqual(position, bound, node.name)
1451
+
1452
+ def test_a_scale_under_1e_12_imports_where_the_read_puts_it(self):
1453
+ """probe_l1b.py's rig, a group scaled (1.32, 1.8, v) over a joint and
1454
+ a locator, v 0, 1e-13 and -1e-13, and -2e-12 beside them: written
1455
+ 1e-12 in place of each v, as Maya's FBXExport writes such a scale,
1456
+ Maya's FBXImport puts every node where load_fbx reads the save.
1457
+ Written as given, the read held 0 or 1e-13 where Maya holds 1e-12,
1458
+ 2.43e-13 off Maya in a locator's position and 9.71e-13 in the 3x3
1459
+ (probe_l1b.py)"""
1460
+ from cgmath._tests.test_fbx_export import gaps, tiny_rig
1461
+
1462
+ out = os.path.join(self.out, "tiny.fbx")
1463
+ tiny_rig().save_fbx(out)
1464
+ worlds, _ = self.imported(out)
1465
+ for node in HierarchyData.load_fbx(out):
1466
+ self.assertTrue(np.isfinite(worlds[node.name]).all(), node.name)
1467
+ axes, position = gaps(worlds[node.name], node.world_matrix)
1468
+ self.assertLessEqual(axes, 1e-9, node.name)
1469
+ # the largest measured on this file
1470
+ self.assertLessEqual(position, 4.440892098500626e-16, node.name)
1471
+
1282
1472
 
1283
1473
  if __name__ == "__main__":
1284
1474
  unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgmath
3
- Version: 3.0.2
3
+ Version: 3.0.3
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
@@ -915,7 +915,11 @@ class LayerData(BaseData):
915
915
 
916
916
  Supports both Maya and FBX naming conventions:
917
917
  - Maya transforms: "translateX" -> "Lcl TranslationX"
918
- - Maya blendshapes: node="face_mesh", property="smile" -> "face_mesh.smile.DeformPercent"
918
+ - Maya blend shapes: node="face_blend.smile", property="DeformPercent"
919
+ (the blend shape's name, not its mesh's) -> the DeformPercent of
920
+ blend shape face_blend's channel smile; node="face_blend",
921
+ property="smile" works only where the file names the channel
922
+ "face_blend.smile", as Maya's FBX export does
919
923
  - FBX naming: Direct usage of FBX property names
920
924
 
921
925
  Args:
@@ -2827,10 +2831,22 @@ class FbxExporter:
2827
2831
  importer brings a root skeleton node (``eRoot``) with no child in as
2828
2832
  a plain transform, without its joint orient.
2829
2833
 
2834
+ Every node takes the inherit type its ``segment_scale_compensate``
2835
+ says, ``eInheritRrs`` when on and ``eInheritRSrs`` when off, a group
2836
+ and a locator too, as Maya's own exporter writes a group or a
2837
+ locator: the sdk's default, ``eInheritRrSs``, which an earlier
2838
+ version left on them, scales a node along its own axes by its
2839
+ parent's world scale, so the sdk and Maya's importer put a group
2840
+ under an unevenly scaled parent, and the nodes under it, elsewhere
2841
+ than the component has them.
2842
+
2830
2843
  The scene is labelled with the axis system and unit the component's
2831
2844
  values are in, as it records them (``HierarchyData.axis_system`` and
2832
2845
  ``unit``; a view's are its rig's, a list's of nodes their rig's): the
2833
- values are written as they are, under that label.
2846
+ values are written as they are, under that label, but for a scale
2847
+ under 1e-12 in size, 0 and -0 among them, written as 1e-12,
2848
+ positive, as Maya's own exporter writes a static one and as Maya's
2849
+ evaluation holds it.
2834
2850
 
2835
2851
  Raises ValueError, before anything is added, when a user defined string
2836
2852
  attr is not plain ASCII, the only text the FBX SDK takes, or a node's user
@@ -2854,7 +2870,7 @@ class FbxExporter:
2854
2870
  # never writes them as scene nodes.
2855
2871
  # imported here, cgmath.hierarchy imports this module.
2856
2872
  from cgmath.hierarchy import SUPPORTED_NODE_TYPES
2857
- from cgmath.hierarchy.hierarchy import TransformList
2873
+ from cgmath.hierarchy.hierarchy import TransformList, _fbx_maya_scale
2858
2874
 
2859
2875
  skipped_nodes = {}
2860
2876
  dropped = set()
@@ -2901,7 +2917,11 @@ class FbxExporter:
2901
2917
 
2902
2918
  node.LclTranslation.Set(fbx.FbxDouble3(*item.translate))
2903
2919
  node.LclRotation.Set(fbx.FbxDouble3(*item.rotate))
2904
- node.LclScaling.Set(fbx.FbxDouble3(*item.scale))
2920
+ # a scale under 1e-12 in size as 1e-12, positive, as Maya's
2921
+ # exporter writes a static one: Maya's evaluation holds such a
2922
+ # scale as 1e-12, so load_fbx and Maya's import of the file then
2923
+ # place the nodes under it alike
2924
+ node.LclScaling.Set(fbx.FbxDouble3(*_fbx_maya_scale(item.scale)))
2905
2925
 
2906
2926
  node.SetRotationOrder(
2907
2927
  fbx.FbxNode.EPivotSet.eSourcePivot,
@@ -2912,6 +2932,17 @@ class FbxExporter:
2912
2932
  # ignores the rotate order of a node without it, turning it in xyz
2913
2933
  node.SetRotationActive(True)
2914
2934
 
2935
+ # the inherit type on every node, a group and a locator too: the
2936
+ # sdk's default, eInheritRrSs, scales a node along its own axes by
2937
+ # its parent's world scale, which no node does in cgmath or in
2938
+ # Maya, whose exporter writes eInheritRSrs on a group or locator
2939
+ inherit_type = (
2940
+ fbx.FbxTransform.EInheritType.eInheritRrs
2941
+ if item.segment_scale_compensate
2942
+ else fbx.FbxTransform.EInheritType.eInheritRSrs
2943
+ )
2944
+ node.SetTransformationInheritType(inherit_type)
2945
+
2915
2946
  # a parent the export leaves out -- outside the view exported, or
2916
2947
  # a parent_uuid no node carries -- makes the node a root, as a
2917
2948
  # view's copy() does: never a node found by its name
@@ -2929,12 +2960,6 @@ class FbxExporter:
2929
2960
  # an eRoot with no child as a transform, its orient dropped
2930
2961
  node_attr = fbx.FbxSkeleton.Create(self.manager, "")
2931
2962
  node_attr.SetSkeletonType(fbx.FbxSkeleton.EType.eLimbNode)
2932
- inherit_type = (
2933
- fbx.FbxTransform.EInheritType.eInheritRrs
2934
- if item.segment_scale_compensate
2935
- else fbx.FbxTransform.EInheritType.eInheritRSrs
2936
- )
2937
- node.SetTransformationInheritType(inherit_type)
2938
2963
  # pre and post rotation, which fbx reads with rotation active
2939
2964
  node.SetPreRotation(
2940
2965
  fbx.FbxNode.EPivotSet.eSourcePivot,