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.
- {cgmath-3.0.2/cgmath.egg-info → cgmath-3.0.3}/PKG-INFO +1 -1
- {cgmath-3.0.2 → cgmath-3.0.3}/__init__.py +1 -1
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_export.py +250 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_labels.py +192 -2
- {cgmath-3.0.2 → cgmath-3.0.3/cgmath.egg-info}/PKG-INFO +1 -1
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/fbx.py +35 -10
- {cgmath-3.0.2 → cgmath-3.0.3}/hierarchy/hierarchy.py +273 -101
- {cgmath-3.0.2 → cgmath-3.0.3}/pyproject.toml +1 -1
- {cgmath-3.0.2 → cgmath-3.0.3}/LICENSE +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/README.md +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_curves.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_deform.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_geometry.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_hierarchy.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_examples_render.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/_maya_rotate_order.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/maya_euler.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/mikktspace_deformed_data.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/mikktspace_test_data.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.curve.pkl +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.mesh.pkl +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.patch.pkl +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_data_module.saved_by_2_0_0.zip +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_geometry.bspline_best_fit_params.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_geometry.bspline_maya_curves.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node.pkl +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.legacy_parent_node_clip.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.results.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_hierarchy.skeleton.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.bitmap_buffer.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.source_mesh_data.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.source_uv_list.json +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_ngons_only.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_preserve_order.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_mesh_data_preserve_order_ngons_only.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_ngons_only.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_preserve_order.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_ngon.triangulate_uv_list_preserve_order_ngons_only.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_bs_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_mesh_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_skin_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.dst_uv_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.overlap.pkl +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_bs_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_mesh_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_skin_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_resample.src_uv_data.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_assets/test_sdf.igloo.npz +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_base.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_base_setattr.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_bspline_kernels.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_clip.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_constraints.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_curves_empty.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_curves_types.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_data_module.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_deform_files.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_deform_utils.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_delta_mush.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_euler_maya.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_import.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_fbx_lifetime.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_ffd.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_file_routes.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry_io.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_geometry_utils.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_glb.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_glb_materials.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_hierarchy.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_hierarchy_io.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_import_order.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_imports.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_list_item_class.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_map.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_mesh_usd.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_morph_target.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_namespaces.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_ngon.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_normals_quadmatch_utils.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_object_skin.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_pack.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_parent_lookups.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_patch_relax.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_procrustes_rotation.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_quadrangulate.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_raycast.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_raycast_bvh.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_rbf.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_cache.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_fit_padding.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_roundtrip_extra.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_serialization.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_render_threads.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_resample.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_resample_bezier.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_robust_skinweights_transfer_bilinear.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_roundtrip.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_saddle_surface.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_scene.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_scene_animate.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_sdf.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_short_names.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_deform.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_fbx.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_glb.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_kernels.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_skin_weights.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_symmetry.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_triangulation.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_usd_prim.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_utils.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/_tests/test_wrap.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/SOURCES.txt +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/dependency_links.txt +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/requires.txt +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/cgmath.egg-info/top_level.txt +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/constraints/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/constraints/procrustes.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/data.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/_fbx_io.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/glb.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/prim.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/formats/usd/stage.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/_cdt.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/_saddle_surface.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/bspline.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/bspline_patch.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/delta_mush.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/ffd.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/patch_relax.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/skin_deform.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/deform/wrap.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/map.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/mesh.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/morph_target.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/pack.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/resample.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/robust_skinweights_transfer_bilinear.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/sdf.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/skin_weights.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bilinear.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_blur.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bspline.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_bvh.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_cdt.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_connectivity.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_delta_mush.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_ffd.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_main.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_normals.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_pack.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_patch_relax.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_rasterize.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_sdf.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_skin_deform.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_skin_weights.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_subdivide.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_subdivision.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_tangent_space.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_topology.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/_numba/_wrap.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/geometry/utils/main.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/hierarchy/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/rbf/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_kernels.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/_kernels.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/rbf/_numba/_lu.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/_numba/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/_numba/_texture.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/_tiles.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/camera.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/frame.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/raytracer.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/scene.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/render/texture.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/setup.cfg +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_axis.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_euler.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_matrix.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_quaternion.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_numba/_vector.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/__init__.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_axis.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_broadcasting.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_euler.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_exports.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_matrix.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_quaternion.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_rotation_conversions.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_slerp.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_threads.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/_tests/test_vector.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/main.py +0 -0
- {cgmath-3.0.2 → cgmath-3.0.3}/transforms/utils.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
|
1199
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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,
|