Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/alembic/intern/abc_object.cc46
-rw-r--r--source/blender/alembic/intern/abc_transform.cc31
-rw-r--r--source/blender/alembic/intern/abc_util.cc10
-rw-r--r--source/blender/alembic/intern/abc_util.h4
-rw-r--r--source/blender/blenkernel/intern/constraint.c3
-rw-r--r--source/blender/blenloader/intern/versioning_280.c9
-rw-r--r--tests/python/bl_alembic_io_test.py104
7 files changed, 153 insertions, 54 deletions
diff --git a/source/blender/alembic/intern/abc_object.cc b/source/blender/alembic/intern/abc_object.cc
index f6f266c808e..4799ed557c8 100644
--- a/source/blender/alembic/intern/abc_object.cc
+++ b/source/blender/alembic/intern/abc_object.cc
@@ -257,11 +257,19 @@ bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/,
void AbcObjectReader::setupObjectTransform(const float time)
{
bool is_constant = false;
+ float transform_from_alembic[4][4];
- this->read_matrix(m_object->obmat, time, m_settings->scale, is_constant);
- invert_m4_m4(m_object->imat, m_object->obmat);
+ /* If the parent is a camera, apply the inverse rotation to make up for the from-Maya rotation.
+ * This assumes that the parent object also was imported from Alembic. */
+ if (m_object->parent != nullptr && m_object->parent->type == OB_CAMERA) {
+ axis_angle_to_mat4_single(m_object->parentinv, 'X', -M_PI_2);
+ }
+
+ this->read_matrix(transform_from_alembic, time, m_settings->scale, is_constant);
- BKE_object_apply_mat4(m_object, m_object->obmat, false, false);
+ /* Apply the matrix to the object. */
+ BKE_object_apply_mat4(m_object, transform_from_alembic, true, false);
+ BKE_object_to_mat4(m_object, m_object->obmat);
if (!is_constant) {
bConstraint *con = BKE_constraint_add_for_object(
@@ -311,7 +319,7 @@ Alembic::AbcGeom::IXform AbcObjectReader::xform()
return IXform();
}
-void AbcObjectReader::read_matrix(float r_mat[4][4],
+void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */,
const float time,
const float scale,
bool &is_constant)
@@ -331,25 +339,19 @@ void AbcObjectReader::read_matrix(float r_mat[4][4],
}
const Imath::M44d matrix = get_matrix(schema, time);
- convert_matrix(matrix, m_object, r_mat);
-
- if (m_inherits_xform) {
- /* In this case, the matrix in Alembic is in local coordinates, so
- * convert to world matrix. To prevent us from reading and accumulating
- * all parent matrices in the Alembic file, we assume that the Blender
- * parent object is already updated for the current timekey, and use its
- * world matrix. */
- if (m_object->parent) {
- mul_m4_m4m4(r_mat, m_object->parent->obmat, r_mat);
- }
- else {
- /* This can happen if the user deleted the parent object, but also if the Alembic parent was
- * not imported (because of unknown/unsupported schema, for example). In that case just use
- * the local matrix as if it is the world matrix. This allows us to import Alembic files from
- * MeshRoom, see T61935. */
- }
+ convert_matrix(matrix, r_mat);
+ copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP);
+
+ /* Convert from Maya to Blender camera orientation. Children of this camera
+ * will have the opposite transform as their Parent Inverse matrix.
+ * See AbcObjectReader::setupObjectTransform(). */
+ if (m_object->type == OB_CAMERA) {
+ float camera_rotation[4][4];
+ axis_angle_to_mat4_single(camera_rotation, 'X', M_PI_2);
+ mul_m4_m4m4(r_mat, r_mat, camera_rotation);
}
- else {
+
+ if (!m_inherits_xform) {
/* Only apply scaling to root objects, parenting will propagate it. */
float scale_mat[4][4];
scale_m4_fl(scale_mat, scale);
diff --git a/source/blender/alembic/intern/abc_transform.cc b/source/blender/alembic/intern/abc_transform.cc
index 585d4178e41..838e657fee9 100644
--- a/source/blender/alembic/intern/abc_transform.cc
+++ b/source/blender/alembic/intern/abc_transform.cc
@@ -42,23 +42,6 @@ using Alembic::AbcGeom::OXform;
/* ************************************************************************** */
-static bool has_parent_camera(Object *ob)
-{
- if (!ob->parent) {
- return false;
- }
-
- Object *parent = ob->parent;
-
- if (parent->type == OB_CAMERA) {
- return true;
- }
-
- return has_parent_camera(parent);
-}
-
-/* ************************************************************************** */
-
AbcTransformWriter::AbcTransformWriter(Object *ob,
const OObject &abc_parent,
AbcTransformWriter *parent,
@@ -98,14 +81,22 @@ void AbcTransformWriter::do_write()
create_transform_matrix(
ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from);
- /* Only apply rotation to root camera, parenting will propagate it. */
- if (ob_eval->type == OB_CAMERA && (!m_inherits_xform || !has_parent_camera(ob_eval))) {
+ /* If the parent is a camera, undo its to-Maya rotation (see below). */
+ bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr;
+ if (!is_root_object && ob_eval->parent->type == OB_CAMERA) {
+ float rot_mat[4][4];
+ axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2);
+ mul_m4_m4m4(yup_mat, rot_mat, yup_mat);
+ }
+
+ /* If the object is a camera, apply an extra rotation to Maya camera orientation. */
+ if (ob_eval->type == OB_CAMERA) {
float rot_mat[4][4];
axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2);
mul_m4_m4m4(yup_mat, yup_mat, rot_mat);
}
- if (!ob_eval->parent || !m_inherits_xform) {
+ if (is_root_object) {
/* Only apply scaling to root objects, parenting will propagate it. */
float scale_mat[4][4];
scale_m4_fl(scale_mat, m_settings.global_scale);
diff --git a/source/blender/alembic/intern/abc_util.cc b/source/blender/alembic/intern/abc_util.cc
index b6743c8b363..56978374512 100644
--- a/source/blender/alembic/intern/abc_util.cc
+++ b/source/blender/alembic/intern/abc_util.cc
@@ -224,21 +224,13 @@ void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMod
mul_m4_m4m4(dst_mat, dst_mat, dst_scale_mat);
}
-void convert_matrix(const Imath::M44d &xform, Object *ob, float r_mat[4][4])
+void convert_matrix(const Imath::M44d &xform, float r_mat[4][4])
{
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
r_mat[i][j] = static_cast<float>(xform[i][j]);
}
}
-
- if (ob->type == OB_CAMERA) {
- float cam_to_yup[4][4];
- axis_angle_to_mat4_single(cam_to_yup, 'X', M_PI_2);
- mul_m4_m4m4(r_mat, r_mat, cam_to_yup);
- }
-
- copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP);
}
/* Recompute transform matrix of object in new coordinate system
diff --git a/source/blender/alembic/intern/abc_util.h b/source/blender/alembic/intern/abc_util.h
index 5eb0ed70599..bfeded37c12 100644
--- a/source/blender/alembic/intern/abc_util.h
+++ b/source/blender/alembic/intern/abc_util.h
@@ -51,6 +51,7 @@ std::string get_id_name(const ID *const id);
std::string get_id_name(const Object *const ob);
std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent);
+/* Convert from float to Alembic matrix representations. Does NOT convert from Z-up to Y-up. */
Imath::M44d convert_matrix(float mat[4][4]);
typedef enum {
@@ -69,7 +70,8 @@ template<class TContainer> bool begins_with(const TContainer &input, const TCont
return input.size() >= match.size() && std::equal(match.begin(), match.end(), input.begin());
}
-void convert_matrix(const Imath::M44d &xform, Object *ob, float r_mat[4][4]);
+/* Convert from Alembic to float matrix representations. Does NOT convert from Y-up to Z-up. */
+void convert_matrix(const Imath::M44d &xform, float r_mat[4][4]);
template<typename Schema>
void get_min_max_time_ex(const Schema &schema, chrono_t &min, chrono_t &max)
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index 76acaf5c91c..42efd9a7057 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -5271,6 +5271,9 @@ static bConstraint *add_new_constraint(Object *ob,
}
break;
}
+ case CONSTRAINT_TYPE_TRANSFORM_CACHE:
+ con->ownspace = CONSTRAINT_SPACE_LOCAL;
+ break;
}
return con;
diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c
index f9c89747120..d306049a7e0 100644
--- a/source/blender/blenloader/intern/versioning_280.c
+++ b/source/blender/blenloader/intern/versioning_280.c
@@ -4473,5 +4473,14 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
+
+ /* Alembic Transform Cache changed from world to local space. */
+ LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
+ LISTBASE_FOREACH (bConstraint *, con, &ob->constraints) {
+ if (con->type == CONSTRAINT_TYPE_TRANSFORM_CACHE) {
+ con->ownspace = CONSTRAINT_SPACE_LOCAL;
+ }
+ }
+ }
}
}
diff --git a/tests/python/bl_alembic_io_test.py b/tests/python/bl_alembic_io_test.py
index 41b28cb7c33..0f74b773c32 100644
--- a/tests/python/bl_alembic_io_test.py
+++ b/tests/python/bl_alembic_io_test.py
@@ -22,11 +22,14 @@
./blender.bin --background -noaudio --factory-startup --python tests/python/bl_alembic_io_test.py -- --testdir /path/to/lib/tests/alembic
"""
+import math
import pathlib
import sys
+import tempfile
import unittest
import bpy
+from mathutils import Euler, Matrix, Vector
args = None
@@ -134,8 +137,6 @@ class SimpleImportTest(AbstractAlembicTest):
self.assertEqual('Cube' in ob.name, ob.select_get())
def test_change_path_constraint(self):
- import math
-
fname = 'cube-rotating1.abc'
abc = self.testdir / fname
relpath = bpy.path.relpath(str(abc))
@@ -250,6 +251,105 @@ class VertexColourImportTest(AbstractAlembicTest):
self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411, 1.0))
+class CameraExportImportTest(unittest.TestCase):
+ names = [
+ 'CAM_Unit_Transform',
+ 'CAM_Look_+Y',
+ 'CAM_Static_Child_Left',
+ 'CAM_Static_Child_Right',
+ 'Static_Child',
+ 'CAM_Animated',
+ 'CAM_Animated_Child_Left',
+ 'CAM_Animated_Child_Right',
+ 'Animated_Child',
+ ]
+
+ def setUp(self):
+ self._tempdir = tempfile.TemporaryDirectory()
+ self.tempdir = pathlib.Path(self._tempdir.name)
+
+ def tearDown(self):
+ self._tempdir.cleanup()
+
+ def test_export_hierarchy(self):
+ self.do_export_import_test(flatten=False)
+
+ # Double-check that the export was hierarchical.
+ objects = bpy.context.scene.collection.objects
+ for name in self.names:
+ if 'Child' in name:
+ self.assertIsNotNone(objects[name].parent)
+ else:
+ self.assertIsNone(objects[name].parent)
+
+ def test_export_flattened(self):
+ self.do_export_import_test(flatten=True)
+
+ # Double-check that the export was flat.
+ objects = bpy.context.scene.collection.objects
+ for name in self.names:
+ self.assertIsNone(objects[name].parent)
+
+ def do_export_import_test(self, *, flatten: bool):
+ bpy.ops.wm.open_mainfile(filepath=str(args.testdir / "camera_transforms.blend"))
+
+ abc_path = self.tempdir / "camera_transforms.abc"
+ self.assertIn('FINISHED', bpy.ops.wm.alembic_export(
+ filepath=str(abc_path),
+ renderable_only=False,
+ flatten=flatten,
+ ))
+
+ # Re-import what we just exported into an empty file.
+ bpy.ops.wm.open_mainfile(filepath=str(args.testdir / "empty.blend"))
+ self.assertIn('FINISHED', bpy.ops.wm.alembic_import(filepath=str(abc_path)))
+
+ # Test that the import was ok.
+ bpy.context.scene.frame_set(1)
+ self.loc_rot_scale('CAM_Unit_Transform', (0, 0, 0), (0, 0, 0))
+
+ self.loc_rot_scale('CAM_Look_+Y', (2, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('CAM_Static_Child_Left', (2-0.15, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('CAM_Static_Child_Right', (2+0.15, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('Static_Child', (2, 0, 1), (90, 0, 0))
+
+ self.loc_rot_scale('CAM_Animated', (4, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('CAM_Animated_Child_Left', (4-0.15, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('CAM_Animated_Child_Right', (4+0.15, 0, 0), (90, 0, 0))
+ self.loc_rot_scale('Animated_Child', (4, 0, 1), (90, 0, 0))
+
+ bpy.context.scene.frame_set(10)
+
+ self.loc_rot_scale('CAM_Animated', (4, 1, 2), (90, 0, 25))
+ self.loc_rot_scale('CAM_Animated_Child_Left', (3.864053, 0.936607, 2), (90, 0, 25))
+ self.loc_rot_scale('CAM_Animated_Child_Right', (4.135946, 1.063392, 2), (90, 0, 25))
+ self.loc_rot_scale('Animated_Child', (4, 1, 3), (90, -45, 25))
+
+ def loc_rot_scale(self, name: str, expect_loc, expect_rot_deg):
+ """Assert world loc/rot/scale is OK."""
+
+ objects = bpy.context.scene.collection.objects
+ depsgraph = bpy.context.evaluated_depsgraph_get()
+ ob_eval = objects[name].evaluated_get(depsgraph)
+
+ actual_loc = ob_eval.matrix_world.to_translation()
+ actual_rot = ob_eval.matrix_world.to_euler('XYZ')
+ actual_scale = ob_eval.matrix_world.to_scale()
+
+ self.assertAlmostEqual(expect_loc[0], actual_loc.x, delta=1e-5)
+ self.assertAlmostEqual(expect_loc[1], actual_loc.y, delta=1e-5)
+ self.assertAlmostEqual(expect_loc[2], actual_loc.z, delta=1e-5)
+
+ self.assertAlmostEqual(expect_rot_deg[0], math.degrees(actual_rot.x), delta=1e-5)
+ self.assertAlmostEqual(expect_rot_deg[1], math.degrees(actual_rot.y), delta=1e-5)
+ self.assertAlmostEqual(expect_rot_deg[2], math.degrees(actual_rot.z), delta=1e-5)
+
+ # This test doesn't use scale.
+ self.assertAlmostEqual(1, actual_scale.x, delta=1e-5)
+ self.assertAlmostEqual(1, actual_scale.y, delta=1e-5)
+ self.assertAlmostEqual(1, actual_scale.z, delta=1e-5)
+
+
def main():
global args
import argparse