Add Chromium-only Blender WebEngine parity work
This commit is contained in:
229
blender-5.2.0/source/blender/io/usd/intern/usd_armature_utils.cc
Normal file
229
blender-5.2.0/source/blender/io/usd/intern/usd_armature_utils.cc
Normal file
@@ -0,0 +1,229 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_armature_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
||||
#include "BKE_armature.hh"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
#include "DNA_action_types.h"
|
||||
#include "DNA_armature_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* Utility: create new fcurve and add it as a channel to a group. */
|
||||
FCurve *create_fcurve(animrig::Channelbag &channelbag,
|
||||
const animrig::FCurveDescriptor &fcurve_descriptor,
|
||||
const int sample_count)
|
||||
{
|
||||
FCurve *fcurve = channelbag.fcurve_create_unique(nullptr, fcurve_descriptor);
|
||||
BLI_assert_msg(fcurve, "The same F-Curve is being created twice, this is unexpected.");
|
||||
if (fcurve) {
|
||||
BKE_fcurve_bezt_resize(*fcurve, sample_count);
|
||||
}
|
||||
return fcurve;
|
||||
}
|
||||
|
||||
/* Utility: fill in a single fcurve sample at the provided index. */
|
||||
void set_fcurve_sample(FCurve *fcu, int64_t sample_index, const float frame, const float value)
|
||||
{
|
||||
BLI_assert(sample_index >= 0 && sample_index < fcu->totvert);
|
||||
BezTriple &bez = fcu->bezt[sample_index];
|
||||
bez.vec[1][0] = frame;
|
||||
bez.vec[1][1] = value;
|
||||
bez.ipo = BEZT_IPO_LIN;
|
||||
bez.f1 = bez.f2 = bez.f3 = BEZT_FLAG_SELECT;
|
||||
bez.h1 = bez.h2 = HD_AUTO;
|
||||
}
|
||||
|
||||
/* Recursively invoke the 'visitor' function on the given bone and its children. */
|
||||
static void visit_bones(const Bone *bone, FunctionRef<void(const Bone *)> visitor)
|
||||
{
|
||||
if (!(bone && visitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visitor(bone);
|
||||
|
||||
for (const Bone &child : bone->childbase) {
|
||||
visit_bones(&child, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
const ModifierData *get_enabled_modifier(const Object &obj,
|
||||
ModifierType type,
|
||||
const Depsgraph *depsgraph)
|
||||
{
|
||||
BLI_assert(depsgraph);
|
||||
|
||||
const Scene *scene = DEG_get_input_scene(depsgraph);
|
||||
eEvaluationMode mode = DEG_get_mode(depsgraph);
|
||||
|
||||
for (ModifierData &md : obj.modifiers) {
|
||||
|
||||
if (!BKE_modifier_is_enabled(scene, &md, mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (md.type == type) {
|
||||
return &md;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Return the armature modifier on the given object. Return null if no armature modifier
|
||||
* can be found. */
|
||||
static const ArmatureModifierData *get_armature_modifier(const Object &obj,
|
||||
const Depsgraph *depsgraph)
|
||||
{
|
||||
const ArmatureModifierData *mod = reinterpret_cast<const ArmatureModifierData *>(
|
||||
get_enabled_modifier(obj, eModifierType_Armature, depsgraph));
|
||||
return mod;
|
||||
}
|
||||
|
||||
void visit_bones(const Object *ob_arm, FunctionRef<void(const Bone *)> visitor)
|
||||
{
|
||||
if (!(ob_arm && ob_arm->type == OB_ARMATURE && ob_arm->data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bArmature *armature = id_cast<bArmature *>(ob_arm->data);
|
||||
for (const Bone &bone : armature->bonebase) {
|
||||
visit_bones(&bone, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
void get_armature_bone_names(const Object *ob_arm,
|
||||
const bool use_deform,
|
||||
Vector<StringRef> &r_names)
|
||||
{
|
||||
Map<StringRef, const Bone *> deform_map;
|
||||
if (use_deform) {
|
||||
init_deform_bones_map(ob_arm, &deform_map);
|
||||
}
|
||||
|
||||
auto visitor = [&](const Bone *bone) {
|
||||
const StringRef bone_name(bone->name);
|
||||
if (use_deform && !deform_map.contains(bone_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
r_names.append(bone_name);
|
||||
};
|
||||
|
||||
visit_bones(ob_arm, visitor);
|
||||
}
|
||||
|
||||
pxr::TfToken build_usd_joint_path(const Bone *bone, bool allow_unicode)
|
||||
{
|
||||
std::string path(make_safe_name(bone->name, allow_unicode));
|
||||
|
||||
const Bone *parent = bone->parent;
|
||||
while (parent) {
|
||||
path = make_safe_name(parent->name, allow_unicode) + '/' + path;
|
||||
parent = parent->parent;
|
||||
}
|
||||
|
||||
return pxr::TfToken(path);
|
||||
}
|
||||
|
||||
void create_pose_joints(pxr::UsdSkelAnimation &skel_anim,
|
||||
const Object &obj,
|
||||
const Map<StringRef, const Bone *> *deform_map,
|
||||
bool allow_unicode)
|
||||
{
|
||||
BLI_assert(obj.pose);
|
||||
|
||||
pxr::VtTokenArray joints;
|
||||
|
||||
const bPose *pose = obj.pose;
|
||||
const bArmature &arm = *id_cast<bArmature *>(obj.data);
|
||||
BKE_pose_ensure_bone_indices(obj);
|
||||
|
||||
for (const bPoseChannel &pchan : pose->chanbase) {
|
||||
const Bone *pchan_bone = pchan.bone_get(arm);
|
||||
if (pchan_bone) {
|
||||
if (deform_map && !deform_map->contains(pchan.name)) {
|
||||
/* If deform_map is passed in, assume we're going deform-only.
|
||||
* Bones not found in the map should be skipped. */
|
||||
continue;
|
||||
}
|
||||
|
||||
joints.push_back(build_usd_joint_path(pchan_bone, allow_unicode));
|
||||
}
|
||||
}
|
||||
|
||||
skel_anim.GetJointsAttr().Set(joints);
|
||||
}
|
||||
|
||||
const Object *get_armature_modifier_obj(const Object &obj, const Depsgraph *depsgraph)
|
||||
{
|
||||
const ArmatureModifierData *mod = get_armature_modifier(obj, depsgraph);
|
||||
return mod ? mod->object : nullptr;
|
||||
}
|
||||
|
||||
bool is_armature_modifier_bone_name(const Object &obj,
|
||||
const StringRefNull name,
|
||||
const Depsgraph *depsgraph)
|
||||
{
|
||||
const ArmatureModifierData *arm_mod = get_armature_modifier(obj, depsgraph);
|
||||
|
||||
if (!arm_mod || !arm_mod->object || !arm_mod->object->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bArmature *arm = id_cast<bArmature *>(arm_mod->object->data);
|
||||
|
||||
return BKE_armature_find_bone_name(arm, name.c_str());
|
||||
}
|
||||
|
||||
bool can_export_skinned_mesh(const Object &obj, const Depsgraph *depsgraph)
|
||||
{
|
||||
return get_enabled_modifier(obj, eModifierType_Armature, depsgraph) != nullptr;
|
||||
}
|
||||
|
||||
void init_deform_bones_map(const Object *obj, Map<StringRef, const Bone *> *deform_map)
|
||||
{
|
||||
if (!deform_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
deform_map->clear();
|
||||
|
||||
auto deform_visitor = [&](const Bone *bone) {
|
||||
if (!bone) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool deform = !(bone->flag & BONE_NO_DEFORM);
|
||||
if (deform) {
|
||||
deform_map->add(bone->name, bone);
|
||||
}
|
||||
};
|
||||
|
||||
visit_bones(obj, deform_visitor);
|
||||
|
||||
/* Get deform parents */
|
||||
for (const auto &item : deform_map->items()) {
|
||||
BLI_assert(item.value);
|
||||
for (const Bone *parent = item.value->parent; parent; parent = parent->parent) {
|
||||
deform_map->add(parent->name, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
148
blender-5.2.0/source/blender/io/usd/intern/usd_armature_utils.hh
Normal file
148
blender-5.2.0/source/blender/io/usd/intern/usd_armature_utils.hh
Normal file
@@ -0,0 +1,148 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/usd/usdSkel/animation.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Bone;
|
||||
struct Depsgraph;
|
||||
struct FCurve;
|
||||
struct ModifierData;
|
||||
struct Object;
|
||||
|
||||
namespace animrig {
|
||||
class Channelbag;
|
||||
struct FCurveDescriptor;
|
||||
} // namespace animrig
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Custom Blender Primvar name used for storing armature bone lengths. */
|
||||
inline const pxr::TfToken BlenderBoneLengths("blender:bone_lengths", pxr::TfToken::Immortal);
|
||||
|
||||
/* Utility: create new fcurve and add it as a channel to a group. */
|
||||
FCurve *create_fcurve(animrig::Channelbag &channelbag,
|
||||
const animrig::FCurveDescriptor &fcurve_descriptor,
|
||||
const int sample_count);
|
||||
|
||||
/* Utility: fill in a single fcurve sample at the provided index. */
|
||||
void set_fcurve_sample(FCurve *fcu, int64_t sample_index, const float frame, const float value);
|
||||
|
||||
/**
|
||||
* Recursively invoke the given function on the given armature object's bones.
|
||||
* This function is a no-op if the object isn't an armature.
|
||||
*
|
||||
* \param ob_arm: The armature object
|
||||
* \param visitor: The function to invoke on each bone
|
||||
*/
|
||||
void visit_bones(const Object *ob_arm, FunctionRef<void(const Bone *)> visitor);
|
||||
|
||||
/**
|
||||
* Return in 'r_names' the names of the given armature object's bones.
|
||||
*
|
||||
* \param ob_arm: The armature object
|
||||
* \param use_deform: If true, use only deform bone names, including their parents, to match
|
||||
* armature export joint indices
|
||||
* \param r_names: The returned list of bone names
|
||||
*/
|
||||
void get_armature_bone_names(const Object *ob_arm, bool use_deform, Vector<StringRef> &r_names);
|
||||
|
||||
/**
|
||||
* Return the USD joint path corresponding to the given bone. For example, for the bone
|
||||
* "Hand", this function might return the full path "Shoulder/Elbow/Hand" of the joint
|
||||
* in the hierarchy.
|
||||
*
|
||||
* \param bone: The bone whose path will be queried.
|
||||
* \param allow_unicode: Whether to allow unicode bone names to be used
|
||||
* \return The path to the joint.
|
||||
*/
|
||||
pxr::TfToken build_usd_joint_path(const Bone *bone, bool allow_unicode);
|
||||
|
||||
/**
|
||||
* Sets the USD joint paths as an attribute on the given USD animation,
|
||||
* where the paths correspond to the bones of the given armature.
|
||||
*
|
||||
* \param skel_anim: The animation whose joints attribute will be set
|
||||
* \param obj: The armature object
|
||||
* \param deform_map: A pointer to a map associating bone names with
|
||||
* deform bones and their parents. If the pointer
|
||||
* is not null, assume only deform bones are to be
|
||||
* exported and bones not found in this map will be
|
||||
* skipped
|
||||
* \param allow_unicode: Whether to allow unicode bone names to be used
|
||||
*/
|
||||
void create_pose_joints(pxr::UsdSkelAnimation &skel_anim,
|
||||
const Object &obj,
|
||||
const Map<StringRef, const Bone *> *deform_map,
|
||||
bool allow_unicode);
|
||||
|
||||
/**
|
||||
* Return the modifier of the given type enabled for the given dependency graph's
|
||||
* evaluation mode (viewport or render).
|
||||
*
|
||||
* \param obj: Object to query for the modifier
|
||||
* \param depsgraph: The dependency graph where the object was evaluated
|
||||
* \return The modifier.
|
||||
*/
|
||||
const ModifierData *get_enabled_modifier(const Object &obj,
|
||||
ModifierType type,
|
||||
const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* If the given object has an enabled armature modifier, return the
|
||||
* armature object bound to the modifier.
|
||||
*
|
||||
* \param: Object to check for the modifier
|
||||
* \param depsgraph: The dependency graph where the object was evaluated
|
||||
* \return The armature object.
|
||||
*/
|
||||
const Object *get_armature_modifier_obj(const Object &obj, const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* If the given object has an armature modifier, query whether the given
|
||||
* name matches the name of a bone on the armature referenced by the modifier.
|
||||
*
|
||||
* \param obj: Object to query for the modifier
|
||||
* \param name: Name to check
|
||||
* \param depsgraph: The dependency graph where the object was evaluated
|
||||
* \return True if the name matches a bone name. Return false if no matching
|
||||
* bone name is found or if the object does not have an armature modifier
|
||||
*/
|
||||
bool is_armature_modifier_bone_name(const Object &obj,
|
||||
StringRefNull name,
|
||||
const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* Query whether exporting a skinned mesh is supported for the given object.
|
||||
* Currently, the object can be exported as a skinned mesh if it has an enabled
|
||||
* armature modifier and no other enabled modifiers.
|
||||
*
|
||||
* \param obj: Object to query
|
||||
* \param depsgraph: The dependency graph where the object was evaluated
|
||||
* \return True if skinned mesh export is supported, false otherwise.
|
||||
*/
|
||||
bool can_export_skinned_mesh(const Object &obj, const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* Initialize the deform bones map:
|
||||
* - First: grab all bones marked for deforming and store them.
|
||||
* - Second: loop the deform bones you found and recursively walk up their parent
|
||||
* hierarchies, marking those bones as deform as well.
|
||||
* \param obj: Object to query
|
||||
* \param deform_map: A pointer to the deform_map to fill with deform bones and
|
||||
* their parents found on the object
|
||||
*/
|
||||
void init_deform_bones_map(const Object *obj, Map<StringRef, const Bone *> *deform_map);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
653
blender-5.2.0/source/blender/io/usd/intern/usd_asset_utils.cc
Normal file
653
blender-5.2.0/source/blender/io/usd/intern/usd_asset_utils.cc
Normal file
@@ -0,0 +1,653 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_asset_utils.hh"
|
||||
#include "usd.hh"
|
||||
|
||||
#include <pxr/usd/ar/asset.h>
|
||||
#include <pxr/usd/ar/packageUtils.h>
|
||||
#include <pxr/usd/ar/resolver.h>
|
||||
#include <pxr/usd/ar/writableAsset.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
constexpr char UDIM_PATTERN[] = "<UDIM>";
|
||||
constexpr char UDIM_PATTERN2[] = "%3CUDIM%3E";
|
||||
|
||||
/* Maximum range of UDIM tiles, per the
|
||||
* UsdPreviewSurface specifications. See
|
||||
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
|
||||
*/
|
||||
constexpr int UDIM_START_TILE = 1001;
|
||||
constexpr int UDIM_END_TILE = 1100;
|
||||
|
||||
/**
|
||||
* The following is copied from `_SplitUdimPattern()` in
|
||||
* USD library source file `materialParamsUtils.cpp`.
|
||||
* Split a UDIM file path such as `/someDir/myFile.<UDIM>.exr` into a
|
||||
* prefix `/someDir/myFile.` and suffix `.exr`.
|
||||
*/
|
||||
static std::pair<std::string, std::string> split_udim_pattern(const std::string &path)
|
||||
{
|
||||
std::string_view patterns[]{UDIM_PATTERN, UDIM_PATTERN2};
|
||||
for (const std::string_view pattern : patterns) {
|
||||
const std::string::size_type pos = path.find(pattern);
|
||||
if (pos != std::string::npos) {
|
||||
return {path.substr(0, pos), path.substr(pos + pattern.size())};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Return the asset file base name, with special handling of
|
||||
* package relative paths. */
|
||||
static std::string get_asset_base_name(const std::string &src_path, ReportList *reports)
|
||||
{
|
||||
char base_name[FILE_MAXFILE];
|
||||
|
||||
if (pxr::ArIsPackageRelativePath(src_path)) {
|
||||
std::pair<std::string, std::string> split = pxr::ArSplitPackageRelativePathInner(src_path);
|
||||
if (split.second.empty()) {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"%s: Couldn't determine package-relative file name from path %s",
|
||||
__func__,
|
||||
src_path.c_str());
|
||||
return src_path;
|
||||
}
|
||||
BLI_path_split_file_part(split.second.c_str(), base_name, sizeof(base_name));
|
||||
}
|
||||
else {
|
||||
BLI_path_split_file_part(src_path.c_str(), base_name, sizeof(base_name));
|
||||
}
|
||||
|
||||
return base_name;
|
||||
}
|
||||
|
||||
/* Copy an asset to a destination directory. */
|
||||
static std::string copy_asset_to_directory(const std::string &src_path,
|
||||
const char *dest_dir_path,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports)
|
||||
{
|
||||
std::string base_name = get_asset_base_name(src_path, reports);
|
||||
|
||||
char dest_file_path[FILE_MAX];
|
||||
BLI_path_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str());
|
||||
BLI_path_normalize(dest_file_path);
|
||||
|
||||
if (name_collision_mode == TexNameCollisionMode::UseExisting && BLI_is_file(dest_file_path)) {
|
||||
return dest_file_path;
|
||||
}
|
||||
|
||||
if (!copy_asset(src_path, dest_file_path, name_collision_mode, reports)) {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"%s: Couldn't copy file %s to %s",
|
||||
__func__,
|
||||
src_path.c_str(),
|
||||
dest_file_path);
|
||||
return src_path;
|
||||
}
|
||||
|
||||
return dest_file_path;
|
||||
}
|
||||
|
||||
static std::string copy_udim_asset_to_directory(const std::string &src_path,
|
||||
const char *dest_dir_path,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports)
|
||||
{
|
||||
/* Get prefix and suffix from udim pattern. */
|
||||
std::pair<std::string, std::string> splitPath = split_udim_pattern(src_path);
|
||||
if (splitPath.first.empty() || splitPath.second.empty()) {
|
||||
BKE_reportf(
|
||||
reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path.c_str());
|
||||
return src_path;
|
||||
}
|
||||
|
||||
/* Copy the individual UDIM tiles. Since there is currently no way to query the contents
|
||||
* of a directory using the USD resolver, we must take a brute force approach. We iterate
|
||||
* over the allowed range of tile indices and copy any tiles that exist. The USDPreviewSurface
|
||||
* specification stipulates "a maximum of ten tiles in the U direction" and that
|
||||
* "the tiles must be within the range [1001, 1100] (as of specification version 2.5)". See
|
||||
* https://graphics.pixar.com/usd/release/spec_usdpreviewsurface.html#texture-reader
|
||||
*/
|
||||
for (int i = UDIM_START_TILE; i <= UDIM_END_TILE; ++i) {
|
||||
const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second;
|
||||
if (asset_exists(src_udim)) {
|
||||
copy_asset_to_directory(src_udim, dest_dir_path, name_collision_mode, reports);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string src_file_name = get_asset_base_name(src_path, reports);
|
||||
char ret_udim_path[FILE_MAX];
|
||||
BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str());
|
||||
|
||||
/* Blender only recognizes the <UDIM> pattern, not the
|
||||
* alternative UDIM_PATTERN2, so we make sure the returned
|
||||
* path has the former. */
|
||||
splitPath = split_udim_pattern(ret_udim_path);
|
||||
if (splitPath.first.empty() || splitPath.second.empty()) {
|
||||
BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path);
|
||||
return ret_udim_path;
|
||||
}
|
||||
|
||||
return splitPath.first + UDIM_PATTERN + splitPath.second;
|
||||
}
|
||||
|
||||
bool copy_asset(const std::string &src,
|
||||
const std::string &dst,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports)
|
||||
{
|
||||
const pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
|
||||
if (name_collision_mode != TexNameCollisionMode::Overwrite) {
|
||||
if (!ar.Resolve(dst).IsEmpty()) {
|
||||
/* The asset exists, so this is a no-op. */
|
||||
BKE_reportf(
|
||||
reports, RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pxr::ArResolvedPath src_path = ar.Resolve(src);
|
||||
|
||||
if (src_path.IsEmpty()) {
|
||||
BKE_reportf(reports, RPT_ERROR, "%s: Cannot resolve path %s", __func__, src.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst);
|
||||
|
||||
if (dst_path.IsEmpty()) {
|
||||
BKE_reportf(
|
||||
reports, RPT_ERROR, "%s: Cannot resolve path %s for writing", __func__, dst.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src_path == dst_path) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Cannot copy %s. The source and destination paths are the same",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string why_not;
|
||||
if (!ar.CanWriteAssetToPath(dst_path, &why_not)) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Cannot write to asset %s: %s",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str(),
|
||||
why_not.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<pxr::ArAsset> src_asset = ar.OpenAsset(src_path);
|
||||
if (!src_asset) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Cannot open source asset %s",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t size = src_asset->GetSize();
|
||||
|
||||
if (size == 0) {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"%s: Will not copy zero size source asset %s",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<const char> buf = src_asset->GetBuffer();
|
||||
|
||||
if (!buf) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Null buffer for source asset %s",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
|
||||
dst_path, pxr::ArResolver::WriteMode::Replace);
|
||||
if (!dst_asset) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Cannot open destination asset %s for writing",
|
||||
__func__,
|
||||
src_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0);
|
||||
|
||||
if (bytes_written == 0) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Error writing to destination asset %s",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str());
|
||||
}
|
||||
|
||||
if (!dst_asset->Close()) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Couldn't close destination asset %s",
|
||||
__func__,
|
||||
dst_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_written > 0;
|
||||
}
|
||||
|
||||
bool asset_exists(const std::string &path)
|
||||
{
|
||||
return !pxr::ArGetResolver().Resolve(path).IsEmpty();
|
||||
}
|
||||
|
||||
std::string import_asset(const std::string &src,
|
||||
const char *import_dir,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (import_dir[0] == '\0') {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Texture import directory path empty, couldn't import %s",
|
||||
__func__,
|
||||
src.c_str());
|
||||
return src;
|
||||
}
|
||||
|
||||
char dest_dir_path[FILE_MAXDIR];
|
||||
STRNCPY(dest_dir_path, import_dir);
|
||||
|
||||
if (BLI_path_is_rel(import_dir)) {
|
||||
const char *basepath = BKE_main_blendfile_path_from_global();
|
||||
if (basepath[0] == '\0') {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: import directory is relative "
|
||||
"but the blend file path is empty. "
|
||||
"Please save the blend file before importing the USD "
|
||||
"or provide an absolute import directory path. "
|
||||
"Cannot import %s",
|
||||
__func__,
|
||||
src.c_str());
|
||||
return src;
|
||||
}
|
||||
char path_temp[FILE_MAX];
|
||||
STRNCPY(path_temp, dest_dir_path);
|
||||
BLI_path_abs(path_temp, basepath);
|
||||
STRNCPY(dest_dir_path, path_temp);
|
||||
}
|
||||
|
||||
BLI_path_normalize(dest_dir_path);
|
||||
|
||||
if (!BLI_dir_create_recursive(dest_dir_path)) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"%s: Couldn't create texture import directory %s",
|
||||
__func__,
|
||||
dest_dir_path);
|
||||
return src;
|
||||
}
|
||||
|
||||
if (is_udim_path(src)) {
|
||||
return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode, reports);
|
||||
}
|
||||
|
||||
return copy_asset_to_directory(src, dest_dir_path, name_collision_mode, reports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the parent directory of the given path exists on the
|
||||
* file system.
|
||||
*
|
||||
* \param path: input file path
|
||||
* \return true if the parent directory exists
|
||||
*/
|
||||
static bool parent_dir_exists_on_file_system(const std::string &path)
|
||||
{
|
||||
char dir_path[FILE_MAX];
|
||||
BLI_path_split_dir_part(path.c_str(), dir_path, FILE_MAX);
|
||||
return BLI_is_dir(dir_path);
|
||||
}
|
||||
|
||||
bool is_udim_path(const std::string &path)
|
||||
{
|
||||
return path.find(UDIM_PATTERN) != std::string::npos ||
|
||||
path.find(UDIM_PATTERN2) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage)
|
||||
{
|
||||
pxr::SdfLayerHandle layer = stage->GetRootLayer();
|
||||
|
||||
if (layer->IsAnonymous()) {
|
||||
WM_global_reportf(RPT_WARNING,
|
||||
"%s: Cannot generate a textures directory path for anonymous stage",
|
||||
__func__);
|
||||
return "";
|
||||
}
|
||||
|
||||
const pxr::ArResolvedPath &stage_path = layer->GetResolvedPath();
|
||||
|
||||
if (stage_path.empty()) {
|
||||
WM_global_reportf(RPT_WARNING, "%s: Cannot get resolved path for stage", __func__);
|
||||
return "";
|
||||
}
|
||||
|
||||
const pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
|
||||
/* Resolve the `./textures` relative path, with the stage path as an anchor. */
|
||||
std::string textures_dir = ar.CreateIdentifierForNewAsset("./textures", stage_path);
|
||||
|
||||
/* If parent of the stage path exists as a file system directory, try to create the
|
||||
* textures directory. */
|
||||
if (parent_dir_exists_on_file_system(stage_path.GetPathString())) {
|
||||
BLI_dir_create_recursive(textures_dir.c_str());
|
||||
}
|
||||
|
||||
return textures_dir;
|
||||
}
|
||||
|
||||
bool should_import_asset(const std::string &path)
|
||||
{
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BLI_path_is_rel(path.c_str())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pxr::ArIsPackageRelativePath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_udim_path(path) && parent_dir_exists_on_file_system(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !BLI_is_file(path.c_str()) && asset_exists(path);
|
||||
}
|
||||
|
||||
bool paths_equal(const std::string &path1, const std::string &path2)
|
||||
{
|
||||
BLI_assert_msg(!BLI_path_is_rel(path1.c_str()) && !BLI_path_is_rel(path2.c_str()),
|
||||
"Paths arguments must be absolute");
|
||||
|
||||
const pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
return ar.ResolveForNewAsset(path1) == ar.ResolveForNewAsset(path2);
|
||||
}
|
||||
|
||||
const char *temp_textures_dir()
|
||||
{
|
||||
static bool inited = false;
|
||||
|
||||
static char temp_dir[FILE_MAXDIR] = {'\0'};
|
||||
|
||||
if (!inited) {
|
||||
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
|
||||
inited = true;
|
||||
}
|
||||
|
||||
return temp_dir;
|
||||
}
|
||||
|
||||
bool write_to_path(const void *data, size_t size, const std::string &path, ReportList *reports)
|
||||
{
|
||||
BLI_assert(data);
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
pxr::ArResolvedPath resolved_path = ar.ResolveForNewAsset(path);
|
||||
|
||||
if (resolved_path.IsEmpty()) {
|
||||
BKE_reportf(reports, RPT_ERROR, "Cannot resolve path %s for writing", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string why_not;
|
||||
if (!ar.CanWriteAssetToPath(resolved_path, &why_not)) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Cannot write to asset %s: %s",
|
||||
resolved_path.GetPathString().c_str(),
|
||||
why_not.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<pxr::ArWritableAsset> dst_asset = ar.OpenAssetForWrite(
|
||||
resolved_path, pxr::ArResolver::WriteMode::Replace);
|
||||
if (!dst_asset) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Cannot open destination asset %s for writing",
|
||||
resolved_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t bytes_written = dst_asset->Write(data, size, 0);
|
||||
|
||||
if (bytes_written == 0) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Error writing to destination asset %s",
|
||||
resolved_path.GetPathString().c_str());
|
||||
}
|
||||
|
||||
if (!dst_asset->Close()) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Couldn't close destination asset %s",
|
||||
resolved_path.GetPathString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_written > 0;
|
||||
}
|
||||
|
||||
void ensure_usd_source_path_prop(const std::string &path, ID *id)
|
||||
{
|
||||
if (!id || path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pxr::ArIsPackageRelativePath(path)) {
|
||||
/* Don't record package-relative paths (e.g., images in USDZ
|
||||
* archives). */
|
||||
return;
|
||||
}
|
||||
|
||||
IDProperty *idgroup = IDP_EnsureProperties(id);
|
||||
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const StringRef prop_name = "usd_source_path";
|
||||
|
||||
if (IDP_GetPropertyFromGroup(idgroup, prop_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.string.str = path.c_str();
|
||||
/* Note length includes null terminator. */
|
||||
val.string.len = path.size() + 1;
|
||||
val.string.subtype = IDP_STRING_SUB_UTF8;
|
||||
|
||||
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
std::string get_usd_source_path(ID *id)
|
||||
{
|
||||
if (!id) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const IDProperty *idgroup = IDP_EnsureProperties(id);
|
||||
if (!idgroup) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const StringRef prop_name = "usd_source_path";
|
||||
const IDProperty *prop = IDP_GetPropertyFromGroup(idgroup, prop_name);
|
||||
if (!prop) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return static_cast<const char *>(prop->data.pointer);
|
||||
}
|
||||
|
||||
std::string get_relative_path(const std::string &path, const std::string &anchor)
|
||||
{
|
||||
if (path.empty() || anchor.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path == anchor) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (BLI_path_is_rel(path.c_str())) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (pxr::ArIsPackageRelativePath(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (BLI_is_file(path.c_str()) && BLI_is_file(anchor.c_str())) {
|
||||
/* Treat the paths as standard files. */
|
||||
char rel_path[FILE_MAX];
|
||||
STRNCPY(rel_path, path.c_str());
|
||||
BLI_path_rel(rel_path, anchor.c_str());
|
||||
if (!BLI_path_is_rel(rel_path)) {
|
||||
return path;
|
||||
}
|
||||
BLI_string_replace_char(rel_path, '\\', '/');
|
||||
return rel_path + 2;
|
||||
}
|
||||
|
||||
/* If we got here, the paths may be URIs or files on the file system. */
|
||||
|
||||
/* We don't have a library to compute relative paths for URIs
|
||||
* so we use the standard file-system calls to do so. This
|
||||
* may not work for all URIs in theory, but is probably sufficient
|
||||
* for the subset of URIs we are likely to encounter in practice
|
||||
* currently.
|
||||
* TODO(makowalski): provide better utilities for this. */
|
||||
|
||||
const pxr::ArResolver &ar = pxr::ArGetResolver();
|
||||
|
||||
std::string resolved_path = ar.Resolve(path);
|
||||
std::string resolved_anchor = ar.Resolve(anchor);
|
||||
|
||||
if (resolved_path.empty() || resolved_anchor.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string prefix = pxr::TfStringGetCommonPrefix(path, anchor);
|
||||
if (prefix.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::replace(prefix.begin(), prefix.end(), '\\', '/');
|
||||
|
||||
size_t last_slash_pos = prefix.find_last_of('/');
|
||||
if (last_slash_pos == std::string::npos) {
|
||||
/* Unexpected: The prefix doesn't contain a slash,
|
||||
* so this was not an absolute path. */
|
||||
return path;
|
||||
}
|
||||
|
||||
/* Replace the common prefix up to the last slash with
|
||||
* a fake root directory to allow computing the relative path
|
||||
* excluding the URI. We omit the URI because it might not
|
||||
* be handled correctly by the standard file-system path computations. */
|
||||
resolved_path = "/root" + resolved_path.substr(last_slash_pos);
|
||||
resolved_anchor = "/root" + resolved_anchor.substr(last_slash_pos);
|
||||
|
||||
char anchor_parent_dir[FILE_MAX];
|
||||
BLI_path_split_dir_part(resolved_anchor.c_str(), anchor_parent_dir, FILE_MAX);
|
||||
|
||||
if (anchor_parent_dir[0] == '\0') {
|
||||
return path;
|
||||
}
|
||||
|
||||
char result_path[FILE_MAX];
|
||||
STRNCPY(result_path, resolved_path.c_str());
|
||||
BLI_path_rel(result_path, anchor_parent_dir);
|
||||
|
||||
if (BLI_path_is_rel(result_path)) {
|
||||
/* Strip the Blender relative path marker, and set paths to Unix-style. */
|
||||
BLI_string_replace_char(result_path, '\\', '/');
|
||||
return std::string(result_path + 2);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void USD_path_abs(char *path, const char *basepath, bool for_import)
|
||||
{
|
||||
if (!BLI_path_is_rel(path)) {
|
||||
pxr::ArResolvedPath resolved_path = for_import ? pxr::ArGetResolver().Resolve(path) :
|
||||
pxr::ArGetResolver().ResolveForNewAsset(path);
|
||||
|
||||
const std::string &path_str = resolved_path.GetPathString();
|
||||
|
||||
if (!path_str.empty()) {
|
||||
if (path_str.length() < FILE_MAX) {
|
||||
BLI_strncpy(path, path_str.c_str(), FILE_MAX);
|
||||
return;
|
||||
}
|
||||
WM_global_reportf(RPT_ERROR,
|
||||
"In %s: resolved path %s exceeds path buffer length.",
|
||||
__func__,
|
||||
path_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/* If we got here, the path couldn't be resolved by the ArResolver, so we
|
||||
* fall back on the standard Blender absolute path resolution. */
|
||||
BLI_path_abs(path, basepath);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
160
blender-5.2.0/source/blender/io/usd/intern/usd_asset_utils.hh
Normal file
160
blender-5.2.0/source/blender/io/usd/intern/usd_asset_utils.hh
Normal file
@@ -0,0 +1,160 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to copy an asset.
|
||||
*
|
||||
* \param src: source path of the asset to copy
|
||||
* \param dst: destination path of the copy
|
||||
* \param name_collision_mode: behavior when `dst` already exists
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
* \return true if the copy succeeded, false otherwise
|
||||
*/
|
||||
bool copy_asset(const std::string &src,
|
||||
const std::string &dst,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports);
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to determine if the
|
||||
* asset with the given path exists.
|
||||
*
|
||||
* \param path: the path to resolve
|
||||
* \return true if the asset exists, false otherwise
|
||||
*/
|
||||
bool asset_exists(const std::string &path);
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to copy an asset to a destination
|
||||
* directory and return the path to the copied file. This function may
|
||||
* be used to copy textures from a USDZ archive to a directory on disk.
|
||||
* The destination directory will be created if it doesn't already exist.
|
||||
* If the copy was unsuccessful, this function will log an error and
|
||||
* return the original source file path unmodified.
|
||||
*
|
||||
* \param src: source path of the asset to import
|
||||
* \param import_dir: path to the destination directory
|
||||
* \param name_collision_mode: behavior when a file of the same name already exists
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
* \return path to copied file or the original `src` path if there was an error
|
||||
*/
|
||||
std::string import_asset(const std::string &src,
|
||||
const char *import_dir,
|
||||
TexNameCollisionMode name_collision_mode,
|
||||
ReportList *reports);
|
||||
|
||||
/**
|
||||
* Check if the given path contains a UDIM token.
|
||||
*
|
||||
* \param path: the path to check
|
||||
* \return true if the path contains a UDIM token, false otherwise
|
||||
*/
|
||||
bool is_udim_path(const std::string &path);
|
||||
|
||||
/**
|
||||
* Invoke the USD asset resolver to return an identifier for a 'textures' directory
|
||||
* which is a sibling of the given stage. The resulting path is created by
|
||||
* resolving the './textures' relative path with the stage's root layer path as
|
||||
* the anchor. If the parent of the stage root layer path resolves to a file
|
||||
* system path, the textures directory will be created, if it doesn't exist.
|
||||
*
|
||||
* \param stage: The stage whose root layer is a sibling of the 'textures'
|
||||
* directory
|
||||
* \return the path to the 'textures' directory
|
||||
*/
|
||||
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage);
|
||||
|
||||
/**
|
||||
* Return true if the asset at the given path is a candidate for importing
|
||||
* with the USD asset resolver. The following heuristics are currently
|
||||
* applied for this test:
|
||||
* - Returns false if it's a Blender relative path.
|
||||
* - Returns true if the path is package-relative.
|
||||
* - Returns true is the path doesn't exist on the file system but can
|
||||
* nonetheless be resolved by the USD asset resolver.
|
||||
* - Returns false otherwise.
|
||||
*
|
||||
* TODO(makowalski): the test currently requires a file-system stat.
|
||||
* Consider possible ways around this, e.g., by determining if the
|
||||
* path is a supported URI.
|
||||
*
|
||||
* \param path: input file path
|
||||
* \return true if the path should be imported, false otherwise
|
||||
*/
|
||||
bool should_import_asset(const std::string &path);
|
||||
|
||||
/**
|
||||
* Invokes the USD asset resolver to resolve the given paths and
|
||||
* returns true if the resolved paths are equal.
|
||||
*
|
||||
* \param path1: first path to compare
|
||||
* \param path2: second path to compare
|
||||
* \return true if the resolved input paths are equal, returns
|
||||
* false otherwise.
|
||||
*
|
||||
*/
|
||||
bool paths_equal(const std::string &path1, const std::string &path2);
|
||||
|
||||
/**
|
||||
* Returns path to temporary folder for saving imported textures prior to packing.
|
||||
* CAUTION: this directory is recursively deleted after material import.
|
||||
*/
|
||||
const char *temp_textures_dir();
|
||||
|
||||
/**
|
||||
* Invokes the USD asset resolver to write data to the given path.
|
||||
*
|
||||
* \param data: pointer to data to write
|
||||
* \param size: number of bytes to write
|
||||
* \param path: path of asset to be written
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
* \return true if the data was written, returns
|
||||
* false otherwise.
|
||||
*
|
||||
*/
|
||||
bool write_to_path(const void *data, size_t size, const std::string &path, ReportList *reports);
|
||||
|
||||
/**
|
||||
* Add the given path as a custom property "usd_source_path" on the given id.
|
||||
* If the path is a package-relative path (i.e., is relative to a USDZ archive)
|
||||
* it will not be added a a property. If custom property "usd_source_path"
|
||||
* already exists, this function does nothing.
|
||||
*
|
||||
* \param path: path to record as a custom property
|
||||
* \param id: id for which to create the custom property
|
||||
*/
|
||||
void ensure_usd_source_path_prop(const std::string &path, ID *id);
|
||||
|
||||
/**
|
||||
* Return the value of the "usd_source_path" custom property on the given id.
|
||||
* Return an empty string if the property does not exist.
|
||||
*/
|
||||
std::string get_usd_source_path(ID *id);
|
||||
|
||||
/**
|
||||
* Return the given path as a relative path with respect to the given anchor
|
||||
* path.
|
||||
*
|
||||
* \param path: path to make relative with respect to the anchor path
|
||||
* \param anchor: the anchor path
|
||||
* \return the relative path string; return the input path unchanged if it can't
|
||||
* be made relative, is already a relative path or is a package-relative
|
||||
* path
|
||||
*
|
||||
*/
|
||||
std::string get_relative_path(const std::string &path, const std::string &anchor);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,215 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd(const bke::AttrType blender_type,
|
||||
bool use_color3f_type)
|
||||
{
|
||||
switch (blender_type) {
|
||||
case bke::AttrType::Float:
|
||||
return pxr::SdfValueTypeNames->FloatArray;
|
||||
case bke::AttrType::Int8:
|
||||
return pxr::SdfValueTypeNames->UCharArray;
|
||||
case bke::AttrType::Int32:
|
||||
return pxr::SdfValueTypeNames->IntArray;
|
||||
case bke::AttrType::Float2:
|
||||
return pxr::SdfValueTypeNames->Float2Array;
|
||||
case bke::AttrType::Float3:
|
||||
return pxr::SdfValueTypeNames->Float3Array;
|
||||
case bke::AttrType::String:
|
||||
return pxr::SdfValueTypeNames->StringArray;
|
||||
case bke::AttrType::Bool:
|
||||
return pxr::SdfValueTypeNames->BoolArray;
|
||||
case bke::AttrType::ColorFloat:
|
||||
case bke::AttrType::ColorByte:
|
||||
return use_color3f_type ? pxr::SdfValueTypeNames->Color3fArray :
|
||||
pxr::SdfValueTypeNames->Color4fArray;
|
||||
case bke::AttrType::Quaternion:
|
||||
return pxr::SdfValueTypeNames->QuatfArray;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bke::AttrType> convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type)
|
||||
{
|
||||
static const Map<pxr::SdfValueTypeName, bke::AttrType> type_map = []() {
|
||||
Map<pxr::SdfValueTypeName, bke::AttrType> map;
|
||||
map.add_new(pxr::SdfValueTypeNames->FloatArray, bke::AttrType::Float);
|
||||
map.add_new(pxr::SdfValueTypeNames->Double, bke::AttrType::Float);
|
||||
map.add_new(pxr::SdfValueTypeNames->UCharArray, bke::AttrType::Int8);
|
||||
map.add_new(pxr::SdfValueTypeNames->IntArray, bke::AttrType::Int32);
|
||||
map.add_new(pxr::SdfValueTypeNames->Float2Array, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2dArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2fArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2hArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3dArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3fArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3hArray, bke::AttrType::Float2);
|
||||
map.add_new(pxr::SdfValueTypeNames->Float3Array, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Point3fArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Point3dArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Point3hArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Normal3fArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Normal3dArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Normal3hArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3fArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3hArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3dArray, bke::AttrType::Float3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3fArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3hArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3dArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color4fArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color4hArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color4dArray, bke::AttrType::ColorFloat);
|
||||
map.add_new(pxr::SdfValueTypeNames->BoolArray, bke::AttrType::Bool);
|
||||
map.add_new(pxr::SdfValueTypeNames->QuatfArray, bke::AttrType::Quaternion);
|
||||
map.add_new(pxr::SdfValueTypeNames->QuatdArray, bke::AttrType::Quaternion);
|
||||
map.add_new(pxr::SdfValueTypeNames->QuathArray, bke::AttrType::Quaternion);
|
||||
return map;
|
||||
}();
|
||||
|
||||
const bke::AttrType *value = type_map.lookup_ptr(usd_type);
|
||||
if (value == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
void copy_primvar_to_blender_attribute(const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::UsdTimeCode time,
|
||||
const bke::AttrType data_type,
|
||||
const bke::AttrDomain domain,
|
||||
const OffsetIndices<int> face_indices,
|
||||
bke::MutableAttributeAccessor attributes)
|
||||
{
|
||||
switch (data_type) {
|
||||
case bke::AttrType::Float:
|
||||
copy_primvar_to_blender_buffer<float, float>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::Int8:
|
||||
copy_primvar_to_blender_buffer<uchar, int8_t>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::Int32:
|
||||
copy_primvar_to_blender_buffer<int32_t, int>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::Float2:
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec2f, float2>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::Float3:
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec3f, float3>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::ColorFloat: {
|
||||
const pxr::SdfValueTypeName pv_type = primvar.GetTypeName();
|
||||
if (ELEM(pv_type,
|
||||
pxr::SdfValueTypeNames->Color3fArray,
|
||||
pxr::SdfValueTypeNames->Color3hArray,
|
||||
pxr::SdfValueTypeNames->Color3dArray))
|
||||
{
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec3f, ColorGeometry4f>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
}
|
||||
else {
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec4f, ColorGeometry4f>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
}
|
||||
} break;
|
||||
case bke::AttrType::Bool:
|
||||
copy_primvar_to_blender_buffer<bool, bool>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
case bke::AttrType::Quaternion:
|
||||
copy_primvar_to_blender_buffer<pxr::GfQuatf, math::Quaternion>(
|
||||
primvar, time, data_type, domain, face_indices, attributes);
|
||||
break;
|
||||
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void copy_blender_attribute_to_primvar(const GVArray &attribute,
|
||||
const bke::AttrType data_type,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer)
|
||||
{
|
||||
switch (data_type) {
|
||||
case bke::AttrType::Float:
|
||||
copy_blender_buffer_to_primvar<float, float>(
|
||||
attribute.typed<float>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::Int8:
|
||||
copy_blender_buffer_to_primvar<int8_t, uchar>(
|
||||
attribute.typed<int8_t>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::Int32:
|
||||
copy_blender_buffer_to_primvar<int, int32_t>(
|
||||
attribute.typed<int>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::Float2:
|
||||
copy_blender_buffer_to_primvar<float2, pxr::GfVec2f>(
|
||||
attribute.typed<float2>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::Float3:
|
||||
copy_blender_buffer_to_primvar<float3, pxr::GfVec3f>(
|
||||
attribute.typed<float3>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::Bool:
|
||||
copy_blender_buffer_to_primvar<bool, bool>(
|
||||
attribute.typed<bool>(), time, primvar, value_writer);
|
||||
break;
|
||||
case bke::AttrType::ColorFloat:
|
||||
if (primvar.GetTypeName() == pxr::SdfValueTypeNames->Color3fArray) {
|
||||
copy_blender_buffer_to_primvar<ColorGeometry4f, pxr::GfVec3f>(
|
||||
attribute.typed<ColorGeometry4f>(), time, primvar, value_writer);
|
||||
}
|
||||
else {
|
||||
copy_blender_buffer_to_primvar<ColorGeometry4f, pxr::GfVec4f>(
|
||||
attribute.typed<ColorGeometry4f>(), time, primvar, value_writer);
|
||||
}
|
||||
colorspace_apply_to_prim(primvar.GetAttr().GetPrim());
|
||||
break;
|
||||
case bke::AttrType::ColorByte:
|
||||
if (primvar.GetTypeName() == pxr::SdfValueTypeNames->Color3fArray) {
|
||||
copy_blender_buffer_to_primvar<ColorGeometry4b, pxr::GfVec3f>(
|
||||
attribute.typed<ColorGeometry4b>(), time, primvar, value_writer);
|
||||
}
|
||||
else {
|
||||
copy_blender_buffer_to_primvar<ColorGeometry4b, pxr::GfVec4f>(
|
||||
attribute.typed<ColorGeometry4b>(), time, primvar, value_writer);
|
||||
}
|
||||
colorspace_apply_to_prim(primvar.GetAttr().GetPrim());
|
||||
break;
|
||||
case bke::AttrType::Quaternion:
|
||||
copy_blender_buffer_to_primvar<math::Quaternion, pxr::GfQuatf>(
|
||||
attribute.typed<math::Quaternion>(), time, primvar, value_writer);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,320 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_colorspace_utils.hh"
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_generic_virtual_array.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#include "IO_validate.hh"
|
||||
|
||||
#include <pxr/base/gf/quatf.h>
|
||||
#include <pxr/base/gf/vec2f.h>
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/base/vt/array.h>
|
||||
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
#include <pxr/usd/usd/timeCode.h>
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
#include <pxr/usd/usdUtils/sparseValueWriter.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace usdtokens {
|
||||
inline const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal);
|
||||
}
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/* Until we can use C++20, implement our own version of std::is_layout_compatible.
|
||||
* Types with compatible layouts can be exchanged much more efficiently than otherwise.
|
||||
*/
|
||||
template<class T, class U> struct is_layout_compatible : std::false_type {};
|
||||
|
||||
template<> struct is_layout_compatible<float2, pxr::GfVec2f> : std::true_type {};
|
||||
template<> struct is_layout_compatible<float3, pxr::GfVec3f> : std::true_type {};
|
||||
|
||||
template<> struct is_layout_compatible<pxr::GfVec2f, float2> : std::true_type {};
|
||||
template<> struct is_layout_compatible<pxr::GfVec3f, float3> : std::true_type {};
|
||||
|
||||
/* Conversion utilities to convert a Blender type to an USD type. */
|
||||
template<typename From, typename To> inline To convert_value(const From value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
template<> inline pxr::GfVec2f convert_value(const float2 value)
|
||||
{
|
||||
return pxr::GfVec2f(value[0], value[1]);
|
||||
}
|
||||
template<> inline pxr::GfVec3f convert_value(const float3 value)
|
||||
{
|
||||
return pxr::GfVec3f(value[0], value[1], value[2]);
|
||||
}
|
||||
template<> inline pxr::GfVec3f convert_value(const ColorGeometry4f value)
|
||||
{
|
||||
return pxr::GfVec3f(value.r, value.g, value.b);
|
||||
}
|
||||
template<> inline pxr::GfVec4f convert_value(const ColorGeometry4f value)
|
||||
{
|
||||
return pxr::GfVec4f(value.r, value.g, value.b, value.a);
|
||||
}
|
||||
template<> inline pxr::GfVec3f convert_value(const ColorGeometry4b value)
|
||||
{
|
||||
ColorGeometry4f color4f = color::decode(value);
|
||||
return pxr::GfVec3f(color4f.r, color4f.g, color4f.b);
|
||||
}
|
||||
template<> inline pxr::GfVec4f convert_value(const ColorGeometry4b value)
|
||||
{
|
||||
ColorGeometry4f color4f = color::decode(value);
|
||||
return pxr::GfVec4f(color4f.r, color4f.g, color4f.b, color4f.a);
|
||||
}
|
||||
template<> inline pxr::GfQuatf convert_value(const math::Quaternion value)
|
||||
{
|
||||
return pxr::GfQuatf(value.w, value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
template<> inline float2 convert_value(const pxr::GfVec2f value)
|
||||
{
|
||||
return float2(value[0], value[1]);
|
||||
}
|
||||
template<> inline float3 convert_value(const pxr::GfVec3f value)
|
||||
{
|
||||
return float3(value[0], value[1], value[2]);
|
||||
}
|
||||
template<> inline ColorGeometry4f convert_value(const pxr::GfVec3f value)
|
||||
{
|
||||
return ColorGeometry4f(value[0], value[1], value[2], 1.0f);
|
||||
}
|
||||
template<> inline ColorGeometry4f convert_value(const pxr::GfVec4f value)
|
||||
{
|
||||
return ColorGeometry4f(value[0], value[1], value[2], value[3]);
|
||||
}
|
||||
template<> inline math::Quaternion convert_value(const pxr::GfQuatf value)
|
||||
{
|
||||
const pxr::GfVec3f &img = value.GetImaginary();
|
||||
return math::Quaternion(value.GetReal(), img[0], img[1], img[2]);
|
||||
}
|
||||
|
||||
template<class T> struct is_vt_array : std::false_type {};
|
||||
template<class T> struct is_vt_array<pxr::VtArray<T>> : std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd(const bke::AttrType blender_type,
|
||||
bool use_color3f_type = false);
|
||||
|
||||
std::optional<bke::AttrType> convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type);
|
||||
|
||||
/**
|
||||
* Set the USD attribute to the provided value at the given time. The value will be written
|
||||
* sparsely.
|
||||
*/
|
||||
template<typename USDT>
|
||||
void set_attribute(const pxr::UsdAttribute &attr,
|
||||
const USDT value,
|
||||
pxr::UsdTimeCode time,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer)
|
||||
{
|
||||
/* This overload should only be use with non-VtArray types. If it is not, then that indicates
|
||||
* an issue on the caller side, usually because of using a const reference rather than non-const
|
||||
* for the `value` parameter. */
|
||||
static_assert(!detail::is_vt_array<USDT>::value, "Wrong set_attribute overload selected.");
|
||||
|
||||
if (!attr.HasValue()) {
|
||||
attr.Set(value, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
value_writer.SetAttribute(attr, pxr::VtValue(value), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the USD attribute to the provided array value at the given time. The value will be written
|
||||
* sparsely. For efficiency, this function swaps out the given value, leaving it empty, so it can
|
||||
* leverage the USD API where no additional copy of the data is required. */
|
||||
template<typename USDT>
|
||||
void set_attribute(const pxr::UsdAttribute &attr,
|
||||
pxr::VtArray<USDT> &value,
|
||||
pxr::UsdTimeCode time,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer)
|
||||
{
|
||||
if (!attr.HasValue()) {
|
||||
attr.Set(value, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
pxr::VtValue val = pxr::VtValue::Take(value);
|
||||
value_writer.SetAttribute(attr, &val, time);
|
||||
}
|
||||
|
||||
/* Copy a typed Blender attribute array into a typed USD primvar attribute. */
|
||||
template<typename BlenderT, typename USDT>
|
||||
void copy_blender_buffer_to_primvar(const VArray<BlenderT> &buffer,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer)
|
||||
{
|
||||
constexpr bool is_same = std::is_same_v<BlenderT, USDT>;
|
||||
constexpr bool is_compatible = detail::is_layout_compatible<BlenderT, USDT>::value;
|
||||
|
||||
pxr::VtArray<USDT> usd_data;
|
||||
if (const std::optional<BlenderT> value = buffer.get_if_single()) {
|
||||
usd_data.assign(buffer.size(), detail::convert_value<BlenderT, USDT>(*value));
|
||||
}
|
||||
else {
|
||||
const VArraySpan<BlenderT> data(buffer);
|
||||
if constexpr (is_same || is_compatible) {
|
||||
usd_data.assign(data.template cast<USDT>().begin(), data.template cast<USDT>().end());
|
||||
}
|
||||
else {
|
||||
usd_data.resize(data.size());
|
||||
for (const int i : data.index_range()) {
|
||||
usd_data[i] = detail::convert_value<BlenderT, USDT>(data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_attribute(primvar, usd_data, time, value_writer);
|
||||
}
|
||||
|
||||
void copy_blender_attribute_to_primvar(const GVArray &attribute,
|
||||
const bke::AttrType data_type,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer);
|
||||
|
||||
template<typename T>
|
||||
pxr::VtArray<T> get_primvar_array(const pxr::UsdGeomPrimvar &primvar, const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::VtValue primvar_val;
|
||||
if (!primvar.ComputeFlattened(&primvar_val, time)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return primvar_val.Cast<pxr::VtArray<T>>().template UncheckedGet<pxr::VtArray<T>>();
|
||||
}
|
||||
|
||||
inline void set_single_value(bke::MutableAttributeAccessor attributes,
|
||||
const StringRef attr_name,
|
||||
const bke::AttrDomain domain,
|
||||
const bke::AttrType data_type,
|
||||
const bke::AttributeInit &value)
|
||||
{
|
||||
if (!attributes.contains(attr_name)) {
|
||||
attributes.add(attr_name, domain, data_type, value);
|
||||
}
|
||||
else {
|
||||
attributes.assign_data(attr_name, value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename USDT, typename BlenderT>
|
||||
void copy_primvar_to_blender_buffer(const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::UsdTimeCode time,
|
||||
const bke::AttrType data_type,
|
||||
const bke::AttrDomain domain,
|
||||
const OffsetIndices<int> faces,
|
||||
bke::MutableAttributeAccessor attributes)
|
||||
{
|
||||
const pxr::VtArray<USDT> usd_data = get_primvar_array<USDT>(primvar, time);
|
||||
if (usd_data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr bool is_same = std::is_same_v<USDT, BlenderT>;
|
||||
constexpr bool is_compatible = detail::is_layout_compatible<USDT, BlenderT>::value;
|
||||
constexpr bool is_color = std::is_same_v<BlenderT, ColorGeometry4f>;
|
||||
|
||||
const pxr::TfToken pv_interp = primvar.GetInterpolation();
|
||||
const pxr::TfToken pv_name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName());
|
||||
const StringRef attr_name = pv_name.GetText();
|
||||
|
||||
/* Map constant interpolation to single-value attributes. */
|
||||
if (pv_interp == pxr::UsdGeomTokens->constant) {
|
||||
BlenderT value = detail::convert_value<USDT, BlenderT>(usd_data[0]);
|
||||
if constexpr (is_color) {
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), value);
|
||||
}
|
||||
set_single_value(attributes, attr_name, domain, data_type, bke::AttributeInitValue(value));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<BlenderT> attribute_writer =
|
||||
attributes.lookup_or_add_for_write_span<BlenderT>(pv_name.GetText(), domain);
|
||||
MutableSpan<BlenderT> attribute = attribute_writer.span;
|
||||
|
||||
if (pv_interp == pxr::UsdGeomTokens->faceVarying) {
|
||||
if (!faces.is_empty()) {
|
||||
/* Reverse the index order. */
|
||||
for (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
for (int j : face.index_range()) {
|
||||
const int rev_index = face.last(j);
|
||||
attribute[face.start() + j] = validate::index_in_range(rev_index, usd_data.size()) ?
|
||||
detail::convert_value<USDT, BlenderT>(
|
||||
usd_data[rev_index]) :
|
||||
BlenderT();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if constexpr (is_same || is_compatible) {
|
||||
const Span<USDT> src(usd_data.data(), usd_data.size());
|
||||
attribute.copy_from(src.template cast<BlenderT>());
|
||||
}
|
||||
else {
|
||||
for (const int64_t i : attribute.index_range()) {
|
||||
attribute[i] = detail::convert_value<USDT, BlenderT>(usd_data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Assume direct one-to-one mapping. */
|
||||
if (usd_data.size() == attribute.size()) {
|
||||
if constexpr (is_same || is_compatible) {
|
||||
const Span<USDT> src(usd_data.data(), usd_data.size());
|
||||
attribute.copy_from(src.template cast<BlenderT>());
|
||||
}
|
||||
else {
|
||||
for (const int64_t i : attribute.index_range()) {
|
||||
attribute[i] = detail::convert_value<USDT, BlenderT>(usd_data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (is_color) {
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), attribute);
|
||||
}
|
||||
|
||||
attribute_writer.finish();
|
||||
}
|
||||
|
||||
void copy_primvar_to_blender_attribute(const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::UsdTimeCode time,
|
||||
const bke::AttrType data_type,
|
||||
const bke::AttrDomain domain,
|
||||
const OffsetIndices<int> face_indices,
|
||||
bke::MutableAttributeAccessor attributes);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,467 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_blend_shape_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdSkel/animMapper.h>
|
||||
#include <pxr/usd/usdSkel/animation.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/blendShape.h>
|
||||
|
||||
#include "DNA_key_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "BKE_key.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace usdtokens {
|
||||
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken joint1("joint1", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken Skel("Skel", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helper struct to facilitate merging blend shape weights time
|
||||
* samples from multiple meshes to a single skeleton animation. */
|
||||
struct BlendShapeMergeInfo {
|
||||
pxr::VtTokenArray src_blend_shapes;
|
||||
pxr::UsdAttribute src_weights_attr;
|
||||
/* Remap blend shape weight array from the
|
||||
* source order to the destination order. */
|
||||
pxr::UsdSkelAnimMapper anim_map;
|
||||
|
||||
void init_anim_map(const pxr::VtTokenArray &dst_blend_shapes)
|
||||
{
|
||||
anim_map = pxr::UsdSkelAnimMapper(src_blend_shapes, dst_blend_shapes);
|
||||
}
|
||||
};
|
||||
|
||||
/* Helper function to avoid name collisions when merging blend shape names from
|
||||
* multiple meshes to a single skeleton.
|
||||
*
|
||||
* Attempt to add the given name to the 'names' set as a unique entry, modifying
|
||||
* the name with a numerical suffix if necessary, and return the unique name that
|
||||
* was added to the set. */
|
||||
std::string add_unique_name(Set<std::string> &names, const std::string &name)
|
||||
{
|
||||
std::string unique_name = name;
|
||||
int suffix = 2;
|
||||
while (names.contains(unique_name)) {
|
||||
unique_name = name + std::to_string(suffix++);
|
||||
}
|
||||
names.add(unique_name);
|
||||
return unique_name;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
pxr::TfToken TempBlendShapeWeightsPrimvarName("temp:weights", pxr::TfToken::Immortal);
|
||||
|
||||
void ensure_blend_shape_skeleton(pxr::UsdStageRefPtr stage, pxr::UsdPrim &mesh_prim)
|
||||
{
|
||||
if (!stage || !mesh_prim) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
|
||||
|
||||
if (!skel_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to mesh prim %s",
|
||||
mesh_prim.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelSkeleton skel;
|
||||
if (!skel_api.GetSkeleton(&skel)) {
|
||||
pxr::SdfPath skel_path = mesh_prim.GetParent().GetPath().AppendChild(usdtokens::Skel);
|
||||
skel = pxr::UsdSkelSkeleton::Define(stage, skel_path);
|
||||
|
||||
if (!skel) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't find or create skeleton bound to mesh prim %s",
|
||||
mesh_prim.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
skel_api.CreateSkeletonRel().AddTarget(skel.GetPath());
|
||||
|
||||
/* Initialize the skeleton. */
|
||||
pxr::VtMatrix4dArray bind_transforms(1, pxr::GfMatrix4d(1.0));
|
||||
pxr::VtMatrix4dArray rest_transforms(1, pxr::GfMatrix4d(1.0));
|
||||
skel.CreateBindTransformsAttr().Set(bind_transforms);
|
||||
skel.GetRestTransformsAttr().Set(rest_transforms);
|
||||
|
||||
/* Some DCCs seem to require joint names to bind the
|
||||
* skeleton to blend-shapes. */
|
||||
pxr::VtTokenArray joints({usdtokens::joint1});
|
||||
skel.CreateJointsAttr().Set(joints);
|
||||
}
|
||||
|
||||
pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).GetPrimvar(
|
||||
TempBlendShapeWeightsPrimvarName);
|
||||
|
||||
if (!temp_weights_attr) {
|
||||
/* No need to create the animation. */
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::SdfPath anim_path = skel.GetPath().AppendChild(usdtokens::Anim);
|
||||
pxr::UsdSkelAnimation anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
|
||||
|
||||
if (!anim) {
|
||||
CLOG_WARN(&LOG, "Couldn't define animation at path %s", anim_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtTokenArray blendshape_names;
|
||||
skel_api.GetBlendShapesAttr().Get(&blendshape_names);
|
||||
anim.CreateBlendShapesAttr().Set(blendshape_names);
|
||||
|
||||
std::vector<double> times;
|
||||
temp_weights_attr.GetTimeSamples(×);
|
||||
|
||||
pxr::UsdAttribute anim_weights_attr = anim.CreateBlendShapeWeightsAttr();
|
||||
|
||||
pxr::VtFloatArray weights;
|
||||
for (const double time : times) {
|
||||
if (temp_weights_attr.Get(&weights, time)) {
|
||||
anim_weights_attr.Set(weights, time);
|
||||
}
|
||||
}
|
||||
|
||||
/* Next, set the animation source on the skeleton. */
|
||||
|
||||
skel_api = pxr::UsdSkelBindingAPI::Apply(skel.GetPrim());
|
||||
|
||||
if (!skel_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to skeleton prim %s",
|
||||
skel.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skel_api.CreateAnimationSourceRel().AddTarget(pxr::SdfPath(usdtokens::Anim))) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't set animation source on skeleton %s",
|
||||
skel.GetPath().GetAsString().c_str());
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI(mesh_prim).RemovePrimvar(TempBlendShapeWeightsPrimvarName);
|
||||
}
|
||||
|
||||
const Key *get_mesh_shape_key(const Object *obj)
|
||||
{
|
||||
BLI_assert(obj);
|
||||
|
||||
if (!obj->data || obj->type != OB_MESH) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Mesh *mesh = id_cast<const Mesh *>(obj->data);
|
||||
|
||||
return mesh->key;
|
||||
}
|
||||
|
||||
bool is_mesh_with_shape_keys(const Object *obj)
|
||||
{
|
||||
const Key *key = get_mesh_shape_key(obj);
|
||||
return key && key->totkey > 0 && key->type == KEY_RELATIVE;
|
||||
}
|
||||
|
||||
void create_blend_shapes(pxr::UsdStageRefPtr stage,
|
||||
const Object *obj,
|
||||
const pxr::UsdPrim &mesh_prim,
|
||||
bool allow_unicode)
|
||||
{
|
||||
const Key *key = get_mesh_shape_key(obj);
|
||||
|
||||
if (!(key && mesh_prim)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
|
||||
|
||||
if (!skel_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to mesh prim %s",
|
||||
mesh_prim.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtTokenArray blendshape_names;
|
||||
std::vector<pxr::SdfPath> blendshape_paths;
|
||||
|
||||
/* Get the basis, which we'll use to calculate offsets. */
|
||||
KeyBlock *basis_key = static_cast<KeyBlock *>(key->block.first);
|
||||
|
||||
if (!basis_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
int basis_totelem = basis_key->totelem;
|
||||
|
||||
for (KeyBlock &kb : key->block) {
|
||||
if (&kb == basis_key) {
|
||||
/* Skip the basis. */
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::TfToken name(make_safe_name(kb.name, allow_unicode));
|
||||
blendshape_names.push_back(name);
|
||||
|
||||
pxr::SdfPath path = mesh_prim.GetPath().AppendChild(name);
|
||||
blendshape_paths.push_back(path);
|
||||
|
||||
pxr::UsdSkelBlendShape blendshape = pxr::UsdSkelBlendShape::Define(stage, path);
|
||||
|
||||
pxr::UsdAttribute offsets_attr = blendshape.CreateOffsetsAttr();
|
||||
|
||||
/* Some applications, like Houdini, don't render blend shapes unless the point
|
||||
* indices are set, so we always create this attribute, even when every index
|
||||
* is included. */
|
||||
pxr::UsdAttribute point_indices_attr = blendshape.CreatePointIndicesAttr();
|
||||
|
||||
pxr::VtVec3fArray offsets(kb.totelem);
|
||||
pxr::VtIntArray indices(kb.totelem);
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
|
||||
const float (*fp)[3] = static_cast<float (*)[3]>(kb.data);
|
||||
|
||||
const float (*basis_fp)[3] = static_cast<float (*)[3]>(basis_key->data);
|
||||
|
||||
for (int i = 0; i < kb.totelem; ++i) {
|
||||
/* Subtract the key positions from the
|
||||
* basis positions to get the offsets. */
|
||||
sub_v3_v3v3(offsets[i].data(), fp[i], basis_fp[i]);
|
||||
}
|
||||
|
||||
offsets_attr.Set(offsets);
|
||||
point_indices_attr.Set(indices);
|
||||
}
|
||||
|
||||
/* Set the blend-shape names and targets on the shape. */
|
||||
pxr::UsdAttribute blendshape_attr = skel_api.CreateBlendShapesAttr();
|
||||
blendshape_attr.Set(blendshape_names);
|
||||
skel_api.CreateBlendShapeTargetsRel().SetTargets(blendshape_paths);
|
||||
|
||||
/* Some DCCs seem to require joint indices and weights to
|
||||
* bind the skeleton for blend-shapes, so we create these primvars, if needed. */
|
||||
|
||||
if (!skel_api.GetJointIndicesAttr().HasAuthoredValue()) {
|
||||
pxr::VtArray<int> joint_indices(basis_totelem, 0);
|
||||
skel_api.CreateJointIndicesPrimvar(false, 1).GetAttr().Set(joint_indices);
|
||||
}
|
||||
|
||||
if (!skel_api.GetJointWeightsAttr().HasAuthoredValue()) {
|
||||
pxr::VtArray<float> joint_weights(basis_totelem, 1.0f);
|
||||
skel_api.CreateJointWeightsPrimvar(false, 1).GetAttr().Set(joint_weights);
|
||||
}
|
||||
}
|
||||
|
||||
pxr::VtFloatArray get_blendshape_weights(const Key *key)
|
||||
{
|
||||
BLI_assert(key);
|
||||
|
||||
pxr::VtFloatArray weights;
|
||||
|
||||
for (KeyBlock &kb : key->block) {
|
||||
if (&kb == key->block.first) {
|
||||
/* Skip the first key, which is the basis. */
|
||||
continue;
|
||||
}
|
||||
weights.push_back(kb.curval);
|
||||
}
|
||||
|
||||
return weights;
|
||||
}
|
||||
|
||||
void remap_blend_shape_anim(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath &skel_path,
|
||||
const pxr::SdfPathSet &mesh_paths)
|
||||
{
|
||||
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Get(stage, skel_path);
|
||||
|
||||
if (!skel_api) {
|
||||
CLOG_WARN(&LOG, "Couldn't get skeleton from path %s", skel_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use existing animation if possible, otherwise create a new one. */
|
||||
pxr::UsdPrim anim_prim;
|
||||
pxr::UsdSkelAnimation anim;
|
||||
if (skel_api.GetAnimationSource(&anim_prim)) {
|
||||
anim = pxr::UsdSkelAnimation(anim_prim);
|
||||
}
|
||||
else {
|
||||
pxr::SdfPath anim_path = skel_path.AppendChild(usdtokens::Anim);
|
||||
anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
|
||||
}
|
||||
|
||||
if (!anim) {
|
||||
CLOG_WARN(&LOG, "Couldn't get animation under skeleton %s", skel_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<BlendShapeMergeInfo> merge_info;
|
||||
|
||||
/* We are merging blend shape names and weights from multiple
|
||||
* meshes to a single animation. In case of name collisions,
|
||||
* we must generate unique blend shape names for the merged
|
||||
* result. This set keeps track of the unique names that will
|
||||
* be combined on the animation. */
|
||||
Set<std::string> merged_names;
|
||||
|
||||
/* Iterate over all the meshes, generate unique blend shape names in case of name
|
||||
* collisions and set up the information we will need to merge the results. */
|
||||
for (const pxr::SdfPath &mesh_path : mesh_paths) {
|
||||
|
||||
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(mesh_path);
|
||||
pxr::UsdSkelBindingAPI mesh_skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
|
||||
if (!mesh_skel_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to mesh prim %s",
|
||||
mesh_path.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the blend shape names for this mesh. */
|
||||
pxr::UsdAttribute blend_shapes_attr = mesh_skel_api.GetBlendShapesAttr();
|
||||
|
||||
if (!blend_shapes_attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::VtTokenArray names;
|
||||
if (!mesh_skel_api.GetBlendShapesAttr().Get(&names)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ensure the names are unique. */
|
||||
pxr::VtTokenArray unique_names;
|
||||
|
||||
for (const pxr::TfToken &name : names.AsConst()) {
|
||||
std::string unique = add_unique_name(merged_names, name.GetString());
|
||||
unique_names.push_back(pxr::TfToken(unique));
|
||||
}
|
||||
|
||||
/* Set the unique names back on the mesh. */
|
||||
mesh_skel_api.GetBlendShapesAttr().Set(unique_names);
|
||||
|
||||
/* Look up the temporary weights time sample we wrote to the mesh. */
|
||||
const pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).GetPrimvar(
|
||||
TempBlendShapeWeightsPrimvarName);
|
||||
|
||||
if (!temp_weights_attr) {
|
||||
/* No need to create the animation. Shouldn't usually happen. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Generate information we will need to merge the weight samples below. */
|
||||
merge_info.append(BlendShapeMergeInfo());
|
||||
merge_info.last().src_blend_shapes = unique_names;
|
||||
merge_info.last().src_weights_attr = temp_weights_attr;
|
||||
}
|
||||
|
||||
if (merged_names.is_empty()) {
|
||||
/* No blend shape names were collected. Shouldn't usually happen. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy the list of name strings to a list of tokens, since we need to work with tokens. */
|
||||
pxr::VtTokenArray skel_blend_shape_names;
|
||||
for (const std::string &name : merged_names) {
|
||||
skel_blend_shape_names.push_back(pxr::TfToken(name));
|
||||
}
|
||||
|
||||
/* Initialize the merge info structs with the list of names on the merged animation. */
|
||||
for (BlendShapeMergeInfo &info : merge_info) {
|
||||
info.init_anim_map(skel_blend_shape_names);
|
||||
}
|
||||
|
||||
/* Set the names on the animation prim. */
|
||||
anim.CreateBlendShapesAttr().Set(skel_blend_shape_names);
|
||||
|
||||
pxr::UsdAttribute dst_weights_attr = anim.CreateBlendShapeWeightsAttr();
|
||||
|
||||
/* Merge the weight time samples. */
|
||||
std::vector<double> times;
|
||||
merge_info.first().src_weights_attr.GetTimeSamples(×);
|
||||
|
||||
if (times.empty()) {
|
||||
/* Times may be empty if there is only a default value for the weights,
|
||||
* so we read the default. */
|
||||
times.push_back(pxr::UsdTimeCode::Default().GetValue());
|
||||
}
|
||||
|
||||
pxr::VtFloatArray dst_weights;
|
||||
|
||||
for (const double time : times) {
|
||||
for (const BlendShapeMergeInfo &info : merge_info) {
|
||||
pxr::VtFloatArray src_weights;
|
||||
if (info.src_weights_attr.Get(&src_weights, time)) {
|
||||
if (!info.anim_map.Remap(src_weights.AsConst(), &dst_weights)) {
|
||||
CLOG_WARN(&LOG, "Failed remapping blend shape weights");
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Set the merged weights on the animation. */
|
||||
dst_weights_attr.Set(dst_weights, time);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *get_shape_key_basis_mesh(Object *obj)
|
||||
{
|
||||
if (!obj || !obj->data || obj->type != OB_MESH) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If we're exporting blend shapes, we export the unmodified mesh with
|
||||
* the verts in the basis key positions. */
|
||||
const Mesh *mesh = BKE_object_get_pre_modified_mesh(obj);
|
||||
|
||||
if (!mesh || !mesh->key || !mesh->key->block.first) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const KeyBlock *basis = reinterpret_cast<KeyBlock *>(mesh->key->block.first);
|
||||
|
||||
if (mesh->verts_num != basis->totelem) {
|
||||
CLOG_WARN(&LOG, "Vertex and shape key element count mismatch for mesh %s", obj->id.name + 2);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Make a copy of the mesh so we can update the verts to the basis shape. */
|
||||
Mesh *temp_mesh = BKE_mesh_copy_for_eval(*mesh);
|
||||
|
||||
/* Update the verts. */
|
||||
BKE_keyblock_convert_to_mesh(basis, temp_mesh->vert_positions_for_write());
|
||||
|
||||
return temp_mesh;
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,106 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Key;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Name of the temporary USD primvar for storing blend shape
|
||||
* weight time samples on the mesh before they are copied
|
||||
* to the bound skeleton. */
|
||||
extern pxr::TfToken TempBlendShapeWeightsPrimvarName;
|
||||
|
||||
/**
|
||||
* Return the shape key on the given mesh object.
|
||||
*
|
||||
* \param obj: The mesh object
|
||||
* \return The shape key on the given object's mesh data, or
|
||||
* null if the object isn't a mesh.
|
||||
*/
|
||||
const Key *get_mesh_shape_key(const Object *obj);
|
||||
|
||||
/**
|
||||
* Query whether the given object is a mesh with relative
|
||||
* shape keys.
|
||||
*
|
||||
* \param obj: The mesh object
|
||||
* \return True if the object is a mesh with shape keys, false otherwise
|
||||
*/
|
||||
bool is_mesh_with_shape_keys(const Object *obj);
|
||||
|
||||
/**
|
||||
* Convert shape keys on the given object to USD blend shapes. The blend-shapes
|
||||
* will be added to the stage as children of the given USD mesh prim. The blend-shape
|
||||
* names and targets will also be set as properties on the primitive.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param obj: The mesh object whose shape keys will be converted to blend shapes
|
||||
* \param mesh_prim: The USD mesh that will be assigned the blend shape targets
|
||||
* \param allow_unicode: Whether to allow unicode encoded characters in the blend shape name
|
||||
*/
|
||||
void create_blend_shapes(pxr::UsdStageRefPtr stage,
|
||||
const Object *obj,
|
||||
const pxr::UsdPrim &mesh_prim,
|
||||
bool allow_unicode);
|
||||
|
||||
/**
|
||||
* Return the current weight values of the given key.
|
||||
*
|
||||
* \param key: The key whose values will be queried
|
||||
* \return The array of key values.
|
||||
*/
|
||||
pxr::VtFloatArray get_blendshape_weights(const Key *key);
|
||||
|
||||
/**
|
||||
* USD implementations expect that a mesh with blend shape targets
|
||||
* be bound to a skeleton with an animation that provides the blend
|
||||
* shape weights. If the given mesh is not already bound to a skeleton
|
||||
* this function will create a dummy skeleton with a single joint and
|
||||
* will bind it to the mesh. This is typically required if the source
|
||||
* Blender mesh has shape keys but not an armature deformer.
|
||||
*
|
||||
* This function will also create a skel animation prim as a child of
|
||||
* the skeleton and will copy the weight time samples from a temporary
|
||||
* primvar on the mesh to the animation prim.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param mesh_prim: The USD mesh to which the skeleton will be bound
|
||||
*/
|
||||
void ensure_blend_shape_skeleton(pxr::UsdStageRefPtr stage, pxr::UsdPrim &mesh_prim);
|
||||
|
||||
/**
|
||||
* When multiple meshes with blend shape animations are bound to one skeleton, USD implementations
|
||||
* typically expect these animations to be combined in a single animation on the skeleton. This
|
||||
* function creates an animation prim as a child of the skeleton and merges the blend shape time
|
||||
* samples from multiple meshes in a single attribute on the animation. Merging the weight samples
|
||||
* requires handling blend shape name collisions by generating unique names for the combined
|
||||
* result.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param skel_path: Path to the skeleton
|
||||
* \param mesh_paths: Paths to one or more mesh primitives bound to the skeleton
|
||||
*/
|
||||
void remap_blend_shape_anim(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath &skel_path,
|
||||
const pxr::SdfPathSet &mesh_paths);
|
||||
|
||||
/**
|
||||
* If the given object is a mesh with shape keys, return a copy of the object's pre-modified mesh
|
||||
* with its verts in the shape key basis positions. The returned mesh must be freed by the caller.
|
||||
*
|
||||
* \param obj: The mesh object with shape keys
|
||||
* \return A new mesh corresponding to the shape key basis shape, or null if the object
|
||||
* isn't a mesh or has no shape keys.
|
||||
*/
|
||||
Mesh *get_shape_key_basis_mesh(Object *obj);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
938
blender-5.2.0/source/blender/io/usd/intern/usd_capi_export.cc
Normal file
938
blender-5.2.0/source/blender/io/usd/intern/usd_capi_export.cc
Normal file
@@ -0,0 +1,938 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
#include "usd.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_instancing_utils.hh"
|
||||
#include "usd_light_convert.hh"
|
||||
#include "usd_private.hh"
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/sdf/assetPath.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdGeom/xform.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usdUI/accessibilityAPI.h>
|
||||
#include <pxr/usd/usdUtils/usdzPackage.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
#include "BKE_blender_version.h"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_image_save.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_report.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "ED_util.hh"
|
||||
|
||||
#include <IMB_imbuf.hh>
|
||||
#include <IMB_imbuf_types.hh>
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct ExportJobData {
|
||||
Main *bmain = nullptr;
|
||||
Depsgraph *depsgraph = nullptr;
|
||||
wmWindowManager *wm = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
|
||||
/** Unarchived_filepath is used for USDA/USDC/USD export. */
|
||||
char unarchived_filepath[FILE_MAX] = {};
|
||||
char usdz_filepath[FILE_MAX] = {};
|
||||
USDExportParams params = {};
|
||||
|
||||
bool export_ok = false;
|
||||
timeit::TimePoint start_time = {};
|
||||
|
||||
bool targets_usdz() const
|
||||
{
|
||||
return usdz_filepath[0] != '\0';
|
||||
}
|
||||
|
||||
const char *export_filepath() const
|
||||
{
|
||||
if (targets_usdz()) {
|
||||
return usdz_filepath;
|
||||
}
|
||||
return unarchived_filepath;
|
||||
}
|
||||
};
|
||||
|
||||
/* Returns true if the given prim path is valid, per
|
||||
* the requirements of the prim path manipulation logic
|
||||
* of the exporter. Also returns true if the path is
|
||||
* the empty string. Returns false otherwise. */
|
||||
static bool prim_path_valid(const std::string &path)
|
||||
{
|
||||
if (path.empty()) {
|
||||
/* Empty paths are ignored in the code,
|
||||
* so they can be passed through. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check path syntax. */
|
||||
std::string errMsg;
|
||||
if (!pxr::SdfPath::IsValidPathString(path, &errMsg)) {
|
||||
WM_global_reportf(
|
||||
RPT_ERROR, "USD Export: invalid path string '%s': %s", path.c_str(), errMsg.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Verify that an absolute prim path can be constructed
|
||||
* from this path string. */
|
||||
|
||||
pxr::SdfPath sdf_path(path);
|
||||
if (!sdf_path.IsAbsolutePath()) {
|
||||
WM_global_reportf(RPT_ERROR, "USD Export: path '%s' is not an absolute path", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sdf_path.IsPrimPath()) {
|
||||
WM_global_reportf(RPT_ERROR, "USD Export: path string '%s' is not a prim path", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform validation of export parameter settings.
|
||||
* \return true if the parameters are valid; returns false otherwise.
|
||||
*
|
||||
* \warning Do not call from worker thread, only from main thread (i.e. before starting the wmJob).
|
||||
*/
|
||||
static bool export_params_valid(const USDExportParams ¶ms)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
if (!prim_path_valid(params.root_prim_path)) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the root Xform primitive, if the Root Prim path has been set
|
||||
* in the export options. In the future, this function can be extended
|
||||
* to author transforms and additional schema data (e.g., model Kind)
|
||||
* on the root prim.
|
||||
*/
|
||||
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams ¶ms)
|
||||
{
|
||||
if (params.root_prim_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomXform root_xf = pxr::UsdGeomXform::Define(stage,
|
||||
pxr::SdfPath(params.root_prim_path));
|
||||
|
||||
if (!root_xf) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomXformCommonAPI xf_api(root_xf.GetPrim());
|
||||
|
||||
if (!xf_api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.convert_scene_units != SceneUnits::Meters) {
|
||||
xf_api.SetScale(pxr::GfVec3f(float(1.0 / get_meters_per_unit(params))));
|
||||
}
|
||||
|
||||
if (params.convert_orientation) {
|
||||
float3x3 mrot;
|
||||
mat3_from_axis_conversion(
|
||||
IO_AXIS_Y, IO_AXIS_Z, params.forward_axis, params.up_axis, mrot.ptr());
|
||||
|
||||
const math::EulerXYZ eul = math::to_euler(math::transpose(mrot));
|
||||
xf_api.SetRotate(pxr::GfVec3f(eul.x().degree(), eul.y().degree(), eul.z().degree()));
|
||||
}
|
||||
|
||||
/* Color-space on the root prim. It's also applied on all individual prims that need
|
||||
* it, but perhaps this is useful to signal the overall color-space of the file. */
|
||||
colorspace_apply_to_prim(root_xf.GetPrim());
|
||||
|
||||
for (const auto &path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) {
|
||||
auto xform = pxr::UsdGeomXform::Define(stage, path);
|
||||
/* Tag generated primitives to allow filtering on import. */
|
||||
xform.GetPrim().SetCustomDataByKey(pxr::TfToken("Blender:generated"), pxr::VtValue(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user has provided an accessibility label and description for the export,
|
||||
* write that information to the exported stage's default prim. This information
|
||||
* will be written with the `UsdUIAccessibilityAPI` under the `default`
|
||||
* namespace. Note: The information will only be added if the label is non-empty.
|
||||
*/
|
||||
static void write_root_accessibility_information(pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams ¶ms)
|
||||
{
|
||||
/* Don't apply the API if both the label and description are empty. */
|
||||
if (params.accessibility_label.empty() && params.accessibility_description.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdUIAccessibilityAPI accessibility_api = pxr::UsdUIAccessibilityAPI::ApplyDefaultAPI(
|
||||
stage->GetDefaultPrim());
|
||||
if (!accessibility_api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params.accessibility_label.empty()) {
|
||||
accessibility_api.CreateLabelAttr().Set(params.accessibility_label);
|
||||
}
|
||||
|
||||
if (!params.accessibility_description.empty()) {
|
||||
accessibility_api.CreateDescriptionAttr().Set(params.accessibility_description);
|
||||
}
|
||||
}
|
||||
|
||||
static void report_job_duration(const ExportJobData *data)
|
||||
{
|
||||
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
|
||||
const char *export_filepath = data->export_filepath();
|
||||
fmt::print("USD export of '{}' took ", export_filepath);
|
||||
timeit::print_duration(duration);
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
static void process_usdz_textures(const ExportJobData *data, const char *path)
|
||||
{
|
||||
const TextureDownscaleSize enum_value = data->params.usdz_downscale_size;
|
||||
if (enum_value == TextureDownscaleSize::Keep) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int image_size = (enum_value == TextureDownscaleSize::Custom) ?
|
||||
data->params.usdz_downscale_custom_size :
|
||||
int(enum_value);
|
||||
|
||||
char texture_path[FILE_MAX];
|
||||
STRNCPY(texture_path, path);
|
||||
BLI_path_append(texture_path, FILE_MAX, "textures");
|
||||
BLI_path_slash_ensure(texture_path, sizeof(texture_path));
|
||||
|
||||
direntry *entries;
|
||||
uint num_files = BLI_filelist_dir_contents(texture_path, &entries);
|
||||
|
||||
for (int index = 0; index < num_files; index++) {
|
||||
/* We can skip checking extensions as this folder is only created
|
||||
* when we're doing a USDZ export. */
|
||||
if (!BLI_is_dir(entries[index].path)) {
|
||||
Image *im = BKE_image_load(data->bmain, entries[index].path);
|
||||
if (!im) {
|
||||
CLOG_WARN(&LOG, "Unable to open file for downscaling: %s", entries[index].path);
|
||||
continue;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
BKE_image_get_size(im, nullptr, &width, &height);
|
||||
const int longest = width >= height ? width : height;
|
||||
const float scale = 1.0 / (float(longest) / float(image_size));
|
||||
|
||||
if (longest > image_size) {
|
||||
const int width_adjusted = float(width) * scale;
|
||||
const int height_adjusted = float(height) * scale;
|
||||
BKE_image_scale(im, width_adjusted, height_adjusted, nullptr);
|
||||
|
||||
ImageSaveOptions opts;
|
||||
|
||||
if (BKE_image_save_options_init(
|
||||
&opts, data->bmain, data->scene, im, nullptr, false, false))
|
||||
{
|
||||
bool result = BKE_image_save(nullptr, data->bmain, im, nullptr, &opts);
|
||||
if (!result) {
|
||||
CLOG_ERROR(&LOG,
|
||||
"Unable to resave '%s' (new size: %dx%d)",
|
||||
data->usdz_filepath,
|
||||
width_adjusted,
|
||||
height_adjusted);
|
||||
}
|
||||
else {
|
||||
CLOG_DEBUG(&LOG,
|
||||
"Downscaled '%s' to %dx%d",
|
||||
entries[index].path,
|
||||
width_adjusted,
|
||||
height_adjusted);
|
||||
}
|
||||
}
|
||||
|
||||
BKE_image_save_options_free(&opts);
|
||||
}
|
||||
|
||||
/* Make sure to free the image so it doesn't stick
|
||||
* around in the library of the open file. */
|
||||
BKE_id_free(data->bmain, static_cast<void *>(im));
|
||||
}
|
||||
}
|
||||
|
||||
BLI_filelist_free(entries, num_files);
|
||||
}
|
||||
|
||||
/**
|
||||
* For usdz export, we must first create a usd/a/c file and then covert it to usdz. In Blender's
|
||||
* case, we first create a usdc file in Blender's temporary working directory, and store the path
|
||||
* to the usdc file in `unarchived_filepath`. This function then does the conversion of that usdc
|
||||
* file into usdz.
|
||||
*
|
||||
* \return true when the conversion from usdc to usdz is successful.
|
||||
*/
|
||||
static bool perform_usdz_conversion(const ExportJobData *data)
|
||||
{
|
||||
char usdc_temp_dir[FILE_MAX], usdc_file[FILE_MAX];
|
||||
BLI_path_split_dir_file(data->unarchived_filepath,
|
||||
usdc_temp_dir,
|
||||
sizeof(usdc_temp_dir),
|
||||
usdc_file,
|
||||
sizeof(usdc_file));
|
||||
|
||||
char usdz_file[FILE_MAX];
|
||||
BLI_path_split_file_part(data->usdz_filepath, usdz_file, FILE_MAX);
|
||||
|
||||
char original_working_dir_buff[FILE_MAX];
|
||||
const char *original_working_dir = BLI_current_working_dir(original_working_dir_buff,
|
||||
sizeof(original_working_dir_buff));
|
||||
/* Buffer is expected to be returned by #BLI_current_working_dir, although in theory other
|
||||
* returns are possible on some platforms, this is not handled by this code. */
|
||||
BLI_assert(original_working_dir == original_working_dir_buff);
|
||||
|
||||
BLI_change_working_dir(usdc_temp_dir);
|
||||
|
||||
process_usdz_textures(data, usdc_temp_dir);
|
||||
|
||||
pxr::UsdUtilsCreateNewUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file);
|
||||
BLI_change_working_dir(original_working_dir);
|
||||
|
||||
char usdz_temp_full_path[FILE_MAX];
|
||||
BLI_path_join(usdz_temp_full_path, FILE_MAX, usdc_temp_dir, usdz_file);
|
||||
|
||||
int result = 0;
|
||||
if (BLI_exists(data->usdz_filepath)) {
|
||||
result = BLI_delete(data->usdz_filepath, false, false);
|
||||
if (result != 0) {
|
||||
BKE_reportf(data->params.worker_status->reports,
|
||||
RPT_ERROR,
|
||||
"USD Export: Unable to delete existing usdz file %s",
|
||||
data->usdz_filepath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath);
|
||||
if (result != 0) {
|
||||
BKE_reportf(data->params.worker_status->reports,
|
||||
RPT_ERROR,
|
||||
"USD Export: Couldn't move new usdz file from temporary location %s to %s",
|
||||
usdz_temp_full_path,
|
||||
data->usdz_filepath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string image_cache_file_path()
|
||||
{
|
||||
char dir_path[FILE_MAX];
|
||||
BLI_path_join(dir_path, sizeof(dir_path), BKE_tempdir_session(), "usd", "image_cache");
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
std::string get_image_cache_file(const std::string &file_name, bool mkdir)
|
||||
{
|
||||
std::string dir_path = image_cache_file_path();
|
||||
if (mkdir) {
|
||||
BLI_dir_create_recursive(dir_path.c_str());
|
||||
}
|
||||
|
||||
char file_path[FILE_MAX];
|
||||
BLI_path_join(file_path, sizeof(file_path), dir_path.c_str(), file_name.c_str());
|
||||
return file_path;
|
||||
}
|
||||
|
||||
std::string cache_image_color(const float color[4])
|
||||
{
|
||||
std::string name = fmt::format("color_{:02X}{:02X}{:02X}.exr",
|
||||
int(color[0] * 255),
|
||||
int(color[1] * 255),
|
||||
int(color[2] * 255));
|
||||
std::string file_path = get_image_cache_file(name);
|
||||
if (BLI_exists(file_path.c_str())) {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
ImBuf *ibuf = IMB_allocImBuf(1, 1, ImBufFlags::FloatData);
|
||||
IMB_rectfill(ibuf, color);
|
||||
ibuf->ftype = IMB_FTYPE_OPENEXR;
|
||||
ibuf->foptions.flag = R_IMF_EXR_CODEC_RLE;
|
||||
|
||||
if (IMB_save_image(ibuf, file_path.c_str(), ImBufFlags::FloatData)) {
|
||||
CLOG_INFO(&LOG, "%s", file_path.c_str());
|
||||
}
|
||||
else {
|
||||
CLOG_ERROR(&LOG, "Can't save %s", file_path.c_str());
|
||||
file_path = "";
|
||||
}
|
||||
IMB_freeImBuf(ibuf);
|
||||
|
||||
return file_path;
|
||||
}
|
||||
|
||||
static void collect_point_instancer_prototypes_and_set_extent(
|
||||
pxr::UsdGeomPointInstancer instancer,
|
||||
const pxr::UsdStageRefPtr &stage,
|
||||
const pxr::SdfPath &wrapper_path,
|
||||
std::vector<pxr::UsdPrim> &proto_list)
|
||||
{
|
||||
/* Compute extent of the current point instancer. */
|
||||
pxr::VtArray<pxr::GfVec3f> extent;
|
||||
instancer.ComputeExtentAtTime(&extent, pxr::UsdTimeCode::Default(), pxr::UsdTimeCode::Default());
|
||||
instancer.CreateExtentAttr().Set(extent);
|
||||
|
||||
pxr::UsdPrim wrapper_prim = stage->GetPrimAtPath(wrapper_path);
|
||||
if (!wrapper_prim || !wrapper_prim.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string real_path_str;
|
||||
|
||||
for (const pxr::SdfPrimSpecHandle &primSpec : wrapper_prim.GetPrimStack()) {
|
||||
if (!primSpec || !primSpec->HasReferences()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const pxr::SdfReference &ref : primSpec->GetReferenceList().GetPrependedItems()) {
|
||||
if (ref.GetAssetPath().empty() && !ref.GetPrimPath().IsEmpty()) {
|
||||
real_path_str = ref.GetPrimPath().GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!real_path_str.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (real_path_str.empty()) {
|
||||
CLOG_WARN(&LOG, "No prototype reference found for: %s", wrapper_path.GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::SdfPath real_path(real_path_str);
|
||||
pxr::UsdPrim proto_prim = stage->GetPrimAtPath(real_path);
|
||||
|
||||
if (!proto_prim || !proto_prim.IsValid()) {
|
||||
CLOG_WARN(&LOG, "Referenced prototype not found at: %s", real_path.GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
proto_list.push_back(proto_prim);
|
||||
proto_list.push_back(wrapper_prim.GetParent());
|
||||
|
||||
std::string doc_message = fmt::format(
|
||||
"This prim is used as a prototype by the PointInstancer \"{}\" so we override the def "
|
||||
"with an \"over\" so that it isn't imaged in the scene, but is available as a prototype "
|
||||
"that can be referenced.",
|
||||
wrapper_prim.GetName().GetString());
|
||||
proto_prim.SetDocumentation(doc_message);
|
||||
|
||||
/* Check if the proto prim itself is a PointInstancer. */
|
||||
if (proto_prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
pxr::UsdGeomPointInstancer nested_instancer(proto_prim);
|
||||
pxr::SdfPathVector nested_targets;
|
||||
if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
|
||||
for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
nested_instancer, stage, nested_wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Also check all children of the proto prim for nested PointInstancers. */
|
||||
for (const pxr::UsdPrim &child : proto_prim.GetAllChildren()) {
|
||||
if (child.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
pxr::UsdGeomPointInstancer nested_instancer(child);
|
||||
pxr::SdfPathVector nested_targets;
|
||||
if (nested_instancer.GetPrototypesRel().GetTargets(&nested_targets)) {
|
||||
for (const pxr::SdfPath &nested_wrapper_path : nested_targets) {
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
nested_instancer, stage, nested_wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms,
|
||||
Depsgraph *depsgraph,
|
||||
const char *filepath)
|
||||
{
|
||||
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath);
|
||||
if (!usd_stage) {
|
||||
return usd_stage;
|
||||
}
|
||||
|
||||
wmJobWorkerStatus *worker_status = params.worker_status;
|
||||
Scene *scene = DEG_get_input_scene(depsgraph);
|
||||
Main *bmain = DEG_get_bmain(depsgraph);
|
||||
|
||||
SubdivModifierDisabler mod_disabler(depsgraph);
|
||||
|
||||
/* If we want to set the subdiv scheme, then we need to the export the mesh
|
||||
* without the subdiv modifier applied. */
|
||||
if (ELEM(params.export_subdiv, SubdivExportMode::Match, SubdivExportMode::Ignore)) {
|
||||
mod_disabler.disable_modifiers();
|
||||
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
||||
}
|
||||
|
||||
/* This whole `export_to_stage` function is assumed to cover about 80% of the whole export
|
||||
* process, from 0.1f to 0.9f. */
|
||||
worker_status->progress = 0.10f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length));
|
||||
usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
|
||||
BKE_blender_version_string());
|
||||
|
||||
/* Set up the stage for animated data. */
|
||||
if (params.export_animation) {
|
||||
usd_stage->SetTimeCodesPerSecond(scene->frames_per_second());
|
||||
usd_stage->SetStartTimeCode(scene->r.sfra);
|
||||
usd_stage->SetEndTimeCode(scene->r.efra);
|
||||
}
|
||||
|
||||
/* For restoring the current frame after exporting animation is done. */
|
||||
const int orig_frame = scene->r.cfra;
|
||||
|
||||
/* Ensure Python types for invoking hooks are registered. */
|
||||
register_hook_converters();
|
||||
|
||||
pxr::VtValue upAxis = pxr::VtValue(pxr::UsdGeomTokens->z);
|
||||
if (params.convert_orientation) {
|
||||
if (params.up_axis == IO_AXIS_X) {
|
||||
upAxis = pxr::VtValue(pxr::UsdGeomTokens->x);
|
||||
}
|
||||
else if (params.up_axis == IO_AXIS_Y) {
|
||||
upAxis = pxr::VtValue(pxr::UsdGeomTokens->y);
|
||||
}
|
||||
}
|
||||
|
||||
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, upAxis);
|
||||
|
||||
const double meters_per_unit = get_meters_per_unit(params);
|
||||
pxr::UsdGeomSetStageMetersPerUnit(usd_stage, meters_per_unit);
|
||||
|
||||
ensure_root_prim(usd_stage, params);
|
||||
|
||||
USDHierarchyIterator iter(bmain, depsgraph, usd_stage, params);
|
||||
|
||||
worker_status->progress = 0.11f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
if (params.export_animation) {
|
||||
/* Writing the animated frames is not 100% of the work, here it's assumed to be 75% of it. */
|
||||
float progress_per_frame = 0.75f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
|
||||
int exported_frame_count = 0;
|
||||
|
||||
for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
|
||||
if (G.is_break || worker_status->stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update the scene for the next frame to render. */
|
||||
scene->r.cfra = int(frame);
|
||||
scene->r.subframe = frame - scene->r.cfra;
|
||||
BKE_scene_graph_update_for_newframe(depsgraph);
|
||||
|
||||
iter.set_export_frame(frame);
|
||||
iter.iterate_and_write();
|
||||
|
||||
/* Check if we need to perform an incremental save. A value of 0 will never trigger. */
|
||||
exported_frame_count++;
|
||||
if (exported_frame_count == params.incremental_frames) {
|
||||
usd_stage->GetRootLayer()->Save();
|
||||
exported_frame_count = 0;
|
||||
}
|
||||
|
||||
worker_status->progress += progress_per_frame;
|
||||
worker_status->do_update = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* If we're not animating, a single iteration over all objects is enough. */
|
||||
iter.iterate_and_write();
|
||||
}
|
||||
|
||||
worker_status->progress = 0.86f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
iter.release_writers();
|
||||
|
||||
if (params.export_shapekeys || params.export_armatures) {
|
||||
iter.process_usd_skel();
|
||||
}
|
||||
|
||||
/* Creating dome lights should be called after writers have
|
||||
* completed, to avoid a name collision when creating the light
|
||||
* prim. */
|
||||
if (params.convert_world_material) {
|
||||
world_material_to_dome_light(params, scene, usd_stage);
|
||||
}
|
||||
|
||||
/* Set the default prim if it doesn't exist */
|
||||
if (!usd_stage->GetDefaultPrim()) {
|
||||
/* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
|
||||
* prim, and is less verbose than getting the PseudoRoot + iterating its children. */
|
||||
for (auto prim : usd_stage->TraverseAll()) {
|
||||
usd_stage->SetDefaultPrim(prim);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write accessibility information to the default prim. */
|
||||
write_root_accessibility_information(usd_stage, params);
|
||||
|
||||
if (params.use_instancing) {
|
||||
process_scene_graph_instances(params, usd_stage);
|
||||
}
|
||||
|
||||
call_export_hooks(depsgraph, &iter, params.worker_status->reports);
|
||||
|
||||
worker_status->progress = 0.88f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
/* Finish up by going back to the keyframe that was current before we started. */
|
||||
if (scene->r.cfra != orig_frame) {
|
||||
scene->r.cfra = orig_frame;
|
||||
BKE_scene_graph_update_for_newframe(depsgraph);
|
||||
}
|
||||
|
||||
worker_status->progress = 0.9f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
return usd_stage;
|
||||
}
|
||||
|
||||
static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
ExportJobData *data = static_cast<ExportJobData *>(customdata);
|
||||
data->export_ok = false;
|
||||
data->start_time = timeit::Clock::now();
|
||||
|
||||
G.is_rendering = true;
|
||||
if (data->wm) {
|
||||
WM_locked_interface_set(data->wm, true);
|
||||
}
|
||||
G.is_break = false;
|
||||
|
||||
worker_status->progress = 0.01f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
/* Evaluate the depsgraph for exporting.
|
||||
*
|
||||
* Note that, unlike with its building, this is expected to be safe to perform from worker
|
||||
* thread, since UI is locked during export, so there should not be any more changes in the Main
|
||||
* original data concurrently done from the main thread at this point. All necessary (deferred)
|
||||
* changes are expected to have been triggered and processed during depsgraph building in
|
||||
* #USD_export. */
|
||||
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
|
||||
|
||||
worker_status->progress = 0.1f;
|
||||
worker_status->do_update = true;
|
||||
data->params.worker_status = worker_status;
|
||||
|
||||
pxr::UsdStageRefPtr usd_stage = export_to_stage(
|
||||
data->params, data->depsgraph, data->unarchived_filepath);
|
||||
if (!usd_stage) {
|
||||
/* This happens when the USD JSON files cannot be found. When that happens,
|
||||
* the USD library doesn't know it has the functionality to write USDA and
|
||||
* USDC files, and creating a new UsdStage fails. */
|
||||
BKE_reportf(worker_status->reports,
|
||||
RPT_ERROR,
|
||||
"USD Export: unable to find suitable USD plugin to write %s",
|
||||
data->unarchived_filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Traverse the point instancer to make sure the prototype referenced by nested point instancers
|
||||
* are also marked as over. */
|
||||
std::vector<pxr::UsdPrim> proto_list;
|
||||
for (const pxr::UsdPrim &prim : usd_stage->Traverse()) {
|
||||
if (!prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
continue;
|
||||
}
|
||||
pxr::UsdGeomPointInstancer instancer(prim);
|
||||
pxr::SdfPathVector targets;
|
||||
if (instancer.GetPrototypesRel().GetTargets(&targets)) {
|
||||
for (const pxr::SdfPath &wrapper_path : targets) {
|
||||
collect_point_instancer_prototypes_and_set_extent(
|
||||
instancer, usd_stage, wrapper_path, proto_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The standard way is to mark the point instancer's prototypes as over. Reference in OpenUSD:
|
||||
* https://openusd.org/docs/api/class_usd_geom_point_instancer.html#:~:text=place%20them%20under%20a%20prim%20that%20is%20just%20an%20%22over%22
|
||||
*/
|
||||
for (pxr::UsdPrim &proto : proto_list) {
|
||||
proto.SetSpecifier(pxr::SdfSpecifierOver);
|
||||
}
|
||||
|
||||
usd_stage->GetRootLayer()->Save();
|
||||
|
||||
data->export_ok = true;
|
||||
worker_status->progress = 1.0f;
|
||||
worker_status->do_update = true;
|
||||
}
|
||||
|
||||
static void export_endjob_usdz_cleanup(const ExportJobData *data)
|
||||
{
|
||||
if (!BLI_exists(data->unarchived_filepath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char dir[FILE_MAX];
|
||||
BLI_path_split_dir_part(data->unarchived_filepath, dir, FILE_MAX);
|
||||
|
||||
char usdc_temp_dir[FILE_MAX];
|
||||
BLI_path_join(usdc_temp_dir, FILE_MAX, BKE_tempdir_session(), "USDZ", SEP_STR);
|
||||
|
||||
BLI_assert_msg(BLI_strcasecmp(dir, usdc_temp_dir) == 0,
|
||||
"USD Export: Attempting to delete directory that doesn't match the expected "
|
||||
"temporary directory for usdz export.");
|
||||
BLI_delete(usdc_temp_dir, true, true);
|
||||
}
|
||||
|
||||
static void export_endjob(void *customdata)
|
||||
{
|
||||
ExportJobData *data = static_cast<ExportJobData *>(customdata);
|
||||
|
||||
DEG_graph_free(data->depsgraph);
|
||||
|
||||
if (data->targets_usdz()) {
|
||||
/* NOTE: call to #perform_usdz_conversion has to be done here instead of the main threaded
|
||||
* worker callback (#export_startjob) because USDZ conversion requires changing the current
|
||||
* working directory. This is not safe to do from a non-main thread. Once the USD library fix
|
||||
* this weird requirement, this call can be moved back at the end of #export_startjob, and not
|
||||
* block the main user interface anymore. */
|
||||
bool usd_conversion_success = perform_usdz_conversion(data);
|
||||
if (!usd_conversion_success) {
|
||||
data->export_ok = false;
|
||||
}
|
||||
|
||||
export_endjob_usdz_cleanup(data);
|
||||
}
|
||||
|
||||
if (!data->export_ok && BLI_exists(data->unarchived_filepath)) {
|
||||
BLI_delete(data->unarchived_filepath, false, false);
|
||||
}
|
||||
|
||||
G.is_rendering = false;
|
||||
if (data->wm) {
|
||||
WM_locked_interface_set(data->wm, false);
|
||||
}
|
||||
report_job_duration(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* To create a USDZ file, we must first create a `.usd/a/c` file and then covert it to `.usdz`.
|
||||
* The temporary files will be created in Blender's temporary session storage.
|
||||
* The `.usdz` file will then be moved to `job->usdz_filepath`.
|
||||
*/
|
||||
static void create_temp_path_for_usdz_export(const char *filepath, io::usd::ExportJobData *job)
|
||||
{
|
||||
char usdc_file[FILE_MAX];
|
||||
STRNCPY(usdc_file, BLI_path_basename(filepath));
|
||||
|
||||
if (BLI_path_extension_check(usdc_file, ".usdz")) {
|
||||
BLI_path_extension_replace(usdc_file, sizeof(usdc_file), ".usdc");
|
||||
}
|
||||
|
||||
char usdc_temp_filepath[FILE_MAX];
|
||||
BLI_path_join(usdc_temp_filepath, FILE_MAX, BKE_tempdir_session(), "USDZ", usdc_file);
|
||||
|
||||
STRNCPY(job->unarchived_filepath, usdc_temp_filepath);
|
||||
STRNCPY(job->usdz_filepath, filepath);
|
||||
}
|
||||
|
||||
static void set_job_filepath(io::usd::ExportJobData *job, const char *filepath)
|
||||
{
|
||||
if (BLI_path_extension_check_n(filepath, ".usdz", nullptr)) {
|
||||
create_temp_path_for_usdz_export(filepath, job);
|
||||
return;
|
||||
}
|
||||
|
||||
STRNCPY(job->unarchived_filepath, filepath);
|
||||
job->usdz_filepath[0] = '\0';
|
||||
}
|
||||
|
||||
bool USD_export(const bContext *C,
|
||||
const char *filepath,
|
||||
const USDExportParams *params,
|
||||
bool as_background_job,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (!io::usd::export_params_valid(*params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
|
||||
io::usd::ExportJobData *job = MEM_new<io::usd::ExportJobData>("ExportJobData");
|
||||
|
||||
job->bmain = CTX_data_main(C);
|
||||
job->wm = CTX_wm_manager(C);
|
||||
job->scene = scene;
|
||||
job->export_ok = false;
|
||||
set_job_filepath(job, filepath);
|
||||
|
||||
ED_editors_flush_edits(job->bmain);
|
||||
|
||||
job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
|
||||
job->params = *params;
|
||||
|
||||
/* Construct the depsgraph for exporting.
|
||||
*
|
||||
* Has to be done from main thread currently, as it may affect Main original data (e.g. when
|
||||
* doing deferred update of the view-layers, see #112534 for details). */
|
||||
if (job->params.collection[0]) {
|
||||
Collection *collection = reinterpret_cast<Collection *>(
|
||||
BKE_libblock_find_name(job->bmain, ID_GR, job->params.collection));
|
||||
if (!collection) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"USD Export: Unable to find collection '%s'",
|
||||
job->params.collection);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEG_graph_build_from_collection(job->depsgraph, collection);
|
||||
}
|
||||
else {
|
||||
DEG_graph_build_from_view_layer(job->depsgraph);
|
||||
}
|
||||
|
||||
bool export_ok = false;
|
||||
if (as_background_job) {
|
||||
wmJob *wm_job = WM_jobs_get(job->wm,
|
||||
CTX_wm_window(C),
|
||||
scene,
|
||||
"Exporting USD...",
|
||||
WM_JOB_PROGRESS,
|
||||
WM_JOB_TYPE_USD_EXPORT);
|
||||
|
||||
/* setup job */
|
||||
WM_jobs_customdata_set(
|
||||
wm_job, job, [](void *j) { MEM_delete(static_cast<io::usd::ExportJobData *>(j)); });
|
||||
WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
|
||||
WM_jobs_callbacks(wm_job, io::usd::export_startjob, nullptr, nullptr, io::usd::export_endjob);
|
||||
|
||||
WM_jobs_start(CTX_wm_manager(C), wm_job);
|
||||
}
|
||||
else {
|
||||
wmJobWorkerStatus worker_status = {};
|
||||
/* Use the operator's reports in non-background case. */
|
||||
worker_status.reports = reports;
|
||||
|
||||
io::usd::export_startjob(job, &worker_status);
|
||||
io::usd::export_endjob(job);
|
||||
export_ok = job->export_ok;
|
||||
|
||||
MEM_delete(job);
|
||||
}
|
||||
|
||||
return export_ok;
|
||||
}
|
||||
|
||||
int USD_get_version()
|
||||
{
|
||||
/* USD 19.11 defines:
|
||||
*
|
||||
* #define PXR_MAJOR_VERSION 0
|
||||
* #define PXR_MINOR_VERSION 19
|
||||
* #define PXR_PATCH_VERSION 11
|
||||
* #define PXR_VERSION 1911
|
||||
*
|
||||
* So the major version is implicit/invisible in the public version number.
|
||||
*/
|
||||
return PXR_VERSION;
|
||||
}
|
||||
|
||||
double get_meters_per_unit(const USDExportParams ¶ms)
|
||||
{
|
||||
double result;
|
||||
switch (params.convert_scene_units) {
|
||||
case SceneUnits::Centimeters:
|
||||
result = 0.01;
|
||||
break;
|
||||
case SceneUnits::Millimeters:
|
||||
result = 0.001;
|
||||
break;
|
||||
case SceneUnits::Kilometers:
|
||||
result = 1000.0;
|
||||
break;
|
||||
case SceneUnits::Inches:
|
||||
result = 0.0254;
|
||||
break;
|
||||
case SceneUnits::Feet:
|
||||
result = 0.3048;
|
||||
break;
|
||||
case SceneUnits::Yards:
|
||||
result = 0.9144;
|
||||
break;
|
||||
case SceneUnits::Custom:
|
||||
result = double(params.custom_meters_per_unit);
|
||||
break;
|
||||
default:
|
||||
result = 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
669
blender-5.2.0/source/blender/io/usd/intern/usd_capi_import.cc
Normal file
669
blender-5.2.0/source/blender/io/usd/intern/usd_capi_import.cc
Normal file
@@ -0,0 +1,669 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "IO_types.hh"
|
||||
#include "usd.hh"
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_reader_domelight.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_reader_stage.hh"
|
||||
|
||||
#include "BKE_cachefile.hh"
|
||||
#include "BKE_collection.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
#include "ED_undo.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
static CacheArchiveHandle *handle_from_stage_reader(USDStageReader *reader)
|
||||
{
|
||||
return reinterpret_cast<CacheArchiveHandle *>(reader);
|
||||
}
|
||||
|
||||
static USDStageReader *stage_reader_from_handle(CacheArchiveHandle *handle)
|
||||
{
|
||||
return reinterpret_cast<USDStageReader *>(handle);
|
||||
}
|
||||
|
||||
static bool gather_objects_paths(const pxr::UsdPrim &object,
|
||||
ListBaseT<CacheObjectPath> *object_paths)
|
||||
{
|
||||
if (!object.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const pxr::UsdPrim &childPrim : object.GetChildren()) {
|
||||
gather_objects_paths(childPrim, object_paths);
|
||||
}
|
||||
|
||||
CacheObjectPath *usd_path = MEM_new<CacheObjectPath>("CacheObjectPath");
|
||||
|
||||
STRNCPY(usd_path->path, object.GetPrimPath().GetString().c_str());
|
||||
BLI_addtail(object_paths, usd_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum {
|
||||
USD_NO_ERROR = 0,
|
||||
USD_ARCHIVE_FAIL,
|
||||
};
|
||||
|
||||
struct ImportJobData {
|
||||
bContext *C;
|
||||
Main *bmain;
|
||||
Scene *scene;
|
||||
wmWindowManager *wm;
|
||||
|
||||
char filepath[FILE_MAX];
|
||||
USDImportParams params;
|
||||
|
||||
USDStageReader *archive;
|
||||
|
||||
char error_code;
|
||||
bool was_canceled;
|
||||
bool import_ok;
|
||||
bool is_background_job;
|
||||
timeit::TimePoint start_time;
|
||||
|
||||
CacheFile *cache_file;
|
||||
};
|
||||
|
||||
static void report_job_duration(const ImportJobData *data)
|
||||
{
|
||||
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
|
||||
fmt::print("USD import of '{}' took ", data->filepath);
|
||||
timeit::print_duration(duration);
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(customdata);
|
||||
data->was_canceled = false;
|
||||
data->archive = nullptr;
|
||||
data->start_time = timeit::Clock::now();
|
||||
data->cache_file = nullptr;
|
||||
|
||||
data->params.worker_status = worker_status;
|
||||
|
||||
if (data->wm) {
|
||||
WM_locked_interface_set(data->wm, true);
|
||||
}
|
||||
G.is_break = false;
|
||||
|
||||
BLI_path_abs(data->filepath, BKE_main_blendfile_path_from_global());
|
||||
|
||||
pxr::UsdStagePopulationMask pop_mask;
|
||||
for (const std::string &mask_token : pxr::TfStringTokenize(data->params.prim_path_mask, ",;")) {
|
||||
pxr::SdfPath prim_path(mask_token);
|
||||
if (!prim_path.IsEmpty()) {
|
||||
pop_mask.Add(prim_path);
|
||||
}
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr stage = pop_mask.IsEmpty() ?
|
||||
pxr::UsdStage::Open(data->filepath) :
|
||||
pxr::UsdStage::OpenMasked(data->filepath, pop_mask);
|
||||
|
||||
if (!stage) {
|
||||
BKE_reportf(worker_status->reports,
|
||||
RPT_ERROR,
|
||||
"USD Import: unable to open stage to read %s",
|
||||
data->filepath);
|
||||
data->import_ok = false;
|
||||
data->error_code = USD_ARCHIVE_FAIL;
|
||||
return;
|
||||
}
|
||||
|
||||
worker_status->progress = 0.05f;
|
||||
worker_status->do_update = true;
|
||||
if (G.is_break) {
|
||||
data->was_canceled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
double scene_scale = data->params.scale;
|
||||
if (data->params.apply_unit_conversion_scale) {
|
||||
scene_scale *= pxr::UsdGeomGetStageMetersPerUnit(stage);
|
||||
}
|
||||
|
||||
/* Callback function to lazily create a cache file when converting
|
||||
* time varying data. */
|
||||
auto get_cache_file = [data, scene_scale]() {
|
||||
if (!data->cache_file) {
|
||||
data->cache_file = static_cast<CacheFile *>(
|
||||
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filepath)));
|
||||
|
||||
/* Decrement the ID ref-count because it is going to be incremented for each
|
||||
* modifier and constraint that it will be attached to, so since currently
|
||||
* it is not used by anyone, its use count will off by one. */
|
||||
id_us_min(&data->cache_file->id);
|
||||
|
||||
data->cache_file->is_sequence = data->params.is_sequence;
|
||||
data->cache_file->scale = scene_scale;
|
||||
STRNCPY(data->cache_file->filepath, data->filepath);
|
||||
if (data->params.relative_path && !BLI_path_is_rel(data->cache_file->filepath)) {
|
||||
BLI_path_rel(data->cache_file->filepath, BKE_main_blendfile_path_from_global());
|
||||
}
|
||||
}
|
||||
return data->cache_file;
|
||||
};
|
||||
|
||||
USDStageReader *archive = new USDStageReader(stage, data->params, get_cache_file);
|
||||
data->archive = archive;
|
||||
|
||||
/* Ensure Python types for invoking hooks are registered. */
|
||||
register_hook_converters();
|
||||
|
||||
archive->find_material_import_hook_sources();
|
||||
|
||||
worker_status->progress = 0.1f;
|
||||
worker_status->do_update = true;
|
||||
if (G.is_break) {
|
||||
data->was_canceled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
archive->collect_readers();
|
||||
|
||||
worker_status->progress = 0.15f;
|
||||
worker_status->do_update = true;
|
||||
if (G.is_break) {
|
||||
data->was_canceled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->params.import_lights && data->params.create_world_material &&
|
||||
!archive->dome_light_readers().is_empty())
|
||||
{
|
||||
/* NOTE: Since Blender does not have dome light objects, we need to modify the Scene directly
|
||||
* in order to setup environment lighting. */
|
||||
if (data->scene) {
|
||||
USDDomeLightReader *dome_light_reader = archive->dome_light_readers().first();
|
||||
dome_light_reader->create_object(data->scene, data->bmain);
|
||||
}
|
||||
}
|
||||
|
||||
if (data->params.import_materials && data->params.import_all_materials) {
|
||||
archive->import_all_materials(data->bmain);
|
||||
}
|
||||
|
||||
worker_status->progress = 0.2f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
/* Sort readers by name: when creating a lot of objects in Blender,
|
||||
* it is much faster if the order is sorted by name. */
|
||||
archive->sort_readers();
|
||||
|
||||
worker_status->progress = 0.25f;
|
||||
worker_status->do_update = true;
|
||||
|
||||
const float size = float(archive->readers().size());
|
||||
size_t i = 0;
|
||||
|
||||
/* Create blender objects. */
|
||||
for (USDPrimReader *reader : archive->readers()) {
|
||||
reader->create_object(data->bmain);
|
||||
|
||||
worker_status->progress = 0.25f + 0.25f * (++i / size);
|
||||
worker_status->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_canceled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup parenthood and read actual object data. */
|
||||
i = 0;
|
||||
for (USDPrimReader *reader : archive->readers()) {
|
||||
Object *ob = reader->object();
|
||||
reader->read_object_data(data->bmain, 0.0);
|
||||
|
||||
USDPrimReader *parent = reader->parent();
|
||||
if (parent == nullptr) {
|
||||
ob->parent = nullptr;
|
||||
}
|
||||
else {
|
||||
ob->parent = parent->object();
|
||||
}
|
||||
|
||||
worker_status->progress = 0.5f + 0.5f * (++i / size);
|
||||
worker_status->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_canceled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->params.import_skeletons) {
|
||||
archive->process_armature_modifiers();
|
||||
}
|
||||
|
||||
data->import_ok = !data->was_canceled;
|
||||
|
||||
worker_status->progress = 1.0f;
|
||||
worker_status->do_update = true;
|
||||
}
|
||||
|
||||
static void import_endjob(void *customdata)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(customdata);
|
||||
|
||||
/* Delete objects on cancellation. */
|
||||
if (data->was_canceled && data->archive) {
|
||||
for (const USDPrimReader *reader : data->archive->readers()) {
|
||||
/* It's possible that cancellation occurred between the creation of
|
||||
* the reader and the creation of the Blender object. */
|
||||
if (Object *ob = reader->object()) {
|
||||
BKE_id_free_us(data->bmain, ob);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data->archive) {
|
||||
Collection *collection_dst = nullptr;
|
||||
|
||||
if (data->scene) {
|
||||
/* Set scene animation range. */
|
||||
if (data->params.set_frame_range) {
|
||||
const pxr::UsdStageRefPtr stage = data->archive->stage();
|
||||
data->scene->r.sfra = stage->GetStartTimeCode();
|
||||
data->scene->r.efra = stage->GetEndTimeCode();
|
||||
}
|
||||
|
||||
ViewLayer *view_layer = CTX_data_view_layer(data->C);
|
||||
|
||||
/* Create a new collection if required. */
|
||||
if (data->params.create_collection) {
|
||||
char display_name[MAX_ID_NAME - 2];
|
||||
BLI_path_to_display_name(
|
||||
display_name, sizeof(display_name), BLI_path_basename(data->filepath));
|
||||
Collection *import_collection = BKE_collection_add(
|
||||
data->bmain, data->scene->master_collection, display_name);
|
||||
|
||||
DEG_id_tag_update(&import_collection->id, ID_RECALC_SYNC_TO_EVAL);
|
||||
DEG_relations_tag_update(data->bmain);
|
||||
|
||||
BKE_view_layer_synced_ensure(*data->bmain, data->scene, view_layer);
|
||||
view_layer->active_collection = BKE_layer_collection_first_from_scene_collection(
|
||||
view_layer, import_collection);
|
||||
}
|
||||
|
||||
BKE_view_layer_base_deselect_all(*data->bmain, data->scene, view_layer);
|
||||
|
||||
LayerCollection *lc = BKE_layer_collection_get_active_editable(view_layer);
|
||||
if (!ID_IS_EDITABLE(lc->collection)) {
|
||||
BKE_report(
|
||||
data->params.worker_status->reports,
|
||||
RPT_WARNING,
|
||||
"Could not find an editable collection in current scene, imported data will not "
|
||||
"be instantiated");
|
||||
}
|
||||
|
||||
collection_dst = lc->collection;
|
||||
}
|
||||
|
||||
/* Create prototype collections for instancing. */
|
||||
data->archive->create_proto_collections(data->bmain, collection_dst);
|
||||
|
||||
/* Add all objects to the collection. */
|
||||
for (const USDPrimReader *reader : data->archive->readers()) {
|
||||
if (reader->is_in_proto()) {
|
||||
/* Skip prototype prims, as these are added to prototype collections. */
|
||||
continue;
|
||||
}
|
||||
Object *ob = reader->object();
|
||||
if (!ob) {
|
||||
continue;
|
||||
}
|
||||
BKE_collection_object_add(data->bmain, collection_dst, ob);
|
||||
}
|
||||
|
||||
/* Sync and do the view layer operations. */
|
||||
if (data->scene) {
|
||||
ViewLayer *view_layer = CTX_data_view_layer(data->C);
|
||||
BKE_view_layer_synced_ensure(*data->bmain, data->scene, view_layer);
|
||||
|
||||
bool has_instantiated_object = false;
|
||||
bool has_uninstantiated_object = false;
|
||||
for (const USDPrimReader *reader : data->archive->readers()) {
|
||||
Object *ob = reader->object();
|
||||
if (!ob) {
|
||||
continue;
|
||||
}
|
||||
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
||||
if (!base) {
|
||||
/* Object not instantiated in current viewlayer. */
|
||||
has_uninstantiated_object = true;
|
||||
continue;
|
||||
}
|
||||
has_instantiated_object = true;
|
||||
/* TODO: is setting active needed? */
|
||||
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
||||
|
||||
DEG_id_tag_update(&collection_dst->id, ID_RECALC_SYNC_TO_EVAL);
|
||||
DEG_id_tag_update_ex(data->bmain,
|
||||
&ob->id,
|
||||
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
|
||||
ID_RECALC_BASE_FLAGS);
|
||||
}
|
||||
if (has_instantiated_object && has_uninstantiated_object) {
|
||||
CLOG_ERROR(&LOG, "Some imported objects were not instantiated, while others were");
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
|
||||
}
|
||||
|
||||
DEG_relations_tag_update(data->bmain);
|
||||
|
||||
if (data->params.import_materials && data->params.import_all_materials) {
|
||||
data->archive->fake_users_for_unused_materials();
|
||||
}
|
||||
|
||||
data->archive->call_material_import_hooks(data->bmain);
|
||||
|
||||
call_import_hooks(data->archive, data->params.worker_status->reports);
|
||||
|
||||
if (data->is_background_job) {
|
||||
/* Blender already returned from the import operator, so we need to store our own extra undo
|
||||
* step. */
|
||||
ED_undo_push(data->C, "USD Import Finished");
|
||||
}
|
||||
}
|
||||
|
||||
if (data->wm) {
|
||||
WM_locked_interface_set(data->wm, false);
|
||||
}
|
||||
|
||||
switch (data->error_code) {
|
||||
default:
|
||||
case USD_NO_ERROR:
|
||||
data->import_ok = !data->was_canceled;
|
||||
break;
|
||||
case USD_ARCHIVE_FAIL:
|
||||
BKE_report(data->params.worker_status->reports,
|
||||
RPT_ERROR,
|
||||
"Could not open USD archive for reading, see console for detail");
|
||||
break;
|
||||
}
|
||||
|
||||
WM_main_add_notifier(NC_ID | NA_ADDED, nullptr);
|
||||
report_job_duration(data);
|
||||
}
|
||||
|
||||
static void import_freejob(void *user_data)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
|
||||
delete data->archive;
|
||||
delete data;
|
||||
}
|
||||
|
||||
bool USD_import(bContext *C,
|
||||
const char *filepath,
|
||||
const USDImportParams *params,
|
||||
bool as_background_job,
|
||||
ReportList *reports)
|
||||
{
|
||||
/* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */
|
||||
ImportJobData *job = new ImportJobData();
|
||||
job->C = C;
|
||||
job->bmain = CTX_data_main(C);
|
||||
job->scene = CTX_data_scene(C); // May be null
|
||||
job->wm = CTX_wm_manager(C); // May be null
|
||||
job->import_ok = false;
|
||||
job->is_background_job = as_background_job;
|
||||
STRNCPY(job->filepath, filepath);
|
||||
|
||||
job->error_code = USD_NO_ERROR;
|
||||
job->was_canceled = false;
|
||||
job->archive = nullptr;
|
||||
|
||||
job->params = *params;
|
||||
|
||||
G.is_break = false;
|
||||
|
||||
bool import_ok = false;
|
||||
if (as_background_job) {
|
||||
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
|
||||
CTX_wm_window(C),
|
||||
CTX_data_scene(C),
|
||||
"Importing USD...",
|
||||
WM_JOB_PROGRESS,
|
||||
WM_JOB_TYPE_USD_IMPORT);
|
||||
|
||||
/* setup job */
|
||||
WM_jobs_customdata_set(wm_job, job, import_freejob);
|
||||
WM_jobs_timer(wm_job, 0.1, NC_SCENE, NC_SCENE);
|
||||
WM_jobs_callbacks(wm_job, import_startjob, nullptr, nullptr, import_endjob);
|
||||
|
||||
WM_jobs_start(CTX_wm_manager(C), wm_job);
|
||||
}
|
||||
else {
|
||||
wmJobWorkerStatus worker_status = {};
|
||||
/* Use the operator's reports in non-background case. */
|
||||
worker_status.reports = reports;
|
||||
|
||||
import_startjob(job, &worker_status);
|
||||
import_endjob(job);
|
||||
import_ok = job->import_ok;
|
||||
|
||||
import_freejob(job);
|
||||
}
|
||||
|
||||
return import_ok;
|
||||
}
|
||||
|
||||
/* TODO(makowalski): Extend this function with basic validation that the
|
||||
* USD reader is compatible with the type of the given (currently unused) 'ob'
|
||||
* Object parameter, similar to the logic in get_abc_reader() in the
|
||||
* Alembic importer code. */
|
||||
static USDPrimReader *get_usd_reader(CacheReader *reader,
|
||||
const Object * /*ob*/,
|
||||
const char **r_err_str)
|
||||
{
|
||||
USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader);
|
||||
pxr::UsdPrim iobject = usd_reader->prim();
|
||||
|
||||
if (!iobject.IsValid()) {
|
||||
*r_err_str = RPT_("Invalid object: verify object path");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return usd_reader;
|
||||
}
|
||||
|
||||
USDMeshReadParams create_mesh_read_params(const double motion_sample_time, const int read_flags)
|
||||
{
|
||||
USDMeshReadParams params = {};
|
||||
params.motion_sample_time = motion_sample_time;
|
||||
params.read_flags = read_flags;
|
||||
return params;
|
||||
}
|
||||
|
||||
void USD_read_geometry(CacheReader *reader,
|
||||
const Object *ob,
|
||||
bke::GeometrySet &geometry_set,
|
||||
const USDMeshReadParams params,
|
||||
const char **r_err_str)
|
||||
{
|
||||
USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, r_err_str));
|
||||
|
||||
if (usd_reader == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
usd_reader->read_geometry(geometry_set, params, r_err_str);
|
||||
}
|
||||
|
||||
bool USD_mesh_topology_changed(CacheReader *reader,
|
||||
const Object *ob,
|
||||
const Mesh *existing_mesh,
|
||||
const double time,
|
||||
const char **r_err_str)
|
||||
{
|
||||
USDGeomReader *usd_reader = dynamic_cast<USDGeomReader *>(get_usd_reader(reader, ob, r_err_str));
|
||||
|
||||
if (usd_reader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return usd_reader->topology_changed(existing_mesh, time);
|
||||
}
|
||||
|
||||
CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle,
|
||||
CacheReader *reader,
|
||||
Object *object,
|
||||
const char *object_path)
|
||||
{
|
||||
if (object_path[0] == '\0') {
|
||||
return reader;
|
||||
}
|
||||
|
||||
USDStageReader *archive = stage_reader_from_handle(handle);
|
||||
|
||||
if (!archive || !archive->valid()) {
|
||||
return reader;
|
||||
}
|
||||
|
||||
if (reader) {
|
||||
USD_CacheReader_free(reader);
|
||||
}
|
||||
|
||||
pxr::UsdPrim prim = archive->stage()->GetPrimAtPath(pxr::SdfPath(object_path));
|
||||
|
||||
if (!prim) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* TODO(makowalski): The handle does not have the proper import params or settings. */
|
||||
USDPrimReader *usd_reader = archive->create_reader(prim);
|
||||
|
||||
if (usd_reader == nullptr) {
|
||||
/* This object is not supported. */
|
||||
return nullptr;
|
||||
}
|
||||
if (!usd_reader->valid()) {
|
||||
/* This object is invalid for some reason. */
|
||||
return nullptr;
|
||||
}
|
||||
usd_reader->object(object);
|
||||
usd_reader->incref();
|
||||
|
||||
return reinterpret_cast<CacheReader *>(usd_reader);
|
||||
}
|
||||
|
||||
void USD_CacheReader_free(CacheReader *reader)
|
||||
{
|
||||
USDPrimReader *usd_reader = reinterpret_cast<USDPrimReader *>(reader);
|
||||
usd_reader->decref();
|
||||
|
||||
if (usd_reader->refcount() == 0) {
|
||||
delete usd_reader;
|
||||
}
|
||||
}
|
||||
|
||||
CacheArchiveHandle *USD_create_handle(Main * /*bmain*/,
|
||||
const char *filepath,
|
||||
ListBaseT<CacheObjectPath> *object_paths)
|
||||
{
|
||||
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(filepath);
|
||||
|
||||
if (!stage) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USDImportParams params{};
|
||||
|
||||
USDStageReader *stage_reader = new USDStageReader(stage, params);
|
||||
|
||||
if (object_paths) {
|
||||
gather_objects_paths(stage->GetPseudoRoot(), object_paths);
|
||||
}
|
||||
|
||||
return handle_from_stage_reader(stage_reader);
|
||||
}
|
||||
|
||||
void USD_free_handle(CacheArchiveHandle *handle)
|
||||
{
|
||||
USDStageReader *stage_reader = stage_reader_from_handle(handle);
|
||||
delete stage_reader;
|
||||
}
|
||||
|
||||
void USD_get_transform(CacheReader *reader, float4x4 &r_mat_world, float time, float scale)
|
||||
{
|
||||
if (!reader) {
|
||||
return;
|
||||
}
|
||||
const USDXformReader *usd_reader = reinterpret_cast<USDXformReader *>(reader);
|
||||
bool is_constant = false;
|
||||
|
||||
/* Convert from the local matrix we obtain from USD to world coordinates
|
||||
* for Blender. This conversion is done here rather than by Blender due to
|
||||
* work around the non-standard interpretation of CONSTRAINT_SPACE_LOCAL in
|
||||
* BKE_constraint_mat_convertspace(). */
|
||||
Object *object = usd_reader->object();
|
||||
if (object->parent == nullptr) {
|
||||
/* No parent, so local space is the same as world space. */
|
||||
usd_reader->read_matrix(r_mat_world, time, scale, &is_constant);
|
||||
return;
|
||||
}
|
||||
|
||||
float4x4 mat_parent;
|
||||
BKE_object_get_parent_matrix(object, object->parent, mat_parent.ptr());
|
||||
|
||||
float4x4 mat_local;
|
||||
usd_reader->read_matrix(mat_local, time, scale, &is_constant);
|
||||
|
||||
r_mat_world = mat_parent * float4x4(object->parentinv);
|
||||
r_mat_world *= mat_local;
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,185 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_colorspace_utils.hh"
|
||||
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/usd/colorSpaceAPI.h>
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
namespace usdtokens {
|
||||
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken auto_("auto", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken data("data", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
pxr::TfToken colorspace_scene_linear_interop_id()
|
||||
{
|
||||
const char *scene_linear_name = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_SCENE_LINEAR);
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(scene_linear_name);
|
||||
StringRefNull interop_id = (cs) ? IMB_colormanagement_space_get_interop_id(cs) : "";
|
||||
return (interop_id.is_empty()) ? pxr::TfToken() : pxr::TfToken(interop_id);
|
||||
}
|
||||
|
||||
void colorspace_apply_to_prim(const pxr::UsdPrim &prim)
|
||||
{
|
||||
const pxr::TfToken interop_id = colorspace_scene_linear_interop_id();
|
||||
if (interop_id.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
pxr::UsdColorSpaceAPI cs_api = pxr::UsdColorSpaceAPI::Apply(prim);
|
||||
cs_api.CreateColorSpaceNameAttr(pxr::VtValue(interop_id));
|
||||
}
|
||||
|
||||
static const ColorSpace *colorspace_from_attr(const pxr::UsdAttribute &attr)
|
||||
{
|
||||
pxr::TfToken cs_name = pxr::UsdColorSpaceAPI::ComputeColorSpaceName(attr);
|
||||
if (cs_name.IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(cs_name.GetText());
|
||||
if (!cs || IMB_colormanagement_space_is_scene_linear(cs) ||
|
||||
IMB_colormanagement_space_is_data(cs))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, pxr::GfVec3f &color)
|
||||
{
|
||||
const ColorSpace *cs = colorspace_from_attr(attr);
|
||||
if (cs) {
|
||||
IMB_colormanagement_colorspace_to_scene_linear_v3(color.data(), cs);
|
||||
}
|
||||
}
|
||||
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, ColorGeometry4f &color)
|
||||
{
|
||||
const ColorSpace *cs = colorspace_from_attr(attr);
|
||||
if (cs) {
|
||||
IMB_colormanagement_colorspace_to_scene_linear_v4(&color.r, false, cs);
|
||||
}
|
||||
}
|
||||
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr,
|
||||
MutableSpan<ColorGeometry4f> colors)
|
||||
{
|
||||
const ColorSpace *cs = colorspace_from_attr(attr);
|
||||
if (cs && !colors.is_empty()) {
|
||||
IMB_colormanagement_colorspace_to_scene_linear(&colors[0].r, colors.size(), 1, 4, cs, false);
|
||||
}
|
||||
}
|
||||
|
||||
static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader)
|
||||
{
|
||||
if (!usd_shader) {
|
||||
return pxr::TfToken();
|
||||
}
|
||||
|
||||
pxr::UsdShadeInput color_space_input = usd_shader.GetInput(usdtokens::sourceColorSpace);
|
||||
|
||||
if (!color_space_input) {
|
||||
return pxr::TfToken();
|
||||
}
|
||||
|
||||
pxr::VtValue color_space_val;
|
||||
if (color_space_input.Get(&color_space_val) && color_space_val.IsHolding<pxr::TfToken>()) {
|
||||
return color_space_val.UncheckedGet<pxr::TfToken>();
|
||||
}
|
||||
|
||||
return pxr::TfToken();
|
||||
}
|
||||
|
||||
void colorspace_to_image_texture(const pxr::UsdShadeShader &usd_shader,
|
||||
const pxr::UsdShadeInput &file_input,
|
||||
const bool is_data,
|
||||
Image *image)
|
||||
{
|
||||
/* Set texture color space.
|
||||
* TODO(makowalski): For now, just checking for RAW color space,
|
||||
* assuming sRGB otherwise, but more complex logic might be
|
||||
* required if the color space is "auto". */
|
||||
pxr::TfToken color_space = get_source_color_space(usd_shader);
|
||||
|
||||
if (color_space.IsEmpty()) {
|
||||
color_space = file_input.GetAttr().GetColorSpace();
|
||||
}
|
||||
|
||||
if (color_space.IsEmpty()) {
|
||||
color_space = pxr::UsdColorSpaceAPI::ComputeColorSpaceName(usd_shader.GetPrim());
|
||||
}
|
||||
|
||||
if (color_space.IsEmpty()) {
|
||||
/* At this point, assume the "auto" space and translate accordingly. */
|
||||
color_space = usdtokens::auto_;
|
||||
}
|
||||
|
||||
if (color_space == usdtokens::auto_) {
|
||||
/* If it's auto, determine whether to apply color correction based
|
||||
* on incoming connection (passed in from outer functions). */
|
||||
STRNCPY_UTF8(image->colorspace_settings.name,
|
||||
IMB_colormanagement_role_colorspace_name_get(is_data ? COLOR_ROLE_DATA :
|
||||
COLOR_ROLE_DEFAULT_BYTE));
|
||||
}
|
||||
|
||||
else if (color_space == usdtokens::sRGB) {
|
||||
STRNCPY_UTF8(image->colorspace_settings.name, IMB_colormanagement_srgb_colorspace_name_get());
|
||||
}
|
||||
/* Due to there being a lot of non-compliant USD assets out there, this is
|
||||
* a special case where we need to check for different spellings here.
|
||||
* On write, we are *only* using the correct, lower-case "raw" token. */
|
||||
else if (ELEM(color_space, usdtokens::data, usdtokens::RAW, usdtokens::raw)) {
|
||||
STRNCPY_UTF8(image->colorspace_settings.name,
|
||||
IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA));
|
||||
}
|
||||
else {
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(color_space.GetText());
|
||||
if (cs) {
|
||||
STRNCPY_UTF8(image->colorspace_settings.name, IMB_colormanagement_colorspace_get_name(cs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void colorspace_from_image_texture(const Image *image, pxr::UsdShadeShader &shader)
|
||||
{
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write sourceColorSpace input for backward compatibility with older USD readers. */
|
||||
if (IMB_colormanagement_space_name_is_data(image->colorspace_settings.name)) {
|
||||
shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token)
|
||||
.Set(usdtokens::raw);
|
||||
}
|
||||
else if (IMB_colormanagement_space_name_is_srgb(image->colorspace_settings.name)) {
|
||||
shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token)
|
||||
.Set(usdtokens::sRGB);
|
||||
}
|
||||
|
||||
/* Write ColorSpaceAPI with the interop ID, which supports any colorspace. */
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(image->colorspace_settings.name);
|
||||
if (cs) {
|
||||
StringRefNull interop_id = IMB_colormanagement_space_get_interop_id(cs);
|
||||
if (!interop_id.is_empty()) {
|
||||
pxr::UsdColorSpaceAPI cs_api = pxr::UsdColorSpaceAPI::Apply(shader.GetPrim());
|
||||
cs_api.CreateColorSpaceNameAttr(pxr::VtValue(pxr::TfToken(interop_id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/usd/usd/attribute.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
|
||||
namespace blender {
|
||||
struct Image;
|
||||
}
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/** Get the interop ID for tagging exported USD stages. */
|
||||
pxr::TfToken colorspace_scene_linear_interop_id();
|
||||
|
||||
/** Tag a prim with the scene linear color space. */
|
||||
void colorspace_apply_to_prim(const pxr::UsdPrim &prim);
|
||||
|
||||
/** Convert an imported USD color to scene linear. */
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, pxr::GfVec3f &color);
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, ColorGeometry4f &color);
|
||||
|
||||
/** Convert imported USD color array to scene linear. */
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr,
|
||||
MutableSpan<ColorGeometry4f> colors);
|
||||
|
||||
/** Set the colorspace on an exported USD texture shader from a Blender image. */
|
||||
void colorspace_from_image_texture(const Image *image, pxr::UsdShadeShader &shader);
|
||||
|
||||
/** Set the Blender image colorspace from an imported USD texture shader. */
|
||||
void colorspace_to_image_texture(const pxr::UsdShadeShader &usd_shader,
|
||||
const pxr::UsdShadeInput &file_input,
|
||||
bool is_data,
|
||||
Image *image);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Image;
|
||||
struct ImageUser;
|
||||
struct Scene;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDHierarchyIterator;
|
||||
|
||||
struct USDExporterContext {
|
||||
Main *bmain;
|
||||
Depsgraph *depsgraph;
|
||||
const pxr::UsdStageRefPtr stage;
|
||||
const pxr::SdfPath usd_path;
|
||||
/**
|
||||
* Wrap a function which returns the current time code
|
||||
* for export. This is necessary since the context
|
||||
* may be used for exporting an animation over a sequence
|
||||
* of frames.
|
||||
*/
|
||||
std::function<pxr::UsdTimeCode()> get_time_code;
|
||||
const USDExportParams &export_params;
|
||||
std::string export_file_path;
|
||||
std::function<std::string(Main *, Scene *, Image *, ImageUser *)> export_image_fn;
|
||||
|
||||
/** Optional callback for skel/shape-key path registration (used by USDPointInstancerWriter). */
|
||||
std::function<void(const Object *, const pxr::SdfPath &)> add_skel_mapping_fn;
|
||||
|
||||
USDHierarchyIterator *hierarchy_iterator;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
36
blender-5.2.0/source/blender/io/usd/intern/usd_hash_types.hh
Normal file
36
blender-5.2.0/source/blender/io/usd/intern/usd_hash_types.hh
Normal file
@@ -0,0 +1,36 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
template<> struct DefaultHash<pxr::SdfValueTypeName> {
|
||||
uint64_t operator()(const pxr::SdfValueTypeName &value) const
|
||||
{
|
||||
return value.GetHash();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct DefaultHash<pxr::TfToken> {
|
||||
uint64_t operator()(const pxr::TfToken &value) const
|
||||
{
|
||||
return value.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct DefaultHash<pxr::SdfPath> {
|
||||
uint64_t operator()(const pxr::SdfPath &value) const
|
||||
{
|
||||
return value.GetHash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,505 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd.hh"
|
||||
|
||||
#include "usd_armature_utils.hh"
|
||||
#include "usd_blend_shape_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_skel_convert.hh"
|
||||
#include "usd_skel_root_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_abstract.hh"
|
||||
#include "usd_writer_armature.hh"
|
||||
#include "usd_writer_camera.hh"
|
||||
#include "usd_writer_curves.hh"
|
||||
#include "usd_writer_hair.hh"
|
||||
#include "usd_writer_light.hh"
|
||||
#include "usd_writer_mesh.hh"
|
||||
#include "usd_writer_metaball.hh"
|
||||
#include "usd_writer_pointinstancer.hh"
|
||||
#include "usd_writer_points.hh"
|
||||
#include "usd_writer_text.hh"
|
||||
#include "usd_writer_transform.hh"
|
||||
#include "usd_writer_volume.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDHierarchyIterator::USDHierarchyIterator(Main *bmain,
|
||||
Depsgraph *depsgraph,
|
||||
pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams ¶ms)
|
||||
: AbstractHierarchyIterator(bmain, depsgraph), stage_(stage), params_(params)
|
||||
{
|
||||
}
|
||||
|
||||
bool USDHierarchyIterator::mark_as_weak_export(const Object *object) const
|
||||
{
|
||||
if (params_.selected_objects_only && (object->base_flag & BASE_SELECTED) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (object->type) {
|
||||
case OB_EMPTY:
|
||||
/* Always assume empties are being exported intentionally. */
|
||||
return false;
|
||||
case OB_MESH:
|
||||
case OB_MBALL:
|
||||
case OB_FONT:
|
||||
case OB_SURF:
|
||||
return !params_.export_meshes;
|
||||
case OB_CAMERA:
|
||||
return !params_.export_cameras;
|
||||
case OB_LAMP:
|
||||
return !params_.export_lights;
|
||||
case OB_CURVES_LEGACY:
|
||||
case OB_CURVES:
|
||||
return !params_.export_curves;
|
||||
case OB_VOLUME:
|
||||
return !params_.export_volumes;
|
||||
case OB_ARMATURE:
|
||||
return !params_.export_armatures;
|
||||
case OB_POINTCLOUD:
|
||||
return !params_.export_points;
|
||||
|
||||
default:
|
||||
/* Assume weak for all other types. */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void USDHierarchyIterator::release_writer(AbstractHierarchyWriter *writer)
|
||||
{
|
||||
delete static_cast<USDAbstractWriter *>(writer);
|
||||
}
|
||||
|
||||
std::string USDHierarchyIterator::make_valid_name(const std::string &name) const
|
||||
{
|
||||
return make_safe_name(name, params_.allow_unicode);
|
||||
}
|
||||
|
||||
void USDHierarchyIterator::process_usd_skel() const
|
||||
{
|
||||
skel_export_chaser(stage_,
|
||||
armature_export_map_,
|
||||
skinned_mesh_export_map_,
|
||||
shape_key_mesh_export_map_,
|
||||
depsgraph_);
|
||||
|
||||
create_skel_roots(stage_, params_);
|
||||
}
|
||||
|
||||
void USDHierarchyIterator::set_export_frame(float frame_nr)
|
||||
{
|
||||
/* The USD stage is already set up to have FPS time-codes per frame. */
|
||||
export_time_ = pxr::UsdTimeCode(frame_nr);
|
||||
}
|
||||
|
||||
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
|
||||
{
|
||||
pxr::SdfPath path;
|
||||
if (!params_.root_prim_path.empty()) {
|
||||
path = pxr::SdfPath(params_.root_prim_path + context->export_path);
|
||||
}
|
||||
else {
|
||||
path = pxr::SdfPath(context->export_path);
|
||||
}
|
||||
|
||||
if (params_.merge_parent_xform && context->is_object_data_context && !context->is_parent) {
|
||||
bool can_merge_with_xform = true;
|
||||
if (params_.export_shapekeys && is_mesh_with_shape_keys(context->object)) {
|
||||
can_merge_with_xform = false;
|
||||
}
|
||||
|
||||
if (params_.use_instancing && (context->is_prototype() || context->is_instance())) {
|
||||
can_merge_with_xform = false;
|
||||
}
|
||||
|
||||
if (can_merge_with_xform) {
|
||||
path = path.GetParentPath();
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the same path that was passed to `stage_` object during it's creation (via
|
||||
* `pxr::UsdStage::CreateNew` function). */
|
||||
const pxr::SdfLayerHandle root_layer = stage_->GetRootLayer();
|
||||
const std::string export_file_path = root_layer->GetRealPath();
|
||||
auto get_time_code = [this]() { return this->export_time_; };
|
||||
|
||||
USDExporterContext exporter_context = USDExporterContext{bmain_,
|
||||
depsgraph_,
|
||||
stage_,
|
||||
path,
|
||||
get_time_code,
|
||||
params_,
|
||||
export_file_path,
|
||||
nullptr,
|
||||
nullptr,
|
||||
this};
|
||||
|
||||
/* Provides optional skel mapping hook. Now it's been used in USDPointInstancerWriter for write
|
||||
* base layer. */
|
||||
exporter_context.add_skel_mapping_fn = [this](const Object *obj, const pxr::SdfPath &usd_path) {
|
||||
this->add_usd_skel_export_mapping(obj, usd_path);
|
||||
};
|
||||
|
||||
return exporter_context;
|
||||
}
|
||||
|
||||
bool USDHierarchyIterator::determine_point_instancers(const HierarchyContext *context)
|
||||
{
|
||||
if (!context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context->object->type == OB_ARMATURE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_referencing_self = false;
|
||||
if (context->is_point_instancer()) {
|
||||
/* Mark the point instancer's children as a point instance. */
|
||||
USDExporterContext usd_export_context = create_usd_export_context(context);
|
||||
const ExportChildren *children = graph_children(context);
|
||||
|
||||
pxr::SdfPath instancer_path;
|
||||
if (!params_.root_prim_path.empty()) {
|
||||
instancer_path = pxr::SdfPath(params_.root_prim_path + context->export_path);
|
||||
}
|
||||
else {
|
||||
instancer_path = pxr::SdfPath(context->export_path);
|
||||
}
|
||||
|
||||
if (children != nullptr) {
|
||||
for (HierarchyContext *child_context : *children) {
|
||||
if (!child_context->original_export_path.empty()) {
|
||||
const pxr::SdfPath parent_export_path(context->export_path);
|
||||
const pxr::SdfPath children_original_export_path(child_context->original_export_path);
|
||||
|
||||
/* Detect if the parent is referencing itself via a prototype. */
|
||||
if (parent_export_path.HasPrefix(children_original_export_path)) {
|
||||
is_referencing_self = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pxr::SdfPath prototype_path;
|
||||
if (child_context->is_instance() && child_context->duplicator != nullptr) {
|
||||
/* When the current child context is point instancer's instance, use reference path
|
||||
* (original_export_path) as the prototype path. */
|
||||
if (!params_.root_prim_path.empty()) {
|
||||
prototype_path = pxr::SdfPath(params_.root_prim_path +
|
||||
child_context->original_export_path);
|
||||
}
|
||||
else {
|
||||
prototype_path = pxr::SdfPath(child_context->original_export_path);
|
||||
}
|
||||
|
||||
prototype_paths_.lookup_or_add(instancer_path, {})
|
||||
.add(std::make_pair(prototype_path, child_context->object));
|
||||
child_context->is_point_instance = true;
|
||||
}
|
||||
else {
|
||||
/* When the current child context is point instancer's prototype, use its own export path
|
||||
* (export_path) as the prototype path. */
|
||||
if (!params_.root_prim_path.empty()) {
|
||||
prototype_path = pxr::SdfPath(params_.root_prim_path + child_context->export_path);
|
||||
}
|
||||
else {
|
||||
prototype_path = pxr::SdfPath(child_context->export_path);
|
||||
}
|
||||
|
||||
prototype_paths_.lookup_or_add(instancer_path, {})
|
||||
.add(std::make_pair(prototype_path, child_context->object));
|
||||
child_context->is_point_proto = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* MARK: If the "Instance on Points" node uses an Object as a prototype,
|
||||
* but the "Object Info" node has not enabled the "As Instance" option,
|
||||
* then the generated reference path is incorrect and refers to itself. */
|
||||
if (is_referencing_self) {
|
||||
BKE_reportf(
|
||||
params_.worker_status->reports,
|
||||
RPT_WARNING,
|
||||
"One or more objects used as prototypes in 'Instance on Points' nodes either do not "
|
||||
"have 'As Instance' enabled in their 'Object Info' nodes, or the prototype is the "
|
||||
"base geometry input itself. Both cases prevent valid point instancer export. If it's "
|
||||
"the former, enable 'As Instance' to avoid incorrect self-referencing.");
|
||||
|
||||
/* Clear any paths which had already been accumulated. */
|
||||
Set<std::pair<pxr::SdfPath, Object *>> *paths = prototype_paths_.lookup_ptr(instancer_path);
|
||||
if (paths) {
|
||||
paths->clear();
|
||||
}
|
||||
for (HierarchyContext *child_context : *children) {
|
||||
child_context->is_point_instance = false;
|
||||
child_context->is_point_proto = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !is_referencing_self;
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
|
||||
const HierarchyContext *context)
|
||||
{
|
||||
/* The transform writer is always called before data writers,
|
||||
* so determine if the #Xform's children is a point instancer before writing data. */
|
||||
if (params_.use_instancing) {
|
||||
if (!determine_point_instancers(context)) {
|
||||
/* If we could not determine that our point instancing setup is safe, we should not continue
|
||||
* writing. Continuing would result in enormous amounts of USD warnings about cyclic
|
||||
* references. */
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return new USDTransformWriter(create_usd_export_context(context));
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
|
||||
{
|
||||
USDExporterContext usd_export_context = create_usd_export_context(context);
|
||||
USDAbstractWriter *data_writer = nullptr;
|
||||
const Set<std::pair<pxr::SdfPath, Object *>> *proto_paths = prototype_paths_.lookup_ptr(
|
||||
usd_export_context.usd_path.GetParentPath());
|
||||
const bool use_point_instancing = context->is_point_instancer() &&
|
||||
(proto_paths && !proto_paths->is_empty());
|
||||
|
||||
switch (context->object->type) {
|
||||
case OB_MESH:
|
||||
case OB_SURF:
|
||||
/* NURBS surfaces are tessellated to a mesh during depsgraph evaluation,
|
||||
* so they can be written through the standard mesh writer. */
|
||||
if (usd_export_context.export_params.export_meshes) {
|
||||
if (params_.use_instancing && use_point_instancing) {
|
||||
USDExporterContext mesh_context = create_point_instancer_context(context,
|
||||
usd_export_context);
|
||||
std::unique_ptr<USDMeshWriter> mesh_writer = std::make_unique<USDMeshWriter>(
|
||||
mesh_context);
|
||||
|
||||
data_writer = new USDPointInstancerWriter(
|
||||
usd_export_context, *proto_paths, std::move(mesh_writer));
|
||||
}
|
||||
else {
|
||||
data_writer = new USDMeshWriter(usd_export_context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_CAMERA:
|
||||
if (usd_export_context.export_params.export_cameras) {
|
||||
data_writer = new USDCameraWriter(usd_export_context);
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_LAMP:
|
||||
if (usd_export_context.export_params.export_lights) {
|
||||
data_writer = new USDLightWriter(usd_export_context);
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_MBALL:
|
||||
data_writer = new USDMetaballWriter(usd_export_context);
|
||||
break;
|
||||
case OB_FONT:
|
||||
data_writer = new USDTextWriter(usd_export_context);
|
||||
break;
|
||||
case OB_CURVES_LEGACY:
|
||||
case OB_CURVES:
|
||||
if (usd_export_context.export_params.export_curves) {
|
||||
if (params_.use_instancing && use_point_instancing) {
|
||||
USDExporterContext curves_context = create_point_instancer_context(context,
|
||||
usd_export_context);
|
||||
std::unique_ptr<USDCurvesWriter> curves_writer = std::make_unique<USDCurvesWriter>(
|
||||
curves_context);
|
||||
|
||||
data_writer = new USDPointInstancerWriter(
|
||||
usd_export_context, *proto_paths, std::move(curves_writer));
|
||||
}
|
||||
else {
|
||||
data_writer = new USDCurvesWriter(usd_export_context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_VOLUME:
|
||||
if (usd_export_context.export_params.export_volumes) {
|
||||
data_writer = new USDVolumeWriter(usd_export_context);
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_ARMATURE:
|
||||
if (usd_export_context.export_params.export_armatures) {
|
||||
data_writer = new USDArmatureWriter(usd_export_context);
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
case OB_POINTCLOUD:
|
||||
if (usd_export_context.export_params.export_points) {
|
||||
if (params_.use_instancing && use_point_instancing) {
|
||||
USDExporterContext point_cloud_context = create_point_instancer_context(
|
||||
context, usd_export_context);
|
||||
std::unique_ptr<USDPointsWriter> point_cloud_writer = std::make_unique<USDPointsWriter>(
|
||||
point_cloud_context);
|
||||
|
||||
data_writer = new USDPointInstancerWriter(
|
||||
usd_export_context, *proto_paths, std::move(point_cloud_writer));
|
||||
}
|
||||
else {
|
||||
data_writer = new USDPointsWriter(usd_export_context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case OB_EMPTY:
|
||||
case OB_SPEAKER:
|
||||
case OB_LIGHTPROBE:
|
||||
case OB_LATTICE:
|
||||
case OB_GREASE_PENCIL:
|
||||
return nullptr;
|
||||
|
||||
case OB_TYPE_MAX:
|
||||
BLI_assert_msg(0, "OB_TYPE_MAX should not be used");
|
||||
return nullptr;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data_writer && !data_writer->is_supported(context)) {
|
||||
delete data_writer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data_writer && (params_.export_armatures || params_.export_shapekeys)) {
|
||||
add_usd_skel_export_mapping(context->object, data_writer->usd_path());
|
||||
}
|
||||
|
||||
return data_writer;
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
|
||||
{
|
||||
if (!params_.export_hair) {
|
||||
return nullptr;
|
||||
}
|
||||
return new USDHairWriter(create_usd_export_context(context));
|
||||
}
|
||||
|
||||
AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(
|
||||
const HierarchyContext * /*context*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool USDHierarchyIterator::include_data_writers(const HierarchyContext *context) const
|
||||
{
|
||||
/* Don't generate data writers for instances. */
|
||||
|
||||
return !(params_.use_instancing && context->is_instance());
|
||||
}
|
||||
|
||||
bool USDHierarchyIterator::include_child_writers(const HierarchyContext *context) const
|
||||
{
|
||||
/* Don't generate writers for children of instances. */
|
||||
|
||||
return !(params_.use_instancing && context->is_instance());
|
||||
}
|
||||
|
||||
void USDHierarchyIterator::add_usd_skel_export_mapping(const Object *obj, const pxr::SdfPath &path)
|
||||
{
|
||||
if (params_.export_shapekeys && is_mesh_with_shape_keys(obj)) {
|
||||
shape_key_mesh_export_map_.add(obj, path);
|
||||
}
|
||||
|
||||
if (params_.export_armatures && obj->type == OB_ARMATURE) {
|
||||
armature_export_map_.add(obj, path);
|
||||
}
|
||||
|
||||
if (params_.export_armatures && obj->type == OB_MESH &&
|
||||
can_export_skinned_mesh(*obj, depsgraph_))
|
||||
{
|
||||
skinned_mesh_export_map_.add(obj, path);
|
||||
}
|
||||
}
|
||||
|
||||
const Map<pxr::SdfPath, Vector<ID *>> &USDHierarchyIterator::get_exported_prim_map() const
|
||||
{
|
||||
return exported_prim_map_;
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr USDHierarchyIterator::get_stage() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
void USDHierarchyIterator::add_to_prim_map(const pxr::SdfPath &usd_path, const ID *id) const
|
||||
{
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
ID *local_id = BKE_libblock_find_name(bmain_, GS(id->name), id->name + 2);
|
||||
if (local_id) {
|
||||
Vector<ID *> &id_list = exported_prim_map_.lookup_or_add_default(usd_path);
|
||||
if (!id_list.contains(local_id)) {
|
||||
id_list.append(local_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USDExporterContext USDHierarchyIterator::create_point_instancer_context(
|
||||
const HierarchyContext *context, const USDExporterContext &export_context) const
|
||||
{
|
||||
BLI_assert(context && context->object);
|
||||
std::string base_name = std::string(BKE_id_name(context->object->id)).append("_base");
|
||||
std::string safe_name = make_safe_name(base_name, export_context.export_params.allow_unicode);
|
||||
|
||||
pxr::SdfPath base_path = export_context.usd_path.GetParentPath().AppendChild(
|
||||
pxr::TfToken(safe_name));
|
||||
|
||||
return {export_context.bmain,
|
||||
export_context.depsgraph,
|
||||
export_context.stage,
|
||||
base_path,
|
||||
export_context.get_time_code,
|
||||
export_context.export_params,
|
||||
export_context.export_file_path,
|
||||
export_context.export_image_fn,
|
||||
export_context.add_skel_mapping_fn,
|
||||
export_context.hierarchy_iterator};
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
#include "usd.hh"
|
||||
#include "usd_exporter_context.hh"
|
||||
#include "usd_skel_convert.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usd/timeCode.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Object;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
using io::AbstractHierarchyIterator;
|
||||
using io::AbstractHierarchyWriter;
|
||||
using io::HierarchyContext;
|
||||
|
||||
class USDHierarchyIterator : public AbstractHierarchyIterator {
|
||||
private:
|
||||
const pxr::UsdStageRefPtr stage_;
|
||||
pxr::UsdTimeCode export_time_;
|
||||
const USDExportParams ¶ms_;
|
||||
|
||||
ObjExportMap armature_export_map_;
|
||||
ObjExportMap skinned_mesh_export_map_;
|
||||
ObjExportMap shape_key_mesh_export_map_;
|
||||
|
||||
/*
|
||||
* The field below is mutable because it is used to keep track
|
||||
* of what the exporter is doing. This is necessary even when all
|
||||
* the other export settings are to remain const.
|
||||
*/
|
||||
|
||||
/* Map a USD prim path to a list of Blender IDs associated with that prim.
|
||||
* This map is updated by writers during stage export. */
|
||||
mutable Map<pxr::SdfPath, Vector<ID *>> exported_prim_map_;
|
||||
|
||||
/* Map prototype_paths[instancer path] = [
|
||||
* (proto_path_1, proto_object_1), (proto_path_2, proto_object_2), ... ] */
|
||||
Map<pxr::SdfPath, Set<std::pair<pxr::SdfPath, Object *>>> prototype_paths_;
|
||||
|
||||
public:
|
||||
USDHierarchyIterator(Main *bmain,
|
||||
Depsgraph *depsgraph,
|
||||
pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams ¶ms);
|
||||
|
||||
void set_export_frame(float frame_nr);
|
||||
|
||||
std::string make_valid_name(const std::string &name) const override;
|
||||
|
||||
void process_usd_skel() const;
|
||||
|
||||
/* Get the USD stage being exported to. */
|
||||
pxr::UsdStageRefPtr get_stage() const;
|
||||
|
||||
/* Get the mapping of exported objects to their USD prim paths. */
|
||||
const Map<pxr::SdfPath, Vector<ID *>> &get_exported_prim_map() const;
|
||||
|
||||
/* Add an ID to the prim map for a given USD path. */
|
||||
void add_to_prim_map(const pxr::SdfPath &usd_path, const ID *id) const;
|
||||
|
||||
protected:
|
||||
bool mark_as_weak_export(const Object *object) const override;
|
||||
bool determine_point_instancers(const HierarchyContext *context);
|
||||
|
||||
AbstractHierarchyWriter *create_transform_writer(const HierarchyContext *context) override;
|
||||
AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override;
|
||||
AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override;
|
||||
AbstractHierarchyWriter *create_particle_writer(const HierarchyContext *context) override;
|
||||
|
||||
void release_writer(AbstractHierarchyWriter *writer) override;
|
||||
|
||||
bool include_data_writers(const HierarchyContext *context) const override;
|
||||
bool include_child_writers(const HierarchyContext *context) const override;
|
||||
|
||||
private:
|
||||
USDExporterContext create_usd_export_context(const HierarchyContext *context);
|
||||
USDExporterContext create_point_instancer_context(
|
||||
const HierarchyContext *context, const USDExporterContext &usd_export_context) const;
|
||||
|
||||
void add_usd_skel_export_mapping(const Object *obj, const pxr::SdfPath &usd_path);
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
684
blender-5.2.0/source/blender/io/usd/intern/usd_hook.cc
Normal file
684
blender-5.2.0/source/blender/io/usd/intern/usd_hook.cc
Normal file
@@ -0,0 +1,684 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_hook.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_hook.hh"
|
||||
#include "usd_asset_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_reader_stage.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
#include "RNA_types.hh"
|
||||
#include "bpy_rna.hh"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <pxr/external/boost/python/call_method.hpp>
|
||||
#include <pxr/external/boost/python/class.hpp>
|
||||
#include <pxr/external/boost/python/dict.hpp>
|
||||
#include <pxr/external/boost/python/import.hpp>
|
||||
#include <pxr/external/boost/python/list.hpp>
|
||||
#include <pxr/external/boost/python/ref.hpp>
|
||||
#include <pxr/external/boost/python/return_value_policy.hpp>
|
||||
#include <pxr/external/boost/python/to_python_converter.hpp>
|
||||
#include <pxr/external/boost/python/tuple.hpp>
|
||||
|
||||
namespace blender {
|
||||
|
||||
using namespace pxr::pxr_boost;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
using USDHookList = std::list<std::unique_ptr<USDHook>>;
|
||||
using ImportedPrimMap = Map<pxr::SdfPath, Vector<PointerRNA>>;
|
||||
|
||||
/* USD hook type declarations */
|
||||
static USDHookList &hook_list()
|
||||
{
|
||||
static USDHookList hooks{};
|
||||
return hooks;
|
||||
}
|
||||
|
||||
void USD_register_hook(std::unique_ptr<USDHook> hook)
|
||||
{
|
||||
if (USD_find_hook_name(hook->idname)) {
|
||||
/* The hook is already in the list. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add hook type to the list. */
|
||||
hook_list().push_back(std::move(hook));
|
||||
}
|
||||
|
||||
void USD_unregister_hook(const USDHook *hook)
|
||||
{
|
||||
hook_list().remove_if(
|
||||
[hook](const std::unique_ptr<USDHook> &item) { return item.get() == hook; });
|
||||
}
|
||||
|
||||
USDHook *USD_find_hook_name(const char idname[])
|
||||
{
|
||||
/* sanity checks */
|
||||
if (hook_list().empty() || (idname == nullptr) || (idname[0] == 0)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USDHookList::iterator hook_iter = std::find_if(
|
||||
hook_list().begin(), hook_list().end(), [idname](const std::unique_ptr<USDHook> &item) {
|
||||
return STREQ(item->idname, idname);
|
||||
});
|
||||
|
||||
return (hook_iter == hook_list().end()) ? nullptr : hook_iter->get();
|
||||
}
|
||||
|
||||
/* Convert PointerRNA to a PyObject*. */
|
||||
struct PointerRNAToPython {
|
||||
|
||||
/* We pass the argument by value because we need
|
||||
* to obtain a non-const pointer to it. */
|
||||
static PyObject *convert(PointerRNA ptr)
|
||||
{
|
||||
return pyrna_struct_CreatePyObject(&ptr);
|
||||
}
|
||||
};
|
||||
|
||||
/* Encapsulate arguments for scene export. */
|
||||
class USDSceneExportContext {
|
||||
private:
|
||||
pxr::UsdStageRefPtr stage_;
|
||||
PointerRNA depsgraph_ptr_;
|
||||
const USDHierarchyIterator *hierarchy_iterator_ = nullptr;
|
||||
|
||||
public:
|
||||
USDSceneExportContext(const USDHierarchyIterator *iter, Depsgraph *depsgraph)
|
||||
: stage_(iter->get_stage()), hierarchy_iterator_(iter)
|
||||
{
|
||||
depsgraph_ptr_ = RNA_pointer_create_discrete(nullptr, RNA_Depsgraph, depsgraph);
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr get_stage() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
const PointerRNA &get_depsgraph() const
|
||||
{
|
||||
return depsgraph_ptr_;
|
||||
}
|
||||
|
||||
python::dict get_prim_map() const
|
||||
{
|
||||
python::dict result;
|
||||
|
||||
const auto &exported_prim_map = hierarchy_iterator_->get_exported_prim_map();
|
||||
exported_prim_map.foreach_item([&](const pxr::SdfPath &path, const Vector<ID *> &ids) {
|
||||
python::list id_list;
|
||||
for (ID *id : ids) {
|
||||
if (id) {
|
||||
PointerRNA ptr_rna = RNA_id_pointer_create(id);
|
||||
id_list.append(ptr_rna);
|
||||
}
|
||||
}
|
||||
result[path] = id_list;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/* Encapsulate arguments for scene import. */
|
||||
class USDSceneImportContext {
|
||||
private:
|
||||
pxr::UsdStageRefPtr stage_;
|
||||
ImportedPrimMap prim_map_;
|
||||
python::dict *prim_map_dict_ = nullptr;
|
||||
|
||||
public:
|
||||
USDSceneImportContext(pxr::UsdStageRefPtr in_stage, const ImportedPrimMap &in_prim_map)
|
||||
: stage_(in_stage), prim_map_(in_prim_map)
|
||||
{
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
delete prim_map_dict_;
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr get_stage() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
python::dict get_prim_map()
|
||||
{
|
||||
if (!prim_map_dict_) {
|
||||
prim_map_dict_ = new python::dict;
|
||||
|
||||
prim_map_.foreach_item([&](const pxr::SdfPath &path, const Vector<PointerRNA> &ids) {
|
||||
if (!prim_map_dict_->has_key(path)) {
|
||||
(*prim_map_dict_)[path] = python::list();
|
||||
}
|
||||
|
||||
python::list list = python::extract<python::list>((*prim_map_dict_)[path]);
|
||||
for (const auto &ptr_rna : ids) {
|
||||
list.append(ptr_rna);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return *prim_map_dict_;
|
||||
}
|
||||
};
|
||||
|
||||
/* Encapsulate arguments for material export. */
|
||||
class USDMaterialExportContext {
|
||||
private:
|
||||
pxr::UsdStageRefPtr stage_;
|
||||
USDExportParams params_ = {};
|
||||
ReportList *reports_ = nullptr;
|
||||
|
||||
public:
|
||||
USDMaterialExportContext(pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams ¶ms,
|
||||
ReportList *reports)
|
||||
: stage_(stage), params_(params), reports_(reports)
|
||||
{
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr get_stage() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the USD asset export path for the given texture image. The image will be copied
|
||||
* to the export directory if exporting textures is enabled in the export options. The
|
||||
* function may return an empty string in case of an error.
|
||||
*/
|
||||
std::string export_texture(python::object obj) const
|
||||
{
|
||||
ID *id;
|
||||
if (!pyrna_id_FromPyObject(obj.ptr(), &id)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (GS(id->name) != ID_IM) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Image *ima = reinterpret_cast<Image *>(id);
|
||||
|
||||
std::string asset_path = get_tex_image_asset_filepath(ima, stage_, params_);
|
||||
|
||||
if (params_.export_textures) {
|
||||
io::usd::export_texture(ima, stage_, params_.overwrite_textures, reports_);
|
||||
}
|
||||
|
||||
return asset_path;
|
||||
}
|
||||
};
|
||||
|
||||
/* Encapsulate arguments for material import. */
|
||||
class USDMaterialImportContext {
|
||||
private:
|
||||
pxr::UsdStageRefPtr stage_;
|
||||
USDImportParams params_ = {};
|
||||
ReportList *reports_ = nullptr;
|
||||
|
||||
public:
|
||||
USDMaterialImportContext(pxr::UsdStageRefPtr stage,
|
||||
const USDImportParams ¶ms,
|
||||
ReportList *reports)
|
||||
: stage_(stage), params_(params), reports_(reports)
|
||||
{
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr get_stage() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given texture asset path is a URI or is relative to a USDZ archive,
|
||||
* attempt to copy the texture to the local file system and returns a `tuple[str, bool]`,
|
||||
* containing the asset's local path and a boolean indicating whether the path references
|
||||
* a temporary file (in the case where imported textures should be packed).
|
||||
* The original asset path will be returned unchanged if it's already a local file
|
||||
* or if it could not be copied to a local destination.
|
||||
*/
|
||||
python::tuple import_texture(const std::string &asset_path) const
|
||||
{
|
||||
if (!should_import_asset(asset_path)) {
|
||||
/* This path does not need to be imported, so return it unchanged. */
|
||||
return python::make_tuple(asset_path, false);
|
||||
}
|
||||
|
||||
const char *textures_dir = params_.import_textures_mode == TexImportMode::Pack ?
|
||||
temp_textures_dir() :
|
||||
params_.import_textures_dir;
|
||||
|
||||
const TexNameCollisionMode name_collision_mode = params_.import_textures_mode ==
|
||||
TexImportMode::Pack ?
|
||||
TexNameCollisionMode::Overwrite :
|
||||
params_.tex_name_collision_mode;
|
||||
|
||||
std::string import_path = import_asset(
|
||||
asset_path, textures_dir, name_collision_mode, reports_);
|
||||
|
||||
if (import_path == asset_path) {
|
||||
/* Path is unchanged. */
|
||||
return python::make_tuple(asset_path, false);
|
||||
}
|
||||
|
||||
const bool is_temporary = params_.import_textures_mode == TexImportMode::Pack;
|
||||
return python::make_tuple(import_path, is_temporary);
|
||||
}
|
||||
};
|
||||
|
||||
void register_hook_converters()
|
||||
{
|
||||
static bool registered = false;
|
||||
|
||||
/* No need to register if there are no hooks. */
|
||||
if (hook_list().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
registered = true;
|
||||
|
||||
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||
|
||||
/* We must import these modules for the USD type converters to work. */
|
||||
python::import("pxr.Usd");
|
||||
python::import("pxr.UsdShade");
|
||||
|
||||
/* Register converter from PoinerRNA to a PyObject*. */
|
||||
python::to_python_converter<PointerRNA, PointerRNAToPython>();
|
||||
|
||||
/* Register context class converters. */
|
||||
python::class_<USDSceneExportContext>("USDSceneExportContext", python::no_init)
|
||||
.def("get_stage", &USDSceneExportContext::get_stage)
|
||||
.def("get_depsgraph",
|
||||
&USDSceneExportContext::get_depsgraph,
|
||||
python::return_value_policy<python::return_by_value>())
|
||||
.def("get_prim_map", &USDSceneExportContext::get_prim_map);
|
||||
|
||||
python::class_<USDMaterialExportContext>("USDMaterialExportContext", python::no_init)
|
||||
.def("get_stage", &USDMaterialExportContext::get_stage)
|
||||
.def("export_texture", &USDMaterialExportContext::export_texture);
|
||||
|
||||
python::class_<USDSceneImportContext>("USDSceneImportContext", python::no_init)
|
||||
.def("get_stage", &USDSceneImportContext::get_stage)
|
||||
.def("get_prim_map", &USDSceneImportContext::get_prim_map);
|
||||
|
||||
python::class_<USDMaterialImportContext>("USDMaterialImportContext", python::no_init)
|
||||
.def("get_stage", &USDMaterialImportContext::get_stage)
|
||||
.def("import_texture", &USDMaterialImportContext::import_texture);
|
||||
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
/* Retrieve and report the current Python error. */
|
||||
static void handle_python_error(USDHook *hook, ReportList *reports)
|
||||
{
|
||||
if (!PyErr_Occurred()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PyErr_Print();
|
||||
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"An exception occurred invoking USD hook '%s'. Please see the console for details",
|
||||
hook->name);
|
||||
}
|
||||
|
||||
/* Abstract base class to facilitate calling a function with a given
|
||||
* signature defined by the registered USDHook classes. Subclasses
|
||||
* override virtual methods to specify the hook function name and to
|
||||
* call the hook with the required arguments.
|
||||
*/
|
||||
class USDHookInvoker {
|
||||
private:
|
||||
ReportList *reports_;
|
||||
|
||||
public:
|
||||
explicit USDHookInvoker(ReportList *reports) : reports_(reports) {}
|
||||
virtual ~USDHookInvoker() = default;
|
||||
|
||||
/* Attempt to call the function, if defined by the registered hooks. */
|
||||
void call()
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||
init_in_gil();
|
||||
|
||||
/* Iterate over the hooks and invoke the hook function, if it's defined. */
|
||||
USDHookList::const_iterator hook_iter = hook_list().begin();
|
||||
while (hook_iter != hook_list().end()) {
|
||||
|
||||
/* XXX: Not sure if this is necessary:
|
||||
* Advance the iterator before invoking the callback, to guard
|
||||
* against the unlikely error where the hook is de-registered in
|
||||
* the callback. This would prevent a crash due to the iterator
|
||||
* getting invalidated. */
|
||||
USDHook *hook = hook_iter->get();
|
||||
++hook_iter;
|
||||
|
||||
if (!hook->rna_ext.data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
PyObject *hook_obj = static_cast<PyObject *>(hook->rna_ext.data);
|
||||
|
||||
if (!PyObject_HasAttrString(hook_obj, function_name())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
call_hook(hook_obj);
|
||||
}
|
||||
catch (python::error_already_set const &) {
|
||||
handle_python_error(hook, reports_);
|
||||
}
|
||||
catch (...) {
|
||||
BKE_reportf(
|
||||
reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name);
|
||||
}
|
||||
}
|
||||
|
||||
release_in_gil();
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
protected:
|
||||
/* Override to specify the name of the function to be called. */
|
||||
virtual const char *function_name() const = 0;
|
||||
/* Override to call the function of the given object with the
|
||||
* required arguments, e.g.,
|
||||
*
|
||||
* python::call_method<void>(hook_obj, function_name(), arg1, arg2); */
|
||||
virtual void call_hook(PyObject *hook_obj) = 0;
|
||||
|
||||
virtual void init_in_gil() {};
|
||||
virtual void release_in_gil() {};
|
||||
};
|
||||
|
||||
class OnExportInvoker final : public USDHookInvoker {
|
||||
private:
|
||||
USDSceneExportContext hook_context_;
|
||||
|
||||
public:
|
||||
OnExportInvoker(const USDHierarchyIterator *iter, Depsgraph *depsgraph, ReportList *reports)
|
||||
: USDHookInvoker(reports), hook_context_(iter, depsgraph)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
const char *function_name() const override
|
||||
{
|
||||
return "on_export";
|
||||
}
|
||||
|
||||
void call_hook(PyObject *hook_obj) override
|
||||
{
|
||||
python::call_method<bool>(hook_obj, function_name(), python::ref(hook_context_));
|
||||
}
|
||||
};
|
||||
|
||||
class OnMaterialExportInvoker final : public USDHookInvoker {
|
||||
private:
|
||||
USDMaterialExportContext hook_context_;
|
||||
pxr::UsdShadeMaterial usd_material_;
|
||||
PointerRNA material_ptr_;
|
||||
|
||||
public:
|
||||
OnMaterialExportInvoker(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDExportParams &export_params,
|
||||
ReportList *reports)
|
||||
: USDHookInvoker(reports),
|
||||
hook_context_(stage, export_params, reports),
|
||||
usd_material_(usd_material)
|
||||
{
|
||||
material_ptr_ = RNA_pointer_create_discrete(nullptr, RNA_Material, material);
|
||||
}
|
||||
|
||||
private:
|
||||
const char *function_name() const override
|
||||
{
|
||||
return "on_material_export";
|
||||
}
|
||||
|
||||
void call_hook(PyObject *hook_obj) override
|
||||
{
|
||||
python::call_method<bool>(
|
||||
hook_obj, function_name(), python::ref(hook_context_), material_ptr_, usd_material_);
|
||||
}
|
||||
};
|
||||
|
||||
class OnImportInvoker final : public USDHookInvoker {
|
||||
private:
|
||||
USDSceneImportContext hook_context_;
|
||||
|
||||
public:
|
||||
OnImportInvoker(pxr::UsdStageRefPtr stage, const ImportedPrimMap &prim_map, ReportList *reports)
|
||||
: USDHookInvoker(reports), hook_context_(stage, prim_map)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
const char *function_name() const override
|
||||
{
|
||||
return "on_import";
|
||||
}
|
||||
|
||||
void call_hook(PyObject *hook_obj) override
|
||||
{
|
||||
python::call_method<bool>(hook_obj, function_name(), python::ref(hook_context_));
|
||||
}
|
||||
|
||||
void release_in_gil() override
|
||||
{
|
||||
hook_context_.release();
|
||||
}
|
||||
};
|
||||
|
||||
class MaterialImportPollInvoker final : public USDHookInvoker {
|
||||
private:
|
||||
USDMaterialImportContext hook_context_;
|
||||
pxr::UsdShadeMaterial usd_material_;
|
||||
bool result_ = false;
|
||||
|
||||
public:
|
||||
MaterialImportPollInvoker(pxr::UsdStageRefPtr stage,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports)
|
||||
: USDHookInvoker(reports),
|
||||
hook_context_(stage, import_params, reports),
|
||||
usd_material_(usd_material)
|
||||
{
|
||||
}
|
||||
|
||||
bool result() const
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
|
||||
private:
|
||||
const char *function_name() const override
|
||||
{
|
||||
return "material_import_poll";
|
||||
}
|
||||
|
||||
void call_hook(PyObject *hook_obj) override
|
||||
{
|
||||
/* If we already know that one of the registered hook classes can import the material
|
||||
* because it returned true in a previous invocation of the callback, we skip the call. */
|
||||
if (!result_) {
|
||||
result_ = python::call_method<bool>(
|
||||
hook_obj, function_name(), python::ref(hook_context_), usd_material_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class OnMaterialImportInvoker final : public USDHookInvoker {
|
||||
private:
|
||||
USDMaterialImportContext hook_context_;
|
||||
pxr::UsdShadeMaterial usd_material_;
|
||||
PointerRNA material_ptr_;
|
||||
bool result_ = false;
|
||||
|
||||
public:
|
||||
OnMaterialImportInvoker(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports)
|
||||
: USDHookInvoker(reports),
|
||||
hook_context_(stage, import_params, reports),
|
||||
usd_material_(usd_material)
|
||||
{
|
||||
material_ptr_ = RNA_pointer_create_discrete(nullptr, RNA_Material, material);
|
||||
}
|
||||
|
||||
bool result() const
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
|
||||
private:
|
||||
const char *function_name() const override
|
||||
{
|
||||
return "on_material_import";
|
||||
}
|
||||
|
||||
void call_hook(PyObject *hook_obj) override
|
||||
{
|
||||
result_ |= python::call_method<bool>(
|
||||
hook_obj, function_name(), python::ref(hook_context_), material_ptr_, usd_material_);
|
||||
}
|
||||
};
|
||||
|
||||
void call_export_hooks(Depsgraph *depsgraph, const USDHierarchyIterator *iter, ReportList *reports)
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnExportInvoker on_export(iter, depsgraph, reports);
|
||||
on_export.call();
|
||||
}
|
||||
|
||||
void call_material_export_hooks(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDExportParams &export_params,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OnMaterialExportInvoker on_material_export(
|
||||
stage, material, usd_material, export_params, reports);
|
||||
on_material_export.call();
|
||||
}
|
||||
|
||||
void call_import_hooks(USDStageReader *archive, ReportList *reports)
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<USDPrimReader *> &readers = archive->readers();
|
||||
const ImportSettings &settings = archive->settings();
|
||||
ImportedPrimMap prim_map;
|
||||
|
||||
/* Resize based on the typical scenario where there will be both Object and Data entries
|
||||
* in the map in addition to each material. */
|
||||
prim_map.reserve((readers.size() * 2) + settings.usd_path_to_mat.size());
|
||||
|
||||
for (const USDPrimReader *reader : readers) {
|
||||
Object *ob = reader->object();
|
||||
|
||||
prim_map.lookup_or_add_default(reader->object_prim_path())
|
||||
.append(RNA_id_pointer_create(&ob->id));
|
||||
if (ob->data) {
|
||||
prim_map.lookup_or_add_default(reader->data_prim_path())
|
||||
.append(RNA_id_pointer_create(ob->data));
|
||||
}
|
||||
}
|
||||
|
||||
settings.usd_path_to_mat.foreach_item([&prim_map](const pxr::SdfPath &path, Material *mat) {
|
||||
prim_map.lookup_or_add_default(path).append(RNA_id_pointer_create(&mat->id));
|
||||
});
|
||||
|
||||
OnImportInvoker on_import(archive->stage(), prim_map, reports);
|
||||
on_import.call();
|
||||
}
|
||||
|
||||
bool have_material_import_hook(pxr::UsdStageRefPtr stage,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MaterialImportPollInvoker poll(stage, usd_material, import_params, reports);
|
||||
poll.call();
|
||||
|
||||
return poll.result();
|
||||
}
|
||||
|
||||
bool call_material_import_hooks(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (hook_list().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnMaterialImportInvoker on_material_import(
|
||||
stage, material, usd_material, import_params, reports);
|
||||
on_material_import.call();
|
||||
return on_material_import.result();
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
56
blender-5.2.0/source/blender/io/usd/intern/usd_hook.hh
Normal file
56
blender-5.2.0/source/blender/io/usd/intern/usd_hook.hh
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Material;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct USDExportParams;
|
||||
class USDHierarchyIterator;
|
||||
struct USDImportParams;
|
||||
class USDStageReader;
|
||||
|
||||
/** Ensure classes and type converters necessary for invoking import and export hooks
|
||||
* are registered. */
|
||||
void register_hook_converters();
|
||||
|
||||
/** Call the 'on_export' chaser function defined in the registered #USDHook classes. */
|
||||
void call_export_hooks(Depsgraph *depsgraph,
|
||||
const USDHierarchyIterator *iter,
|
||||
ReportList *reports);
|
||||
|
||||
/** Call the 'on_material_export' hook functions defined in the registered #USDHook classes. */
|
||||
void call_material_export_hooks(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDExportParams &export_params,
|
||||
ReportList *reports);
|
||||
|
||||
/** Call the 'on_import' chaser function defined in the registered USDHook classes. */
|
||||
void call_import_hooks(USDStageReader *archive, ReportList *reports);
|
||||
|
||||
/** Returns true if there is a registered #USDHook class that can convert the given material. */
|
||||
bool have_material_import_hook(pxr::UsdStageRefPtr stage,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports);
|
||||
|
||||
/** Call the 'on_material_import' hook functions defined in the registered #USDHook classes.
|
||||
* Returns true if any of the hooks were successful, false otherwise. */
|
||||
bool call_material_import_hooks(pxr::UsdStageRefPtr stage,
|
||||
Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const USDImportParams &import_params,
|
||||
ReportList *reports);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,192 @@
|
||||
/* SPDX-FileCopyrightText: 2025 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_instancing_utils.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include <pxr/usd/sdf/copyUtils.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/primCompositionQuery.h>
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
#include <pxr/usd/usd/references.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* We need an ordered map so we use std::map. */
|
||||
using PathMap = std::map<pxr::SdfPath, pxr::SdfPath>;
|
||||
using PathSet = Set<pxr::SdfPath>;
|
||||
|
||||
/* Map an instanceable prim path to a list of prototype prim paths. */
|
||||
using ReferencesMap = Map<pxr::SdfPath, Vector<pxr::SdfPath>>;
|
||||
|
||||
/* Convert the given prototype prim to an instance by deleting its children and making
|
||||
* it an instanceable reference to the prim at ref_path. */
|
||||
static void convert_proto_to_instance(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath &proto_path,
|
||||
const pxr::SdfPath &ref_path)
|
||||
{
|
||||
pxr::UsdPrim proto_prim = stage->GetPrimAtPath(proto_path);
|
||||
|
||||
if (!proto_prim) {
|
||||
CLOG_ERROR(&LOG, "Couldn't find prototype prim %s", proto_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Collect child paths. */
|
||||
pxr::SdfPathVector child_paths;
|
||||
pxr::UsdPrimSiblingRange children = proto_prim.GetFilteredChildren(
|
||||
pxr::Usd_PrimFlagsPredicate());
|
||||
for (const auto &child_prim : children) {
|
||||
child_paths.push_back(child_prim.GetPath());
|
||||
}
|
||||
|
||||
/* Remove children from the sage. */
|
||||
for (const pxr::SdfPath &child_path : child_paths) {
|
||||
stage->RemovePrim(child_path);
|
||||
}
|
||||
|
||||
proto_prim.GetReferences().AddInternalReference(pxr::SdfPath(ref_path));
|
||||
proto_prim.SetInstanceable(true);
|
||||
}
|
||||
|
||||
void process_scene_graph_instances(const USDExportParams &export_params, pxr::UsdStageRefPtr stage)
|
||||
{
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Collect paths to instanceable references and prototypes. */
|
||||
PathSet protos;
|
||||
/* Map an instance to the prototypes it references. */
|
||||
ReferencesMap references_map;
|
||||
|
||||
pxr::UsdPrimRange range(stage->GetPseudoRoot());
|
||||
for (pxr::UsdPrim prim : range) {
|
||||
if (prim.IsInstanceable()) {
|
||||
/* Get the prototypes referenced by this prim. */
|
||||
pxr::UsdPrimCompositionQuery query = pxr::UsdPrimCompositionQuery::GetDirectReferences(prim);
|
||||
Vector<pxr::SdfPath> references;
|
||||
for (const auto &arc : query.GetCompositionArcs()) {
|
||||
pxr::SdfPath target_prim_path = arc.GetTargetPrimPath();
|
||||
protos.add(target_prim_path);
|
||||
references.append(target_prim_path);
|
||||
}
|
||||
references_map.add(prim.GetPath(), references);
|
||||
}
|
||||
}
|
||||
|
||||
if (protos.is_empty()) {
|
||||
/* No prototypes to move. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Map an original prototype path to the location where it will be copied. */
|
||||
PathMap proto_to_copy_map;
|
||||
|
||||
std::string protos_root_str(export_params.root_prim_path);
|
||||
protos_root_str += "/prototypes";
|
||||
pxr::SdfPath protos_root_path = get_unique_path(stage, protos_root_str);
|
||||
|
||||
/* Create the abstract prim under which prototypes will be copied. */
|
||||
if (!stage->CreateClassPrim(protos_root_path)) {
|
||||
CLOG_ERROR(&LOG, "Couldn't create class prim %s.", protos_root_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each original prototype, create a placeholder Xform prim under the protos root
|
||||
* which will be the new location where the prototype will be copied.
|
||||
*/
|
||||
for (const pxr::SdfPath &proto_path : protos) {
|
||||
pxr::SdfPath copy_path = protos_root_path;
|
||||
|
||||
copy_path = copy_path.AppendChild(proto_path.GetNameToken());
|
||||
copy_path = get_unique_path(stage, copy_path.GetAsString());
|
||||
|
||||
/* Create the placeholder prim. */
|
||||
static pxr::TfToken xform_type_tok("Xform");
|
||||
pxr::UsdPrim dest_prim = stage->DefinePrim(copy_path, xform_type_tok);
|
||||
if (!dest_prim) {
|
||||
CLOG_ERROR(&LOG,
|
||||
"Couldn't create destination prim %s for copying prototype %s",
|
||||
copy_path.GetAsString().c_str(),
|
||||
proto_path.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Record where original prototype path will be copied. */
|
||||
proto_to_copy_map.insert(std::make_pair(proto_path, dest_prim.GetPath()));
|
||||
}
|
||||
|
||||
/* Update all references to point to new prototype locations. */
|
||||
for (const auto item : references_map.items()) {
|
||||
pxr::SdfPath inst_path = item.key;
|
||||
pxr::UsdPrim inst_prim = stage->GetPrimAtPath(item.key);
|
||||
if (!inst_prim) {
|
||||
CLOG_ERROR(&LOG, "Couldn't get prim for instance %s.", inst_path.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Updated references pointing to new prototype locations. */
|
||||
Vector<pxr::SdfPath> new_ref_targets;
|
||||
const Vector<pxr::SdfPath> &ref_targets = item.value;
|
||||
for (const pxr::SdfPath &target_path : ref_targets) {
|
||||
PathMap::const_iterator iter = proto_to_copy_map.find(target_path);
|
||||
if (iter != proto_to_copy_map.end()) {
|
||||
new_ref_targets.append(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
/* Replace existing references with the updated ones. */
|
||||
if (!new_ref_targets.is_empty()) {
|
||||
pxr::UsdReferences refs = inst_prim.GetReferences();
|
||||
refs.ClearReferences();
|
||||
for (const pxr::SdfPath &target : new_ref_targets) {
|
||||
refs.AddInternalReference(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the original prototypes to their new locations and update
|
||||
* the original prototype roots to be references to the new locations.
|
||||
* Since prototypes may be nested, we must copy the most nested prototypes
|
||||
* first by iterating backwards through the sorted prototype map.
|
||||
*/
|
||||
for (PathMap::reverse_iterator riter = proto_to_copy_map.rbegin();
|
||||
riter != proto_to_copy_map.rend();
|
||||
++riter)
|
||||
{
|
||||
const pxr::SdfPath &src_path = riter->first;
|
||||
const pxr::SdfPath &dst_path = riter->second;
|
||||
if (!pxr::SdfCopySpec(
|
||||
stage->GetRootLayer(), riter->first, stage->GetRootLayer(), riter->second))
|
||||
{
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't copy prim %s to %s",
|
||||
src_path.GetAsString().c_str(),
|
||||
dst_path.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
convert_proto_to_instance(stage, src_path, dst_path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2025 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
struct USDExportParams;
|
||||
|
||||
/**
|
||||
* This function processes the USD stage generated by the USD hierarchy iterator to
|
||||
* change scene graph instancing prototypes from defined USD prims to abstract prims.
|
||||
*
|
||||
* In the following example, instance /root/proto/Plane_0 references prototype prim
|
||||
* /root/proto_001/Plane_0:
|
||||
*
|
||||
* def Xform "root"
|
||||
* {
|
||||
* def Xform "proto_001"
|
||||
* {
|
||||
* def Xform "Plane_0"
|
||||
* {
|
||||
* def Mesh "Plane"
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* def Xform "proto"
|
||||
* {
|
||||
* def Xform "Plane_0" (
|
||||
* instanceable = true
|
||||
* prepend references = </root/proto_001/Plane_0>
|
||||
* )
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* The function will copy prototype /root/proto_001/Plane_0 under a new class prim
|
||||
* named /root/prototypes and convert prim /root/proto_001/Plane_0 to be an instance referencing
|
||||
* the copied abstract prim /root/prototypes/Plane_0.
|
||||
*
|
||||
* def Xform "root"
|
||||
* {
|
||||
* def Xform "proto"
|
||||
* {
|
||||
* def Xform "Plane_0" (
|
||||
* instanceable = true
|
||||
* prepend references = </root/prototypes/Plane_0>
|
||||
* )
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* def Xform "proto_001"
|
||||
* {
|
||||
* def Xform "Plane_0" (
|
||||
* instanceable = true
|
||||
* references = </root/prototypes/Plane_0>
|
||||
* )
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class "prototypes"
|
||||
* {
|
||||
* def Xform "Plane_0"
|
||||
* {
|
||||
* def Mesh "Plane"
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* \param export_params: The export parameters
|
||||
*
|
||||
* \param stage: Pointer to the stage to process
|
||||
*
|
||||
*/
|
||||
void process_scene_graph_instances(const USDExportParams &export_params,
|
||||
pxr::UsdStageRefPtr stage);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
489
blender-5.2.0/source/blender/io/usd/intern/usd_light_convert.cc
Normal file
489
blender-5.2.0/source/blender/io/usd/intern/usd_light_convert.cc
Normal file
@@ -0,0 +1,489 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_light_convert.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_asset_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_private.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
||||
#include <pxr/base/gf/rotation.h>
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdGeom/xformCache.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdLux/tokens.h>
|
||||
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_legacy_types.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_world_types.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace usdtokens {
|
||||
// Attribute values.
|
||||
static const pxr::TfToken pole_axis_z("Z", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
namespace {
|
||||
|
||||
struct WorldNtreeSearchPayload {
|
||||
const io::usd::USDExportParams ¶ms;
|
||||
pxr::UsdStageRefPtr stage;
|
||||
|
||||
WorldNtreeSearchPayload(const io::usd::USDExportParams &in_params, pxr::UsdStageRefPtr in_stage)
|
||||
: params(in_params), stage(in_stage)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // End anonymous namespace.
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/**
|
||||
* Load the image at the given path. Handle packing and copying based in the import options.
|
||||
* Return the opened image on success or a nullptr on failure.
|
||||
*/
|
||||
static Image *load_image(std::string tex_path, Main *bmain, const USDImportParams ¶ms)
|
||||
{
|
||||
/* Optionally copy the asset if it's inside a USDZ package. */
|
||||
const bool import_textures = params.import_textures_mode != TexImportMode::None &&
|
||||
should_import_asset(tex_path);
|
||||
|
||||
std::string imported_file_source_path = tex_path;
|
||||
|
||||
if (import_textures) {
|
||||
/* If we are packing the imported textures, we first write them
|
||||
* to a temporary directory. */
|
||||
const char *textures_dir = params.import_textures_mode == TexImportMode::Pack ?
|
||||
temp_textures_dir() :
|
||||
params.import_textures_dir;
|
||||
|
||||
const TexNameCollisionMode name_collision_mode = params.import_textures_mode ==
|
||||
TexImportMode::Pack ?
|
||||
TexNameCollisionMode::Overwrite :
|
||||
params.tex_name_collision_mode;
|
||||
|
||||
tex_path = import_asset(tex_path, textures_dir, name_collision_mode, nullptr);
|
||||
}
|
||||
|
||||
Image *image = BKE_image_load_exists(bmain, tex_path.c_str());
|
||||
if (!image) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (import_textures && imported_file_source_path != tex_path) {
|
||||
ensure_usd_source_path_prop(imported_file_source_path, &image->id);
|
||||
}
|
||||
|
||||
if (import_textures && params.import_textures_mode == TexImportMode::Pack &&
|
||||
!BKE_image_has_packedfile(image))
|
||||
{
|
||||
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain, &image->id));
|
||||
if (BLI_is_dir(temp_textures_dir())) {
|
||||
BLI_delete(temp_textures_dir(), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/* Create a new node of type 'new_node_type' and connect it
|
||||
* as an upstream source to 'dst_node' with the given sockets. */
|
||||
static bNode *append_node(bNode *dst_node,
|
||||
int new_node_type,
|
||||
const StringRef out_sock,
|
||||
const StringRef in_sock,
|
||||
bNodeTree *ntree,
|
||||
float offset)
|
||||
{
|
||||
bNode *src_node = bke::node_add_static_node(nullptr, *ntree, new_node_type);
|
||||
bke::node_add_link(*ntree,
|
||||
*src_node,
|
||||
*bke::node_find_socket(*src_node, SOCK_OUT, UString(out_sock)),
|
||||
*dst_node,
|
||||
*bke::node_find_socket(*dst_node, SOCK_IN, UString(in_sock)));
|
||||
|
||||
src_node->location[0] = dst_node->location[0] - offset;
|
||||
src_node->location[1] = dst_node->location[1];
|
||||
|
||||
return src_node;
|
||||
}
|
||||
|
||||
void world_material_to_dome_light(const USDExportParams ¶ms,
|
||||
const Scene *scene,
|
||||
pxr::UsdStageRefPtr stage)
|
||||
{
|
||||
if (!(stage && scene && scene->world)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WorldToDomeLight res;
|
||||
world_material_to_dome_light(scene, res);
|
||||
|
||||
if (!(res.color_found || res.image)) {
|
||||
/* No nodes to convert */
|
||||
return;
|
||||
}
|
||||
|
||||
std::string image_filepath;
|
||||
if (res.image) {
|
||||
/* Compute image filepath and export if needed. */
|
||||
image_filepath = get_tex_image_asset_filepath(res.image, stage, params);
|
||||
if (image_filepath.empty()) {
|
||||
return;
|
||||
}
|
||||
if (params.export_textures) {
|
||||
export_texture(res.image, stage, params.overwrite_textures);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create USD dome light. */
|
||||
pxr::SdfPath env_light_path = get_unique_path(stage, params.root_prim_path + "/env_light");
|
||||
pxr::UsdLuxDomeLight dome_light = pxr::UsdLuxDomeLight::Define(stage, env_light_path);
|
||||
colorspace_apply_to_prim(dome_light.GetPrim());
|
||||
|
||||
if (res.image) {
|
||||
/* Use existing image texture file. */
|
||||
dome_light.CreateTextureFileAttr().Set(pxr::SdfAssetPath(image_filepath));
|
||||
|
||||
/* Set optional color multiplication. */
|
||||
if (res.mult_found) {
|
||||
pxr::GfVec3f color_val(res.color_mult[0], res.color_mult[1], res.color_mult[2]);
|
||||
dome_light.CreateColorAttr().Set(color_val);
|
||||
}
|
||||
|
||||
/* Set transform. */
|
||||
pxr::GfVec3d angles = res.transform.DecomposeRotation(
|
||||
pxr::GfVec3d::ZAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::XAxis());
|
||||
pxr::GfVec3f rot_vec(angles[2], angles[1], angles[0]);
|
||||
pxr::UsdGeomXformCommonAPI xform_api(dome_light);
|
||||
xform_api.SetRotate(rot_vec, pxr::UsdGeomXformCommonAPI::RotationOrderXYZ);
|
||||
}
|
||||
else if (res.color_found) {
|
||||
/* If no texture is found export a solid color texture as a stand-in so that Hydra
|
||||
* renderers don't throw errors. */
|
||||
dome_light.CreateIntensityAttr().Set(res.intensity);
|
||||
|
||||
std::string source_path = cache_image_color(res.color);
|
||||
const std::string base_path = stage->GetRootLayer()->GetRealPath();
|
||||
|
||||
char file_name[FILE_MAX];
|
||||
BLI_path_split_file_part(source_path.c_str(), file_name, FILE_MAX);
|
||||
char dest_path[FILE_MAX];
|
||||
BLI_path_split_dir_part(base_path.c_str(), dest_path, FILE_MAX);
|
||||
|
||||
BLI_path_append_dir(dest_path, FILE_MAX, "textures");
|
||||
BLI_dir_create_recursive(dest_path);
|
||||
|
||||
BLI_path_append(dest_path, FILE_MAX, file_name);
|
||||
|
||||
if (BLI_copy(source_path.c_str(), dest_path) != 0) {
|
||||
CLOG_WARN(&LOG, "USD Export: Couldn't write world color image to %s", dest_path);
|
||||
}
|
||||
else {
|
||||
BLI_path_join(dest_path, FILE_MAX, ".", "textures", file_name);
|
||||
BLI_string_replace_char(dest_path, '\\', '/');
|
||||
dome_light.CreateTextureFileAttr().Set(pxr::SdfAssetPath(dest_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Import the dome light as a world material. */
|
||||
|
||||
void dome_light_to_world_material(const USDImportParams ¶ms,
|
||||
Scene *scene,
|
||||
Main *bmain,
|
||||
const USDImportDomeLightData &dome_light_data,
|
||||
const pxr::UsdPrim &prim,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
if (!(scene && scene->world && prim)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bNodeTree *ntree = scene->world->nodetree;
|
||||
BLI_assert(ntree != nullptr);
|
||||
bNode *output = nullptr;
|
||||
bNode *bgshader = nullptr;
|
||||
|
||||
/* We never delete existing nodes, but we might disconnect them
|
||||
* and move them out of the way. */
|
||||
|
||||
/* Look for the output and background shader nodes, which we will reuse. */
|
||||
for (bNode *node : ntree->all_nodes()) {
|
||||
if (node->type_legacy == SH_NODE_OUTPUT_WORLD) {
|
||||
output = node;
|
||||
}
|
||||
else if (node->type_legacy == SH_NODE_BACKGROUND) {
|
||||
bgshader = node;
|
||||
}
|
||||
else {
|
||||
/* Move existing node out of the way. */
|
||||
node->location[1] += 300;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the output and background shader nodes, if they don't exist. */
|
||||
if (!output) {
|
||||
output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_WORLD);
|
||||
output->location[0] = 300.0f;
|
||||
output->location[1] = 300.0f;
|
||||
}
|
||||
|
||||
if (!bgshader) {
|
||||
bgshader = append_node(output, SH_NODE_BACKGROUND, "Background", "Surface", ntree, 200);
|
||||
|
||||
/* Set the default background color. */
|
||||
bNodeSocket *color_sock = bke::node_find_socket(*bgshader, SOCK_IN, "Color"_ustr);
|
||||
copy_v3_v3(color_sock->default_value_typed<bNodeSocketValueRGBA>()->value,
|
||||
&scene->world->horr);
|
||||
}
|
||||
|
||||
/* Make sure the first input to the shader node is disconnected. */
|
||||
bNodeSocket *shader_input = bke::node_find_socket(*bgshader, SOCK_IN, "Color"_ustr);
|
||||
|
||||
if (shader_input && shader_input->link) {
|
||||
bke::node_remove_link(ntree, *shader_input->link);
|
||||
}
|
||||
|
||||
/* Set the background shader intensity. */
|
||||
float intensity = dome_light_data.intensity * params.light_intensity_scale;
|
||||
|
||||
bNodeSocket *strength_sock = bke::node_find_socket(*bgshader, SOCK_IN, "Strength"_ustr);
|
||||
strength_sock->default_value_typed<bNodeSocketValueFloat>()->value = intensity;
|
||||
|
||||
if (!dome_light_data.has_tex) {
|
||||
/* No texture file is authored on the dome light. Set the color, if it was authored,
|
||||
* and return early. */
|
||||
if (dome_light_data.has_color) {
|
||||
bNodeSocket *color_sock = bke::node_find_socket(*bgshader, SOCK_IN, "Color"_ustr);
|
||||
copy_v3_v3(color_sock->default_value_typed<bNodeSocketValueRGBA>()->value,
|
||||
dome_light_data.color.data());
|
||||
}
|
||||
|
||||
bke::node_set_active(*ntree, *output);
|
||||
BKE_ntree_update_after_single_tree_change(*bmain, *ntree);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the light has authored color, create a color multiply node for the environment
|
||||
* texture output. */
|
||||
bNode *mult = nullptr;
|
||||
|
||||
if (dome_light_data.has_color) {
|
||||
mult = append_node(bgshader, SH_NODE_VECTOR_MATH, "Vector", "Color", ntree, 200);
|
||||
mult->custom1 = NODE_VECTOR_MATH_MULTIPLY;
|
||||
|
||||
/* Set the color in the vector math node's second socket. */
|
||||
bNodeSocket *vec_sock = bke::node_find_socket(*mult, SOCK_IN, "Vector"_ustr);
|
||||
if (vec_sock) {
|
||||
vec_sock = vec_sock->next;
|
||||
}
|
||||
|
||||
if (vec_sock) {
|
||||
copy_v3_v3(vec_sock->default_value_typed<bNodeSocketValueVector>()->value,
|
||||
dome_light_data.color.data());
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG, "Couldn't find vector multiply second vector socket");
|
||||
}
|
||||
}
|
||||
|
||||
bNode *tex = nullptr;
|
||||
|
||||
/* Append an environment texture node to the mult node, if it was created, or directly to
|
||||
* the background shader. */
|
||||
if (mult) {
|
||||
tex = append_node(mult, SH_NODE_TEX_ENVIRONMENT, "Color", "Vector", ntree, 400);
|
||||
}
|
||||
else {
|
||||
tex = append_node(bgshader, SH_NODE_TEX_ENVIRONMENT, "Color", "Color", ntree, 400);
|
||||
}
|
||||
|
||||
bNode *mapping = append_node(tex, SH_NODE_MAPPING, "Vector", "Vector", ntree, 200);
|
||||
|
||||
append_node(mapping, SH_NODE_TEX_COORD, "Generated", "Vector", ntree, 200);
|
||||
|
||||
/* Load the texture image. */
|
||||
const std::string &resolved_path = dome_light_data.tex_path.GetResolvedPath();
|
||||
if (resolved_path.empty()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't get resolved path for asset %s",
|
||||
dome_light_data.tex_path.GetAssetPath().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Image *image = load_image(resolved_path, bmain, params);
|
||||
if (!image) {
|
||||
CLOG_WARN(&LOG, "Couldn't load image file %s", resolved_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
tex->id = &image->id;
|
||||
|
||||
/* Set the transform. */
|
||||
pxr::UsdGeomXformCache xf_cache(time);
|
||||
pxr::GfMatrix4d xf = xf_cache.GetLocalToWorldTransform(prim);
|
||||
|
||||
pxr::UsdStageRefPtr stage = prim.GetStage();
|
||||
|
||||
if (!stage) {
|
||||
CLOG_WARN(&LOG, "Couldn't get stage for dome light %s", prim.GetPath().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note: This logic tries to produce identical results to `usdview` as of USD 25.05.
|
||||
* However, `usdview` seems to handle Y-Up stages differently; some scenes match while others
|
||||
* do not unless we keep the second conditional below (+90 on x-axis). */
|
||||
const pxr::TfToken stage_up = pxr::UsdGeomGetStageUpAxis(stage);
|
||||
const bool needs_stage_z_adjust = stage_up == pxr::UsdGeomTokens->z &&
|
||||
ELEM(dome_light_data.pole_axis,
|
||||
pxr::UsdLuxTokens->Z,
|
||||
pxr::UsdLuxTokens->scene);
|
||||
const bool needs_stage_y_adjust = stage_up == pxr::UsdGeomTokens->y &&
|
||||
ELEM(dome_light_data.pole_axis, pxr::UsdLuxTokens->Z);
|
||||
if (needs_stage_z_adjust || needs_stage_y_adjust) {
|
||||
xf *= pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 1.0, 0.0), 90.0));
|
||||
}
|
||||
else if (stage_up == pxr::UsdGeomTokens->y) {
|
||||
/* Convert from Y-up to Z-up with a 90 degree rotation about the X-axis. */
|
||||
xf *= pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0));
|
||||
}
|
||||
|
||||
/* Rotate into Blender's frame of reference. */
|
||||
xf = pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -90.0)) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -90.0)) * xf;
|
||||
|
||||
pxr::GfVec3d angles = xf.DecomposeRotation(
|
||||
pxr::GfVec3d::XAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::ZAxis());
|
||||
pxr::GfVec3f rot_vec(-angles[0], -angles[1], -angles[2]);
|
||||
|
||||
/* Convert degrees to radians. */
|
||||
rot_vec *= M_PI / 180.0f;
|
||||
|
||||
if (bNodeSocket *socket = bke::node_find_socket(*mapping, SOCK_IN, "Rotation"_ustr)) {
|
||||
bNodeSocketValueVector *rot_value = static_cast<bNodeSocketValueVector *>(
|
||||
socket->default_value);
|
||||
copy_v3_v3(rot_value->value, rot_vec.data());
|
||||
}
|
||||
|
||||
bke::node_set_active(*ntree, *output);
|
||||
DEG_id_tag_update(&ntree->id, ID_RECALC_NTREE_OUTPUT);
|
||||
BKE_ntree_update_after_single_tree_change(*bmain, *ntree);
|
||||
}
|
||||
|
||||
static bool node_search(bNode *fromnode, bNode * /*tonode*/, void *userdata, bool /*reversed*/)
|
||||
{
|
||||
if (!(userdata && fromnode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WorldToDomeLight &res = *static_cast<WorldToDomeLight *>(userdata);
|
||||
|
||||
if (!res.color_found && fromnode->type_legacy == SH_NODE_BACKGROUND) {
|
||||
/* Get light color and intensity */
|
||||
const bNodeSocketValueRGBA *color_data = bke::node_find_socket(
|
||||
*fromnode, SOCK_IN, "Color"_ustr)
|
||||
->default_value_typed<bNodeSocketValueRGBA>();
|
||||
const bNodeSocketValueFloat *strength_data =
|
||||
bke::node_find_socket(*fromnode, SOCK_IN, "Strength"_ustr)
|
||||
->default_value_typed<bNodeSocketValueFloat>();
|
||||
|
||||
res.color_found = true;
|
||||
res.intensity = strength_data->value;
|
||||
res.color[0] = color_data->value[0];
|
||||
res.color[1] = color_data->value[1];
|
||||
res.color[2] = color_data->value[2];
|
||||
res.color[3] = 1.0f;
|
||||
}
|
||||
else if (!res.image && fromnode->type_legacy == SH_NODE_TEX_ENVIRONMENT) {
|
||||
NodeTexImage *tex = static_cast<NodeTexImage *>(fromnode->storage);
|
||||
res.image = reinterpret_cast<Image *>(fromnode->id);
|
||||
res.iuser = &tex->iuser;
|
||||
|
||||
/* Always adjust for rotational differences between Blender and USD. */
|
||||
res.transform =
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0));
|
||||
}
|
||||
else if (!res.image && !res.mult_found && fromnode->type_legacy == SH_NODE_VECTOR_MATH) {
|
||||
if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) {
|
||||
res.mult_found = true;
|
||||
|
||||
bNodeSocket *vec_sock = bke::node_find_socket(*fromnode, SOCK_IN, "Vector"_ustr);
|
||||
if (vec_sock) {
|
||||
vec_sock = vec_sock->next;
|
||||
}
|
||||
|
||||
if (vec_sock) {
|
||||
copy_v3_v3(res.color_mult, vec_sock->default_value_typed<bNodeSocketValueVector>()->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (res.image && fromnode->type_legacy == SH_NODE_MAPPING) {
|
||||
if (bNodeSocket *socket = bke::node_find_socket(*fromnode, SOCK_IN, "Rotation"_ustr)) {
|
||||
const bNodeSocketValueVector *rot_value =
|
||||
socket->default_value_typed<bNodeSocketValueVector>();
|
||||
/* Convert radians to degrees. */
|
||||
pxr::GfVec3f rot(rot_value->value);
|
||||
rot *= 180.0f / M_PI;
|
||||
|
||||
res.transform *=
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -rot[2])) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 1.0, 0.0), -rot[1])) *
|
||||
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -rot[0]));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void world_material_to_dome_light(const Scene *scene, WorldToDomeLight &res)
|
||||
{
|
||||
/* Find the world output. */
|
||||
scene->world->nodetree->ensure_topology_cache();
|
||||
const Span<const bNode *> bsdf_nodes = scene->world->nodetree->nodes_by_type(
|
||||
"ShaderNodeOutputWorld"_ustr);
|
||||
|
||||
for (const bNode *node : bsdf_nodes) {
|
||||
if (node->flag & NODE_DO_OUTPUT) {
|
||||
bke::node_chain_iterator(scene->world->nodetree, node, node_search, &res, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNode;
|
||||
struct bNodeTree;
|
||||
|
||||
struct Main;
|
||||
struct Scene;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct USDExportParams;
|
||||
struct USDImportParams;
|
||||
|
||||
/* This struct contains all DomeLight attribute needed to
|
||||
* create a world environment */
|
||||
struct USDImportDomeLightData {
|
||||
float intensity;
|
||||
pxr::GfVec3f color;
|
||||
pxr::SdfAssetPath tex_path;
|
||||
pxr::TfToken pole_axis;
|
||||
|
||||
bool has_color;
|
||||
bool has_tex;
|
||||
};
|
||||
|
||||
/**
|
||||
* If the Blender scene has an environment texture,
|
||||
* export it as a USD dome light.
|
||||
*/
|
||||
void world_material_to_dome_light(const USDExportParams ¶ms,
|
||||
const Scene *scene,
|
||||
pxr::UsdStageRefPtr stage);
|
||||
|
||||
void dome_light_to_world_material(const USDImportParams ¶ms,
|
||||
Scene *scene,
|
||||
Main *bmain,
|
||||
const USDImportDomeLightData &dome_light_data,
|
||||
const pxr::UsdPrim &prim,
|
||||
const pxr::UsdTimeCode time = 0.0);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
150
blender-5.2.0/source/blender/io/usd/intern/usd_mesh_utils.cc
Normal file
150
blender-5.2.0/source/blender/io/usd/intern/usd_mesh_utils.cc
Normal file
@@ -0,0 +1,150 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_mesh_utils.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
template<typename USDT>
|
||||
static void read_face_display_color(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::TfToken &pv_name,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const pxr::VtArray<USDT> usd_colors = get_primvar_array<USDT>(primvar, time);
|
||||
if (usd_colors.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
const bke::AttrDomain color_domain = bke::AttrDomain::Corner;
|
||||
|
||||
const StringRef attr_name(pv_name.GetString());
|
||||
|
||||
if (primvar.GetInterpolation() == pxr::UsdGeomTokens->constant) {
|
||||
ColorGeometry4f value = detail::convert_value<USDT, ColorGeometry4f>(usd_colors[0]);
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), value);
|
||||
set_single_value(attributes,
|
||||
attr_name,
|
||||
color_domain,
|
||||
bke::AttrType::ColorFloat,
|
||||
bke::AttributeInitValue(value));
|
||||
return;
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<ColorGeometry4f> color_data =
|
||||
attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(attr_name, color_domain);
|
||||
if (!color_data) {
|
||||
CLOG_WARN(&LOG, "Primvar '%s' could not be added to Blender", primvar.GetBaseName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
for (const int i : faces.index_range()) {
|
||||
if (i >= usd_colors.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Take the per-face USD color and place it on each face-corner. */
|
||||
const IndexRange face = faces[i];
|
||||
for (const int j : face.index_range()) {
|
||||
const int corner = face.start() + j;
|
||||
color_data.span[corner] = detail::convert_value<USDT, ColorGeometry4f>(usd_colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), color_data.span);
|
||||
color_data.finish();
|
||||
}
|
||||
|
||||
static std::optional<bke::AttrDomain> convert_usd_varying_to_blender(const pxr::TfToken usd_domain)
|
||||
{
|
||||
static const Map<pxr::TfToken, bke::AttrDomain> domain_map = []() {
|
||||
Map<pxr::TfToken, bke::AttrDomain> map;
|
||||
map.add_new(pxr::UsdGeomTokens->faceVarying, bke::AttrDomain::Corner);
|
||||
map.add_new(pxr::UsdGeomTokens->vertex, bke::AttrDomain::Point);
|
||||
map.add_new(pxr::UsdGeomTokens->varying, bke::AttrDomain::Point);
|
||||
map.add_new(pxr::UsdGeomTokens->face, bke::AttrDomain::Face);
|
||||
/* Since there's no "domain" concept in USD, promote prim-level "constant" primvars to the
|
||||
* Point domain in Blender. */
|
||||
map.add_new(pxr::UsdGeomTokens->constant, bke::AttrDomain::Point);
|
||||
map.add_new(pxr::UsdGeomTokens->uniform, bke::AttrDomain::Face);
|
||||
/* Notice: Edge types are not supported! */
|
||||
return map;
|
||||
}();
|
||||
|
||||
const bke::AttrDomain *value = domain_map.lookup_ptr(usd_domain);
|
||||
|
||||
if (value == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
void read_generic_mesh_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::UsdTimeCode time,
|
||||
const bool is_left_handed)
|
||||
{
|
||||
const pxr::SdfValueTypeName pv_type = primvar.GetTypeName();
|
||||
const pxr::TfToken pv_interp = primvar.GetInterpolation();
|
||||
const pxr::TfToken pv_name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName());
|
||||
|
||||
const std::optional<bke::AttrDomain> domain = convert_usd_varying_to_blender(pv_interp);
|
||||
const std::optional<bke::AttrType> type = convert_usd_type_to_blender(pv_type);
|
||||
|
||||
if (!domain.has_value() || !type.has_value()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Primvar '%s' (interpolation %s, type %s) cannot be converted to Blender",
|
||||
pv_name.GetText(),
|
||||
pv_interp.GetText(),
|
||||
pv_type.GetAsToken().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Blender does not currently support displaying Face colors with the Viewport Shading
|
||||
* "Attribute" color type. Make a special case for "displayColor" primvars and put them on
|
||||
* the Corner domain instead. */
|
||||
if (pv_name == usdtokens::displayColor &&
|
||||
ELEM(pv_interp, pxr::UsdGeomTokens->uniform, pxr::UsdGeomTokens->constant))
|
||||
{
|
||||
if (ELEM(pv_type,
|
||||
pxr::SdfValueTypeNames->Color3fArray,
|
||||
pxr::SdfValueTypeNames->Color3hArray,
|
||||
pxr::SdfValueTypeNames->Color3dArray))
|
||||
{
|
||||
read_face_display_color<pxr::GfVec3f>(mesh, primvar, pv_name, time);
|
||||
}
|
||||
else {
|
||||
read_face_display_color<pxr::GfVec4f>(mesh, primvar, pv_name, time);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
OffsetIndices<int> faces;
|
||||
if (is_left_handed) {
|
||||
faces = mesh->faces();
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
copy_primvar_to_blender_attribute(primvar, time, *type, *domain, faces, attributes);
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
19
blender-5.2.0/source/blender/io/usd/intern/usd_mesh_utils.hh
Normal file
19
blender-5.2.0/source/blender/io/usd/intern/usd_mesh_utils.hh
Normal file
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
void read_generic_mesh_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
pxr::UsdTimeCode time,
|
||||
bool is_left_handed);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
13
blender-5.2.0/source/blender/io/usd/intern/usd_precomp.hh
Normal file
13
blender-5.2.0/source/blender/io/usd/intern/usd_precomp.hh
Normal file
@@ -0,0 +1,13 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/usd/object.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/primData.h>
|
||||
#include <pxr/usd/usdGeom/xformable.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
|
||||
#include <pxr/imaging/hd/sceneDelegate.h>
|
||||
#include <pxr/imaging/hd/tokens.h>
|
||||
349
blender-5.2.0/source/blender/io/usd/intern/usd_reader_camera.cc
Normal file
349
blender-5.2.0/source/blender/io/usd/intern/usd_reader_camera.cc
Normal file
@@ -0,0 +1,349 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#include "usd_reader_camera.hh"
|
||||
#include "usd_armature_utils.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_animdata.hh"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BKE_camera.h"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/usd/usdGeom/camera.h>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T> struct SampleData {
|
||||
float frame;
|
||||
T value;
|
||||
};
|
||||
|
||||
template<typename T> struct AttributeData {
|
||||
std::optional<T> initial_value = std::nullopt;
|
||||
Vector<SampleData<T>> samples;
|
||||
|
||||
void reset()
|
||||
{
|
||||
initial_value = std::nullopt;
|
||||
samples.clear();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
bool read_attribute_values(const pxr::UsdAttribute &attr,
|
||||
const pxr::UsdTimeCode initial_time,
|
||||
AttributeData<T> &data)
|
||||
{
|
||||
data.reset(); /* Clear any prior data. */
|
||||
|
||||
T value{};
|
||||
if (attr.Get(&value, initial_time)) {
|
||||
data.initial_value = value;
|
||||
}
|
||||
else {
|
||||
data.initial_value = std::nullopt;
|
||||
}
|
||||
|
||||
if (attr.ValueMightBeTimeVarying()) {
|
||||
std::vector<double> times;
|
||||
attr.GetTimeSamples(×);
|
||||
|
||||
data.samples.resize(times.size());
|
||||
for (int64_t i = 0; i < times.size(); i++) {
|
||||
data.samples[i].frame = float(times[i]);
|
||||
attr.Get(&data.samples[i].value, times[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return data.initial_value.has_value() || !data.samples.is_empty();
|
||||
}
|
||||
|
||||
void read_aperture_data(Camera *camera,
|
||||
const pxr::UsdAttribute &usd_horiz_aperture,
|
||||
const pxr::UsdAttribute &usd_vert_aperture,
|
||||
const pxr::UsdAttribute &usd_horiz_offset,
|
||||
const pxr::UsdAttribute &usd_vert_offset,
|
||||
const pxr::UsdTimeCode initial_time,
|
||||
const double tenth_unit_to_millimeters,
|
||||
animrig::Channelbag &channelbag)
|
||||
{
|
||||
/* If the Aperture values are changing, that effects the sensor_fit and shift_x|y values as
|
||||
* well. We need to put animation data on all of them. */
|
||||
if (usd_horiz_aperture.ValueMightBeTimeVarying() || usd_vert_aperture.ValueMightBeTimeVarying())
|
||||
{
|
||||
std::vector<double> times;
|
||||
pxr::UsdAttribute::GetUnionedTimeSamples(
|
||||
{usd_horiz_aperture, usd_vert_aperture, usd_horiz_offset, usd_vert_offset}, ×);
|
||||
|
||||
std::array<FCurve *, 5> curves = {
|
||||
create_fcurve(channelbag, {"sensor_width", 0}, times.size()),
|
||||
create_fcurve(channelbag, {"sensor_height", 0}, times.size()),
|
||||
create_fcurve(channelbag, {"sensor_fit", 0}, times.size()),
|
||||
create_fcurve(channelbag, {"shift_x", 0}, times.size()),
|
||||
create_fcurve(channelbag, {"shift_y", 0}, times.size())};
|
||||
|
||||
for (int64_t i = 0; i < times.size(); i++) {
|
||||
const double time = times[i];
|
||||
|
||||
float horiz_aperture, vert_aperture;
|
||||
float shift_x, shift_y;
|
||||
usd_horiz_aperture.Get(&horiz_aperture, time);
|
||||
usd_vert_aperture.Get(&vert_aperture, time);
|
||||
usd_horiz_offset.Get(&shift_x, time);
|
||||
usd_vert_offset.Get(&shift_y, time);
|
||||
|
||||
const float sensor_x = horiz_aperture * tenth_unit_to_millimeters;
|
||||
const float sensor_y = vert_aperture * tenth_unit_to_millimeters;
|
||||
const char sensor_fit = horiz_aperture >= vert_aperture ? CAMERA_SENSOR_FIT_HOR :
|
||||
CAMERA_SENSOR_FIT_VERT;
|
||||
|
||||
const float sensor_size = sensor_x >= sensor_y ? sensor_x : sensor_y;
|
||||
shift_x = (shift_x * tenth_unit_to_millimeters) / sensor_size;
|
||||
shift_y = (shift_y * tenth_unit_to_millimeters) / sensor_size;
|
||||
|
||||
set_fcurve_sample(curves[0], i, float(time), sensor_x);
|
||||
set_fcurve_sample(curves[1], i, float(time), sensor_y);
|
||||
set_fcurve_sample(curves[2], i, float(time), sensor_fit);
|
||||
set_fcurve_sample(curves[3], i, float(time), shift_x);
|
||||
set_fcurve_sample(curves[4], i, float(time), shift_y);
|
||||
}
|
||||
}
|
||||
else if (usd_horiz_offset.ValueMightBeTimeVarying() || usd_vert_offset.ValueMightBeTimeVarying())
|
||||
{
|
||||
/* Only the shift_x|y values are changing. Load in the initial values for aperture and
|
||||
* sensor_fit and use those when setting the shift_x|y curves. */
|
||||
float horiz_aperture, vert_aperture;
|
||||
usd_horiz_aperture.Get(&horiz_aperture, initial_time);
|
||||
usd_vert_aperture.Get(&vert_aperture, initial_time);
|
||||
|
||||
camera->sensor_x = horiz_aperture * tenth_unit_to_millimeters;
|
||||
camera->sensor_y = vert_aperture * tenth_unit_to_millimeters;
|
||||
camera->sensor_fit = camera->sensor_x >= camera->sensor_y ? CAMERA_SENSOR_FIT_HOR :
|
||||
CAMERA_SENSOR_FIT_VERT;
|
||||
const float sensor_size = camera->sensor_x >= camera->sensor_y ? camera->sensor_x :
|
||||
camera->sensor_y;
|
||||
|
||||
std::vector<double> times;
|
||||
if (usd_horiz_offset.GetTimeSamples(×)) {
|
||||
FCurve *fcu = create_fcurve(channelbag, {"shift_x", 0}, times.size());
|
||||
for (int64_t i = 0; i < times.size(); i++) {
|
||||
const double time = times[i];
|
||||
float shift;
|
||||
usd_horiz_offset.Get(&shift, time);
|
||||
|
||||
shift = (shift * tenth_unit_to_millimeters) / sensor_size;
|
||||
set_fcurve_sample(fcu, i, float(time), shift);
|
||||
}
|
||||
}
|
||||
|
||||
if (usd_vert_offset.GetTimeSamples(×)) {
|
||||
FCurve *fcu = create_fcurve(channelbag, {"shift_y", 0}, times.size());
|
||||
for (int64_t i = 0; i < times.size(); i++) {
|
||||
const double time = times[i];
|
||||
float shift;
|
||||
usd_vert_offset.Get(&shift, time);
|
||||
|
||||
shift = (shift * tenth_unit_to_millimeters) / sensor_size;
|
||||
set_fcurve_sample(fcu, i, float(time), shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* No animation data. */
|
||||
float horiz_aperture, vert_aperture;
|
||||
float shift_x, shift_y;
|
||||
usd_horiz_aperture.Get(&horiz_aperture, initial_time);
|
||||
usd_vert_aperture.Get(&vert_aperture, initial_time);
|
||||
usd_horiz_offset.Get(&shift_x, initial_time);
|
||||
usd_vert_offset.Get(&shift_y, initial_time);
|
||||
|
||||
camera->sensor_x = horiz_aperture * tenth_unit_to_millimeters;
|
||||
camera->sensor_y = vert_aperture * tenth_unit_to_millimeters;
|
||||
camera->sensor_fit = camera->sensor_x >= camera->sensor_y ? CAMERA_SENSOR_FIT_HOR :
|
||||
CAMERA_SENSOR_FIT_VERT;
|
||||
const float sensor_size = camera->sensor_x >= camera->sensor_y ? camera->sensor_x :
|
||||
camera->sensor_y;
|
||||
camera->shiftx = (shift_x * tenth_unit_to_millimeters) / sensor_size;
|
||||
camera->shifty = (shift_y * tenth_unit_to_millimeters) / sensor_size;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void USDCameraReader::create_object(Main *bmain)
|
||||
{
|
||||
Camera *camera = BKE_camera_add(bmain, name_.c_str());
|
||||
|
||||
object_ = BKE_object_add_only_object(bmain, OB_CAMERA, name_.c_str());
|
||||
object_->data = id_cast<ID *>(camera);
|
||||
}
|
||||
|
||||
void USDCameraReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::UsdAttribute usd_focal_length = cam_prim_.GetFocalLengthAttr();
|
||||
pxr::UsdAttribute usd_focus_dist = cam_prim_.GetFocusDistanceAttr();
|
||||
pxr::UsdAttribute usd_fstop = cam_prim_.GetFStopAttr();
|
||||
pxr::UsdAttribute usd_clipping_range = cam_prim_.GetClippingRangeAttr();
|
||||
pxr::UsdAttribute usd_horiz_aperture = cam_prim_.GetHorizontalApertureAttr();
|
||||
pxr::UsdAttribute usd_vert_aperture = cam_prim_.GetVerticalApertureAttr();
|
||||
pxr::UsdAttribute usd_horiz_offset = cam_prim_.GetHorizontalApertureOffsetAttr();
|
||||
pxr::UsdAttribute usd_vert_offset = cam_prim_.GetVerticalApertureOffsetAttr();
|
||||
|
||||
/* If any of the camera attributes are time varying, then prepare the animation data. */
|
||||
const bool is_time_varying = usd_focal_length.ValueMightBeTimeVarying() ||
|
||||
usd_focus_dist.ValueMightBeTimeVarying() ||
|
||||
usd_fstop.ValueMightBeTimeVarying() ||
|
||||
usd_clipping_range.ValueMightBeTimeVarying() ||
|
||||
usd_horiz_aperture.ValueMightBeTimeVarying() ||
|
||||
usd_vert_aperture.ValueMightBeTimeVarying() ||
|
||||
usd_horiz_offset.ValueMightBeTimeVarying() ||
|
||||
usd_vert_offset.ValueMightBeTimeVarying();
|
||||
|
||||
Camera *camera = id_cast<Camera *>(object_->data);
|
||||
|
||||
bAction *action = nullptr;
|
||||
if (is_time_varying) {
|
||||
action = animrig::id_action_ensure(bmain, &camera->id);
|
||||
}
|
||||
|
||||
animrig::Channelbag empty{};
|
||||
animrig::Channelbag &channelbag = is_time_varying ?
|
||||
animrig::action_channelbag_ensure(*action, camera->id) :
|
||||
empty;
|
||||
|
||||
/*
|
||||
* In USD, some camera properties are in tenths of a world unit.
|
||||
* https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits
|
||||
*
|
||||
* tenth_unit_to_meters = stage_meters_per_unit / 10
|
||||
* tenth_unit_to_millimeters = 1000 * tenth_unit_to_meters
|
||||
* = 100 * stage_meters_per_unit
|
||||
*/
|
||||
const double tenth_unit_to_millimeters = 100.0 * settings_->stage_meters_per_unit;
|
||||
auto scale_default = [](std::optional<float> input, double scale, float default_value) {
|
||||
return input.has_value() ? input.value() * scale : default_value;
|
||||
};
|
||||
|
||||
AttributeData<float> data;
|
||||
if (read_attribute_values(usd_focal_length, time, data)) {
|
||||
camera->lens = scale_default(data.initial_value, tenth_unit_to_millimeters, camera->lens);
|
||||
|
||||
if (!data.samples.is_empty()) {
|
||||
FCurve *fcu = create_fcurve(channelbag, {"lens", 0}, data.samples.size());
|
||||
for (int64_t i = 0; i < data.samples.size(); i++) {
|
||||
const SampleData<float> &sample = data.samples[i];
|
||||
set_fcurve_sample(fcu, i, sample.frame, sample.value * tenth_unit_to_millimeters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read_attribute_values(usd_focus_dist, time, data)) {
|
||||
camera->dof.focus_distance = scale_default(
|
||||
data.initial_value, this->settings_->scene_scale, camera->dof.focus_distance);
|
||||
|
||||
if (!data.samples.is_empty()) {
|
||||
FCurve *fcu = create_fcurve(channelbag, {"dof.focus_distance", 0}, data.samples.size());
|
||||
for (int64_t i = 0; i < data.samples.size(); i++) {
|
||||
const SampleData<float> &sample = data.samples[i];
|
||||
set_fcurve_sample(fcu, i, sample.frame, sample.value * this->settings_->scene_scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The FStop controls camera focusing and values of 0.0 should disable DOF.
|
||||
* https://openusd.org/release/api/class_usd_geom_camera.html#a335e1647b730a575e3c0565e91eb8d49
|
||||
*/
|
||||
if (read_attribute_values(usd_fstop, time, data)) {
|
||||
camera->dof.aperture_fstop = scale_default(data.initial_value, 1, camera->dof.aperture_fstop);
|
||||
camera->dof.flag |= data.initial_value.value_or(0.0f) != 0.0f ? CAM_DOF_ENABLED :
|
||||
eCamera_DOF_Flag{};
|
||||
|
||||
if (!data.samples.is_empty()) {
|
||||
FCurve *curve1 = create_fcurve(channelbag, {"dof.aperture_fstop", 0}, data.samples.size());
|
||||
FCurve *curve2 = create_fcurve(channelbag, {"dof.use_dof", 0}, data.samples.size());
|
||||
for (int64_t i = 0; i < data.samples.size(); i++) {
|
||||
const SampleData<float> &sample = data.samples[i];
|
||||
const bool use_dof = sample.value != 0.0f;
|
||||
set_fcurve_sample(curve1, i, sample.frame, sample.value);
|
||||
set_fcurve_sample(curve2, i, sample.frame, use_dof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AttributeData<pxr::GfVec2f> clip_data;
|
||||
if (read_attribute_values(usd_clipping_range, time, clip_data)) {
|
||||
auto clamp_clip = [this](pxr::GfVec2f value) {
|
||||
/* Clamp the value for clip-start, matching the range defined in RNA. */
|
||||
return pxr::GfVec2f(max_ff(1e-6f, value[0] * settings_->scene_scale),
|
||||
value[1] * settings_->scene_scale);
|
||||
};
|
||||
|
||||
pxr::GfVec2f clip_range = clip_data.initial_value.has_value() ?
|
||||
clamp_clip(clip_data.initial_value.value()) :
|
||||
pxr::GfVec2f(camera->clip_start, camera->clip_end);
|
||||
camera->clip_start = clip_range[0];
|
||||
camera->clip_end = clip_range[1];
|
||||
|
||||
if (!clip_data.samples.is_empty()) {
|
||||
std::array<FCurve *, 2> curves = {
|
||||
create_fcurve(channelbag, {"clip_start", 0}, clip_data.samples.size()),
|
||||
create_fcurve(channelbag, {"clip_end", 0}, clip_data.samples.size())};
|
||||
|
||||
for (int64_t i = 0; i < clip_data.samples.size(); i++) {
|
||||
const SampleData<pxr::GfVec2f> &sample = clip_data.samples[i];
|
||||
clip_range = clamp_clip(sample.value);
|
||||
set_fcurve_sample(curves[0], i, sample.frame, clip_range[0]);
|
||||
set_fcurve_sample(curves[1], i, sample.frame, clip_range[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Aperture data impacts sensor size, sensor fit, and shift values simultaneously. */
|
||||
read_aperture_data(camera,
|
||||
usd_horiz_aperture,
|
||||
usd_vert_aperture,
|
||||
usd_horiz_offset,
|
||||
usd_vert_offset,
|
||||
time,
|
||||
tenth_unit_to_millimeters,
|
||||
channelbag);
|
||||
|
||||
/* USD Orthographic cameras have very limited support. Support a basic, non-animated, translation
|
||||
* between USD and Blender. */
|
||||
pxr::TfToken projection;
|
||||
cam_prim_.GetProjectionAttr().Get(&projection, time);
|
||||
camera->type = (projection.GetString() == "perspective") ? CAM_PERSP : CAM_ORTHO;
|
||||
if (camera->type == CAM_ORTHO) {
|
||||
float horiz_aperture, vert_aperture;
|
||||
usd_horiz_aperture.Get(&horiz_aperture, time);
|
||||
usd_vert_aperture.Get(&vert_aperture, time);
|
||||
camera->ortho_scale = max_ff(vert_aperture, horiz_aperture);
|
||||
}
|
||||
|
||||
/* Recalculate any animation curve handles. */
|
||||
for (FCurve *fcu : channelbag.fcurves()) {
|
||||
if (fcu) {
|
||||
BKE_fcurve_handles_recalc(*fcu);
|
||||
}
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/camera.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDCameraReader : public USDXformReader {
|
||||
private:
|
||||
pxr::UsdGeomCamera cam_prim_;
|
||||
|
||||
public:
|
||||
USDCameraReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDXformReader(prim, import_params, settings), cam_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(cam_prim_);
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
414
blender-5.2.0/source/blender/io/usd/intern/usd_reader_curve.cc
Normal file
414
blender-5.2.0/source/blender/io/usd/intern/usd_reader_curve.cc
Normal file
@@ -0,0 +1,414 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Adapted from the Blender Alembic importer implementation. Copyright 2016 Kévin Dietrich.
|
||||
* Modifications Copyright 2021 Tangent Animation. All rights reserved. */
|
||||
|
||||
#include "usd_reader_curve.hh"
|
||||
#include "usd.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/base/vt/types.h>
|
||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
static inline float3 to_float3(pxr::GfVec3f vec3f)
|
||||
{
|
||||
return float3(vec3f.data());
|
||||
}
|
||||
|
||||
static inline int bezier_point_count(int usd_count, bool is_cyclic)
|
||||
{
|
||||
return is_cyclic ? (usd_count / 3) : ((usd_count / 3) + 1);
|
||||
}
|
||||
|
||||
static int point_count(int usdCount, CurveType curve_type, bool is_cyclic)
|
||||
{
|
||||
if (curve_type == CURVE_TYPE_BEZIER) {
|
||||
return bezier_point_count(usdCount, is_cyclic);
|
||||
}
|
||||
return usdCount;
|
||||
}
|
||||
|
||||
static Array<int> calc_curve_offsets(const pxr::VtIntArray &usdCounts,
|
||||
const CurveType curve_type,
|
||||
bool is_cyclic)
|
||||
{
|
||||
Array<int> offsets(usdCounts.size() + 1);
|
||||
threading::parallel_for(IndexRange(usdCounts.size()), 4096, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
offsets[i] = point_count(usdCounts[i], curve_type, is_cyclic);
|
||||
}
|
||||
});
|
||||
offset_indices::accumulate_counts_to_offsets(offsets);
|
||||
return offsets;
|
||||
}
|
||||
|
||||
static void add_bezier_control_point(int cp,
|
||||
int offset,
|
||||
MutableSpan<float3> positions,
|
||||
MutableSpan<float3> handles_left,
|
||||
MutableSpan<float3> handles_right,
|
||||
const Span<pxr::GfVec3f> usdPoints)
|
||||
{
|
||||
if (offset == 0) {
|
||||
positions[cp] = to_float3(usdPoints[offset]);
|
||||
handles_right[cp] = to_float3(usdPoints[offset + 1]);
|
||||
handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
|
||||
}
|
||||
else if (offset >= usdPoints.size() - 1) {
|
||||
positions[cp] = to_float3(usdPoints.last());
|
||||
handles_left[cp] = to_float3(usdPoints.last(1));
|
||||
handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
|
||||
}
|
||||
else {
|
||||
positions[cp] = to_float3(usdPoints[offset]);
|
||||
handles_left[cp] = to_float3(usdPoints[offset - 1]);
|
||||
handles_right[cp] = to_float3(usdPoints[offset + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the number of curves or the number of curve points in each curve differ. */
|
||||
static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span<int> usd_offsets)
|
||||
{
|
||||
if (curves.offsets() != usd_offsets) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static CurveType get_curve_type(pxr::TfToken type, pxr::TfToken basis)
|
||||
{
|
||||
if (type == pxr::UsdGeomTokens->cubic) {
|
||||
if (basis == pxr::UsdGeomTokens->bezier) {
|
||||
return CURVE_TYPE_BEZIER;
|
||||
}
|
||||
if (basis == pxr::UsdGeomTokens->bspline) {
|
||||
return CURVE_TYPE_NURBS;
|
||||
}
|
||||
if (basis == pxr::UsdGeomTokens->catmullRom) {
|
||||
return CURVE_TYPE_CATMULL_ROM;
|
||||
}
|
||||
}
|
||||
|
||||
return CURVE_TYPE_POLY;
|
||||
}
|
||||
|
||||
static std::optional<bke::AttrDomain> convert_usd_interp_to_blender(const pxr::TfToken usd_domain)
|
||||
{
|
||||
static const Map<pxr::TfToken, bke::AttrDomain> domain_map = []() {
|
||||
Map<pxr::TfToken, bke::AttrDomain> map;
|
||||
map.add_new(pxr::UsdGeomTokens->vertex, bke::AttrDomain::Point);
|
||||
map.add_new(pxr::UsdGeomTokens->varying, bke::AttrDomain::Point);
|
||||
map.add_new(pxr::UsdGeomTokens->constant, bke::AttrDomain::Curve);
|
||||
map.add_new(pxr::UsdGeomTokens->uniform, bke::AttrDomain::Curve);
|
||||
return map;
|
||||
}();
|
||||
|
||||
const bke::AttrDomain *value = domain_map.lookup_ptr(usd_domain);
|
||||
|
||||
if (value == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
void USDCurvesReader::create_object(Main *bmain)
|
||||
{
|
||||
Curves *curve = BKE_curves_add(bmain, name_.c_str());
|
||||
|
||||
object_ = BKE_object_add_only_object(bmain, OB_CURVES, name_.c_str());
|
||||
object_->data = id_cast<ID *>(curve);
|
||||
}
|
||||
|
||||
void USDCurvesReader::read_object_data(Main *bmain, pxr::UsdTimeCode time)
|
||||
{
|
||||
Curves *cu = id_cast<Curves *>(object_->data);
|
||||
this->read_curve_sample(cu, time);
|
||||
|
||||
if (this->is_animated()) {
|
||||
this->add_cache_modifier();
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
void USDCurvesReader::read_velocities(bke::CurvesGeometry &curves,
|
||||
const pxr::UsdGeomCurves &usd_curves,
|
||||
const pxr::UsdTimeCode time) const
|
||||
{
|
||||
pxr::VtVec3fArray velocities;
|
||||
usd_curves.GetVelocitiesAttr().Get(&velocities, time);
|
||||
|
||||
if (!velocities.empty()) {
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
bke::SpanAttributeWriter<float3> velocity =
|
||||
attributes.lookup_or_add_for_write_only_span<float3>("velocity", bke::AttrDomain::Point);
|
||||
|
||||
Span<pxr::GfVec3f> usd_data(velocities.cdata(), velocities.size());
|
||||
velocity.span.copy_from(usd_data.cast<float3>());
|
||||
velocity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
void USDCurvesReader::read_custom_data(bke::CurvesGeometry &curves,
|
||||
const pxr::UsdTimeCode time) const
|
||||
{
|
||||
pxr::UsdGeomPrimvarsAPI pv_api(prim_);
|
||||
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
for (const pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
const pxr::SdfValueTypeName pv_type = pv.GetTypeName();
|
||||
if (!pv_type.IsArray()) {
|
||||
continue; /* Skip non-array primvar attributes. */
|
||||
}
|
||||
|
||||
const pxr::TfToken pv_interp = pv.GetInterpolation();
|
||||
const std::optional<bke::AttrDomain> domain = convert_usd_interp_to_blender(pv_interp);
|
||||
const std::optional<bke::AttrType> type = convert_usd_type_to_blender(pv_type);
|
||||
|
||||
if (!domain.has_value() || !type.has_value()) {
|
||||
const pxr::TfToken pv_name = pxr::UsdGeomPrimvar::StripPrimvarsName(pv.GetPrimvarName());
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"Primvar '%s' (interpolation %s, type %s) cannot be converted to Blender",
|
||||
pv_name.GetText(),
|
||||
pv_interp.GetText(),
|
||||
pv_type.GetAsToken().GetText());
|
||||
continue;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
copy_primvar_to_blender_attribute(pv, time, *type, *domain, {}, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
void USDCurvesReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
const USDMeshReadParams params,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
if (!geometry_set.has_curves()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Curves *curves = geometry_set.get_curves_for_write();
|
||||
read_curve_sample(curves, params.motion_sample_time);
|
||||
}
|
||||
|
||||
bool USDBasisCurvesReader::is_animated() const
|
||||
{
|
||||
if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying() ||
|
||||
curve_prim_.GetWidthsAttr().ValueMightBeTimeVarying() ||
|
||||
curve_prim_.GetVelocitiesAttr().ValueMightBeTimeVarying())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI pv_api(curve_prim_);
|
||||
for (const pxr::UsdGeomPrimvar &pv : pv_api.GetPrimvarsWithValues()) {
|
||||
if (pv.ValueMightBeTimeVarying()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void USDBasisCurvesReader::read_curve_sample(Curves *curves_id, const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::VtIntArray usd_counts;
|
||||
pxr::VtVec3fArray usd_points;
|
||||
pxr::VtFloatArray usd_widths;
|
||||
pxr::TfToken basis;
|
||||
pxr::TfToken type;
|
||||
pxr::TfToken wrap;
|
||||
|
||||
curve_prim_.GetCurveVertexCountsAttr().Get(&usd_counts, time);
|
||||
curve_prim_.GetPointsAttr().Get(&usd_points, time);
|
||||
curve_prim_.GetWidthsAttr().Get(&usd_widths, time);
|
||||
curve_prim_.GetBasisAttr().Get(&basis, time);
|
||||
curve_prim_.GetTypeAttr().Get(&type, time);
|
||||
curve_prim_.GetWrapAttr().Get(&wrap, time);
|
||||
|
||||
const CurveType curve_type = get_curve_type(type, basis);
|
||||
const bool is_cyclic = wrap == pxr::UsdGeomTokens->periodic;
|
||||
const int curves_num = usd_counts.size();
|
||||
const Array<int> new_offsets = calc_curve_offsets(usd_counts, curve_type, is_cyclic);
|
||||
|
||||
// Check validity of curve counts
|
||||
const int min_points = (curve_type == CURVE_TYPE_BEZIER) ? 3 : 1;
|
||||
const bool all_valid = std::all_of(usd_counts.cbegin(),
|
||||
usd_counts.cend(),
|
||||
[min_points](int count) { return count >= min_points; });
|
||||
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
if (all_valid && curves_topology_changed(curves, new_offsets)) {
|
||||
curves.resize(new_offsets.last(), curves_num);
|
||||
}
|
||||
|
||||
// Early out if there are no curves to load.
|
||||
if (curves.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves.offsets_for_write().copy_from(new_offsets);
|
||||
curves.fill_curve_types(curve_type);
|
||||
|
||||
if (is_cyclic) {
|
||||
curves.attributes_for_write().add<bool>(
|
||||
"cyclic", bke::AttrDomain::Curve, bke::AttributeInitValue(true));
|
||||
}
|
||||
|
||||
if (curve_type == CURVE_TYPE_NURBS) {
|
||||
const int8_t curve_order = type == pxr::UsdGeomTokens->cubic ? 4 : 2;
|
||||
curves.attributes_for_write().add<int8_t>(
|
||||
"nurbs_order", bke::AttrDomain::Curve, bke::AttributeInitValue(curve_order));
|
||||
}
|
||||
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
Span<pxr::GfVec3f> points = Span(usd_points.cdata(), usd_points.size());
|
||||
Span<int> counts = Span(usd_counts.cdata(), usd_counts.size());
|
||||
|
||||
/* If there's no points defined, fill positions with default values and exit. */
|
||||
if (points.is_empty()) {
|
||||
positions.fill(float3(0.0f, 0.0f, 0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Bezier curves require care in filing out their left/right handles. */
|
||||
if (type == pxr::UsdGeomTokens->cubic && basis == pxr::UsdGeomTokens->bezier) {
|
||||
curves.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN);
|
||||
curves.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN);
|
||||
|
||||
MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
|
||||
MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
|
||||
|
||||
int usd_point_offset = 0;
|
||||
int point_offset = 0;
|
||||
for (const int i : curves.curves_range()) {
|
||||
const int usd_point_count = counts[i];
|
||||
const int point_count = bezier_point_count(usd_point_count, is_cyclic);
|
||||
|
||||
int cp_offset = 0;
|
||||
for (const int cp : IndexRange(point_count)) {
|
||||
add_bezier_control_point(cp,
|
||||
cp_offset,
|
||||
positions.slice(point_offset, point_count),
|
||||
handles_left.slice(point_offset, point_count),
|
||||
handles_right.slice(point_offset, point_count),
|
||||
points.slice_safe(usd_point_offset, usd_point_count));
|
||||
cp_offset += 3;
|
||||
}
|
||||
|
||||
point_offset += point_count;
|
||||
usd_point_offset += usd_point_count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
static_assert(sizeof(pxr::GfVec3f) == sizeof(float3));
|
||||
if (positions.size() != points.size()) {
|
||||
positions.fill(float3(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
const int copy_size = std::min(positions.size(), points.size());
|
||||
positions.slice(0, copy_size).copy_from(points.slice(0, copy_size).cast<float3>());
|
||||
}
|
||||
|
||||
if (!usd_widths.empty()) {
|
||||
Span<float> widths = Span(usd_widths.cdata(), usd_widths.size());
|
||||
|
||||
pxr::TfToken widths_interp = curve_prim_.GetWidthsInterpolation();
|
||||
if (widths_interp == pxr::UsdGeomTokens->constant || widths.size() == 1) {
|
||||
set_single_value(curves.attributes_for_write(),
|
||||
"radius",
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttrType::Float,
|
||||
bke::AttributeInitValue(widths[0] / 2.0f));
|
||||
}
|
||||
else {
|
||||
MutableSpan<float> radii = curves.radius_for_write();
|
||||
|
||||
const bool is_bezier_vertex_interp = (type == pxr::UsdGeomTokens->cubic &&
|
||||
basis == pxr::UsdGeomTokens->bezier &&
|
||||
widths_interp == pxr::UsdGeomTokens->vertex);
|
||||
const bool is_bspline_varying_interp = (type == pxr::UsdGeomTokens->cubic &&
|
||||
basis == pxr::UsdGeomTokens->bspline &&
|
||||
widths_interp == pxr::UsdGeomTokens->varying);
|
||||
const bool is_catmull_varying_interp = (type == pxr::UsdGeomTokens->cubic &&
|
||||
basis == pxr::UsdGeomTokens->catmullRom &&
|
||||
widths_interp == pxr::UsdGeomTokens->varying);
|
||||
if (is_bezier_vertex_interp) {
|
||||
/* Blender does not support bezier 'vertex' interpolation.
|
||||
* Assign the widths as-if it were 'varying' only. */
|
||||
int usd_point_offset = 0;
|
||||
int point_offset = 0;
|
||||
for (const int i : curves.curves_range()) {
|
||||
const int usd_point_count = counts[i];
|
||||
const int point_count = bezier_point_count(usd_point_count, is_cyclic);
|
||||
|
||||
int cp_offset = 0;
|
||||
for (const int cp : IndexRange(point_count)) {
|
||||
const int usd_index = std::min(usd_point_offset + cp_offset, int(widths.size()) - 1);
|
||||
radii[point_offset + cp] = widths[usd_index] / 2.0f;
|
||||
cp_offset += 3;
|
||||
}
|
||||
|
||||
point_offset += point_count;
|
||||
usd_point_offset += usd_point_count;
|
||||
}
|
||||
}
|
||||
else if (!is_cyclic && (is_bspline_varying_interp || is_catmull_varying_interp)) {
|
||||
/* Blender does not support general cubic 'varying' interpolation. Duplicate the first/last
|
||||
* radius values as a best-effort solution. */
|
||||
int radii_offset = 0;
|
||||
int width_offset = 0;
|
||||
for (const int i : curves.curves_range()) {
|
||||
const int radii_count = counts[i];
|
||||
const int width_count = std::max(2, counts[i] - 2);
|
||||
|
||||
Span<float> usd_curve_widths = widths.slice_safe(width_offset, width_count);
|
||||
MutableSpan<float> curve_radii = radii.slice_safe(radii_offset, radii_count);
|
||||
if (usd_curve_widths.size() != width_count || curve_radii.size() != radii_count) {
|
||||
/* Generally unsafe to continue loading data. */
|
||||
break;
|
||||
}
|
||||
|
||||
curve_radii.first() = usd_curve_widths.first() / 2.0f;
|
||||
curve_radii.last() = usd_curve_widths.last() / 2.0f;
|
||||
for (const int i : usd_curve_widths.index_range()) {
|
||||
curve_radii[i + 1] = usd_curve_widths[i] / 2.0f;
|
||||
}
|
||||
|
||||
radii_offset += radii_count;
|
||||
width_offset += width_count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int i_point : IndexRange(std::min(radii.size(), widths.size()))) {
|
||||
radii[i_point] = widths[i_point] / 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->read_velocities(curves, curve_prim_, time);
|
||||
this->read_custom_data(curves, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,75 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Adapted from the Blender Alembic importer implementation. Copyright 2016 Kévin Dietrich.
|
||||
* Modifications Copyright 2021 Tangent Animation. All rights reserved. */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||
#include <pxr/usd/usdGeom/curves.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Curves;
|
||||
struct Main;
|
||||
|
||||
namespace bke {
|
||||
struct GeometrySet;
|
||||
class CurvesGeometry;
|
||||
} // namespace bke
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDCurvesReader : public USDGeomReader {
|
||||
public:
|
||||
USDCurvesReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDGeomReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
void read_velocities(bke::CurvesGeometry &curves,
|
||||
const pxr::UsdGeomCurves &usd_curves,
|
||||
const pxr::UsdTimeCode time) const;
|
||||
void read_custom_data(bke::CurvesGeometry &curves, const pxr::UsdTimeCode time) const;
|
||||
|
||||
virtual bool is_animated() const = 0;
|
||||
virtual void read_curve_sample(Curves *curves_id, pxr::UsdTimeCode time) = 0;
|
||||
};
|
||||
|
||||
class USDBasisCurvesReader : public USDCurvesReader {
|
||||
private:
|
||||
pxr::UsdGeomBasisCurves curve_prim_;
|
||||
|
||||
public:
|
||||
USDBasisCurvesReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDCurvesReader(prim, import_params, settings), curve_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(curve_prim_);
|
||||
}
|
||||
|
||||
bool is_animated() const override;
|
||||
void read_curve_sample(Curves *curves_id, pxr::UsdTimeCode time) override;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,124 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_domelight.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_light_convert.hh"
|
||||
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdLux/domeLight_1.h>
|
||||
#include <pxr/usd/usdLux/tokens.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace usdtokens {
|
||||
// Attribute names.
|
||||
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken texture_file("texture:file", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken pole_axis("poleAxis", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/**
|
||||
* If the given attribute has an authored value, return its value in the r_value
|
||||
* out parameter.
|
||||
*
|
||||
* We wish to support older UsdLux APIs in older versions of USD. For example,
|
||||
* in previous versions of the API, shader input attributes did not have the
|
||||
* "inputs:" prefix. One can provide the older input attribute name in the
|
||||
* 'fallback_attr_name' argument, and that attribute will be queried if 'attr'
|
||||
* doesn't exist or doesn't have an authored value.
|
||||
*/
|
||||
template<typename T>
|
||||
static bool get_authored_value(const pxr::UsdAttribute &attr,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdPrim &prim,
|
||||
const pxr::TfToken fallback_attr_name,
|
||||
T *r_value)
|
||||
{
|
||||
if (attr && attr.HasAuthoredValue()) {
|
||||
return attr.Get<T>(r_value, time);
|
||||
}
|
||||
|
||||
if (!prim || fallback_attr_name.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute fallback_attr = prim.GetAttribute(fallback_attr_name);
|
||||
if (fallback_attr && fallback_attr.HasAuthoredValue()) {
|
||||
return fallback_attr.Get<T>(r_value, time);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> static float get_intensity(const T &dome_light, const pxr::UsdTimeCode time)
|
||||
{
|
||||
float intensity = 1.0f;
|
||||
get_authored_value(
|
||||
dome_light.GetIntensityAttr(), time, dome_light.GetPrim(), usdtokens::intensity, &intensity);
|
||||
return intensity;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool get_tex_path(const T &dome_light,
|
||||
const pxr::UsdTimeCode time,
|
||||
pxr::SdfAssetPath *tex_path)
|
||||
{
|
||||
bool has_tex = get_authored_value(dome_light.GetTextureFileAttr(),
|
||||
time,
|
||||
dome_light.GetPrim(),
|
||||
usdtokens::texture_file,
|
||||
tex_path);
|
||||
return has_tex;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool get_color(const T &dome_light, const pxr::UsdTimeCode time, pxr::GfVec3f *color)
|
||||
{
|
||||
bool has_color = get_authored_value(
|
||||
dome_light.GetColorAttr(), time, dome_light.GetPrim(), usdtokens::color, color);
|
||||
if (has_color) {
|
||||
colorspace_attr_to_scene_linear(dome_light.GetColorAttr(), *color);
|
||||
}
|
||||
return has_color;
|
||||
}
|
||||
|
||||
static pxr::TfToken get_pole_axis(const pxr::UsdLuxDomeLight_1 &dome_light,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::TfToken pole_axis = pxr::UsdLuxTokens->scene;
|
||||
get_authored_value(dome_light.GetPoleAxisAttr(), time, dome_light.GetPrim(), {}, &pole_axis);
|
||||
return pole_axis;
|
||||
}
|
||||
|
||||
void USDDomeLightReader::create_object(Scene *scene, Main *bmain)
|
||||
{
|
||||
USDImportDomeLightData dome_light_data;
|
||||
|
||||
/* Time varying dome lights are not currently supported. */
|
||||
constexpr pxr::UsdTimeCode time = 0.0;
|
||||
|
||||
if (prim_.IsA<pxr::UsdLuxDomeLight>()) {
|
||||
pxr::UsdLuxDomeLight dome_light = pxr::UsdLuxDomeLight(prim_);
|
||||
dome_light_data.intensity = get_intensity(dome_light, time);
|
||||
dome_light_data.has_tex = get_tex_path(dome_light, time, &dome_light_data.tex_path);
|
||||
dome_light_data.has_color = get_color(dome_light, time, &dome_light_data.color);
|
||||
dome_light_data.pole_axis = pxr::UsdLuxTokens->Y;
|
||||
}
|
||||
else if (prim_.IsA<pxr::UsdLuxDomeLight_1>()) {
|
||||
pxr::UsdLuxDomeLight_1 dome_light = pxr::UsdLuxDomeLight_1(prim_);
|
||||
dome_light_data.intensity = get_intensity(dome_light, time);
|
||||
dome_light_data.has_tex = get_tex_path(dome_light, time, &dome_light_data.tex_path);
|
||||
dome_light_data.has_color = get_color(dome_light, time, &dome_light_data.color);
|
||||
dome_light_data.pole_axis = get_pole_axis(dome_light, time);
|
||||
}
|
||||
|
||||
dome_light_to_world_material(import_params_, scene, bmain, dome_light_data, prim_);
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,42 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdLux/domeLight_1.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct Scene;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDDomeLightReader : public USDPrimReader {
|
||||
|
||||
public:
|
||||
USDDomeLightReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDPrimReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return prim_.IsA<pxr::UsdLuxDomeLight>() || prim_.IsA<pxr::UsdLuxDomeLight_1>();
|
||||
}
|
||||
|
||||
/* Until Blender supports DomeLight objects natively, use a separate create_object overload that
|
||||
* allows the caller to pass in the required Scene data. */
|
||||
|
||||
void create_object(Main * /*bmain*/) override {};
|
||||
void create_object(Scene *scene, Main *bmain);
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDGeomReader::add_cache_modifier()
|
||||
{
|
||||
if (!settings_->get_cache_file) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
|
||||
BLI_addtail(&object_->modifiers, md);
|
||||
BKE_modifiers_persistent_uid_init(*object_, *md);
|
||||
|
||||
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
|
||||
|
||||
mcmd->cache_file = settings_->get_cache_file();
|
||||
id_us_plus(&mcmd->cache_file->id);
|
||||
mcmd->read_flag = import_params_.mesh_read_flag;
|
||||
|
||||
STRNCPY(mcmd->object_path, prim_.GetPath().GetString().c_str());
|
||||
}
|
||||
|
||||
void USDGeomReader::add_subdiv_modifier()
|
||||
{
|
||||
ModifierData *md = BKE_modifier_new(eModifierType_Subsurf);
|
||||
BLI_addtail(&object_->modifiers, md);
|
||||
BKE_modifiers_persistent_uid_init(*object_, *md);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace bke {
|
||||
struct GeometrySet;
|
||||
}
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDGeomReader : public USDXformReader {
|
||||
|
||||
public:
|
||||
USDGeomReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDXformReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str) = 0;
|
||||
|
||||
virtual bool topology_changed(const Mesh * /*existing_mesh*/, pxr::UsdTimeCode /*time*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void add_cache_modifier();
|
||||
void add_subdiv_modifier();
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_instance.hh"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDInstanceReader::create_object(Main *bmain)
|
||||
{
|
||||
this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str());
|
||||
this->object_->data = nullptr;
|
||||
this->object_->instance_collection = nullptr;
|
||||
this->object_->transflag |= OB_DUPLICOLLECTION;
|
||||
}
|
||||
|
||||
void USDInstanceReader::set_instance_collection(Collection *coll)
|
||||
{
|
||||
if (this->object_ && this->object_->instance_collection != coll) {
|
||||
if (this->object_->instance_collection) {
|
||||
id_us_min(&this->object_->instance_collection->id);
|
||||
this->object_->instance_collection = nullptr;
|
||||
}
|
||||
id_us_plus(&coll->id);
|
||||
this->object_->instance_collection = coll;
|
||||
}
|
||||
}
|
||||
|
||||
pxr::SdfPath USDInstanceReader::proto_path() const
|
||||
{
|
||||
if (pxr::UsdPrim proto = prim_.GetPrototype()) {
|
||||
return proto.GetPath();
|
||||
}
|
||||
|
||||
return pxr::SdfPath();
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Collection;
|
||||
struct Main;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/**
|
||||
* Convert a USD instanced prim to a blender collection instance.
|
||||
*/
|
||||
class USDInstanceReader : public USDXformReader {
|
||||
|
||||
public:
|
||||
USDInstanceReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDXformReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return prim_.IsValid() && prim_.IsInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object that instances a collection.
|
||||
*/
|
||||
void create_object(Main *bmain) override;
|
||||
|
||||
/**
|
||||
* Assign the given collection to the object.
|
||||
*/
|
||||
void set_instance_collection(Collection *coll);
|
||||
|
||||
/**
|
||||
* Get the path of the USD prototype prim.
|
||||
*/
|
||||
pxr::SdfPath proto_path() const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
213
blender-5.2.0/source/blender/io/usd/intern/usd_reader_light.cc
Normal file
213
blender-5.2.0/source/blender/io/usd/intern/usd_reader_light.cc
Normal file
@@ -0,0 +1,213 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_light.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
|
||||
#include "BLI_math_rotation.h"
|
||||
|
||||
#include "BKE_light.h"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_light_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/usd/usdLux/diskLight.h>
|
||||
#include <pxr/usd/usdLux/distantLight.h>
|
||||
#include <pxr/usd/usdLux/rectLight.h>
|
||||
#include <pxr/usd/usdLux/shapingAPI.h>
|
||||
#include <pxr/usd/usdLux/sphereLight.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDLightReader::create_object(Main *bmain)
|
||||
{
|
||||
Light *blight = BKE_light_add(bmain, name_.c_str());
|
||||
|
||||
object_ = BKE_object_add_only_object(bmain, OB_LAMP, name_.c_str());
|
||||
object_->data = id_cast<ID *>(blight);
|
||||
}
|
||||
|
||||
void USDLightReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
||||
{
|
||||
Light *blight = id_cast<Light *>(object_->data);
|
||||
|
||||
if (blight == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdLuxLightAPI light_api(prim_);
|
||||
if (!light_api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdLuxDiskLight>()) {
|
||||
/* Disk area light. */
|
||||
blight->type = LA_AREA;
|
||||
blight->area_shape = LA_AREA_DISK;
|
||||
|
||||
pxr::UsdLuxDiskLight disk_light(prim_);
|
||||
if (disk_light) {
|
||||
if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) {
|
||||
float radius = 0.0f;
|
||||
if (radius_attr.Get(&radius, time)) {
|
||||
blight->area_size = radius * 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prim_.IsA<pxr::UsdLuxRectLight>()) {
|
||||
/* Rectangular area light. */
|
||||
blight->type = LA_AREA;
|
||||
blight->area_shape = LA_AREA_RECT;
|
||||
|
||||
pxr::UsdLuxRectLight rect_light(prim_);
|
||||
if (rect_light) {
|
||||
if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) {
|
||||
float width = 0.0f;
|
||||
if (width_attr.Get(&width, time)) {
|
||||
blight->area_size = width;
|
||||
}
|
||||
}
|
||||
|
||||
if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) {
|
||||
float height = 0.0f;
|
||||
if (height_attr.Get(&height, time)) {
|
||||
blight->area_sizey = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
|
||||
/* Point and spot light. */
|
||||
blight->type = LA_LOCAL;
|
||||
|
||||
pxr::UsdLuxSphereLight sphere_light(prim_);
|
||||
if (sphere_light) {
|
||||
pxr::UsdAttribute treatAsPoint_attr = sphere_light.GetTreatAsPointAttr();
|
||||
bool treatAsPoint;
|
||||
if (treatAsPoint_attr && treatAsPoint_attr.Get(&treatAsPoint, time) && treatAsPoint) {
|
||||
blight->radius = 0.0f;
|
||||
}
|
||||
else if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) {
|
||||
float radius = 0.0f;
|
||||
if (radius_attr.Get(&radius, time)) {
|
||||
blight->radius = radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxr::UsdLuxShapingAPI shaping_api = pxr::UsdLuxShapingAPI(prim_);
|
||||
if (shaping_api && shaping_api.GetShapingConeAngleAttr().IsAuthored()) {
|
||||
blight->type = LA_SPOT;
|
||||
|
||||
if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) {
|
||||
float cone_angle = 0.0f;
|
||||
if (cone_angle_attr.Get(&cone_angle, time)) {
|
||||
blight->spotsize = DEG2RADF(cone_angle) * 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (pxr::UsdAttribute cone_softness_attr = shaping_api.GetShapingConeSoftnessAttr()) {
|
||||
float cone_softness = 0.0f;
|
||||
if (cone_softness_attr.Get(&cone_softness, time)) {
|
||||
blight->spotblend = cone_softness;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prim_.IsA<pxr::UsdLuxDistantLight>()) {
|
||||
blight->type = LA_SUN;
|
||||
|
||||
pxr::UsdLuxDistantLight distant_light(prim_);
|
||||
if (distant_light) {
|
||||
if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) {
|
||||
float angle = 0.0f;
|
||||
if (angle_attr.Get(&angle, time)) {
|
||||
blight->sun_angle = DEG2RADF(angle * 2.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Intensity */
|
||||
if (pxr::UsdAttribute intensity_attr = light_api.GetIntensityAttr()) {
|
||||
float intensity = 0.0f;
|
||||
if (intensity_attr.Get(&intensity, time)) {
|
||||
if (blight->type == LA_SUN) {
|
||||
/* Unclear why, but approximately matches Karma. */
|
||||
blight->energy = intensity * 4.0f;
|
||||
}
|
||||
else {
|
||||
/* Convert from intensity to radiant flux. */
|
||||
blight->energy = intensity * M_PI;
|
||||
}
|
||||
blight->energy *= this->import_params_.light_intensity_scale;
|
||||
}
|
||||
}
|
||||
|
||||
/* Exposure. */
|
||||
if (pxr::UsdAttribute exposure_attr = light_api.GetExposureAttr()) {
|
||||
float exposure = 0.0f;
|
||||
if (exposure_attr.Get(&exposure, time)) {
|
||||
blight->exposure = exposure;
|
||||
}
|
||||
}
|
||||
|
||||
/* Color. */
|
||||
if (pxr::UsdAttribute color_attr = light_api.GetColorAttr()) {
|
||||
pxr::GfVec3f color;
|
||||
if (color_attr.Get(&color, time)) {
|
||||
colorspace_attr_to_scene_linear(color_attr, color);
|
||||
blight->r = color[0];
|
||||
blight->g = color[1];
|
||||
blight->b = color[2];
|
||||
}
|
||||
}
|
||||
|
||||
/* Temperature */
|
||||
if (pxr::UsdAttribute enable_temperature_attr = light_api.GetEnableColorTemperatureAttr()) {
|
||||
bool enable_temperature = false;
|
||||
if (enable_temperature_attr.Get(&enable_temperature, time)) {
|
||||
if (enable_temperature) {
|
||||
blight->mode |= LA_USE_TEMPERATURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pxr::UsdAttribute color_temperature_attr = light_api.GetColorTemperatureAttr()) {
|
||||
float color_temperature = 6500.0f;
|
||||
if (color_temperature_attr.Get(&color_temperature, time)) {
|
||||
blight->temperature = color_temperature;
|
||||
}
|
||||
}
|
||||
|
||||
/* Diffuse and Specular. */
|
||||
if (pxr::UsdAttribute diff_attr = light_api.GetDiffuseAttr()) {
|
||||
float diff_fac = 1.0f;
|
||||
if (diff_attr.Get(&diff_fac, time)) {
|
||||
blight->diff_fac = diff_fac;
|
||||
}
|
||||
}
|
||||
if (pxr::UsdAttribute spec_attr = light_api.GetSpecularAttr()) {
|
||||
float spec_fac = 1.0f;
|
||||
if (spec_attr.Get(&spec_fac, time)) {
|
||||
blight->spec_fac = spec_fac;
|
||||
}
|
||||
}
|
||||
|
||||
/* Normalize */
|
||||
if (pxr::UsdAttribute normalize_attr = light_api.GetNormalizeAttr()) {
|
||||
bool normalize = false;
|
||||
if (normalize_attr.Get(&normalize, time)) {
|
||||
if (!normalize) {
|
||||
blight->mode |= LA_UNNORMALIZED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDLightReader : public USDXformReader {
|
||||
|
||||
public:
|
||||
USDLightReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDXformReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
1566
blender-5.2.0/source/blender/io/usd/intern/usd_reader_material.cc
Normal file
1566
blender-5.2.0/source/blender/io/usd/intern/usd_reader_material.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
/* SPDX-FileCopyrightText: 2021 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct Material;
|
||||
struct bNode;
|
||||
struct bNodeTree;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
using ShaderToNodeMap = Map<std::string, bNode *>;
|
||||
|
||||
/* Helper struct used when arranging nodes in columns, keeping track the
|
||||
* occupancy information for a given column. I.e., for column n,
|
||||
* column_offsets[n] is the y-offset (from top to bottom) of the occupied
|
||||
* region in that column. */
|
||||
struct NodePlacementContext {
|
||||
const float origx_;
|
||||
const float origy_;
|
||||
const float horizontal_step_;
|
||||
const float vertical_step_;
|
||||
Vector<float, 8> column_offsets_ = Vector<float, 8>(8, 0.0f);
|
||||
|
||||
/* Map a USD shader prim path to the Blender node converted
|
||||
* from that shader. This map is updated during shader
|
||||
* conversion and is used to avoid creating duplicate nodes
|
||||
* for a given shader. */
|
||||
ShaderToNodeMap node_cache_;
|
||||
|
||||
NodePlacementContext(float origx,
|
||||
float origy,
|
||||
float horizontal_step = 300.0f,
|
||||
float vertical_step = 300.0f)
|
||||
: origx_(origx),
|
||||
origy_(origy),
|
||||
horizontal_step_(horizontal_step),
|
||||
vertical_step_(vertical_step)
|
||||
{
|
||||
}
|
||||
|
||||
/* Compute the x- and y-coordinates for placing a new node in an unoccupied region of
|
||||
* the column with the given index. */
|
||||
float2 compute_node_loc(int column);
|
||||
|
||||
/**
|
||||
* Generate a key for caching a Blender node created for a given USD shader by returning the
|
||||
* shader prim path with an optional tag suffix. The tag can be specified in order to generate a
|
||||
* unique key when more than one Blender node is created for the USD shader. */
|
||||
std::string get_key(const pxr::UsdShadeShader &usd_shader, const StringRef tag) const;
|
||||
|
||||
/* Returns the Blender node previously cached for the given USD shader. Returns null if no cached
|
||||
* shader was found. */
|
||||
bNode *get_cached_node(const pxr::UsdShadeShader &usd_shader, const StringRef tag = {}) const;
|
||||
|
||||
/* Cache the Blender node translated from the given USD shader. */
|
||||
void cache_node(const pxr::UsdShadeShader &usd_shader, bNode *node, const StringRef tag = {});
|
||||
};
|
||||
|
||||
/* Helper struct which carries an assortment of optional
|
||||
* information that is sometimes required when linking
|
||||
* nodes together. */
|
||||
struct ExtraLinkInfo {
|
||||
bool is_color_corrected = false;
|
||||
|
||||
/* Is the value inverted with respect to Blender.
|
||||
* For example: Opacity 0.85 <-> Transmission Weight 0.15 */
|
||||
bool is_inverted = false;
|
||||
|
||||
float opacity_threshold = 0.0f;
|
||||
};
|
||||
|
||||
/* Converts USD materials to Blender representation. */
|
||||
|
||||
/**
|
||||
* By default, the #USDMaterialReader creates a Blender material with
|
||||
* the same name as the USD material. If the USD material has a
|
||||
* #UsdPreviewSurface source, the Blender material's viewport display
|
||||
* color, roughness and metallic properties are set to the corresponding
|
||||
* #UsdPreoviewSurface inputs.
|
||||
*
|
||||
* If the Import USD Preview option is enabled, the current implementation
|
||||
* converts #UsdPreviewSurface to Blender nodes as follows:
|
||||
*
|
||||
* - #UsdPreviewSurface -> Principled BSDF
|
||||
* - #UsdUVTexture -> Texture Image + Normal Map
|
||||
* - UsdPrimvarReader_float2 -> UV Map
|
||||
*
|
||||
* Limitations: arbitrary primvar readers or UsdTransform2d not yet
|
||||
* supported. For #UsdUVTexture, only the file, st and #sourceColorSpace
|
||||
* inputs are handled.
|
||||
*
|
||||
* TODO(makowalski): Investigate adding support for converting additional
|
||||
* shaders and inputs. Supporting certain types of inputs, such as texture
|
||||
* scale and bias, will probably require creating Blender Group nodes with
|
||||
* the corresponding inputs.
|
||||
*/
|
||||
class USDMaterialReader {
|
||||
private:
|
||||
const USDImportParams ¶ms_;
|
||||
Main &bmain_;
|
||||
|
||||
public:
|
||||
USDMaterialReader(const USDImportParams ¶ms, Main &bmain);
|
||||
|
||||
Material *add_material(const pxr::UsdShadeMaterial &usd_material,
|
||||
bool read_usd_preview = true) const;
|
||||
|
||||
void import_usd_preview(Material *mtl, const pxr::UsdShadeMaterial &usd_material) const;
|
||||
|
||||
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
|
||||
ReportList *reports() const;
|
||||
|
||||
protected:
|
||||
/** Create the Principled BSDF shader node network. */
|
||||
void import_usd_preview_nodes(Material *mtl,
|
||||
const pxr::UsdShadeMaterial &usd_material,
|
||||
const pxr::UsdShadeShader &usd_shader) const;
|
||||
|
||||
void set_principled_node_inputs(bNode *principled_node,
|
||||
bNodeTree *ntree,
|
||||
const pxr::UsdShadeShader &usd_shader) const;
|
||||
|
||||
bool set_displacement_node_inputs(bNodeTree *ntree,
|
||||
bNode *output,
|
||||
const pxr::UsdShadeShader &usd_shader) const;
|
||||
|
||||
/** Convert the given USD shader input to an input on the given Blender node. */
|
||||
bool set_node_input(const pxr::UsdShadeInput &usd_input,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx,
|
||||
const ExtraLinkInfo &extra = {}) const;
|
||||
|
||||
/**
|
||||
* Follow the connected source of the USD input to create corresponding inputs
|
||||
* for the given Blender node.
|
||||
*/
|
||||
bool follow_connection(const pxr::UsdShadeInput &usd_input,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx,
|
||||
const ExtraLinkInfo &extra = {}) const;
|
||||
|
||||
void convert_usd_uv_texture(const pxr::UsdShadeShader &usd_shader,
|
||||
const pxr::TfToken &usd_source_name,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx,
|
||||
const ExtraLinkInfo &extra = {}) const;
|
||||
|
||||
void convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx) const;
|
||||
|
||||
/**
|
||||
* Load the texture image node's texture from the path given by the USD shader's
|
||||
* file input value.
|
||||
*/
|
||||
void load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
||||
bNode *tex_image,
|
||||
const ExtraLinkInfo &extra = {}) const;
|
||||
|
||||
/**
|
||||
* This function creates a Blender UV Map node, under the simplifying assumption that
|
||||
* UsdPrimvarReader_float2 shaders output UV coordinates.
|
||||
*/
|
||||
void convert_usd_primvar_reader_float2(const pxr::UsdShadeShader &usd_shader,
|
||||
const pxr::TfToken &usd_source_name,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx) const;
|
||||
void convert_usd_primvar_reader_generic(const pxr::UsdShadeShader &usd_shader,
|
||||
StringRef output_type,
|
||||
bNode *dest_node,
|
||||
const StringRefNull dest_socket_name,
|
||||
bNodeTree *ntree,
|
||||
int column,
|
||||
NodePlacementContext &ctx) const;
|
||||
};
|
||||
|
||||
/* Utility functions. */
|
||||
|
||||
/**
|
||||
* Returns a map containing all the Blender materials which allows a fast
|
||||
* lookup of the material by name. Note that the material name key
|
||||
* might be modified to be a valid USD identifier, to match material
|
||||
* names in the imported USD.
|
||||
*/
|
||||
void build_material_map(const Main *bmain, Map<std::string, Material *> &r_mat_map);
|
||||
|
||||
/**
|
||||
* Returns an existing Blender material that corresponds to the USD material with the given path.
|
||||
* Returns null if no such material exists.
|
||||
*
|
||||
* \param mat_map: Map a material name to a Blender material. Note that the name key
|
||||
* might be the Blender material name modified to be a valid USD identifier,
|
||||
* to match the material names in the imported USD.
|
||||
* \param usd_path_to_mat: Map a USD material path to the imported Blender material.
|
||||
*
|
||||
* The usd_path_to_mat is needed to determine the name of the Blender
|
||||
* material imported from a USD path in the case when a unique name was generated
|
||||
* for the material due to a name collision.
|
||||
*/
|
||||
Material *find_existing_material(const pxr::SdfPath &usd_mat_path,
|
||||
const USDImportParams ¶ms,
|
||||
const Map<std::string, Material *> &mat_map,
|
||||
const Map<pxr::SdfPath, Material *> &usd_path_to_mat);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
1107
blender-5.2.0/source/blender/io/usd/intern/usd_reader_mesh.cc
Normal file
1107
blender-5.2.0/source/blender/io/usd/intern/usd_reader_mesh.cc
Normal file
File diff suppressed because it is too large
Load Diff
154
blender-5.2.0/source/blender/io/usd/intern/usd_reader_mesh.hh
Normal file
154
blender-5.2.0/source/blender/io/usd/intern/usd_reader_mesh.hh
Normal file
@@ -0,0 +1,154 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* Adapted from the Blender Alembic importer implementation.
|
||||
* Modifications Copyright 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved. */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
struct USDMeshReadData {
|
||||
private:
|
||||
pxr::VtVec3fArray positions_;
|
||||
pxr::VtVec3fArray normals_;
|
||||
pxr::VtIntArray face_indices_;
|
||||
pxr::VtIntArray face_counts_;
|
||||
|
||||
public:
|
||||
USDMeshReadData(const pxr::UsdGeomMesh &mesh_prim, pxr::UsdTimeCode time);
|
||||
|
||||
Span<int> face_indices() const
|
||||
{
|
||||
return Span(face_indices_.cdata(), face_indices_.size());
|
||||
}
|
||||
Span<int> face_counts() const
|
||||
{
|
||||
return Span(face_counts_.cdata(), face_counts_.size());
|
||||
}
|
||||
Span<float3> positions() const
|
||||
{
|
||||
return Span(positions_.cdata(), positions_.size()).cast<float3>();
|
||||
}
|
||||
Span<float3> normals() const
|
||||
{
|
||||
return Span(normals_.cdata(), normals_.size()).cast<float3>();
|
||||
}
|
||||
MutableSpan<float3> normals_for_write()
|
||||
{
|
||||
return MutableSpan(normals_.data(), normals_.size()).cast<float3>();
|
||||
}
|
||||
|
||||
pxr::TfToken normal_interpolation;
|
||||
pxr::TfToken orientation;
|
||||
};
|
||||
|
||||
class USDMeshReader : public USDGeomReader {
|
||||
private:
|
||||
pxr::UsdGeomMesh mesh_prim_;
|
||||
|
||||
bool is_left_handed_ = false;
|
||||
bool is_time_varying_ = false;
|
||||
|
||||
/* This is to ensure we load all data once, because we reuse the read_mesh function
|
||||
* in the mesh seq modifier, and in initial load. Ideally, a better fix would be
|
||||
* implemented. Note this will break if faces or positions vary. */
|
||||
bool is_initial_load_ = false;
|
||||
|
||||
Map<const pxr::TfToken, bool> primvar_varying_map_;
|
||||
|
||||
public:
|
||||
USDMeshReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDGeomReader(prim, import_params, settings), mesh_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(mesh_prim_);
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
bool topology_changed(const Mesh *existing_mesh, pxr::UsdTimeCode time) 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.
|
||||
*/
|
||||
pxr::SdfPath get_skeleton_path() const;
|
||||
|
||||
private:
|
||||
void process_normals_vertex_varying(Mesh *mesh, MutableSpan<float3> usd_normals) const;
|
||||
void process_normals_face_varying(Mesh *mesh, Span<float3> usd_normals) const;
|
||||
/** Set USD uniform (per-face) normals as Blender loop normals. */
|
||||
void process_normals_uniform(Mesh *mesh, Span<float3> usd_normals) const;
|
||||
void readFaceSetsSample(Main *bmain, Mesh *mesh, pxr::UsdTimeCode time);
|
||||
void assign_facesets_to_material_indices(pxr::UsdTimeCode time,
|
||||
MutableSpan<int> material_indices,
|
||||
Map<pxr::SdfPath, int> *r_mat_map);
|
||||
|
||||
bool read_faces(Mesh *mesh, const USDMeshReadData &usd_data) const;
|
||||
void read_subdiv();
|
||||
void read_vertex_creases(Mesh *mesh, pxr::UsdTimeCode time);
|
||||
void read_edge_creases(Mesh *mesh, pxr::UsdTimeCode time);
|
||||
void read_velocities(Mesh *mesh, pxr::UsdTimeCode time);
|
||||
|
||||
void read_mesh_sample(ImportSettings *settings,
|
||||
Mesh *mesh,
|
||||
USDMeshReadData &usd_data,
|
||||
pxr::UsdTimeCode time,
|
||||
bool new_mesh);
|
||||
|
||||
Mesh *read_mesh(struct Mesh *existing_mesh,
|
||||
const USDMeshReadParams params,
|
||||
const char **r_err_str);
|
||||
|
||||
void read_custom_data(const ImportSettings *settings,
|
||||
Mesh *mesh,
|
||||
pxr::UsdTimeCode time,
|
||||
bool new_mesh);
|
||||
|
||||
void read_uv_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const pxr::UsdTimeCode time);
|
||||
|
||||
bool topology_changed(const Mesh *existing_mesh, const USDMeshReadData &usd_data) const;
|
||||
|
||||
/**
|
||||
* Override transform computation to account for the binding
|
||||
* transformation for skinned meshes.
|
||||
*/
|
||||
std::optional<XformResult> get_local_usd_xform(pxr::UsdTimeCode time) const override;
|
||||
|
||||
/**
|
||||
* A skinned mesh is re-parented to its bound armature during import
|
||||
* (#USDStageReader::process_armature_modifiers), so it is never the root of a transform
|
||||
* hierarchy; treating it as one would apply the scene scale / axis conversion to its
|
||||
* (skeleton-space) local transform on top of the same conversion already carried by the
|
||||
* armature.
|
||||
*/
|
||||
bool is_root_xform_prim() const override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
474
blender-5.2.0/source/blender/io/usd/intern/usd_reader_nurbs.cc
Normal file
474
blender-5.2.0/source/blender/io/usd/intern/usd_reader_nurbs.cc
Normal file
@@ -0,0 +1,474 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#include "usd_reader_nurbs.hh"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
|
||||
#include <pxr/base/vt/types.h>
|
||||
|
||||
#include <pxr/usd/usdGeom/curves.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Store incoming USD data privately and expose Blender-friendly Spans publicly. */
|
||||
struct USDCurveData {
|
||||
private:
|
||||
pxr::VtArray<pxr::GfVec3f> points_;
|
||||
pxr::VtArray<int> counts_;
|
||||
pxr::VtArray<int> orders_;
|
||||
pxr::VtArray<double> knots_;
|
||||
pxr::VtArray<double> weights_;
|
||||
pxr::VtArray<float> widths_;
|
||||
pxr::VtArray<pxr::GfVec3f> velocities_;
|
||||
|
||||
public:
|
||||
Span<float3> points() const
|
||||
{
|
||||
return Span(points_.cdata(), points_.size()).cast<float3>();
|
||||
}
|
||||
Span<int> counts() const
|
||||
{
|
||||
return Span(counts_.cdata(), counts_.size());
|
||||
}
|
||||
Span<int> orders() const
|
||||
{
|
||||
return Span(orders_.cdata(), orders_.size());
|
||||
}
|
||||
Span<double> knots() const
|
||||
{
|
||||
return Span(knots_.cdata(), knots_.size());
|
||||
}
|
||||
Span<double> weights() const
|
||||
{
|
||||
return Span(weights_.cdata(), weights_.size());
|
||||
}
|
||||
Span<float> widths() const
|
||||
{
|
||||
return Span(widths_.cdata(), widths_.size());
|
||||
}
|
||||
Span<float3> velocities() const
|
||||
{
|
||||
return Span(velocities_.cdata(), velocities_.size()).cast<float3>();
|
||||
}
|
||||
|
||||
bool load(const pxr::UsdGeomNurbsCurves &curve_prim, const pxr::UsdTimeCode time)
|
||||
{
|
||||
curve_prim.GetCurveVertexCountsAttr().Get(&counts_, time);
|
||||
curve_prim.GetOrderAttr().Get(&orders_, time);
|
||||
|
||||
if (counts_.size() != orders_.size()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Curve vertex and order size mismatch for NURBS prim %s",
|
||||
curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::any_of(counts_.cbegin(), counts_.cend(), [](int value) { return value < 0; }) ||
|
||||
std::any_of(orders_.cbegin(), orders_.cend(), [](int value) { return value < 0; }))
|
||||
{
|
||||
CLOG_WARN(&LOG,
|
||||
"Invalid curve vertex count or order value detected for NURBS prim %s",
|
||||
curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
curve_prim.GetPointsAttr().Get(&points_, time);
|
||||
curve_prim.GetKnotsAttr().Get(&knots_, time);
|
||||
curve_prim.GetWidthsAttr().Get(&widths_, time);
|
||||
|
||||
curve_prim.GetPointWeightsAttr().Get(&weights_, time);
|
||||
if (!weights_.empty() && points_.size() != weights_.size()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Invalid curve weights count for NURBS prim %s",
|
||||
curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
|
||||
|
||||
/* Only clear, but continue to load other curve data. */
|
||||
weights_.clear();
|
||||
}
|
||||
|
||||
curve_prim.GetVelocitiesAttr().Get(&velocities_, time);
|
||||
if (!velocities_.empty() && points_.size() != velocities_.size()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Invalid curve velocity count for NURBS prim %s",
|
||||
curve_prim.GetPrim().GetPrimPath().GetAsString().c_str());
|
||||
|
||||
/* Only clear, but continue to load other curve data. */
|
||||
velocities_.clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct CurveData {
|
||||
Array<int> blender_offsets;
|
||||
Array<int> usd_offsets;
|
||||
Array<int> usd_knot_offsets;
|
||||
Array<bool> is_cyclic;
|
||||
};
|
||||
|
||||
static KnotsMode determine_knots_mode(const Span<double> usd_knots,
|
||||
const int order,
|
||||
const bool is_cyclic)
|
||||
{
|
||||
/* TODO: We have to convert knot values to float for usage in Blender APIs. Look into making
|
||||
* calculate_multiplicity_sequence a template. */
|
||||
Array<float> blender_knots(usd_knots.size());
|
||||
for (const int knot_i : usd_knots.index_range()) {
|
||||
blender_knots[knot_i] = float(usd_knots[knot_i]);
|
||||
}
|
||||
|
||||
Vector<int> multiplicity = bke::curves::nurbs::calculate_multiplicity_sequence(blender_knots);
|
||||
if (multiplicity.size() < 2) {
|
||||
/* Invalid knot vector. Use normal mode. */
|
||||
return NURBS_KNOT_MODE_NORMAL;
|
||||
}
|
||||
|
||||
const int head = multiplicity.first();
|
||||
const int tail = multiplicity.last();
|
||||
const Span<int> inner = multiplicity.as_span().slice(1, multiplicity.size() - 2);
|
||||
|
||||
/* If the knot vector starts and ends with full multiplicity knots, then this is classified as
|
||||
* Blender's endpoint mode. */
|
||||
const int degree = order - 1;
|
||||
const bool is_endpoint = is_cyclic ? (tail >= degree) : (head == order && tail >= order);
|
||||
|
||||
/* If all of the inner multiplicities are equal to the degree, then this is a Bezier curve. */
|
||||
if (degree > 1 &&
|
||||
std::all_of(inner.begin(), inner.end(), [degree](int value) { return value == degree; }))
|
||||
{
|
||||
return is_endpoint ? NURBS_KNOT_MODE_ENDPOINT_BEZIER : NURBS_KNOT_MODE_BEZIER;
|
||||
}
|
||||
|
||||
if (is_endpoint) {
|
||||
return NURBS_KNOT_MODE_ENDPOINT;
|
||||
}
|
||||
|
||||
/* If all of the inner knot values are equally spaced, then this is a regular/uniform curve and
|
||||
* we assume that our normal knot mode will match. Use custom knots otherwise. */
|
||||
const Span<float> inner_values = blender_knots.as_span().drop_front(head).drop_back(tail);
|
||||
if (inner_values.size() > 2) {
|
||||
const float delta = inner_values[1] - inner_values[0];
|
||||
if (delta < 0) {
|
||||
/* Invalid knot vector. Use normal mode. */
|
||||
return NURBS_KNOT_MODE_NORMAL;
|
||||
}
|
||||
for (int i = 2; i < inner.size(); i++) {
|
||||
if (inner_values[i] - inner_values[i - 1] != delta) {
|
||||
/* The knot values are not equally spaced. Use custom knots. */
|
||||
return NURBS_KNOT_MODE_CUSTOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing matches. Use normal mode. */
|
||||
return NURBS_KNOT_MODE_NORMAL;
|
||||
}
|
||||
|
||||
static CurveData calc_curve_offsets(const Span<float3> usd_points,
|
||||
const Span<int> usd_counts,
|
||||
const Span<int> usd_orders,
|
||||
const Span<double> usd_knots)
|
||||
{
|
||||
CurveData data;
|
||||
data.blender_offsets.reinitialize(usd_counts.size() + 1);
|
||||
data.usd_offsets.reinitialize(usd_counts.size() + 1);
|
||||
data.usd_knot_offsets.reinitialize(usd_counts.size() + 1);
|
||||
data.is_cyclic.reinitialize(usd_counts.size());
|
||||
|
||||
Span<float3> usd_remaining_points = usd_points;
|
||||
Span<double> usd_remaining_knots = usd_knots;
|
||||
|
||||
for (const int curve_i : usd_counts.index_range()) {
|
||||
const int points_num = usd_counts[curve_i];
|
||||
const int knots_num = points_num + usd_orders[curve_i];
|
||||
const int degree = usd_orders[curve_i] - 1;
|
||||
const Span<double> usd_current_knots = usd_remaining_knots.take_front(knots_num);
|
||||
const Span<float3> usd_current_points = usd_remaining_points.take_front(points_num);
|
||||
if (knots_num < 4 || knots_num != usd_current_knots.size()) {
|
||||
data.is_cyclic[curve_i] = false;
|
||||
}
|
||||
else {
|
||||
data.is_cyclic[curve_i] = usd_current_points.take_front(degree) ==
|
||||
usd_current_points.take_back(degree);
|
||||
}
|
||||
|
||||
int blender_count = usd_counts[curve_i];
|
||||
|
||||
/* Account for any repeated degree(order - 1) number of points from USD cyclic curves which
|
||||
* Blender does not use internally. */
|
||||
if (data.is_cyclic[curve_i]) {
|
||||
blender_count -= degree;
|
||||
}
|
||||
|
||||
data.blender_offsets[curve_i] = blender_count;
|
||||
data.usd_offsets[curve_i] = points_num;
|
||||
data.usd_knot_offsets[curve_i] = knots_num;
|
||||
|
||||
/* Move to next sequence of values. */
|
||||
usd_remaining_points = usd_remaining_points.drop_front(points_num);
|
||||
usd_remaining_knots = usd_remaining_knots.drop_front(knots_num);
|
||||
}
|
||||
|
||||
offset_indices::accumulate_counts_to_offsets(data.blender_offsets);
|
||||
offset_indices::accumulate_counts_to_offsets(data.usd_offsets);
|
||||
offset_indices::accumulate_counts_to_offsets(data.usd_knot_offsets);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns true if the number of curves or the number of curve points in each curve differ. */
|
||||
static bool curves_topology_changed(const bke::CurvesGeometry &curves, const Span<int> usd_offsets)
|
||||
{
|
||||
if (curves.offsets() != usd_offsets) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static IndexRange get_usd_points_range_de_dup(IndexRange blender_points_range,
|
||||
IndexRange usd_points_range)
|
||||
{
|
||||
/* Take from the front of USD's range to exclude any duplicates at the end. */
|
||||
return usd_points_range.take_front(blender_points_range.size());
|
||||
};
|
||||
|
||||
bool USDNurbsReader::is_animated() const
|
||||
{
|
||||
if (curve_prim_.GetPointsAttr().ValueMightBeTimeVarying() ||
|
||||
curve_prim_.GetWidthsAttr().ValueMightBeTimeVarying() ||
|
||||
curve_prim_.GetPointWeightsAttr().ValueMightBeTimeVarying())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI pv_api(curve_prim_);
|
||||
for (const pxr::UsdGeomPrimvar &pv : pv_api.GetPrimvarsWithValues()) {
|
||||
if (pv.ValueMightBeTimeVarying()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void USDNurbsReader::read_curve_sample(Curves *curves_id, const pxr::UsdTimeCode time)
|
||||
{
|
||||
USDCurveData usd_data;
|
||||
if (!usd_data.load(curve_prim_, time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Span<float3> usd_points = usd_data.points();
|
||||
const Span<int> usd_counts = usd_data.counts();
|
||||
const Span<int> usd_orders = usd_data.orders();
|
||||
const Span<double> usd_knots = usd_data.knots();
|
||||
const Span<double> usd_weights = usd_data.weights();
|
||||
const Span<float3> usd_velocities = usd_data.velocities();
|
||||
|
||||
/* Calculate and set the Curves topology. */
|
||||
CurveData data = calc_curve_offsets(usd_points, usd_counts, usd_orders, usd_knots);
|
||||
|
||||
// Check validity of curve counts
|
||||
const bool all_valid = std::all_of(usd_counts.begin(), usd_counts.end(), [](int count) {
|
||||
const int min_points = 2;
|
||||
return count >= min_points;
|
||||
});
|
||||
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
if (all_valid && curves_topology_changed(curves, data.blender_offsets)) {
|
||||
curves.resize(data.blender_offsets.last(), usd_counts.size());
|
||||
}
|
||||
|
||||
// Early out if there are no curves to load.
|
||||
if (curves.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves.offsets_for_write().copy_from(data.blender_offsets);
|
||||
curves.fill_curve_types(CurveType::CURVE_TYPE_NURBS);
|
||||
|
||||
MutableSpan<float3> curves_positions = curves.positions_for_write();
|
||||
|
||||
/* If there's no points defined, fill positions with default values and exit. */
|
||||
if (usd_points.is_empty()) {
|
||||
curves_positions.fill(float3(0.0f, 0.0f, 0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
/* NOTE: USD contains duplicated points for periodic(cyclic) curves. The indices into each curve
|
||||
* will differ from what Blender expects so we need to maintain and use separate offsets for
|
||||
* each. A side effect of this dissonance is that all primvar/attribute loading needs to be
|
||||
* handled in a special manner vs. what might be seen in our other USD readers. */
|
||||
const OffsetIndices blender_points_by_curve = curves.points_by_curve();
|
||||
const OffsetIndices usd_points_by_curve = OffsetIndices<int>(data.usd_offsets,
|
||||
offset_indices::NoSortCheck{});
|
||||
const OffsetIndices usd_knots_by_curve = OffsetIndices<int>(data.usd_knot_offsets,
|
||||
offset_indices::NoSortCheck{});
|
||||
|
||||
/* TODO: We cannot read custom primvars for cyclic curves at the moment. */
|
||||
const bool can_read_primvars = std::all_of(
|
||||
data.is_cyclic.begin(), data.is_cyclic.end(), [](bool item) { return item == false; });
|
||||
|
||||
/* Set all curve data. */
|
||||
for (const int curve_i : blender_points_by_curve.index_range()) {
|
||||
const IndexRange blender_points_range = blender_points_by_curve[curve_i];
|
||||
const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
|
||||
blender_points_range, usd_points_by_curve[curve_i]);
|
||||
|
||||
const int copy_size = std::min(
|
||||
usd_points.size(), std::min(blender_points_range.size(), usd_points_range_de_dup.size()));
|
||||
curves_positions.slice(blender_points_range.start(), copy_size)
|
||||
.copy_from(usd_points.slice(usd_points_range_de_dup.start(), copy_size));
|
||||
/* Fill any missing items with a default value. */
|
||||
for (int i = copy_size; i < blender_points_range.size(); i++) {
|
||||
curves_positions[blender_points_range.start() + i] = float3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
MutableSpan<bool> curves_cyclic = curves.cyclic_for_write();
|
||||
curves_cyclic.copy_from(data.is_cyclic);
|
||||
|
||||
MutableSpan<int8_t> curves_nurbs_orders = curves.nurbs_orders_for_write();
|
||||
for (const int curve_i : blender_points_by_curve.index_range()) {
|
||||
const int order = usd_orders[curve_i];
|
||||
curves_nurbs_orders[curve_i] = int8_t(order >= 2 ? order : 2);
|
||||
}
|
||||
|
||||
MutableSpan<int8_t> curves_knots_mode = curves.nurbs_knots_modes_for_write();
|
||||
for (const int curve_i : blender_points_by_curve.index_range()) {
|
||||
const IndexRange usd_knots_range = usd_knots_by_curve[curve_i];
|
||||
curves_knots_mode[curve_i] = determine_knots_mode(
|
||||
usd_knots.slice_safe(usd_knots_range), usd_orders[curve_i], data.is_cyclic[curve_i]);
|
||||
}
|
||||
|
||||
/* Load in the optional weights. */
|
||||
if (!usd_weights.is_empty()) {
|
||||
MutableSpan<float> curves_weights = curves.nurbs_weights_for_write();
|
||||
for (const int curve_i : blender_points_by_curve.index_range()) {
|
||||
const IndexRange blender_points_range = blender_points_by_curve[curve_i];
|
||||
const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
|
||||
blender_points_range, usd_points_by_curve[curve_i]);
|
||||
|
||||
const Span<double> usd_weights_de_dup = usd_weights.slice(usd_points_range_de_dup);
|
||||
int64_t usd_point_i = 0;
|
||||
for (const int point_i : blender_points_range) {
|
||||
curves_weights[point_i] = float(usd_weights_de_dup[usd_point_i]);
|
||||
usd_point_i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Load in the optional velocities. */
|
||||
if (!usd_velocities.is_empty()) {
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
bke::SpanAttributeWriter<float3> curves_velocity =
|
||||
attributes.lookup_or_add_for_write_only_span<float3>("velocity", bke::AttrDomain::Point);
|
||||
|
||||
for (const int curve_i : blender_points_by_curve.index_range()) {
|
||||
const IndexRange blender_points_range = blender_points_by_curve[curve_i];
|
||||
const IndexRange usd_points_range_de_dup = get_usd_points_range_de_dup(
|
||||
blender_points_range, usd_points_by_curve[curve_i]);
|
||||
|
||||
curves_velocity.span.slice(blender_points_range)
|
||||
.copy_from(usd_velocities.slice(usd_points_range_de_dup));
|
||||
}
|
||||
|
||||
curves_velocity.finish();
|
||||
}
|
||||
|
||||
/* Once all of the curves metadata (orders, cyclic, knots_mode) has been set, we can prepare
|
||||
* Blender for any custom knots that need to be loaded. */
|
||||
MutableSpan<float> blender_custom_knots;
|
||||
OffsetIndices<int> blender_knots_by_curve;
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
if (curves_knots_mode[curve_i] != NURBS_KNOT_MODE_CUSTOM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If this is our first time through, we need to update Blender's topology data to prepare for
|
||||
* the incoming custom knots. */
|
||||
if (blender_custom_knots.is_empty()) {
|
||||
curves.nurbs_custom_knots_update_size();
|
||||
blender_knots_by_curve = curves.nurbs_custom_knots_by_curve();
|
||||
blender_custom_knots = curves.nurbs_custom_knots_for_write();
|
||||
}
|
||||
|
||||
const IndexRange blender_knots_range = blender_knots_by_curve[curve_i];
|
||||
const IndexRange usd_knots_range = usd_knots_by_curve[curve_i];
|
||||
const Span<double> usd_knots_values = usd_knots.slice(usd_knots_range);
|
||||
MutableSpan<float> blender_knots = blender_custom_knots.slice(blender_knots_range);
|
||||
int usd_knot_i = 0;
|
||||
for (float &blender_knot : blender_knots) {
|
||||
blender_knot = usd_knots_values[usd_knot_i] > 0.0 ? float(usd_knots_values[usd_knot_i]) : 0;
|
||||
usd_knot_i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Curve widths. */
|
||||
const Span<float> usd_widths = usd_data.widths();
|
||||
if (!usd_widths.is_empty()) {
|
||||
MutableSpan<float> radii = curves.radius_for_write();
|
||||
|
||||
const pxr::TfToken widths_interp = curve_prim_.GetWidthsInterpolation();
|
||||
if (widths_interp == pxr::UsdGeomTokens->constant || usd_widths.size() == 1) {
|
||||
radii.fill(usd_widths[0] / 2.0f);
|
||||
}
|
||||
else if (widths_interp == pxr::UsdGeomTokens->varying) {
|
||||
int point_offset = 0;
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
const float usd_curve_radius = usd_widths[curve_i] / 2.0f;
|
||||
int point_count = usd_counts[curve_i];
|
||||
if (curves_cyclic[curve_i]) {
|
||||
point_count -= usd_orders[curve_i] - 1;
|
||||
}
|
||||
for (const int point : IndexRange(point_count)) {
|
||||
radii[point_offset + point] = usd_curve_radius;
|
||||
}
|
||||
|
||||
point_offset += point_count;
|
||||
}
|
||||
}
|
||||
else if (widths_interp == pxr::UsdGeomTokens->vertex) {
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
const IndexRange blender_points_range = blender_points_by_curve[curve_i];
|
||||
const IndexRange usd_points_range = usd_points_by_curve[curve_i];
|
||||
|
||||
/* Take from the front of USD's range to exclude any duplicates at the end. */
|
||||
const IndexRange usd_points_range_de_dup = usd_points_range.take_front(
|
||||
blender_points_range.size());
|
||||
|
||||
const Span<float> usd_widths_de_dup = usd_widths.slice(usd_points_range_de_dup);
|
||||
int64_t usd_point_i = 0;
|
||||
for (const int point_i : blender_points_range) {
|
||||
radii[point_i] = usd_widths_de_dup[usd_point_i] / 2.0f;
|
||||
usd_point_i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (can_read_primvars) {
|
||||
this->read_custom_data(curves, time);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_curve.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdGeom/nurbsCurves.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Curves;
|
||||
|
||||
namespace bke {
|
||||
class CurvesGeometry;
|
||||
} // namespace bke
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDNurbsReader : public USDCurvesReader {
|
||||
private:
|
||||
pxr::UsdGeomNurbsCurves curve_prim_;
|
||||
|
||||
public:
|
||||
USDNurbsReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDCurvesReader(prim, import_params, settings), curve_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(curve_prim_);
|
||||
}
|
||||
|
||||
void read_curve_sample(Curves *curves_id, pxr::UsdTimeCode time) override;
|
||||
bool is_animated() const override;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,342 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_pointinstancer.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_legacy_types.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_pointcloud.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/**
|
||||
* Create a node to read a geometry attribute of the given name and type.
|
||||
*/
|
||||
static bNode *add_input_named_attrib_node(bNodeTree *ntree, const char *name, int8_t prop_type)
|
||||
{
|
||||
bNode *node = bke::node_add_static_node(nullptr, *ntree, GEO_NODE_INPUT_NAMED_ATTRIBUTE);
|
||||
auto *storage = reinterpret_cast<NodeGeometryInputNamedAttribute *>(node->storage);
|
||||
storage->data_type = prop_type;
|
||||
|
||||
bNodeSocket *socket = bke::node_find_socket(*node, SOCK_IN, "Name"_ustr);
|
||||
bNodeSocketValueString *str_value = static_cast<bNodeSocketValueString *>(socket->default_value);
|
||||
BLI_strncpy(str_value->value, name, MAX_NAME);
|
||||
return node;
|
||||
}
|
||||
|
||||
void USDPointInstancerReader::create_object(Main *bmain)
|
||||
{
|
||||
PointCloud *pointcloud = BKE_pointcloud_add(bmain, name_.c_str());
|
||||
this->object_ = BKE_object_add_only_object(bmain, OB_POINTCLOUD, name_.c_str());
|
||||
this->object_->data = id_cast<ID *>(pointcloud);
|
||||
}
|
||||
|
||||
void USDPointInstancerReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
pxr::VtArray<pxr::GfVec3f> usd_positions;
|
||||
pxr::VtArray<pxr::GfVec3f> usd_scales;
|
||||
pxr::VtArray<pxr::GfQuath> usd_orientations;
|
||||
pxr::VtInt64Array usd_ids;
|
||||
pxr::VtArray<int> usd_proto_indices;
|
||||
std::vector<bool> usd_mask = point_instancer_prim_.ComputeMaskAtTime(params.motion_sample_time);
|
||||
|
||||
point_instancer_prim_.GetPositionsAttr().Get(&usd_positions, params.motion_sample_time);
|
||||
point_instancer_prim_.GetScalesAttr().Get(&usd_scales, params.motion_sample_time);
|
||||
point_instancer_prim_.GetOrientationsAttr().Get(&usd_orientations, params.motion_sample_time);
|
||||
point_instancer_prim_.GetIdsAttr().Get(&usd_ids, params.motion_sample_time);
|
||||
point_instancer_prim_.GetProtoIndicesAttr().Get(&usd_proto_indices, params.motion_sample_time);
|
||||
|
||||
PointCloud *pointcloud = geometry_set.get_pointcloud_for_write();
|
||||
if (pointcloud->totpoint != usd_positions.size()) {
|
||||
/* Size changed so we must reallocate. */
|
||||
pointcloud = BKE_pointcloud_new_nomain(usd_positions.size());
|
||||
}
|
||||
|
||||
MutableSpan<float3> point_positions = pointcloud->positions_for_write();
|
||||
point_positions.copy_from(Span(usd_positions.cdata(), usd_positions.size()).cast<float3>());
|
||||
|
||||
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
|
||||
|
||||
bke::SpanAttributeWriter<float3> scales_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<float3>("scale", bke::AttrDomain::Point);
|
||||
|
||||
/* Here and below, handle the case where instancing attributes are empty or
|
||||
* not of the expected size. */
|
||||
if (usd_scales.size() < usd_positions.size()) {
|
||||
scales_attribute.span.fill(float3(1.0f));
|
||||
}
|
||||
|
||||
Span<pxr::GfVec3f> scales = Span(usd_scales.cdata(), usd_scales.size());
|
||||
for (const int i : IndexRange(std::min(usd_scales.size(), usd_positions.size()))) {
|
||||
scales_attribute.span[i] = float3(scales[i][0], scales[i][1], scales[i][2]);
|
||||
}
|
||||
|
||||
scales_attribute.finish();
|
||||
|
||||
bke::SpanAttributeWriter<math::Quaternion> orientations_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<math::Quaternion>("orientation",
|
||||
bke::AttrDomain::Point);
|
||||
|
||||
if (usd_orientations.size() < usd_positions.size()) {
|
||||
orientations_attribute.span.fill(math::Quaternion::identity());
|
||||
}
|
||||
|
||||
Span<pxr::GfQuath> orientations = Span(usd_orientations.cdata(), usd_orientations.size());
|
||||
for (const int i : IndexRange(std::min(usd_orientations.size(), usd_positions.size()))) {
|
||||
orientations_attribute.span[i] = math::Quaternion(orientations[i].GetReal(),
|
||||
orientations[i].GetImaginary()[0],
|
||||
orientations[i].GetImaginary()[1],
|
||||
orientations[i].GetImaginary()[2]);
|
||||
}
|
||||
|
||||
orientations_attribute.finish();
|
||||
|
||||
if (!usd_ids.empty()) {
|
||||
bke::SpanAttributeWriter<int> ids_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<int>("id", bke::AttrDomain::Point);
|
||||
|
||||
const Span<int64_t> usd_data(usd_ids.cdata(), usd_ids.size());
|
||||
if (usd_data.size() < ids_attribute.span.size()) {
|
||||
ids_attribute.span.fill(0);
|
||||
}
|
||||
|
||||
for (const int i : IndexRange(std::min(usd_data.size(), ids_attribute.span.size()))) {
|
||||
/* Blender only supports int ID attributes so we have to narrow the value. */
|
||||
ids_attribute.span[i] = int(usd_data[i]);
|
||||
}
|
||||
|
||||
ids_attribute.finish();
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<int> proto_indices_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<int>("proto_index", bke::AttrDomain::Point);
|
||||
|
||||
if (usd_proto_indices.size() < usd_positions.size()) {
|
||||
proto_indices_attribute.span.fill(0);
|
||||
}
|
||||
|
||||
Span<int> proto_indices = Span(usd_proto_indices.cdata(), usd_proto_indices.size());
|
||||
for (const int i : IndexRange(std::min(usd_proto_indices.size(), usd_positions.size()))) {
|
||||
proto_indices_attribute.span[i] = proto_indices[i];
|
||||
}
|
||||
|
||||
proto_indices_attribute.finish();
|
||||
|
||||
bke::SpanAttributeWriter<bool> mask_attribute =
|
||||
attributes.lookup_or_add_for_write_only_span<bool>("mask", bke::AttrDomain::Point);
|
||||
|
||||
if (usd_mask.size() < usd_positions.size()) {
|
||||
mask_attribute.span.fill(true);
|
||||
}
|
||||
|
||||
for (const int i : IndexRange(std::min(usd_mask.size(), usd_positions.size()))) {
|
||||
mask_attribute.span[i] = usd_mask[i];
|
||||
}
|
||||
|
||||
mask_attribute.finish();
|
||||
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
}
|
||||
|
||||
void USDPointInstancerReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
||||
{
|
||||
PointCloud *pointcloud = id_cast<PointCloud *>(object_->data);
|
||||
|
||||
bke::GeometrySet geometry_set = bke::GeometrySet::from_pointcloud(
|
||||
pointcloud, bke::GeometryOwnershipType::Editable);
|
||||
|
||||
const USDMeshReadParams params = create_mesh_read_params(time.GetValue(),
|
||||
import_params_.mesh_read_flag);
|
||||
|
||||
read_geometry(geometry_set, params, nullptr);
|
||||
|
||||
PointCloud *read_pointcloud =
|
||||
geometry_set.get_component_for_write<bke::PointCloudComponent>().release();
|
||||
|
||||
if (read_pointcloud != pointcloud) {
|
||||
BKE_pointcloud_nomain_to_pointcloud(read_pointcloud, pointcloud);
|
||||
}
|
||||
|
||||
if (is_animated()) {
|
||||
/* If the point cloud has time-varying data, we add the cache modifier. */
|
||||
add_cache_modifier();
|
||||
}
|
||||
|
||||
ModifierData *md = BKE_modifier_new(eModifierType_Nodes);
|
||||
BLI_addtail(&object_->modifiers, md);
|
||||
BKE_modifiers_persistent_uid_init(*object_, *md);
|
||||
|
||||
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
|
||||
nmd.node_group = bke::node_tree_add_tree(bmain, "Instances", "GeometryNodeTree");
|
||||
|
||||
bNodeTree *ntree = nmd.node_group;
|
||||
|
||||
ntree->tree_interface.add_socket(
|
||||
"Geometry", "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
|
||||
ntree->tree_interface.add_socket(
|
||||
"Geometry", "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_INPUT, nullptr);
|
||||
bNode *group_input = bke::node_add_static_node(nullptr, *ntree, NODE_GROUP_INPUT);
|
||||
group_input->location[0] = -400.0f;
|
||||
bNode *group_output = bke::node_add_static_node(nullptr, *ntree, NODE_GROUP_OUTPUT);
|
||||
group_output->location[0] = 500.0f;
|
||||
group_output->flag |= NODE_DO_OUTPUT;
|
||||
|
||||
bNode *instance_on_points_node = bke::node_add_static_node(
|
||||
nullptr, *ntree, GEO_NODE_INSTANCE_ON_POINTS);
|
||||
instance_on_points_node->location[0] = 300.0f;
|
||||
bNodeSocket *socket = bke::node_find_socket(
|
||||
*instance_on_points_node, SOCK_IN, "Pick Instance"_ustr);
|
||||
socket->default_value_typed<bNodeSocketValueBoolean>()->value = true;
|
||||
|
||||
bNode *mask_attrib_node = add_input_named_attrib_node(ntree, "mask", CD_PROP_BOOL);
|
||||
mask_attrib_node->location[0] = 100.0f;
|
||||
mask_attrib_node->location[1] = -100.0f;
|
||||
|
||||
bNode *collection_info_node = bke::node_add_static_node(
|
||||
nullptr, *ntree, GEO_NODE_COLLECTION_INFO);
|
||||
collection_info_node->location[0] = 100.0f;
|
||||
collection_info_node->location[1] = -300.0f;
|
||||
socket = bke::node_find_socket(*collection_info_node, SOCK_IN, "Separate Children"_ustr);
|
||||
socket->default_value_typed<bNodeSocketValueBoolean>()->value = true;
|
||||
|
||||
bNode *indices_attrib_node = add_input_named_attrib_node(ntree, "proto_index", CD_PROP_INT32);
|
||||
indices_attrib_node->location[0] = 100.0f;
|
||||
indices_attrib_node->location[1] = -500.0f;
|
||||
|
||||
bNode *rotation_attrib_node = add_input_named_attrib_node(
|
||||
ntree, "orientation", CD_PROP_QUATERNION);
|
||||
rotation_attrib_node->location[0] = 100.0f;
|
||||
rotation_attrib_node->location[1] = -700.0f;
|
||||
|
||||
bNode *scale_attrib_node = add_input_named_attrib_node(ntree, "scale", CD_PROP_FLOAT3);
|
||||
scale_attrib_node->location[0] = 100.0f;
|
||||
scale_attrib_node->location[1] = -900.0f;
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*group_input,
|
||||
*static_cast<bNodeSocket *>(group_input->outputs.first),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Points"_ustr));
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*mask_attrib_node,
|
||||
*bke::node_find_socket(*mask_attrib_node, SOCK_OUT, "Attribute"_ustr),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Selection"_ustr));
|
||||
|
||||
bke::node_add_link(
|
||||
*ntree,
|
||||
*indices_attrib_node,
|
||||
*bke::node_find_socket(*indices_attrib_node, SOCK_OUT, "Attribute"_ustr),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Instance Index"_ustr));
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*scale_attrib_node,
|
||||
*bke::node_find_socket(*scale_attrib_node, SOCK_OUT, "Attribute"_ustr),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Scale"_ustr));
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*rotation_attrib_node,
|
||||
*bke::node_find_socket(*rotation_attrib_node, SOCK_OUT, "Attribute"_ustr),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Rotation"_ustr));
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*collection_info_node,
|
||||
*bke::node_find_socket(*collection_info_node, SOCK_OUT, "Instances"_ustr),
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_IN, "Instance"_ustr));
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*instance_on_points_node,
|
||||
*bke::node_find_socket(*instance_on_points_node, SOCK_OUT, "Instances"_ustr),
|
||||
*group_output,
|
||||
*static_cast<bNodeSocket *>(group_output->inputs.first));
|
||||
|
||||
BKE_ntree_update_after_single_tree_change(*bmain, *ntree);
|
||||
|
||||
BKE_object_modifier_set_active(object_, md);
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
pxr::SdfPathVector USDPointInstancerReader::proto_paths() const
|
||||
{
|
||||
pxr::SdfPathVector paths;
|
||||
point_instancer_prim_.GetPrototypesRel().GetTargets(&paths);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void USDPointInstancerReader::set_collection(Main *bmain, Collection &coll)
|
||||
{
|
||||
/* create_object() should have been called already. */
|
||||
BLI_assert(object_);
|
||||
|
||||
ModifierData *md = BKE_modifiers_findby_type(this->object_, eModifierType_Nodes);
|
||||
if (!md) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
||||
|
||||
bNodeTree *ntree = nmd->node_group;
|
||||
if (!ntree) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
bNode *collection_node = bke::node_find_node_by_name(*ntree, "Collection Info");
|
||||
if (!collection_node) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
bNodeSocket *sock = bke::node_find_socket(*collection_node, SOCK_IN, "Collection"_ustr);
|
||||
if (!sock) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
bNodeSocketValueCollection *socket_data = static_cast<bNodeSocketValueCollection *>(
|
||||
sock->default_value);
|
||||
|
||||
if (socket_data->value != &coll) {
|
||||
socket_data->value = &coll;
|
||||
BKE_ntree_update_tag_socket_property(ntree, sock);
|
||||
BKE_ntree_update_after_single_tree_change(*bmain, *ntree);
|
||||
}
|
||||
}
|
||||
|
||||
bool USDPointInstancerReader::is_animated() const
|
||||
{
|
||||
bool is_animated = false;
|
||||
is_animated |= point_instancer_prim_.GetPositionsAttr().ValueMightBeTimeVarying();
|
||||
is_animated |= point_instancer_prim_.GetScalesAttr().ValueMightBeTimeVarying();
|
||||
is_animated |= point_instancer_prim_.GetOrientationsAttr().ValueMightBeTimeVarying();
|
||||
is_animated |= point_instancer_prim_.GetProtoIndicesAttr().ValueMightBeTimeVarying();
|
||||
|
||||
return is_animated;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,64 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Collection;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Wraps the UsdGeomPointInstancer schema. Creates a Blender point cloud object. */
|
||||
|
||||
class USDPointInstancerReader : public USDGeomReader {
|
||||
private:
|
||||
pxr::UsdGeomPointInstancer point_instancer_prim_;
|
||||
|
||||
public:
|
||||
USDPointInstancerReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDGeomReader(prim, import_params, settings), point_instancer_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(point_instancer_prim_);
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
|
||||
/* This may be called by the cache modifier to update animated geometry. */
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
pxr::SdfPathVector proto_paths() const;
|
||||
|
||||
/**
|
||||
* Set the given collection on the Collection Info
|
||||
* node referenced by the geometry nodes modifier
|
||||
* on the object created by the reader. This assumes
|
||||
* create_object() and read_object_data() have already
|
||||
* been called.
|
||||
*
|
||||
* \param bmain: Pointer to Main
|
||||
* \param coll: The collection to set
|
||||
*/
|
||||
void set_collection(Main *bmain, Collection &coll);
|
||||
|
||||
/* Return true if the USD data may be time varying. */
|
||||
bool is_animated() const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
192
blender-5.2.0/source/blender/io/usd/intern/usd_reader_points.cc
Normal file
192
blender-5.2.0/source/blender/io/usd/intern/usd_reader_points.cc
Normal file
@@ -0,0 +1,192 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_points.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_pointcloud.hh"
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDPointsReader::create_object(Main *bmain)
|
||||
{
|
||||
PointCloud *pointcloud = BKE_pointcloud_add(bmain, name_.c_str());
|
||||
object_ = BKE_object_add_only_object(bmain, OB_POINTCLOUD, name_.c_str());
|
||||
object_->data = id_cast<ID *>(pointcloud);
|
||||
}
|
||||
|
||||
void USDPointsReader::read_object_data(Main *bmain, pxr::UsdTimeCode time)
|
||||
{
|
||||
const USDMeshReadParams params = create_mesh_read_params(time.GetValue(),
|
||||
import_params_.mesh_read_flag);
|
||||
|
||||
PointCloud *pointcloud = id_cast<PointCloud *>(object_->data);
|
||||
|
||||
bke::GeometrySet geometry_set = bke::GeometrySet::from_pointcloud(
|
||||
pointcloud, bke::GeometryOwnershipType::Editable);
|
||||
|
||||
read_geometry(geometry_set, params, nullptr);
|
||||
|
||||
PointCloud *read_pointcloud =
|
||||
geometry_set.get_component_for_write<bke::PointCloudComponent>().release();
|
||||
|
||||
if (read_pointcloud != pointcloud) {
|
||||
BKE_pointcloud_nomain_to_pointcloud(read_pointcloud, pointcloud);
|
||||
}
|
||||
|
||||
if (is_animated()) {
|
||||
/* If the point cloud has animated positions or attributes, we add the cache
|
||||
* modifier. */
|
||||
add_cache_modifier();
|
||||
}
|
||||
|
||||
/* Update the transform. */
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
void USDPointsReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
PointCloud *pointcloud = geometry_set.get_pointcloud_for_write();
|
||||
|
||||
/* Get the existing point cloud data. */
|
||||
pxr::VtVec3fArray usd_positions;
|
||||
points_prim_.GetPointsAttr().Get(&usd_positions, params.motion_sample_time);
|
||||
|
||||
if (pointcloud->totpoint != usd_positions.size()) {
|
||||
/* Size changed so we must reallocate. */
|
||||
pointcloud = BKE_pointcloud_new_nomain(usd_positions.size());
|
||||
}
|
||||
|
||||
/* Update point positions and radii */
|
||||
static_assert(sizeof(pxr::GfVec3f) == sizeof(float3));
|
||||
MutableSpan<float3> positions = pointcloud->positions_for_write();
|
||||
positions.copy_from(Span(usd_positions.cdata(), usd_positions.size()).cast<float3>());
|
||||
|
||||
pxr::VtFloatArray usd_widths;
|
||||
points_prim_.GetWidthsAttr().Get(&usd_widths, params.motion_sample_time);
|
||||
|
||||
if (!usd_widths.empty()) {
|
||||
Span<float> widths = Span(usd_widths.cdata(), usd_widths.size());
|
||||
|
||||
const pxr::TfToken widths_interp = points_prim_.GetWidthsInterpolation();
|
||||
if (widths_interp == pxr::UsdGeomTokens->constant) {
|
||||
set_single_value(pointcloud->attributes_for_write(),
|
||||
"radius",
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttrType::Float,
|
||||
bke::AttributeInitValue(widths[0] / 2.0f));
|
||||
}
|
||||
else {
|
||||
MutableSpan<float> radii = pointcloud->radius_for_write();
|
||||
for (const int i_point : IndexRange(std::min(radii.size(), widths.size()))) {
|
||||
radii[i_point] = widths[i_point] / 2.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Once Blender supports custom normals for points, we can consider reading in normals.
|
||||
* See UsdGeomPointBased::GetNormalsAttr */
|
||||
|
||||
/* Read in IDs, velocity, and generic data. */
|
||||
this->read_ids(pointcloud, params.motion_sample_time);
|
||||
this->read_velocities(pointcloud, params.motion_sample_time);
|
||||
this->read_custom_data(pointcloud, params.motion_sample_time);
|
||||
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
}
|
||||
|
||||
void USDPointsReader::read_ids(PointCloud *pointcloud, const pxr::UsdTimeCode time) const
|
||||
{
|
||||
pxr::VtInt64Array usd_ids;
|
||||
points_prim_.GetIdsAttr().Get(&usd_ids, time);
|
||||
|
||||
if (!usd_ids.empty()) {
|
||||
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
|
||||
bke::SpanAttributeWriter<int> ids = attributes.lookup_or_add_for_write_only_span<int>(
|
||||
"id", bke::AttrDomain::Point);
|
||||
|
||||
const Span<int64_t> usd_data(usd_ids.cdata(), usd_ids.size());
|
||||
for (const int i_point : IndexRange(std::min(ids.span.size(), usd_data.size()))) {
|
||||
/* Blender only supports int ID attributes so we have to narrow the value. */
|
||||
ids.span[i_point] = int(usd_ids[i_point]);
|
||||
}
|
||||
|
||||
ids.finish();
|
||||
}
|
||||
}
|
||||
|
||||
void USDPointsReader::read_velocities(PointCloud *pointcloud, const pxr::UsdTimeCode time) const
|
||||
{
|
||||
pxr::VtVec3fArray velocities;
|
||||
points_prim_.GetVelocitiesAttr().Get(&velocities, time);
|
||||
|
||||
if (!velocities.empty()) {
|
||||
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
|
||||
bke::SpanAttributeWriter<float3> velocity =
|
||||
attributes.lookup_or_add_for_write_only_span<float3>("velocity", bke::AttrDomain::Point);
|
||||
|
||||
Span<pxr::GfVec3f> usd_data(velocities.cdata(), velocities.size());
|
||||
velocity.span.copy_from(usd_data.cast<float3>());
|
||||
velocity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
void USDPointsReader::read_custom_data(PointCloud *pointcloud, const pxr::UsdTimeCode time) const
|
||||
{
|
||||
pxr::UsdGeomPrimvarsAPI pv_api(points_prim_);
|
||||
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
for (const pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
const pxr::SdfValueTypeName pv_type = pv.GetTypeName();
|
||||
if (!pv_type.IsArray()) {
|
||||
continue; /* Skip non-array primvar attributes. */
|
||||
}
|
||||
|
||||
const bke::AttrDomain domain = bke::AttrDomain::Point;
|
||||
const std::optional<bke::AttrType> type = convert_usd_type_to_blender(pv_type);
|
||||
if (!type.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
|
||||
copy_primvar_to_blender_attribute(pv, time, *type, domain, {}, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
bool USDPointsReader::is_animated() const
|
||||
{
|
||||
if (!points_prim_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_animated = false;
|
||||
|
||||
is_animated |= points_prim_.GetPointsAttr().ValueMightBeTimeVarying();
|
||||
|
||||
is_animated |= points_prim_.GetVelocitiesAttr().ValueMightBeTimeVarying();
|
||||
|
||||
is_animated |= points_prim_.GetWidthsAttr().ValueMightBeTimeVarying();
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI pv_api(points_prim_);
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
for (const pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
is_animated |= pv.ValueMightBeTimeVarying();
|
||||
}
|
||||
|
||||
return is_animated;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/points.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct PointCloud;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/*
|
||||
* Read UsdGeomPoints primitives as Blender point clouds.
|
||||
*/
|
||||
class USDPointsReader : public USDGeomReader {
|
||||
private:
|
||||
pxr::UsdGeomPoints points_prim_;
|
||||
|
||||
public:
|
||||
USDPointsReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDGeomReader(prim, import_params, settings), points_prim_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(points_prim_);
|
||||
}
|
||||
|
||||
/* Initial object creation. */
|
||||
void create_object(Main *bmain) override;
|
||||
|
||||
/* Initial point cloud data update. */
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
|
||||
/* Implement point cloud update. This may be called by the cache modifier
|
||||
* to update animated geometry. */
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
void read_ids(PointCloud *pointcloud, const pxr::UsdTimeCode time) const;
|
||||
void read_velocities(PointCloud *pointcloud, const pxr::UsdTimeCode time) const;
|
||||
void read_custom_data(PointCloud *pointcloud, const pxr::UsdTimeCode time) const;
|
||||
|
||||
/* Return true if the USD data may be time varying. */
|
||||
bool is_animated() const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
117
blender-5.2.0/source/blender/io/usd/intern/usd_reader_prim.cc
Normal file
117
blender-5.2.0/source/blender/io/usd/intern/usd_reader_prim.cc
Normal file
@@ -0,0 +1,117 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_reader_utils.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
ReportList *USDPrimReader::reports() const
|
||||
{
|
||||
return import_params_.worker_status ? import_params_.worker_status->reports : nullptr;
|
||||
}
|
||||
|
||||
void USDPrimReader::set_props(const bool merge_with_parent, const pxr::UsdTimeCode time)
|
||||
{
|
||||
if (!prim_ || !object_) {
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyImportMode property_import_mode = this->import_params_.property_import_mode;
|
||||
|
||||
if (property_import_mode == PropertyImportMode::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (merge_with_parent) {
|
||||
/* This object represents a parent Xform merged with its child prim.
|
||||
* Set the parent prim's custom properties on the Object ID. */
|
||||
if (const pxr::UsdPrim parent_prim = prim_.GetParent()) {
|
||||
set_id_props_from_prim(&object_->id, parent_prim, property_import_mode, time);
|
||||
}
|
||||
}
|
||||
if (!object_->data) {
|
||||
/* If the object has no data, set the prim's custom properties on the object.
|
||||
* This applies to Xforms that have been converted to Empty objects. */
|
||||
set_id_props_from_prim(&object_->id, prim_, property_import_mode, time);
|
||||
}
|
||||
|
||||
if (object_->data) {
|
||||
/* If the object has data, the data represents the USD prim, so set the prim's custom
|
||||
* properties on the data directly. */
|
||||
set_id_props_from_prim(object_->data, prim_, property_import_mode, time);
|
||||
}
|
||||
}
|
||||
|
||||
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: name_(prim.GetName().GetString()),
|
||||
object_(nullptr),
|
||||
prim_(prim),
|
||||
parent_reader_(nullptr),
|
||||
import_params_(import_params),
|
||||
settings_(&settings),
|
||||
refcount_(0),
|
||||
is_in_instancer_proto_(false)
|
||||
{
|
||||
}
|
||||
|
||||
USDPrimReader::~USDPrimReader() = default;
|
||||
|
||||
const pxr::UsdPrim &USDPrimReader::prim() const
|
||||
{
|
||||
return prim_;
|
||||
}
|
||||
|
||||
Object *USDPrimReader::object() const
|
||||
{
|
||||
return object_;
|
||||
}
|
||||
|
||||
void USDPrimReader::object(Object *ob)
|
||||
{
|
||||
object_ = ob;
|
||||
}
|
||||
|
||||
bool USDPrimReader::valid() const
|
||||
{
|
||||
return prim_.IsValid();
|
||||
}
|
||||
|
||||
int USDPrimReader::refcount() const
|
||||
{
|
||||
return refcount_;
|
||||
}
|
||||
|
||||
void USDPrimReader::incref()
|
||||
{
|
||||
refcount_++;
|
||||
}
|
||||
|
||||
void USDPrimReader::decref()
|
||||
{
|
||||
refcount_--;
|
||||
BLI_assert(refcount_ >= 0);
|
||||
}
|
||||
|
||||
bool USDPrimReader::is_in_proto() const
|
||||
{
|
||||
return prim_ && (prim_.IsInPrototype() || is_in_instancer_proto_);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
183
blender-5.2.0/source/blender/io/usd/intern/usd_reader_prim.hh
Normal file
183
blender-5.2.0/source/blender/io/usd/intern/usd_reader_prim.hh
Normal file
@@ -0,0 +1,183 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct CacheFile;
|
||||
struct Main;
|
||||
struct Material;
|
||||
struct Object;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct ImportSettings {
|
||||
bool blender_stage_version_prior_44 = false;
|
||||
bool do_convert_mat = false;
|
||||
float4x4 conversion_mat;
|
||||
|
||||
/* From MeshSeqCacheModifierData.read_flag */
|
||||
int read_flag = 0;
|
||||
|
||||
std::function<CacheFile *()> get_cache_file{};
|
||||
|
||||
/*
|
||||
* The fields below are mutable because they are used to keep track
|
||||
* of what the importer is doing. This is necessary even when all
|
||||
* the other import settings are to remain const.
|
||||
*/
|
||||
|
||||
/* Map a USD material prim path to a Blender material.
|
||||
* This map is updated by readers during stage traversal. */
|
||||
mutable Map<pxr::SdfPath, Material *> usd_path_to_mat{};
|
||||
/* Map a material name to Blender material.
|
||||
* This map is updated by readers during stage traversal. */
|
||||
mutable Map<std::string, Material *> mat_name_to_mat{};
|
||||
/* Map a USD material prim path to a Blender material to be
|
||||
* converted by invoking the 'on_material_import' USD hook.
|
||||
* This map is updated by readers during stage traversal. */
|
||||
mutable Map<pxr::SdfPath, Material *> usd_path_to_mat_for_hook{};
|
||||
/* Set of paths to USD material primitives that can be converted by the
|
||||
* 'on_material_import' USD hook. For efficiency this set should
|
||||
* be populated prior to stage traversal. */
|
||||
mutable Set<pxr::SdfPath> mat_import_hook_sources{};
|
||||
|
||||
/* We use the stage metersPerUnit to convert camera properties from USD scene units to the
|
||||
* correct millimeter scale that Blender uses for camera parameters. */
|
||||
double stage_meters_per_unit = 1.0;
|
||||
|
||||
pxr::SdfPath skip_prefix{};
|
||||
|
||||
/* Combined user-specified and unit conversion scales. */
|
||||
double scene_scale = 1.0;
|
||||
};
|
||||
|
||||
/* Most generic USD Reader. */
|
||||
|
||||
class USDPrimReader {
|
||||
|
||||
protected:
|
||||
StringRefNull name_;
|
||||
Object *object_;
|
||||
pxr::UsdPrim prim_;
|
||||
USDPrimReader *parent_reader_;
|
||||
const USDImportParams &import_params_;
|
||||
const ImportSettings *settings_;
|
||||
int refcount_;
|
||||
bool is_in_instancer_proto_;
|
||||
|
||||
public:
|
||||
USDPrimReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings);
|
||||
virtual ~USDPrimReader();
|
||||
|
||||
const pxr::UsdPrim &prim() const;
|
||||
|
||||
virtual bool valid() const;
|
||||
|
||||
virtual void create_object(Main *bmain) = 0;
|
||||
virtual void read_object_data(Main * /*bmain*/, pxr::UsdTimeCode /*time*/) {};
|
||||
|
||||
Object *object() const;
|
||||
void object(Object *ob);
|
||||
|
||||
USDPrimReader *parent() const
|
||||
{
|
||||
return parent_reader_;
|
||||
}
|
||||
void parent(USDPrimReader *parent)
|
||||
{
|
||||
parent_reader_ = parent;
|
||||
}
|
||||
|
||||
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
|
||||
ReportList *reports() const;
|
||||
|
||||
/* Since readers might be referenced through handles
|
||||
* maintained by modifiers and constraints, we provide
|
||||
* a reference count to facilitate managing the object
|
||||
* lifetime.
|
||||
* TODO(makowalski): investigate transitioning to using
|
||||
* smart pointers for readers, or, alternatively look into
|
||||
* making the lifetime management more robust, e.g., by
|
||||
* making the destructors protected and implementing deletion
|
||||
* in decref(), etc. */
|
||||
int refcount() const;
|
||||
void incref();
|
||||
void decref();
|
||||
|
||||
StringRefNull name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
pxr::SdfPath prim_path() const
|
||||
{
|
||||
return prim_.GetPrimPath();
|
||||
}
|
||||
|
||||
virtual pxr::SdfPath object_prim_path() const
|
||||
{
|
||||
return prim_path();
|
||||
}
|
||||
|
||||
virtual pxr::SdfPath data_prim_path() const
|
||||
{
|
||||
return prim_path();
|
||||
}
|
||||
|
||||
void set_is_in_instancer_proto(bool flag)
|
||||
{
|
||||
is_in_instancer_proto_ = flag;
|
||||
}
|
||||
|
||||
bool is_in_instancer_proto() const
|
||||
{
|
||||
return is_in_instancer_proto_;
|
||||
}
|
||||
|
||||
bool is_in_proto() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Convert custom attributes on the encapsulated USD prim (or on its parent)
|
||||
* to custom properties on the generated object and/or data. This function
|
||||
* assumes create_object() and read_object_data() have been called.
|
||||
*
|
||||
* If the generated object has instantiated data, it's assumed that the data
|
||||
* represents the USD prim, and the prim properties will be set on the data ID.
|
||||
* If the object data is null (which would be the case when a USD Xform is
|
||||
* converted to an Empty object), then the prim properties will be set on the
|
||||
* object ID. Finally, a true value for the 'merge_with_parent' argument indicates
|
||||
* that the object represents a USD Xform and its child prim that were merged
|
||||
* on import, and the properties of the prim's parent will be set on the object
|
||||
* ID.
|
||||
*
|
||||
* \param merge_with_parent: If true, set the properties of the prim's parent
|
||||
* on the object ID
|
||||
* \param time: The time code for sampling the USD attributes.
|
||||
*/
|
||||
void set_props(bool merge_with_parent = false,
|
||||
pxr::UsdTimeCode time = pxr::UsdTimeCode::Default());
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
337
blender-5.2.0/source/blender/io/usd/intern/usd_reader_shape.cc
Normal file
337
blender-5.2.0/source/blender/io/usd/intern/usd_reader_shape.cc
Normal file
@@ -0,0 +1,337 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Nvidia. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_mesh_utils.hh"
|
||||
#include "usd_reader_shape.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/capsule.h>
|
||||
#include <pxr/usd/usdGeom/capsule_1.h>
|
||||
#include <pxr/usd/usdGeom/cone.h>
|
||||
#include <pxr/usd/usdGeom/cube.h>
|
||||
#include <pxr/usd/usdGeom/cylinder.h>
|
||||
#include <pxr/usd/usdGeom/cylinder_1.h>
|
||||
#include <pxr/usd/usdGeom/plane.h>
|
||||
#include <pxr/usd/usdGeom/sphere.h>
|
||||
#include <pxr/usdImaging/usdImaging/capsuleAdapter.h>
|
||||
#include <pxr/usdImaging/usdImaging/coneAdapter.h>
|
||||
#include <pxr/usdImaging/usdImaging/cubeAdapter.h>
|
||||
#include <pxr/usdImaging/usdImaging/cylinderAdapter.h>
|
||||
#include <pxr/usdImaging/usdImaging/planeAdapter.h>
|
||||
#include <pxr/usdImaging/usdImaging/sphereAdapter.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDShapeReader::USDShapeReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDGeomReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
void USDShapeReader::create_object(Main *bmain)
|
||||
{
|
||||
Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
|
||||
object_ = BKE_object_add_only_object(bmain, OB_MESH, name_.c_str());
|
||||
object_->data = id_cast<ID *>(mesh);
|
||||
}
|
||||
|
||||
void USDShapeReader::read_object_data(Main *bmain, pxr::UsdTimeCode time)
|
||||
{
|
||||
const USDMeshReadParams params = create_mesh_read_params(time.GetValue(),
|
||||
import_params_.mesh_read_flag);
|
||||
Mesh *mesh = id_cast<Mesh *>(object_->data);
|
||||
Mesh *read_mesh = this->read_mesh(mesh, params, nullptr);
|
||||
|
||||
if (read_mesh != mesh) {
|
||||
BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
|
||||
if (is_time_varying()) {
|
||||
USDGeomReader::add_cache_modifier();
|
||||
}
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
template<typename Adapter>
|
||||
void USDShapeReader::read_values(const pxr::UsdTimeCode time,
|
||||
pxr::VtVec3fArray &positions,
|
||||
pxr::VtIntArray &face_indices,
|
||||
pxr::VtIntArray &face_counts) const
|
||||
{
|
||||
Adapter adapter;
|
||||
pxr::VtValue points_val = adapter.GetPoints(prim_, time);
|
||||
|
||||
if (points_val.IsHolding<pxr::VtVec3fArray>()) {
|
||||
positions = points_val.UncheckedGet<pxr::VtVec3fArray>();
|
||||
}
|
||||
|
||||
pxr::VtValue topology_val = adapter.GetTopology(prim_, pxr::SdfPath(), time);
|
||||
|
||||
if (topology_val.IsHolding<pxr::HdMeshTopology>()) {
|
||||
const pxr::HdMeshTopology &topology = topology_val.UncheckedGet<pxr::HdMeshTopology>();
|
||||
face_counts = topology.GetFaceVertexCounts();
|
||||
face_indices = topology.GetFaceVertexIndices();
|
||||
}
|
||||
}
|
||||
|
||||
bool USDShapeReader::read_mesh_values(pxr::UsdTimeCode time,
|
||||
pxr::VtVec3fArray &positions,
|
||||
pxr::VtIntArray &face_indices,
|
||||
pxr::VtIntArray &face_counts) const
|
||||
{
|
||||
if (prim_.IsA<pxr::UsdGeomCapsule>() || prim_.IsA<pxr::UsdGeomCapsule_1>()) {
|
||||
read_values<pxr::UsdImagingCapsuleAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCylinder>() || prim_.IsA<pxr::UsdGeomCylinder_1>()) {
|
||||
read_values<pxr::UsdImagingCylinderAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCone>()) {
|
||||
read_values<pxr::UsdImagingConeAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCube>()) {
|
||||
read_values<pxr::UsdImagingCubeAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomSphere>()) {
|
||||
read_values<pxr::UsdImagingSphereAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomPlane>()) {
|
||||
read_values<pxr::UsdImagingPlaneAdapter>(time, positions, face_indices, face_counts);
|
||||
return true;
|
||||
}
|
||||
|
||||
BKE_reportf(reports(),
|
||||
RPT_ERROR,
|
||||
"Unhandled Gprim type: %s (%s)",
|
||||
prim_.GetTypeName().GetText(),
|
||||
prim_.GetPath().GetText());
|
||||
return false;
|
||||
}
|
||||
|
||||
Mesh *USDShapeReader::read_mesh(Mesh *existing_mesh,
|
||||
const USDMeshReadParams params,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
if (!prim_) {
|
||||
return existing_mesh;
|
||||
}
|
||||
|
||||
pxr::VtVec3fArray usd_positions;
|
||||
pxr::VtIntArray usd_face_indices;
|
||||
pxr::VtIntArray usd_face_counts;
|
||||
if (!read_mesh_values(
|
||||
params.motion_sample_time, usd_positions, usd_face_indices, usd_face_counts))
|
||||
{
|
||||
return existing_mesh;
|
||||
}
|
||||
|
||||
/* Build or update the existing mesh. */
|
||||
Mesh *active_mesh = mesh_from_prim(
|
||||
existing_mesh, params, usd_positions, usd_face_indices, usd_face_counts);
|
||||
|
||||
return active_mesh;
|
||||
}
|
||||
|
||||
void USDShapeReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
USDMeshReadParams params,
|
||||
const char **r_err_str)
|
||||
{
|
||||
Mesh *existing_mesh = geometry_set.get_mesh_for_write();
|
||||
Mesh *new_mesh = read_mesh(existing_mesh, params, r_err_str);
|
||||
|
||||
if (new_mesh != existing_mesh) {
|
||||
geometry_set.replace_mesh(new_mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void USDShapeReader::apply_primvars_to_mesh(Mesh *mesh, const pxr::UsdTimeCode time) const
|
||||
{
|
||||
/* TODO: also handle the displayOpacity primvar. */
|
||||
if (!mesh || !prim_) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(prim_);
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
|
||||
pxr::TfToken active_color_name;
|
||||
|
||||
for (const pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
const pxr::SdfValueTypeName pv_type = pv.GetTypeName();
|
||||
if (!pv_type.IsArray()) {
|
||||
continue; /* Skip non-array primvar attributes. */
|
||||
}
|
||||
|
||||
const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(pv.GetPrimvarName());
|
||||
|
||||
/* Skip reading primvars that have been read before and are not time varying. */
|
||||
if (primvar_time_varying_map_.contains(name) && !primvar_time_varying_map_.lookup(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::optional<bke::AttrType> type = convert_usd_type_to_blender(pv_type);
|
||||
if (type == bke::AttrType::ColorFloat) {
|
||||
/* Set the active color name to 'displayColor', if a color primvar
|
||||
* with this name exists. Otherwise, use the name of the first
|
||||
* color primvar we find for the active color. */
|
||||
if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
|
||||
active_color_name = name;
|
||||
}
|
||||
}
|
||||
|
||||
read_generic_mesh_primvar(mesh, pv, time, false);
|
||||
|
||||
/* Record whether the primvar attribute might be time varying. */
|
||||
if (!primvar_time_varying_map_.contains(name)) {
|
||||
primvar_time_varying_map_.add(name, pv.ValueMightBeTimeVarying());
|
||||
}
|
||||
}
|
||||
|
||||
if (!active_color_name.IsEmpty()) {
|
||||
BKE_id_attributes_default_color_set(&mesh->id, active_color_name.GetText());
|
||||
BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh,
|
||||
const USDMeshReadParams params,
|
||||
pxr::VtVec3fArray &usd_positions,
|
||||
pxr::VtIntArray &usd_face_indices,
|
||||
pxr::VtIntArray &usd_face_counts) const
|
||||
{
|
||||
Span<int> face_indices = Span(usd_face_indices.cdata(), usd_face_indices.size());
|
||||
Span<int> face_counts = Span(usd_face_counts.cdata(), usd_face_counts.size());
|
||||
Span<float3> positions = Span(usd_positions.cdata(), usd_positions.size()).cast<float3>();
|
||||
|
||||
const bool poly_counts_match = existing_mesh ? face_counts.size() == existing_mesh->faces_num :
|
||||
false;
|
||||
const bool position_counts_match = existing_mesh ? positions.size() == existing_mesh->verts_num :
|
||||
false;
|
||||
|
||||
Mesh *active_mesh = nullptr;
|
||||
if (!position_counts_match || !poly_counts_match) {
|
||||
active_mesh = BKE_mesh_new_nomain_from_template(
|
||||
existing_mesh, positions.size(), 0, face_counts.size(), face_indices.size());
|
||||
}
|
||||
else {
|
||||
active_mesh = existing_mesh;
|
||||
}
|
||||
|
||||
MutableSpan<float3> vert_positions = active_mesh->vert_positions_for_write();
|
||||
vert_positions.copy_from(positions);
|
||||
|
||||
MutableSpan<int> face_offsets = active_mesh->face_offsets_for_write();
|
||||
for (const int i : IndexRange(active_mesh->faces_num)) {
|
||||
face_offsets[i] = face_counts[i];
|
||||
}
|
||||
offset_indices::accumulate_counts_to_offsets(face_offsets);
|
||||
|
||||
MutableSpan<int> corner_verts = active_mesh->corner_verts_for_write();
|
||||
for (const int i : corner_verts.index_range()) {
|
||||
corner_verts[i] = face_indices[i];
|
||||
}
|
||||
|
||||
bke::mesh_calc_edges(*active_mesh, false, false);
|
||||
|
||||
/* Don't smooth-shade cubes; we're not worrying about sharpness for Gprims. */
|
||||
bke::mesh_smooth_set(*active_mesh, !prim_.IsA<pxr::UsdGeomCube>());
|
||||
|
||||
if (params.read_flags & MOD_MESHSEQ_READ_COLOR) {
|
||||
apply_primvars_to_mesh(active_mesh, params.motion_sample_time);
|
||||
}
|
||||
|
||||
return active_mesh;
|
||||
}
|
||||
|
||||
bool USDShapeReader::is_time_varying()
|
||||
{
|
||||
for (const bool animating_flag : primvar_time_varying_map_.values()) {
|
||||
if (animating_flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCapsule>()) {
|
||||
pxr::UsdGeomCapsule geom(prim_);
|
||||
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCapsule_1>()) {
|
||||
pxr::UsdGeomCapsule_1 geom(prim_);
|
||||
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusTopAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusBottomAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCylinder>()) {
|
||||
pxr::UsdGeomCylinder geom(prim_);
|
||||
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCylinder_1>()) {
|
||||
pxr::UsdGeomCylinder_1 geom(prim_);
|
||||
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusTopAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusBottomAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCone>()) {
|
||||
pxr::UsdGeomCone geom(prim_);
|
||||
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomCube>()) {
|
||||
pxr::UsdGeomCube geom(prim_);
|
||||
return geom.GetSizeAttr().ValueMightBeTimeVarying();
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomSphere>()) {
|
||||
pxr::UsdGeomSphere geom(prim_);
|
||||
return geom.GetRadiusAttr().ValueMightBeTimeVarying();
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomPlane>()) {
|
||||
pxr::UsdGeomPlane geom(prim_);
|
||||
return (geom.GetWidthAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetLengthAttr().ValueMightBeTimeVarying() ||
|
||||
geom.GetAxisAttr().ValueMightBeTimeVarying());
|
||||
}
|
||||
|
||||
BKE_reportf(reports(),
|
||||
RPT_ERROR,
|
||||
"Unhandled Gprim type: %s (%s)",
|
||||
prim_.GetTypeName().GetText(),
|
||||
prim_.GetPath().GetText());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,78 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Nvidia. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_api_modifier.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
#include "usd_reader_geom.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/*
|
||||
* Read USDGeom primitive shapes as Blender Meshes. This class uses the same adapter functions
|
||||
* as the GL viewport to generate geometry for each of the supported types.
|
||||
*/
|
||||
class USDShapeReader : public USDGeomReader {
|
||||
/* A cache to record whether a given primvar is time-varying, so that static primvars are not
|
||||
* read more than once when the mesh is evaluated for animation by the cache file modifier.
|
||||
* The map is mutable so that it can be updated in const functions. */
|
||||
mutable Map<const pxr::TfToken, bool> primvar_time_varying_map_;
|
||||
|
||||
private:
|
||||
/* Template required to read mesh information out of Shape prims,
|
||||
* as each prim type has a separate subclass. */
|
||||
template<typename Adapter>
|
||||
void read_values(pxr::UsdTimeCode time,
|
||||
pxr::VtVec3fArray &positions,
|
||||
pxr::VtIntArray &face_indices,
|
||||
pxr::VtIntArray &face_counts) const;
|
||||
|
||||
/* Wrapper for the templated method read_values, calling the correct template
|
||||
* instantiation based on the introspected prim type. */
|
||||
bool read_mesh_values(pxr::UsdTimeCode time,
|
||||
pxr::VtVec3fArray &positions,
|
||||
pxr::VtIntArray &face_indices,
|
||||
pxr::VtIntArray &face_counts) const;
|
||||
|
||||
void apply_primvars_to_mesh(Mesh *mesh, pxr::UsdTimeCode time) const;
|
||||
|
||||
/* Read the pxr:UsdGeomMesh values and convert them to a Blender Mesh,
|
||||
* also returning face_indices and counts for further loop processing. */
|
||||
Mesh *mesh_from_prim(Mesh *existing_mesh,
|
||||
USDMeshReadParams params,
|
||||
pxr::VtVec3fArray &positions,
|
||||
pxr::VtIntArray &face_indices,
|
||||
pxr::VtIntArray &face_counts) const;
|
||||
|
||||
Mesh *read_mesh(Mesh *existing_mesh, USDMeshReadParams params, const char ** /*r_err_str*/);
|
||||
|
||||
public:
|
||||
USDShapeReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings);
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
void read_geometry(bke::GeometrySet & /*geometry_set*/,
|
||||
USDMeshReadParams /*params*/,
|
||||
const char ** /*r_err_str*/) override;
|
||||
|
||||
/* Returns the generated mesh might be affected by time-varying attributes.
|
||||
* This assumes mesh_from_prim() has been called. */
|
||||
bool is_time_varying();
|
||||
|
||||
bool topology_changed(const Mesh * /*existing_mesh*/, pxr::UsdTimeCode /*time*/) override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,35 @@
|
||||
/* SPDX-FileCopyrightText: 2021 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_skeleton.hh"
|
||||
#include "usd_skel_convert.hh"
|
||||
|
||||
#include "BKE_armature.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_armature_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDSkeletonReader::create_object(Main *bmain)
|
||||
{
|
||||
bArmature *arm = BKE_armature_add(bmain, name_.c_str());
|
||||
|
||||
object_ = BKE_object_add_only_object(bmain, OB_ARMATURE, name_.c_str());
|
||||
object_->data = id_cast<ID *>(arm);
|
||||
}
|
||||
|
||||
void USDSkeletonReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
||||
{
|
||||
if (!object_ || !object_->data) {
|
||||
return;
|
||||
}
|
||||
|
||||
import_skeleton(bmain, object_, skel_, reports());
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
#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
|
||||
{
|
||||
return bool(skel_);
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
999
blender-5.2.0/source/blender/io/usd/intern/usd_reader_stage.cc
Normal file
999
blender-5.2.0/source/blender/io/usd/intern/usd_reader_stage.cc
Normal file
@@ -0,0 +1,999 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_stage.hh"
|
||||
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_reader_camera.hh"
|
||||
#include "usd_reader_curve.hh"
|
||||
#include "usd_reader_instance.hh"
|
||||
#include "usd_reader_light.hh"
|
||||
#include "usd_reader_material.hh"
|
||||
#include "usd_reader_mesh.hh"
|
||||
#include "usd_reader_nurbs.hh"
|
||||
#include "usd_reader_pointinstancer.hh"
|
||||
#include "usd_reader_points.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
#include "usd_reader_shape.hh"
|
||||
#include "usd_reader_skeleton.hh"
|
||||
#include "usd_reader_volume.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
#include <pxr/usd/usdGeom/camera.h>
|
||||
#include <pxr/usd/usdGeom/capsule.h>
|
||||
#include <pxr/usd/usdGeom/capsule_1.h>
|
||||
#include <pxr/usd/usdGeom/cone.h>
|
||||
#include <pxr/usd/usdGeom/cube.h>
|
||||
#include <pxr/usd/usdGeom/cylinder.h>
|
||||
#include <pxr/usd/usdGeom/cylinder_1.h>
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/nurbsCurves.h>
|
||||
#include <pxr/usd/usdGeom/plane.h>
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
#include <pxr/usd/usdGeom/points.h>
|
||||
#include <pxr/usd/usdGeom/scope.h>
|
||||
#include <pxr/usd/usdGeom/sphere.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdGeom/xform.h>
|
||||
#include <pxr/usd/usdLux/boundableLightBase.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdLux/domeLight_1.h>
|
||||
#include <pxr/usd/usdLux/nonboundableLightBase.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_euler_types.hh"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_sort.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_collection.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
static void decref(USDPrimReader *reader)
|
||||
{
|
||||
reader->decref();
|
||||
|
||||
if (reader->refcount() == 0) {
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection with the given parent and name.
|
||||
*/
|
||||
static Collection *create_collection(Main *bmain, Collection *parent, const char *name)
|
||||
{
|
||||
if (!bmain) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return BKE_collection_add(bmain, parent, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the instance collection on the given instance reader.
|
||||
* The collection is assigned from the given map based on
|
||||
* the prototype prim path.
|
||||
*/
|
||||
static void set_instance_collection(USDInstanceReader *instance_reader,
|
||||
const Map<pxr::SdfPath, Collection *> &proto_collection_map)
|
||||
{
|
||||
if (!instance_reader) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::SdfPath proto_path = instance_reader->proto_path();
|
||||
|
||||
Collection *collection = proto_collection_map.lookup_default(proto_path, nullptr);
|
||||
if (collection != nullptr) {
|
||||
instance_reader->set_instance_collection(collection);
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't find prototype collection for %s",
|
||||
instance_reader->prim_path().GetAsString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the given import settings with the global rotation matrix to orient
|
||||
* imported objects with Z-up, if necessary */
|
||||
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings &settings)
|
||||
{
|
||||
if (!stage || pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.do_convert_mat = true;
|
||||
|
||||
/* Rotate 90 degrees about the X-axis. */
|
||||
settings.conversion_mat = math::from_rotation<float4x4>(math::EulerXYZ(M_PI_2, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest level of Blender generated roots
|
||||
* so that round tripping an export can be more invisible
|
||||
*/
|
||||
static void find_prefix_to_skip(pxr::UsdStageRefPtr stage, ImportSettings &settings)
|
||||
{
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::TfToken generated_key("Blender:generated");
|
||||
pxr::SdfPath path("/");
|
||||
auto prim = stage->GetPseudoRoot();
|
||||
while (true) {
|
||||
|
||||
uint32_t child_count = 0;
|
||||
for (auto child : prim.GetChildren()) {
|
||||
if (child_count == 0) {
|
||||
prim = child.GetPrim();
|
||||
}
|
||||
++child_count;
|
||||
}
|
||||
|
||||
if (child_count != 1) {
|
||||
/* Our blender write out only supports a single root chain,
|
||||
* so whenever we encounter more than one child, we should
|
||||
* early exit */
|
||||
break;
|
||||
}
|
||||
|
||||
/* We only care about prims that have the key and the value doesn't matter */
|
||||
if (!prim.HasCustomDataKey(generated_key)) {
|
||||
break;
|
||||
}
|
||||
path = path.AppendChild(prim.GetName());
|
||||
}
|
||||
|
||||
/* Treat the root as empty */
|
||||
if (path == pxr::SdfPath("/")) {
|
||||
path = pxr::SdfPath();
|
||||
}
|
||||
|
||||
settings.skip_prefix = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compatibility flags if the Stage was written by Blender.
|
||||
*/
|
||||
static void determine_blender_compat(pxr::UsdStageRefPtr stage, ImportSettings &settings)
|
||||
{
|
||||
const std::string doc = stage->GetRootLayer()->GetDocumentation();
|
||||
|
||||
/* Was the incoming Stage written by Blender? If so, set some broad compatibility flags. */
|
||||
if (doc.find("Blender v", 0) == 0) {
|
||||
/* Set flag if the Blender Stage was from before version 4.4. */
|
||||
settings.blender_stage_version_prior_44 = doc < "Blender v4.4";
|
||||
}
|
||||
}
|
||||
|
||||
USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
|
||||
const USDImportParams ¶ms,
|
||||
const std::function<CacheFile *()> &get_cache_file_fn)
|
||||
: stage_(stage), params_(params)
|
||||
{
|
||||
determine_blender_compat(stage_, settings_);
|
||||
convert_to_z_up(stage_, settings_);
|
||||
find_prefix_to_skip(stage_, settings_);
|
||||
settings_.get_cache_file = get_cache_file_fn;
|
||||
settings_.stage_meters_per_unit = pxr::UsdGeomGetStageMetersPerUnit(stage);
|
||||
settings_.scene_scale = params.scale;
|
||||
if (params.apply_unit_conversion_scale) {
|
||||
settings_.scene_scale *= settings_.stage_meters_per_unit;
|
||||
}
|
||||
}
|
||||
|
||||
USDStageReader::~USDStageReader()
|
||||
{
|
||||
clear_readers();
|
||||
}
|
||||
|
||||
bool USDStageReader::valid() const
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
|
||||
bool USDStageReader::is_primitive_prim(const pxr::UsdPrim &prim) const
|
||||
{
|
||||
return (prim.IsA<pxr::UsdGeomCapsule>() || prim.IsA<pxr::UsdGeomCapsule_1>() ||
|
||||
prim.IsA<pxr::UsdGeomCylinder>() || prim.IsA<pxr::UsdGeomCylinder_1>() ||
|
||||
prim.IsA<pxr::UsdGeomCone>() || prim.IsA<pxr::UsdGeomCube>() ||
|
||||
prim.IsA<pxr::UsdGeomSphere>() || prim.IsA<pxr::UsdGeomPlane>());
|
||||
}
|
||||
|
||||
ReportList *USDStageReader::reports() const
|
||||
{
|
||||
return params_.worker_status ? params_.worker_status->reports : nullptr;
|
||||
}
|
||||
|
||||
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim)
|
||||
{
|
||||
if (params_.support_scene_instancing && prim.IsInstance()) {
|
||||
return new USDInstanceReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_shapes && is_primitive_prim(prim)) {
|
||||
return new USDShapeReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
return new USDPointInstancerReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
|
||||
return new USDCameraReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_curves && prim.IsA<pxr::UsdGeomBasisCurves>()) {
|
||||
return new USDBasisCurvesReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_curves && prim.IsA<pxr::UsdGeomNurbsCurves>()) {
|
||||
return new USDNurbsReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
|
||||
return new USDMeshReader(prim, params_, settings_);
|
||||
}
|
||||
if (params_.import_lights &&
|
||||
(prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()))
|
||||
{
|
||||
/* Dome lights are handled elsewhere. */
|
||||
return nullptr;
|
||||
}
|
||||
if (params_.import_lights &&
|
||||
(prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()))
|
||||
{
|
||||
return new USDLightReader(prim, params_, settings_);
|
||||
}
|
||||
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 (params_.import_points && prim.IsA<pxr::UsdGeomPoints>()) {
|
||||
return new USDPointsReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||
return new USDXformReader(prim, params_, settings_);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
|
||||
{
|
||||
if (params_.support_scene_instancing && prim.IsInstance()) {
|
||||
return new USDInstanceReader(prim, params_, settings_);
|
||||
}
|
||||
if (is_primitive_prim(prim)) {
|
||||
return new USDShapeReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomCamera>()) {
|
||||
return new USDCameraReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomBasisCurves>()) {
|
||||
return new USDBasisCurvesReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomNurbsCurves>()) {
|
||||
return new USDNurbsReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomMesh>()) {
|
||||
return new USDMeshReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()) {
|
||||
/* We don't handle dome lights. */
|
||||
return nullptr;
|
||||
}
|
||||
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
|
||||
return new USDLightReader(prim, params_, settings_);
|
||||
}
|
||||
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::UsdGeomPoints>()) {
|
||||
return new USDPointsReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomPointInstancer>()) {
|
||||
return new USDPointInstancerReader(prim, params_, settings_);
|
||||
}
|
||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||
return new USDXformReader(prim, params_, settings_);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageable) const
|
||||
{
|
||||
if (!params_.import_visible_only) {
|
||||
/* Invisible prims are allowed. */
|
||||
return true;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute visibility_attr = imageable.GetVisibilityAttr();
|
||||
|
||||
if (!visibility_attr) {
|
||||
/* No visibility attribute, so allow. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Include if the prim has an animating visibility attribute or is not invisible. */
|
||||
|
||||
if (visibility_attr.ValueMightBeTimeVarying()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pxr::TfToken visibility;
|
||||
visibility_attr.Get(&visibility);
|
||||
return visibility != pxr::UsdGeomTokens->invisible;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute purpose_attr = imageable.GetPurposeAttr();
|
||||
|
||||
if (!purpose_attr) {
|
||||
/* No purpose attribute, so trivially include the prim. */
|
||||
return true;
|
||||
}
|
||||
|
||||
pxr::TfToken purpose;
|
||||
purpose_attr.Get(&purpose);
|
||||
|
||||
if (purpose == pxr::UsdGeomTokens->guide) {
|
||||
return params_.import_guide;
|
||||
}
|
||||
if (purpose == pxr::UsdGeomTokens->proxy) {
|
||||
return params_.import_proxy;
|
||||
}
|
||||
if (purpose == pxr::UsdGeomTokens->render) {
|
||||
return params_.import_render;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool USDStageReader::merge_with_parent(USDPrimReader *reader) const
|
||||
{
|
||||
/* Don't merge if the param is set to false */
|
||||
if (!params_.merge_parent_xform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
|
||||
|
||||
if (!xform_reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if the Xform reader is already merged. */
|
||||
if (xform_reader->use_parent_xform()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only merge if the parent is an Xform. */
|
||||
if (!xform_reader->prim().GetParent().IsA<pxr::UsdGeomXform>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't merge Xform and Scope prims. */
|
||||
if (xform_reader->prim().IsA<pxr::UsdGeomXform>() ||
|
||||
xform_reader->prim().IsA<pxr::UsdGeomScope>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't merge if the prim has authored transform ops. */
|
||||
if (xform_reader->prim_has_xform_ops()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Flag the Xform reader as merged. */
|
||||
xform_reader->set_use_parent_xform(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
USDPrimReader *USDStageReader::collect_readers(const pxr::UsdPrim &prim,
|
||||
const UsdPathSet &pruned_prims,
|
||||
const bool defined_prims_only,
|
||||
Vector<USDPrimReader *> &r_readers)
|
||||
{
|
||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||
pxr::UsdGeomImageable imageable(prim);
|
||||
|
||||
if (!include_by_purpose(imageable)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!include_by_visibility(imageable)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (prim.IsA<pxr::UsdLuxDomeLight>() || prim.IsA<pxr::UsdLuxDomeLight_1>()) {
|
||||
USDDomeLightReader *reader = new USDDomeLightReader(prim, params_, settings_);
|
||||
reader->incref();
|
||||
dome_light_readers_.append(reader);
|
||||
}
|
||||
|
||||
pxr::Usd_PrimFlagsConjunction filter_flags = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
|
||||
!pxr::UsdPrimIsAbstract;
|
||||
|
||||
if (defined_prims_only) {
|
||||
filter_flags &= pxr::UsdPrimIsDefined;
|
||||
}
|
||||
|
||||
pxr::Usd_PrimFlagsPredicate filter_predicate(filter_flags);
|
||||
if (!params_.support_scene_instancing) {
|
||||
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
|
||||
}
|
||||
|
||||
Vector<USDPrimReader *> child_readers;
|
||||
|
||||
pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_predicate);
|
||||
|
||||
for (const auto &child_prim : children) {
|
||||
if (pruned_prims.contains(child_prim.GetPath())) {
|
||||
continue;
|
||||
}
|
||||
if (USDPrimReader *child_reader = collect_readers(
|
||||
child_prim, pruned_prims, defined_prims_only, r_readers))
|
||||
{
|
||||
child_readers.append(child_reader);
|
||||
}
|
||||
}
|
||||
|
||||
if (prim.IsPseudoRoot()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If we find prims that have been auto generated by Blender, we skip them on import
|
||||
* so that the imported scene can closely match the exported scene */
|
||||
if (!settings_.skip_prefix.IsEmpty()) {
|
||||
if (settings_.skip_prefix.HasPrefix(prim.GetPath())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we can merge an Xform with its child prim. */
|
||||
if (child_readers.size() == 1) {
|
||||
|
||||
USDPrimReader *child_reader = child_readers.first();
|
||||
|
||||
if (merge_with_parent(child_reader)) {
|
||||
return child_reader;
|
||||
}
|
||||
}
|
||||
|
||||
if (prim.IsA<pxr::UsdShadeMaterial>()) {
|
||||
/* Record material path for later processing, if needed,
|
||||
* e.g., when importing all materials. */
|
||||
material_paths_.append(prim.GetPath());
|
||||
|
||||
/* We don't create readers for materials, so return early. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USDPrimReader *reader = create_reader_if_allowed(prim);
|
||||
|
||||
if (!reader) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!reader->valid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
r_readers.append(reader);
|
||||
reader->incref();
|
||||
|
||||
/* Set each child reader's parent. */
|
||||
for (USDPrimReader *child_reader : child_readers) {
|
||||
child_reader->parent(reader);
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
void USDStageReader::collect_readers()
|
||||
{
|
||||
if (!valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
clear_readers();
|
||||
|
||||
/* Identify paths to point instancer prototypes, as these will be converted
|
||||
* in a separate pass over the stage. */
|
||||
UsdPathSet instancer_proto_paths = collect_point_instancer_proto_paths();
|
||||
|
||||
/* Iterate through the stage. */
|
||||
pxr::UsdPrim root = stage_->GetPseudoRoot();
|
||||
|
||||
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
|
||||
|
||||
/* Create readers, skipping over prototype prims in this pass. */
|
||||
collect_readers(root, instancer_proto_paths, params_.import_defined_only, readers_);
|
||||
|
||||
if (params_.support_scene_instancing) {
|
||||
/* Collect the scene-graph instance prototypes. */
|
||||
std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
|
||||
|
||||
for (const pxr::UsdPrim &proto_prim : protos) {
|
||||
Vector<USDPrimReader *> proto_readers;
|
||||
collect_readers(proto_prim, instancer_proto_paths, true, proto_readers);
|
||||
proto_readers_.add(proto_prim.GetPath(), proto_readers);
|
||||
|
||||
for (USDPrimReader *reader : proto_readers) {
|
||||
readers_.append(reader);
|
||||
reader->incref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!instancer_proto_paths.is_empty()) {
|
||||
create_point_instancer_proto_readers(instancer_proto_paths);
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::process_armature_modifiers() const
|
||||
{
|
||||
/* Iterate over the skeleton readers to create the
|
||||
* armature object map, which maps a USD skeleton prim
|
||||
* path to the corresponding armature object. */
|
||||
Map<pxr::SdfPath, Object *> usd_path_to_armature;
|
||||
for (const USDPrimReader *reader : readers_) {
|
||||
if (dynamic_cast<const USDSkeletonReader *>(reader) && reader->object()) {
|
||||
usd_path_to_armature.add(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. */
|
||||
pxr::SdfPath skel_path = mesh_reader->get_skeleton_path();
|
||||
Object *object = usd_path_to_armature.lookup_default(skel_path, nullptr);
|
||||
if (object == nullptr) {
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"%s: Couldn't find armature object corresponding to USD skeleton %s",
|
||||
__func__,
|
||||
skel_path.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
amd->object = object;
|
||||
|
||||
/* Per the UsdSkel spec, a skinned mesh's own and ancestor xformOps below the SkelRoot do not
|
||||
* position the skinned result: the geometry is placed by the bound Skeleton's world transform,
|
||||
* with `primvars:skel:geomBindTransform` aligning it (skinned mesh world =
|
||||
* skeleton world * geomBindTransform). Blender's armature deform reproduces this when the mesh
|
||||
* object is a child of the armature with its local transform equal to the geomBindTransform.
|
||||
* USDMeshReader::get_local_usd_xform already set the mesh local transform to the
|
||||
* geomBindTransform (or the identity when no usable geomBindTransform is authored), so
|
||||
* re-parent the mesh to the armature (with an identity parent-inverse) here. Otherwise the
|
||||
* mesh keeps the transform of its USD-hierarchy parent and ends up mis-scaled/rotated whenever
|
||||
* that differs from the skeleton's transform. */
|
||||
Object *mesh_object = reader->object();
|
||||
|
||||
/* Guard against a parent cycle in the unusual case where the bound Skeleton prim is a USD
|
||||
* descendant of the skinned mesh, in which case the armature is already parented to the mesh.
|
||||
*/
|
||||
if (mesh_object == object || BKE_object_parent_loop_check(object, mesh_object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mesh_object->parent = object;
|
||||
unit_m4(mesh_object->parentinv);
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::import_all_materials(Main *bmain)
|
||||
{
|
||||
BLI_assert(valid());
|
||||
|
||||
/* Build the material name map if it's not built yet. */
|
||||
if (settings_.mat_name_to_mat.is_empty()) {
|
||||
build_material_map(bmain, settings_.mat_name_to_mat);
|
||||
}
|
||||
|
||||
USDMaterialReader mtl_reader(params_, *bmain);
|
||||
for (const pxr::SdfPath &mtl_path : material_paths_) {
|
||||
pxr::UsdPrim prim = stage_->GetPrimAtPath(mtl_path);
|
||||
|
||||
pxr::UsdShadeMaterial usd_mtl(prim);
|
||||
if (!usd_mtl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (io::usd::find_existing_material(
|
||||
prim.GetPath(), params_, settings_.mat_name_to_mat, settings_.usd_path_to_mat))
|
||||
{
|
||||
/* The material already exists. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Can the material be handled by an import hook? */
|
||||
const bool have_import_hook = settings_.mat_import_hook_sources.contains(mtl_path);
|
||||
|
||||
/* Add the Blender material. If we have an import hook which can handle this material
|
||||
* we don't import USD Preview Surface shaders. */
|
||||
Material *new_mtl = mtl_reader.add_material(usd_mtl, !have_import_hook);
|
||||
BLI_assert_msg(new_mtl, "Failed to create material");
|
||||
|
||||
settings_.mat_name_to_mat.add_new(new_mtl->id.name + 2, new_mtl);
|
||||
|
||||
if (params_.mtl_name_collision_mode == MtlNameCollisionMode::MakeUnique) {
|
||||
/* Record the Blender material we created for the USD material with the given path.
|
||||
* This is to prevent importing the material again when assigning materials to objects
|
||||
* elsewhere in the code. */
|
||||
settings_.usd_path_to_mat.add_new(mtl_path, new_mtl);
|
||||
}
|
||||
|
||||
if (have_import_hook) {
|
||||
/* Defer invoking the hook to convert the material till we can do so from
|
||||
* the main thread. */
|
||||
settings_.usd_path_to_mat_for_hook.add_new(mtl_path, new_mtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::fake_users_for_unused_materials()
|
||||
{
|
||||
/* Iterate over the imported materials and set a fake user for any unused
|
||||
* materials. */
|
||||
for (Material *mat : settings_.usd_path_to_mat.values()) {
|
||||
if (mat->id.us == 0) {
|
||||
id_fake_user_set(&mat->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::find_material_import_hook_sources()
|
||||
{
|
||||
pxr::UsdPrimRange range = stage_->Traverse();
|
||||
for (pxr::UsdPrim prim : range) {
|
||||
if (prim.IsA<pxr::UsdShadeMaterial>()) {
|
||||
pxr::UsdShadeMaterial usd_mat(prim);
|
||||
if (have_material_import_hook(stage_, usd_mat, params_, reports())) {
|
||||
settings_.mat_import_hook_sources.add(prim.GetPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::call_material_import_hooks(Main *bmain) const
|
||||
{
|
||||
if (settings_.usd_path_to_mat_for_hook.is_empty()) {
|
||||
/* No materials can be converted by a hook. */
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto item : settings_.usd_path_to_mat_for_hook.items()) {
|
||||
pxr::UsdPrim prim = stage_->GetPrimAtPath(item.key);
|
||||
|
||||
pxr::UsdShadeMaterial usd_mtl(prim);
|
||||
if (!usd_mtl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool success = io::usd::call_material_import_hooks(
|
||||
stage_, item.value, usd_mtl, params_, reports());
|
||||
|
||||
if (!success) {
|
||||
/* None of the hooks succeeded, so fall back on importing USD Preview Surface if possible. */
|
||||
CLOG_WARN(&LOG,
|
||||
"USD hook 'on_material_import' for material %s failed, attempting to convert USD "
|
||||
"Preview Surface material",
|
||||
usd_mtl.GetPath().GetAsString().c_str());
|
||||
|
||||
USDMaterialReader mat_reader(this->params_, *bmain);
|
||||
mat_reader.import_usd_preview(item.value, usd_mtl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::clear_readers()
|
||||
{
|
||||
for (USDPrimReader *reader : readers_) {
|
||||
decref(reader);
|
||||
}
|
||||
readers_.clear();
|
||||
|
||||
for (const auto item : proto_readers_.items()) {
|
||||
for (USDPrimReader *reader : item.value) {
|
||||
decref(reader);
|
||||
}
|
||||
}
|
||||
proto_readers_.clear();
|
||||
|
||||
for (const auto item : instancer_proto_readers_.items()) {
|
||||
for (USDPrimReader *reader : item.value) {
|
||||
decref(reader);
|
||||
}
|
||||
}
|
||||
instancer_proto_readers_.clear();
|
||||
|
||||
for (USDDomeLightReader *reader : dome_light_readers_) {
|
||||
decref(reader);
|
||||
}
|
||||
dome_light_readers_.clear();
|
||||
}
|
||||
|
||||
void USDStageReader::sort_readers()
|
||||
{
|
||||
parallel_sort(
|
||||
readers_.begin(), readers_.end(), [](const USDPrimReader *a, const USDPrimReader *b) {
|
||||
int result = BLI_strcasecmp(a->name().c_str(), b->name().c_str());
|
||||
|
||||
/* The sorting should be deterministic and consistent regardless of how the list of readers
|
||||
* was created. Use the original, unique, USD prim path to break ties. */
|
||||
if (result == 0) {
|
||||
return a->prim_path() < b->prim_path();
|
||||
}
|
||||
|
||||
return result < 0;
|
||||
});
|
||||
}
|
||||
|
||||
void USDStageReader::create_proto_collections(Main *bmain, Collection *parent_collection)
|
||||
{
|
||||
if (proto_readers_.is_empty() && instancer_proto_readers_.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection *all_protos_collection = create_collection(bmain, parent_collection, "prototypes");
|
||||
|
||||
if (all_protos_collection) {
|
||||
all_protos_collection->flag |= COLLECTION_HIDE_VIEWPORT;
|
||||
all_protos_collection->flag |= COLLECTION_HIDE_RENDER;
|
||||
if (parent_collection) {
|
||||
DEG_id_tag_update(&parent_collection->id, ID_RECALC_HIERARCHY);
|
||||
}
|
||||
}
|
||||
|
||||
Map<pxr::SdfPath, Collection *> proto_collection_map;
|
||||
|
||||
for (const pxr::SdfPath &path : proto_readers_.keys()) {
|
||||
Collection *proto_collection = create_collection(bmain, all_protos_collection, "proto");
|
||||
|
||||
proto_collection_map.add(path, proto_collection);
|
||||
}
|
||||
|
||||
/* Set the instance collections on the readers, including the prototype
|
||||
* readers (which are included in readers_), as instancing may be nested. */
|
||||
|
||||
for (USDPrimReader *reader : readers_) {
|
||||
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
|
||||
set_instance_collection(instance_reader, proto_collection_map);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the prototype objects to the collections. */
|
||||
for (const auto &item : proto_readers_.items()) {
|
||||
Collection *collection = proto_collection_map.lookup_default(item.key, nullptr);
|
||||
if (collection == nullptr) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't find collection when adding objects for prototype %s",
|
||||
item.key.GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const USDPrimReader *reader : item.value) {
|
||||
Object *ob = reader->object();
|
||||
|
||||
if (!ob) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BKE_collection_object_add(bmain, collection, ob);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create collections for the point instancer prototypes. */
|
||||
|
||||
/* For every point instancer reader, create a "prototypes" collection and set it
|
||||
* on the Collection Info node referenced by the geometry nodes modifier created by
|
||||
* the reader. We also create collections containing prototype geometry as children
|
||||
* of the "prototypes" collection. These child collections will be indexed for
|
||||
* instancing by the Instance on Points geometry node.
|
||||
*
|
||||
* Note that the prototype collections will be ordered alphabetically by the Collection
|
||||
* Info node. We must therefore take care to generate collection names that will maintain
|
||||
* the original prototype order, so that the prototype indices will remain valid. We use
|
||||
* the naming convention proto_<index>, where the index suffix may be zero padded (e.g.,
|
||||
* "proto_00", "proto_01", "proto_02", etc.).
|
||||
*/
|
||||
|
||||
for (USDPrimReader *reader : readers_) {
|
||||
USDPointInstancerReader *instancer_reader = dynamic_cast<USDPointInstancerReader *>(reader);
|
||||
if (!instancer_reader) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::SdfPathVector proto_paths = instancer_reader->proto_paths();
|
||||
const pxr::SdfPath &instancer_path = reader->prim().GetPath();
|
||||
Collection *instancer_protos_coll = create_collection(
|
||||
bmain, all_protos_collection, instancer_path.GetName().c_str());
|
||||
|
||||
/* Determine the max number of digits we will need for the possibly zero-padded
|
||||
* string representing the prototype index. */
|
||||
const int max_index_digits = integer_digits_i(proto_paths.size());
|
||||
|
||||
int proto_index = 0;
|
||||
|
||||
for (const pxr::SdfPath &proto_path : proto_paths) {
|
||||
BLI_assert(max_index_digits > 0);
|
||||
|
||||
/* Format the collection name to follow the proto_<index> pattern. */
|
||||
std::string coll_name = fmt::format("proto_{0:0{1}}", proto_index, max_index_digits);
|
||||
|
||||
/* Create the collection and populate it with the prototype objects. */
|
||||
Collection *proto_coll = create_collection(bmain, instancer_protos_coll, coll_name.c_str());
|
||||
Vector<USDPrimReader *> proto_readers = instancer_proto_readers_.lookup_default(proto_path,
|
||||
{});
|
||||
for (const USDPrimReader *proto : proto_readers) {
|
||||
Object *ob = proto->object();
|
||||
if (!ob) {
|
||||
continue;
|
||||
}
|
||||
BKE_collection_object_add(bmain, proto_coll, ob);
|
||||
}
|
||||
++proto_index;
|
||||
}
|
||||
|
||||
instancer_reader->set_collection(bmain, *instancer_protos_coll);
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::create_point_instancer_proto_readers(const UsdPathSet &proto_paths)
|
||||
{
|
||||
if (proto_paths.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pxr::SdfPath &path : proto_paths) {
|
||||
|
||||
pxr::UsdPrim proto_prim = stage_->GetPrimAtPath(path);
|
||||
|
||||
if (!proto_prim) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<USDPrimReader *> proto_readers;
|
||||
|
||||
/* Note that point instancer prototypes may be defined as overs, so
|
||||
* we must call collect readers with argument defined_prims_only = false. */
|
||||
collect_readers(proto_prim, proto_paths, false /* include undefined prims */, proto_readers);
|
||||
|
||||
instancer_proto_readers_.add(path, proto_readers);
|
||||
|
||||
for (USDPrimReader *reader : proto_readers) {
|
||||
reader->set_is_in_instancer_proto(true);
|
||||
readers_.append(reader);
|
||||
reader->incref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDStageReader::collect_point_instancer_proto_paths(const pxr::UsdPrim &prim,
|
||||
UsdPathSet &r_paths) const
|
||||
{
|
||||
/* Note that we use custom filter flags to allow traversing undefined prims,
|
||||
* because prototype prims may be defined as overs which are skipped by the
|
||||
* default predicate. */
|
||||
pxr::Usd_PrimFlagsConjunction filter_flags = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
|
||||
!pxr::UsdPrimIsAbstract;
|
||||
|
||||
pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_flags);
|
||||
|
||||
for (const auto &child_prim : children) {
|
||||
|
||||
/* Note we allow undefined prims in case prototypes are defined as overs.
|
||||
* If the prim is defined, we apply additional checks for inclusion. */
|
||||
if (child_prim.IsDefined()) {
|
||||
const pxr::UsdGeomImageable imageable = pxr::UsdGeomImageable(child_prim);
|
||||
if (!imageable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We should only traverse through a hierarchy, and any potential instancers, if they would
|
||||
* be included by our purpose and visibility checks, matching what is inside
|
||||
* #collect_readers. */
|
||||
if (!include_by_purpose(imageable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!include_by_visibility(imageable)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* We should only consider potential point instancers if they would be included by the scene
|
||||
* instancing flags. */
|
||||
if (!params_.support_scene_instancing && child_prim.IsInPrototype()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pxr::UsdGeomPointInstancer instancer = pxr::UsdGeomPointInstancer(child_prim)) {
|
||||
pxr::SdfPathVector paths;
|
||||
instancer.GetPrototypesRel().GetTargets(&paths);
|
||||
for (const pxr::SdfPath &path : paths) {
|
||||
r_paths.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
collect_point_instancer_proto_paths(child_prim, r_paths);
|
||||
}
|
||||
}
|
||||
|
||||
UsdPathSet USDStageReader::collect_point_instancer_proto_paths() const
|
||||
{
|
||||
UsdPathSet result;
|
||||
|
||||
if (!stage_) {
|
||||
return result;
|
||||
}
|
||||
|
||||
collect_point_instancer_proto_paths(stage_->GetPseudoRoot(), result);
|
||||
|
||||
std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
|
||||
|
||||
for (const pxr::UsdPrim &proto_prim : protos) {
|
||||
collect_point_instancer_proto_paths(proto_prim, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
216
blender-5.2.0/source/blender/io/usd/intern/usd_reader_stage.hh
Normal file
216
blender-5.2.0/source/blender/io/usd/intern/usd_reader_stage.hh
Normal file
@@ -0,0 +1,216 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
#include "usd_reader_domelight.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/imageable.h>
|
||||
struct ImportSettings;
|
||||
namespace blender {
|
||||
|
||||
struct Collection;
|
||||
struct Main;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDPointInstancerReader;
|
||||
|
||||
/**
|
||||
* Map a USD prototype prim path to the list of readers that convert
|
||||
* the prototype data.
|
||||
*/
|
||||
using ProtoReaderMap = Map<pxr::SdfPath, Vector<USDPrimReader *>>;
|
||||
|
||||
using UsdPathSet = Set<pxr::SdfPath>;
|
||||
|
||||
class USDStageReader {
|
||||
|
||||
protected:
|
||||
pxr::UsdStageRefPtr stage_;
|
||||
USDImportParams params_;
|
||||
ImportSettings settings_;
|
||||
|
||||
Vector<USDPrimReader *> readers_;
|
||||
|
||||
/* USD dome lights are converted to a world material,
|
||||
* rather than light objects, so are handled differently */
|
||||
Vector<USDDomeLightReader *> dome_light_readers_;
|
||||
|
||||
/* USD material prim paths encountered during stage
|
||||
* traversal, for importing unused materials. */
|
||||
Vector<pxr::SdfPath> material_paths_;
|
||||
|
||||
/* Readers for scene-graph instance prototypes. */
|
||||
ProtoReaderMap proto_readers_;
|
||||
|
||||
/* Readers for point instancer prototypes. */
|
||||
ProtoReaderMap instancer_proto_readers_;
|
||||
|
||||
public:
|
||||
USDStageReader(pxr::UsdStageRefPtr stage,
|
||||
const USDImportParams ¶ms,
|
||||
const std::function<CacheFile *()> &get_cache_file_fn = {});
|
||||
|
||||
~USDStageReader();
|
||||
|
||||
USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim);
|
||||
|
||||
USDPrimReader *create_reader(const pxr::UsdPrim &prim);
|
||||
|
||||
void collect_readers();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* import_all_materials(). */
|
||||
void import_all_materials(struct Main *bmain);
|
||||
|
||||
/* Add fake users for any imported materials with no
|
||||
* users. This is typically required when importing all
|
||||
* materials. */
|
||||
void fake_users_for_unused_materials();
|
||||
|
||||
/**
|
||||
* Discover the USD materials that can be converted
|
||||
* by material import hook add-ons.
|
||||
*/
|
||||
void find_material_import_hook_sources();
|
||||
|
||||
/**
|
||||
* Invoke USD hook add-ons to convert materials. This function
|
||||
* should be called from the main thread and not from a
|
||||
* background job.
|
||||
*/
|
||||
void call_material_import_hooks(struct Main *bmain) const;
|
||||
|
||||
bool valid() const;
|
||||
|
||||
pxr::UsdStageRefPtr stage()
|
||||
{
|
||||
return stage_;
|
||||
}
|
||||
const USDImportParams ¶ms() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
const ImportSettings &settings() const
|
||||
{
|
||||
return settings_;
|
||||
}
|
||||
|
||||
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
|
||||
ReportList *reports() const;
|
||||
|
||||
/** Clear all cached reader collections. */
|
||||
void clear_readers();
|
||||
|
||||
const Vector<USDPrimReader *> &readers() const
|
||||
{
|
||||
return readers_;
|
||||
};
|
||||
|
||||
const Vector<USDDomeLightReader *> &dome_light_readers() const
|
||||
{
|
||||
return dome_light_readers_;
|
||||
};
|
||||
|
||||
void sort_readers();
|
||||
|
||||
/**
|
||||
* Create prototype collections for instancing by the USD instance readers.
|
||||
*/
|
||||
void create_proto_collections(Main *bmain, Collection *parent_collection);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Create readers for the subtree rooted at the given prim and append the
|
||||
* new readers in r_readers.
|
||||
*
|
||||
* \param prim: Root of the subtree to convert to readers
|
||||
* \param pruned_prims: Set of paths to prune when iterating over the
|
||||
* stage during conversion. I.e., these prims
|
||||
* and their descendants will not be converted to
|
||||
* readers.
|
||||
* \param defined_prims_only: If true, only defined prims will be converted,
|
||||
* skipping abstract and over prims. This should
|
||||
* be set to false when converting point instancer
|
||||
* prototype prims, which can be declared as overs.
|
||||
* \param r_readers: Readers created for the prims in the converted subtree.
|
||||
* \return A pointer to the reader created for the given prim or null if
|
||||
* the prim cannot be converted.
|
||||
*/
|
||||
USDPrimReader *collect_readers(const pxr::UsdPrim &prim,
|
||||
const UsdPathSet &pruned_prims,
|
||||
bool defined_prims_only,
|
||||
Vector<USDPrimReader *> &r_readers);
|
||||
|
||||
/**
|
||||
* Returns true if the given prim should be included in the
|
||||
* traversal based on the import options and the prim's visibility
|
||||
* attribute. Note that the prim will be trivially included
|
||||
* if it has no visibility attribute or if the visibility
|
||||
* is inherited.
|
||||
*/
|
||||
bool include_by_visibility(const pxr::UsdGeomImageable &imageable) const;
|
||||
|
||||
/**
|
||||
* Returns true if the given prim should be included in the
|
||||
* traversal based on the import options and the prim's purpose
|
||||
* attribute. E.g., return false (to exclude the prim) if the prim
|
||||
* represents guide geometry and the 'Import Guide' option is
|
||||
* toggled off.
|
||||
*/
|
||||
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
|
||||
|
||||
/**
|
||||
* Returns true if the given reader can use the parent of the encapsulated USD prim
|
||||
* to compute the Blender object's transform. If so, the reader is appropriately
|
||||
* flagged and the function returns true. Otherwise, the function returns false.
|
||||
*/
|
||||
bool merge_with_parent(USDPrimReader *reader) const;
|
||||
|
||||
/**
|
||||
* Returns true if the specified UsdPrim is a UsdGeom primitive,
|
||||
* procedural shape, such as UsdGeomCube.
|
||||
*/
|
||||
bool is_primitive_prim(const pxr::UsdPrim &prim) const;
|
||||
|
||||
/**
|
||||
* Iterate over the stage and return the paths of all prototype
|
||||
* primitives references by point instancers.
|
||||
*
|
||||
* \return The prototype paths, or an empty path set if the scene
|
||||
* does not contain any point instancers.
|
||||
*/
|
||||
UsdPathSet collect_point_instancer_proto_paths() const;
|
||||
void collect_point_instancer_proto_paths(const pxr::UsdPrim &prim, UsdPathSet &r_paths) const;
|
||||
|
||||
/**
|
||||
* Populate the instancer_proto_readers_ map for the prototype prims
|
||||
* in the given set. For each prototype path, this function will
|
||||
* create readers for the prims in the subtree rooted at the prototype
|
||||
* prim.
|
||||
*/
|
||||
void create_point_instancer_proto_readers(const UsdPathSet &proto_paths);
|
||||
};
|
||||
|
||||
}; // namespace io::usd
|
||||
|
||||
} // namespace blender
|
||||
334
blender-5.2.0/source/blender/io/usd/intern/usd_reader_utils.cc
Normal file
334
blender-5.2.0/source/blender/io/usd/intern/usd_reader_utils.cc
Normal file
@@ -0,0 +1,334 @@
|
||||
/* SPDX-FileCopyrightText: 2024 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_utils.hh"
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "BKE_idprop.hh"
|
||||
|
||||
#include <pxr/usd/usd/attribute.h>
|
||||
#include <pxr/usd/usdUI/accessibilityAPI.h>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename VECT>
|
||||
void set_array_prop(IDProperty *idgroup,
|
||||
const StringRefNull prop_name,
|
||||
const pxr::UsdAttribute &attr,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
if (!idgroup || !attr) {
|
||||
return;
|
||||
}
|
||||
|
||||
VECT vec;
|
||||
if (!attr.Get<VECT>(&vec, time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.array.len = int(vec.dimension);
|
||||
|
||||
if (val.array.len <= 0) {
|
||||
CLOG_WARN(&LOG, "Invalid array length for prop %s", prop_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::is_same<float, typename VECT::ScalarType>()) {
|
||||
val.array.type = IDP_FLOAT;
|
||||
}
|
||||
else if (std::is_same<pxr::GfHalf, typename VECT::ScalarType>()) {
|
||||
val.array.type = IDP_FLOAT;
|
||||
}
|
||||
else if (std::is_same<double, typename VECT::ScalarType>()) {
|
||||
val.array.type = IDP_DOUBLE;
|
||||
}
|
||||
else if (std::is_same<int, typename VECT::ScalarType>()) {
|
||||
val.array.type = IDP_INT;
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG, "Couldn't determine array type for prop %s", prop_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
IDProperty *prop = IDP_New(IDP_ARRAY, &val, prop_name);
|
||||
|
||||
if (!prop) {
|
||||
CLOG_WARN(&LOG, "Couldn't create array prop %s", prop_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::is_same<pxr::GfHalf, typename VECT::ScalarType>()) {
|
||||
float *prop_data = static_cast<float *>(prop->data.pointer);
|
||||
for (int i = 0; i < val.array.len; ++i) {
|
||||
prop_data[i] = vec[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::memcpy(prop->data.pointer, vec.data(), prop->len * sizeof(typename VECT::ScalarType));
|
||||
}
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
bool equivalent(const pxr::SdfValueTypeName &type_name1, const pxr::SdfValueTypeName &type_name2)
|
||||
{
|
||||
return type_name1.GetType().IsA(type_name2.GetType());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
static void set_string_prop(IDProperty *idgroup,
|
||||
const StringRefNull prop_name,
|
||||
const StringRefNull str_val)
|
||||
{
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.string.str = str_val.data();
|
||||
/* Note length includes null terminator. */
|
||||
val.string.len = str_val.size() + 1;
|
||||
val.string.subtype = IDP_STRING_SUB_UTF8;
|
||||
|
||||
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
static void set_int_prop(IDProperty *idgroup, const StringRefNull prop_name, const int ival)
|
||||
{
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.i = ival;
|
||||
IDProperty *prop = IDP_New(IDP_INT, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
static void set_bool_prop(IDProperty *idgroup, const StringRefNull prop_name, const bool bval)
|
||||
{
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.i = bval;
|
||||
IDProperty *prop = IDP_New(IDP_BOOLEAN, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
static void set_float_prop(IDProperty *idgroup, const StringRefNull prop_name, const float fval)
|
||||
{
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.f = fval;
|
||||
IDProperty *prop = IDP_New(IDP_FLOAT, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
static void set_double_prop(IDProperty *idgroup, const StringRefNull prop_name, const double dval)
|
||||
{
|
||||
if (!idgroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDPropertyTemplate val = {0};
|
||||
val.d = dval;
|
||||
IDProperty *prop = IDP_New(IDP_DOUBLE, &val, prop_name);
|
||||
|
||||
IDP_AddToGroup(idgroup, prop);
|
||||
}
|
||||
|
||||
static void set_accessibility_property(const pxr::UsdAttribute &attr,
|
||||
IDProperty *idgroup,
|
||||
const pxr::UsdTimeCode time_code)
|
||||
{
|
||||
/* Only set the property if the attribute has an authored value. */
|
||||
if (!attr.IsAuthored()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Since STRING properties do not support keyframes, if there is already a value written
|
||||
* for this property, don't try to write it again. */
|
||||
std::string property_name = attr.GetName().GetString();
|
||||
if (IDP_GetPropertyFromGroup(idgroup, property_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::SdfValueTypeName type_name = attr.GetTypeName();
|
||||
if (type_name == pxr::SdfValueTypeNames->String) {
|
||||
std::string value;
|
||||
if (attr.Get<std::string>(&value, time_code)) {
|
||||
set_string_prop(idgroup, property_name, value);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Token) {
|
||||
pxr::TfToken value;
|
||||
if (attr.Get<pxr::TfToken>(&value, time_code)) {
|
||||
set_string_prop(idgroup, property_name, value.GetString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_id_props_from_prim(ID *id,
|
||||
const pxr::UsdPrim &prim,
|
||||
const PropertyImportMode property_import_mode,
|
||||
const pxr::UsdTimeCode time_code)
|
||||
{
|
||||
for (const auto &api : pxr::UsdUIAccessibilityAPI::GetAll(prim)) {
|
||||
IDProperty *idgroup = IDP_EnsureProperties(id);
|
||||
set_accessibility_property(api.GetLabelAttr(), idgroup, time_code);
|
||||
set_accessibility_property(api.GetDescriptionAttr(), idgroup, time_code);
|
||||
set_accessibility_property(api.GetPriorityAttr(), idgroup, time_code);
|
||||
}
|
||||
|
||||
pxr::UsdAttributeVector attribs = prim.GetAuthoredAttributes();
|
||||
if (attribs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool all_custom_attrs = (property_import_mode == PropertyImportMode::All);
|
||||
|
||||
for (const pxr::UsdAttribute &attr : attribs) {
|
||||
if (!attr.IsCustom()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> attr_names = attr.SplitName();
|
||||
|
||||
const bool is_user_prop = attr_names[0] == "userProperties";
|
||||
|
||||
if (attr_names.size() > 2 && is_user_prop && attr_names[1] == "blender") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!all_custom_attrs && !is_user_prop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IDProperty *idgroup = IDP_EnsureProperties(id);
|
||||
|
||||
/* When importing user properties, strip the namespace. */
|
||||
pxr::TfToken attr_name;
|
||||
if (is_user_prop) {
|
||||
/* We strip the userProperties namespace, but leave others in case
|
||||
* someone's custom attribute namespace is important in their pipeline. */
|
||||
const StringRefNull token = "userProperties:";
|
||||
const StringRefNull name = attr.GetName().GetString();
|
||||
attr_name = pxr::TfToken(name.substr(token.size()));
|
||||
}
|
||||
else {
|
||||
attr_name = attr.GetName();
|
||||
}
|
||||
|
||||
pxr::SdfValueTypeName type_name = attr.GetTypeName();
|
||||
|
||||
if (type_name == pxr::SdfValueTypeNames->Int) {
|
||||
int ival = 0;
|
||||
if (attr.Get<int>(&ival, time_code)) {
|
||||
set_int_prop(idgroup, attr_name.GetString(), ival);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Float) {
|
||||
float fval = 0.0f;
|
||||
if (attr.Get<float>(&fval, time_code)) {
|
||||
set_float_prop(idgroup, attr_name.GetString(), fval);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Double) {
|
||||
double dval = 0.0;
|
||||
if (attr.Get<double>(&dval, time_code)) {
|
||||
set_double_prop(idgroup, attr_name.GetString(), dval);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Half) {
|
||||
pxr::GfHalf hval = 0.0f;
|
||||
if (attr.Get<pxr::GfHalf>(&hval, time_code)) {
|
||||
set_float_prop(idgroup, attr_name.GetString(), hval);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->String) {
|
||||
std::string sval;
|
||||
if (attr.Get<std::string>(&sval, time_code)) {
|
||||
set_string_prop(idgroup, attr_name.GetString(), sval);
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Token) {
|
||||
pxr::TfToken tval;
|
||||
if (attr.Get<pxr::TfToken>(&tval, time_code)) {
|
||||
set_string_prop(idgroup, attr_name.GetString(), tval.GetString());
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Asset) {
|
||||
pxr::SdfAssetPath aval;
|
||||
if (attr.Get<pxr::SdfAssetPath>(&aval, time_code)) {
|
||||
set_string_prop(idgroup, attr_name.GetString(), aval.GetAssetPath());
|
||||
}
|
||||
}
|
||||
else if (type_name == pxr::SdfValueTypeNames->Bool) {
|
||||
bool bval = false;
|
||||
if (attr.Get<bool>(&bval, time_code)) {
|
||||
set_bool_prop(idgroup, attr_name.GetString(), bval);
|
||||
}
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float2)) {
|
||||
set_array_prop<pxr::GfVec2f>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float3)) {
|
||||
set_array_prop<pxr::GfVec3f>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Float4)) {
|
||||
set_array_prop<pxr::GfVec4f>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double2)) {
|
||||
set_array_prop<pxr::GfVec2d>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double3)) {
|
||||
set_array_prop<pxr::GfVec3d>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Double4)) {
|
||||
set_array_prop<pxr::GfVec4d>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int2)) {
|
||||
set_array_prop<pxr::GfVec2i>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int3)) {
|
||||
set_array_prop<pxr::GfVec3i>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Int4)) {
|
||||
set_array_prop<pxr::GfVec4i>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half2)) {
|
||||
set_array_prop<pxr::GfVec2h>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half3)) {
|
||||
set_array_prop<pxr::GfVec3h>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
else if (equivalent(type_name, pxr::SdfValueTypeNames->Half4)) {
|
||||
set_array_prop<pxr::GfVec4h>(idgroup, attr_name.GetString(), attr, time_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2024 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/timeCode.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ID;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
void set_id_props_from_prim(ID *id,
|
||||
const pxr::UsdPrim &prim,
|
||||
PropertyImportMode property_import_mode = PropertyImportMode::All,
|
||||
pxr::UsdTimeCode time_code = pxr::UsdTimeCode::Default());
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,81 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_volume.hh"
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_volume.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_volume_types.h"
|
||||
|
||||
#include <pxr/usd/usdVol/openVDBAsset.h>
|
||||
#include <pxr/usd/usdVol/volume.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDVolumeReader::create_object(Main *bmain)
|
||||
{
|
||||
Volume *volume = BKE_volume_add(bmain, name_.c_str());
|
||||
|
||||
object_ = BKE_object_add_only_object(bmain, OB_VOLUME, name_.c_str());
|
||||
object_->data = id_cast<ID *>(volume);
|
||||
}
|
||||
|
||||
void USDVolumeReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
||||
{
|
||||
Volume *volume = id_cast<Volume *>(object_->data);
|
||||
|
||||
pxr::UsdVolVolume::FieldMap fields = volume_.GetFieldPaths();
|
||||
|
||||
for (pxr::UsdVolVolume::FieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it) {
|
||||
|
||||
pxr::UsdPrim fieldPrim = prim_.GetStage()->GetPrimAtPath(it->second);
|
||||
|
||||
if (!fieldPrim.IsA<pxr::UsdVolOpenVDBAsset>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::UsdVolOpenVDBAsset fieldBase(fieldPrim);
|
||||
|
||||
pxr::UsdAttribute filepathAttr = fieldBase.GetFilePathAttr();
|
||||
|
||||
if (filepathAttr.IsAuthored()) {
|
||||
pxr::SdfAssetPath fp;
|
||||
filepathAttr.Get(&fp, time);
|
||||
|
||||
const std::string filepath = fp.GetResolvedPath();
|
||||
STRNCPY(volume->filepath, filepath.c_str());
|
||||
|
||||
if (import_params_.relative_path && !BLI_path_is_rel(volume->filepath)) {
|
||||
BLI_path_rel(volume->filepath, BKE_main_blendfile_path_from_global());
|
||||
}
|
||||
|
||||
if (filepathAttr.ValueMightBeTimeVarying()) {
|
||||
std::vector<double> filePathTimes;
|
||||
filepathAttr.GetTimeSamples(&filePathTimes);
|
||||
|
||||
if (!filePathTimes.empty()) {
|
||||
const int start = int(filePathTimes.front());
|
||||
const int end = int(filePathTimes.back());
|
||||
const int offset = BLI_path_sequence_decode(
|
||||
volume->filepath, nullptr, 0, nullptr, 0, nullptr);
|
||||
|
||||
volume->is_sequence = char(true);
|
||||
volume->frame_start = start;
|
||||
volume->frame_duration = (end - start) + 1;
|
||||
volume->frame_offset = offset - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USDXformReader::read_object_data(bmain, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
#include <pxr/usd/usdVol/volume.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDVolumeReader : public USDXformReader {
|
||||
private:
|
||||
pxr::UsdVolVolume volume_;
|
||||
|
||||
public:
|
||||
USDVolumeReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDXformReader(prim, import_params, settings), volume_(prim)
|
||||
{
|
||||
}
|
||||
|
||||
bool valid() const override
|
||||
{
|
||||
return bool(volume_);
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
180
blender-5.2.0/source/blender/io/usd/intern/usd_reader_xform.cc
Normal file
180
blender-5.2.0/source/blender/io/usd/intern/usd_reader_xform.cc
Normal file
@@ -0,0 +1,180 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#include "usd_reader_xform.hh"
|
||||
|
||||
#include "BKE_constraint.h"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_constraint_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/usd/usdGeom/xformable.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDXformReader::create_object(Main *bmain)
|
||||
{
|
||||
object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str());
|
||||
object_->empty_drawsize = 0.1f;
|
||||
object_->data = nullptr;
|
||||
}
|
||||
|
||||
void USDXformReader::read_object_data(Main * /*bmain*/, const pxr::UsdTimeCode time)
|
||||
{
|
||||
bool is_constant;
|
||||
float4x4 transform_from_usd;
|
||||
|
||||
read_matrix(transform_from_usd, time, settings_->scene_scale, &is_constant);
|
||||
|
||||
if (!is_constant && settings_->get_cache_file) {
|
||||
bConstraint *con = BKE_constraint_add_for_object(
|
||||
object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
|
||||
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
|
||||
|
||||
pxr::SdfPath object_path = use_parent_xform_ ? prim_.GetParent().GetPath() : this->prim_path();
|
||||
|
||||
STRNCPY(data->object_path, object_path.GetAsString().c_str());
|
||||
|
||||
data->cache_file = settings_->get_cache_file();
|
||||
id_us_plus(&data->cache_file->id);
|
||||
}
|
||||
|
||||
BKE_object_apply_mat4(object_, transform_from_usd.ptr(), true, false);
|
||||
|
||||
/* Make sure to collect custom attributes */
|
||||
set_props(use_parent_xform(), time);
|
||||
}
|
||||
|
||||
pxr::SdfPath USDXformReader::object_prim_path() const
|
||||
{
|
||||
return get_xformable().GetPrim().GetPath();
|
||||
}
|
||||
|
||||
void USDXformReader::read_matrix(float4x4 &r_mat /* local matrix */,
|
||||
const pxr::UsdTimeCode time,
|
||||
const float scale,
|
||||
bool *r_is_constant) const
|
||||
{
|
||||
BLI_assert(r_is_constant);
|
||||
|
||||
*r_is_constant = true;
|
||||
r_mat = float4x4::identity();
|
||||
|
||||
std::optional<XformResult> xf_result = get_local_usd_xform(time);
|
||||
if (!xf_result) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::get<0>(*xf_result).Get(r_mat.ptr());
|
||||
*r_is_constant = std::get<1>(*xf_result);
|
||||
|
||||
/* Apply global scaling and rotation only to root objects, parenting
|
||||
* will propagate it. */
|
||||
if (is_root_xform_prim()) {
|
||||
if (scale != 1.0f) {
|
||||
const float4x4 mat_scale = math::from_scale<float4x4>(float3(scale));
|
||||
r_mat = mat_scale * r_mat;
|
||||
}
|
||||
|
||||
if (settings_->do_convert_mat) {
|
||||
r_mat = settings_->conversion_mat * r_mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool USDXformReader::prim_has_xform_ops() const
|
||||
{
|
||||
const pxr::UsdGeomXformable xformable(prim_);
|
||||
|
||||
if (!xformable) {
|
||||
/* This might happen if the prim is a Scope. */
|
||||
return false;
|
||||
}
|
||||
|
||||
bool reset_xform_stack = false;
|
||||
|
||||
return !xformable.GetOrderedXformOps(&reset_xform_stack).empty();
|
||||
}
|
||||
|
||||
bool USDXformReader::is_root_xform_prim() const
|
||||
{
|
||||
if (!prim_.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_in_proto()) {
|
||||
/* We don't consider prototypes to be root prims,
|
||||
* because we never want to apply global scaling
|
||||
* or rotations to the prototypes themselves. */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prim_.IsA<pxr::UsdGeomXformable>()) {
|
||||
/* If this prim doesn't have an ancestor that's a
|
||||
* UsdGeomXformable, then it's a root prim. Note
|
||||
* that it's not sufficient to only check the immediate
|
||||
* parent prim, since the immediate parent could be a
|
||||
* UsdGeomScope that has an xformable ancestor. */
|
||||
pxr::UsdPrim cur_parent = prim_.GetParent();
|
||||
|
||||
if (use_parent_xform_) {
|
||||
cur_parent = cur_parent.GetParent();
|
||||
}
|
||||
|
||||
while (cur_parent && !cur_parent.IsPseudoRoot()) {
|
||||
if (cur_parent.IsA<pxr::UsdGeomXformable>()) {
|
||||
return false;
|
||||
}
|
||||
cur_parent = cur_parent.GetParent();
|
||||
}
|
||||
|
||||
/* We didn't find an xformable ancestor. */
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<XformResult> USDXformReader::get_local_usd_xform(const pxr::UsdTimeCode time) const
|
||||
{
|
||||
const pxr::UsdGeomXformable xformable = get_xformable();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pxr::UsdGeomXformable USDXformReader::get_xformable() const
|
||||
{
|
||||
pxr::UsdPrim prim = use_parent_xform_ ? prim_.GetParent() : prim_;
|
||||
return pxr::UsdGeomXformable(prim);
|
||||
}
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2021 Tangent Animation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Adapted from the Blender Alembic importer implementation. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
#include "usd_reader_prim.hh"
|
||||
|
||||
/* For #UsdGeomXformable. */
|
||||
#include <pxr/usd/usdGeom/xformable.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
|
||||
namespace 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_ = false;
|
||||
|
||||
public:
|
||||
USDXformReader(const pxr::UsdPrim &prim,
|
||||
const USDImportParams &import_params,
|
||||
const ImportSettings &settings)
|
||||
: USDPrimReader(prim, import_params, settings)
|
||||
{
|
||||
}
|
||||
|
||||
void create_object(Main *bmain) override;
|
||||
void read_object_data(Main *bmain, pxr::UsdTimeCode time) override;
|
||||
|
||||
pxr::SdfPath object_prim_path() const override;
|
||||
|
||||
void read_matrix(float4x4 &r_mat, pxr::UsdTimeCode time, float scale, bool *r_is_constant) const;
|
||||
|
||||
bool use_parent_xform() const
|
||||
{
|
||||
return use_parent_xform_;
|
||||
}
|
||||
void set_use_parent_xform(bool flag)
|
||||
{
|
||||
use_parent_xform_ = flag;
|
||||
}
|
||||
|
||||
bool prim_has_xform_ops() const;
|
||||
|
||||
protected:
|
||||
/* Returns true if the contained USD prim is the root of a transform hierarchy. */
|
||||
virtual 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(pxr::UsdTimeCode time) const;
|
||||
|
||||
private:
|
||||
pxr::UsdGeomXformable get_xformable() const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
1416
blender-5.2.0/source/blender/io/usd/intern/usd_skel_convert.cc
Normal file
1416
blender-5.2.0/source/blender/io/usd/intern/usd_skel_convert.cc
Normal file
File diff suppressed because it is too large
Load Diff
142
blender-5.2.0/source/blender/io/usd/intern/usd_skel_convert.hh
Normal file
142
blender-5.2.0/source/blender/io/usd/intern/usd_skel_convert.hh
Normal file
@@ -0,0 +1,142 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdGeom/xformCache.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/**
|
||||
* 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 blend-shapes will be imported
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
* \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,
|
||||
ReportList *reports,
|
||||
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 hierarchy will be added
|
||||
* \param skel: The USD skeleton from which bones and animation will be imported
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
* \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,
|
||||
ReportList *reports,
|
||||
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
|
||||
* binding defined, this function is a no-op.
|
||||
*
|
||||
* \param mesh_obj: Mesh object to which an armature modifier will be added
|
||||
* \param prim: The USD primitive from which skinning data will be imported
|
||||
* \param reports: the storage for potential warning or error reports (generated using BKE_report
|
||||
* API).
|
||||
*/
|
||||
void import_mesh_skel_bindings(Object *mesh_obj, const pxr::UsdPrim &prim, ReportList *reports);
|
||||
|
||||
/**
|
||||
* Map an object to its USD prim export path.
|
||||
*/
|
||||
using ObjExportMap = Map<const Object *, pxr::SdfPath>;
|
||||
|
||||
/**
|
||||
* This function is called after the USD writers are invoked, to
|
||||
* complete the UsdSkel export process, for example, to bind skinned
|
||||
* meshes to skeletons or to set blend shape animation data.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param armature_export_map: Map armature objects to USD skeletons
|
||||
* \param skinned_mesh_export_map: Map mesh objects to USD skinned meshes
|
||||
* \param shape_key_mesh_export_map: Map mesh objects with shape-key to USD meshes
|
||||
* with blend shape targets
|
||||
* \param depsgraph: The dependency graph in which objects were evaluated
|
||||
*/
|
||||
void skel_export_chaser(pxr::UsdStageRefPtr stage,
|
||||
const ObjExportMap &armature_export_map,
|
||||
const ObjExportMap &skinned_mesh_export_map,
|
||||
const ObjExportMap &shape_key_mesh_export_map,
|
||||
const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* Complete the export process for skinned meshes.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param armature_export_map: Map armature objects to USD skeleton paths
|
||||
* \param skinned_mesh_export_map: Map mesh objects to USD skinned meshes
|
||||
* \param xf_cache: Cache to speed up USD prim transform computations
|
||||
* \param depsgraph: The dependency graph in which objects were evaluated
|
||||
*/
|
||||
void skinned_mesh_export_chaser(pxr::UsdStageRefPtr stage,
|
||||
const ObjExportMap &armature_export_map,
|
||||
const ObjExportMap &skinned_mesh_export_map,
|
||||
pxr::UsdGeomXformCache &xf_cache,
|
||||
const Depsgraph *depsgraph);
|
||||
|
||||
/**
|
||||
* Complete the export process for shape keys.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param shape_key_mesh_export_map: Map mesh objects with shape-key to USD meshes
|
||||
* with blend shape targets
|
||||
*/
|
||||
void shape_key_export_chaser(pxr::UsdStageRefPtr stage,
|
||||
const ObjExportMap &shape_key_mesh_export_map);
|
||||
|
||||
/**
|
||||
* Convert deform groups on the given mesh to USD joint index and weight attributes.
|
||||
*
|
||||
* \param mesh: The source mesh with deform groups to export
|
||||
* \param skel_api: API for setting the attributes on the USD prim
|
||||
* \param bone_names: List of armature bone names corresponding to the deform groups
|
||||
*/
|
||||
void export_deform_verts(const Mesh *mesh,
|
||||
const pxr::UsdSkelBindingAPI &skel_api,
|
||||
Span<StringRef> bone_names);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,145 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_skel_root_utils.hh"
|
||||
|
||||
#include <pxr/usd/usd/primRange.h>
|
||||
#include <pxr/usd/usdGeom/xform.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/root.h>
|
||||
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
/* Utility: return the common Xform ancestor of the given prims. Is no such ancestor can
|
||||
* be found, return an in valid Xform. */
|
||||
static pxr::UsdGeomXform get_xform_ancestor(const pxr::UsdPrim &prim1, const pxr::UsdPrim &prim2)
|
||||
{
|
||||
if (!prim1 || !prim2) {
|
||||
return pxr::UsdGeomXform();
|
||||
}
|
||||
|
||||
pxr::SdfPath prefix = prim1.GetPath().GetCommonPrefix(prim2.GetPath());
|
||||
|
||||
if (prefix.IsEmpty()) {
|
||||
return pxr::UsdGeomXform();
|
||||
}
|
||||
|
||||
pxr::UsdPrim ancestor = prim1.GetStage()->GetPrimAtPath(prefix);
|
||||
|
||||
if (!ancestor) {
|
||||
return pxr::UsdGeomXform();
|
||||
}
|
||||
|
||||
while (ancestor && !ancestor.IsA<pxr::UsdGeomXform>()) {
|
||||
ancestor = ancestor.GetParent();
|
||||
}
|
||||
|
||||
if (ancestor && ancestor.IsA<pxr::UsdGeomXform>()) {
|
||||
return pxr::UsdGeomXform(ancestor);
|
||||
}
|
||||
|
||||
return pxr::UsdGeomXform();
|
||||
}
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
void create_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams ¶ms)
|
||||
{
|
||||
if (!stage || !(params.export_armatures || params.export_shapekeys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReportList *reports = params.worker_status ? params.worker_status->reports : nullptr;
|
||||
|
||||
/* Whether we converted any prims to UsdSkel. */
|
||||
bool converted_to_usdskel = false;
|
||||
|
||||
pxr::UsdPrimRange it = stage->Traverse();
|
||||
for (pxr::UsdPrim prim : it) {
|
||||
|
||||
if (!prim) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prim.IsA<pxr::UsdSkelSkeleton>() || !prim.HasAPI<pxr::UsdSkelBindingAPI>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::UsdSkelBindingAPI skel_bind_api(prim);
|
||||
|
||||
if (!skel_bind_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to prim %s",
|
||||
prim.GetPath().GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If we got here, then this prim has the skel binding API. */
|
||||
|
||||
/* Get this prim's bound skeleton. */
|
||||
pxr::UsdSkelSkeleton skel;
|
||||
if (!skel_bind_api.GetSkeleton(&skel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!skel.GetPrim().IsValid()) {
|
||||
CLOG_WARN(&LOG, "Invalid skeleton for prim %s", prim.GetPath().GetAsString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to find a common ancestor of the skinned prim and its bound skeleton. */
|
||||
pxr::UsdSkelRoot prim_skel_root = pxr::UsdSkelRoot::Find(prim);
|
||||
pxr::UsdSkelRoot skel_skel_root = pxr::UsdSkelRoot::Find(skel.GetPrim());
|
||||
|
||||
if (prim_skel_root && skel_skel_root && prim_skel_root.GetPath() == skel_skel_root.GetPath()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pxr::UsdGeomXform xf = get_xform_ancestor(prim, skel.GetPrim())) {
|
||||
/* We found a common Xform ancestor, so we set its type to UsdSkelRoot. */
|
||||
CLOG_DEBUG(
|
||||
&LOG, "Converting Xform prim %s to a SkelRoot", prim.GetPath().GetAsString().c_str());
|
||||
|
||||
pxr::UsdSkelRoot::Define(stage, xf.GetPath());
|
||||
converted_to_usdskel = true;
|
||||
}
|
||||
else {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"%s: Couldn't find a common Xform ancestor for skinned prim %s "
|
||||
"and skeleton %s to convert to a USD SkelRoot. "
|
||||
"This can be addressed by setting a root primitive in the export options",
|
||||
__func__,
|
||||
prim.GetPath().GetAsString().c_str(),
|
||||
skel.GetPath().GetAsString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted_to_usdskel) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for nested SkelRoots, i.e., SkelRoots beneath other SkelRoots, which we want to avoid.
|
||||
*/
|
||||
it = stage->Traverse();
|
||||
for (pxr::UsdPrim prim : it) {
|
||||
if (prim.IsA<pxr::UsdSkelRoot>()) {
|
||||
if (pxr::UsdSkelRoot root = pxr::UsdSkelRoot::Find(prim.GetParent())) {
|
||||
/* This is a nested SkelRoot, so convert it to an Xform. */
|
||||
pxr::UsdGeomXform::Define(stage, prim.GetPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd.hh"
|
||||
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/**
|
||||
* We must structure the scene graph to encapsulate skinned prim under a UsdSkelRoot
|
||||
* prim. Per the USD documentation, a SkelRoot is a:
|
||||
*
|
||||
* "Boundable prim type used to identify a scope beneath which skeletally-posed primitives are
|
||||
* defined. A SkelRoot must be defined at or above a skinned primitive for any skinning behaviors
|
||||
* in UsdSkel."
|
||||
*
|
||||
* See: https://openusd.org/23.05/api/class_usd_skel_root.html#details
|
||||
*
|
||||
* This function attempts to ensure that skinned primitives and skeletons are encapsulated
|
||||
* under SkelRoots, converting existing Xform primitives to SkelRoots to achieve this,
|
||||
* if possible. In the case where no common ancestor which can be converted to a SkelRoot
|
||||
* is found, this function issues a warning. One way to address such a case is by setting a
|
||||
* root prim in the export options, so that this root prim can be converted to a SkelRoot
|
||||
* for the entire scene.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param params: The export parameters
|
||||
*/
|
||||
void create_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams ¶ms);
|
||||
|
||||
} // namespace blender::io::usd
|
||||
120
blender-5.2.0/source/blender/io/usd/intern/usd_utils.cc
Normal file
120
blender-5.2.0/source/blender/io/usd/intern/usd_utils.cc
Normal file
@@ -0,0 +1,120 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include <pxr/base/tf/stringUtils.h>
|
||||
#include <pxr/base/tf/unicodeUtils.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
static bool is_safe_char(const pxr::TfUtf8CodePoint cp, bool is_first, bool allow_unicode)
|
||||
{
|
||||
constexpr pxr::TfUtf8CodePoint cp_underscore = pxr::TfUtf8CodePointFromAscii('_');
|
||||
|
||||
if (cp == cp_underscore) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allow_unicode) {
|
||||
return is_first ? pxr::TfIsUtf8CodePointXidStart(cp) : pxr::TfIsUtf8CodePointXidContinue(cp);
|
||||
}
|
||||
|
||||
constexpr uint32_t cp_A = pxr::TfUtf8CodePointFromAscii('A').AsUInt32();
|
||||
constexpr uint32_t cp_Z = pxr::TfUtf8CodePointFromAscii('Z').AsUInt32();
|
||||
constexpr uint32_t cp_a = pxr::TfUtf8CodePointFromAscii('a').AsUInt32();
|
||||
constexpr uint32_t cp_z = pxr::TfUtf8CodePointFromAscii('z').AsUInt32();
|
||||
constexpr uint32_t cp_0 = pxr::TfUtf8CodePointFromAscii('0').AsUInt32();
|
||||
constexpr uint32_t cp_9 = pxr::TfUtf8CodePointFromAscii('9').AsUInt32();
|
||||
|
||||
const uint32_t cp_u32 = cp.AsUInt32();
|
||||
const bool is_letter = (cp_u32 >= cp_A && cp_u32 <= cp_Z) || (cp_u32 >= cp_a && cp_u32 <= cp_z);
|
||||
const bool is_digit = cp_u32 >= cp_0 && cp_u32 <= cp_9;
|
||||
return is_first ? is_letter : (is_letter || is_digit);
|
||||
}
|
||||
|
||||
static std::string make_safe_identifier(const StringRef name, bool allow_unicode)
|
||||
{
|
||||
if (name.is_empty()) {
|
||||
return "_";
|
||||
}
|
||||
|
||||
const bool has_leading_digit = std::isdigit(name[0]);
|
||||
const bool need_leading_underscore = has_leading_digit;
|
||||
|
||||
/* Create temporary buffer using the original incoming string size, which can be larger than
|
||||
* required if unicode characters are converted to '_'. This size serves as the upper limit of
|
||||
* what might be produced. */
|
||||
const int64_t adjust = (need_leading_underscore ? 1 : 0);
|
||||
Array<char, 256> storage(name.size() + adjust);
|
||||
MutableSpan<char> buf(storage);
|
||||
|
||||
/* Insert a leading '_' to account for invalid starting characters. */
|
||||
size_t offset = 0;
|
||||
bool first = true;
|
||||
if (need_leading_underscore) {
|
||||
buf[0] = '_';
|
||||
offset = 1;
|
||||
first = false;
|
||||
}
|
||||
|
||||
for (auto cp : pxr::TfUtf8CodePointView{name}) {
|
||||
const bool cp_allowed = is_safe_char(cp, first, allow_unicode);
|
||||
if (!cp_allowed) {
|
||||
offset += BLI_str_utf8_from_unicode(uint32_t('_'), buf.data() + offset, buf.size() - offset);
|
||||
}
|
||||
else {
|
||||
offset += BLI_str_utf8_from_unicode(cp.AsUInt32(), buf.data() + offset, buf.size() - offset);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
return {buf.data(), offset};
|
||||
}
|
||||
|
||||
std::string make_safe_name(const StringRef name, bool allow_unicode)
|
||||
{
|
||||
return make_safe_identifier(name, allow_unicode);
|
||||
}
|
||||
|
||||
std::string make_safe_primvar_name(const StringRef name, bool allow_unicode)
|
||||
{
|
||||
/* Allow namespaced identifiers, separated by ':'. */
|
||||
const std::string original(name);
|
||||
std::vector<std::string> tokens = pxr::TfStringSplit(original, ":");
|
||||
if (tokens.empty()) {
|
||||
return "_";
|
||||
}
|
||||
|
||||
std::string safe_name;
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
const std::string &token = tokens[i];
|
||||
safe_name += make_safe_identifier(token, allow_unicode);
|
||||
if (i != tokens.size() - 1) {
|
||||
safe_name += ":";
|
||||
}
|
||||
}
|
||||
|
||||
return safe_name;
|
||||
}
|
||||
|
||||
pxr::SdfPath get_unique_path(pxr::UsdStageRefPtr stage, const std::string &path)
|
||||
{
|
||||
std::string unique_path = path;
|
||||
int suffix = 2;
|
||||
while (stage->GetPrimAtPath(pxr::SdfPath(unique_path)).IsValid()) {
|
||||
unique_path = path + std::to_string(suffix++);
|
||||
}
|
||||
|
||||
return pxr::SdfPath(unique_path);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
44
blender-5.2.0/source/blender/io/usd/intern/usd_utils.hh
Normal file
44
blender-5.2.0/source/blender/io/usd/intern/usd_utils.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/**
|
||||
* Return a valid USD identifier based on the passed in string.
|
||||
*
|
||||
* \param name: Incoming name to sanitize
|
||||
* \param allow_unicode: Whether to allow unicode encoded characters in the USD identifier
|
||||
* \return A valid USD identifier
|
||||
*/
|
||||
std::string make_safe_name(StringRef name, bool allow_unicode);
|
||||
|
||||
/**
|
||||
* Return a valid USD primvar name based on the passed in string. The name is permitted to contain
|
||||
* namespaces separated by colons. E.g. "ns1:ns2:primvar_name".
|
||||
*
|
||||
* \param name: Incoming name to sanitize
|
||||
* \param allow_unicode: Whether to allow unicode encoded characters in the USD primvar name
|
||||
* \return A valid USD primvar name
|
||||
*/
|
||||
std::string make_safe_primvar_name(StringRef name, bool allow_unicode);
|
||||
|
||||
/**
|
||||
* Return a unique USD `SdfPath`. If the given path already exists on the given stage, return
|
||||
* the path with a numerical suffix appended to the name that ensures the path is unique.
|
||||
* If the path does not exist on the stage, it will be returned unchanged.
|
||||
*
|
||||
* \param stage: The stage
|
||||
* \param path: The original path
|
||||
* \return A valid, and unique, USD `SdfPath`
|
||||
*/
|
||||
pxr::SdfPath get_unique_path(pxr::UsdStageRefPtr stage, const std::string &path);
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,588 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_abstract.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
||||
#include <pxr/base/tf/stringUtils.h>
|
||||
#include <pxr/usd/usdGeom/bboxCache.h>
|
||||
#include <pxr/usd/usdGeom/scope.h>
|
||||
#include <pxr/usd/usdUI/accessibilityAPI.h>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_bounds_types.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
/* TfToken objects are not cheap to construct, so we do it once. */
|
||||
namespace usdtokens {
|
||||
static const pxr::TfToken blender_ns("userProperties:blender", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
namespace {
|
||||
struct AccessibilityPropertyName {
|
||||
pxr::TfToken property_namespace;
|
||||
pxr::TfToken property_base_name;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
static std::optional<AccessibilityPropertyName> parse_accessibility_property_name(
|
||||
IDProperty *prop, bool allow_unicode)
|
||||
{
|
||||
std::vector<std::string> property_tokens = pxr::TfStringTokenize(prop->name, ":");
|
||||
|
||||
/* First check if the property name matches the UsdUIAccessibility format exactly. */
|
||||
if (property_tokens.size() == 3) {
|
||||
pxr::TfToken accessibility_token(property_tokens[0]);
|
||||
pxr::TfToken basename(property_tokens[2]);
|
||||
if (accessibility_token == pxr::UsdUITokens->accessibility &&
|
||||
pxr::UsdUIAccessibilityAPI::IsSchemaPropertyBaseName(basename))
|
||||
{
|
||||
AccessibilityPropertyName property_name;
|
||||
|
||||
/* Sanitize the namespace since this is user-generated and might need to be conformed
|
||||
* to the `allow_unicode` export setting. */
|
||||
property_name.property_namespace = pxr::TfToken(
|
||||
io::usd::make_safe_name(property_tokens[1], allow_unicode));
|
||||
property_name.property_base_name = basename;
|
||||
return property_name;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static bool is_valid_accessibility_priority(const pxr::TfToken &token)
|
||||
{
|
||||
return token == pxr::UsdUITokens->low || token == pxr::UsdUITokens->standard ||
|
||||
token == pxr::UsdUITokens->high;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accessibility property on the given prim. Note: although the
|
||||
* UsdUIAccessibilityAPI DOES allow time-sampled data for the `label`
|
||||
* and `description` properties, Blender does not currently support
|
||||
* keyframes on string custom properties so time-sample authoring will
|
||||
* not be done here.
|
||||
*/
|
||||
static void write_accessibility_property(const pxr::UsdPrim &prim,
|
||||
const AccessibilityPropertyName &property_name,
|
||||
const std::string &value)
|
||||
{
|
||||
pxr::UsdUIAccessibilityAPI accessibility_api = pxr::UsdUIAccessibilityAPI::Apply(
|
||||
prim, property_name.property_namespace);
|
||||
if (!accessibility_api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (property_name.property_base_name == pxr::UsdUITokens->label) {
|
||||
accessibility_api.CreateLabelAttr().Set(value);
|
||||
}
|
||||
else if (property_name.property_base_name == pxr::UsdUITokens->description) {
|
||||
accessibility_api.CreateDescriptionAttr().Set(value);
|
||||
}
|
||||
else if (property_name.property_base_name == pxr::UsdUITokens->priority) {
|
||||
pxr::TfToken priority(value);
|
||||
if (is_valid_accessibility_priority(priority)) {
|
||||
accessibility_api.CreatePriorityAttr().Set(priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_mesh_active_uvlayer_name(const Object *ob)
|
||||
{
|
||||
if (!ob || ob->type != OB_MESH || !ob->data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const Mesh *mesh = id_cast<Mesh *>(ob->data);
|
||||
return mesh->active_uv_map_name();
|
||||
}
|
||||
|
||||
template<typename USDT>
|
||||
bool set_vec_attrib(const pxr::UsdPrim &prim,
|
||||
const IDProperty *prop,
|
||||
const pxr::TfToken &prop_token,
|
||||
const pxr::SdfValueTypeName &type_name,
|
||||
const pxr::UsdTimeCode &time)
|
||||
{
|
||||
if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
|
||||
|
||||
if (!vec_attr) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't create USD attribute for array property %s",
|
||||
prop_token.GetString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
USDT vec_value(static_cast<typename USDT::ScalarType *>(prop->data.pointer));
|
||||
|
||||
return vec_attr.Set(vec_value, time);
|
||||
}
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
static void create_vector_attrib(const pxr::UsdPrim &prim,
|
||||
const IDProperty *prop,
|
||||
const pxr::TfToken &prop_token,
|
||||
const pxr::UsdTimeCode &time)
|
||||
{
|
||||
if (!prim || !prop || prop_token.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop->type != IDP_ARRAY) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Property %s is not an array type and can't be converted to a vector attribute",
|
||||
prop->name);
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::SdfValueTypeName type_name;
|
||||
bool success = false;
|
||||
|
||||
if (prop->subtype == IDP_FLOAT) {
|
||||
if (prop->len == 2) {
|
||||
type_name = pxr::SdfValueTypeNames->Float2;
|
||||
success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 3) {
|
||||
type_name = pxr::SdfValueTypeNames->Float3;
|
||||
success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 4) {
|
||||
type_name = pxr::SdfValueTypeNames->Float4;
|
||||
success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
}
|
||||
else if (prop->subtype == IDP_DOUBLE) {
|
||||
if (prop->len == 2) {
|
||||
type_name = pxr::SdfValueTypeNames->Double2;
|
||||
success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 3) {
|
||||
type_name = pxr::SdfValueTypeNames->Double3;
|
||||
success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 4) {
|
||||
type_name = pxr::SdfValueTypeNames->Double4;
|
||||
success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
}
|
||||
else if (prop->subtype == IDP_INT) {
|
||||
if (prop->len == 2) {
|
||||
type_name = pxr::SdfValueTypeNames->Int2;
|
||||
success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 3) {
|
||||
type_name = pxr::SdfValueTypeNames->Int3;
|
||||
success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
else if (prop->len == 4) {
|
||||
type_name = pxr::SdfValueTypeNames->Int4;
|
||||
success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, time);
|
||||
}
|
||||
}
|
||||
|
||||
if (!type_name) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't determine USD type name for array property %s",
|
||||
prop_token.GetString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
CLOG_WARN(
|
||||
&LOG, "Couldn't set USD attribute from array property %s", prop_token.GetString().c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
|
||||
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string USDAbstractWriter::get_export_file_path() const
|
||||
{
|
||||
return usd_export_context_.export_file_path;
|
||||
}
|
||||
|
||||
pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const
|
||||
{
|
||||
if (is_animated_) {
|
||||
BLI_assert(usd_export_context_.get_time_code);
|
||||
return usd_export_context_.get_time_code();
|
||||
}
|
||||
/* By using the default time-code USD won't even write a single `timeSample` for non-animated
|
||||
* data. Instead, it writes it as non-time-sampled. */
|
||||
return pxr::UsdTimeCode::Default();
|
||||
}
|
||||
|
||||
ReportList *USDAbstractWriter::reports() const
|
||||
{
|
||||
return usd_export_context_.export_params.worker_status->reports;
|
||||
}
|
||||
|
||||
void USDAbstractWriter::write(HierarchyContext &context)
|
||||
{
|
||||
if (!frame_has_been_written_) {
|
||||
is_animated_ = usd_export_context_.export_params.export_animation &&
|
||||
check_is_animated(context);
|
||||
}
|
||||
else if (!is_animated_) {
|
||||
/* A frame has already been written, and without animation one frame is enough. */
|
||||
return;
|
||||
}
|
||||
|
||||
do_write(context);
|
||||
|
||||
frame_has_been_written_ = true;
|
||||
}
|
||||
|
||||
const pxr::SdfPath &USDAbstractWriter::usd_path() const
|
||||
{
|
||||
return usd_export_context_.usd_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath USDAbstractWriter::get_material_library_path() const
|
||||
{
|
||||
static std::string material_library_path("/_materials");
|
||||
|
||||
const std::string &root_prim_path = usd_export_context_.export_params.root_prim_path;
|
||||
|
||||
if (!root_prim_path.empty()) {
|
||||
return pxr::SdfPath(root_prim_path + material_library_path);
|
||||
}
|
||||
|
||||
return pxr::SdfPath(material_library_path);
|
||||
}
|
||||
|
||||
pxr::SdfPath USDAbstractWriter::get_proto_material_root_path(const HierarchyContext &context) const
|
||||
{
|
||||
static std::string material_library_path("/_materials");
|
||||
|
||||
std::string path_prefix(usd_export_context_.export_params.root_prim_path);
|
||||
|
||||
path_prefix += context.higher_up_export_path;
|
||||
|
||||
return pxr::SdfPath(path_prefix + material_library_path);
|
||||
}
|
||||
|
||||
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material_created(
|
||||
const HierarchyContext &context, Material *material) const
|
||||
{
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
|
||||
/* Construct the material. */
|
||||
pxr::TfToken material_name(
|
||||
make_safe_name(material->id.name + 2, usd_export_context_.export_params.allow_unicode));
|
||||
pxr::SdfPath usd_path = pxr::UsdGeomScope::Define(stage, get_material_library_path())
|
||||
.GetPath()
|
||||
.AppendChild(material_name);
|
||||
pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
|
||||
if (usd_material) {
|
||||
return usd_material;
|
||||
}
|
||||
|
||||
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
|
||||
|
||||
usd_material = create_usd_material(
|
||||
usd_export_context_, usd_path, material, active_uv, reports());
|
||||
|
||||
auto prim = usd_material.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &material->id);
|
||||
write_id_properties(prim, material->id, get_export_time_code());
|
||||
colorspace_apply_to_prim(prim);
|
||||
|
||||
return usd_material;
|
||||
}
|
||||
|
||||
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
|
||||
Material *material) const
|
||||
{
|
||||
pxr::UsdShadeMaterial library_material = ensure_usd_material_created(context, material);
|
||||
|
||||
/* If instancing is enabled and the object is an instancing prototype, create a material
|
||||
* under the prototype root referencing the library material. This is considered a best
|
||||
* practice and is required for certain renderers (e.g., karma). */
|
||||
|
||||
if (!(usd_export_context_.export_params.use_instancing && context.is_prototype())) {
|
||||
/* We don't need to handle the material for the prototype. */
|
||||
return library_material;
|
||||
}
|
||||
|
||||
/* Create the prototype material. */
|
||||
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
|
||||
pxr::SdfPath usd_path = pxr::UsdGeomScope::Define(stage, get_proto_material_root_path(context))
|
||||
.GetPath()
|
||||
.AppendChild(library_material.GetPath().GetNameToken());
|
||||
|
||||
pxr::UsdShadeMaterial proto_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
|
||||
|
||||
if (!proto_material.GetPrim().GetReferences().AddInternalReference(library_material.GetPath())) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Unable to add a material reference from %s to %s for prototype %s",
|
||||
proto_material.GetPath().GetAsString().c_str(),
|
||||
library_material.GetPath().GetAsString().c_str(),
|
||||
context.export_path.c_str());
|
||||
return library_material;
|
||||
}
|
||||
|
||||
return proto_material;
|
||||
}
|
||||
|
||||
void USDAbstractWriter::write_visibility(const HierarchyContext &context,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdGeomImageable &usd_geometry)
|
||||
{
|
||||
pxr::UsdAttribute attr_visibility = usd_geometry.CreateVisibilityAttr(pxr::VtValue(), true);
|
||||
|
||||
const bool is_visible = context.is_object_visible(
|
||||
usd_export_context_.export_params.evaluation_mode);
|
||||
const pxr::TfToken visibility = is_visible ? pxr::UsdGeomTokens->inherited :
|
||||
pxr::UsdGeomTokens->invisible;
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_visibility, pxr::VtValue(visibility), time);
|
||||
}
|
||||
|
||||
bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
|
||||
{
|
||||
BLI_assert(context.is_instance());
|
||||
|
||||
if (context.export_path == context.original_export_path) {
|
||||
CLOG_ERROR(&LOG,
|
||||
"Reference error: export path matches reference path: %s",
|
||||
context.export_path.c_str());
|
||||
BLI_assert_msg(0, "USD reference error");
|
||||
return false;
|
||||
}
|
||||
|
||||
BLI_assert(!context.original_export_path.empty());
|
||||
BLI_assert(context.original_export_path.front() == '/');
|
||||
|
||||
std::string ref_path_str(usd_export_context_.export_params.root_prim_path);
|
||||
ref_path_str += context.original_export_path;
|
||||
|
||||
pxr::SdfPath ref_path(ref_path_str);
|
||||
|
||||
/* To avoid USD errors, make sure the referenced path exists. */
|
||||
usd_export_context_.stage->DefinePrim(ref_path);
|
||||
|
||||
if (!prim.GetReferences().AddInternalReference(ref_path)) {
|
||||
/* See this URL for a description for why referencing may fail:
|
||||
* https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
|
||||
*/
|
||||
CLOG_WARN(&LOG,
|
||||
"Unable to add reference from %s to %s, not instancing object for export",
|
||||
context.export_path.c_str(),
|
||||
context.original_export_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
prim.SetInstanceable(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void USDAbstractWriter::write_id_properties(const pxr::UsdPrim &prim,
|
||||
const ID &id,
|
||||
pxr::UsdTimeCode time) const
|
||||
{
|
||||
if (!usd_export_context_.export_params.export_custom_properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.author_blender_name) {
|
||||
if (GS(id.name) == ID_OB) {
|
||||
/* Author property of original blender Object name. */
|
||||
prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":object_name"),
|
||||
pxr::SdfValueTypeNames->String,
|
||||
true)
|
||||
.Set<std::string>(std::string(id.name + 2));
|
||||
}
|
||||
else {
|
||||
prim.CreateAttribute(pxr::TfToken(usdtokens::blender_ns.GetString() + ":data_name"),
|
||||
pxr::SdfValueTypeNames->String,
|
||||
true)
|
||||
.Set<std::string>(std::string(id.name + 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (id.properties) {
|
||||
write_user_properties(prim, id.properties, time);
|
||||
}
|
||||
}
|
||||
|
||||
void USDAbstractWriter::write_user_properties(const pxr::UsdPrim &prim,
|
||||
IDProperty *properties,
|
||||
pxr::UsdTimeCode time) const
|
||||
{
|
||||
if (properties == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (properties->type != IDP_GROUP) {
|
||||
return;
|
||||
}
|
||||
|
||||
const StringRef displayName_identifier = "displayName";
|
||||
|
||||
const std::string default_namespace(
|
||||
usd_export_context_.export_params.custom_properties_namespace);
|
||||
|
||||
for (IDProperty *prop = static_cast<IDProperty *>(properties->data.group.first); prop;
|
||||
prop = prop->next)
|
||||
{
|
||||
if (displayName_identifier == prop->name) {
|
||||
if (prop->type == IDP_STRING && prop->data.pointer) {
|
||||
prim.SetDisplayName(static_cast<char *>(prop->data.pointer));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto accessibility_property_name = parse_accessibility_property_name(
|
||||
prop, usd_export_context_.export_params.allow_unicode))
|
||||
{
|
||||
if (prop->type == IDP_STRING && prop->data.pointer) {
|
||||
write_accessibility_property(
|
||||
prim, *accessibility_property_name, static_cast<char *>(prop->data.pointer));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> path_names = pxr::TfStringTokenize(prop->name, ":");
|
||||
|
||||
/* If the path does not already have a namespace prefix, prepend the default namespace
|
||||
* specified by the user, if any. */
|
||||
if (!default_namespace.empty() && path_names.size() < 2) {
|
||||
path_names.insert(path_names.begin(), default_namespace);
|
||||
}
|
||||
|
||||
std::vector<std::string> safe_names;
|
||||
for (const std::string &name : path_names) {
|
||||
safe_names.push_back(make_safe_name(name, usd_export_context_.export_params.allow_unicode));
|
||||
}
|
||||
|
||||
std::string full_prop_name = pxr::SdfPath::JoinIdentifier(safe_names);
|
||||
pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
|
||||
|
||||
if (prim.HasAttribute(prop_token)) {
|
||||
/* Don't overwrite existing attributes, as these may have been
|
||||
* created by the exporter logic and shouldn't be changed. */
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (prop->type) {
|
||||
case IDP_INT:
|
||||
if (pxr::UsdAttribute int_attr = prim.CreateAttribute(
|
||||
prop_token, pxr::SdfValueTypeNames->Int, true))
|
||||
{
|
||||
int_attr.Set<int>(prop->data.val, time);
|
||||
}
|
||||
break;
|
||||
case IDP_FLOAT:
|
||||
if (pxr::UsdAttribute float_attr = prim.CreateAttribute(
|
||||
prop_token, pxr::SdfValueTypeNames->Float, true))
|
||||
{
|
||||
float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), time);
|
||||
}
|
||||
break;
|
||||
case IDP_DOUBLE:
|
||||
if (pxr::UsdAttribute double_attr = prim.CreateAttribute(
|
||||
prop_token, pxr::SdfValueTypeNames->Double, true))
|
||||
{
|
||||
double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), time);
|
||||
}
|
||||
break;
|
||||
case IDP_STRING:
|
||||
if (pxr::UsdAttribute str_attr = prim.CreateAttribute(
|
||||
prop_token, pxr::SdfValueTypeNames->String, true))
|
||||
{
|
||||
str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), time);
|
||||
}
|
||||
break;
|
||||
case IDP_BOOLEAN:
|
||||
if (pxr::UsdAttribute bool_attr = prim.CreateAttribute(
|
||||
prop_token, pxr::SdfValueTypeNames->Bool, true))
|
||||
{
|
||||
bool_attr.Set<bool>(prop->data.val, time);
|
||||
}
|
||||
break;
|
||||
case IDP_ARRAY:
|
||||
create_vector_attrib(prim, prop, prop_token, time);
|
||||
break;
|
||||
case IDP_GROUP:
|
||||
case IDP_ID:
|
||||
case IDP_IDPARRAY:
|
||||
/* Not supported. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDAbstractWriter::author_extent(const pxr::UsdGeomBoundable &boundable,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
/* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when
|
||||
* authoring it. */
|
||||
const bool useExtentsHint = false;
|
||||
const pxr::TfTokenVector includedPurposes{pxr::UsdGeomTokens->default_};
|
||||
pxr::UsdGeomBBoxCache bboxCache(time, includedPurposes, useExtentsHint);
|
||||
pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(boundable.GetPrim());
|
||||
|
||||
/* Note: An empty 'bounds' is still valid (e.g. a mesh with no vertices). */
|
||||
pxr::VtArray<pxr::GfVec3f> extent{pxr::GfVec3f(bounds.GetRange().GetMin()),
|
||||
pxr::GfVec3f(bounds.GetRange().GetMax())};
|
||||
|
||||
pxr::UsdAttribute attr_extent = boundable.CreateExtentAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_extent, extent, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDAbstractWriter::author_extent(const pxr::UsdGeomBoundable &boundable,
|
||||
const std::optional<Bounds<float3>> &bounds,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::VtArray<pxr::GfVec3f> extent(2);
|
||||
if (bounds) {
|
||||
extent[0].Set(bounds->min);
|
||||
extent[1].Set(bounds->max);
|
||||
}
|
||||
|
||||
pxr::UsdAttribute attr_extent = boundable.CreateExtentAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_extent, extent, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDAbstractWriter::add_to_prim_map(const pxr::SdfPath &usd_path, const ID *id) const
|
||||
{
|
||||
if (usd_export_context_.hierarchy_iterator) {
|
||||
usd_export_context_.hierarchy_iterator->add_to_prim_map(usd_path, id);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,126 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
#include "usd_exporter_context.hh"
|
||||
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdGeom/boundable.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdUtils/sparseValueWriter.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ID;
|
||||
struct IDProperty;
|
||||
struct Material;
|
||||
struct ReportList;
|
||||
|
||||
template<typename T> struct Bounds;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
using io::AbstractHierarchyWriter;
|
||||
using io::HierarchyContext;
|
||||
|
||||
class USDAbstractWriter : public AbstractHierarchyWriter {
|
||||
protected:
|
||||
const USDExporterContext usd_export_context_;
|
||||
pxr::UsdUtilsSparseValueWriter usd_value_writer_;
|
||||
|
||||
bool frame_has_been_written_;
|
||||
bool is_animated_;
|
||||
|
||||
public:
|
||||
USDAbstractWriter(const USDExporterContext &usd_export_context);
|
||||
|
||||
void write(HierarchyContext &context) override;
|
||||
|
||||
/**
|
||||
* Returns true if the data to be written is actually supported. This would, for example, allow a
|
||||
* hypothetical camera writer accept a perspective camera but reject an orthogonal one.
|
||||
*
|
||||
* Returning false from a transform writer will prevent the object and all its descendants from
|
||||
* being exported. Returning false from a data writer (object data, hair, or particles) will
|
||||
* only prevent that data from being written (and thus cause the object to be exported as an
|
||||
* Empty).
|
||||
*/
|
||||
virtual bool is_supported(const HierarchyContext *context) const;
|
||||
|
||||
const pxr::SdfPath &usd_path() const;
|
||||
|
||||
/** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */
|
||||
ReportList *reports() const;
|
||||
|
||||
protected:
|
||||
virtual void do_write(HierarchyContext &context) = 0;
|
||||
std::string get_export_file_path() const;
|
||||
pxr::UsdTimeCode get_export_time_code() const;
|
||||
|
||||
/* Returns the parent path of exported materials. */
|
||||
pxr::SdfPath get_material_library_path() const;
|
||||
/* Returns the parent path of exported materials for instance prototypes. */
|
||||
pxr::SdfPath get_proto_material_root_path(const HierarchyContext &context) const;
|
||||
/* Ensure the USD material is created in the default material library folder. */
|
||||
pxr::UsdShadeMaterial ensure_usd_material_created(const HierarchyContext &context,
|
||||
Material *material) const;
|
||||
/* Calls ensure_usd_material_created(). Additionally, if the context is an
|
||||
* instancing prototype, creates a reference to the library material under the
|
||||
* prototype root. */
|
||||
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context,
|
||||
Material *material) const;
|
||||
|
||||
void write_id_properties(const pxr::UsdPrim &prim,
|
||||
const ID &id,
|
||||
pxr::UsdTimeCode = pxr::UsdTimeCode::Default()) const;
|
||||
void write_user_properties(const pxr::UsdPrim &prim,
|
||||
IDProperty *properties,
|
||||
pxr::UsdTimeCode = pxr::UsdTimeCode::Default()) const;
|
||||
|
||||
void write_visibility(const HierarchyContext &context,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::UsdGeomImageable &usd_geometry);
|
||||
|
||||
/**
|
||||
* Turn `prim` into an instance referencing `context.original_export_path`.
|
||||
* Return true when the instancing was successful, false otherwise.
|
||||
*
|
||||
* Reference the original data instead of writing a copy.
|
||||
*/
|
||||
virtual bool mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim);
|
||||
|
||||
/**
|
||||
* Compute the bounds for a boundable prim, and author the result as the `extent` attribute.
|
||||
*
|
||||
* Although this method works for any boundable prim, it is preferred to use Blender's own
|
||||
* cached bounds when possible.
|
||||
*
|
||||
* This method does not author the `extentsHint` attribute, which is also important to provide.
|
||||
* Whereas the `extent` attribute can only be authored on prims inheriting from
|
||||
* `UsdGeomBoundable`, an `extentsHint` can be provided on any prim, including scopes. This
|
||||
* `extentsHint` should be authored on every prim in a hierarchy being exported.
|
||||
*
|
||||
* Note that this hint is only useful when importing or inspecting layers, and should not be
|
||||
* taken into account when computing extents during export.
|
||||
*
|
||||
* TODO: also provide method for authoring extentsHint on every prim in a hierarchy.
|
||||
*/
|
||||
void author_extent(const pxr::UsdGeomBoundable &boundable, const pxr::UsdTimeCode time);
|
||||
|
||||
/**
|
||||
* Author the `extent` attribute for a boundable prim given the Blender `bounds`.
|
||||
*/
|
||||
void author_extent(const pxr::UsdGeomBoundable &boundable,
|
||||
const std::optional<Bounds<float3>> &bounds,
|
||||
const pxr::UsdTimeCode time);
|
||||
|
||||
void add_to_prim_map(const pxr::SdfPath &usd_path, const ID *id) const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,233 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_writer_armature.hh"
|
||||
#include "usd_armature_utils.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "BKE_action.hh"
|
||||
|
||||
#include "DNA_armature_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/base/gf/matrix4d.h>
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdSkel/animation.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/skeleton.h>
|
||||
#include <pxr/usd/usdSkel/utils.h>
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
/**
|
||||
* Get the pose matrix for the given channel.
|
||||
* The matrix is computed relative to its parent, if a parent exists.
|
||||
* The returned matrix corresponds to the USD joint-local transform.
|
||||
*/
|
||||
static pxr::GfMatrix4d parent_relative_pose_mat(const bPoseChannel *pchan)
|
||||
{
|
||||
/* Note that the float matrix will be returned as GfMatrix4d, because
|
||||
* USD requires doubles. */
|
||||
const pxr::GfMatrix4f pose_mat(pchan->pose_mat);
|
||||
|
||||
if (pchan->parent) {
|
||||
const pxr::GfMatrix4f parent_pose_mat(pchan->parent->pose_mat);
|
||||
const pxr::GfMatrix4f xf = pose_mat * parent_pose_mat.GetInverse();
|
||||
return pxr::GfMatrix4d(xf);
|
||||
}
|
||||
|
||||
/* No parent, so return the pose matrix directly. */
|
||||
return pxr::GfMatrix4d(pose_mat);
|
||||
}
|
||||
|
||||
/* Initialize the given skeleton and animation from
|
||||
* the given armature object. */
|
||||
static void initialize(const Object *obj,
|
||||
pxr::UsdSkelSkeleton &skel,
|
||||
pxr::UsdSkelAnimation &skel_anim,
|
||||
const Map<StringRef, const Bone *> *deform_bones,
|
||||
bool allow_unicode)
|
||||
{
|
||||
using namespace blender::io::usd;
|
||||
|
||||
pxr::VtTokenArray joints;
|
||||
pxr::VtArray<float> bone_lengths;
|
||||
pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
|
||||
pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
|
||||
|
||||
/* Function to collect the bind and rest transforms from each bone. */
|
||||
auto visitor = [&](const Bone *bone) {
|
||||
if (!bone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (deform_bones && !deform_bones->contains(bone->name)) {
|
||||
/* If deform_map is passed in, assume we're going deform-only.
|
||||
* Bones not found in the map should be skipped. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store Blender bone lengths to facilitate better round-tripping. */
|
||||
bone_lengths.push_back(bone->length);
|
||||
|
||||
joints.push_back(build_usd_joint_path(bone, allow_unicode));
|
||||
const pxr::GfMatrix4f arm_mat(bone->arm_mat);
|
||||
bind_xforms.push_back(pxr::GfMatrix4d(arm_mat));
|
||||
|
||||
/* Set the rest transform to the parent-relative pose matrix, or the parent-relative
|
||||
* armature matrix, if no pose channel exists. */
|
||||
if (const bPoseChannel *pchan = BKE_pose_channel_find_name(obj->pose, bone->name)) {
|
||||
rest_xforms.push_back(parent_relative_pose_mat(pchan));
|
||||
}
|
||||
else if (bone->parent) {
|
||||
pxr::GfMatrix4f parent_arm_mat(bone->parent->arm_mat);
|
||||
const pxr::GfMatrix4f rest_mat = arm_mat * parent_arm_mat.GetInverse();
|
||||
rest_xforms.push_back(pxr::GfMatrix4d(rest_mat));
|
||||
}
|
||||
else {
|
||||
rest_xforms.push_back(pxr::GfMatrix4d(arm_mat));
|
||||
}
|
||||
};
|
||||
|
||||
visit_bones(obj, visitor);
|
||||
skel.GetJointsAttr().Set(joints);
|
||||
skel.GetBindTransformsAttr().Set(bind_xforms);
|
||||
skel.GetRestTransformsAttr().Set(rest_xforms);
|
||||
|
||||
const pxr::UsdPrim skel_prim = skel.GetPrim();
|
||||
|
||||
/* Store the custom bone lengths as just a regular Primvar attached to the Skeleton. */
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(skel_prim);
|
||||
pxr::UsdGeomPrimvar pv_lengths = pv_api.CreatePrimvar(
|
||||
BlenderBoneLengths, pxr::SdfValueTypeNames->FloatArray, pxr::UsdGeomTokens->uniform);
|
||||
pv_lengths.Set(bone_lengths);
|
||||
|
||||
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim);
|
||||
|
||||
if (skel_anim) {
|
||||
usd_skel_api.CreateAnimationSourceRel().SetTargets(
|
||||
pxr::SdfPathVector({pxr::SdfPath(skel_anim.GetPath().GetName())}));
|
||||
create_pose_joints(skel_anim, *obj, deform_bones, allow_unicode);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Add skeleton transform samples from the armature pose channels. */
|
||||
static void add_anim_sample(pxr::UsdSkelAnimation &skel_anim,
|
||||
const Object *obj,
|
||||
const pxr::UsdTimeCode time,
|
||||
const Map<StringRef, const Bone *> *deform_map,
|
||||
pxr::UsdUtilsSparseValueWriter &value_writer)
|
||||
{
|
||||
if (!(skel_anim && obj && obj->pose)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtArray<pxr::GfMatrix4d> xforms;
|
||||
|
||||
const bPose *pose = obj->pose;
|
||||
|
||||
for (const bPoseChannel &pchan : pose->chanbase) {
|
||||
if (deform_map && !deform_map->contains(pchan.name)) {
|
||||
/* If deform_map is passed in, assume we're going deform-only.
|
||||
* Bones not found in the map should be skipped. */
|
||||
continue;
|
||||
}
|
||||
|
||||
xforms.push_back(parent_relative_pose_mat(&pchan));
|
||||
}
|
||||
|
||||
/* Perform the same steps as UsdSkelAnimation::SetTransforms but write data out sparsely. */
|
||||
pxr::VtArray<pxr::GfVec3f> translations;
|
||||
pxr::VtArray<pxr::GfQuatf> rotations;
|
||||
pxr::VtArray<pxr::GfVec3h> scales;
|
||||
if (pxr::UsdSkelDecomposeTransforms(xforms, &translations, &rotations, &scales)) {
|
||||
set_attribute(skel_anim.GetTranslationsAttr(), translations, time, value_writer);
|
||||
set_attribute(skel_anim.GetRotationsAttr(), rotations, time, value_writer);
|
||||
set_attribute(skel_anim.GetScalesAttr(), scales, time, value_writer);
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG, "Could not decompose skeleton transforms for frame time %f", time.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
USDArmatureWriter::USDArmatureWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
void USDArmatureWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
if (!(context.object && context.object->type == OB_ARMATURE && context.object->data)) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the skeleton. */
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
pxr::UsdSkelSkeleton skel = pxr::UsdSkelSkeleton::Define(stage, usd_export_context_.usd_path);
|
||||
|
||||
if (!skel) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't define UsdSkelSkeleton %s",
|
||||
usd_export_context_.usd_path.GetString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelAnimation skel_anim;
|
||||
|
||||
const bool allow_unicode = usd_export_context_.export_params.allow_unicode;
|
||||
|
||||
if (usd_export_context_.export_params.export_animation) {
|
||||
/* Use the action name as the animation name. */
|
||||
const animrig::Action *action = animrig::get_action(context.object->id);
|
||||
const pxr::TfToken anim_name(action ? make_safe_name(action->id.name + 2, allow_unicode) :
|
||||
"Action");
|
||||
|
||||
/* Create the skeleton animation primitive as a child of the skeleton. */
|
||||
pxr::SdfPath anim_path = usd_export_context_.usd_path.AppendChild(anim_name);
|
||||
skel_anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
|
||||
|
||||
if (!skel_anim) {
|
||||
CLOG_WARN(&LOG, "Couldn't define UsdSkelAnimation %s", anim_path.GetString().c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Map<StringRef, const Bone *> *deform_map = usd_export_context_.export_params.only_deform_bones ?
|
||||
&deform_map_ :
|
||||
nullptr;
|
||||
|
||||
if (!this->frame_has_been_written_) {
|
||||
init_deform_bones_map(context.object, deform_map);
|
||||
initialize(context.object, skel, skel_anim, deform_map, allow_unicode);
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.export_animation) {
|
||||
add_anim_sample(
|
||||
skel_anim, context.object, get_export_time_code(), deform_map, usd_value_writer_);
|
||||
}
|
||||
}
|
||||
|
||||
bool USDArmatureWriter::check_is_animated(const HierarchyContext &context) const
|
||||
{
|
||||
const Object *obj = context.object;
|
||||
|
||||
if (!(obj && obj->type == OB_ARMATURE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return obj->adt != nullptr;
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Bone;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDArmatureWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDArmatureWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
bool check_is_animated(const HierarchyContext &context) const override;
|
||||
|
||||
private:
|
||||
Map<StringRef, const Bone *> deform_map_;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
132
blender-5.2.0/source/blender/io/usd/intern/usd_writer_camera.cc
Normal file
132
blender-5.2.0/source/blender/io/usd/intern/usd_writer_camera.cc
Normal file
@@ -0,0 +1,132 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_camera.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/camera.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
|
||||
#include "BKE_camera.h"
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
bool USDCameraWriter::is_supported(const HierarchyContext *context) const
|
||||
{
|
||||
const Camera *camera = id_cast<const Camera *>(context->object->data);
|
||||
return camera->type == CAM_PERSP;
|
||||
}
|
||||
|
||||
static void camera_sensor_size_for_render(const Camera *camera,
|
||||
const RenderData *rd,
|
||||
float *r_sensor,
|
||||
float *r_sensor_x,
|
||||
float *r_sensor_y)
|
||||
{
|
||||
/* Compute the final image size in pixels. */
|
||||
float sizex = rd->xsch * rd->xasp;
|
||||
float sizey = rd->ysch * rd->yasp;
|
||||
|
||||
int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey);
|
||||
float sensor_size = BKE_camera_sensor_size(
|
||||
camera->sensor_fit, camera->sensor_x, camera->sensor_y);
|
||||
*r_sensor = sensor_size;
|
||||
|
||||
switch (sensor_fit) {
|
||||
case CAMERA_SENSOR_FIT_HOR:
|
||||
*r_sensor_x = sensor_size;
|
||||
*r_sensor_y = sensor_size * sizey / sizex;
|
||||
break;
|
||||
case CAMERA_SENSOR_FIT_VERT:
|
||||
*r_sensor_x = sensor_size * sizex / sizey;
|
||||
*r_sensor_y = sensor_size;
|
||||
break;
|
||||
case CAMERA_SENSOR_FIT_AUTO:
|
||||
BLI_assert_msg(0, "Camera fit should be either horizontal or vertical");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void USDCameraWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
const double meters_per_unit = get_meters_per_unit(usd_export_context_.export_params);
|
||||
const float unit_scale = float(1.0 / meters_per_unit);
|
||||
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage,
|
||||
usd_export_context_.usd_path);
|
||||
|
||||
const Camera *camera = id_cast<const Camera *>(context.object->data);
|
||||
const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
|
||||
|
||||
usd_camera.CreateProjectionAttr().Set(pxr::UsdGeomTokens->perspective);
|
||||
|
||||
/*
|
||||
* For USD, these camera properties are in tenths of a world unit.
|
||||
* https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits
|
||||
*
|
||||
* tenth_unit_to_meters = stage_meters_per_unit / 10
|
||||
* tenth_unit_to_millimeters = 1000 * unit_to_tenth_unit
|
||||
* = 100 * stage_meters_per_unit
|
||||
*/
|
||||
const float tenth_unit_to_mm = float(100.0 * meters_per_unit * scene->unit.scale_length);
|
||||
|
||||
float sensor_size, aperture_x, aperture_y;
|
||||
camera_sensor_size_for_render(camera, &scene->r, &sensor_size, &aperture_x, &aperture_y);
|
||||
|
||||
set_attribute(usd_camera.CreateFocalLengthAttr(pxr::VtValue(), true),
|
||||
camera->lens / tenth_unit_to_mm,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateHorizontalApertureAttr(pxr::VtValue(), true),
|
||||
aperture_x / tenth_unit_to_mm,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateVerticalApertureAttr(pxr::VtValue(), true),
|
||||
aperture_y / tenth_unit_to_mm,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateHorizontalApertureOffsetAttr(pxr::VtValue(), true),
|
||||
sensor_size * camera->shiftx / tenth_unit_to_mm,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateVerticalApertureOffsetAttr(pxr::VtValue(), true),
|
||||
sensor_size * camera->shifty / tenth_unit_to_mm,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateClippingRangeAttr(pxr::VtValue(), true),
|
||||
pxr::GfVec2f(camera->clip_start * unit_scale, camera->clip_end * unit_scale),
|
||||
time,
|
||||
usd_value_writer_);
|
||||
|
||||
/* Write DoF-related attributes. */
|
||||
if (camera->dof.flag & CAM_DOF_ENABLED) {
|
||||
const float focus_distance = BKE_camera_object_dof_distance(context.object);
|
||||
set_attribute(usd_camera.CreateFStopAttr(pxr::VtValue(), true),
|
||||
camera->dof.aperture_fstop,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_camera.CreateFocusDistanceAttr(pxr::VtValue(), true),
|
||||
focus_distance * unit_scale,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
}
|
||||
else {
|
||||
set_attribute(usd_camera.CreateFStopAttr(pxr::VtValue(), true), 0.0f, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
auto prim = usd_camera.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &camera->id);
|
||||
write_id_properties(prim, camera->id, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,20 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* Writer for writing camera data to UsdGeomCamera. */
|
||||
class USDCameraWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDCameraWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
bool is_supported(const HierarchyContext *context) const override;
|
||||
void do_write(HierarchyContext &context) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
737
blender-5.2.0/source/blender/io/usd/intern/usd_writer_curves.cc
Normal file
737
blender-5.2.0/source/blender/io/usd/intern/usd_writer_curves.cc
Normal file
@@ -0,0 +1,737 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cstdint>
|
||||
#include <numeric>
|
||||
|
||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||
#include <pxr/usd/usdGeom/curves.h>
|
||||
#include <pxr/usd/usdGeom/nurbsCurves.h>
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_curves.hh"
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_generic_virtual_array.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curve_legacy_convert.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_material.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
pxr::UsdGeomBasisCurves USDCurvesWriter::DefineUsdGeomBasisCurves(pxr::VtValue curve_basis,
|
||||
const bool is_cyclic,
|
||||
const bool is_cubic) const
|
||||
{
|
||||
pxr::UsdGeomBasisCurves basis_curves = pxr::UsdGeomBasisCurves::Define(
|
||||
usd_export_context_.stage, usd_export_context_.usd_path);
|
||||
/* Not required to set the basis attribute for linear curves
|
||||
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_basis_curves.html#details */
|
||||
if (is_cubic) {
|
||||
basis_curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
||||
basis_curves.CreateBasisAttr(curve_basis);
|
||||
}
|
||||
else {
|
||||
basis_curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
|
||||
}
|
||||
|
||||
if (is_cyclic) {
|
||||
basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->periodic));
|
||||
}
|
||||
else if (curve_basis == pxr::VtValue(pxr::UsdGeomTokens->catmullRom)) {
|
||||
/* In Blender the first and last points are treated as endpoints. The pinned attribute tells
|
||||
* the client that to evaluate or render the curve, it must effectively add 'phantom
|
||||
* points' at the beginning and end of every curve in a batch. These phantom points are
|
||||
* injected to ensure that the interpolated curve begins at P[0] and ends at P[n-1]. */
|
||||
basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->pinned));
|
||||
}
|
||||
else {
|
||||
basis_curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
|
||||
}
|
||||
|
||||
return basis_curves;
|
||||
}
|
||||
|
||||
static void populate_curve_widths(const bke::CurvesGeometry &curves, pxr::VtArray<float> &widths)
|
||||
{
|
||||
const VArray<float> radii = curves.radius();
|
||||
|
||||
widths.resize(radii.size());
|
||||
for (const int i : radii.index_range()) {
|
||||
widths[i] = radii[i] * 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray<float> &widths,
|
||||
const pxr::VtArray<int> &segments,
|
||||
const pxr::VtIntArray &control_point_counts,
|
||||
const bool is_cyclic,
|
||||
ReportList *reports)
|
||||
{
|
||||
if (widths.empty()) {
|
||||
return pxr::TfToken();
|
||||
}
|
||||
|
||||
const size_t accumulated_control_point_count = std::accumulate(
|
||||
control_point_counts.begin(), control_point_counts.end(), 0);
|
||||
|
||||
/* For Blender curves, radii are always stored per point. For linear curves, this should match
|
||||
* with USD's vertex interpolation. For cubic curves, this should match with USD's varying
|
||||
* interpolation. */
|
||||
if (widths.size() == accumulated_control_point_count) {
|
||||
return pxr::UsdGeomTokens->vertex;
|
||||
}
|
||||
|
||||
size_t expectedVaryingSize = std::accumulate(segments.begin(), segments.end(), 0);
|
||||
if (!is_cyclic) {
|
||||
expectedVaryingSize += control_point_counts.size();
|
||||
}
|
||||
|
||||
if (widths.size() == expectedVaryingSize) {
|
||||
return pxr::UsdGeomTokens->varying;
|
||||
}
|
||||
|
||||
BKE_report(reports, RPT_WARNING, "Curve width size not supported for USD interpolation");
|
||||
return pxr::TfToken();
|
||||
}
|
||||
|
||||
static void populate_curve_verts(const bke::CurvesGeometry &curves,
|
||||
const Span<float3> positions,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<int> &segments,
|
||||
const bool is_cyclic,
|
||||
const bool is_cubic)
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
|
||||
const IndexRange points = points_by_curve[i_curve];
|
||||
for (const int i_point : points) {
|
||||
verts.push_back(
|
||||
pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
|
||||
}
|
||||
|
||||
const int tot_points = points.size();
|
||||
control_point_counts[i_curve] = tot_points;
|
||||
|
||||
/* For periodic linear curve, segment count = curveVertexCount.
|
||||
* For periodic cubic curve, segment count = curveVertexCount / vstep.
|
||||
* For nonperiodic linear curve, segment count = curveVertexCount - 1.
|
||||
* For nonperiodic cubic curve, segment count = ((curveVertexCount - 4) / vstep) + 1.
|
||||
* This function handles linear and Catmull-Rom curves. For Catmull-Rom, vstep is 1.
|
||||
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_basis_curves.html */
|
||||
if (is_cyclic) {
|
||||
segments[i_curve] = tot_points;
|
||||
}
|
||||
else if (is_cubic) {
|
||||
segments[i_curve] = (tot_points - 4) + 1;
|
||||
}
|
||||
else {
|
||||
segments[i_curve] = tot_points - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void populate_curve_props(const bke::CurvesGeometry &curves,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<float> &widths,
|
||||
pxr::TfToken &interpolation,
|
||||
const bool is_cyclic,
|
||||
const bool is_cubic,
|
||||
ReportList *reports)
|
||||
{
|
||||
const int num_curves = curves.curve_num;
|
||||
const Span<float3> positions = curves.positions();
|
||||
|
||||
pxr::VtArray<int> segments(num_curves);
|
||||
|
||||
populate_curve_verts(
|
||||
curves, positions, verts, control_point_counts, segments, is_cyclic, is_cubic);
|
||||
|
||||
populate_curve_widths(curves, widths);
|
||||
interpolation = get_curve_width_interpolation(
|
||||
widths, segments, control_point_counts, is_cyclic, reports);
|
||||
}
|
||||
|
||||
static void populate_curve_verts_for_bezier(const bke::CurvesGeometry &curves,
|
||||
const Span<float3> positions,
|
||||
const Span<float3> handles_l,
|
||||
const Span<float3> handles_r,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<int> &segments,
|
||||
const bool is_cyclic)
|
||||
{
|
||||
const int bezier_vstep = 3;
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
|
||||
const IndexRange points = points_by_curve[i_curve];
|
||||
const int start_point_index = points[0];
|
||||
const int last_point_index = points[points.size() - 1];
|
||||
|
||||
const int start_verts_count = verts.size();
|
||||
|
||||
for (int i_point = start_point_index; i_point < last_point_index; i_point++) {
|
||||
|
||||
/* The order verts in the USD bezier curve representation is [control point 0, right handle
|
||||
* 0, left handle 1, control point 1, right handle 1, left handle 2, control point 2, ...].
|
||||
* The last vert in the array doesn't need a right handle because the curve stops at that
|
||||
* point. */
|
||||
verts.push_back(
|
||||
pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
|
||||
|
||||
const float3 right_handle = handles_r[i_point];
|
||||
verts.push_back(pxr::GfVec3f(right_handle[0], right_handle[1], right_handle[2]));
|
||||
|
||||
const float3 left_handle = handles_l[i_point + 1];
|
||||
verts.push_back(pxr::GfVec3f(left_handle[0], left_handle[1], left_handle[2]));
|
||||
}
|
||||
|
||||
verts.push_back(pxr::GfVec3f(positions[last_point_index][0],
|
||||
positions[last_point_index][1],
|
||||
positions[last_point_index][2]));
|
||||
|
||||
/* For USD periodic bezier curves, since the curve is closed, we need to include
|
||||
* the right handle of the last point and the left handle of the first point.
|
||||
*/
|
||||
if (is_cyclic) {
|
||||
const float3 right_handle = handles_r[last_point_index];
|
||||
verts.push_back(pxr::GfVec3f(right_handle[0], right_handle[1], right_handle[2]));
|
||||
|
||||
const float3 left_handle = handles_l[start_point_index];
|
||||
verts.push_back(pxr::GfVec3f(left_handle[0], left_handle[1], left_handle[2]));
|
||||
}
|
||||
|
||||
const int tot_points = verts.size() - start_verts_count;
|
||||
control_point_counts[i_curve] = tot_points;
|
||||
|
||||
if (is_cyclic) {
|
||||
segments[i_curve] = tot_points / bezier_vstep;
|
||||
}
|
||||
else {
|
||||
segments[i_curve] = ((tot_points - 4) / bezier_vstep) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void populate_curve_props_for_bezier(const bke::CurvesGeometry &curves,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<float> &widths,
|
||||
pxr::TfToken &interpolation,
|
||||
const bool is_cyclic,
|
||||
ReportList *reports)
|
||||
{
|
||||
const int num_curves = curves.curve_num;
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
const std::optional<Span<float3>> handles_l = curves.handle_positions_left();
|
||||
const std::optional<Span<float3>> handles_r = curves.handle_positions_right();
|
||||
|
||||
pxr::VtArray<int> segments(num_curves);
|
||||
|
||||
populate_curve_verts_for_bezier(curves,
|
||||
positions,
|
||||
handles_l.value_or(Span<float3>{}),
|
||||
handles_r.value_or(Span<float3>{}),
|
||||
verts,
|
||||
control_point_counts,
|
||||
segments,
|
||||
is_cyclic);
|
||||
|
||||
populate_curve_widths(curves, widths);
|
||||
interpolation = get_curve_width_interpolation(
|
||||
widths, segments, control_point_counts, is_cyclic, reports);
|
||||
}
|
||||
|
||||
static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &curves,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<float> &widths,
|
||||
pxr::VtArray<double> &knots,
|
||||
pxr::VtArray<double> &weights,
|
||||
pxr::VtArray<int> &orders,
|
||||
pxr::TfToken &interpolation,
|
||||
const bool is_cyclic)
|
||||
{
|
||||
/* Order and range, when representing a batched NurbsCurve should be authored one value per
|
||||
* curve. */
|
||||
const int num_curves = curves.curve_num;
|
||||
orders.resize(num_curves);
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
const Span<float> custom_knots = curves.nurbs_custom_knots();
|
||||
const std::optional<Span<float>> nurbs_weights = curves.nurbs_weights();
|
||||
|
||||
VArray<int8_t> geom_orders = curves.nurbs_orders();
|
||||
VArray<int8_t> knots_modes = curves.nurbs_knots_modes();
|
||||
const VArray<float> radii = curves.radius();
|
||||
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
const OffsetIndices custom_knots_by_curve = curves.nurbs_custom_knots_by_curve();
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[i_curve];
|
||||
const size_t curr_vert_num = verts.size();
|
||||
for (const int i_point : points) {
|
||||
verts.push_back(
|
||||
pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
|
||||
widths.push_back(radii[i_point] * 2.0f);
|
||||
}
|
||||
|
||||
if (nurbs_weights) {
|
||||
for (const int i_point : points) {
|
||||
weights.push_back((*nurbs_weights)[i_point]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Repeat the first degree(order - 1) number of points and weights if curve is cyclic. */
|
||||
if (is_cyclic) {
|
||||
for (const int i_point : points.take_front(geom_orders[i_curve] - 1)) {
|
||||
verts.push_back(
|
||||
pxr::GfVec3f(positions[i_point][0], positions[i_point][1], positions[i_point][2]));
|
||||
widths.push_back(radii[i_point] * 2.0f);
|
||||
if (nurbs_weights) {
|
||||
weights.push_back((*nurbs_weights)[i_point]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int tot_blender_points = int(points.size());
|
||||
const int tot_usd_points = int(verts.size() - curr_vert_num);
|
||||
control_point_counts[i_curve] = tot_usd_points;
|
||||
|
||||
const int8_t order = geom_orders[i_curve];
|
||||
orders[i_curve] = int(geom_orders[i_curve]);
|
||||
|
||||
const KnotsMode mode = KnotsMode(knots_modes[i_curve]);
|
||||
|
||||
const int knots_num = bke::curves::nurbs::knots_num(tot_blender_points, order, is_cyclic);
|
||||
Array<float> temp_knots(knots_num);
|
||||
bke::curves::nurbs::load_curve_knots(mode,
|
||||
tot_blender_points,
|
||||
order,
|
||||
is_cyclic,
|
||||
custom_knots_by_curve[i_curve],
|
||||
custom_knots,
|
||||
temp_knots);
|
||||
|
||||
/* Knots should be the concatenation of all batched curves.
|
||||
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
|
||||
for (int i_knot = 0; i_knot < knots_num; i_knot++) {
|
||||
knots.push_back(double(temp_knots[i_knot]));
|
||||
}
|
||||
|
||||
/* For USD it is required to set specific end knots for periodic/non-periodic curves
|
||||
* https://graphics.pixar.com/usd/dev/api/class_usd_geom_nurbs_curves.html#details */
|
||||
int zeroth_knot_index = knots.size() - knots_num;
|
||||
if (is_cyclic) {
|
||||
knots[zeroth_knot_index] = knots[zeroth_knot_index + 1] -
|
||||
(knots[knots.size() - 2] - knots[knots.size() - 3]);
|
||||
knots[knots.size() - 1] = knots[knots.size() - 2] +
|
||||
(knots[zeroth_knot_index + 2] - knots[zeroth_knot_index + 1]);
|
||||
}
|
||||
else {
|
||||
knots[zeroth_knot_index] = knots[zeroth_knot_index + 1];
|
||||
knots[knots.size() - 1] = knots[knots.size() - 2];
|
||||
}
|
||||
}
|
||||
|
||||
interpolation = pxr::UsdGeomTokens->vertex;
|
||||
}
|
||||
|
||||
void USDCurvesWriter::set_writer_attributes_for_nurbs(
|
||||
const pxr::UsdGeomNurbsCurves &usd_nurbs_curves,
|
||||
pxr::VtArray<double> &knots,
|
||||
pxr::VtArray<double> &weights,
|
||||
pxr::VtArray<int> &orders,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
pxr::UsdAttribute attr_knots = usd_nurbs_curves.CreateKnotsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_knots, knots, time, usd_value_writer_);
|
||||
pxr::UsdAttribute attr_weights = usd_nurbs_curves.CreatePointWeightsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_weights, weights, time, usd_value_writer_);
|
||||
pxr::UsdAttribute attr_order = usd_nurbs_curves.CreateOrderAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_order, orders, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::set_writer_attributes(pxr::UsdGeomCurves &usd_curves,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<float> &widths,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::TfToken interpolation)
|
||||
{
|
||||
pxr::UsdAttribute attr_points = usd_curves.CreatePointsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_points, verts, time, usd_value_writer_);
|
||||
|
||||
pxr::UsdAttribute attr_counts = usd_curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_counts, control_point_counts, time, usd_value_writer_);
|
||||
|
||||
pxr::UsdAttribute attr_widths = usd_curves.CreateWidthsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_widths, widths, time, usd_value_writer_);
|
||||
|
||||
if (!interpolation.IsEmpty()) {
|
||||
usd_curves.SetWidthsInterpolation(interpolation);
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
|
||||
const bke::AttrDomain blender_domain, bool is_bezier)
|
||||
{
|
||||
switch (blender_domain) {
|
||||
case bke::AttrDomain::Point:
|
||||
return is_bezier ? pxr::UsdGeomTokens->varying : pxr::UsdGeomTokens->vertex;
|
||||
case bke::AttrDomain::Curve:
|
||||
return pxr::UsdGeomTokens->uniform;
|
||||
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/* Excluded attributes are those which are handled through native USD concepts
|
||||
* and should not be exported as generic attributes. */
|
||||
static bool is_excluded_attr(StringRefNull name)
|
||||
{
|
||||
static const Set<StringRefNull> excluded_attrs = []() {
|
||||
Set<StringRefNull> set;
|
||||
set.add_new("position");
|
||||
set.add_new("radius");
|
||||
set.add_new("resolution");
|
||||
set.add_new("id");
|
||||
set.add_new("cyclic");
|
||||
set.add_new("curve_type");
|
||||
set.add_new("normal_mode");
|
||||
set.add_new("handle_left");
|
||||
set.add_new("handle_right");
|
||||
set.add_new("handle_type_left");
|
||||
set.add_new("handle_type_right");
|
||||
set.add_new("knots_mode");
|
||||
set.add_new("nurbs_order");
|
||||
set.add_new("nurbs_weight");
|
||||
set.add_new("velocity");
|
||||
return set;
|
||||
}();
|
||||
|
||||
return excluded_attrs.contains(name);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::write_generic_data(const bke::CurvesGeometry &curves,
|
||||
const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomCurves &usd_curves)
|
||||
{
|
||||
const CurveType curve_type = CurveType(curves.curve_types().first());
|
||||
const bool is_bezier = curve_type == CURVE_TYPE_BEZIER;
|
||||
|
||||
const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain,
|
||||
is_bezier);
|
||||
const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(attr.data_type);
|
||||
|
||||
if (!pv_interp || !pv_type) {
|
||||
BKE_reportf(this->reports(),
|
||||
RPT_WARNING,
|
||||
"Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
|
||||
attr.name.c_str(),
|
||||
int8_t(attr.domain),
|
||||
int(attr.data_type));
|
||||
return;
|
||||
}
|
||||
|
||||
const GVArray attribute = *attr.get();
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(attr.name, usd_export_context_.export_params.allow_unicode));
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
|
||||
|
||||
copy_blender_attribute_to_primvar(attribute, attr.data_type, time, pv_attr, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::write_uv_data(const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomCurves &usd_curves)
|
||||
{
|
||||
const VArray<float2> buffer = *attr.get<float2>(bke::AttrDomain::Curve);
|
||||
if (buffer.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(attr.name, usd_export_context_.export_params.allow_unicode));
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
|
||||
pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->uniform);
|
||||
|
||||
copy_blender_buffer_to_primvar<float2, pxr::GfVec2f>(buffer, time, pv_uv, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::write_velocities(const bke::CurvesGeometry &curves,
|
||||
const pxr::UsdGeomCurves &usd_curves)
|
||||
{
|
||||
const VArraySpan velocity = *curves.attributes().lookup<float3>("velocity",
|
||||
bke::AttrDomain::Point);
|
||||
if (velocity.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Export per-vertex velocity vectors. */
|
||||
Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
|
||||
pxr::VtVec3fArray usd_velocities;
|
||||
usd_velocities.assign(data.begin(), data.end());
|
||||
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
pxr::UsdAttribute attr_vel = usd_curves.CreateVelocitiesAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_vel, usd_velocities, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::write_custom_data(const bke::CurvesGeometry &curves,
|
||||
const pxr::UsdGeomCurves &usd_curves)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = curves.attributes();
|
||||
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
/* Skip "internal" Blender properties and attributes dealt with elsewhere. */
|
||||
if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
|
||||
is_excluded_attr(iter.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Spline UV data */
|
||||
if (iter.domain == bke::AttrDomain::Curve && iter.data_type == bke::AttrType::Float2) {
|
||||
if (usd_export_context_.export_params.export_uvmaps) {
|
||||
this->write_uv_data(iter, usd_curves);
|
||||
}
|
||||
}
|
||||
|
||||
/* Everything else. */
|
||||
else {
|
||||
this->write_generic_data(curves, iter, usd_curves);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void USDCurvesWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
Curves *curves_id;
|
||||
std::unique_ptr<Curves, std::function<void(Curves *)>> converted_curves;
|
||||
int8_t curve_type_default = CURVE_TYPE_CATMULL_ROM;
|
||||
|
||||
switch (context.object->type) {
|
||||
case OB_CURVES_LEGACY: {
|
||||
const Curve *legacy_curve = id_cast<Curve *>(context.object->data);
|
||||
converted_curves = std::unique_ptr<Curves, std::function<void(Curves *)>>(
|
||||
bke::curve_legacy_to_curves(*legacy_curve), [](Curves *c) { BKE_id_free(nullptr, c); });
|
||||
curves_id = converted_curves.get();
|
||||
curve_type_default = CURVE_TYPE_BEZIER;
|
||||
break;
|
||||
}
|
||||
case OB_CURVES:
|
||||
curves_id = id_cast<Curves *>(context.object->data);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
const bke::CurvesGeometry empty;
|
||||
const bke::CurvesGeometry &curves = curves_id ? curves_id->geometry.wrap() : empty;
|
||||
|
||||
const std::array<int, CURVE_TYPES_NUM> &curve_type_counts = curves.curve_type_counts();
|
||||
const int number_of_curve_types = std::count_if(curve_type_counts.begin(),
|
||||
curve_type_counts.end(),
|
||||
[](const int count) { return count > 0; });
|
||||
if (number_of_curve_types > 1) {
|
||||
BKE_report(
|
||||
reports(), RPT_WARNING, "Cannot export mixed curve types in the same Curves object");
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_utils::booleans_mix_calc(curves.cyclic()) == array_utils::BooleanMix::Mixed) {
|
||||
BKE_report(reports(),
|
||||
RPT_WARNING,
|
||||
"Cannot export mixed cyclic and non-cyclic curves in the same Curves object");
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const int8_t curve_type_fallback = first_frame_curve_type == -1 ? curve_type_default :
|
||||
first_frame_curve_type;
|
||||
const int8_t curve_type = curves.curves_num() > 0 ? curves.curve_types()[0] :
|
||||
curve_type_fallback;
|
||||
|
||||
if (first_frame_curve_type == -1) {
|
||||
first_frame_curve_type = curve_type;
|
||||
}
|
||||
else if (first_frame_curve_type != curve_type) {
|
||||
const char *first_frame_curve_type_name = nullptr;
|
||||
RNA_enum_name_from_value(
|
||||
rna_enum_curves_type_items, int(first_frame_curve_type), &first_frame_curve_type_name);
|
||||
|
||||
const char *current_curve_type_name = nullptr;
|
||||
RNA_enum_name_from_value(
|
||||
rna_enum_curves_type_items, int(curve_type), ¤t_curve_type_name);
|
||||
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"USD does not support animating curve types. The curve type changes from %s to "
|
||||
"%s on frame %f",
|
||||
IFACE_(first_frame_curve_type_name),
|
||||
IFACE_(current_curve_type_name),
|
||||
time.GetValue());
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_cyclic = curves.curves_num() > 0 ? curves.cyclic().first() : false;
|
||||
pxr::VtArray<pxr::GfVec3f> verts;
|
||||
pxr::VtIntArray control_point_counts;
|
||||
pxr::VtArray<float> widths;
|
||||
pxr::TfToken interpolation;
|
||||
|
||||
pxr::UsdGeomBasisCurves usd_basis_curves;
|
||||
pxr::UsdGeomNurbsCurves usd_nurbs_curves;
|
||||
pxr::UsdGeomCurves *usd_curves = nullptr;
|
||||
|
||||
control_point_counts.resize(curves.curves_num());
|
||||
switch (curve_type) {
|
||||
case CURVE_TYPE_POLY:
|
||||
usd_basis_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false);
|
||||
usd_curves = &usd_basis_curves;
|
||||
|
||||
populate_curve_props(
|
||||
curves, verts, control_point_counts, widths, interpolation, is_cyclic, false, reports());
|
||||
break;
|
||||
case CURVE_TYPE_CATMULL_ROM:
|
||||
usd_basis_curves = DefineUsdGeomBasisCurves(
|
||||
pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true);
|
||||
usd_curves = &usd_basis_curves;
|
||||
|
||||
populate_curve_props(
|
||||
curves, verts, control_point_counts, widths, interpolation, is_cyclic, true, reports());
|
||||
break;
|
||||
case CURVE_TYPE_BEZIER:
|
||||
usd_basis_curves = DefineUsdGeomBasisCurves(
|
||||
pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true);
|
||||
usd_curves = &usd_basis_curves;
|
||||
|
||||
populate_curve_props_for_bezier(
|
||||
curves, verts, control_point_counts, widths, interpolation, is_cyclic, reports());
|
||||
break;
|
||||
case CURVE_TYPE_NURBS: {
|
||||
pxr::VtArray<double> knots;
|
||||
pxr::VtArray<double> weights;
|
||||
pxr::VtArray<int> orders;
|
||||
|
||||
usd_nurbs_curves = pxr::UsdGeomNurbsCurves::Define(usd_export_context_.stage,
|
||||
usd_export_context_.usd_path);
|
||||
usd_curves = &usd_nurbs_curves;
|
||||
|
||||
populate_curve_props_for_nurbs(curves,
|
||||
verts,
|
||||
control_point_counts,
|
||||
widths,
|
||||
knots,
|
||||
weights,
|
||||
orders,
|
||||
interpolation,
|
||||
is_cyclic);
|
||||
|
||||
set_writer_attributes_for_nurbs(usd_nurbs_curves, knots, weights, orders, time);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
this->set_writer_attributes(
|
||||
*usd_curves, verts, control_point_counts, widths, time, interpolation);
|
||||
|
||||
this->assign_materials(context, *usd_curves);
|
||||
|
||||
/* TODO: We cannot write custom primvars for cyclic NURBS curves at the moment. */
|
||||
if (!is_cyclic || (is_cyclic && curve_type != CURVE_TYPE_NURBS)) {
|
||||
this->write_velocities(curves, *usd_curves);
|
||||
this->write_custom_data(curves, *usd_curves);
|
||||
}
|
||||
|
||||
if (curves_id) {
|
||||
const pxr::UsdPrim prim = usd_curves->GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &curves_id->id);
|
||||
write_id_properties(prim, curves_id->id, time);
|
||||
}
|
||||
|
||||
this->author_extent(*usd_curves, curves.bounds_min_max(), time);
|
||||
}
|
||||
|
||||
void USDCurvesWriter::assign_materials(const HierarchyContext &context,
|
||||
const pxr::UsdGeomCurves &usd_curves)
|
||||
{
|
||||
if (context.object->totcol == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool curve_material_bound = false;
|
||||
for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
|
||||
Material *material = BKE_object_material_get(context.object, mat_num + 1);
|
||||
if (material == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::UsdPrim curve_prim = usd_curves.GetPrim();
|
||||
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(curve_prim);
|
||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||
api.Bind(usd_material);
|
||||
pxr::UsdShadeMaterialBindingAPI::Apply(curve_prim);
|
||||
|
||||
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
|
||||
* use the flag from the first non-empty material slot. */
|
||||
usd_curves.CreateDoubleSidedAttr(
|
||||
pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
|
||||
|
||||
curve_material_bound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!curve_material_bound) {
|
||||
/* Blender defaults to double-sided, but USD to single-sided. */
|
||||
usd_curves.CreateDoubleSidedAttr(pxr::VtValue(true));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||
#include <pxr/usd/usdGeom/curves.h>
|
||||
#include <pxr/usd/usdGeom/nurbsCurves.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace bke {
|
||||
class AttributeIter;
|
||||
class CurvesGeometry;
|
||||
} // namespace bke
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Writer for writing Curves data as USD curves. */
|
||||
class USDCurvesWriter final : public USDAbstractWriter {
|
||||
public:
|
||||
USDCurvesWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
~USDCurvesWriter() final = default;
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
void assign_materials(const HierarchyContext &context, const pxr::UsdGeomCurves &usd_curves);
|
||||
|
||||
private:
|
||||
int8_t first_frame_curve_type = -1;
|
||||
pxr::UsdGeomBasisCurves DefineUsdGeomBasisCurves(pxr::VtValue curve_basis,
|
||||
bool cyclic,
|
||||
bool cubic) const;
|
||||
|
||||
void set_writer_attributes(pxr::UsdGeomCurves &usd_curves,
|
||||
pxr::VtArray<pxr::GfVec3f> &verts,
|
||||
pxr::VtIntArray &control_point_counts,
|
||||
pxr::VtArray<float> &widths,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::TfToken interpolation);
|
||||
|
||||
void set_writer_attributes_for_nurbs(const pxr::UsdGeomNurbsCurves &usd_nurbs_curves,
|
||||
pxr::VtArray<double> &knots,
|
||||
pxr::VtArray<double> &weights,
|
||||
pxr::VtArray<int> &orders,
|
||||
const pxr::UsdTimeCode time);
|
||||
|
||||
void write_generic_data(const bke::CurvesGeometry &curves,
|
||||
const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomCurves &usd_curves);
|
||||
|
||||
void write_uv_data(const bke::AttributeIter &attr, const pxr::UsdGeomCurves &usd_curves);
|
||||
|
||||
void write_velocities(const bke::CurvesGeometry &curves, const pxr::UsdGeomCurves &usd_curves);
|
||||
|
||||
void write_custom_data(const bke::CurvesGeometry &curves, const pxr::UsdGeomCurves &usd_curves);
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
123
blender-5.2.0/source/blender/io/usd/intern/usd_writer_hair.cc
Normal file
123
blender-5.2.0/source/blender/io/usd/intern/usd_writer_hair.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_hair.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
|
||||
#include "BKE_material.hh"
|
||||
#include "BKE_particle.h"
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_particle_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
void USDHairWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
ParticleSystem *psys = context.particle_system;
|
||||
ParticleCacheKey **cache = psys->pathcache;
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
|
||||
usd_export_context_.usd_path);
|
||||
|
||||
if (psys->part->flag & PART_HAIR_BSPLINE) {
|
||||
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
|
||||
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
||||
}
|
||||
else {
|
||||
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->catmullRom));
|
||||
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
||||
curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->pinned));
|
||||
}
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> points;
|
||||
pxr::VtIntArray curve_point_counts;
|
||||
curve_point_counts.reserve(psys->totpart);
|
||||
|
||||
/* Reverse current transform since the Hair curves will be placed under the object's Xform and we
|
||||
* don't want a double-transform to happen. */
|
||||
const float4x4 inv = math::invert(context.object->object_to_world());
|
||||
|
||||
ParticleCacheKey *strand;
|
||||
for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
|
||||
strand = cache[strand_index];
|
||||
|
||||
int point_count = strand->segments + 1;
|
||||
curve_point_counts.push_back(point_count);
|
||||
|
||||
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
|
||||
const float3 vert = math::transform_point(inv, float3(strand->co));
|
||||
points.push_back(pxr::GfVec3f(vert.x, vert.y, vert.z));
|
||||
}
|
||||
}
|
||||
|
||||
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
|
||||
if (!attr_points.HasValue()) {
|
||||
attr_points.Set(points, pxr::UsdTimeCode::Default());
|
||||
attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), time);
|
||||
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), time);
|
||||
|
||||
if (psys->totpart > 0) {
|
||||
pxr::VtArray<pxr::GfVec3f> colors;
|
||||
colors.push_back(pxr::GfVec3f(cache[0]->col));
|
||||
curves.CreateDisplayColorAttr(pxr::VtValue(colors));
|
||||
}
|
||||
|
||||
if (psys->part) {
|
||||
auto prim = curves.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &psys->part->id);
|
||||
write_id_properties(prim, psys->part->id, time);
|
||||
}
|
||||
|
||||
assign_material(context, curves, psys->part->omat);
|
||||
|
||||
this->author_extent(curves, time);
|
||||
}
|
||||
|
||||
void USDHairWriter::assign_material(const HierarchyContext &context,
|
||||
const pxr::UsdGeomBasisCurves &curves,
|
||||
const int material_slot)
|
||||
{
|
||||
if (!usd_export_context_.export_params.export_materials) {
|
||||
return;
|
||||
}
|
||||
|
||||
Material *material = BKE_object_material_get_eval(context.object, material_slot);
|
||||
if (material == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||
if (!usd_material) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto curves_prim = curves.GetPrim();
|
||||
pxr::UsdShadeMaterialBindingAPI binding(curves_prim);
|
||||
binding.Bind(usd_material);
|
||||
pxr::UsdShadeMaterialBindingAPI::Apply(curves_prim);
|
||||
}
|
||||
|
||||
bool USDHairWriter::check_is_animated(const HierarchyContext & /*context*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,23 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* Writer for writing hair particle data as USD curves. */
|
||||
class USDHairWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDHairWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
void assign_material(const HierarchyContext &context,
|
||||
const pxr::UsdGeomBasisCurves &curves,
|
||||
const int material_slot);
|
||||
bool check_is_animated(const HierarchyContext &context) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
187
blender-5.2.0/source/blender/io/usd/intern/usd_writer_light.cc
Normal file
187
blender-5.2.0/source/blender/io/usd/intern/usd_writer_light.cc
Normal file
@@ -0,0 +1,187 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_light.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
|
||||
#include <pxr/usd/usdLux/diskLight.h>
|
||||
#include <pxr/usd/usdLux/distantLight.h>
|
||||
#include <pxr/usd/usdLux/rectLight.h>
|
||||
#include <pxr/usd/usdLux/shapingAPI.h>
|
||||
#include <pxr/usd/usdLux/sphereLight.h>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_constants.h"
|
||||
|
||||
#include "DNA_light_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
bool USDLightWriter::is_supported(const HierarchyContext * /*context*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void USDLightWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
const Light *light = id_cast<const Light *>(context.object->data);
|
||||
pxr::UsdLuxLightAPI usd_light_api;
|
||||
|
||||
switch (light->type) {
|
||||
case LA_AREA: {
|
||||
switch (light->area_shape) {
|
||||
case LA_AREA_RECT: {
|
||||
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
|
||||
set_attribute(rect_light.CreateWidthAttr(pxr::VtValue(), true),
|
||||
light->area_size,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(rect_light.CreateHeightAttr(pxr::VtValue(), true),
|
||||
light->area_sizey,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
usd_light_api = rect_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
case LA_AREA_SQUARE: {
|
||||
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
|
||||
set_attribute(rect_light.CreateWidthAttr(pxr::VtValue(), true),
|
||||
light->area_size,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(rect_light.CreateHeightAttr(pxr::VtValue(), true),
|
||||
light->area_size,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
usd_light_api = rect_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
case LA_AREA_DISK: {
|
||||
pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
|
||||
set_attribute(disk_light.CreateRadiusAttr(pxr::VtValue(), true),
|
||||
light->area_size / 2.0f,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
usd_light_api = disk_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
case LA_AREA_ELLIPSE: {
|
||||
/* An ellipse light deteriorates into a disk light. */
|
||||
pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
|
||||
set_attribute(disk_light.CreateRadiusAttr(pxr::VtValue(), true),
|
||||
(light->area_size + light->area_sizey) / 4.0f,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
usd_light_api = disk_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LA_LOCAL:
|
||||
case LA_SPOT: {
|
||||
pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path);
|
||||
set_attribute(sphere_light.CreateRadiusAttr(pxr::VtValue(), true),
|
||||
light->radius,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(sphere_light.CreateTreatAsPointAttr(pxr::VtValue(), true),
|
||||
light->radius == 0.0f,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
|
||||
if (light->type == LA_SPOT) {
|
||||
pxr::UsdLuxShapingAPI shaping_api = pxr::UsdLuxShapingAPI::Apply(sphere_light.GetPrim());
|
||||
if (shaping_api) {
|
||||
set_attribute(shaping_api.CreateShapingConeAngleAttr(pxr::VtValue(), true),
|
||||
RAD2DEGF(light->spotsize) / 2.0f,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(shaping_api.CreateShapingConeSoftnessAttr(pxr::VtValue(), true),
|
||||
light->spotblend,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
}
|
||||
}
|
||||
|
||||
usd_light_api = sphere_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
case LA_SUN: {
|
||||
pxr::UsdLuxDistantLight distant_light = pxr::UsdLuxDistantLight::Define(stage, usd_path);
|
||||
set_attribute(distant_light.CreateAngleAttr(pxr::VtValue(), true),
|
||||
RAD2DEGF(light->sun_angle / 2.0f),
|
||||
time,
|
||||
usd_value_writer_);
|
||||
usd_light_api = distant_light.LightAPI();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
float intensity;
|
||||
if (light->type == LA_SUN) {
|
||||
/* Unclear why, but approximately matches Karma. */
|
||||
intensity = light->energy / 4.0f;
|
||||
}
|
||||
else {
|
||||
/* Convert from radiant flux to intensity. */
|
||||
intensity = light->energy / M_PI;
|
||||
}
|
||||
|
||||
set_attribute(
|
||||
usd_light_api.CreateIntensityAttr(pxr::VtValue(), true), intensity, time, usd_value_writer_);
|
||||
set_attribute(usd_light_api.CreateExposureAttr(pxr::VtValue(), true),
|
||||
light->exposure,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
|
||||
set_attribute(usd_light_api.CreateColorAttr(pxr::VtValue(), true),
|
||||
pxr::GfVec3f(light->r, light->g, light->b),
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_light_api.CreateEnableColorTemperatureAttr(pxr::VtValue(), true),
|
||||
(light->mode & LA_USE_TEMPERATURE) != 0,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_light_api.CreateColorTemperatureAttr(pxr::VtValue(), true),
|
||||
light->temperature,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
|
||||
set_attribute(usd_light_api.CreateDiffuseAttr(pxr::VtValue(), true),
|
||||
light->diff_fac,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_light_api.CreateSpecularAttr(pxr::VtValue(), true),
|
||||
light->spec_fac,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
set_attribute(usd_light_api.CreateNormalizeAttr(pxr::VtValue(), true),
|
||||
(light->mode & LA_UNNORMALIZED) == 0,
|
||||
time,
|
||||
usd_value_writer_);
|
||||
|
||||
pxr::UsdPrim prim = usd_light_api.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &light->id);
|
||||
write_id_properties(prim, light->id, time);
|
||||
colorspace_apply_to_prim(prim);
|
||||
|
||||
/* Only a subset of light types are "boundable". */
|
||||
if (auto boundable = pxr::UsdGeomBoundable(prim)) {
|
||||
this->author_extent(boundable, time);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDLightWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDLightWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
bool is_supported(const HierarchyContext *context) const override;
|
||||
void do_write(HierarchyContext &context) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
1761
blender-5.2.0/source/blender/io/usd/intern/usd_writer_material.cc
Normal file
1761
blender-5.2.0/source/blender/io/usd/intern/usd_writer_material.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bNode;
|
||||
struct Image;
|
||||
struct Material;
|
||||
struct ReportList;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct USDExporterContext;
|
||||
struct USDExportParams;
|
||||
|
||||
/**
|
||||
* Create USDMaterial from Blender material.
|
||||
*
|
||||
* \param active_uvmap_name: used as the default UV set name sampled by the `primvar`
|
||||
* reader shaders generated for image texture nodes that don't have an attached UVMap node.
|
||||
*/
|
||||
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
|
||||
pxr::SdfPath usd_path,
|
||||
Material *material,
|
||||
const std::string &active_uvmap_name,
|
||||
ReportList *reports);
|
||||
|
||||
/**
|
||||
* Create a viewport UsdPreviewSurface material from a Blender material.
|
||||
*/
|
||||
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
|
||||
const Material *material,
|
||||
const pxr::UsdShadeMaterial &usd_material);
|
||||
|
||||
/**
|
||||
* Returns a USDPreviewSurface token name for a given Blender shader Socket name,
|
||||
* or an empty TfToken if the input name is not found in the map.
|
||||
*/
|
||||
pxr::TfToken token_for_input(const StringRef input_name);
|
||||
|
||||
void export_texture(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const bool allow_overwrite = false,
|
||||
ReportList *reports = nullptr);
|
||||
|
||||
void export_texture(Image *ima,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const bool allow_overwrite = false,
|
||||
ReportList *reports = nullptr);
|
||||
|
||||
/**
|
||||
* Gets an asset path for the given texture image / node. The resulting path
|
||||
* may be absolute, relative to the USD file, or in a 'textures' directory
|
||||
* in the same directory as the USD file, depending on the export parameters.
|
||||
* The filename is typically the image filepath but might also be automatically
|
||||
* generated based on the image name for in-memory textures when exporting textures.
|
||||
* This function may return an empty string if the image does not have a filepath
|
||||
* assigned and no asset path could be determined.
|
||||
*/
|
||||
std::string get_tex_image_asset_filepath(bNode *node,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams &export_params);
|
||||
|
||||
std::string get_tex_image_asset_filepath(Image *ima,
|
||||
const pxr::UsdStageRefPtr stage,
|
||||
const USDExportParams &export_params);
|
||||
/**
|
||||
* Return a USD asset path referencing the given texture file.
|
||||
* The resulting path may be absolute, relative to the USD file,
|
||||
* or in a 'textures' directory in the same directory as the USD file,
|
||||
* depending on the export parameters.
|
||||
*/
|
||||
std::string get_tex_image_asset_filepath(const std::string &asset_path,
|
||||
const std::string &stage_path,
|
||||
const USDExportParams &export_params);
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
960
blender-5.2.0/source/blender/io/usd/intern/usd_writer_mesh.cc
Normal file
960
blender-5.2.0/source/blender/io/usd/intern/usd_writer_mesh.cc
Normal file
@@ -0,0 +1,960 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_mesh.hh"
|
||||
|
||||
#include "usd_armature_utils.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_blend_shape_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_skel_convert.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_material.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_mesh_mapping.hh"
|
||||
#include "BKE_mesh_wrapper.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_report.hh"
|
||||
#include "BKE_subdiv.hh"
|
||||
|
||||
#include "bmesh.hh"
|
||||
#include "bmesh_tools.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "DNA_key_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
|
||||
{
|
||||
return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
|
||||
}
|
||||
|
||||
/* Get the last subdiv modifier, regardless of enable/disable status */
|
||||
static const SubsurfModifierData *get_last_subdiv_modifier(eEvaluationMode eval_mode, Object *obj)
|
||||
{
|
||||
BLI_assert(obj);
|
||||
|
||||
/* Return the subdiv modifier if it is the last modifier and has
|
||||
* the required mode enabled. */
|
||||
|
||||
ModifierData *md = static_cast<ModifierData *>(obj->modifiers.last);
|
||||
|
||||
if (!md) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Determine if the modifier is enabled for the current evaluation mode. */
|
||||
ModifierMode mod_mode = (eval_mode == DAG_EVAL_RENDER) ? eModifierMode_Render :
|
||||
eModifierMode_Realtime;
|
||||
|
||||
if ((md->mode & mod_mode) != mod_mode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (md->type == eModifierType_Subsurf) {
|
||||
return reinterpret_cast<SubsurfModifierData *>(md);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
Object *object_eval = context.object;
|
||||
bool needsfree = false;
|
||||
Mesh *mesh = get_export_mesh(object_eval, needsfree);
|
||||
|
||||
if (mesh == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.triangulate_meshes) {
|
||||
const bool tag_only = false;
|
||||
const int quad_method = usd_export_context_.export_params.quad_method;
|
||||
const int ngon_method = usd_export_context_.export_params.ngon_method;
|
||||
|
||||
BMeshCreateParams bmesh_create_params{};
|
||||
BMeshFromMeshParams bmesh_from_mesh_params{};
|
||||
bmesh_from_mesh_params.calc_face_normal = true;
|
||||
bmesh_from_mesh_params.calc_vert_normal = true;
|
||||
BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmesh_create_params, &bmesh_from_mesh_params);
|
||||
|
||||
BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
|
||||
|
||||
Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
|
||||
BM_mesh_free(bm);
|
||||
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
}
|
||||
mesh = triangulated_mesh;
|
||||
needsfree = true;
|
||||
}
|
||||
|
||||
try {
|
||||
/* Fetch the subdiv modifier, if one exists and it is the last modifier. */
|
||||
const SubsurfModifierData *subsurfData = get_last_subdiv_modifier(
|
||||
usd_export_context_.export_params.evaluation_mode, object_eval);
|
||||
|
||||
write_mesh(context, mesh, subsurfData);
|
||||
|
||||
auto prim = usd_export_context_.stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||
if (prim.IsValid() && object_eval) {
|
||||
prim.SetActive((object_eval->duplicator_visibility_flag & OB_DUPLI_FLAG_RENDER) != 0);
|
||||
add_to_prim_map(prim.GetPath(), &mesh->id);
|
||||
write_id_properties(prim, mesh->id, get_export_time_code());
|
||||
}
|
||||
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_custom_data(const Object *obj,
|
||||
const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
|
||||
const StringRef active_uvmap_name = mesh->default_uv_map_name();
|
||||
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
/* Skip "internal" Blender properties and attributes processed elsewhere.
|
||||
* Skip edge domain because USD doesn't have a good conversion for them. */
|
||||
if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
|
||||
iter.domain == bke::AttrDomain::Edge ||
|
||||
ELEM(iter.name,
|
||||
"position",
|
||||
"material_index",
|
||||
"velocity",
|
||||
"crease_vert",
|
||||
"custom_normal",
|
||||
"sharp_face"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((usd_export_context_.export_params.export_armatures ||
|
||||
usd_export_context_.export_params.export_shapekeys) &&
|
||||
iter.name.rfind("skel:") == 0)
|
||||
{
|
||||
/* If we're exporting armatures or shape keys to UsdSkel, we skip any
|
||||
* attributes that have names with the "skel:" namespace, to avoid possible
|
||||
* conflicts. Such attribute might have been previously imported into Blender
|
||||
* from USD, but can no longer be considered valid. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.export_armatures &&
|
||||
is_armature_modifier_bone_name(*obj, iter.name, usd_export_context_.depsgraph))
|
||||
{
|
||||
/* This attribute is likely a vertex group for the armature modifier,
|
||||
* and it may conflict with skinning data that will be written to
|
||||
* the USD mesh, so we skip it. Such vertex groups will instead be
|
||||
* handled in #export_deform_verts(). */
|
||||
return;
|
||||
}
|
||||
|
||||
/* UV Data. */
|
||||
if (iter.domain == bke::AttrDomain::Corner && iter.data_type == bke::AttrType::Float2) {
|
||||
if (usd_export_context_.export_params.export_uvmaps) {
|
||||
this->write_uv_data(mesh, usd_mesh, iter, active_uvmap_name);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
this->write_generic_data(mesh, usd_mesh, iter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
|
||||
const bke::AttrDomain blender_domain)
|
||||
{
|
||||
switch (blender_domain) {
|
||||
case bke::AttrDomain::Corner:
|
||||
return pxr::UsdGeomTokens->faceVarying;
|
||||
case bke::AttrDomain::Point:
|
||||
return pxr::UsdGeomTokens->vertex;
|
||||
case bke::AttrDomain::Face:
|
||||
return pxr::UsdGeomTokens->uniform;
|
||||
|
||||
/* Notice: Edge types are not supported in USD! */
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_generic_data(const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const bke::AttributeIter &attr)
|
||||
{
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(attr.name, usd_export_context_.export_params.allow_unicode));
|
||||
const bool use_color3f_type = pv_name == usdtokens::displayColor;
|
||||
const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain);
|
||||
const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(
|
||||
attr.data_type, use_color3f_type);
|
||||
|
||||
if (!pv_interp || !pv_type) {
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"Mesh '%s', Attribute '%s' (domain %d, type %d) cannot be converted to USD",
|
||||
BKE_id_name(mesh->id),
|
||||
attr.name.c_str(),
|
||||
int8_t(attr.domain),
|
||||
int(attr.data_type));
|
||||
return;
|
||||
}
|
||||
|
||||
const GVArray attribute = *attr.get();
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_mesh);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
|
||||
|
||||
copy_blender_attribute_to_primvar(attribute, attr.data_type, time, pv_attr, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_uv_data(const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const bke::AttributeIter &attr,
|
||||
const StringRef active_uvmap_name)
|
||||
{
|
||||
const VArray<float2> buffer = *attr.get<float2>(bke::AttrDomain::Corner);
|
||||
if (buffer.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Optionally rename active UV map to "st", to follow USD conventions
|
||||
* and better work with MaterialX shader nodes. */
|
||||
const StringRef name = usd_export_context_.export_params.rename_uvmaps &&
|
||||
active_uvmap_name == attr.name ?
|
||||
"st" :
|
||||
attr.name;
|
||||
|
||||
/* Construct the UvVertMap containing the connectivity data for the UVs. */
|
||||
const OffsetIndices<int> faces = mesh->faces();
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
const VArraySpan<float2> uv_data(buffer);
|
||||
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(
|
||||
faces, corner_verts, uv_data, mesh->verts_num, float2(STD_UV_CONNECT_LIMIT), false);
|
||||
|
||||
/* This will only be a nullptr if the `faces` are empty OR allocating space for
|
||||
* the VertMap fails. */
|
||||
if (!uv_vert_map) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't resolve UV connectivity for mesh %s",
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* From the connectivity data, extract the unique uvs and a mapping of corner index
|
||||
* to the index of the corresponding unique uv for that corner. */
|
||||
pxr::VtArray<pxr::GfVec2f> unique_uvs;
|
||||
unique_uvs.reserve(mesh->verts_num);
|
||||
Array<int> corner_to_uv_index(corner_verts.size(), -1);
|
||||
for (int vertex_index = 0; vertex_index < mesh->verts_num; vertex_index++) {
|
||||
const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index);
|
||||
|
||||
/* Loop over all of the face vertices connected to this mesh vertex and accumulate the
|
||||
* unique UVs. */
|
||||
for (; uv_vert; uv_vert = uv_vert->next) {
|
||||
const int corner_index = faces[uv_vert->face_index].start() + uv_vert->loop_of_face_index;
|
||||
const float2 uv = uv_data[corner_index];
|
||||
if (uv_vert->separate) {
|
||||
unique_uvs.push_back(pxr::GfVec2f(uv.x, uv.y));
|
||||
}
|
||||
corner_to_uv_index[corner_index] = unique_uvs.size() - 1;
|
||||
}
|
||||
}
|
||||
BKE_mesh_uv_vert_map_free(uv_vert_map);
|
||||
|
||||
/* Finally, build the USD indices array. */
|
||||
pxr::VtIntArray indices;
|
||||
indices.reserve(corner_verts.size());
|
||||
for (const int corner_idx : corner_verts.index_range()) {
|
||||
indices.push_back(corner_to_uv_index[corner_idx]);
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(name, usd_export_context_.export_params.allow_unicode));
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_mesh);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
|
||||
pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
|
||||
set_attribute(pv_uv, unique_uvs, time, usd_value_writer_);
|
||||
|
||||
pxr::UsdAttribute attr_indices = pv_uv.CreateIndicesAttr();
|
||||
if (!attr_indices.HasValue()) {
|
||||
attr_indices.Set(indices, time);
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_indices, pxr::VtValue(indices), time);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
|
||||
{
|
||||
BKE_id_free(nullptr, mesh);
|
||||
}
|
||||
|
||||
struct USDMeshData {
|
||||
pxr::VtArray<pxr::GfVec3f> points;
|
||||
pxr::VtIntArray face_vertex_counts;
|
||||
pxr::VtIntArray face_indices;
|
||||
MaterialFaceGroups face_groups;
|
||||
|
||||
/* The length of this array specifies the number of creases on the surface. Each element gives
|
||||
* the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
|
||||
* in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
|
||||
* element of this array should be greater than one. */
|
||||
pxr::VtIntArray crease_lengths;
|
||||
/* The indices of all vertices forming creased edges. The size of this array must be equal to the
|
||||
* sum of all elements of the 'creaseLengths' attribute. */
|
||||
pxr::VtIntArray crease_vertex_indices;
|
||||
/* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
|
||||
* perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
|
||||
* the number of elements in this array will be either `len(creaseLengths)` or the sum over all X
|
||||
* of `(creaseLengths[X] - 1)`. Note that while the RI spec allows each crease to have either a
|
||||
* single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
|
||||
* a mesh, or sharpness's for all edges making up the creases on a mesh. */
|
||||
pxr::VtFloatArray crease_sharpnesses;
|
||||
|
||||
/* The lengths of this array specifies the number of sharp corners (or vertex crease) on the
|
||||
* surface. Each value is the index of a vertex in the mesh's vertex list. */
|
||||
pxr::VtIntArray corner_indices;
|
||||
/* The per-vertex sharpnesses. The lengths of this array must match that of `corner_indices`. */
|
||||
pxr::VtFloatArray corner_sharpnesses;
|
||||
};
|
||||
|
||||
void USDGenericMeshWriter::write_mesh(HierarchyContext &context,
|
||||
Mesh *mesh,
|
||||
const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
||||
|
||||
pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
|
||||
write_visibility(context, time, usd_mesh);
|
||||
|
||||
USDMeshData usd_mesh_data;
|
||||
/* Ensure data exists if currently in edit mode. */
|
||||
BKE_mesh_wrapper_ensure_mdata(mesh);
|
||||
get_geometry_data(mesh, usd_mesh_data);
|
||||
|
||||
pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
|
||||
true);
|
||||
pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
|
||||
true);
|
||||
|
||||
if (!attr_points.HasValue()) {
|
||||
/* Provide the initial value as default. This makes USD write the value as constant if they
|
||||
* don't change over time. */
|
||||
attr_points.Set(usd_mesh_data.points, pxr::UsdTimeCode::Default());
|
||||
attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, pxr::UsdTimeCode::Default());
|
||||
attr_face_vertex_indices.Set(usd_mesh_data.face_indices, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), time);
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), time);
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), time);
|
||||
|
||||
if (!usd_mesh_data.crease_lengths.empty()) {
|
||||
pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
|
||||
true);
|
||||
|
||||
if (!attr_crease_lengths.HasValue()) {
|
||||
attr_crease_lengths.Set(usd_mesh_data.crease_lengths, pxr::UsdTimeCode::Default());
|
||||
attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, pxr::UsdTimeCode::Default());
|
||||
attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), time);
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), time);
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), time);
|
||||
}
|
||||
|
||||
if (!usd_mesh_data.corner_indices.empty() &&
|
||||
usd_mesh_data.corner_indices.size() == usd_mesh_data.corner_sharpnesses.size())
|
||||
{
|
||||
pxr::UsdAttribute attr_corner_indices = usd_mesh.CreateCornerIndicesAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_corner_sharpnesses = usd_mesh.CreateCornerSharpnessesAttr(
|
||||
pxr::VtValue(), true);
|
||||
|
||||
if (!attr_corner_indices.HasValue()) {
|
||||
attr_corner_indices.Set(usd_mesh_data.corner_indices, pxr::UsdTimeCode::Default());
|
||||
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_corner_indices, pxr::VtValue(usd_mesh_data.corner_indices), time);
|
||||
usd_value_writer_.SetAttribute(
|
||||
attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.corner_sharpnesses), time);
|
||||
}
|
||||
|
||||
write_custom_data(context.object, mesh, usd_mesh);
|
||||
write_surface_velocity(mesh, usd_mesh);
|
||||
|
||||
const pxr::TfToken subdiv_scheme = get_subdiv_scheme(subsurfData);
|
||||
|
||||
/* Normals can be animated, so ensure these are written for each frame,
|
||||
* unless a subdiv modifier is used, in which case normals are computed,
|
||||
* not stored with the mesh. */
|
||||
if (usd_export_context_.export_params.export_normals &&
|
||||
subdiv_scheme == pxr::UsdGeomTokens->none)
|
||||
{
|
||||
write_normals(mesh, usd_mesh);
|
||||
}
|
||||
|
||||
this->author_extent(usd_mesh, mesh->bounds_min_max(), time);
|
||||
|
||||
/* TODO(Sybren): figure out what happens when the face groups change. */
|
||||
if (frame_has_been_written_) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* The subdivision scheme is a uniform according to spec,
|
||||
* so this value cannot be animated. */
|
||||
write_subdiv(subdiv_scheme, usd_mesh, subsurfData);
|
||||
|
||||
if (usd_export_context_.export_params.export_materials) {
|
||||
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
|
||||
}
|
||||
}
|
||||
|
||||
pxr::TfToken USDGenericMeshWriter::get_subdiv_scheme(const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
/* Default to setting the subdivision scheme to None. */
|
||||
pxr::TfToken subdiv_scheme = pxr::UsdGeomTokens->none;
|
||||
|
||||
if (subsurfData) {
|
||||
if (subsurfData->subdivType == SUBSURF_TYPE_CATMULL_CLARK) {
|
||||
if (usd_export_context_.export_params.export_subdiv == SubdivExportMode::Match) {
|
||||
/* If a subdivision modifier exists, and it uses Catmull-Clark, then apply Catmull-Clark
|
||||
* SubD scheme. */
|
||||
subdiv_scheme = pxr::UsdGeomTokens->catmullClark;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* "Simple" is currently the only other subdivision type provided by Blender, */
|
||||
/* and we do not yet provide a corresponding representation for USD export. */
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"USD export: Simple subdivision not supported, exporting subdivided mesh");
|
||||
}
|
||||
}
|
||||
|
||||
return subdiv_scheme;
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_subdiv(const pxr::TfToken &subdiv_scheme,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const SubsurfModifierData *subsurfData)
|
||||
{
|
||||
usd_mesh.CreateSubdivisionSchemeAttr().Set(subdiv_scheme);
|
||||
if (subdiv_scheme == pxr::UsdGeomTokens->catmullClark) {
|
||||
/* For Catmull-Clark, also consider the various interpolation modes. */
|
||||
/* For reference, see
|
||||
* https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#face-varying-interpolation-rules
|
||||
*/
|
||||
switch (subsurfData->uv_smooth) {
|
||||
case SUBSURF_UV_SMOOTH_NONE:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->all);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersOnly);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_AND_JUNCTIONS:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus1);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_JUNCTIONS_AND_CONCAVE:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus2);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->boundaries);
|
||||
break;
|
||||
case SUBSURF_UV_SMOOTH_ALL:
|
||||
usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->none);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported UV smoothing mode.");
|
||||
}
|
||||
|
||||
/* For reference, see
|
||||
* https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#boundary-interpolation-rules
|
||||
*/
|
||||
switch (subsurfData->boundary_smooth) {
|
||||
case SUBSURF_BOUNDARY_SMOOTH_ALL:
|
||||
usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeOnly);
|
||||
break;
|
||||
case SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS:
|
||||
usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeAndCorner);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported boundary smoothing mode.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_positions(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
const Span<pxr::GfVec3f> positions = mesh->vert_positions().cast<pxr::GfVec3f>();
|
||||
usd_mesh_data.points = pxr::VtArray<pxr::GfVec3f>(positions.begin(), positions.end());
|
||||
}
|
||||
|
||||
static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
/* Only construct face groups (a.k.a. geometry subsets) when we need them for material
|
||||
* assignments. */
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const VArray<int> material_indices = *attributes.lookup_or_default<int>(
|
||||
"material_index", bke::AttrDomain::Face, 0);
|
||||
if (!material_indices.is_single() && mesh->totcol > 1) {
|
||||
const VArraySpan<int> indices_span(material_indices);
|
||||
for (const int i : indices_span.index_range()) {
|
||||
usd_mesh_data.face_groups.lookup_or_add_default(indices_span[i]).push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
usd_mesh_data.face_vertex_counts.resize(mesh->faces_num);
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
offset_indices::copy_group_sizes(
|
||||
faces,
|
||||
faces.index_range(),
|
||||
MutableSpan(usd_mesh_data.face_vertex_counts.data(), mesh->faces_num));
|
||||
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
usd_mesh_data.face_indices = pxr::VtIntArray(corner_verts.begin(), corner_verts.end());
|
||||
}
|
||||
|
||||
static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const bke::AttributeReader attribute = attributes.lookup<float>("crease_edge",
|
||||
bke::AttrDomain::Edge);
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
const VArraySpan creases(*attribute);
|
||||
const Span<int2> edges = mesh->edges();
|
||||
for (const int i : edges.index_range()) {
|
||||
const float crease = std::clamp(creases[i], 0.0f, 1.0f);
|
||||
|
||||
if (crease != 0.0f) {
|
||||
usd_mesh_data.crease_vertex_indices.push_back(edges[i][0]);
|
||||
usd_mesh_data.crease_vertex_indices.push_back(edges[i][1]);
|
||||
usd_mesh_data.crease_lengths.push_back(2);
|
||||
usd_mesh_data.crease_sharpnesses.push_back(bke::subdiv::crease_to_sharpness(crease));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const bke::AttributeReader attribute = attributes.lookup<float>("crease_vert",
|
||||
bke::AttrDomain::Point);
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
const VArraySpan creases(*attribute);
|
||||
for (const int i : creases.index_range()) {
|
||||
const float crease = std::clamp(creases[i], 0.0f, 1.0f);
|
||||
|
||||
if (crease != 0.0f) {
|
||||
usd_mesh_data.corner_indices.push_back(i);
|
||||
usd_mesh_data.corner_sharpnesses.push_back(bke::subdiv::crease_to_sharpness(crease));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
|
||||
{
|
||||
get_positions(mesh, usd_mesh_data);
|
||||
get_loops_polys(mesh, usd_mesh_data);
|
||||
get_edge_creases(mesh, usd_mesh_data);
|
||||
get_vert_creases(mesh, usd_mesh_data);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const MaterialFaceGroups &usd_face_groups)
|
||||
{
|
||||
const int totcol = BKE_object_material_count_eval(context.object);
|
||||
if (totcol == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
|
||||
* which is why we always bind the first material to the entire mesh. See
|
||||
* https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
|
||||
bool mesh_material_bound = false;
|
||||
auto mesh_prim = usd_mesh.GetPrim();
|
||||
pxr::UsdShadeMaterialBindingAPI material_binding_api(mesh_prim);
|
||||
for (int mat_num = 0; mat_num < totcol; mat_num++) {
|
||||
Material *material = BKE_object_material_get_eval(context.object, mat_num + 1);
|
||||
if (material == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||
material_binding_api.Bind(usd_material);
|
||||
|
||||
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
|
||||
* use the flag from the first non-empty material slot. */
|
||||
usd_mesh.CreateDoubleSidedAttr(
|
||||
pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
|
||||
|
||||
mesh_material_bound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mesh_material_bound) {
|
||||
/* USD will require that prims with material bindings have the #MaterialBindingAPI applied
|
||||
* schema. While Bind() above will create the binding attribute, Apply() needs to be called as
|
||||
* well to add the #MaterialBindingAPI schema to the prim itself. */
|
||||
pxr::UsdShadeMaterialBindingAPI::Apply(mesh_prim);
|
||||
}
|
||||
else {
|
||||
/* Blender defaults to double-sided, but USD to single-sided. */
|
||||
usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
|
||||
}
|
||||
|
||||
if (!mesh_material_bound || usd_face_groups.size() < 2) {
|
||||
/* Either all material slots were empty or there is only one material in use. As geometry
|
||||
* subsets are only written when actually used to assign a material, and the mesh already has
|
||||
* the material assigned, there is no need to continue. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Define a geometry subset per material. */
|
||||
for (const MaterialFaceGroups::Item &face_group : usd_face_groups.items()) {
|
||||
short material_number = face_group.key;
|
||||
const pxr::VtIntArray &face_indices = face_group.value;
|
||||
|
||||
Material *material = BKE_object_material_get_eval(context.object, material_number + 1);
|
||||
if (material == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||
pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
|
||||
|
||||
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
|
||||
material_name, face_indices);
|
||||
auto subset_prim = usd_face_subset.GetPrim();
|
||||
auto subset_material_api = pxr::UsdShadeMaterialBindingAPI(subset_prim);
|
||||
subset_material_api.Bind(usd_material);
|
||||
/* Apply the #MaterialBindingAPI applied schema, as required by USD. */
|
||||
pxr::UsdShadeMaterialBindingAPI::Apply(subset_prim);
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh &usd_mesh)
|
||||
{
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
pxr::VtVec3fArray loop_normals;
|
||||
loop_normals.resize(mesh->corners_num);
|
||||
|
||||
MutableSpan dst_normals(reinterpret_cast<float3 *>(loop_normals.data()), loop_normals.size());
|
||||
|
||||
switch (mesh->normals_domain()) {
|
||||
case bke::MeshNormalDomain::Point: {
|
||||
array_utils::gather(mesh->vert_normals(), mesh->corner_verts(), dst_normals);
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Face: {
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
const Span<float3> face_normals = mesh->face_normals();
|
||||
for (const int i : faces.index_range()) {
|
||||
dst_normals.slice(faces[i]).fill(face_normals[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case bke::MeshNormalDomain::Corner: {
|
||||
array_utils::copy(mesh->corner_normals(), dst_normals);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
|
||||
if (!attr_normals.HasValue()) {
|
||||
attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), time);
|
||||
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh)
|
||||
{
|
||||
/* Export velocity attribute output by fluid sim, sequence cache modifier
|
||||
* and geometry nodes. */
|
||||
const VArraySpan velocity = *mesh->attributes().lookup<float3>("velocity",
|
||||
bke::AttrDomain::Point);
|
||||
if (velocity.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Export per-vertex velocity vectors. */
|
||||
Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
|
||||
pxr::VtVec3fArray usd_velocities;
|
||||
usd_velocities.assign(data.begin(), data.end());
|
||||
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
pxr::UsdAttribute attr_vel = usd_mesh.CreateVelocitiesAttr(pxr::VtValue(), true);
|
||||
if (!attr_vel.HasValue()) {
|
||||
attr_vel.Set(usd_velocities, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_vel, usd_velocities, time);
|
||||
}
|
||||
|
||||
USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx)
|
||||
: USDGenericMeshWriter(ctx), write_skinned_mesh_(false), write_blend_shapes_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void USDMeshWriter::set_skel_export_flags(const HierarchyContext &context)
|
||||
{
|
||||
write_skinned_mesh_ = false;
|
||||
write_blend_shapes_ = false;
|
||||
|
||||
const USDExportParams ¶ms = usd_export_context_.export_params;
|
||||
|
||||
/* We can write a skinned mesh if exporting armatures is enabled and the object has an armature
|
||||
* modifier. */
|
||||
write_skinned_mesh_ = params.export_armatures &&
|
||||
can_export_skinned_mesh(*context.object, usd_export_context_.depsgraph);
|
||||
|
||||
/* We can write blend shapes if exporting shape keys is enabled and the object has shape keys. */
|
||||
write_blend_shapes_ = params.export_shapekeys && is_mesh_with_shape_keys(context.object);
|
||||
}
|
||||
|
||||
void USDMeshWriter::init_skinned_mesh(const HierarchyContext &context)
|
||||
{
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
|
||||
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||
|
||||
if (!mesh_prim.IsValid()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"%s: couldn't get valid mesh prim for mesh %s",
|
||||
__func__,
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
|
||||
|
||||
if (!skel_api) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't apply UsdSkelBindingAPI to mesh prim %s",
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const Object *arm_obj = get_armature_modifier_obj(*context.object,
|
||||
usd_export_context_.depsgraph);
|
||||
|
||||
if (!arm_obj) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't get armature modifier object for skinned mesh %s",
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<StringRef> bone_names;
|
||||
get_armature_bone_names(
|
||||
arm_obj, usd_export_context_.export_params.only_deform_bones, bone_names);
|
||||
|
||||
if (bone_names.is_empty()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"No armature bones for skinned mesh %s",
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsfree = false;
|
||||
Mesh *mesh = get_export_mesh(context.object, needsfree);
|
||||
|
||||
if (mesh == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
export_deform_verts(mesh, skel_api, bone_names);
|
||||
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
if (needsfree) {
|
||||
free_export_mesh(mesh);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void USDMeshWriter::init_blend_shapes(const HierarchyContext &context)
|
||||
{
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
|
||||
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||
|
||||
if (!mesh_prim.IsValid()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't get valid mesh prim for mesh %s",
|
||||
mesh_prim.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
create_blend_shapes(this->usd_export_context_.stage,
|
||||
context.object,
|
||||
mesh_prim,
|
||||
usd_export_context_.export_params.allow_unicode);
|
||||
}
|
||||
|
||||
void USDMeshWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
set_skel_export_flags(context);
|
||||
|
||||
if (frame_has_been_written_ && (write_skinned_mesh_ || write_blend_shapes_)) {
|
||||
/* When writing skinned meshes or blend shapes, we only write the rest mesh once,
|
||||
* so we return early after the first frame has been written. However, we still
|
||||
* update blend shape weights if needed. */
|
||||
if (write_blend_shapes_) {
|
||||
add_shape_key_weights_sample(context.object);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
USDGenericMeshWriter::do_write(context);
|
||||
|
||||
if (write_skinned_mesh_) {
|
||||
init_skinned_mesh(context);
|
||||
}
|
||||
|
||||
if (write_blend_shapes_) {
|
||||
init_blend_shapes(context);
|
||||
add_shape_key_weights_sample(context.object);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
|
||||
{
|
||||
if (write_blend_shapes_) {
|
||||
r_needsfree = true;
|
||||
/* We return the pre-modified mesh with the verts in the shape key
|
||||
* basis positions. */
|
||||
return get_shape_key_basis_mesh(object_eval);
|
||||
}
|
||||
|
||||
if (write_skinned_mesh_) {
|
||||
r_needsfree = false;
|
||||
/* We must export the skinned mesh in its rest pose. We therefore
|
||||
* return the pre-modified mesh, so that the armature modifier isn't
|
||||
* applied. */
|
||||
/* TODO: Store the "needs free" mesh in a separate variable. */
|
||||
return const_cast<Mesh *>(BKE_object_get_pre_modified_mesh(object_eval));
|
||||
}
|
||||
|
||||
/* Return the fully evaluated mesh. */
|
||||
r_needsfree = false;
|
||||
return BKE_object_get_evaluated_mesh(object_eval);
|
||||
}
|
||||
|
||||
void USDMeshWriter::add_shape_key_weights_sample(const Object *obj)
|
||||
{
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Key *key = get_mesh_shape_key(obj);
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
|
||||
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||
|
||||
if (!mesh_prim.IsValid()) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't get valid mesh prim for mesh %s",
|
||||
usd_export_context_.usd_path.GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtFloatArray weights = get_blendshape_weights(key);
|
||||
pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
/* Save the weights samples to a temporary privar which will be copied to
|
||||
* a skeleton animation later. */
|
||||
pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).CreatePrimvar(
|
||||
TempBlendShapeWeightsPrimvarName, pxr::SdfValueTypeNames->FloatArray);
|
||||
|
||||
if (!temp_weights_attr) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Couldn't create primvar %s on prim %s",
|
||||
TempBlendShapeWeightsPrimvarName.GetText(),
|
||||
mesh_prim.GetPath().GetAsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
temp_weights_attr.Set(weights, time);
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct SubsurfModifierData;
|
||||
|
||||
namespace bke {
|
||||
class AttributeIter;
|
||||
} // namespace bke
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
struct USDMeshData;
|
||||
|
||||
/* Mapping from material slot number to array of face indices with that material. */
|
||||
using MaterialFaceGroups = Map<short, pxr::VtArray<int>>;
|
||||
|
||||
/* Writer for USD geometry. Does not assume the object is a mesh object. */
|
||||
class USDGenericMeshWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDGenericMeshWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
bool is_supported(const HierarchyContext *context) const override;
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
|
||||
virtual void free_export_mesh(Mesh *mesh);
|
||||
|
||||
private:
|
||||
void write_mesh(HierarchyContext &context, Mesh *mesh, const SubsurfModifierData *subsurfData);
|
||||
pxr::TfToken get_subdiv_scheme(const SubsurfModifierData *subsurfData);
|
||||
void write_subdiv(const pxr::TfToken &subdiv_scheme,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const SubsurfModifierData *subsurfData);
|
||||
void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data);
|
||||
void assign_materials(const HierarchyContext &context,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const MaterialFaceGroups &usd_face_groups);
|
||||
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh &usd_mesh);
|
||||
void write_surface_velocity(const Mesh *mesh, const pxr::UsdGeomMesh &usd_mesh);
|
||||
|
||||
void write_custom_data(const Object *obj, const Mesh *mesh, const pxr::UsdGeomMesh &usd_mesh);
|
||||
void write_generic_data(const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const bke::AttributeIter &attr);
|
||||
void write_uv_data(const Mesh *mesh,
|
||||
const pxr::UsdGeomMesh &usd_mesh,
|
||||
const bke::AttributeIter &attr,
|
||||
StringRef active_uvmap_name);
|
||||
};
|
||||
|
||||
class USDMeshWriter : public USDGenericMeshWriter {
|
||||
bool write_skinned_mesh_;
|
||||
bool write_blend_shapes_;
|
||||
|
||||
public:
|
||||
USDMeshWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
|
||||
|
||||
/**
|
||||
* Determine whether we should write skinned mesh or blend shape data
|
||||
* based on the export parameters and the modifiers enabled on the object.
|
||||
*/
|
||||
void set_skel_export_flags(const HierarchyContext &context);
|
||||
|
||||
void init_skinned_mesh(const HierarchyContext &context);
|
||||
void init_blend_shapes(const HierarchyContext &context);
|
||||
|
||||
void add_shape_key_weights_sample(const Object *obj);
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_metaball.hh"
|
||||
#include "usd_exporter_context.hh"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mball.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDMetaballWriter::USDMetaballWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx) {}
|
||||
|
||||
bool USDMetaballWriter::is_supported(const HierarchyContext *context) const
|
||||
{
|
||||
Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph);
|
||||
return is_basis_ball(scene, context->object) && USDGenericMeshWriter::is_supported(context);
|
||||
}
|
||||
|
||||
bool USDMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const
|
||||
{
|
||||
/* We assume that meta-balls are always animated, as the current object may
|
||||
* not be animated but another ball in the same group may be. */
|
||||
return true;
|
||||
}
|
||||
|
||||
Mesh *USDMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
|
||||
{
|
||||
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
|
||||
if (mesh_eval != nullptr) {
|
||||
/* Mesh_eval only exists when generative modifiers are in use. */
|
||||
r_needsfree = false;
|
||||
return mesh_eval;
|
||||
}
|
||||
r_needsfree = true;
|
||||
return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false, false, true);
|
||||
}
|
||||
|
||||
void USDMetaballWriter::free_export_mesh(Mesh *mesh)
|
||||
{
|
||||
BKE_id_free(nullptr, mesh);
|
||||
}
|
||||
|
||||
bool USDMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const
|
||||
{
|
||||
const Object *basis_ob = BKE_mball_basis_find(*usd_export_context_.bmain, scene, ob);
|
||||
return ob == basis_ob;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_mesh.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDMetaballWriter : public USDGenericMeshWriter {
|
||||
public:
|
||||
USDMetaballWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
|
||||
void free_export_mesh(Mesh *mesh) override;
|
||||
bool is_supported(const HierarchyContext *context) const override;
|
||||
bool check_is_animated(const HierarchyContext &context) const override;
|
||||
|
||||
private:
|
||||
bool is_basis_ball(Scene *scene, Object *ob) const;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,605 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_writer_pointinstancer.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_collection.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_geometry_set_instances.hh"
|
||||
#include "BKE_instances.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "BLI_math_euler.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_layer_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include <pxr/base/gf/quatf.h>
|
||||
#include <pxr/base/gf/vec3d.h>
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/base/vt/array.h>
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
#include "IO_abstract_hierarchy_iterator.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDPointInstancerWriter::USDPointInstancerWriter(
|
||||
const USDExporterContext &ctx,
|
||||
const Set<std::pair<pxr::SdfPath, Object *>> &prototype_paths,
|
||||
std::unique_ptr<USDAbstractWriter> base_writer)
|
||||
: USDAbstractWriter(ctx),
|
||||
base_writer_(std::move(base_writer)),
|
||||
prototype_paths_(prototype_paths)
|
||||
{
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
/* Write the base data first (e.g., mesh, curves, points) */
|
||||
if (base_writer_) {
|
||||
base_writer_->write(context);
|
||||
|
||||
if (usd_export_context_.add_skel_mapping_fn &&
|
||||
(usd_export_context_.export_params.export_armatures ||
|
||||
usd_export_context_.export_params.export_shapekeys))
|
||||
{
|
||||
usd_export_context_.add_skel_mapping_fn(context.object, base_writer_->usd_path());
|
||||
}
|
||||
}
|
||||
|
||||
const pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
const Object *object_eval = context.object;
|
||||
bke::GeometrySet instance_geometry_set = bke::object_get_evaluated_geometry_set(*object_eval);
|
||||
|
||||
const bke::GeometryComponent *component = instance_geometry_set.get_component(
|
||||
bke::GeometryComponent::Type::Instance);
|
||||
|
||||
const bke::Instances *instances = static_cast<const bke::InstancesComponent &>(*component).get();
|
||||
|
||||
int instance_num = instances->instances_num();
|
||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
||||
const pxr::UsdGeomPointInstancer usd_instancer = pxr::UsdGeomPointInstancer::Define(stage,
|
||||
usd_path);
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
Span<float4x4> transforms = instances->transforms();
|
||||
BLI_assert(transforms.size() >= instance_num);
|
||||
|
||||
if (transforms.size() != instance_num) {
|
||||
BKE_reportf(this->reports(),
|
||||
RPT_ERROR,
|
||||
"Instances number '%d' does not match transforms size '%d'",
|
||||
instance_num,
|
||||
int(transforms.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
/* evaluated positions */
|
||||
pxr::UsdAttribute position_attr = usd_instancer.CreatePositionsAttr();
|
||||
pxr::VtArray<pxr::GfVec3f> positions(instance_num);
|
||||
for (int i = 0; i < instance_num; i++) {
|
||||
const float3 &pos = transforms[i].location();
|
||||
positions[i] = pxr::GfVec3f(pos.x, pos.y, pos.z);
|
||||
}
|
||||
io::usd::set_attribute(position_attr, positions, time, usd_value_writer_);
|
||||
|
||||
/* orientations */
|
||||
pxr::UsdAttribute orientations_attr = usd_instancer.CreateOrientationsAttr();
|
||||
pxr::VtArray<pxr::GfQuath> orientation(instance_num);
|
||||
for (int i = 0; i < instance_num; i++) {
|
||||
const float3 euler = float3(math::to_euler(math::normalize(transforms[i])));
|
||||
const math::Quaternion quat = math::to_quaternion(math::EulerXYZ(euler));
|
||||
orientation[i] = pxr::GfQuath(quat.w, pxr::GfVec3h(quat.x, quat.y, quat.z));
|
||||
}
|
||||
io::usd::set_attribute(orientations_attr, orientation, time, usd_value_writer_);
|
||||
|
||||
/* scales */
|
||||
pxr::UsdAttribute scales_attr = usd_instancer.CreateScalesAttr();
|
||||
pxr::VtArray<pxr::GfVec3f> scales(instance_num);
|
||||
for (int i = 0; i < instance_num; i++) {
|
||||
const MatBase<float, 4, 4> &mat = transforms[i];
|
||||
float3 scale_vec = math::to_scale<true>(mat);
|
||||
scales[i] = pxr::GfVec3f(scale_vec.x, scale_vec.y, scale_vec.z);
|
||||
}
|
||||
io::usd::set_attribute(scales_attr, scales, time, usd_value_writer_);
|
||||
|
||||
/* IDs */
|
||||
const Span<int> ids = instances->unique_ids();
|
||||
pxr::VtInt64Array usd_ids(ids.begin(), ids.end());
|
||||
pxr::UsdAttribute attr_ids = usd_instancer.CreateIdsAttr();
|
||||
io::usd::set_attribute(attr_ids, usd_ids, time, usd_value_writer_);
|
||||
|
||||
/* other attr */
|
||||
bke::AttributeAccessor attributes_eval = *component->attributes();
|
||||
attributes_eval.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
|
||||
ELEM(iter.name, "instance_transform", "scale", "orientation", "mask", "proto_index", "id"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_attribute_data(iter, usd_instancer, time);
|
||||
});
|
||||
|
||||
/* prototypes relations */
|
||||
const pxr::SdfPath protoParentPath = usd_path.AppendChild(pxr::TfToken("Prototypes"));
|
||||
pxr::UsdPrim prototypesOver = stage->DefinePrim(protoParentPath);
|
||||
pxr::SdfPathVector proto_wrapper_paths;
|
||||
|
||||
Map<std::string, int> proto_index_map;
|
||||
Map<std::string, pxr::SdfPath> proto_path_map;
|
||||
|
||||
if (!prototype_paths_.is_empty() && usd_instancer) {
|
||||
int iter = 0;
|
||||
|
||||
for (const std::pair<pxr::SdfPath, Object *> &entry : prototype_paths_) {
|
||||
const pxr::SdfPath &source_path = entry.first;
|
||||
Object *obj = entry.second;
|
||||
|
||||
if (source_path.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxr::SdfPath proto_path = protoParentPath.AppendChild(
|
||||
pxr::TfToken("Prototype_" + std::to_string(iter)));
|
||||
|
||||
pxr::UsdPrim prim = stage->DefinePrim(proto_path);
|
||||
|
||||
/* To avoid USD error of Unresolved reference prim path, make sure the referenced path
|
||||
* exists. */
|
||||
stage->DefinePrim(source_path);
|
||||
prim.GetReferences().AddReference(pxr::SdfReference("", source_path));
|
||||
proto_wrapper_paths.push_back(proto_path);
|
||||
|
||||
std::string ob_name = BKE_id_name(obj->id);
|
||||
proto_index_map.add_new(ob_name, iter);
|
||||
proto_path_map.add_new(ob_name, proto_path);
|
||||
|
||||
++iter;
|
||||
}
|
||||
usd_instancer.GetPrototypesRel().SetTargets(proto_wrapper_paths);
|
||||
}
|
||||
|
||||
/* proto indices */
|
||||
/* must be the last to populate */
|
||||
pxr::UsdAttribute proto_indices_attr = usd_instancer.CreateProtoIndicesAttr();
|
||||
pxr::VtArray<int> proto_indices;
|
||||
Vector<std::pair<int, int>> collection_instance_object_count_map;
|
||||
|
||||
Span<int> reference_handles = instances->reference_handles();
|
||||
Span<bke::InstanceReference> references = instances->references();
|
||||
Map<std::string, int> final_proto_index_map;
|
||||
|
||||
for (int i = 0; i < instance_num; i++) {
|
||||
bke::InstanceReference reference = references[reference_handles[i]];
|
||||
|
||||
process_instance_reference(reference,
|
||||
i,
|
||||
proto_index_map,
|
||||
final_proto_index_map,
|
||||
proto_path_map,
|
||||
stage,
|
||||
proto_indices,
|
||||
collection_instance_object_count_map);
|
||||
}
|
||||
|
||||
io::usd::set_attribute(proto_indices_attr, proto_indices, time, usd_value_writer_);
|
||||
|
||||
/* Handle Collection Prototypes */
|
||||
if (!collection_instance_object_count_map.is_empty()) {
|
||||
handle_collection_prototypes(
|
||||
usd_instancer, time, instance_num, collection_instance_object_count_map);
|
||||
}
|
||||
|
||||
/* Clean unused prototype. When finding prototype paths under the context of a point instancer,
|
||||
* all the prototypes are collected, even those used by lower-level nested child PointInstancers.
|
||||
* It can happen that different levels in nested PointInstancers share the same prototypes, but
|
||||
* if not, we need to clean the extra prototypes from the prototype relationship for a cleaner
|
||||
* USD export. */
|
||||
compact_prototypes(usd_instancer, time, proto_wrapper_paths);
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::process_instance_reference(
|
||||
const bke::InstanceReference &reference,
|
||||
int instance_index,
|
||||
Map<std::string, int> &proto_index_map,
|
||||
Map<std::string, int> &final_proto_index_map,
|
||||
Map<std::string, pxr::SdfPath> &proto_path_map,
|
||||
pxr::UsdStageRefPtr stage,
|
||||
pxr::VtArray<int> &proto_indices,
|
||||
Vector<std::pair<int, int>> &collection_instance_object_count_map)
|
||||
{
|
||||
/* TODO: Verify logic around the `add_overwrite` calls below. Using `add_new` will trigger
|
||||
* asserts because multiple items are being added to the map with the same key. Original code
|
||||
* was using std::map and repeatedly reassigning with `final_proto_index_map[ob_name] = ...` */
|
||||
switch (reference.type()) {
|
||||
case bke::InstanceReference::Type::Object: {
|
||||
Object &object = reference.object();
|
||||
std::string ob_name = BKE_id_name(object.id);
|
||||
|
||||
if (proto_index_map.contains(ob_name)) {
|
||||
proto_indices.push_back(proto_index_map.lookup(ob_name));
|
||||
|
||||
final_proto_index_map.add_overwrite(ob_name, proto_index_map.lookup(ob_name));
|
||||
|
||||
/* If the reference is Object, clear prototype's local transform to identity to avoid
|
||||
* double transforms. The PointInstancer will fully control instance placement. */
|
||||
override_transform(stage, proto_path_map.lookup(ob_name), float4x4::identity());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case bke::InstanceReference::Type::Collection: {
|
||||
Collection &collection = reference.collection();
|
||||
int object_num = 0;
|
||||
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) {
|
||||
std::string ob_name = BKE_id_name(object->id);
|
||||
|
||||
if (proto_index_map.contains(ob_name)) {
|
||||
object_num += 1;
|
||||
proto_indices.push_back(proto_index_map.lookup(ob_name));
|
||||
|
||||
final_proto_index_map.add_overwrite(ob_name, proto_index_map.lookup(ob_name));
|
||||
}
|
||||
}
|
||||
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
|
||||
collection_instance_object_count_map.append(std::make_pair(instance_index, object_num));
|
||||
break;
|
||||
}
|
||||
|
||||
case bke::InstanceReference::Type::GeometrySet: {
|
||||
bke::GeometrySet geometry_set = reference.geometry_set();
|
||||
const StringRef set_name = geometry_set.name();
|
||||
|
||||
if (proto_index_map.contains_as(set_name)) {
|
||||
proto_indices.push_back(proto_index_map.lookup_as(set_name));
|
||||
|
||||
final_proto_index_map.add_overwrite(set_name, proto_index_map.lookup_as(set_name));
|
||||
}
|
||||
|
||||
Vector<const bke::GeometryComponent *> components = geometry_set.get_components();
|
||||
for (const bke::GeometryComponent *comp : components) {
|
||||
if (const bke::Instances *instances =
|
||||
static_cast<const bke::InstancesComponent &>(*comp).get())
|
||||
{
|
||||
Span<int> ref_handles = instances->reference_handles();
|
||||
Span<bke::InstanceReference> refs = instances->references();
|
||||
|
||||
/* If the top-level GeometrySet is not in proto_index_map, recursively traverse child
|
||||
* InstanceReferences to resolve prototype indices. If the name matches proto_index_map,
|
||||
* skip traversal to avoid duplicates, since GeometrySet names may overlap with object
|
||||
* names. */
|
||||
if (!proto_index_map.contains(set_name)) {
|
||||
for (int index = 0; index < ref_handles.size(); ++index) {
|
||||
const bke::InstanceReference &child_ref = refs[ref_handles[index]];
|
||||
|
||||
/* Recursively traverse nested GeometrySets to resolve prototype indices for all
|
||||
* instances. */
|
||||
process_instance_reference(child_ref,
|
||||
instance_index,
|
||||
proto_index_map,
|
||||
final_proto_index_map,
|
||||
proto_path_map,
|
||||
stage,
|
||||
proto_indices,
|
||||
collection_instance_object_count_map);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the reference is GeometrySet, then override the transform with the transform of the
|
||||
* Instance inside this GeometrySet. */
|
||||
Span<float4x4> transforms = instances->transforms();
|
||||
if (transforms.size() == 1) {
|
||||
if (proto_path_map.contains(set_name)) {
|
||||
override_transform(stage, proto_path_map.lookup(set_name), transforms[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case bke::InstanceReference::Type::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::compact_prototypes(const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::SdfPathVector &proto_paths) const
|
||||
{
|
||||
pxr::UsdAttribute proto_indices_attr = usd_instancer.GetProtoIndicesAttr();
|
||||
pxr::VtArray<int> proto_indices;
|
||||
if (!proto_indices_attr.Get(&proto_indices, time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find actually used prototype indices. */
|
||||
Set<int> used_proto_indices;
|
||||
used_proto_indices.add_multiple(Span(proto_indices.cbegin(), proto_indices.size()));
|
||||
|
||||
Map<int, int> remap;
|
||||
int new_index = 0;
|
||||
for (int i = 0; i < proto_paths.size(); ++i) {
|
||||
if (used_proto_indices.contains(i)) {
|
||||
remap.add(i, new_index++);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remap protoIndices. */
|
||||
for (int &idx : proto_indices) {
|
||||
idx = remap.lookup(idx);
|
||||
}
|
||||
proto_indices_attr.Set(proto_indices, time);
|
||||
|
||||
pxr::SdfPathVector compact_proto_paths;
|
||||
for (int i = 0; i < proto_paths.size(); ++i) {
|
||||
if (used_proto_indices.contains(i)) {
|
||||
compact_proto_paths.push_back(proto_paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
usd_instancer.GetPrototypesRel().SetTargets(compact_proto_paths);
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::override_transform(const pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath &proto_path,
|
||||
const float4x4 &transform) const
|
||||
{
|
||||
pxr::UsdPrim prim = stage->GetPrimAtPath(proto_path);
|
||||
if (!prim) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract translation. */
|
||||
const float3 &pos = transform.location();
|
||||
pxr::GfVec3d override_position(pos.x, pos.y, pos.z);
|
||||
|
||||
/* Extract rotation. */
|
||||
const float3 euler = float3(math::to_euler(math::normalize(transform)));
|
||||
pxr::GfVec3f override_rotation(euler.x, euler.y, euler.z);
|
||||
|
||||
/* Extract scale. */
|
||||
const float3 scale_vec = math::to_scale<true>(transform);
|
||||
pxr::GfVec3f override_scale(scale_vec.x, scale_vec.y, scale_vec.z);
|
||||
|
||||
pxr::UsdGeomXformable xformable(prim);
|
||||
xformable.ClearXformOpOrder();
|
||||
xformable.AddTranslateOp().Set(override_position);
|
||||
xformable.AddRotateXYZOp().Set(override_rotation);
|
||||
xformable.AddScaleOp().Set(override_scale);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static pxr::VtArray<T> DuplicateArray(const pxr::VtArray<T> &original, size_t copies)
|
||||
{
|
||||
pxr::VtArray<T> newArray;
|
||||
size_t originalSize = original.size();
|
||||
newArray.resize(originalSize * copies);
|
||||
for (size_t i = 0; i < copies; ++i) {
|
||||
std::copy(original.begin(), original.end(), newArray.begin() + i * originalSize);
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
template<typename T, typename GetterFunc, typename CreatorFunc>
|
||||
static void DuplicatePerInstanceAttribute(const GetterFunc &getter,
|
||||
const CreatorFunc &creator,
|
||||
size_t copies,
|
||||
const pxr::UsdTimeCode &time)
|
||||
{
|
||||
pxr::VtArray<T> values;
|
||||
if (getter().Get(&values, time) && !values.empty()) {
|
||||
auto newValues = DuplicateArray(values, copies);
|
||||
creator().Set(newValues, time);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename GetterFunc, typename CreatorFunc>
|
||||
static void ExpandAttributePerInstance(const GetterFunc &getter,
|
||||
const CreatorFunc &creator,
|
||||
const Span<std::pair<int, int>> instance_object_map,
|
||||
const pxr::UsdTimeCode &time)
|
||||
{
|
||||
/* MARK: Handle Collection Prototypes
|
||||
* ----------------------------------
|
||||
* In Blender, a Collection is not an actual Object type. When exporting, the iterator
|
||||
* flattens the Collection hierarchy, treating each object inside the Collection as an
|
||||
* individual prototype. However, all these prototypes share the same instance attributes
|
||||
* (e.g., positions, orientations, scales).
|
||||
*
|
||||
* To ensure correct arrangement, reading, and drawing in OpenUSD, we need to explicitly
|
||||
* duplicate the instance attributes across all prototypes derived from the Collection. */
|
||||
pxr::VtArray<T> original_values;
|
||||
if (!getter().Get(&original_values, time) || original_values.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtArray<T> expanded_values;
|
||||
for (const auto &[instance_index, object_count] : instance_object_map) {
|
||||
if (instance_index < int(original_values.size())) {
|
||||
for (int i = 0; i < object_count; ++i) {
|
||||
expanded_values.push_back(original_values[instance_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
creator().Set(expanded_values, time);
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::handle_collection_prototypes(
|
||||
const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time,
|
||||
const int instance_num,
|
||||
const Span<std::pair<int, int>> collection_instance_object_count_map) const
|
||||
{
|
||||
/* Duplicate attributes. */
|
||||
if (usd_instancer.GetPositionsAttr().HasAuthoredValue()) {
|
||||
ExpandAttributePerInstance<pxr::GfVec3f>([&]() { return usd_instancer.GetPositionsAttr(); },
|
||||
[&]() { return usd_instancer.CreatePositionsAttr(); },
|
||||
collection_instance_object_count_map,
|
||||
time);
|
||||
}
|
||||
if (usd_instancer.GetOrientationsAttr().HasAuthoredValue()) {
|
||||
ExpandAttributePerInstance<pxr::GfQuath>(
|
||||
[&]() { return usd_instancer.GetOrientationsAttr(); },
|
||||
[&]() { return usd_instancer.CreateOrientationsAttr(); },
|
||||
collection_instance_object_count_map,
|
||||
time);
|
||||
}
|
||||
if (usd_instancer.GetScalesAttr().HasAuthoredValue()) {
|
||||
ExpandAttributePerInstance<pxr::GfVec3f>([&]() { return usd_instancer.GetScalesAttr(); },
|
||||
[&]() { return usd_instancer.CreateScalesAttr(); },
|
||||
collection_instance_object_count_map,
|
||||
time);
|
||||
}
|
||||
if (usd_instancer.GetVelocitiesAttr().HasAuthoredValue()) {
|
||||
ExpandAttributePerInstance<pxr::GfVec3f>(
|
||||
[&]() { return usd_instancer.GetVelocitiesAttr(); },
|
||||
[&]() { return usd_instancer.CreateVelocitiesAttr(); },
|
||||
collection_instance_object_count_map,
|
||||
time);
|
||||
}
|
||||
if (usd_instancer.GetAngularVelocitiesAttr().HasAuthoredValue()) {
|
||||
ExpandAttributePerInstance<pxr::GfVec3f>(
|
||||
[&]() { return usd_instancer.GetAngularVelocitiesAttr(); },
|
||||
[&]() { return usd_instancer.CreateAngularVelocitiesAttr(); },
|
||||
collection_instance_object_count_map,
|
||||
time);
|
||||
}
|
||||
|
||||
/* Duplicate Primvars. */
|
||||
const pxr::UsdGeomPrimvarsAPI primvars_api(usd_instancer);
|
||||
for (const pxr::UsdGeomPrimvar &primvar : primvars_api.GetPrimvars()) {
|
||||
if (!primvar.HasAuthoredValue()) {
|
||||
continue;
|
||||
}
|
||||
const pxr::TfToken pv_name = primvar.GetPrimvarName();
|
||||
const pxr::SdfValueTypeName pv_type = primvar.GetTypeName();
|
||||
const pxr::TfToken pv_interp = primvar.GetInterpolation();
|
||||
auto create = [&]() { return primvars_api.CreatePrimvar(pv_name, pv_type, pv_interp); };
|
||||
|
||||
if (pv_type == pxr::SdfValueTypeNames->FloatArray) {
|
||||
ExpandAttributePerInstance<float>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->IntArray) {
|
||||
ExpandAttributePerInstance<int>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->UCharArray) {
|
||||
ExpandAttributePerInstance<uchar>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->Float2Array) {
|
||||
ExpandAttributePerInstance<pxr::GfVec2f>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (ELEM(pv_type,
|
||||
pxr::SdfValueTypeNames->Float3Array,
|
||||
pxr::SdfValueTypeNames->Color3fArray,
|
||||
pxr::SdfValueTypeNames->Color4fArray))
|
||||
{
|
||||
ExpandAttributePerInstance<pxr::GfVec3f>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->QuatfArray) {
|
||||
ExpandAttributePerInstance<pxr::GfQuatf>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->BoolArray) {
|
||||
ExpandAttributePerInstance<bool>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
else if (pv_type == pxr::SdfValueTypeNames->StringArray) {
|
||||
ExpandAttributePerInstance<std::string>(
|
||||
[&]() { return primvar; }, create, collection_instance_object_count_map, time);
|
||||
}
|
||||
}
|
||||
|
||||
/* MARK: Ensure Instance Indices Exist
|
||||
* -----------------------------------
|
||||
* If the PointInstancer has no authored instance indices, manually generate a default
|
||||
* sequence of indices to ensure the PointInstancer functions correctly in OpenUSD.
|
||||
* This guarantees that each instance can correctly reference its prototype. */
|
||||
pxr::UsdAttribute proto_indices_attr = usd_instancer.GetProtoIndicesAttr();
|
||||
if (!proto_indices_attr.HasAuthoredValue()) {
|
||||
Vector<int> index;
|
||||
for (int i = 0; i < prototype_paths_.size(); i++) {
|
||||
index.append_n_times(i, instance_num);
|
||||
}
|
||||
|
||||
proto_indices_attr.Set(pxr::VtArray<int>(index.begin(), index.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void USDPointInstancerWriter::write_attribute_data(const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(attr.data_type);
|
||||
|
||||
if (!pv_type) {
|
||||
BKE_reportf(this->reports(),
|
||||
RPT_WARNING,
|
||||
"Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
|
||||
attr.name.c_str(),
|
||||
int(attr.domain),
|
||||
int(attr.data_type));
|
||||
return;
|
||||
}
|
||||
|
||||
const GVArray attribute = *attr.get();
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name == "mask") {
|
||||
pxr::UsdAttribute idsAttr = usd_instancer.GetIdsAttr();
|
||||
if (!idsAttr) {
|
||||
idsAttr = usd_instancer.CreateIdsAttr();
|
||||
}
|
||||
|
||||
pxr::UsdAttribute invisibleIdsAttr = usd_instancer.GetInvisibleIdsAttr();
|
||||
if (!invisibleIdsAttr) {
|
||||
invisibleIdsAttr = usd_instancer.CreateInvisibleIdsAttr();
|
||||
}
|
||||
|
||||
Vector<bool> mask_values(attribute.size());
|
||||
attribute.materialize(IndexMask(attribute.size()), mask_values.data());
|
||||
|
||||
pxr::VtArray<int64_t> ids;
|
||||
pxr::VtArray<int64_t> invisibleIds;
|
||||
ids.reserve(mask_values.size());
|
||||
|
||||
for (int64_t i = 0; i < mask_values.size(); i++) {
|
||||
ids.push_back(i);
|
||||
if (!mask_values[i]) {
|
||||
invisibleIds.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
io::usd::set_attribute(idsAttr, ids, time, usd_value_writer_);
|
||||
io::usd::set_attribute(invisibleIdsAttr, invisibleIds, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(attr.name, usd_export_context_.export_params.allow_unicode));
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_instancer);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type);
|
||||
|
||||
copy_blender_attribute_to_primvar(attribute, attr.data_type, time, pv_attr, usd_value_writer_);
|
||||
}
|
||||
|
||||
} /* namespace blender::io::usd */
|
||||
@@ -0,0 +1,63 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||
|
||||
struct USDExporterContext;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDPointInstancerWriter final : public USDAbstractWriter {
|
||||
private:
|
||||
std::unique_ptr<USDAbstractWriter> base_writer_;
|
||||
Set<std::pair<pxr::SdfPath, Object *>> prototype_paths_;
|
||||
|
||||
public:
|
||||
USDPointInstancerWriter(const USDExporterContext &ctx,
|
||||
const Set<std::pair<pxr::SdfPath, Object *>> &prototype_paths,
|
||||
std::unique_ptr<USDAbstractWriter> base_writer);
|
||||
~USDPointInstancerWriter() override = default;
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
private:
|
||||
void write_attribute_data(const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time);
|
||||
|
||||
void process_instance_reference(
|
||||
const bke::InstanceReference &reference,
|
||||
int instance_index,
|
||||
Map<std::string, int> &proto_index_map,
|
||||
Map<std::string, int> &final_proto_index_map,
|
||||
Map<std::string, pxr::SdfPath> &proto_path_map,
|
||||
pxr::UsdStageRefPtr stage,
|
||||
pxr::VtArray<int> &proto_indices,
|
||||
Vector<std::pair<int, int>> &collection_instance_object_count_map);
|
||||
|
||||
void compact_prototypes(const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time,
|
||||
const pxr::SdfPathVector &proto_paths) const;
|
||||
|
||||
void override_transform(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath &proto_path,
|
||||
const float4x4 &transform) const;
|
||||
|
||||
void handle_collection_prototypes(
|
||||
const pxr::UsdGeomPointInstancer &usd_instancer,
|
||||
const pxr::UsdTimeCode time,
|
||||
int instance_num,
|
||||
const Span<std::pair<int, int>> collection_instance_object_count_map) const;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
161
blender-5.2.0/source/blender/io/usd/intern/usd_writer_points.cc
Normal file
161
blender-5.2.0/source/blender/io/usd/intern/usd_writer_points.cc
Normal file
@@ -0,0 +1,161 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_writer_points.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include <pxr/base/vt/array.h>
|
||||
#include <pxr/usd/usdGeom/points.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
void USDPointsWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
const pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
|
||||
const PointCloud *points = id_cast<const PointCloud *>(context.object->data);
|
||||
Span<pxr::GfVec3f> positions = points->positions().cast<pxr::GfVec3f>();
|
||||
VArray<float> radii = points->radius();
|
||||
|
||||
const pxr::UsdGeomPoints usd_points = pxr::UsdGeomPoints::Define(stage, usd_path);
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> usd_positions;
|
||||
usd_positions.assign(positions.begin(), positions.end());
|
||||
|
||||
pxr::UsdAttribute attr_positions = usd_points.CreatePointsAttr(pxr::VtValue(), true);
|
||||
if (!attr_positions.HasValue()) {
|
||||
attr_positions.Set(usd_positions, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_positions, usd_positions, time);
|
||||
|
||||
if (!radii.is_empty()) {
|
||||
pxr::VtArray<float> usd_widths;
|
||||
usd_widths.resize(radii.size());
|
||||
for (const int i : radii.index_range()) {
|
||||
usd_widths[i] = radii[i] * 2.0f;
|
||||
}
|
||||
|
||||
pxr::UsdAttribute attr_widths = usd_points.CreateWidthsAttr(pxr::VtValue(), true);
|
||||
if (!attr_widths.HasValue()) {
|
||||
attr_widths.Set(usd_widths, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
usd_value_writer_.SetAttribute(attr_widths, usd_widths, time);
|
||||
}
|
||||
|
||||
this->write_ids(points, usd_points, time);
|
||||
this->write_velocities(points, usd_points, time);
|
||||
this->write_custom_data(points, usd_points, time);
|
||||
|
||||
this->author_extent(usd_points, points->bounds_min_max(), time);
|
||||
}
|
||||
|
||||
static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
|
||||
const bke::AttrDomain blender_domain)
|
||||
{
|
||||
switch (blender_domain) {
|
||||
case bke::AttrDomain::Point:
|
||||
return pxr::UsdGeomTokens->varying;
|
||||
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void USDPointsWriter::write_generic_data(const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(attr.domain);
|
||||
const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(attr.data_type);
|
||||
|
||||
if (!pv_interp || !pv_type) {
|
||||
BKE_reportf(this->reports(),
|
||||
RPT_WARNING,
|
||||
"Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
|
||||
attr.name.c_str(),
|
||||
int(attr.domain),
|
||||
int(attr.data_type));
|
||||
return;
|
||||
}
|
||||
|
||||
const GVArray attribute = *attr.get();
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::TfToken pv_name(
|
||||
make_safe_primvar_name(attr.name, usd_export_context_.export_params.allow_unicode));
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_points);
|
||||
|
||||
pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
|
||||
|
||||
copy_blender_attribute_to_primvar(attribute, attr.data_type, time, pv_attr, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDPointsWriter::write_custom_data(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = points->attributes();
|
||||
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
/* Skip "internal" Blender properties and attributes dealt with elsewhere. */
|
||||
if (iter.name[0] == '.' || bke::attribute_name_is_anonymous(iter.name) ||
|
||||
ELEM(iter.name, "position", "radius", "id", "velocity"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_generic_data(iter, usd_points, time);
|
||||
});
|
||||
}
|
||||
|
||||
void USDPointsWriter::write_ids(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const VArraySpan ids = *points->attributes().lookup<int>("id", bke::AttrDomain::Point);
|
||||
if (ids.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::VtInt64Array usd_ids(ids.begin(), ids.end());
|
||||
pxr::UsdAttribute attr_ids = usd_points.CreateIdsAttr(pxr::VtValue(), true);
|
||||
set_attribute(attr_ids, usd_ids, time, usd_value_writer_);
|
||||
}
|
||||
|
||||
void USDPointsWriter::write_velocities(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
const pxr::UsdTimeCode time)
|
||||
{
|
||||
const VArraySpan velocity = *points->attributes().lookup<float3>("velocity",
|
||||
bke::AttrDomain::Point);
|
||||
if (velocity.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Span<pxr::GfVec3f> data = velocity.cast<pxr::GfVec3f>();
|
||||
pxr::VtArray<pxr::GfVec3f> usd_velocities;
|
||||
usd_velocities.assign(data.begin(), data.end());
|
||||
|
||||
pxr::UsdAttribute attr_vel = usd_points.CreateVelocitiesAttr(pxr::VtValue(), true);
|
||||
if (!attr_vel.HasValue()) {
|
||||
attr_vel.Set(usd_velocities, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_vel, usd_velocities, time);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/points.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
struct PointCloud;
|
||||
|
||||
namespace bke {
|
||||
class AttributeIter;
|
||||
} // namespace bke
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Writer for USD points. */
|
||||
class USDPointsWriter final : public USDAbstractWriter {
|
||||
public:
|
||||
USDPointsWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
~USDPointsWriter() final = default;
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
private:
|
||||
void write_generic_data(const bke::AttributeIter &attr,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
pxr::UsdTimeCode time);
|
||||
|
||||
void write_custom_data(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
pxr::UsdTimeCode time);
|
||||
|
||||
void write_ids(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
pxr::UsdTimeCode time);
|
||||
void write_velocities(const PointCloud *points,
|
||||
const pxr::UsdGeomPoints &usd_points,
|
||||
pxr::UsdTimeCode time);
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_text.hh"
|
||||
#include "usd_exporter_context.hh"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDTextWriter::USDTextWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx) {}
|
||||
|
||||
Mesh *USDTextWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
|
||||
{
|
||||
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
|
||||
if (mesh_eval != nullptr) {
|
||||
/* Mesh_eval only exists when generative modifiers are in use. */
|
||||
r_needsfree = false;
|
||||
return mesh_eval;
|
||||
}
|
||||
r_needsfree = true;
|
||||
return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false, false, true);
|
||||
}
|
||||
|
||||
void USDTextWriter::free_export_mesh(Mesh *mesh)
|
||||
{
|
||||
BKE_id_free(nullptr, mesh);
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,25 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_mesh.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
class USDTextWriter : public USDGenericMeshWriter {
|
||||
public:
|
||||
USDTextWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
|
||||
void free_export_mesh(Mesh *mesh) override;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,216 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_transform.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
|
||||
#include <pxr/base/gf/matrix4d.h>
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/usd/usdGeom/xform.h>
|
||||
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_quaternion.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.usd"};
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
pxr::UsdGeomXformable USDTransformWriter::create_xformable() const
|
||||
{
|
||||
pxr::UsdGeomXform xform;
|
||||
|
||||
/* If prim exists, cast to #UsdGeomXform
|
||||
* (Solves merge transform and shape issue for animated exports). */
|
||||
pxr::UsdPrim existing_prim = usd_export_context_.stage->GetPrimAtPath(
|
||||
usd_export_context_.usd_path);
|
||||
if (existing_prim.IsValid() && existing_prim.IsA<pxr::UsdGeomXform>()) {
|
||||
xform = pxr::UsdGeomXform(existing_prim);
|
||||
}
|
||||
else {
|
||||
xform = pxr::UsdGeomXform::Define(usd_export_context_.stage, usd_export_context_.usd_path);
|
||||
}
|
||||
|
||||
return pxr::UsdGeomXformable(xform.GetPrim());
|
||||
}
|
||||
|
||||
bool USDTransformWriter::should_apply_root_xform(const HierarchyContext &context) const
|
||||
{
|
||||
if (!(usd_export_context_.export_params.convert_orientation ||
|
||||
usd_export_context_.export_params.convert_scene_units != SceneUnits::Meters))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!usd_export_context_.export_params.root_prim_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.export_parent != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void USDTransformWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
if (context.is_point_proto || context.is_point_instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomXformable xform = create_xformable();
|
||||
|
||||
if (!xform) {
|
||||
CLOG_ERROR(&LOG, "USDTransformWriter: couldn't create xformable");
|
||||
return;
|
||||
}
|
||||
|
||||
float4x4 parent_relative_matrix; /* The object matrix relative to the parent. */
|
||||
|
||||
if (should_apply_root_xform(context)) {
|
||||
float4x4 matrix_world = context.matrix_world;
|
||||
|
||||
if (usd_export_context_.export_params.convert_orientation) {
|
||||
float3x3 mrot;
|
||||
float4x4 mat = float4x4::identity();
|
||||
mat3_from_axis_conversion(IO_AXIS_Y,
|
||||
IO_AXIS_Z,
|
||||
usd_export_context_.export_params.forward_axis,
|
||||
usd_export_context_.export_params.up_axis,
|
||||
mrot.ptr());
|
||||
mat.view<3, 3>() = math::transpose(mrot);
|
||||
matrix_world = mat * context.matrix_world;
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.convert_scene_units != SceneUnits::Meters) {
|
||||
const float3 scale = float3(1.0 / get_meters_per_unit(usd_export_context_.export_params));
|
||||
const float4x4 mat_scale = math::from_scale<float4x4>(scale);
|
||||
matrix_world = mat_scale * matrix_world;
|
||||
}
|
||||
|
||||
parent_relative_matrix = context.parent_matrix_inv_world * matrix_world;
|
||||
}
|
||||
else {
|
||||
parent_relative_matrix = context.parent_matrix_inv_world * context.matrix_world;
|
||||
}
|
||||
|
||||
/* USD Xforms are by default the identity transform; only write if necessary when static. */
|
||||
if (is_animated_ || !math::is_equal(parent_relative_matrix, float4x4::identity(), 0.000000001f))
|
||||
{
|
||||
set_xform_ops(parent_relative_matrix, xform);
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
|
||||
mark_as_instance(context, xform.GetPrim());
|
||||
}
|
||||
|
||||
if (context.object) {
|
||||
auto prim = xform.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &context.object->id);
|
||||
write_id_properties(prim, context.object->id, get_export_time_code());
|
||||
}
|
||||
}
|
||||
|
||||
bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
|
||||
{
|
||||
if (context.duplicator != nullptr) {
|
||||
/* This object is being duplicated, so could be emitted by a particle system and thus
|
||||
* influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the
|
||||
* depsgraph whether this object instance has a time source. */
|
||||
return true;
|
||||
}
|
||||
if (check_has_physics(context)) {
|
||||
return true;
|
||||
}
|
||||
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
|
||||
}
|
||||
|
||||
void USDTransformWriter::set_xform_ops(const float4x4 &parent_relative_matrix,
|
||||
const pxr::UsdGeomXformable &xf)
|
||||
{
|
||||
if (!xf) {
|
||||
return;
|
||||
}
|
||||
|
||||
XformOpMode xfOpMode = usd_export_context_.export_params.xform_op_mode;
|
||||
|
||||
if (xformOps_.is_empty()) {
|
||||
switch (xfOpMode) {
|
||||
case XformOpMode::TRS:
|
||||
xformOps_.append(xf.AddTranslateOp());
|
||||
xformOps_.append(xf.AddRotateXYZOp());
|
||||
xformOps_.append(xf.AddScaleOp());
|
||||
break;
|
||||
case XformOpMode::TOS:
|
||||
xformOps_.append(xf.AddTranslateOp());
|
||||
xformOps_.append(xf.AddOrientOp());
|
||||
xformOps_.append(xf.AddScaleOp());
|
||||
break;
|
||||
case XformOpMode::MAT:
|
||||
xformOps_.append(xf.AddTransformOp());
|
||||
break;
|
||||
default:
|
||||
CLOG_WARN(&LOG, "Unknown XformOp type");
|
||||
xformOps_.append(xf.AddTransformOp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (xformOps_.is_empty()) {
|
||||
/* Shouldn't happen. */
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdTimeCode time_code = get_export_time_code();
|
||||
|
||||
if (xformOps_.size() == 1) {
|
||||
pxr::GfMatrix4d mat_val(parent_relative_matrix.ptr());
|
||||
usd_value_writer_.SetAttribute(xformOps_[0].GetAttr(), mat_val, time_code);
|
||||
}
|
||||
else if (xformOps_.size() == 3) {
|
||||
float3 loc;
|
||||
math::Quaternion rot;
|
||||
float3 scale;
|
||||
|
||||
math::to_loc_rot_scale<true>(parent_relative_matrix, loc, rot, scale);
|
||||
|
||||
if (xfOpMode == XformOpMode::TRS) {
|
||||
pxr::GfVec3d loc_val(loc.x, loc.y, loc.z);
|
||||
usd_value_writer_.SetAttribute(xformOps_[0].GetAttr(), loc_val, time_code);
|
||||
|
||||
const math::EulerXYZ eul = math::to_euler(rot);
|
||||
pxr::GfVec3f rot_val(eul.x().degree(), eul.y().degree(), eul.z().degree());
|
||||
usd_value_writer_.SetAttribute(xformOps_[1].GetAttr(), rot_val, time_code);
|
||||
|
||||
pxr::GfVec3f scale_val(scale.x, scale.y, scale.z);
|
||||
usd_value_writer_.SetAttribute(xformOps_[2].GetAttr(), scale_val, time_code);
|
||||
}
|
||||
else if (xfOpMode == XformOpMode::TOS) {
|
||||
pxr::GfVec3d loc_val(loc.x, loc.y, loc.z);
|
||||
usd_value_writer_.SetAttribute(xformOps_[0].GetAttr(), loc_val, time_code);
|
||||
|
||||
pxr::GfQuatf quat_val(rot.w, rot.x, rot.y, rot.z);
|
||||
usd_value_writer_.SetAttribute(xformOps_[1].GetAttr(), quat_val, time_code);
|
||||
|
||||
pxr::GfVec3f scale_val(scale.x, scale.y, scale.z);
|
||||
usd_value_writer_.SetAttribute(xformOps_[2].GetAttr(), scale_val, time_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2019 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <pxr/usd/usdGeom/xformable.h>
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
class USDTransformWriter : public USDAbstractWriter {
|
||||
private:
|
||||
Vector<pxr::UsdGeomXformOp> xformOps_;
|
||||
|
||||
public:
|
||||
USDTransformWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
void do_write(HierarchyContext &context) override;
|
||||
bool check_is_animated(const HierarchyContext &context) const override;
|
||||
bool should_apply_root_xform(const HierarchyContext &context) const;
|
||||
void set_xform_ops(const float4x4 &parent_relative_matrix, const pxr::UsdGeomXformable &xf);
|
||||
|
||||
/* Subclasses may override this to create prims other than UsdGeomXform. */
|
||||
virtual pxr::UsdGeomXformable create_xformable() const;
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
226
blender-5.2.0/source/blender/io/usd/intern/usd_writer_volume.cc
Normal file
226
blender-5.2.0/source/blender/io/usd/intern/usd_writer_volume.cc
Normal file
@@ -0,0 +1,226 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_writer_volume.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_utils.hh"
|
||||
|
||||
#include <pxr/base/tf/pathUtils.h>
|
||||
#include <pxr/base/vt/value.h>
|
||||
#include <pxr/usd/usdVol/openVDBAsset.h>
|
||||
#include <pxr/usd/usdVol/volume.h>
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_volume_types.h"
|
||||
|
||||
#include "BKE_report.hh"
|
||||
#include "BKE_volume.hh"
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
static bool has_varying_modifiers(const Object *ob)
|
||||
{
|
||||
/* These modifiers may vary the Volume either over time or by deformation/transformation. */
|
||||
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first);
|
||||
while (md) {
|
||||
if (ELEM(md->type,
|
||||
eModifierType_Nodes,
|
||||
eModifierType_VolumeDisplace,
|
||||
eModifierType_MeshToVolume))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
md = md->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
USDVolumeWriter::USDVolumeWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) {}
|
||||
|
||||
bool USDVolumeWriter::check_is_animated(const HierarchyContext &context) const
|
||||
{
|
||||
const Volume *volume = id_cast<Volume *>(context.object->data);
|
||||
return volume->is_sequence || has_varying_modifiers(context.object);
|
||||
}
|
||||
|
||||
void USDVolumeWriter::do_write(HierarchyContext &context)
|
||||
{
|
||||
Volume *volume = id_cast<Volume *>(context.object->data);
|
||||
if (!BKE_volume_load(volume, usd_export_context_.bmain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int num_grids = BKE_volume_num_grids(volume);
|
||||
if (!num_grids) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool has_modifiers = has_varying_modifiers(context.object);
|
||||
auto vdb_file_path = resolve_vdb_file(volume, has_modifiers);
|
||||
if (!vdb_file_path.has_value()) {
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"USD Export: failed to resolve .vdb file for object: %s",
|
||||
volume->id.name + 2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.relative_paths) {
|
||||
if (auto relative_vdb_file_path = construct_vdb_relative_file_path(*vdb_file_path)) {
|
||||
vdb_file_path = relative_vdb_file_path;
|
||||
}
|
||||
else {
|
||||
BKE_reportf(reports(),
|
||||
RPT_WARNING,
|
||||
"USD Export: couldn't construct relative file path for .vdb file, absolute path "
|
||||
"will be used instead");
|
||||
}
|
||||
}
|
||||
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
const pxr::SdfPath &volume_path = usd_export_context_.usd_path;
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
pxr::UsdVolVolume usd_volume = pxr::UsdVolVolume::Define(stage, volume_path);
|
||||
|
||||
for (const int i : IndexRange(num_grids)) {
|
||||
const bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
|
||||
const std::string grid_name = bke::volume_grid::get_name(*grid);
|
||||
const std::string grid_id = make_safe_name(grid_name,
|
||||
usd_export_context_.export_params.allow_unicode);
|
||||
const pxr::SdfPath grid_path = volume_path.AppendPath(pxr::SdfPath(grid_id));
|
||||
pxr::UsdVolOpenVDBAsset usd_grid = pxr::UsdVolOpenVDBAsset::Define(stage, grid_path);
|
||||
|
||||
pxr::TfToken grid_name_token = pxr::TfToken(grid_name);
|
||||
pxr::SdfAssetPath asset_path = pxr::SdfAssetPath(*vdb_file_path);
|
||||
pxr::UsdAttribute attr_field = usd_grid.CreateFieldNameAttr(pxr::VtValue(), true);
|
||||
pxr::UsdAttribute attr_file = usd_grid.CreateFilePathAttr(pxr::VtValue(), true);
|
||||
if (!attr_field.HasValue()) {
|
||||
attr_field.Set(grid_name_token, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
if (!attr_file.HasValue()) {
|
||||
attr_file.Set(asset_path, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
usd_value_writer_.SetAttribute(attr_field, grid_name_token, time);
|
||||
usd_value_writer_.SetAttribute(attr_file, asset_path, time);
|
||||
|
||||
usd_volume.CreateFieldRelationship(pxr::TfToken(grid_id), grid_path);
|
||||
}
|
||||
|
||||
this->author_extent(usd_volume, BKE_volume_min_max(volume), time);
|
||||
|
||||
BKE_volume_unload(volume);
|
||||
}
|
||||
|
||||
std::optional<std::string> USDVolumeWriter::resolve_vdb_file(const Volume *volume,
|
||||
bool has_modifiers) const
|
||||
{
|
||||
std::optional<std::string> vdb_file_path;
|
||||
|
||||
const bool needs_vdb_save = volume->filepath[0] == '\0' || has_modifiers;
|
||||
if (needs_vdb_save) {
|
||||
/* Entering this section means that the Volume object contains OpenVDB data that is not
|
||||
* obtained solely from external `.vdb` files but is generated or modified inside of Blender.
|
||||
* Write this data as a new `.vdb` files. */
|
||||
|
||||
vdb_file_path = construct_vdb_file_path(volume);
|
||||
if (!BKE_volume_save(
|
||||
volume, usd_export_context_.bmain, nullptr, vdb_file_path.value_or("").c_str()))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vdb_file_path.has_value()) {
|
||||
vdb_file_path = BKE_volume_grids_frame_filepath(volume);
|
||||
if (vdb_file_path->empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return vdb_file_path;
|
||||
}
|
||||
|
||||
std::optional<std::string> USDVolumeWriter::construct_vdb_file_path(const Volume *volume) const
|
||||
{
|
||||
const std::string usd_file_path = get_export_file_path();
|
||||
if (usd_file_path.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char usd_directory_path[FILE_MAX];
|
||||
char usd_file_name[FILE_MAXFILE];
|
||||
BLI_path_split_dir_file(usd_file_path.c_str(),
|
||||
usd_directory_path,
|
||||
sizeof(usd_directory_path),
|
||||
usd_file_name,
|
||||
sizeof(usd_file_name));
|
||||
|
||||
if (usd_directory_path[0] == '\0' || usd_file_name[0] == '\0') {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char *vdb_directory_name = "volumes";
|
||||
|
||||
char vdb_directory_path[FILE_MAX];
|
||||
STRNCPY(vdb_directory_path, usd_directory_path);
|
||||
BLI_strncat(vdb_directory_path, vdb_directory_name, sizeof(vdb_directory_path));
|
||||
BLI_dir_create_recursive(vdb_directory_path);
|
||||
|
||||
const Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph);
|
||||
const int max_frame_digits = std::max(2, integer_digits_i(abs(scene->r.efra)));
|
||||
|
||||
char vdb_file_name[FILE_MAXFILE];
|
||||
STRNCPY(vdb_file_name, volume->id.name + 2);
|
||||
const pxr::UsdTimeCode time = get_export_time_code();
|
||||
if (!time.IsDefault()) {
|
||||
const int frame = int(time.GetValue());
|
||||
BLI_path_frame(vdb_file_name, sizeof(vdb_file_name), frame, max_frame_digits);
|
||||
}
|
||||
BLI_strncat(vdb_file_name, ".vdb", sizeof(vdb_file_name));
|
||||
|
||||
char vdb_file_path[FILE_MAX];
|
||||
BLI_path_join(vdb_file_path, sizeof(vdb_file_path), vdb_directory_path, vdb_file_name);
|
||||
|
||||
return vdb_file_path;
|
||||
}
|
||||
|
||||
std::optional<std::string> USDVolumeWriter::construct_vdb_relative_file_path(
|
||||
const std::string &vdb_file_path) const
|
||||
{
|
||||
const std::string usd_file_path = get_export_file_path();
|
||||
if (usd_file_path.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char relative_path[FILE_MAX];
|
||||
STRNCPY(relative_path, vdb_file_path.c_str());
|
||||
BLI_path_rel(relative_path, usd_file_path.c_str());
|
||||
if (!BLI_path_is_rel(relative_path)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Following code was written with an assumption that Blender's relative paths start with
|
||||
* `//` characters as well as have OS dependent slashes. Inside of USD files those relative
|
||||
* paths should start with either `./` or `../` characters and have always forward slashes (`/`)
|
||||
* separating directories. This is the convention used in USD documentation (and it seems
|
||||
* to be used in other DCC packages as well). */
|
||||
std::string relative_path_processed = pxr::TfNormPath(relative_path + 2);
|
||||
if (relative_path_processed[0] != '.') {
|
||||
relative_path_processed.insert(0, "./");
|
||||
}
|
||||
|
||||
return relative_path_processed;
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "usd_writer_abstract.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Volume;
|
||||
|
||||
namespace io::usd {
|
||||
|
||||
/* Writer for writing OpenVDB assets to UsdVolVolume. Volume data is stored in separate `.vdb`
|
||||
* files which are referenced in USD file. */
|
||||
class USDVolumeWriter : public USDAbstractWriter {
|
||||
public:
|
||||
USDVolumeWriter(const USDExporterContext &ctx);
|
||||
|
||||
protected:
|
||||
bool check_is_animated(const HierarchyContext &context) const override;
|
||||
void do_write(HierarchyContext &context) override;
|
||||
|
||||
private:
|
||||
/* Try to ensure that external `.vdb` file is available for USD to be referenced. Blender can
|
||||
* either reference external OpenVDB data or generate such data internally. Latter option will
|
||||
* mean that `resolve_vdb_file` method will try to export volume data to a new `.vdb` file.
|
||||
* If successful, this method returns absolute file path to the resolved `.vdb` file, if not,
|
||||
* returns `std::nullopt`. */
|
||||
std::optional<std::string> resolve_vdb_file(const Volume *volume, bool has_modifiers) const;
|
||||
|
||||
std::optional<std::string> construct_vdb_file_path(const Volume *volume) const;
|
||||
std::optional<std::string> construct_vdb_relative_file_path(
|
||||
const std::string &vdb_file_path) const;
|
||||
};
|
||||
|
||||
} // namespace io::usd
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user