USD: Skeleton and blend shape import #110912
@ -429,6 +429,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");
|
||||
|
||||
@ -492,6 +494,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;
|
||||
@ -538,6 +542,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);
|
||||
|
||||
@ -632,6 +638,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",
|
||||
|
@ -110,6 +110,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.hh
|
||||
usd.h
|
||||
@ -142,6 +144,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
|
||||
)
|
||||
|
||||
if(WITH_HYDRA)
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
@ -43,6 +44,7 @@
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdGeom/subset.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@ -268,6 +270,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_mesh_skel_bindings(bmain, object_, prim_);
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, motionSampleTime);
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@ -1099,4 +1109,57 @@ 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 generates
|
||||
* a USD error. */
|
||||
if (!prim_ || prim_.IsInstanceProxy()) {
|
||||
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 "";
|
||||
}
|
||||
|
||||
std::optional<XformResult> USDMeshReader::get_local_usd_xform(const float time) const
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)) {
|
||||
/* 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 {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"%s: Couldn't compute geom bind transform for %s",
|
||||
__func__,
|
||||
prim_.GetPath().GetAsString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return USDXformReader::get_local_usd_xform(time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
@ -56,6 +56,16 @@ 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:
|
||||
void process_normals_vertex_varying(Mesh *mesh);
|
||||
void process_normals_face_varying(Mesh *mesh);
|
||||
@ -82,6 +92,7 @@ class USDMeshReader : public USDGeomReader {
|
||||
void read_color_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &color_primvar,
|
||||
const double motionSampleTime);
|
||||
|
||||
void read_uv_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime);
|
||||
@ -94,6 +105,12 @@ class USDMeshReader : public USDGeomReader {
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime,
|
||||
MutableSpan<BlenderT> attribute);
|
||||
|
||||
/**
|
||||
* Override transform computation to account for the binding
|
||||
* transformation for skinned meshes.
|
||||
*/
|
||||
std::optional<XformResult> get_local_usd_xform(float time) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
47
source/blender/io/usd/intern/usd_reader_skeleton.cc
Normal file
47
source/blender/io/usd/intern/usd_reader_skeleton.cc
Normal file
@ -0,0 +1,47 @@
|
||||
/* 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_armature.h"
|
||||
#include "BKE_idprop.h"
|
||||
#include "BKE_object.h"
|
||||
|
||||
#include "DNA_armature_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
import_skeleton(bmain, object_, skel_);
|
||||
|
||||
USDXformReader::read_object_data(bmain, motionSampleTime);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
30
source/blender/io/usd/intern/usd_reader_skeleton.h
Normal file
30
source/blender/io/usd/intern/usd_reader_skeleton.h
Normal file
@ -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 <pxr/usd/usdSkel/skeleton.h>
|
||||
|
||||
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
|
@ -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,9 +43,12 @@
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_modifier.h"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
|
||||
@ -100,6 +104,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
|
||||
if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) {
|
||||
return new USDVolumeReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_skeletons && prim.IsA<pxr::UsdSkelSkeleton>()) {
|
||||
return new USDSkeletonReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||
return new USDXformReader(prim, params_, settings_);
|
||||
}
|
||||
@ -134,6 +141,9 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
|
||||
if (prim.IsA<pxr::UsdVolVolume>()) {
|
||||
return new USDVolumeReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdSkelSkeleton>()) {
|
||||
return new USDSkeletonReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||
return new USDXformReader(prim, params_, settings_);
|
||||
}
|
||||
@ -167,6 +177,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<pxr::UsdSkelSkeleton>()) {
|
||||
/* 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 +333,48 @@ void USDStageReader::collect_readers(Main *bmain)
|
||||
collect_readers(bmain, root);
|
||||
}
|
||||
|
||||
void USDStageReader::process_armature_modifiers() const
|
||||
{
|
||||
/* 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<std::string, Object *> usd_path_to_armature;
|
||||
for (const USDPrimReader *reader : readers_) {
|
||||
if (dynamic_cast<const USDSkeletonReader *>(reader) && reader->object()) {
|
||||
usd_path_to_armature.insert(std::make_pair(reader->prim_path(), reader->object()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate over the mesh readers and set armature objects on armature modifiers. */
|
||||
for (const USDPrimReader *reader : readers_) {
|
||||
if (!reader->object()) {
|
||||
continue;
|
||||
}
|
||||
const USDMeshReader *mesh_reader = dynamic_cast<const USDMeshReader *>(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<ArmatureModifierData *>(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<std::string, Object *>::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;
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::import_all_materials(Main *bmain)
|
||||
{
|
||||
BLI_assert(valid());
|
||||
|
@ -45,6 +45,13 @@ 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
|
||||
* material, including materials not used by any geometry.
|
||||
* Note that collect_readers() must be called before calling
|
||||
|
@ -67,37 +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::UsdGeomXformable xformable;
|
||||
std::optional<XformResult> xf_result = get_local_usd_xform(time);
|
||||
|
||||
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. */
|
||||
if (!xf_result) {
|
||||
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);
|
||||
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. */
|
||||
@ -168,4 +151,28 @@ bool USDXformReader::is_root_xform_prim() const
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<XformResult> USDXformReader::get_local_usd_xform(const float time) const
|
||||
{
|
||||
pxr::UsdGeomXformable xformable = use_parent_xform_ ? pxr::UsdGeomXformable(prim_.GetParent()) :
|
||||
pxr::UsdGeomXformable(prim_);
|
||||
|
||||
makowalski marked this conversation as resolved
|
||||
if (!xformable) {
|
||||
/* This might happen if the prim is a Scope. */
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool is_constant = !xformable.TransformMightBeTimeVarying();
|
||||
|
||||
bool reset_xform_stack;
|
||||
pxr::GfMatrix4d xform;
|
||||
if (!xformable.GetLocalTransformation(&xform, &reset_xform_stack, 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);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
@ -12,6 +12,10 @@
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/** A transformation matrix and a boolean indicating
|
||||
* whether the matrix is constant over time. */
|
||||
using XformResult = std::tuple<pxr::GfMatrix4f, bool>;
|
||||
|
||||
class USDXformReader : public USDPrimReader {
|
||||
private:
|
||||
bool use_parent_xform_;
|
||||
@ -50,6 +54,18 @@ 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;
|
||||
|
||||
/**
|
||||
* Return the USD prim's local transformation.
|
||||
*
|
||||
* \param time: Time code for evaluating the transform.
|
||||
*
|
||||
* \return: Optional tuple with the following elements:
|
||||
* - The transform matrix.
|
||||
* - A boolean flag indicating whether the matrix
|
||||
* is constant over time.
|
||||
*/
|
||||
virtual std::optional<XformResult> get_local_usd_xform(float time) const;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
1038
source/blender/io/usd/intern/usd_skel_convert.cc
Normal file
1038
source/blender/io/usd/intern/usd_skel_convert.cc
Normal file
File diff suppressed because it is too large
Load Diff
72
source/blender/io/usd/intern/usd_skel_convert.h
Normal file
72
source/blender/io/usd/intern/usd_skel_convert.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdSkel/skeletonQuery.h>
|
||||
|
||||
struct Main;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
struct USDExportParams;
|
||||
struct USDImportParams;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
struct ImportSettings;
|
||||
|
||||
/**
|
||||
* 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. 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 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 *mesh_obj,
|
||||
const pxr::UsdPrim &prim,
|
||||
bool import_anim = true);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* \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);
|
||||
/**
|
||||
* Import skinning data from a source USD prim as deform groups and an armature
|
||||
nathanvegdahl marked this conversation as resolved
Nathan Vegdahl
commented
I might be misunderstanding the comment, but I think by deform groups you mean vertex groups? That's the Blender terminology. I might be misunderstanding the comment, but I think by deform groups you mean vertex groups? That's the Blender terminology.
Nathan Vegdahl
commented
@mont29 informed me that in fact "deform group" is also used in some areas of Blender's code. So never mind! (Might be good to unify the terminology in the code base at some point, but that's obviously not for this PR!) @mont29 informed me that in fact "deform group" is also used in some areas of Blender's code. So never mind! (Might be good to unify the terminology in the code base at some point, but that's obviously not for this PR!)
|
||||
* 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
|
@ -72,6 +72,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;
|
||||
|
@ -13,6 +13,8 @@ from pxr import Sdf
|
||||
|
||||
import bpy
|
||||
|
||||
from mathutils import Matrix, Vector, Quaternion, Euler
|
||||
|
||||
args = None
|
||||
|
||||
|
||||
@ -302,6 +304,106 @@ 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 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
|
||||
|
Loading…
Reference in New Issue
Block a user
r_xform
is not documented as being optional, and this function has no meaning when it isnullptr
. This means that havingr_xform == nullptr
is a programming error, rather than an expected result of some input. UseBLI_assert()
to check such errors, instead of silently ignoring them.It might even be better to circumvent the whole situation, and make this class of errors impossible. This can be done by changing the return type to
std::optional<pxr::GfMatrix4d>
. Ifr_is_constant
is optional, document it as such. Otherwise it might be useful to just return an optional tuple(matrix, is_constant)
.I refactored the function to return an optional tuple, as you recommended. I think this is an elegant solution. Thanks for the suggestion!