From 3e158fa560923bf2cfd0b9ec74691fa8c9bb4e86 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Thu, 3 Aug 2023 17:37:02 -0400 Subject: [PATCH 01/30] USD: port of UsdSkel import from the USD branch. --- source/blender/editors/io/io_usd.cc | 8 + source/blender/io/usd/CMakeLists.txt | 4 + .../blender/io/usd/intern/usd_capi_import.cc | 4 + .../blender/io/usd/intern/usd_reader_mesh.cc | 32 + .../blender/io/usd/intern/usd_reader_mesh.h | 2 + .../io/usd/intern/usd_reader_skeleton.cc | 256 +++++++ .../io/usd/intern/usd_reader_skeleton.h | 30 + .../blender/io/usd/intern/usd_reader_stage.cc | 48 ++ .../blender/io/usd/intern/usd_reader_stage.h | 2 + .../blender/io/usd/intern/usd_skel_convert.cc | 690 ++++++++++++++++++ .../blender/io/usd/intern/usd_skel_convert.h | 29 + source/blender/io/usd/usd.h | 2 + 12 files changed, 1107 insertions(+) create mode 100644 source/blender/io/usd/intern/usd_reader_skeleton.cc create mode 100644 source/blender/io/usd/intern/usd_reader_skeleton.h create mode 100644 source/blender/io/usd/intern/usd_skel_convert.cc create mode 100644 source/blender/io/usd/intern/usd_skel_convert.h diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index ff22c43fa86..e0ff08e17cc 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -418,6 +418,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) const bool import_meshes = RNA_boolean_get(op->ptr, "import_meshes"); const bool import_volumes = RNA_boolean_get(op->ptr, "import_volumes"); const bool import_shapes = RNA_boolean_get(op->ptr, "import_shapes"); + const bool import_skeletons = RNA_boolean_get(op->ptr, "import_skeletons"); + const bool import_blendshapes = RNA_boolean_get(op->ptr, "import_blendshapes"); const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv"); @@ -481,6 +483,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) params.import_meshes = import_meshes; params.import_volumes = import_volumes; params.import_shapes = import_shapes; + params.import_skeletons = import_skeletons; + params.import_blendshapes = import_blendshapes; params.prim_path_mask = prim_path_mask; params.import_subdiv = import_subdiv; params.import_instance_proxies = import_instance_proxies; @@ -527,6 +531,8 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op) uiItemR(col, ptr, "import_meshes", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, ptr, "import_volumes", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, ptr, "import_shapes", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "import_skeletons", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "import_blendshapes", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(box, ptr, "prim_path_mask", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(box, ptr, "scale", UI_ITEM_NONE, nullptr, ICON_NONE); @@ -620,6 +626,8 @@ void WM_OT_usd_import(wmOperatorType *ot) RNA_def_boolean(ot->srna, "import_meshes", true, "Meshes", ""); RNA_def_boolean(ot->srna, "import_volumes", true, "Volumes", ""); RNA_def_boolean(ot->srna, "import_shapes", true, "Shapes", ""); + RNA_def_boolean(ot->srna, "import_skeletons", true, "Skeletons", ""); + RNA_def_boolean(ot->srna, "import_blendshapes", true, "Blend Shapes", ""); RNA_def_boolean(ot->srna, "import_subdiv", diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index d028379cff5..b2831eda9e2 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -93,6 +93,8 @@ set(SRC intern/usd_reader_stage.cc intern/usd_reader_volume.cc intern/usd_reader_xform.cc + intern/usd_reader_skeleton.cc + intern/usd_skel_convert.cc usd.h @@ -122,6 +124,8 @@ set(SRC intern/usd_reader_stage.h intern/usd_reader_volume.h intern/usd_reader_xform.h + intern/usd_reader_skeleton.h + intern/usd_skel_convert.h ) set(LIB diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 82a2e3dcd48..153c51393e0 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -303,6 +303,10 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float } } + if (data->params.import_skeletons) { + archive->process_armature_modifiers(); + } + data->import_ok = !data->was_canceled; *progress = 1.0f; diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 71343eab24a..3a02252d039 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -7,6 +7,7 @@ #include "usd_reader_mesh.h" #include "usd_reader_material.h" +#include "usd_skel_convert.h" #include "BKE_attribute.hh" #include "BKE_customdata.h" @@ -41,6 +42,7 @@ #include #include #include +#include #include @@ -228,6 +230,14 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) } } + if (import_params_.import_blendshapes) { + import_blendshapes(bmain, object_, prim_); + } + + if (import_params_.import_skeletons) { + import_skel_bindings(bmain, object_, prim_); + } + USDXformReader::read_object_data(bmain, motionSampleTime); } // namespace blender::io::usd @@ -982,4 +992,26 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, return active_mesh; } +std::string USDMeshReader::get_skeleton_path() const +{ + /* Make sure we can apply UsdSkelBindingAPI to the prim. + * Attempting to apply the API to instance proxies or + * prototypes generates an error. */ + if (!prim_ || prim_.IsInstanceProxy() || prim_.IsInPrototype()) { + return ""; + } + + pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_); + + if (!skel_api) { + return ""; + } + + if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) { + return skel.GetPath().GetAsString(); + } + + return ""; +} + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index f0407ca685c..1ed696e047f 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -56,6 +56,8 @@ class USDMeshReader : public USDGeomReader { bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override; + std::string get_skeleton_path() const; + private: void process_normals_vertex_varying(Mesh *mesh); void process_normals_face_varying(Mesh *mesh); diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc new file mode 100644 index 00000000000..0ade780ea4c --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -0,0 +1,256 @@ +/* SPDX-FileCopyrightText: 2021 NVIDIA Corporation. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "usd_reader_skeleton.h" +#include "usd_skel_convert.h" + +#include "BKE_idprop.h" +#include "BKE_armature.h" +#include "BKE_object.h" + +#include "BLI_math.h" + +#include "DNA_armature_types.h" +#include "DNA_object_types.h" + +#include "ED_armature.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" + +#include +#include +#include + +#include + +namespace blender::io::usd { + +bool USDSkeletonReader::valid() const +{ + return skel_ && USDXformReader::valid(); +} + +void USDSkeletonReader::create_object(Main *bmain, const double /* motionSampleTime */) +{ + object_ = BKE_object_add_only_object(bmain, OB_ARMATURE, name_.c_str()); + + bArmature *arm = BKE_armature_add(bmain, name_.c_str()); + object_->data = arm; +} + +void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleTime) +{ + if (!object_ || !object_->data || !skel_) { + return; + } + + pxr::UsdSkelCache skel_cache; + pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel_); + + if (!skel_query.IsValid()) { + std::cout << "WARNING: couldn't query skeleton " << skel_.GetPath() << std::endl; + return; + } + + const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); + + pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); + + if (joint_order.size() != skel_topology.size()) { + std::cout << "WARNING: skel topology and joint order size mismatch\n"; + return; + } + + bArmature *arm = static_cast(object_->data); + + ED_armature_to_edit(arm); + + /* The bones we create, stored in the skeleton's joint order. */ + std::vector edit_bones; + + size_t num_joints = skel_topology.GetNumJoints(); + + /* Keep track of the bones we create for each joint. */ + std::map joint_to_bone_map; + + /* Create the bones. */ + for (const pxr::TfToken &joint : joint_order) { + std::string name = pxr::SdfPath(joint).GetName(); + EditBone * bone = ED_armature_ebone_add(arm, name.c_str()); + if (!bone) { + std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl; + edit_bones.push_back(nullptr); + continue; + } + joint_to_bone_map.insert(std::make_pair(joint, bone->name)); + edit_bones.push_back(bone); + } + + /* Sanity check: we should have created a bone for each joint. */ + + if (edit_bones.size() != num_joints) { + std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel_.GetPath() << std::endl; + return; + } + + /* Record the child bone indices per parent bone. */ + std::vector> child_bones(num_joints); + + /* Set bone parenting. */ + for (size_t i = 0; i < num_joints; ++i) { + int parent_idx = skel_topology.GetParent(i); + if (parent_idx < 0) { + continue; + } + if (parent_idx >= edit_bones.size()) { + std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i]) + << " for skeleton " << skel_.GetPath() << std::endl; + continue; + } + + child_bones[parent_idx].push_back(i); + if (edit_bones[i] && edit_bones[parent_idx]) { + edit_bones[i]->parent = edit_bones[parent_idx]; + } + } + + /* Joint bind transforms. */ + pxr::VtMatrix4dArray bind_xforms; + if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { + std::cout << "WARNING: couldn't get world bind transforms for skeleton " + << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; + return; + } + + if (bind_xforms.size() != num_joints) { + std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " << skel_.GetPath() << std::endl; + return; + } + + /* Check if any bone natrices have negative determinants, + * indicating negative scales, possibly due to mirroring + * operations. Such matrices can't be propery converted + * to Blender's axis/roll bone representation (see + * https://developer.blender.org/T82930). If we detect + * such matrices, we will flag an error and won't try + * to import the animation, since the rotations would + * be incorrect in such cases. Unfortunately, the Pixar + * UsdSkel examples of the "HumanFemale" suffer from + * this issue. */ + bool negative_determinant = false; + + /* Set bone rest transforms. */ + for (size_t i = 0; i < num_joints; ++i) { + EditBone *ebone = edit_bones[i]; + + if (!ebone) { + continue; + } + + pxr::GfMatrix4f mat(bind_xforms[i]); + + float mat4[4][4]; + mat.Get(mat4); + + pxr::GfVec3f head(0.0f, 0.0f, 0.0f); + pxr::GfVec3f tail(0.0f, 1.0f, 0.0f); + + copy_v3_v3(ebone->head, head.data()); + copy_v3_v3(ebone->tail, tail.data()); + + ED_armature_ebone_from_mat4(ebone, mat4); + + if (mat.GetDeterminant() < 0.0) { + negative_determinant = true; + } + } + + bool valid_skeleton = true; + if (negative_determinant) { + valid_skeleton = false; + WM_reportf(RPT_WARNING, + "USD Skeleton Import: bone matrices with negative determinants detected in prim %s." + "Such matrices may indicate negative scales, possibly due to mirroring operations, " + "and can't currently be converted to Blender's bone representation. " + "The skeletal animation won't be imported", prim_.GetPath().GetAsString().c_str()); + } + + /* Scale bones to account for separation between parents and + * children, so that the bone size is in proportion with the + * overall skeleton hierarchy. USD skeletons are composed of + * joints which we imperfectly represent as bones. */ + + float avg_len_scale = 0; + for (size_t i = 0; i < num_joints; ++i) { + + /* If the bone has any children, scale its length + * by the distance between this bone's head + * and the average head location of its children. */ + + if (child_bones[i].empty()) { + continue; + } + + EditBone *parent = edit_bones[i]; + if (!parent) { + continue; + } + + pxr::GfVec3f avg_child_head(0); + for (int j : child_bones[i]) { + EditBone *child = edit_bones[j]; + if (!child) { + continue; + } + pxr::GfVec3f child_head(child->head); + avg_child_head += child_head; + } + + avg_child_head /= child_bones[i].size(); + + pxr::GfVec3f parent_head(parent->head); + pxr::GfVec3f parent_tail(parent->tail); + + float new_len = (avg_child_head - parent_head).GetLength(); + + /* Be sure not to scale by zero. */ + if (new_len > .00001) { + parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len; + copy_v3_v3(parent->tail, parent_tail.data()); + avg_len_scale += new_len; + } + } + + /* Scale terminal bones by the average length scale. */ + avg_len_scale /= num_joints; + + if (avg_len_scale > .00001) { + for (size_t i = 0; i < num_joints; ++i) { + if (!child_bones[i].empty()) { + continue; + } + EditBone *bone = edit_bones[i]; + if (!bone) { + continue; + } + pxr::GfVec3f head(bone->head); + pxr::GfVec3f tail(bone->tail); + tail = head + (tail - head).GetNormalized() * avg_len_scale; + copy_v3_v3(bone->tail, tail.data()); + } + } + + ED_armature_from_edit(bmain, arm); + ED_armature_edit_free(arm); + + if (valid_skeleton) { + create_skeleton_curves(bmain, object_, skel_query, joint_to_bone_map); + } + + USDXformReader::read_object_data(bmain, motionSampleTime); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.h b/source/blender/io/usd/intern/usd_reader_skeleton.h new file mode 100644 index 00000000000..abc6b08e306 --- /dev/null +++ b/source/blender/io/usd/intern/usd_reader_skeleton.h @@ -0,0 +1,30 @@ +/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include "usd.h" +#include "usd_reader_xform.h" + +#include + +namespace blender::io::usd { + +class USDSkeletonReader : public USDXformReader { + private: + pxr::UsdSkelSkeleton skel_; + + public: + USDSkeletonReader(const pxr::UsdPrim &prim, + const USDImportParams &import_params, + const ImportSettings &settings) + : USDXformReader(prim, import_params, settings), skel_(prim) + { + } + + bool valid() const override; + void create_object(Main *bmain, double motionSampleTime) override; + void read_object_data(Main *bmain, double motionSampleTime) override; +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 317924005d3..b876ee11847 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -11,6 +11,7 @@ #include "usd_reader_nurbs.h" #include "usd_reader_prim.h" #include "usd_reader_shape.h" +#include "usd_reader_skeleton.h" #include "usd_reader_volume.h" #include "usd_reader_xform.h" @@ -42,6 +43,7 @@ #include "BLI_string.h" #include "BKE_lib_id.h" +#include "BKE_modifier.h" #include "DNA_material_types.h" @@ -100,6 +102,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim if (params_.import_volumes && prim.IsA()) { return new USDVolumeReader(prim, params_, settings_); } + if (params_.import_skeletons && prim.IsA()) { + return new USDSkeletonReader(prim, params_, settings_); + } if (prim.IsA()) { return new USDXformReader(prim, params_, settings_); } @@ -134,6 +139,9 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim) if (prim.IsA()) { return new USDVolumeReader(prim, params_, settings_); } + if (prim.IsA()) { + return new USDSkeletonReader(prim, params_, settings_); + } if (prim.IsA()) { return new USDXformReader(prim, params_, settings_); } @@ -167,6 +175,11 @@ bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageabl bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const { + if (params_.import_skeletons && imageable.GetPrim().IsA()) { + /* Always include skeletons, if requested by the user, regardless of purpose. */ + return true; + } + if (params_.import_guide && params_.import_proxy && params_.import_render) { /* The options allow any purpose, so we trivially include the prim. */ return true; @@ -318,6 +331,41 @@ void USDStageReader::collect_readers(Main *bmain) collect_readers(bmain, root); } +void USDStageReader::process_armature_modifiers() const +{ + /* Create armature object map. */ + std::map usd_path_to_armature; + for (const USDPrimReader *reader : readers_) { + if (dynamic_cast(reader) && reader->object()) { + usd_path_to_armature.insert(std::make_pair(reader->prim_path(), reader->object())); + } + } + + /* Set armature objects on armature modifiers. */ + for (const USDPrimReader *reader : readers_) { + if (!reader->object()) { + /* This should never happen. */ + continue; + } + if (const USDMeshReader *mesh_reader = dynamic_cast(reader)) { + ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature); + if (!md) { + continue; + } + ArmatureModifierData *amd = reinterpret_cast(md); + std::string skel_path = mesh_reader->get_skeleton_path(); + std::map::const_iterator it = usd_path_to_armature.find(skel_path); + if (it != usd_path_to_armature.end()) { + amd->object = it->second; + } + else { + std::cout << "WARNING: couldn't find armature object for armature modifier for USD prim " + << reader->prim_path() << " bound to skeleton " << skel_path << std::endl; + } + } + } +} + void USDStageReader::import_all_materials(Main *bmain) { BLI_assert(valid()); diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 6104ddb9ba1..06150072ff3 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -45,6 +45,8 @@ class USDStageReader { void collect_readers(struct Main *bmain); + void process_armature_modifiers() const; + /* Convert every material prim on the stage to a Blender * material, including materials not used by any geometry. * Note that collect_readers() must be called before calling diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc new file mode 100644 index 00000000000..2d9fdae4e94 --- /dev/null +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -0,0 +1,690 @@ +/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "usd_skel_convert.h" + +#include "usd.h" + +#include +#include +#include +#include + +#include "DNA_anim_types.h" +#include "DNA_armature_types.h" +#include "DNA_key_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_meta_types.h" +#include "DNA_scene_types.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_deform.h" +#include "BKE_fcurve.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_object_deform.h" + +#include "BLI_math_vector.h" + +#include "ED_keyframing.h" +#include "ED_mesh.h" + +#include +#include + +namespace { + +FCurve *create_fcurve(int array_index, const char *rna_path) +{ + FCurve *fcu = BKE_fcurve_create(); + fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED); + fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path)); + fcu->array_index = array_index; + return fcu; +} + +FCurve *create_chan_fcurve(bAction *act, + bActionGroup *grp, + int array_index, + const char *rna_path, + int totvert) +{ + FCurve *fcu = create_fcurve(array_index, rna_path); + fcu->totvert = totvert; + action_groups_add_channel(act, grp, fcu); + return fcu; +} + +void add_bezt(FCurve *fcu, + float frame, + float value, + eBezTriple_Interpolation ipo = BEZT_IPO_LIN) +{ + BezTriple bez; + memset(&bez, 0, sizeof(BezTriple)); + bez.vec[1][0] = frame; + bez.vec[1][1] = value; + bez.ipo = ipo; /* use default interpolation mode here... */ + bez.f1 = bez.f2 = bez.f3 = SELECT; + bez.h1 = bez.h2 = HD_AUTO; + insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); + BKE_fcurve_handles_recalc(fcu); +} + +} // End anonymous namespace. + +namespace blender::io::usd { + +//pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time) +//{ +// pxr::GfMatrix4d local_xf(1.0f); +// +// if (!prim) { +// return local_xf; +// } +// +// pxr::UsdGeomXformable xformable(prim); +// +// if (xformable) { +// bool reset_xform_stack = false; +// if (!xformable.GetLocalTransformation(&local_xf, &reset_xform_stack, time)) { +// std::cout << "WARNING: couldn't get local xform for prim " << prim.GetPath() << std::endl; +// return local_xf; +// } +// } +// +// return local_xf * get_world_matrix(prim.GetParent(), time); +//} + +void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) +{ + if (!(obj && obj->data && obj->type == OB_MESH && prim)) { + return; + } + + if (prim.IsInstanceProxy() || prim.IsInPrototype()) { + /* Attempting to create a UsdSkelBindingAPI for + * instance proxies and prototypes generates USD errors. */ + return; + } + + pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim); + + if (!skel_api) { + return; + } + + if (!skel_api.GetBlendShapeTargetsRel().HasAuthoredTargets()) { + return; + } + + pxr::SdfPathVector targets; + if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { + std::cout << "Couldn't get blendshape targets for prim " << prim.GetPath() << std::endl; + return; + } + + if (targets.empty()) { + return; + } + + if (!skel_api.GetBlendShapesAttr().HasAuthoredValue()) { + return; + } + + pxr::VtTokenArray blendshapes; + if (!skel_api.GetBlendShapesAttr().Get(&blendshapes)) { + return; + } + + if (blendshapes.empty()) { + return; + } + + if (targets.size() != blendshapes.size()) { + std::cout << "Number of blendshapes doesn't match number of blendshape targets for prim " << prim.GetPath() << std::endl; + return; + } + + Mesh *mesh = static_cast(obj->data); + + /* insert key to source mesh */ + Key *key = BKE_key_add(bmain, (ID *)mesh); + key->type = KEY_RELATIVE; + + mesh->key = key; + + /* insert basis key */ + KeyBlock *kb = BKE_keyblock_add(key, "Basis"); + BKE_keyblock_convert_from_mesh(mesh, key, kb); + + pxr::UsdStageRefPtr stage = prim.GetStage(); + + if (!stage) { + return; + } + + /* Keep track of the shapkeys we're adding, for + * validation when creating curves later. */ + std::set shapekey_names; + + for (int i = 0; i < targets.size(); ++i) { + + const pxr::SdfPath &path = targets[i]; + + pxr::UsdSkelBlendShape blendshape(stage->GetPrimAtPath(path)); + + if (!blendshape) { + continue; + } + + if (!blendshape.GetOffsetsAttr().HasAuthoredValue()) { + continue; + } + + pxr::VtVec3fArray offsets; + if (!blendshape.GetOffsetsAttr().Get(&offsets)) { + std::cout << "Couldn't get offsets for blendshape " << path << std::endl; + continue; + } + + if (offsets.empty()) { + std::cout << "No offsets for blendshape " << path << std::endl; + continue; + } + + shapekey_names.insert(blendshapes[i]); + + kb = BKE_keyblock_add(key, blendshapes[i].GetString().c_str()); + BKE_keyblock_convert_from_mesh(mesh, key, kb); + + pxr::VtArray point_indices; + if (blendshape.GetPointIndicesAttr().HasAuthoredValue()) { + blendshape.GetPointIndicesAttr().Get(&point_indices); + } + + float *fp = static_cast(kb->data); + + if (point_indices.empty()) { + for (int a = 0; a < kb->totelem; ++a, fp += 3) { + if (a >= offsets.size()) { + std::cout << "Number of offsets greater than number of mesh vertices for blendshape " + << path << std::endl; + break; + } + add_v3_v3(fp, offsets[a].data()); + } + } + else { + int a = 0; + for (int i : point_indices) { + if (i < 0 || i > kb->totelem) { + std::cout << "Out of bounds point index " << i << " for blendshape " << path << std::endl; + ++a; + continue; + } + if (a >= offsets.size()) { + std::cout << "Number of offsets greater than number of mesh vertices for blendshape " << path << std::endl; + break; + } + add_v3_v3(&fp[3 * i], offsets[a].data()); + ++a; + } + } + } + + pxr::UsdSkelSkeleton skel_prim = skel_api.GetInheritedSkeleton(); + + if (!skel_prim) { + return; + } + + skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim.GetPrim()); + + if (!skel_api) { + return; + } + + pxr::UsdPrim anim_prim = skel_api.GetInheritedAnimationSource(); + + if (!anim_prim) { + return; + } + + pxr::UsdSkelAnimation skel_anim(anim_prim); + + if (!skel_anim) { + return; + } + + if (!skel_anim.GetBlendShapesAttr().HasAuthoredValue()) { + return; + } + + pxr::UsdAttribute weights_attr = skel_anim.GetBlendShapeWeightsAttr(); + + if (!(weights_attr && weights_attr.HasAuthoredValue())) { + return; + } + + std::vector times; + if (!weights_attr.GetTimeSamples(×)) { + return; + } + + if (times.empty()) { + return; + } + + if (!skel_anim.GetBlendShapesAttr().Get(&blendshapes)) { + return; + } + + if (blendshapes.empty()) { + return; + } + + size_t num_samples = times.size(); + + /* Create the animation and curves. */ + bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id); + std::vector curves; + + for (auto blendshape_name : blendshapes) { + + if (shapekey_names.find(blendshape_name) == shapekey_names.end()) { + /* We didn't create a shapekey fo this blendshape, so we don't + * create a curve and insert a null placeholder in the curve array. */ + curves.push_back(nullptr); + continue; + } + + std::string rna_path = "key_blocks[\"" + blendshape_name.GetString() + "\"].value"; + FCurve *fcu = create_fcurve(0, rna_path.c_str()); + fcu->totvert = num_samples; + curves.push_back(fcu); + BLI_addtail(&act->curves, fcu); + } + + for (double frame : times) { + pxr::VtFloatArray weights; + if (!weights_attr.Get(&weights, frame)) { + std::cout << "Couldn't get blendshape weights for time " << frame << std::endl; + continue; + } + + if (weights.size() != curves.size()) { + std::cout << "Programmer error: number of weight samples doesn't match number of shapekey curve entries for frame " << frame << std::endl; + continue; + } + + for (int wi = 0; wi < weights.size(); ++wi) { + if (curves[wi] != nullptr) { + add_bezt(curves[wi], frame, weights[wi]); + } + } + } + +} + +void create_skeleton_curves(Main *bmain, + Object *obj, + const pxr::UsdSkelSkeletonQuery &skel_query, + const std::map &joint_to_bone_map) + +{ + if (!(bmain && obj && skel_query)) { + return; + } + + if (joint_to_bone_map.empty()) { + return; + } + + const pxr::UsdSkelAnimQuery &anim_query = skel_query.GetAnimQuery(); + + if (!anim_query) { + return; + } + + std::vector samples; + anim_query.GetJointTransformTimeSamples(&samples); + + if (samples.empty()) { + return; + } + + size_t num_samples = samples.size(); + + bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id); + + pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); + + std::vector loc_curves; + std::vector rot_curves; + std::vector scale_curves; + + for (const pxr::TfToken &joint : joint_order) { + std::map::const_iterator it = joint_to_bone_map.find(joint); + + if (it == joint_to_bone_map.end()) { + /* This joint doesn't correspond to any bone we created. + * Add null placeholders for the channel curves. */ + loc_curves.push_back(nullptr); + loc_curves.push_back(nullptr); + loc_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + continue; + } + + bActionGroup *grp = action_groups_add_new(act, it->second.c_str()); + + /* Add translation curves. */ + std::string rna_path = "pose.bones[\"" + it->second + "\"].location"; + loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + + /* Add rotation curves. */ + rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion"; + rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples)); + + /* Add scale curves. */ + rna_path = "pose.bones[\"" + it->second + "\"].scale"; + scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + } + + if (loc_curves.size() != joint_order.size() * 3) { + std::cout << "PROGRAMMER ERROR: location curve count mismatch\n"; + return; + } + + if (rot_curves.size() != joint_order.size() * 4) { + std::cout << "PROGRAMMER ERROR: rotation curve count mismatch\n"; + return; + } + + if (scale_curves.size() != joint_order.size() * 3) { + std::cout << "PROGRAMMER ERROR: scale curve count mismatch\n"; + return; + } + + /* Joint bind transforms. */ + pxr::VtMatrix4dArray bind_xforms; + if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { + std::cout << "WARNING: couldn't get world bind transforms for skeleton " + << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; + return; + } + + if (bind_xforms.size() != joint_order.size()) { + std::cout << "WARNING: number of bind transforms doesn't match the number of joints\n"; + return; + } + + const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); + + /* This will store the inverse of the parent-relative bind xforms. */ + pxr::VtMatrix4dArray inv_bind_xforms(bind_xforms.size()); + + for (int i = 0; i < bind_xforms.size(); ++i) { + int parent_id = skel_topology.GetParent(i); + + if (parent_id >= 0) { + /* This is a non-root bone. Compute the transform of the joint + * relative to its parent. */ + pxr::GfMatrix4d parent_relative_xf = bind_xforms[i] * bind_xforms[parent_id].GetInverse(); + inv_bind_xforms[i] = parent_relative_xf.GetInverse(); + } else { + inv_bind_xforms[i] = bind_xforms[i].GetInverse(); + } + } + + for (double frame : samples) { + pxr::VtMatrix4dArray joint_local_xforms; + + if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) { + std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame << std::endl; + continue; + } + + if (joint_local_xforms.size() != joint_order.size()) { + std::cout << "WARNING: number of joint local transform entries " << joint_local_xforms.size() + << " doesn't match the number of joints " << joint_order.size() << std::endl; + continue; + } + + for (int i = 0; i < joint_local_xforms.size(); ++i) { + + pxr::GfMatrix4d bind_relative_xf = joint_local_xforms[i] * inv_bind_xforms[i]; + + pxr::GfVec3f t; + pxr::GfQuatf qrot; + pxr::GfVec3h s; + + if (!pxr::UsdSkelDecomposeTransform(bind_relative_xf, &t, &qrot, &s)) { + std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl; + continue; + } + + float re = qrot.GetReal(); + pxr::GfVec3f im = qrot.GetImaginary(); + + for (int j = 0; j < 3; ++j) { + int k = 3 * i + j; + if (k >= loc_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl; + break; + } + if (FCurve *fcu = loc_curves[k]) { + add_bezt(fcu, frame, t[j]); + } + } + + for (int j = 0; j < 4; ++j) { + int k = 4 * i + j; + if (k >= rot_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl; + break; + } + if (FCurve *fcu = rot_curves[k]) { + if (j == 0) { + add_bezt(fcu, frame, re); + } + else { + add_bezt(fcu, frame, im[j - 1]); + } + } + } + + for (int j = 0; j < 3; ++j) { + int k = 3 * i + j; + if (k >= scale_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl; + break; + } + if (FCurve *fcu = scale_curves[k]) { + add_bezt(fcu, frame, s[j]); + } + } + } + } +} + +void import_skel_bindings(Main *bmain, Object *mesh_obj, pxr::UsdPrim prim) +{ + if (!(bmain && mesh_obj && prim)) { + return; + } + + if (prim.IsInstanceProxy() || prim.IsInPrototype()) { + /* Attempting to create a UsdSkelBindingAPI for + * instance proxies and prototypes generates USD errors. */ + return; + } + + if (mesh_obj->type != OB_MESH) { + return; + } + + pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim); + + if (!skel_api) { + return; + } + + pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton(); + + if (!skel) { + return; + } + + pxr::VtArray joints; + + if (skel_api.GetJointsAttr().HasAuthoredValue()) { + skel_api.GetJointsAttr().Get(&joints); + } + else if (skel.GetJointsAttr().HasAuthoredValue()) { + skel.GetJointsAttr().Get(&joints); + } + + if (joints.empty()) { + return; + } + + pxr::UsdGeomPrimvar joint_indices_primvar = skel_api.GetJointIndicesPrimvar(); + + if (!(joint_indices_primvar && joint_indices_primvar.HasAuthoredValue())) { + return; + } + + pxr::UsdGeomPrimvar joint_weights_primvar = skel_api.GetJointWeightsPrimvar(); + + if (!(joint_weights_primvar && joint_weights_primvar.HasAuthoredValue())) { + return; + } + + int joint_indices_elem_size = joint_indices_primvar.GetElementSize(); + int joint_weights_elem_size = joint_weights_primvar.GetElementSize(); + + if (joint_indices_elem_size != joint_weights_elem_size) { + std::cout << "WARNING: joint weights and joint indices element size mismatch." << std::endl; + return; + } + + /* The set of unique joint indices referenced in the joint indices + * attribute. */ + pxr::VtIntArray joint_indices; + joint_indices_primvar.ComputeFlattened(&joint_indices); + + pxr::VtFloatArray joint_weights; + joint_weights_primvar.ComputeFlattened(&joint_weights); + + if (joint_indices.empty() || joint_weights.empty()) { + return; + } + + if (joint_indices.size() != joint_weights.size()) { + std::cout << "WARNING: joint weights and joint indices size mismatch." << std::endl; + return; + } + + Mesh *mesh = static_cast(mesh_obj->data); + + pxr::TfToken interp = joint_weights_primvar.GetInterpolation(); + + if (interp != pxr::UsdGeomTokens->vertex && interp != pxr::UsdGeomTokens->constant) { + std::cout << "WARNING: unexpected joint weights interpolation type " << interp + << std::endl; + return; + } + + if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) { + std::cout << "WARNING: joint weights of unexpected size for vertex interpolation." << std::endl; + return; + } + + if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) { + std::cout << "WARNING: joint weights of unexpected size for constant interpolation." + << std::endl; + return; + } + + std::vector used_indices; + for (int index : joint_indices) { + if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) { + /* We haven't accounted for this index yet. */ + if (index < 0 || index >= joints.size()) { + std::cout << "Out of bound joint index " << index << std::endl; + continue; + } + used_indices.push_back(index); + } + } + + if (used_indices.empty()) { + return; + } + + if (BKE_object_defgroup_data_create(static_cast(mesh_obj->data)) == NULL) { + return; + } + + /* Add the armature modifier, if one doesn't exist. */ + if (!BKE_modifiers_findby_type(mesh_obj, eModifierType_Armature)) { + ModifierData *md = BKE_modifier_new(eModifierType_Armature); + BLI_addtail(&mesh_obj->modifiers, md); + } + + std::vector joint_def_grps(joints.size(), nullptr); + + for (int idx : used_indices) { + std::string joint_name = pxr::SdfPath(joints[idx]).GetName(); + if (!BKE_object_defgroup_find_name(mesh_obj, joint_name.c_str())) { + bDeformGroup *def_grp = BKE_object_defgroup_add_name(mesh_obj, joint_name.c_str()); + joint_def_grps[idx] = def_grp; + } + } + + for (int i = 0; i < mesh->totvert; ++i) { + /* Offset into the weights array, which is + * always 0 for constant interpolation. */ + int offset = 0; + if (interp == pxr::UsdGeomTokens->vertex) { + offset = i * joint_weights_elem_size; + } + for (int j = 0; j < joint_weights_elem_size; ++j) { + int k = offset + j; + float w = joint_weights[k]; + if (w < .00001) { + /* No deform group if zero weight. */ + continue; + } + int joint_idx = joint_indices[k]; + bDeformGroup *def_grp = joint_def_grps[joint_idx]; + if (def_grp) { + ED_vgroup_vert_add(mesh_obj, def_grp, i, w, WEIGHT_REPLACE); + } + } + } +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h new file mode 100644 index 00000000000..2461663cb58 --- /dev/null +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -0,0 +1,29 @@ +/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include +#include +#include + +struct Main; +struct Object; +struct Scene; +struct USDExportParams; +struct USDImportParams; + +namespace blender::io::usd { + +struct ImportSettings; + +void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim); + +void create_skeleton_curves(Main *bmain, + Object *obj, + const pxr::UsdSkelSkeletonQuery &skel_query, + const std::map &joint_to_bone_map); + +void import_skel_bindings(Main *bmain, Object *shape_obj, pxr::UsdPrim prim); + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 9a8ab9e11be..945fc4ec19e 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -69,6 +69,8 @@ struct USDImportParams { bool import_meshes; bool import_volumes; bool import_shapes; + bool import_skeletons; + bool import_blendshapes; char *prim_path_mask; bool import_subdiv; bool import_instance_proxies; -- 2.30.2 From a7d1c177d389e47644c23aee207009af185a0cc1 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Thu, 3 Aug 2023 20:04:09 -0400 Subject: [PATCH 02/30] USD import: set binding xform for skinned meshes. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 36 +++++++++++++ .../blender/io/usd/intern/usd_reader_mesh.h | 6 +++ .../blender/io/usd/intern/usd_reader_xform.cc | 52 ++++++++++++------- .../blender/io/usd/intern/usd_reader_xform.h | 4 ++ 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 3a02252d039..0831c819d05 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -1014,4 +1014,40 @@ std::string USDMeshReader::get_skeleton_path() const return ""; } +bool USDMeshReader::get_local_usd_xform(const float time, + pxr::GfMatrix4d *r_xform, + bool *r_is_constant) const +{ + if (!r_xform) { + return false; + } + + if (!import_params_.import_skeletons || prim_.IsInstanceProxy() || prim_.IsInPrototype()) { + /* Use the standard transform computation, since we are ignoring + * skinning data. */ + return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); + } + + if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) { + if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) { + pxr::GfMatrix4d bind_xf; + if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) { + /* Assume that if a bind transform is defined, then the + * transform is constant. */ + if (r_is_constant) { + *r_is_constant = true; + } + *r_xform = bind_xf; + return true; + } + else { + std::cout << "WARNING: couldn't compute geom bind transform for " << prim_.GetPath() + << std::endl; + } + } + } + + return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); +} + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index 1ed696e047f..590b9f48864 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -83,6 +83,12 @@ class USDMeshReader : public USDGeomReader { void read_color_data_primvar(Mesh *mesh, const pxr::UsdGeomPrimvar &color_primvar, const double motionSampleTime); + + /* Override transform computation to account for the binding + * transformation for skinned meshes. */ + bool get_local_usd_xform(const float time, + pxr::GfMatrix4d *r_xform, + bool *r_is_constant) const override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc index a704956b52b..2f2400dc238 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.cc +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -72,28 +72,11 @@ void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */, unit_m4(r_mat); - pxr::UsdGeomXformable xformable; - - if (use_parent_xform_) { - xformable = pxr::UsdGeomXformable(prim_.GetParent()); - } - else { - xformable = pxr::UsdGeomXformable(prim_); - } - - if (!xformable) { - /* This might happen if the prim is a Scope. */ + pxr::GfMatrix4d usd_local_xf; + if (!get_local_usd_xform(time, &usd_local_xf, r_is_constant)) { return; } - if (r_is_constant) { - *r_is_constant = !xformable.TransformMightBeTimeVarying(); - } - - pxr::GfMatrix4d usd_local_xf; - bool reset_xform_stack; - xformable.GetLocalTransformation(&usd_local_xf, &reset_xform_stack, time); - /* Convert the result to a float matrix. */ pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf); mat4f.Get(r_mat); @@ -167,4 +150,35 @@ bool USDXformReader::is_root_xform_prim() const return false; } +bool USDXformReader::get_local_usd_xform(const float time, + pxr::GfMatrix4d *r_xform, + bool *r_is_constant) const +{ + if (!r_xform) { + return false; + } + + pxr::UsdGeomXformable xformable; + + if (use_parent_xform_) { + xformable = pxr::UsdGeomXformable(prim_.GetParent()); + } + else { + xformable = pxr::UsdGeomXformable(prim_); + } + + if (!xformable) { + /* This might happen if the prim is a Scope. */ + return false; + } + + if (r_is_constant) { + *r_is_constant = !xformable.TransformMightBeTimeVarying(); + } + + bool reset_xform_stack; + return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time); +} + + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h index 851f6906d85..0b3f8cb9606 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.h +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -50,6 +50,10 @@ class USDXformReader : public USDPrimReader { protected: /* Returns true if the contained USD prim is the root of a transform hierarchy. */ bool is_root_xform_prim() const; + + virtual bool get_local_usd_xform(const float time, + pxr::GfMatrix4d *r_xform, + bool *r_is_constant) const; }; } // namespace blender::io::usd -- 2.30.2 From 076689ae9c82632cd578555123b69ceafca91c25 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Fri, 4 Aug 2023 14:04:24 -0400 Subject: [PATCH 03/30] USD import: allow skel binding to prototype prims. Also removed commented out code. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 11 +++---- .../blender/io/usd/intern/usd_skel_convert.cc | 29 +++---------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 0831c819d05..353d3da6cb8 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -995,9 +995,9 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, std::string USDMeshReader::get_skeleton_path() const { /* Make sure we can apply UsdSkelBindingAPI to the prim. - * Attempting to apply the API to instance proxies or - * prototypes generates an error. */ - if (!prim_ || prim_.IsInstanceProxy() || prim_.IsInPrototype()) { + * Attempting to apply the API to instance proxies generates + * a USD error. */ + if (!prim_ || prim_.IsInstanceProxy()) { return ""; } @@ -1022,9 +1022,10 @@ bool USDMeshReader::get_local_usd_xform(const float time, return false; } - if (!import_params_.import_skeletons || prim_.IsInstanceProxy() || prim_.IsInPrototype()) { + if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) { /* Use the standard transform computation, since we are ignoring - * skinning data. */ + * skinning data. Note that applying theUsdSkelBindingAPI to an + * instance proxy generates a USD error. */ return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); } diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 2d9fdae4e94..5b290773767 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -82,36 +82,15 @@ void add_bezt(FCurve *fcu, namespace blender::io::usd { -//pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time) -//{ -// pxr::GfMatrix4d local_xf(1.0f); -// -// if (!prim) { -// return local_xf; -// } -// -// pxr::UsdGeomXformable xformable(prim); -// -// if (xformable) { -// bool reset_xform_stack = false; -// if (!xformable.GetLocalTransformation(&local_xf, &reset_xform_stack, time)) { -// std::cout << "WARNING: couldn't get local xform for prim " << prim.GetPath() << std::endl; -// return local_xf; -// } -// } -// -// return local_xf * get_world_matrix(prim.GetParent(), time); -//} - void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) { if (!(obj && obj->data && obj->type == OB_MESH && prim)) { return; } - if (prim.IsInstanceProxy() || prim.IsInPrototype()) { + if (prim.IsInstanceProxy()) { /* Attempting to create a UsdSkelBindingAPI for - * instance proxies and prototypes generates USD errors. */ + * instance proxies generates USD errors. */ return; } @@ -535,9 +514,9 @@ void import_skel_bindings(Main *bmain, Object *mesh_obj, pxr::UsdPrim prim) return; } - if (prim.IsInstanceProxy() || prim.IsInPrototype()) { + if (prim.IsInstanceProxy()) { /* Attempting to create a UsdSkelBindingAPI for - * instance proxies and prototypes generates USD errors. */ + * instance proxies generates USD errors. */ return; } -- 2.30.2 From 0bf5c465b2c180745c7f5178168c36c81b04e745 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sat, 5 Aug 2023 15:49:54 -0400 Subject: [PATCH 04/30] USD: new import_skeleton() function. Moved skeleton conversion code from the USDSkeletonReader class to a new import_skeleton() utility function. --- .../io/usd/intern/usd_reader_skeleton.cc | 209 +---------------- .../blender/io/usd/intern/usd_skel_convert.cc | 214 ++++++++++++++++++ .../blender/io/usd/intern/usd_skel_convert.h | 2 + 3 files changed, 217 insertions(+), 208 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index 0ade780ea4c..0db8b9bbbed 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -14,16 +14,10 @@ #include "DNA_armature_types.h" #include "DNA_object_types.h" -#include "ED_armature.h" - #include "MEM_guardedalloc.h" #include "WM_api.h" -#include -#include -#include - #include namespace blender::io::usd { @@ -47,208 +41,7 @@ void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleT return; } - pxr::UsdSkelCache skel_cache; - pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel_); - - if (!skel_query.IsValid()) { - std::cout << "WARNING: couldn't query skeleton " << skel_.GetPath() << std::endl; - return; - } - - const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); - - pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); - - if (joint_order.size() != skel_topology.size()) { - std::cout << "WARNING: skel topology and joint order size mismatch\n"; - return; - } - - bArmature *arm = static_cast(object_->data); - - ED_armature_to_edit(arm); - - /* The bones we create, stored in the skeleton's joint order. */ - std::vector edit_bones; - - size_t num_joints = skel_topology.GetNumJoints(); - - /* Keep track of the bones we create for each joint. */ - std::map joint_to_bone_map; - - /* Create the bones. */ - for (const pxr::TfToken &joint : joint_order) { - std::string name = pxr::SdfPath(joint).GetName(); - EditBone * bone = ED_armature_ebone_add(arm, name.c_str()); - if (!bone) { - std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl; - edit_bones.push_back(nullptr); - continue; - } - joint_to_bone_map.insert(std::make_pair(joint, bone->name)); - edit_bones.push_back(bone); - } - - /* Sanity check: we should have created a bone for each joint. */ - - if (edit_bones.size() != num_joints) { - std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel_.GetPath() << std::endl; - return; - } - - /* Record the child bone indices per parent bone. */ - std::vector> child_bones(num_joints); - - /* Set bone parenting. */ - for (size_t i = 0; i < num_joints; ++i) { - int parent_idx = skel_topology.GetParent(i); - if (parent_idx < 0) { - continue; - } - if (parent_idx >= edit_bones.size()) { - std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i]) - << " for skeleton " << skel_.GetPath() << std::endl; - continue; - } - - child_bones[parent_idx].push_back(i); - if (edit_bones[i] && edit_bones[parent_idx]) { - edit_bones[i]->parent = edit_bones[parent_idx]; - } - } - - /* Joint bind transforms. */ - pxr::VtMatrix4dArray bind_xforms; - if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - std::cout << "WARNING: couldn't get world bind transforms for skeleton " - << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; - return; - } - - if (bind_xforms.size() != num_joints) { - std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " << skel_.GetPath() << std::endl; - return; - } - - /* Check if any bone natrices have negative determinants, - * indicating negative scales, possibly due to mirroring - * operations. Such matrices can't be propery converted - * to Blender's axis/roll bone representation (see - * https://developer.blender.org/T82930). If we detect - * such matrices, we will flag an error and won't try - * to import the animation, since the rotations would - * be incorrect in such cases. Unfortunately, the Pixar - * UsdSkel examples of the "HumanFemale" suffer from - * this issue. */ - bool negative_determinant = false; - - /* Set bone rest transforms. */ - for (size_t i = 0; i < num_joints; ++i) { - EditBone *ebone = edit_bones[i]; - - if (!ebone) { - continue; - } - - pxr::GfMatrix4f mat(bind_xforms[i]); - - float mat4[4][4]; - mat.Get(mat4); - - pxr::GfVec3f head(0.0f, 0.0f, 0.0f); - pxr::GfVec3f tail(0.0f, 1.0f, 0.0f); - - copy_v3_v3(ebone->head, head.data()); - copy_v3_v3(ebone->tail, tail.data()); - - ED_armature_ebone_from_mat4(ebone, mat4); - - if (mat.GetDeterminant() < 0.0) { - negative_determinant = true; - } - } - - bool valid_skeleton = true; - if (negative_determinant) { - valid_skeleton = false; - WM_reportf(RPT_WARNING, - "USD Skeleton Import: bone matrices with negative determinants detected in prim %s." - "Such matrices may indicate negative scales, possibly due to mirroring operations, " - "and can't currently be converted to Blender's bone representation. " - "The skeletal animation won't be imported", prim_.GetPath().GetAsString().c_str()); - } - - /* Scale bones to account for separation between parents and - * children, so that the bone size is in proportion with the - * overall skeleton hierarchy. USD skeletons are composed of - * joints which we imperfectly represent as bones. */ - - float avg_len_scale = 0; - for (size_t i = 0; i < num_joints; ++i) { - - /* If the bone has any children, scale its length - * by the distance between this bone's head - * and the average head location of its children. */ - - if (child_bones[i].empty()) { - continue; - } - - EditBone *parent = edit_bones[i]; - if (!parent) { - continue; - } - - pxr::GfVec3f avg_child_head(0); - for (int j : child_bones[i]) { - EditBone *child = edit_bones[j]; - if (!child) { - continue; - } - pxr::GfVec3f child_head(child->head); - avg_child_head += child_head; - } - - avg_child_head /= child_bones[i].size(); - - pxr::GfVec3f parent_head(parent->head); - pxr::GfVec3f parent_tail(parent->tail); - - float new_len = (avg_child_head - parent_head).GetLength(); - - /* Be sure not to scale by zero. */ - if (new_len > .00001) { - parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len; - copy_v3_v3(parent->tail, parent_tail.data()); - avg_len_scale += new_len; - } - } - - /* Scale terminal bones by the average length scale. */ - avg_len_scale /= num_joints; - - if (avg_len_scale > .00001) { - for (size_t i = 0; i < num_joints; ++i) { - if (!child_bones[i].empty()) { - continue; - } - EditBone *bone = edit_bones[i]; - if (!bone) { - continue; - } - pxr::GfVec3f head(bone->head); - pxr::GfVec3f tail(bone->tail); - tail = head + (tail - head).GetNormalized() * avg_len_scale; - copy_v3_v3(bone->tail, tail.data()); - } - } - - ED_armature_from_edit(bmain, arm); - ED_armature_edit_free(arm); - - if (valid_skeleton) { - create_skeleton_curves(bmain, object_, skel_query, joint_to_bone_map); - } + import_skeleton(bmain, object_, skel_); USDXformReader::read_object_data(bmain, motionSampleTime); } diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 5b290773767..dbe63bfcdb3 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include "DNA_anim_types.h" @@ -33,9 +35,12 @@ #include "BLI_math_vector.h" +#include "ED_armature.h" #include "ED_keyframing.h" #include "ED_mesh.h" +#include "WM_api.h" + #include #include @@ -313,6 +318,215 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) } +void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel) +{ + pxr::UsdSkelCache skel_cache; + pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); + + if (!skel_query.IsValid()) { + std::cout << "WARNING: couldn't query skeleton " << skel.GetPath() << std::endl; + return; + } + + const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); + + pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); + + if (joint_order.size() != skel_topology.size()) { + std::cout << "WARNING: skel topology and joint order size mismatch\n"; + return; + } + + bArmature *arm = static_cast(obj->data); + + ED_armature_to_edit(arm); + + /* The bones we create, stored in the skeleton's joint order. */ + std::vector edit_bones; + + size_t num_joints = skel_topology.GetNumJoints(); + + /* Keep track of the bones we create for each joint. */ + std::map joint_to_bone_map; + + /* Create the bones. */ + for (const pxr::TfToken &joint : joint_order) { + std::string name = pxr::SdfPath(joint).GetName(); + EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); + if (!bone) { + std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl; + edit_bones.push_back(nullptr); + continue; + } + joint_to_bone_map.insert(std::make_pair(joint, bone->name)); + edit_bones.push_back(bone); + } + + /* Sanity check: we should have created a bone for each joint. */ + + if (edit_bones.size() != num_joints) { + std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel.GetPath() + << std::endl; + return; + } + + /* Record the child bone indices per parent bone. */ + std::vector> child_bones(num_joints); + + /* Set bone parenting. */ + for (size_t i = 0; i < num_joints; ++i) { + int parent_idx = skel_topology.GetParent(i); + if (parent_idx < 0) { + continue; + } + if (parent_idx >= edit_bones.size()) { + std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i]) + << " for skeleton " << skel.GetPath() << std::endl; + continue; + } + + child_bones[parent_idx].push_back(i); + if (edit_bones[i] && edit_bones[parent_idx]) { + edit_bones[i]->parent = edit_bones[parent_idx]; + } + } + + /* Joint bind transforms. */ + pxr::VtMatrix4dArray bind_xforms; + if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { + std::cout << "WARNING: couldn't get world bind transforms for skeleton " + << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; + return; + } + + if (bind_xforms.size() != num_joints) { + std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " + << skel.GetPath() << std::endl; + return; + } + + /* Check if any bone natrices have negative determinants, + * indicating negative scales, possibly due to mirroring + * operations. Such matrices can't be propery converted + * to Blender's axis/roll bone representation (see + * https://developer.blender.org/T82930). If we detect + * such matrices, we will flag an error and won't try + * to import the animation, since the rotations would + * be incorrect in such cases. Unfortunately, the Pixar + * UsdSkel examples of the "HumanFemale" suffer from + * this issue. */ + bool negative_determinant = false; + + /* Set bone rest transforms. */ + for (size_t i = 0; i < num_joints; ++i) { + EditBone *ebone = edit_bones[i]; + + if (!ebone) { + continue; + } + + pxr::GfMatrix4f mat(bind_xforms[i]); + + float mat4[4][4]; + mat.Get(mat4); + + pxr::GfVec3f head(0.0f, 0.0f, 0.0f); + pxr::GfVec3f tail(0.0f, 1.0f, 0.0f); + + copy_v3_v3(ebone->head, head.data()); + copy_v3_v3(ebone->tail, tail.data()); + + ED_armature_ebone_from_mat4(ebone, mat4); + + if (mat.GetDeterminant() < 0.0) { + negative_determinant = true; + } + } + + bool valid_skeleton = true; + if (negative_determinant) { + valid_skeleton = false; + WM_reportf(RPT_WARNING, + "USD Skeleton Import: bone matrices with negative determinants detected in prim %s." + "Such matrices may indicate negative scales, possibly due to mirroring operations, " + "and can't currently be converted to Blender's bone representation. " + "The skeletal animation won't be imported", + skel.GetPath().GetAsString().c_str()); + } + + /* Scale bones to account for separation between parents and + * children, so that the bone size is in proportion with the + * overall skeleton hierarchy. USD skeletons are composed of + * joints which we imperfectly represent as bones. */ + + float avg_len_scale = 0; + for (size_t i = 0; i < num_joints; ++i) { + + /* If the bone has any children, scale its length + * by the distance between this bone's head + * and the average head location of its children. */ + + if (child_bones[i].empty()) { + continue; + } + + EditBone *parent = edit_bones[i]; + if (!parent) { + continue; + } + + pxr::GfVec3f avg_child_head(0); + for (int j : child_bones[i]) { + EditBone *child = edit_bones[j]; + if (!child) { + continue; + } + pxr::GfVec3f child_head(child->head); + avg_child_head += child_head; + } + + avg_child_head /= child_bones[i].size(); + + pxr::GfVec3f parent_head(parent->head); + pxr::GfVec3f parent_tail(parent->tail); + + float new_len = (avg_child_head - parent_head).GetLength(); + + /* Be sure not to scale by zero. */ + if (new_len > .00001) { + parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len; + copy_v3_v3(parent->tail, parent_tail.data()); + avg_len_scale += new_len; + } + } + + /* Scale terminal bones by the average length scale. */ + avg_len_scale /= num_joints; + + if (avg_len_scale > .00001) { + for (size_t i = 0; i < num_joints; ++i) { + if (!child_bones[i].empty()) { + continue; + } + EditBone *bone = edit_bones[i]; + if (!bone) { + continue; + } + pxr::GfVec3f head(bone->head); + pxr::GfVec3f tail(bone->tail); + tail = head + (tail - head).GetNormalized() * avg_len_scale; + copy_v3_v3(bone->tail, tail.data()); + } + } + + ED_armature_from_edit(bmain, arm); + ED_armature_edit_free(arm); + + if (valid_skeleton) { + create_skeleton_curves(bmain, obj, skel_query, joint_to_bone_map); + } +} + void create_skeleton_curves(Main *bmain, Object *obj, const pxr::UsdSkelSkeletonQuery &skel_query, diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index 2461663cb58..6657a504006 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -19,6 +19,8 @@ struct ImportSettings; void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim); +void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel); + void create_skeleton_curves(Main *bmain, Object *obj, const pxr::UsdSkelSkeletonQuery &skel_query, -- 2.30.2 From b6cee3cdcd4295316790adb43bc3d9b615262991 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sat, 5 Aug 2023 22:25:54 -0400 Subject: [PATCH 05/30] USD skel import: comments and cleanup. Replaced cout statements with Blender warnings and general cleanup. --- .../blender/io/usd/intern/usd_skel_convert.cc | 83 ++++++++++++++----- .../blender/io/usd/intern/usd_skel_convert.h | 21 ++++- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index dbe63bfcdb3..fb6eacfec90 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -102,16 +102,23 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim); if (!skel_api) { + /* No skel binding. */ return; } + /* Get the blend shape targets, which are the USD paths to the + * blend shape primitives. */ + if (!skel_api.GetBlendShapeTargetsRel().HasAuthoredTargets()) { + /* No targets. */ return; } pxr::SdfPathVector targets; if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { - std::cout << "Couldn't get blendshape targets for prim " << prim.GetPath() << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't get blendshape targets for prim %s", + __func__, prim.GetPath().GetAsString().c_str()); return; } @@ -123,6 +130,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } + /* Get the blend shape name tokens. */ pxr::VtTokenArray blendshapes; if (!skel_api.GetBlendShapesAttr().Get(&blendshapes)) { return; @@ -132,63 +140,82 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } + /* Sanity check. */ if (targets.size() != blendshapes.size()) { - std::cout << "Number of blendshapes doesn't match number of blendshape targets for prim " << prim.GetPath() << std::endl; + WM_reportf(RPT_WARNING, + "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); + return; + } + + pxr::UsdStageRefPtr stage = prim.GetStage(); + + if (!stage) { + WM_reportf(RPT_WARNING, + "%s: Couldn't get stage for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } Mesh *mesh = static_cast(obj->data); - /* insert key to source mesh */ + /* Insert key to source mesh. */ Key *key = BKE_key_add(bmain, (ID *)mesh); key->type = KEY_RELATIVE; mesh->key = key; - /* insert basis key */ + /* Insert basis key. */ KeyBlock *kb = BKE_keyblock_add(key, "Basis"); BKE_keyblock_convert_from_mesh(mesh, key, kb); - pxr::UsdStageRefPtr stage = prim.GetStage(); - - if (!stage) { - return; - } - /* Keep track of the shapkeys we're adding, for * validation when creating curves later. */ std::set shapekey_names; for (int i = 0; i < targets.size(); ++i) { + /* Get USD path to blend shape. */ const pxr::SdfPath &path = targets[i]; - pxr::UsdSkelBlendShape blendshape(stage->GetPrimAtPath(path)); if (!blendshape) { continue; } + /* Get the blend shape offests. */ if (!blendshape.GetOffsetsAttr().HasAuthoredValue()) { + /* Blend shape has no authored offsets. */ continue; } pxr::VtVec3fArray offsets; if (!blendshape.GetOffsetsAttr().Get(&offsets)) { - std::cout << "Couldn't get offsets for blendshape " << path << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't get offsets for blend shape %s", + __func__, + path.GetAsString().c_str()); continue; } if (offsets.empty()) { - std::cout << "No offsets for blendshape " << path << std::endl; + WM_reportf(RPT_WARNING, + "%s: No offsets for blend shape %s", + __func__, + path.GetAsString().c_str()); continue; } shapekey_names.insert(blendshapes[i]); + /* Add the key block. */ kb = BKE_keyblock_add(key, blendshapes[i].GetString().c_str()); BKE_keyblock_convert_from_mesh(mesh, key, kb); + /* if authored, point indices are indices into the original mesh + * that correspond to the values in the offsets array. */ pxr::VtArray point_indices; if (blendshape.GetPointIndicesAttr().HasAuthoredValue()) { blendshape.GetPointIndicesAttr().Get(&point_indices); @@ -197,25 +224,35 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) float *fp = static_cast(kb->data); if (point_indices.empty()) { + /* Iterate over all key block elements and add the corresponding + * offset to the key block point. */ for (int a = 0; a < kb->totelem; ++a, fp += 3) { if (a >= offsets.size()) { - std::cout << "Number of offsets greater than number of mesh vertices for blendshape " - << path << std::endl; + WM_reportf(RPT_WARNING, + "%s: Number of offsets greater than number of mesh vertices for blend shape %s", + __func__, + path.GetAsString().c_str()); break; } add_v3_v3(fp, offsets[a].data()); } } else { + /* Iterate over the point indices and add the offset to the corresponding + * key block point. */ int a = 0; for (int i : point_indices) { if (i < 0 || i > kb->totelem) { - std::cout << "Out of bounds point index " << i << " for blendshape " << path << std::endl; + std::cerr << "Out of bounds point index " << i << " for blendshape " << path << std::endl; ++a; continue; } if (a >= offsets.size()) { - std::cout << "Number of offsets greater than number of mesh vertices for blendshape " << path << std::endl; + WM_reportf( + RPT_WARNING, + "%s: Number of offsets greater than number of mesh vertices for blend shape %s", + __func__, + path.GetAsString().c_str()); break; } add_v3_v3(&fp[3 * i], offsets[a].data()); @@ -224,6 +261,8 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) } } + /* Get the blend animation source from the skeleton. */ + pxr::UsdSkelSkeleton skel_prim = skel_api.GetInheritedSkeleton(); if (!skel_prim) { @@ -248,6 +287,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } + /* Check if a blend shape weight animation was authored. */ if (!skel_anim.GetBlendShapesAttr().HasAuthoredValue()) { return; } @@ -258,6 +298,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } + /* Get the animation time samples. */ std::vector times; if (!weights_attr.GetTimeSamples(×)) { return; @@ -267,6 +308,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } + /* Get the blend shape name tokens. */ if (!skel_anim.GetBlendShapesAttr().Get(&blendshapes)) { return; } @@ -290,6 +332,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) continue; } + /* Create the curve for this shape key. */ std::string rna_path = "key_blocks[\"" + blendshape_name.GetString() + "\"].value"; FCurve *fcu = create_fcurve(0, rna_path.c_str()); fcu->totvert = num_samples; @@ -297,15 +340,17 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) BLI_addtail(&act->curves, fcu); } + /* Add the weight time samples to the curves. */ for (double frame : times) { pxr::VtFloatArray weights; if (!weights_attr.Get(&weights, frame)) { - std::cout << "Couldn't get blendshape weights for time " << frame << std::endl; + std::cerr << "Couldn't get blendshape weights for time " << frame << std::endl; continue; } if (weights.size() != curves.size()) { - std::cout << "Programmer error: number of weight samples doesn't match number of shapekey curve entries for frame " << frame << std::endl; + std::cerr << "Programmer error: number of weight samples doesn't match number of shapekey curve entries for frame " + << frame << std::endl; continue; } diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index 6657a504006..ba11c9a72f7 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -17,7 +17,26 @@ namespace blender::io::usd { struct ImportSettings; -void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim); +/** + * This file contains utilities for converting between UsdSkel data and + * Blender armatures and shape keys. The following is a reference on the + * UsdSkel API: + * + * https://openusd.org/23.05/api/usd_skel_page_front.html + */ + +/** + * Import USD blend shapes from a USD primitive as shape keys on a mesh + * object. If the blend shapes have animating weights, the time-sampled + * weights will be imported as shape key animation curves. If the USD + * primitive does not have blend shape targets defined, this function is a + * no-op. + * + * \param bmain: Main pointer + * \param obj: Mesh object to which imported shape keys will be added + * \param prim: The USD primitive from which blendshapes will be imported + */ +void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim); void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel); -- 2.30.2 From 83ef1765023acc5824e74c552451eb936c2ff3e3 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 6 Aug 2023 13:01:18 -0400 Subject: [PATCH 06/30] USD skel import: optional curve import, comments. Added flag to control whether to import animation cuves. Also added comments and made naming more descriptive. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 2 +- .../blender/io/usd/intern/usd_skel_convert.cc | 31 ++++++++----- .../blender/io/usd/intern/usd_skel_convert.h | 46 +++++++++++++++---- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 353d3da6cb8..458b473a35b 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -235,7 +235,7 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) } if (import_params_.import_skeletons) { - import_skel_bindings(bmain, object_, prim_); + import_mesh_skel_bindings(bmain, object_, prim_); } USDXformReader::read_object_data(bmain, motionSampleTime); diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index fb6eacfec90..8ebbcbe424f 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -87,9 +87,9 @@ void add_bezt(FCurve *fcu, namespace blender::io::usd { -void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) +void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, const bool import_anim) { - if (!(obj && obj->data && obj->type == OB_MESH && prim)) { + if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) { return; } @@ -159,7 +159,7 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) return; } - Mesh *mesh = static_cast(obj->data); + Mesh *mesh = static_cast(mesh_obj->data); /* Insert key to source mesh. */ Key *key = BKE_key_add(bmain, (ID *)mesh); @@ -261,6 +261,11 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) } } + if (!import_anim) { + /* We're not importing animation, so we are done. */ + return; + } + /* Get the blend animation source from the skeleton. */ pxr::UsdSkelSkeleton skel_prim = skel_api.GetInheritedSkeleton(); @@ -363,8 +368,12 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim) } -void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel) +void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, const bool import_anim) { + if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) { + return; + } + pxr::UsdSkelCache skel_cache; pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); @@ -382,7 +391,7 @@ void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel) return; } - bArmature *arm = static_cast(obj->data); + bArmature *arm = static_cast(arm_obj->data); ED_armature_to_edit(arm); @@ -567,18 +576,18 @@ void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel) ED_armature_from_edit(bmain, arm); ED_armature_edit_free(arm); - if (valid_skeleton) { - create_skeleton_curves(bmain, obj, skel_query, joint_to_bone_map); + if (import_anim && valid_skeleton) { + create_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map); } } void create_skeleton_curves(Main *bmain, - Object *obj, + Object *arm_obj, const pxr::UsdSkelSkeletonQuery &skel_query, const std::map &joint_to_bone_map) { - if (!(bmain && obj && skel_query)) { + if (!(bmain && arm_obj && skel_query)) { return; } @@ -601,7 +610,7 @@ void create_skeleton_curves(Main *bmain, size_t num_samples = samples.size(); - bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id); + bAction *act = ED_id_action_ensure(bmain, (ID *)&arm_obj->id); pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); @@ -767,7 +776,7 @@ void create_skeleton_curves(Main *bmain, } } -void import_skel_bindings(Main *bmain, Object *mesh_obj, pxr::UsdPrim prim) +void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) { if (!(bmain && mesh_obj && prim)) { return; diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index ba11c9a72f7..742edef95f9 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -27,24 +27,52 @@ struct ImportSettings; /** * Import USD blend shapes from a USD primitive as shape keys on a mesh - * object. If the blend shapes have animating weights, the time-sampled - * weights will be imported as shape key animation curves. If the USD - * primitive does not have blend shape targets defined, this function is a - * no-op. + * object. Optionally, if the blend shapes have animating weights, the + * time-sampled weights will be imported as shape key animation curves. + * If the USD primitive does not have blend shape targets defined, this + * function is a no-op. * * \param bmain: Main pointer - * \param obj: Mesh object to which imported shape keys will be added + * \param mesh_obj: Mesh object to which imported shape keys will be added * \param prim: The USD primitive from which blendshapes will be imported + * \param import_anim: Whether to import time-sampled weights as shape key + * animation curves */ -void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim); +void import_blendshapes(Main *bmain, + Object *mesh_obj, + const pxr::UsdPrim &prim, + bool import_anim=true); -void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel); +/** + * Import the given USD sekeleton as an armature object. Optionally, if the + * skeleton has an animation defined, the time sampled joint transforms will be + * imported as bone animation curves. + * + * \param bmain: Main pointer + * \param arm_obj: Armature object to which the bone hierachy will be added + * \param skel: The USD skeleton from which bones and animation will be imported + * \param import_anim: Whether to import time-sampled joint transforms as bone + * animation curves + */ +void import_skeleton(Main *bmain, + Object *arm_obj, + const pxr::UsdSkelSkeleton &skel, + bool import_anim=true); void create_skeleton_curves(Main *bmain, - Object *obj, + Object *arm_obj, const pxr::UsdSkelSkeletonQuery &skel_query, const std::map &joint_to_bone_map); -void import_skel_bindings(Main *bmain, Object *shape_obj, pxr::UsdPrim prim); +/** + * Import skinning data from a source USD prim as deform groups and an armature + * modifier on the given mesh object. If the USD prim does not have a skeleton + * binding defined, this function is a no-op. + * + * \param bmain: Main pointer + * \param obj: Mesh object to which an armature modifier will be added + * \param prim: The USD primitive from which skinning data will be imported + */ +void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim); } // namespace blender::io::usd -- 2.30.2 From 3acd02441dd48348e9dc2683363a739221d968a1 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 6 Aug 2023 19:59:54 -0400 Subject: [PATCH 07/30] USD: refactor bone curve creation. Made import_skeleton_curves() local to the file and simplified rest-relative transform computation. Added comments. --- .../blender/io/usd/intern/usd_skel_convert.cc | 384 +++++++++--------- .../blender/io/usd/intern/usd_skel_convert.h | 6 - 2 files changed, 190 insertions(+), 200 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 8ebbcbe424f..26fcc274a0d 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -46,6 +46,7 @@ namespace { +/* Utility: create curve at the given array index. */ FCurve *create_fcurve(int array_index, const char *rna_path) { FCurve *fcu = BKE_fcurve_create(); @@ -55,6 +56,8 @@ FCurve *create_fcurve(int array_index, const char *rna_path) return fcu; } +/* Utility: create curve at the given array index and + * adds it as a channel to a group. */ FCurve *create_chan_fcurve(bAction *act, bActionGroup *grp, int array_index, @@ -67,6 +70,7 @@ FCurve *create_chan_fcurve(bAction *act, return fcu; } +/* Utility: add curve sample. */ void add_bezt(FCurve *fcu, float frame, float value, @@ -83,6 +87,191 @@ void add_bezt(FCurve *fcu, BKE_fcurve_handles_recalc(fcu); } +/** + * Import a USD skeleton animation as an action on the given armature object. + * This assumes bones have already been created on the armature. + * + * \param bmain: Main pointer + * \param arm_obj: Armature object to which the action will be added + * \param skel_query: The USD skeleton query for reading the animation + * \param joint_to_bone_map: Map a USD skeleton joint name to a bone name + */ +void import_skeleton_curves(Main *bmain, + Object *arm_obj, + const pxr::UsdSkelSkeletonQuery &skel_query, + const std::map &joint_to_bone_map) + +{ + if (!(bmain && arm_obj && skel_query)) { + return; + } + + if (joint_to_bone_map.empty()) { + return; + } + + const pxr::UsdSkelAnimQuery &anim_query = skel_query.GetAnimQuery(); + + if (!anim_query) { + /* No animation is defined. */ + return; + } + + std::vector samples; + anim_query.GetJointTransformTimeSamples(&samples); + + if (samples.empty()) { + return; + } + + size_t num_samples = samples.size(); + + /* Create the action on the armature. */ + bAction *act = ED_id_action_ensure(bmain, (ID *)&arm_obj->id); + + /* Create the curves. */ + + /* Get the joint paths. */ + pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); + + std::vector loc_curves; + std::vector rot_curves; + std::vector scale_curves; + + /* Iterate over the joints and create the corresponding curves for the bones. */ + for (const pxr::TfToken &joint : joint_order) { + std::map::const_iterator it = joint_to_bone_map.find(joint); + + if (it == joint_to_bone_map.end()) { + /* This joint doesn't correspond to any bone we created. + * Add null placeholders for the channel curves. */ + loc_curves.push_back(nullptr); + loc_curves.push_back(nullptr); + loc_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + rot_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + scale_curves.push_back(nullptr); + continue; + } + + bActionGroup *grp = action_groups_add_new(act, it->second.c_str()); + + /* Add translation curves. */ + std::string rna_path = "pose.bones[\"" + it->second + "\"].location"; + loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + + /* Add rotation curves. */ + rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion"; + rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples)); + + /* Add scale curves. */ + rna_path = "pose.bones[\"" + it->second + "\"].scale"; + scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + } + + /* Sanity checks: make sure we have a curve entry for each joint. */ + if (loc_curves.size() != joint_order.size() * 3) { + std::cout << "PROGRAMMER ERROR: location curve count mismatch\n"; + return; + } + + if (rot_curves.size() != joint_order.size() * 4) { + std::cout << "PROGRAMMER ERROR: rotation curve count mismatch\n"; + return; + } + + if (scale_curves.size() != joint_order.size() * 3) { + std::cout << "PROGRAMMER ERROR: scale curve count mismatch\n"; + return; + } + + /* Set the curve samples. */ + + for (double frame : samples) { + /* Compute joint transforms which, when concatenated against the rest pose, + * produce joint transforms in joint-local space. More specifically, this + * computes restRelativeTransform in: + * restRelativeTransform * restTransform = jointLocalTransform + */ + pxr::VtMatrix4dArray rest_relative_xforms; + if (!skel_query.ComputeJointRestRelativeTransforms(&rest_relative_xforms, frame)) { + std::cout << "WARNING: couldn't compute joint rest relative transforms on frame " << frame + << std::endl; + continue; + } + + if (rest_relative_xforms.size() != joint_order.size()) { + std::cout << "WARNING: number of joint rest relative transform entries " << rest_relative_xforms.size() + << " doesn't match the number of joints " << joint_order.size() << std::endl; + continue; + } + + for (int i = 0; i < rest_relative_xforms.size(); ++i) { + + pxr::GfVec3f t; + pxr::GfQuatf qrot; + pxr::GfVec3h s; + + if (!pxr::UsdSkelDecomposeTransform(rest_relative_xforms[i], &t, &qrot, &s)) { + std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl; + continue; + } + + float re = qrot.GetReal(); + pxr::GfVec3f im = qrot.GetImaginary(); + + for (int j = 0; j < 3; ++j) { + int k = 3 * i + j; + if (k >= loc_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl; + break; + } + if (FCurve *fcu = loc_curves[k]) { + add_bezt(fcu, frame, t[j]); + } + } + + for (int j = 0; j < 4; ++j) { + int k = 4 * i + j; + if (k >= rot_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl; + break; + } + if (FCurve *fcu = rot_curves[k]) { + if (j == 0) { + add_bezt(fcu, frame, re); + } + else { + add_bezt(fcu, frame, im[j - 1]); + } + } + } + + for (int j = 0; j < 3; ++j) { + int k = 3 * i + j; + if (k >= scale_curves.size()) { + std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl; + break; + } + if (FCurve *fcu = scale_curves[k]) { + add_bezt(fcu, frame, s[j]); + } + } + } + } +} + } // End anonymous namespace. namespace blender::io::usd { @@ -577,204 +766,11 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s ED_armature_edit_free(arm); if (import_anim && valid_skeleton) { - create_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map); + import_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map); } } -void create_skeleton_curves(Main *bmain, - Object *arm_obj, - const pxr::UsdSkelSkeletonQuery &skel_query, - const std::map &joint_to_bone_map) -{ - if (!(bmain && arm_obj && skel_query)) { - return; - } - - if (joint_to_bone_map.empty()) { - return; - } - - const pxr::UsdSkelAnimQuery &anim_query = skel_query.GetAnimQuery(); - - if (!anim_query) { - return; - } - - std::vector samples; - anim_query.GetJointTransformTimeSamples(&samples); - - if (samples.empty()) { - return; - } - - size_t num_samples = samples.size(); - - bAction *act = ED_id_action_ensure(bmain, (ID *)&arm_obj->id); - - pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); - - std::vector loc_curves; - std::vector rot_curves; - std::vector scale_curves; - - for (const pxr::TfToken &joint : joint_order) { - std::map::const_iterator it = joint_to_bone_map.find(joint); - - if (it == joint_to_bone_map.end()) { - /* This joint doesn't correspond to any bone we created. - * Add null placeholders for the channel curves. */ - loc_curves.push_back(nullptr); - loc_curves.push_back(nullptr); - loc_curves.push_back(nullptr); - rot_curves.push_back(nullptr); - rot_curves.push_back(nullptr); - rot_curves.push_back(nullptr); - rot_curves.push_back(nullptr); - scale_curves.push_back(nullptr); - scale_curves.push_back(nullptr); - scale_curves.push_back(nullptr); - continue; - } - - bActionGroup *grp = action_groups_add_new(act, it->second.c_str()); - - /* Add translation curves. */ - std::string rna_path = "pose.bones[\"" + it->second + "\"].location"; - loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); - - /* Add rotation curves. */ - rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion"; - rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples)); - - /* Add scale curves. */ - rna_path = "pose.bones[\"" + it->second + "\"].scale"; - scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); - } - - if (loc_curves.size() != joint_order.size() * 3) { - std::cout << "PROGRAMMER ERROR: location curve count mismatch\n"; - return; - } - - if (rot_curves.size() != joint_order.size() * 4) { - std::cout << "PROGRAMMER ERROR: rotation curve count mismatch\n"; - return; - } - - if (scale_curves.size() != joint_order.size() * 3) { - std::cout << "PROGRAMMER ERROR: scale curve count mismatch\n"; - return; - } - - /* Joint bind transforms. */ - pxr::VtMatrix4dArray bind_xforms; - if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - std::cout << "WARNING: couldn't get world bind transforms for skeleton " - << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; - return; - } - - if (bind_xforms.size() != joint_order.size()) { - std::cout << "WARNING: number of bind transforms doesn't match the number of joints\n"; - return; - } - - const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); - - /* This will store the inverse of the parent-relative bind xforms. */ - pxr::VtMatrix4dArray inv_bind_xforms(bind_xforms.size()); - - for (int i = 0; i < bind_xforms.size(); ++i) { - int parent_id = skel_topology.GetParent(i); - - if (parent_id >= 0) { - /* This is a non-root bone. Compute the transform of the joint - * relative to its parent. */ - pxr::GfMatrix4d parent_relative_xf = bind_xforms[i] * bind_xforms[parent_id].GetInverse(); - inv_bind_xforms[i] = parent_relative_xf.GetInverse(); - } else { - inv_bind_xforms[i] = bind_xforms[i].GetInverse(); - } - } - - for (double frame : samples) { - pxr::VtMatrix4dArray joint_local_xforms; - - if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) { - std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame << std::endl; - continue; - } - - if (joint_local_xforms.size() != joint_order.size()) { - std::cout << "WARNING: number of joint local transform entries " << joint_local_xforms.size() - << " doesn't match the number of joints " << joint_order.size() << std::endl; - continue; - } - - for (int i = 0; i < joint_local_xforms.size(); ++i) { - - pxr::GfMatrix4d bind_relative_xf = joint_local_xforms[i] * inv_bind_xforms[i]; - - pxr::GfVec3f t; - pxr::GfQuatf qrot; - pxr::GfVec3h s; - - if (!pxr::UsdSkelDecomposeTransform(bind_relative_xf, &t, &qrot, &s)) { - std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl; - continue; - } - - float re = qrot.GetReal(); - pxr::GfVec3f im = qrot.GetImaginary(); - - for (int j = 0; j < 3; ++j) { - int k = 3 * i + j; - if (k >= loc_curves.size()) { - std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl; - break; - } - if (FCurve *fcu = loc_curves[k]) { - add_bezt(fcu, frame, t[j]); - } - } - - for (int j = 0; j < 4; ++j) { - int k = 4 * i + j; - if (k >= rot_curves.size()) { - std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl; - break; - } - if (FCurve *fcu = rot_curves[k]) { - if (j == 0) { - add_bezt(fcu, frame, re); - } - else { - add_bezt(fcu, frame, im[j - 1]); - } - } - } - - for (int j = 0; j < 3; ++j) { - int k = 3 * i + j; - if (k >= scale_curves.size()) { - std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl; - break; - } - if (FCurve *fcu = scale_curves[k]) { - add_bezt(fcu, frame, s[j]); - } - } - } - } -} void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) { diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index 742edef95f9..b7725c656e4 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -58,12 +58,6 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, bool import_anim=true); - -void create_skeleton_curves(Main *bmain, - Object *arm_obj, - const pxr::UsdSkelSkeletonQuery &skel_query, - const std::map &joint_to_bone_map); - /** * Import skinning data from a source USD prim as deform groups and an armature * modifier on the given mesh object. If the USD prim does not have a skeleton -- 2.30.2 From f445fef389364fe9aba7366f81e037248c0a944f Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 00:26:20 -0400 Subject: [PATCH 08/30] USD: fixed bug in bone animation import. --- .../blender/io/usd/intern/usd_skel_convert.cc | 69 +++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 26fcc274a0d..17c32e57aff 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -196,34 +196,77 @@ void import_skeleton_curves(Main *bmain, return; } - /* Set the curve samples. */ + /* The curve for each joint represents the transform relative + * to the bind transform in joint-local space. I.e., + * + * jointLocalTransform * inv(jointLocalBindTransform) + * + * There doesn't appear to be a way to query the joint-local + * bind transform through the API, so we have to compute it + * ourselves from the world bind transforms and the skeleton + * topology. + */ + /* Get the world space joint transforms at bind time. */ + pxr::VtMatrix4dArray bind_xforms; + if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { + WM_reportf(RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + return; + } + + if (bind_xforms.size() != joint_order.size()) { + WM_reportf(RPT_WARNING, + "%s: Number of bind transforms doesn't match the number of joints for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + return; + } + + const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology(); + + pxr::VtMatrix4dArray joint_local_bind_xforms(bind_xforms.size()); + for (int i = 0; i < bind_xforms.size(); ++i) { + int parent_id = skel_topology.GetParent(i); + + if (parent_id >= 0) { + /* This is a non-root joint. Compute the bind transform of the joint + * relative to its parent. */ + joint_local_bind_xforms[i] = bind_xforms[i] * bind_xforms[parent_id].GetInverse(); + } + else { + /* This is the root joint. */ + joint_local_bind_xforms[i] = bind_xforms[i]; + } + } + + /* Set the curve samples. */ for (double frame : samples) { - /* Compute joint transforms which, when concatenated against the rest pose, - * produce joint transforms in joint-local space. More specifically, this - * computes restRelativeTransform in: - * restRelativeTransform * restTransform = jointLocalTransform - */ - pxr::VtMatrix4dArray rest_relative_xforms; - if (!skel_query.ComputeJointRestRelativeTransforms(&rest_relative_xforms, frame)) { - std::cout << "WARNING: couldn't compute joint rest relative transforms on frame " << frame + + pxr::VtMatrix4dArray joint_local_xforms; + if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) { + std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame << std::endl; continue; } - if (rest_relative_xforms.size() != joint_order.size()) { - std::cout << "WARNING: number of joint rest relative transform entries " << rest_relative_xforms.size() + if (joint_local_xforms.size() != joint_order.size()) { + std::cout << "WARNING: number of joint local transform entries " << joint_local_xforms.size() << " doesn't match the number of joints " << joint_order.size() << std::endl; continue; } - for (int i = 0; i < rest_relative_xforms.size(); ++i) { + for (int i = 0; i < joint_local_xforms.size(); ++i) { + + pxr::GfMatrix4d bone_xform = joint_local_xforms[i] * joint_local_bind_xforms[i].GetInverse(); pxr::GfVec3f t; pxr::GfQuatf qrot; pxr::GfVec3h s; - if (!pxr::UsdSkelDecomposeTransform(rest_relative_xforms[i], &t, &qrot, &s)) { + if (!pxr::UsdSkelDecomposeTransform(bone_xform, &t, &qrot, &s)) { std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl; continue; } -- 2.30.2 From e89681955ecfc092d718248c24346fede25ac359 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 00:37:40 -0400 Subject: [PATCH 09/30] USD: cleanup skel import. Added comments, reporting warnings and reorganized code to make it easier to follow. --- .../blender/io/usd/intern/usd_skel_convert.cc | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 17c32e57aff..b270a65453a 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -610,7 +610,10 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); if (!skel_query.IsValid()) { - std::cout << "WARNING: couldn't query skeleton " << skel.GetPath() << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't query skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -619,20 +622,24 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); if (joint_order.size() != skel_topology.size()) { - std::cout << "WARNING: skel topology and joint order size mismatch\n"; + WM_reportf(RPT_WARNING, + "%s: Topology and joint order size mismatch for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } bArmature *arm = static_cast(arm_obj->data); + /* Set the armature to edit mode when creating the bones. */ ED_armature_to_edit(arm); /* The bones we create, stored in the skeleton's joint order. */ std::vector edit_bones; - size_t num_joints = skel_topology.GetNumJoints(); - - /* Keep track of the bones we create for each joint. */ + /* Keep track of the bones we create for each joint. + * We'll need this when creating animation curves + * later. */ std::map joint_to_bone_map; /* Create the bones. */ @@ -640,7 +647,10 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s std::string name = pxr::SdfPath(joint).GetName(); EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); if (!bone) { - std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't add bone for joint %s", + __func__, + joint.GetString().c_str()); edit_bones.push_back(nullptr); continue; } @@ -649,45 +659,30 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s } /* Sanity check: we should have created a bone for each joint. */ - + size_t num_joints = skel_topology.GetNumJoints(); if (edit_bones.size() != num_joints) { - std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel.GetPath() - << std::endl; + WM_reportf(RPT_WARNING, + "%s: Mismatch in bone and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } - /* Record the child bone indices per parent bone. */ - std::vector> child_bones(num_joints); - - /* Set bone parenting. */ - for (size_t i = 0; i < num_joints; ++i) { - int parent_idx = skel_topology.GetParent(i); - if (parent_idx < 0) { - continue; - } - if (parent_idx >= edit_bones.size()) { - std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i]) - << " for skeleton " << skel.GetPath() << std::endl; - continue; - } - - child_bones[parent_idx].push_back(i); - if (edit_bones[i] && edit_bones[parent_idx]) { - edit_bones[i]->parent = edit_bones[parent_idx]; - } - } - - /* Joint bind transforms. */ + /* Get the world space joint transforms at bind time. */ pxr::VtMatrix4dArray bind_xforms; if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - std::cout << "WARNING: couldn't get world bind transforms for skeleton " - << skel_query.GetSkeleton().GetPrim().GetPath() << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } if (bind_xforms.size() != num_joints) { - std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " - << skel.GetPath() << std::endl; + WM_reportf(RPT_WARNING, + "%s: Mismatch in bind xforms and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -745,6 +740,27 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s * overall skeleton hierarchy. USD skeletons are composed of * joints which we imperfectly represent as bones. */ + /* First, record the child bone indices per parent bone, + * to simplify accessing children when computing lengths. */ + std::vector> child_bones(num_joints); + + for (size_t i = 0; i < num_joints; ++i) { + int parent_idx = skel_topology.GetParent(i); + if (parent_idx < 0) { + continue; + } + if (parent_idx >= edit_bones.size()) { + std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i]) + << " for skeleton " << skel.GetPath() << std::endl; + continue; + } + + child_bones[parent_idx].push_back(i); + if (edit_bones[i] && edit_bones[parent_idx]) { + edit_bones[i]->parent = edit_bones[parent_idx]; + } + } + float avg_len_scale = 0; for (size_t i = 0; i < num_joints; ++i) { @@ -805,6 +821,7 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s } } + /* Get out of edit mode. */ ED_armature_from_edit(bmain, arm); ED_armature_edit_free(arm); -- 2.30.2 From da0aaf6023decba3b9562dfa0cc5d3957b1fefd5 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 19:12:29 -0400 Subject: [PATCH 10/30] Fixed compile errors. --- source/blender/io/usd/intern/usd_reader_skeleton.cc | 2 +- source/blender/io/usd/intern/usd_skel_convert.cc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index 0db8b9bbbed..08d0c911c9e 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -16,7 +16,7 @@ #include "MEM_guardedalloc.h" -#include "WM_api.h" +#include "WM_api.hh" #include diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index b270a65453a..0c0343562cd 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -28,18 +28,18 @@ #include "BKE_key.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" +#include "BKE_mesh_runtime.hh" #include "BKE_modifier.h" #include "BKE_object.h" #include "BKE_object_deform.h" #include "BLI_math_vector.h" -#include "ED_armature.h" -#include "ED_keyframing.h" -#include "ED_mesh.h" +#include "ED_armature.hh" +#include "ED_keyframing.hh" +#include "ED_mesh.hh" -#include "WM_api.h" +#include "WM_api.hh" #include #include -- 2.30.2 From 6a56a8547f7153cb474b81bc410b7c5d21cbb4d1 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 21:37:28 -0400 Subject: [PATCH 11/30] USD: Add comments for import_mesh_skel_bindings. Also reporting warnings rather than printing to std out. --- .../blender/io/usd/intern/usd_skel_convert.cc | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 0c0343562cd..f02a6e5c5ab 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -834,7 +834,7 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) { - if (!(bmain && mesh_obj && prim)) { + if (!(bmain && mesh_obj && mesh_obj->type == OB_MESH && prim)) { return; } @@ -844,10 +844,6 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim return; } - if (mesh_obj->type != OB_MESH) { - return; - } - pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim); if (!skel_api) { @@ -860,6 +856,8 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim return; } + /* Get the joint identifiers from the skeleton. We will + * need these to construct deform groups. */ pxr::VtArray joints; if (skel_api.GetJointsAttr().HasAuthoredValue()) { @@ -873,28 +871,34 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim return; } + /* Get the joint indices, which specify which joints influence a given point. */ pxr::UsdGeomPrimvar joint_indices_primvar = skel_api.GetJointIndicesPrimvar(); - if (!(joint_indices_primvar && joint_indices_primvar.HasAuthoredValue())) { return; } + /* Get the weights, which specify the weight of a joint on a given point. */ pxr::UsdGeomPrimvar joint_weights_primvar = skel_api.GetJointWeightsPrimvar(); - if (!(joint_weights_primvar && joint_weights_primvar.HasAuthoredValue())) { return; } + /* Element size specifies the number of joints that might influece a given point. + * This is the stride we take when accessing the indices and weights for a + * given point. */ int joint_indices_elem_size = joint_indices_primvar.GetElementSize(); int joint_weights_elem_size = joint_weights_primvar.GetElementSize(); + /* We expect the element counts to match. */ if (joint_indices_elem_size != joint_weights_elem_size) { - std::cout << "WARNING: joint weights and joint indices element size mismatch." << std::endl; + WM_reportf(RPT_WARNING, + "%s: Joint weights and joint indices element size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } - /* The set of unique joint indices referenced in the joint indices - * attribute. */ + /* Get the joint indices and weights. */ pxr::VtIntArray joint_indices; joint_indices_primvar.ComputeFlattened(&joint_indices); @@ -906,7 +910,10 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } if (joint_indices.size() != joint_weights.size()) { - std::cout << "WARNING: joint weights and joint indices size mismatch." << std::endl; + WM_reportf(RPT_WARNING, + "%s: Joint weights and joint indices size mismatch size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -914,29 +921,40 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim pxr::TfToken interp = joint_weights_primvar.GetInterpolation(); + /* Sanity check: we expect only vertex or constant interpolation. */ if (interp != pxr::UsdGeomTokens->vertex && interp != pxr::UsdGeomTokens->constant) { - std::cout << "WARNING: unexpected joint weights interpolation type " << interp - << std::endl; + WM_reportf(RPT_WARNING, + "%s: Unexpected joint weights interpolation type %s for prim %s", + __func__, + interp.GetString().c_str(), + prim.GetPath().GetAsString().c_str()); return; } + /* Sanity check: make sure we have the expected number of values for the interpolation type. */ if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) { - std::cout << "WARNING: joint weights of unexpected size for vertex interpolation." << std::endl; + WM_reportf(RPT_WARNING, + "%s: Joint weights of unexpected size for vertex interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) { - std::cout << "WARNING: joint weights of unexpected size for constant interpolation." - << std::endl; + WM_reportf(RPT_WARNING, + "%s: Joint weights of unexpected size for constant interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } + /* Determine which joint indices are used for skinning this prim. */ std::vector used_indices; for (int index : joint_indices) { if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) { /* We haven't accounted for this index yet. */ if (index < 0 || index >= joints.size()) { - std::cout << "Out of bound joint index " << index << std::endl; + std::cerr << "Out of bound joint index " << index << std::endl; continue; } used_indices.push_back(index); @@ -948,6 +966,10 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } if (BKE_object_defgroup_data_create(static_cast(mesh_obj->data)) == NULL) { + WM_reportf(RPT_WARNING, + "%s: Error creating deform group data for mesh %s", + __func__, + mesh_obj->id.name + 2); return; } @@ -957,6 +979,7 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim BLI_addtail(&mesh_obj->modifiers, md); } + /* Create a deform group per joint. */ std::vector joint_def_grps(joints.size(), nullptr); for (int idx : used_indices) { @@ -967,6 +990,7 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } } + /* Set the deform group verts and weights. */ for (int i = 0; i < mesh->totvert; ++i) { /* Offset into the weights array, which is * always 0 for constant interpolation. */ -- 2.30.2 From 0bb38c39d010c4664522a9e16e1185696ac55e24 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 22:23:21 -0400 Subject: [PATCH 12/30] USD: comments for process_armature_modifiers. Also reporting warnings. --- .../blender/io/usd/intern/usd_reader_stage.cc | 24 ++++++++++++------- .../blender/io/usd/intern/usd_reader_stage.h | 3 +++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index b876ee11847..562111883da 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -47,6 +47,8 @@ #include "DNA_material_types.h" +#include "WM_api.hh" + namespace blender::io::usd { USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, @@ -333,7 +335,9 @@ void USDStageReader::collect_readers(Main *bmain) void USDStageReader::process_armature_modifiers() const { - /* Create armature object map. */ + /* Iteratate over the skeleton readers to create the + * armature object map, which maps a USD skeleton prim + * path to the corresponding armature object. */ std::map usd_path_to_armature; for (const USDPrimReader *reader : readers_) { if (dynamic_cast(reader) && reader->object()) { @@ -341,27 +345,29 @@ void USDStageReader::process_armature_modifiers() const } } - /* Set armature objects on armature modifiers. */ + /* Iterate over the mesh readers and set armature objects on armature modifiers. */ for (const USDPrimReader *reader : readers_) { if (!reader->object()) { - /* This should never happen. */ continue; } if (const USDMeshReader *mesh_reader = dynamic_cast(reader)) { + /* Check if the mesh object has an armature modifier. */ ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature); if (!md) { continue; } ArmatureModifierData *amd = reinterpret_cast(md); + + /* Assign the armature based on the bound USD skeleton path of the skinned mesh. */ std::string skel_path = mesh_reader->get_skeleton_path(); std::map::const_iterator it = usd_path_to_armature.find(skel_path); - if (it != usd_path_to_armature.end()) { - amd->object = it->second; - } - else { - std::cout << "WARNING: couldn't find armature object for armature modifier for USD prim " - << reader->prim_path() << " bound to skeleton " << skel_path << std::endl; + if (it == usd_path_to_armature.end()) { + WM_reportf(RPT_WARNING, + "%s: Couldn't find armature object corresponding to USD skeleton %s", + __func__, + skel_path.c_str()); } + amd->object = it->second; } } } diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 06150072ff3..332f4dab727 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -45,6 +45,9 @@ class USDStageReader { void collect_readers(struct Main *bmain); + /* Complete setting up the armature modifiers that + * were created for skinned meshes by setting the + * modifier object on the corresponding modifier. */ void process_armature_modifiers() const; /* Convert every material prim on the stage to a Blender -- 2.30.2 From 85413cfdd94bce7d48e0e9942aafdf16b670222d Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 22:58:16 -0400 Subject: [PATCH 13/30] Added comments. --- source/blender/io/usd/intern/usd_reader_xform.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h index 0b3f8cb9606..769c6a32734 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.h +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -51,6 +51,14 @@ class USDXformReader : public USDPrimReader { /* Returns true if the contained USD prim is the root of a transform hierarchy. */ bool is_root_xform_prim() const; + /** + * Return the USD prim's local transformation. + * + * \param time: Time code for evaluating the transform. + * \param r_xform: The transform matrix return value + * \param r_is_constant: Return value flag, set to false if the transform is + * time varying + */ virtual bool get_local_usd_xform(const float time, pxr::GfMatrix4d *r_xform, bool *r_is_constant) const; -- 2.30.2 From 4dddd6a96d46b7931c27dc954959a2dafa686b8e Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 23:15:16 -0400 Subject: [PATCH 14/30] Format fixes. --- .../io/usd/intern/usd_reader_skeleton.cc | 2 +- .../blender/io/usd/intern/usd_reader_xform.cc | 1 - .../blender/io/usd/intern/usd_skel_convert.cc | 61 +++++++++---------- .../blender/io/usd/intern/usd_skel_convert.h | 6 +- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index 08d0c911c9e..e19c08a6290 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -5,8 +5,8 @@ #include "usd_reader_skeleton.h" #include "usd_skel_convert.h" -#include "BKE_idprop.h" #include "BKE_armature.h" +#include "BKE_idprop.h" #include "BKE_object.h" #include "BLI_math.h" diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc index 2f2400dc238..15dc06daced 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.cc +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -180,5 +180,4 @@ bool USDXformReader::get_local_usd_xform(const float time, return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time); } - } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index f02a6e5c5ab..b7e0b38d954 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -7,8 +7,8 @@ #include "usd.h" #include -#include #include +#include #include #include #include @@ -58,11 +58,8 @@ FCurve *create_fcurve(int array_index, const char *rna_path) /* Utility: create curve at the given array index and * adds it as a channel to a group. */ -FCurve *create_chan_fcurve(bAction *act, - bActionGroup *grp, - int array_index, - const char *rna_path, - int totvert) +FCurve *create_chan_fcurve( + bAction *act, bActionGroup *grp, int array_index, const char *rna_path, int totvert) { FCurve *fcu = create_fcurve(array_index, rna_path); fcu->totvert = totvert; @@ -71,10 +68,7 @@ FCurve *create_chan_fcurve(bAction *act, } /* Utility: add curve sample. */ -void add_bezt(FCurve *fcu, - float frame, - float value, - eBezTriple_Interpolation ipo = BEZT_IPO_LIN) +void add_bezt(FCurve *fcu, float frame, float value, eBezTriple_Interpolation ipo = BEZT_IPO_LIN) { BezTriple bez; memset(&bez, 0, sizeof(BezTriple)); @@ -319,7 +313,10 @@ void import_skeleton_curves(Main *bmain, namespace blender::io::usd { -void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, const bool import_anim) +void import_blendshapes(Main *bmain, + Object *mesh_obj, + const pxr::UsdPrim &prim, + const bool import_anim) { if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) { return; @@ -350,7 +347,8 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { WM_reportf(RPT_WARNING, "%s: Couldn't get blendshape targets for prim %s", - __func__, prim.GetPath().GetAsString().c_str()); + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -433,10 +431,8 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, } if (offsets.empty()) { - WM_reportf(RPT_WARNING, - "%s: No offsets for blend shape %s", - __func__, - path.GetAsString().c_str()); + WM_reportf( + RPT_WARNING, "%s: No offsets for blend shape %s", __func__, path.GetAsString().c_str()); continue; } @@ -460,10 +456,11 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, * offset to the key block point. */ for (int a = 0; a < kb->totelem; ++a, fp += 3) { if (a >= offsets.size()) { - WM_reportf(RPT_WARNING, - "%s: Number of offsets greater than number of mesh vertices for blend shape %s", - __func__, - path.GetAsString().c_str()); + WM_reportf( + RPT_WARNING, + "%s: Number of offsets greater than number of mesh vertices for blend shape %s", + __func__, + path.GetAsString().c_str()); break; } add_v3_v3(fp, offsets[a].data()); @@ -475,7 +472,8 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, int a = 0; for (int i : point_indices) { if (i < 0 || i > kb->totelem) { - std::cerr << "Out of bounds point index " << i << " for blendshape " << path << std::endl; + std::cerr << "Out of bounds point index " << i << " for blendshape " << path + << std::endl; ++a; continue; } @@ -586,7 +584,8 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, } if (weights.size() != curves.size()) { - std::cerr << "Programmer error: number of weight samples doesn't match number of shapekey curve entries for frame " + std::cerr << "Programmer error: number of weight samples doesn't match number of shapekey " + "curve entries for frame " << frame << std::endl; continue; } @@ -597,10 +596,12 @@ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, } } } - } -void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, const bool import_anim) +void import_skeleton(Main *bmain, + Object *arm_obj, + const pxr::UsdSkelSkeleton &skel, + const bool import_anim) { if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) { return; @@ -647,10 +648,8 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s std::string name = pxr::SdfPath(joint).GetName(); EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); if (!bone) { - WM_reportf(RPT_WARNING, - "%s: Couldn't add bone for joint %s", - __func__, - joint.GetString().c_str()); + WM_reportf( + RPT_WARNING, "%s: Couldn't add bone for joint %s", __func__, joint.GetString().c_str()); edit_bones.push_back(nullptr); continue; } @@ -830,8 +829,6 @@ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &s } } - - void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) { if (!(bmain && mesh_obj && mesh_obj->type == OB_MESH && prim)) { @@ -932,7 +929,9 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim } /* Sanity check: make sure we have the expected number of values for the interpolation type. */ - if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) { + if (interp == pxr::UsdGeomTokens->vertex && + joint_weights.size() != mesh->totvert * joint_weights_elem_size) + { WM_reportf(RPT_WARNING, "%s: Joint weights of unexpected size for vertex interpolation for prim %s", __func__, diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index b7725c656e4..984c3aa6134 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -3,9 +3,9 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include #include #include -#include struct Main; struct Object; @@ -41,7 +41,7 @@ struct ImportSettings; void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, - bool import_anim=true); + bool import_anim = true); /** * Import the given USD sekeleton as an armature object. Optionally, if the @@ -57,7 +57,7 @@ void import_blendshapes(Main *bmain, void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, - bool import_anim=true); + bool import_anim = true); /** * Import skinning data from a source USD prim as deform groups and an armature * modifier on the given mesh object. If the USD prim does not have a skeleton -- 2.30.2 From bdb808a9b839412cb5e86af75f9ff402c7f1a627 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Tue, 8 Aug 2023 15:05:37 -0400 Subject: [PATCH 15/30] USD skel import: review fixes. Fixes based on review by Bastien. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 8 +-- .../blender/io/usd/intern/usd_skel_convert.cc | 53 ++++++++++--------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 81014ca4cb4..a6fc4443802 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -1023,7 +1023,7 @@ bool USDMeshReader::get_local_usd_xform(const float time, if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) { /* Use the standard transform computation, since we are ignoring - * skinning data. Note that applying theUsdSkelBindingAPI to an + * skinning data. Note that applying the UsdSkelBinding API to an * instance proxy generates a USD error. */ return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); } @@ -1041,8 +1041,10 @@ bool USDMeshReader::get_local_usd_xform(const float time, return true; } else { - std::cout << "WARNING: couldn't compute geom bind transform for " << prim_.GetPath() - << std::endl; + WM_reportf(RPT_WARNING, + "%s: Couldn't compute geom bind transform for %s", + __func__, + prim_.GetPath().GetAsString().c_str()); } } } diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index b7e0b38d954..a35627710fe 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -47,11 +47,11 @@ namespace { /* Utility: create curve at the given array index. */ -FCurve *create_fcurve(int array_index, const char *rna_path) +FCurve *create_fcurve(int array_index, const std::string &rna_path) { FCurve *fcu = BKE_fcurve_create(); fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED); - fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path)); + fcu->rna_path = BLI_strdup(rna_path.c_str()); fcu->array_index = array_index; return fcu; } @@ -59,7 +59,7 @@ FCurve *create_fcurve(int array_index, const char *rna_path) /* Utility: create curve at the given array index and * adds it as a channel to a group. */ FCurve *create_chan_fcurve( - bAction *act, bActionGroup *grp, int array_index, const char *rna_path, int totvert) + bAction *act, bActionGroup *grp, int array_index, const std::string &rna_path, int totvert) { FCurve *fcu = create_fcurve(array_index, rna_path); fcu->totvert = totvert; @@ -78,7 +78,6 @@ void add_bezt(FCurve *fcu, float frame, float value, eBezTriple_Interpolation ip bez.f1 = bez.f2 = bez.f3 = SELECT; bez.h1 = bez.h2 = HD_AUTO; insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); - BKE_fcurve_handles_recalc(fcu); } /** @@ -156,22 +155,22 @@ void import_skeleton_curves(Main *bmain, /* Add translation curves. */ std::string rna_path = "pose.bones[\"" + it->second + "\"].location"; - loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path, num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path, num_samples)); + loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path, num_samples)); /* Add rotation curves. */ rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion"; - rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); - rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path, num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path, num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path, num_samples)); + rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path, num_samples)); /* Add scale curves. */ rna_path = "pose.bones[\"" + it->second + "\"].scale"; - scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples)); - scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples)); - scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path, num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path, num_samples)); + scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path, num_samples)); } /* Sanity checks: make sure we have a curve entry for each joint. */ @@ -237,8 +236,7 @@ void import_skeleton_curves(Main *bmain, } /* Set the curve samples. */ - for (double frame : samples) { - + for (const double frame : samples) { pxr::VtMatrix4dArray joint_local_xforms; if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) { std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame @@ -253,7 +251,6 @@ void import_skeleton_curves(Main *bmain, } for (int i = 0; i < joint_local_xforms.size(); ++i) { - pxr::GfMatrix4d bone_xform = joint_local_xforms[i] * joint_local_bind_xforms[i].GetInverse(); pxr::GfVec3f t; @@ -307,6 +304,12 @@ void import_skeleton_curves(Main *bmain, } } } + + /* Recalculate curve handles. */ + auto recalc_handles = [](FCurve *fcu) { BKE_fcurve_handles_recalc(fcu); }; + std::for_each(loc_curves.begin(), loc_curves.end(), recalc_handles); + std::for_each(rot_curves.begin(), rot_curves.end(), recalc_handles); + std::for_each(scale_curves.begin(), scale_curves.end(), recalc_handles); } } // End anonymous namespace. @@ -406,7 +409,6 @@ void import_blendshapes(Main *bmain, std::set shapekey_names; for (int i = 0; i < targets.size(); ++i) { - /* Get USD path to blend shape. */ const pxr::SdfPath &path = targets[i]; pxr::UsdSkelBlendShape blendshape(stage->GetPrimAtPath(path)); @@ -559,7 +561,6 @@ void import_blendshapes(Main *bmain, std::vector curves; for (auto blendshape_name : blendshapes) { - if (shapekey_names.find(blendshape_name) == shapekey_names.end()) { /* We didn't create a shapekey fo this blendshape, so we don't * create a curve and insert a null placeholder in the curve array. */ @@ -569,7 +570,7 @@ void import_blendshapes(Main *bmain, /* Create the curve for this shape key. */ std::string rna_path = "key_blocks[\"" + blendshape_name.GetString() + "\"].value"; - FCurve *fcu = create_fcurve(0, rna_path.c_str()); + FCurve *fcu = create_fcurve(0, rna_path); fcu->totvert = num_samples; curves.push_back(fcu); BLI_addtail(&act->curves, fcu); @@ -596,6 +597,10 @@ void import_blendshapes(Main *bmain, } } } + + /* Recalculate curve handles. */ + auto recalc_handles = [](FCurve *fcu) { BKE_fcurve_handles_recalc(fcu); }; + std::for_each(curves.begin(), curves.end(), recalc_handles); } void import_skeleton(Main *bmain, @@ -685,13 +690,13 @@ void import_skeleton(Main *bmain, return; } - /* Check if any bone natrices have negative determinants, + /* Check if any bone matrices have negative determinants, * indicating negative scales, possibly due to mirroring * operations. Such matrices can't be propery converted * to Blender's axis/roll bone representation (see - * https://developer.blender.org/T82930). If we detect - * such matrices, we will flag an error and won't try - * to import the animation, since the rotations would + * https://projects.blender.org/blender/blender/issues/82930). + * If we detect such matrices, we will flag an error and won't + * try to import the animation, since the rotations would * be incorrect in such cases. Unfortunately, the Pixar * UsdSkel examples of the "HumanFemale" suffer from * this issue. */ -- 2.30.2 From 7c9e6ee6782bde8b238e16a08be474e6a3b5e3bd Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Tue, 8 Aug 2023 15:15:24 -0400 Subject: [PATCH 16/30] USD skel import: cleanup using const. --- source/blender/io/usd/intern/usd_skel_convert.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index a35627710fe..fced8da16d7 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -47,7 +47,7 @@ namespace { /* Utility: create curve at the given array index. */ -FCurve *create_fcurve(int array_index, const std::string &rna_path) +FCurve *create_fcurve(const int array_index, const std::string &rna_path) { FCurve *fcu = BKE_fcurve_create(); fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED); @@ -59,7 +59,7 @@ FCurve *create_fcurve(int array_index, const std::string &rna_path) /* Utility: create curve at the given array index and * adds it as a channel to a group. */ FCurve *create_chan_fcurve( - bAction *act, bActionGroup *grp, int array_index, const std::string &rna_path, int totvert) + bAction *act, bActionGroup *grp, const int array_index, const std::string &rna_path, const int totvert) { FCurve *fcu = create_fcurve(array_index, rna_path); fcu->totvert = totvert; @@ -68,7 +68,7 @@ FCurve *create_chan_fcurve( } /* Utility: add curve sample. */ -void add_bezt(FCurve *fcu, float frame, float value, eBezTriple_Interpolation ipo = BEZT_IPO_LIN) +void add_bezt(FCurve *fcu, const float frame, const float value, const eBezTriple_Interpolation ipo = BEZT_IPO_LIN) { BezTriple bez; memset(&bez, 0, sizeof(BezTriple)); @@ -117,7 +117,7 @@ void import_skeleton_curves(Main *bmain, return; } - size_t num_samples = samples.size(); + const size_t num_samples = samples.size(); /* Create the action on the armature. */ bAction *act = ED_id_action_ensure(bmain, (ID *)&arm_obj->id); -- 2.30.2 From 7db97b831ff8105ab651153665f03540475ffa64 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Tue, 8 Aug 2023 15:35:30 -0400 Subject: [PATCH 17/30] USD: format fixes. --- source/blender/io/usd/intern/usd_skel_convert.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index fced8da16d7..e1b21bd2bc3 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -58,8 +58,11 @@ FCurve *create_fcurve(const int array_index, const std::string &rna_path) /* Utility: create curve at the given array index and * adds it as a channel to a group. */ -FCurve *create_chan_fcurve( - bAction *act, bActionGroup *grp, const int array_index, const std::string &rna_path, const int totvert) +FCurve *create_chan_fcurve(bAction *act, + bActionGroup *grp, + const int array_index, + const std::string &rna_path, + const int totvert) { FCurve *fcu = create_fcurve(array_index, rna_path); fcu->totvert = totvert; @@ -68,7 +71,10 @@ FCurve *create_chan_fcurve( } /* Utility: add curve sample. */ -void add_bezt(FCurve *fcu, const float frame, const float value, const eBezTriple_Interpolation ipo = BEZT_IPO_LIN) +void add_bezt(FCurve *fcu, + const float frame, + const float value, + const eBezTriple_Interpolation ipo = BEZT_IPO_LIN) { BezTriple bez; memset(&bez, 0, sizeof(BezTriple)); -- 2.30.2 From e6a7c23bdda8aaf3f039efe1eac98fc4e0eb88e4 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Fri, 11 Aug 2023 09:44:08 -0400 Subject: [PATCH 18/30] USD: removed obsolete include. --- source/blender/io/usd/intern/usd_reader_skeleton.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index e19c08a6290..0fa74e8effd 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -9,8 +9,6 @@ #include "BKE_idprop.h" #include "BKE_object.h" -#include "BLI_math.h" - #include "DNA_armature_types.h" #include "DNA_object_types.h" -- 2.30.2 From a7f52e2e8ed8a27da640153921367304e6bdeed9 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sat, 12 Aug 2023 18:25:07 -0400 Subject: [PATCH 19/30] USD import: added comment for get_skeleton_path. --- source/blender/io/usd/intern/usd_reader_mesh.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index 353d65f26bb..3802f7c438a 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -57,6 +57,14 @@ class USDMeshReader : public USDGeomReader { bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override; + /** + * If the USD mesh prim has a valid UsdSkel schema defined, return the USD path + * string to the bound skeleton, if any. Returns the empty string if no skeleton + * binding is defined. + * + * The returned path is currently used to match armature modifiers with armature + * objects during import. + */ std::string get_skeleton_path() const; private: -- 2.30.2 From a26a473ed7c77d533c07e5d00d524328b6bf6ae6 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 13 Aug 2023 20:07:57 -0400 Subject: [PATCH 20/30] USD Skel import: addressed review comments. Fixes as suggested by Nathan Vegdahl in his review, including making const where possible and more robust epsilon checks. Additional minor cleanup. --- .../blender/io/usd/intern/usd_skel_convert.cc | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index e1b21bd2bc3..b8e4697e536 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -46,6 +46,13 @@ namespace { +/* Utility: return the magnitude of the largest component + * of the given vector. */ +inline float max_mag_component(const pxr::GfVec3d &vec) +{ + return pxr::GfMax(pxr::GfAbs(vec[0]), pxr::GfAbs(vec[1]), pxr::GfAbs(vec[2])); +} + /* Utility: create curve at the given array index. */ FCurve *create_fcurve(const int array_index, const std::string &rna_path) { @@ -57,7 +64,7 @@ FCurve *create_fcurve(const int array_index, const std::string &rna_path) } /* Utility: create curve at the given array index and - * adds it as a channel to a group. */ + * add it as a channel to a group. */ FCurve *create_chan_fcurve(bAction *act, bActionGroup *grp, const int array_index, @@ -228,7 +235,7 @@ void import_skeleton_curves(Main *bmain, pxr::VtMatrix4dArray joint_local_bind_xforms(bind_xforms.size()); for (int i = 0; i < bind_xforms.size(); ++i) { - int parent_id = skel_topology.GetParent(i); + const int parent_id = skel_topology.GetParent(i); if (parent_id >= 0) { /* This is a non-root joint. Compute the bind transform of the joint @@ -268,11 +275,11 @@ void import_skeleton_curves(Main *bmain, continue; } - float re = qrot.GetReal(); - pxr::GfVec3f im = qrot.GetImaginary(); + const float re = qrot.GetReal(); + const pxr::GfVec3f &im = qrot.GetImaginary(); for (int j = 0; j < 3; ++j) { - int k = 3 * i + j; + const int k = 3 * i + j; if (k >= loc_curves.size()) { std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl; break; @@ -283,7 +290,7 @@ void import_skeleton_curves(Main *bmain, } for (int j = 0; j < 4; ++j) { - int k = 4 * i + j; + const int k = 4 * i + j; if (k >= rot_curves.size()) { std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl; break; @@ -299,7 +306,7 @@ void import_skeleton_curves(Main *bmain, } for (int j = 0; j < 3; ++j) { - int k = 3 * i + j; + const int k = 3 * i + j; if (k >= scale_curves.size()) { std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl; break; @@ -560,7 +567,7 @@ void import_blendshapes(Main *bmain, return; } - size_t num_samples = times.size(); + const size_t num_samples = times.size(); /* Create the animation and curves. */ bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id); @@ -669,7 +676,7 @@ void import_skeleton(Main *bmain, } /* Sanity check: we should have created a bone for each joint. */ - size_t num_joints = skel_topology.GetNumJoints(); + const size_t num_joints = skel_topology.GetNumJoints(); if (edit_bones.size() != num_joints) { WM_reportf(RPT_WARNING, "%s: Mismatch in bone and joint counts for skeleton %s", @@ -755,7 +762,7 @@ void import_skeleton(Main *bmain, std::vector> child_bones(num_joints); for (size_t i = 0; i < num_joints; ++i) { - int parent_idx = skel_topology.GetParent(i); + const int parent_idx = skel_topology.GetParent(i); if (parent_idx < 0) { continue; } @@ -802,10 +809,10 @@ void import_skeleton(Main *bmain, pxr::GfVec3f parent_head(parent->head); pxr::GfVec3f parent_tail(parent->tail); - float new_len = (avg_child_head - parent_head).GetLength(); + const float new_len = (avg_child_head - parent_head).GetLength(); - /* Be sure not to scale by zero. */ - if (new_len > .00001) { + /* Check for epsilon relative to the parent head before scaling. */ + if (new_len > .00001 * max_mag_component(parent_head)) { parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len; copy_v3_v3(parent->tail, parent_tail.data()); avg_len_scale += new_len; @@ -815,16 +822,19 @@ void import_skeleton(Main *bmain, /* Scale terminal bones by the average length scale. */ avg_len_scale /= num_joints; - if (avg_len_scale > .00001) { - for (size_t i = 0; i < num_joints; ++i) { - if (!child_bones[i].empty()) { - continue; - } - EditBone *bone = edit_bones[i]; - if (!bone) { - continue; - } - pxr::GfVec3f head(bone->head); + for (size_t i = 0; i < num_joints; ++i) { + if (!child_bones[i].empty()) { + /* Not a terminal bone. */ + continue; + } + EditBone *bone = edit_bones[i]; + if (!bone) { + continue; + } + pxr::GfVec3f head(bone->head); + + /* Check for epsilon relative to the head before scaling. */ + if (avg_len_scale > .00001 * max_mag_component(head)) { pxr::GfVec3f tail(bone->tail); tail = head + (tail - head).GetNormalized() * avg_len_scale; copy_v3_v3(bone->tail, tail.data()); @@ -927,7 +937,7 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim Mesh *mesh = static_cast(mesh_obj->data); - pxr::TfToken interp = joint_weights_primvar.GetInterpolation(); + const pxr::TfToken interp = joint_weights_primvar.GetInterpolation(); /* Sanity check: we expect only vertex or constant interpolation. */ if (interp != pxr::UsdGeomTokens->vertex && interp != pxr::UsdGeomTokens->constant) { @@ -1009,15 +1019,14 @@ void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim offset = i * joint_weights_elem_size; } for (int j = 0; j < joint_weights_elem_size; ++j) { - int k = offset + j; - float w = joint_weights[k]; + const int k = offset + j; + const float w = joint_weights[k]; if (w < .00001) { /* No deform group if zero weight. */ continue; } - int joint_idx = joint_indices[k]; - bDeformGroup *def_grp = joint_def_grps[joint_idx]; - if (def_grp) { + const int joint_idx = joint_indices[k]; + if (bDeformGroup *def_grp = joint_def_grps[joint_idx]) { ED_vgroup_vert_add(mesh_obj, def_grp, i, w, WEIGHT_REPLACE); } } -- 2.30.2 From 3f8d06e474b7419a7f11f64d7ebad8b91a221444 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 13 Aug 2023 20:42:36 -0400 Subject: [PATCH 21/30] USD: edited comments. --- source/blender/io/usd/intern/usd_skel_convert.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index b8e4697e536..58acf2ceeb7 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -752,12 +752,13 @@ void import_skeleton(Main *bmain, skel.GetPath().GetAsString().c_str()); } - /* Scale bones to account for separation between parents and - * children, so that the bone size is in proportion with the - * overall skeleton hierarchy. USD skeletons are composed of - * joints which we imperfectly represent as bones. */ + /* Set bone parenting. In addition, scale bones to account + * for separation between parents and children, so that the + * bone size is in proportion with the overall skeleton hierarchy. + * USD skeletons are composed of joints which we imperfectly + * represent as bones. */ - /* First, record the child bone indices per parent bone, + /* This will record the child bone indices per parent bone, * to simplify accessing children when computing lengths. */ std::vector> child_bones(num_joints); -- 2.30.2 From cfaf4d288b536a35b32ab4050d6f000944ae3fc3 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 13 Aug 2023 20:47:40 -0400 Subject: [PATCH 22/30] USD: format fix. --- source/blender/io/usd/intern/usd_skel_convert.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index 58acf2ceeb7..d0cec5031c6 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -46,7 +46,7 @@ namespace { -/* Utility: return the magnitude of the largest component +/* Utility: return the magnitude of the largest component * of the given vector. */ inline float max_mag_component(const pxr::GfVec3d &vec) { -- 2.30.2 From c1f4b0dd3b6740764ef06944ad4f9a68d96ee06f Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Sun, 13 Aug 2023 21:23:34 -0400 Subject: [PATCH 23/30] USD skel import: fixed typo. --- source/blender/io/usd/intern/usd_skel_convert.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index 984c3aa6134..0cc4b1c3443 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -44,7 +44,7 @@ void import_blendshapes(Main *bmain, bool import_anim = true); /** - * Import the given USD sekeleton as an armature object. Optionally, if the + * Import the given USD skeleton as an armature object. Optionally, if the * skeleton has an animation defined, the time sampled joint transforms will be * imported as bone animation curves. * -- 2.30.2 From 30557a84f3116e6df6641d5c404ad24db820ff92 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Tue, 15 Aug 2023 20:51:24 -0400 Subject: [PATCH 24/30] USD skel import: new blend shape import test. --- tests/python/bl_usd_import_test.py | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index 8f2517ddd7b..08091bf7d39 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -13,6 +13,8 @@ from pxr import Sdf import bpy +from mathutils import Matrix, Vector, Quaternion, Euler + args = None @@ -302,6 +304,44 @@ class USDImportTest(AbstractUSDTest): self.assertEqual(4, num_uvmaps_found, "One or more test materials failed to import") + def test_import_usd_blend_shapes(self): + """Test importing USD blend shapes with animated weights.""" + + infile = str(self.testdir / "usd_blend_shape_test.usda") + res = bpy.ops.wm.usd_import(filepath=infile) + self.assertEqual({'FINISHED'}, res) + + obj = bpy.data.objects["Plane"] + + obj.active_shape_key_index = 1 + + key = obj.active_shape_key + self.assertEqual(key.name, "Key_1", "Unexpected shape key name") + + # Verify the number of shape key points. + self.assertEqual(len(key.data), 4, "Unexpected number of shape key point") + + # Verify shape key point coordinates + + # Reference point values. + refs = ((-2.51, -1.92, 0.20), (0.86, -1.46, -0.1), + (-1.33, 1.29, .84), (1.32, 2.20, -0.42)) + + for i in range(4): + co = key.data[i].co + ref = refs[i] + # Compare coordinates. + for j in range(3): + self.assertAlmostEqual(co[j], ref[j], 2) + + # Verify the shape key values. + bpy.context.scene.frame_set(1) + self.assertAlmostEqual(key.value, .002, 1) + bpy.context.scene.frame_set(30) + self.assertAlmostEqual(key.value, .900, 3) + bpy.context.scene.frame_set(60) + self.assertAlmostEqual(key.value, .100, 3) + def main(): global args -- 2.30.2 From 9d5236302385163422c64cf6eccfcc2625bf7e53 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Wed, 16 Aug 2023 09:37:39 -0400 Subject: [PATCH 25/30] USD test: new skel import test. --- tests/python/bl_usd_import_test.py | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index 08091bf7d39..bb1e27e0e56 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -342,6 +342,68 @@ class USDImportTest(AbstractUSDTest): bpy.context.scene.frame_set(60) self.assertAlmostEqual(key.value, .100, 3) + def test_import_usd_skel_joints(self): + """Test importing USD animated skeleton joints.""" + + infile = str(self.testdir / "arm.usda") + res = bpy.ops.wm.usd_import(filepath=infile) + self.assertEqual({'FINISHED'}, res) + + # Verify armature was imported. + arm_obj = bpy.data.objects["Skel"] + self.assertEqual(arm_obj.type, "ARMATURE", "'Skel' object is not an armature") + + arm = arm_obj.data + bones = arm.bones + + # Verify bone parenting. + self.assertIsNone(bones['Shoulder'].parent, "Shoulder bone should not be parented") + self.assertEqual(bones['Shoulder'], bones['Elbow'].parent, "Elbow bone should be child of Shoulder bone") + self.assertEqual(bones['Elbow'], bones['Hand'].parent, "Hand bone should be child of Elbow bone") + + # Verify armature modifier was created on the mesh. + mesh_obj = bpy.data.objects['Arm'] + # Get all the armature modifiers on the mesh. + arm_mods = [m for m in mesh_obj.modifiers if m.type == "ARMATURE"] + self.assertEqual(len(arm_mods), 1, "Didn't get expected armatrue modifier") + self.assertEqual(arm_mods[0].object, arm_obj, "Armature modifier does not reference the imported armature") + + # Verify expected deform groups. + # There are 4 points in each group. + for i in range(4): + self.assertAlmostEqual(mesh_obj.vertex_groups['Hand'].weight(i), 1.0, 2, "Unexpected weight for Hand deform vert") + self.assertAlmostEqual(mesh_obj.vertex_groups['Shoulder'].weight(4 + i), 1.0, 2, "Unexpected weight for Shoulder deform vert") + self.assertAlmostEqual(mesh_obj.vertex_groups['Elbow'].weight(8 + i), 1.0, 2, "Unexpected weight for Elbow deform vert") + + action = bpy.data.actions['SkelAction'] + + # Verify the Elbow joint rotation animation. + curve_path = 'pose.bones["Elbow"].rotation_quaternion' + + # Quat W + f = action.fcurves.find(curve_path, index=0) + self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion W curve") + self.assertAlmostEqual(f.evaluate(0), 1.0, 2, "Unexpected value for rotation quaternion W curve at frame 0") + self.assertAlmostEqual(f.evaluate(10), 0.707, 2, "Unexpected value for rotation quaternion W curve at frame 10") + + # Quat X + f = action.fcurves.find(curve_path, index=1) + self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion X curve") + self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion X curve at frame 0") + self.assertAlmostEqual(f.evaluate(10), 0.707, 2, "Unexpected value for rotation quaternion X curve at frame 10") + + # Quat Y + f = action.fcurves.find(curve_path, index=2) + self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion Y curve") + self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion Y curve at frame 0") + self.assertAlmostEqual(f.evaluate(10), 0.0, 2, "Unexpected value for rotation quaternion Y curve at frame 10") + + # Quat Z + f = action.fcurves.find(curve_path, index=3) + self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion Z curve") + self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 0") + self.assertAlmostEqual(f.evaluate(10), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 10") + def main(): global args -- 2.30.2 From c68dd1176674617a9dd86bb1cb3ff9d668008b0d Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Wed, 16 Aug 2023 09:42:07 -0400 Subject: [PATCH 26/30] USD: fixed docstring format, per review comment. --- source/blender/io/usd/intern/usd_reader_stage.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 332f4dab727..b0d50f0902a 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -45,9 +45,11 @@ class USDStageReader { void collect_readers(struct Main *bmain); - /* Complete setting up the armature modifiers that + /** + * Complete setting up the armature modifiers that * were created for skinned meshes by setting the - * modifier object on the corresponding modifier. */ + * modifier object on the corresponding modifier. + */ void process_armature_modifiers() const; /* Convert every material prim on the stage to a Blender -- 2.30.2 From 02a715f0c00724d058425ece46e4789cb009fb34 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Wed, 16 Aug 2023 13:58:13 -0400 Subject: [PATCH 27/30] USD import: refactor get_local_usd_xform(). Per review comments by Sybren, simplified get_local_usd_xform() to return an optional tuple. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 18 ++----- .../blender/io/usd/intern/usd_reader_mesh.h | 10 ++-- .../blender/io/usd/intern/usd_reader_xform.cc | 47 ++++++++----------- .../blender/io/usd/intern/usd_reader_xform.h | 14 +++--- 4 files changed, 36 insertions(+), 53 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 6fd3f7da22e..d407663bd45 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -1130,19 +1130,13 @@ std::string USDMeshReader::get_skeleton_path() const return ""; } -bool USDMeshReader::get_local_usd_xform(const float time, - pxr::GfMatrix4d *r_xform, - bool *r_is_constant) const +std::optional USDMeshReader::get_local_usd_xform(const float time) const { - if (!r_xform) { - return false; - } - if (!import_params_.import_skeletons || prim_.IsInstanceProxy()) { /* Use the standard transform computation, since we are ignoring * skinning data. Note that applying the UsdSkelBinding API to an * instance proxy generates a USD error. */ - return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); + return USDXformReader::get_local_usd_xform(time); } if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) { @@ -1151,11 +1145,7 @@ bool USDMeshReader::get_local_usd_xform(const float time, if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) { /* Assume that if a bind transform is defined, then the * transform is constant. */ - if (r_is_constant) { - *r_is_constant = true; - } - *r_xform = bind_xf; - return true; + return XformResult(pxr::GfMatrix4f(bind_xf), true); } else { WM_reportf(RPT_WARNING, @@ -1166,7 +1156,7 @@ bool USDMeshReader::get_local_usd_xform(const float time, } } - return USDXformReader::get_local_usd_xform(time, r_xform, r_is_constant); + return USDXformReader::get_local_usd_xform(time); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index 52827cc26d2..3db79a1111d 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -106,11 +106,11 @@ class USDMeshReader : public USDGeomReader { const double motionSampleTime, MutableSpan attribute); - /* Override transform computation to account for the binding - * transformation for skinned meshes. */ - bool get_local_usd_xform(const float time, - pxr::GfMatrix4d *r_xform, - bool *r_is_constant) const override; + /** + * Override transform computation to account for the binding + * transformation for skinned meshes. + */ + std::optional get_local_usd_xform(const float time) const override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc index 72b0e3d6bb7..451eae756cb 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.cc +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -67,20 +67,20 @@ void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */, const float scale, bool *r_is_constant) { - if (r_is_constant) { - *r_is_constant = true; - } + BLI_assert(r_mat); + BLI_assert(r_is_constant); + *r_is_constant = true; unit_m4(r_mat); - pxr::GfMatrix4d usd_local_xf; - if (!get_local_usd_xform(time, &usd_local_xf, r_is_constant)) { + std::optional xf_result = get_local_usd_xform(time); + + if (!xf_result) { return; } - /* Convert the result to a float matrix. */ - pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf); - mat4f.Get(r_mat); + std::get<0>(*xf_result).Get(r_mat); + *r_is_constant = std::get<1>(*xf_result); /* Apply global scaling and rotation only to root objects, parenting * will propagate it. */ @@ -151,34 +151,25 @@ bool USDXformReader::is_root_xform_prim() const return false; } -bool USDXformReader::get_local_usd_xform(const float time, - pxr::GfMatrix4d *r_xform, - bool *r_is_constant) const +std::optional USDXformReader::get_local_usd_xform(const float time) const { - if (!r_xform) { - return false; - } - - pxr::UsdGeomXformable xformable; - - if (use_parent_xform_) { - xformable = pxr::UsdGeomXformable(prim_.GetParent()); - } - else { - xformable = pxr::UsdGeomXformable(prim_); - } + pxr::UsdGeomXformable xformable = use_parent_xform_ ? pxr::UsdGeomXformable(prim_.GetParent()) : + pxr::UsdGeomXformable(prim_); if (!xformable) { /* This might happen if the prim is a Scope. */ - return false; + return std::nullopt; } - if (r_is_constant) { - *r_is_constant = !xformable.TransformMightBeTimeVarying(); - } + bool is_constant = !xformable.TransformMightBeTimeVarying(); bool reset_xform_stack; - return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time); + pxr::GfMatrix4d xform; + if (!xformable.GetLocalTransformation(&xform, &reset_xform_stack, time)) { + return std::nullopt; + } + + return XformResult(pxr::GfMatrix4f(xform), is_constant); } } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h index 769c6a32734..26bd5c984c3 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.h +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -12,6 +12,8 @@ namespace blender::io::usd { +using XformResult = std::tuple; + class USDXformReader : public USDPrimReader { private: bool use_parent_xform_; @@ -55,13 +57,13 @@ class USDXformReader : public USDPrimReader { * Return the USD prim's local transformation. * * \param time: Time code for evaluating the transform. - * \param r_xform: The transform matrix return value - * \param r_is_constant: Return value flag, set to false if the transform is - * time varying + * + * \return: Optional tuple with the following elements: + * - The transform matrix. + * - A boolean flag indicating whether the matrix + * is constant over time. */ - virtual bool get_local_usd_xform(const float time, - pxr::GfMatrix4d *r_xform, - bool *r_is_constant) const; + virtual std::optional get_local_usd_xform(const float time) const; }; } // namespace blender::io::usd -- 2.30.2 From 14e3f80f3da484df89f80995c155743e733a573c Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Wed, 16 Aug 2023 14:30:46 -0400 Subject: [PATCH 28/30] USD: add missing include. --- source/blender/io/usd/intern/usd_skel_convert.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index d0cec5031c6..5fa3492cb59 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -41,6 +41,7 @@ #include "WM_api.hh" +#include #include #include -- 2.30.2 From 8d67a3a9b405f49c93fbda27b6d9bdf299c2f815 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Thu, 17 Aug 2023 12:29:16 -0400 Subject: [PATCH 29/30] USD skel import: misc cleanup. Fixes based on review comments by Sybren. --- .../blender/io/usd/intern/usd_reader_mesh.cc | 6 ++- .../blender/io/usd/intern/usd_reader_mesh.h | 2 +- .../blender/io/usd/intern/usd_reader_stage.cc | 39 ++++++++++--------- .../blender/io/usd/intern/usd_reader_xform.h | 4 +- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 905f521b92e..e9dffe540d3 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -1144,8 +1144,10 @@ std::optional USDMeshReader::get_local_usd_xform(const float time) if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) { pxr::GfMatrix4d bind_xf; if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) { - /* Assume that if a bind transform is defined, then the - * transform is constant. */ + /* The USD bind transform is a matrix of doubles, + * but we cast it to GfMatrix4f because Blender expects + * a matrix of floats. Also, we assume the transform + * is constant over time. */ return XformResult(pxr::GfMatrix4f(bind_xf), true); } else { diff --git a/source/blender/io/usd/intern/usd_reader_mesh.h b/source/blender/io/usd/intern/usd_reader_mesh.h index 3e2459a5c8d..9355c0902d3 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.h +++ b/source/blender/io/usd/intern/usd_reader_mesh.h @@ -110,7 +110,7 @@ class USDMeshReader : public USDGeomReader { * Override transform computation to account for the binding * transformation for skinned meshes. */ - std::optional get_local_usd_xform(const float time) const override; + std::optional get_local_usd_xform(float time) const override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 562111883da..7f91507741a 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -350,25 +350,28 @@ void USDStageReader::process_armature_modifiers() const if (!reader->object()) { continue; } - if (const USDMeshReader *mesh_reader = dynamic_cast(reader)) { - /* Check if the mesh object has an armature modifier. */ - ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature); - if (!md) { - continue; - } - ArmatureModifierData *amd = reinterpret_cast(md); - - /* Assign the armature based on the bound USD skeleton path of the skinned mesh. */ - std::string skel_path = mesh_reader->get_skeleton_path(); - std::map::const_iterator it = usd_path_to_armature.find(skel_path); - if (it == usd_path_to_armature.end()) { - WM_reportf(RPT_WARNING, - "%s: Couldn't find armature object corresponding to USD skeleton %s", - __func__, - skel_path.c_str()); - } - amd->object = it->second; + const USDMeshReader *mesh_reader = dynamic_cast(reader); + if (!mesh_reader) { + continue; } + /* Check if the mesh object has an armature modifier. */ + ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature); + if (!md) { + continue; + } + + ArmatureModifierData *amd = reinterpret_cast(md); + + /* Assign the armature based on the bound USD skeleton path of the skinned mesh. */ + std::string skel_path = mesh_reader->get_skeleton_path(); + std::map::const_iterator it = usd_path_to_armature.find(skel_path); + if (it == usd_path_to_armature.end()) { + WM_reportf(RPT_WARNING, + "%s: Couldn't find armature object corresponding to USD skeleton %s", + __func__, + skel_path.c_str()); + } + amd->object = it->second; } } diff --git a/source/blender/io/usd/intern/usd_reader_xform.h b/source/blender/io/usd/intern/usd_reader_xform.h index d34d38e5bbb..41b9cee2d99 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.h +++ b/source/blender/io/usd/intern/usd_reader_xform.h @@ -12,6 +12,8 @@ namespace blender::io::usd { +/** A transformation matrix and a boolean indicating + * whether the matrix is constant over time. */ using XformResult = std::tuple; class USDXformReader : public USDPrimReader { @@ -63,7 +65,7 @@ class USDXformReader : public USDPrimReader { * - A boolean flag indicating whether the matrix * is constant over time. */ - virtual std::optional get_local_usd_xform(const float time) const; + virtual std::optional get_local_usd_xform(float time) const; }; } // namespace blender::io::usd -- 2.30.2 From 85ad6b89a7065fe6715e86c09b0cc8505d16e8b3 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Thu, 17 Aug 2023 13:01:32 -0400 Subject: [PATCH 30/30] USD skel import: added comment. --- source/blender/io/usd/intern/usd_reader_xform.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/io/usd/intern/usd_reader_xform.cc b/source/blender/io/usd/intern/usd_reader_xform.cc index 87314d3575b..b9cfa03ce2b 100644 --- a/source/blender/io/usd/intern/usd_reader_xform.cc +++ b/source/blender/io/usd/intern/usd_reader_xform.cc @@ -169,6 +169,9 @@ std::optional USDXformReader::get_local_usd_xform(const float time) return std::nullopt; } + /* The USD bind transform is a matrix of doubles, + * but we cast it to GfMatrix4f because Blender expects + * a matrix of floats. */ return XformResult(pxr::GfMatrix4f(xform), is_constant); } -- 2.30.2