USD IO: Generic Attributes Support
Part 2 of the patch I wrote moving USD over to the new Attributes API for Colors: https://projects.blender.org/blender/blender/pulls/105347 This patch adds support for more types of generic Mesh Attributes. Attribute Types and Domains are converted to their USD counterparts where possible. For example, float Attributes used for modifying shader masks or int Attributes for grouping are now able to be round-tripped. Due to the differences in the two systems some conversions are necessary, but attempts were made to keep data loss to a minimum. If you export to USDA, you'll find the Attributes get prefixed with a "primvars:" namespace; this is expected behavior and identifies the exported Attributes as different from other USD Schema. Not supported: - Edge domain. There doesn't seem to be a proper conversion for this in USD. One exception is for creasing and sharpness, but if they are desired I can add them in a future patch. Co-authored-by: kiki <charles@skeletalstudios.com> Co-authored-by: Charles Wardlaw <cwardlaw@nvidia.com> Co-authored-by: Hans Goudey <h.goudey@me.com> Co-authored-by: Charles Wardlaw <kattkieru@users.noreply.github.com> Co-authored-by: Michael Kowalski <makowalski@nvidia.com> Pull Request: https://projects.blender.org/blender/blender/pulls/109518
This commit is contained in:
committed by
Michael Kowalski
parent
6ec842c43c
commit
cf5666345d
@@ -145,6 +145,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
const bool export_animation = RNA_boolean_get(op->ptr, "export_animation");
|
||||
const bool export_hair = RNA_boolean_get(op->ptr, "export_hair");
|
||||
const bool export_uvmaps = RNA_boolean_get(op->ptr, "export_uvmaps");
|
||||
const bool export_mesh_colors = RNA_boolean_get(op->ptr, "export_mesh_colors");
|
||||
const bool export_normals = RNA_boolean_get(op->ptr, "export_normals");
|
||||
const bool export_materials = RNA_boolean_get(op->ptr, "export_materials");
|
||||
const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing");
|
||||
@@ -164,6 +165,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
|
||||
export_hair,
|
||||
export_uvmaps,
|
||||
export_normals,
|
||||
export_mesh_colors,
|
||||
export_materials,
|
||||
selected_objects_only,
|
||||
visible_objects_only,
|
||||
@@ -309,6 +311,11 @@ void WM_OT_usd_export(wmOperatorType *ot)
|
||||
ot->srna, "export_hair", false, "Hair", "Export hair particle systems as USD curves");
|
||||
RNA_def_boolean(
|
||||
ot->srna, "export_uvmaps", true, "UV Maps", "Include all mesh UV maps in the export");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"export_mesh_colors",
|
||||
true,
|
||||
"Color Attributes",
|
||||
"Include mesh color attributes in the export");
|
||||
RNA_def_boolean(ot->srna,
|
||||
"export_normals",
|
||||
true,
|
||||
@@ -402,6 +409,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
||||
|
||||
const bool read_mesh_uvs = RNA_boolean_get(op->ptr, "read_mesh_uvs");
|
||||
const bool read_mesh_colors = RNA_boolean_get(op->ptr, "read_mesh_colors");
|
||||
const bool read_mesh_attributes = RNA_boolean_get(op->ptr, "read_mesh_attributes");
|
||||
|
||||
char mesh_read_flag = MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY;
|
||||
if (read_mesh_uvs) {
|
||||
@@ -410,6 +418,9 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
|
||||
if (read_mesh_colors) {
|
||||
mesh_read_flag |= MOD_MESHSEQ_READ_COLOR;
|
||||
}
|
||||
if (read_mesh_attributes) {
|
||||
mesh_read_flag |= MOD_MESHSEQ_READ_ATTRIBUTES;
|
||||
}
|
||||
|
||||
const bool import_cameras = RNA_boolean_get(op->ptr, "import_cameras");
|
||||
const bool import_curves = RNA_boolean_get(op->ptr, "import_curves");
|
||||
@@ -534,6 +545,7 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op)
|
||||
col = uiLayoutColumnWithHeading(box, true, IFACE_("Mesh Data"));
|
||||
uiItemR(col, ptr, "read_mesh_uvs", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "read_mesh_colors", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "read_mesh_attributes", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
col = uiLayoutColumnWithHeading(box, true, IFACE_("Include"));
|
||||
uiItemR(col, ptr, "import_subdiv", UI_ITEM_NONE, IFACE_("Subdivision"), ICON_NONE);
|
||||
uiItemR(col, ptr, "import_instance_proxies", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
@@ -653,6 +665,12 @@ void WM_OT_usd_import(wmOperatorType *ot)
|
||||
RNA_def_boolean(
|
||||
ot->srna, "read_mesh_colors", true, "Color Attributes", "Read mesh color attributes");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"read_mesh_attributes",
|
||||
true,
|
||||
"Mesh Attributes",
|
||||
"Read USD Primvars as mesh attributes");
|
||||
|
||||
RNA_def_string(ot->srna,
|
||||
"prim_path_mask",
|
||||
nullptr,
|
||||
|
||||
@@ -116,6 +116,7 @@ set(SRC
|
||||
|
||||
intern/usd_asset_utils.h
|
||||
intern/usd_exporter_context.h
|
||||
intern/usd_hash_types.h
|
||||
intern/usd_hierarchy_iterator.h
|
||||
intern/usd_hook.h
|
||||
intern/usd_writer_abstract.h
|
||||
|
||||
26
source/blender/io/usd/intern/usd_hash_types.h
Normal file
26
source/blender/io/usd/intern/usd_hash_types.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
|
||||
#include <pxr/base/tf/token.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();
|
||||
}
|
||||
};
|
||||
} // namespace blender
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "usd_hash_types.h"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
@@ -146,31 +148,6 @@ static void assign_materials(Main *bmain,
|
||||
|
||||
} // namespace utils
|
||||
|
||||
static void *add_customdata_cb(Mesh *mesh, const char *name, const int data_type)
|
||||
{
|
||||
eCustomDataType cd_data_type = static_cast<eCustomDataType>(data_type);
|
||||
void *cd_ptr;
|
||||
CustomData *loopdata;
|
||||
int numloops;
|
||||
|
||||
/* unsupported custom data type -- don't do anything. */
|
||||
if (!ELEM(cd_data_type, CD_PROP_FLOAT2, CD_PROP_BYTE_COLOR)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
loopdata = &mesh->loop_data;
|
||||
cd_ptr = CustomData_get_layer_named_for_write(loopdata, cd_data_type, name, mesh->totloop);
|
||||
if (cd_ptr != nullptr) {
|
||||
/* layer already exists, so just return it. */
|
||||
return cd_ptr;
|
||||
}
|
||||
|
||||
/* Create a new layer. */
|
||||
numloops = mesh->totloop;
|
||||
cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_SET_DEFAULT, numloops, name);
|
||||
return cd_ptr;
|
||||
}
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
|
||||
@@ -185,6 +162,70 @@ USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
|
||||
{
|
||||
}
|
||||
|
||||
static std::optional<eCustomDataType> convert_usd_type_to_blender(
|
||||
const pxr::SdfValueTypeName usd_type)
|
||||
{
|
||||
static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
|
||||
blender::Map<pxr::SdfValueTypeName, eCustomDataType> map;
|
||||
map.add_new(pxr::SdfValueTypeNames->FloatArray, CD_PROP_FLOAT);
|
||||
map.add_new(pxr::SdfValueTypeNames->Double, CD_PROP_FLOAT);
|
||||
map.add_new(pxr::SdfValueTypeNames->IntArray, CD_PROP_INT32);
|
||||
map.add_new(pxr::SdfValueTypeNames->Float2Array, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2dArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2fArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord2hArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3dArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3fArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->TexCoord3hArray, CD_PROP_FLOAT2);
|
||||
map.add_new(pxr::SdfValueTypeNames->Float3Array, CD_PROP_FLOAT3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3fArray, CD_PROP_FLOAT3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3hArray, CD_PROP_FLOAT3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Vector3dArray, CD_PROP_FLOAT3);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3fArray, CD_PROP_COLOR);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3hArray, CD_PROP_COLOR);
|
||||
map.add_new(pxr::SdfValueTypeNames->Color3dArray, CD_PROP_COLOR);
|
||||
map.add_new(pxr::SdfValueTypeNames->StringArray, CD_PROP_STRING);
|
||||
map.add_new(pxr::SdfValueTypeNames->BoolArray, CD_PROP_BOOL);
|
||||
map.add_new(pxr::SdfValueTypeNames->QuatfArray, CD_PROP_QUATERNION);
|
||||
return map;
|
||||
}();
|
||||
|
||||
const eCustomDataType *value = type_map.lookup_ptr(usd_type);
|
||||
if (value == nullptr) {
|
||||
WM_reportf(RPT_WARNING, "Unsupported type for mesh data");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
static const std::optional<eAttrDomain> convert_usd_varying_to_blender(
|
||||
const pxr::TfToken usd_domain)
|
||||
{
|
||||
static const blender::Map<pxr::TfToken, eAttrDomain> domain_map = []() {
|
||||
blender::Map<pxr::TfToken, eAttrDomain> map;
|
||||
map.add_new(pxr::UsdGeomTokens->faceVarying, ATTR_DOMAIN_CORNER);
|
||||
map.add_new(pxr::UsdGeomTokens->vertex, ATTR_DOMAIN_POINT);
|
||||
map.add_new(pxr::UsdGeomTokens->varying, ATTR_DOMAIN_POINT);
|
||||
map.add_new(pxr::UsdGeomTokens->face, ATTR_DOMAIN_FACE);
|
||||
/* As there's no "constant" type in Blender, for now we're
|
||||
* translating into a point Attribute. */
|
||||
map.add_new(pxr::UsdGeomTokens->constant, ATTR_DOMAIN_POINT);
|
||||
map.add_new(pxr::UsdGeomTokens->uniform, ATTR_DOMAIN_FACE);
|
||||
/* Notice: Edge types are not supported! */
|
||||
return map;
|
||||
}();
|
||||
|
||||
const eAttrDomain *value = domain_map.lookup_ptr(usd_domain);
|
||||
|
||||
if (value == nullptr) {
|
||||
WM_reportf(RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
void USDMeshReader::create_object(Main *bmain, const double /* motionSampleTime */)
|
||||
{
|
||||
Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
|
||||
@@ -296,203 +337,47 @@ void USDMeshReader::read_mpolys(Mesh *mesh)
|
||||
BKE_mesh_calc_edges(mesh, false, false);
|
||||
}
|
||||
|
||||
void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bool load_uvs)
|
||||
template<typename T>
|
||||
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime)
|
||||
{
|
||||
uint loop_index = 0;
|
||||
uint rev_loop_index = 0;
|
||||
uint uv_index = 0;
|
||||
pxr::VtArray<T> array;
|
||||
|
||||
const CustomData *ldata = &mesh->loop_data;
|
||||
pxr::VtValue primvar_val;
|
||||
|
||||
struct UVSample {
|
||||
pxr::VtVec2fArray uvs;
|
||||
pxr::TfToken interpolation;
|
||||
};
|
||||
|
||||
std::vector<UVSample> uv_primvars(ldata->totlayer);
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI primvarsAPI(mesh_prim_);
|
||||
|
||||
if (has_uvs_) {
|
||||
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
|
||||
const CustomDataLayer *layer = &ldata->layers[layer_idx];
|
||||
std::string layer_name = std::string(layer->name);
|
||||
if (layer->type != CD_PROP_FLOAT2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::TfToken uv_token;
|
||||
|
||||
/* If first time seeing uv token, store in map of `<layer->uid, TfToken>`. */
|
||||
if (uv_token_map_.find(layer_name) == uv_token_map_.end()) {
|
||||
uv_token = pxr::TfToken(layer_name);
|
||||
uv_token_map_.insert(std::make_pair(layer_name, uv_token));
|
||||
}
|
||||
else {
|
||||
uv_token = uv_token_map_.at(layer_name);
|
||||
}
|
||||
|
||||
/* Early out if no token found, this should never happen */
|
||||
if (uv_token.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
/* Early out if not first load and UVs aren't animated. */
|
||||
if (!load_uvs && primvar_varying_map_.find(uv_token) != primvar_varying_map_.end() &&
|
||||
!primvar_varying_map_.at(uv_token))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Early out if mesh doesn't have primvar. */
|
||||
if (!primvarsAPI.HasPrimvar(uv_token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pxr::UsdGeomPrimvar uv_primvar = primvarsAPI.GetPrimvar(uv_token)) {
|
||||
uv_primvar.ComputeFlattened(&uv_primvars[layer_idx].uvs, motionSampleTime);
|
||||
uv_primvars[layer_idx].interpolation = uv_primvar.GetInterpolation();
|
||||
}
|
||||
}
|
||||
if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) {
|
||||
WM_reportf(
|
||||
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText());
|
||||
return array;
|
||||
}
|
||||
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
for (int i = 0; i < face_counts_.size(); i++) {
|
||||
const int face_size = face_counts_[i];
|
||||
|
||||
rev_loop_index = loop_index + (face_size - 1);
|
||||
|
||||
for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) {
|
||||
|
||||
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
|
||||
const CustomDataLayer *layer = &ldata->layers[layer_idx];
|
||||
if (layer->type != CD_PROP_FLOAT2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Early out if mismatched layer sizes. */
|
||||
if (layer_idx > uv_primvars.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Early out if no uvs loaded. */
|
||||
if (uv_primvars[layer_idx].uvs.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const UVSample &sample = uv_primvars[layer_idx];
|
||||
|
||||
if (!ELEM(
|
||||
sample.interpolation, pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->vertex))
|
||||
{
|
||||
std::cerr << "WARNING: unexpected interpolation type " << sample.interpolation
|
||||
<< " for uv " << layer->name << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* For Vertex interpolation, use the vertex index. */
|
||||
int usd_uv_index = sample.interpolation == pxr::UsdGeomTokens->vertex ?
|
||||
corner_verts[loop_index] :
|
||||
loop_index;
|
||||
|
||||
if (usd_uv_index >= sample.uvs.size()) {
|
||||
std::cerr << "WARNING: out of bounds uv index " << usd_uv_index << " for uv "
|
||||
<< layer->name << " of size " << sample.uvs.size() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
float2 *mloopuv = static_cast<float2 *>(layer->data);
|
||||
if (is_left_handed_) {
|
||||
uv_index = rev_loop_index;
|
||||
}
|
||||
else {
|
||||
uv_index = loop_index;
|
||||
}
|
||||
mloopuv[uv_index][0] = sample.uvs[usd_uv_index][0];
|
||||
mloopuv[uv_index][1] = sample.uvs[usd_uv_index][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDMeshReader::read_color_data_all_primvars(Mesh *mesh, const double motionSampleTime)
|
||||
{
|
||||
if (!(mesh && mesh_prim_ && mesh->totloop > 0)) {
|
||||
return;
|
||||
if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: can't cast attribute '%s' to array",
|
||||
primvar.GetName().GetText());
|
||||
return array;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
|
||||
pxr::TfToken active_color_name;
|
||||
|
||||
/* Convert color primvars to custom layer data. */
|
||||
for (pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
if (!pv.HasValue()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::SdfValueTypeName type = pv.GetTypeName();
|
||||
|
||||
if (!ELEM(type,
|
||||
pxr::SdfValueTypeNames->Color3hArray,
|
||||
pxr::SdfValueTypeNames->Color3fArray,
|
||||
pxr::SdfValueTypeNames->Color3dArray))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::TfToken name = pv.GetPrimvarName();
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Skip if we read this primvar before and it isn't animated. */
|
||||
const std::map<const pxr::TfToken, bool>::const_iterator is_animated_iter =
|
||||
primvar_varying_map_.find(name);
|
||||
if (is_animated_iter != primvar_varying_map_.end() && !is_animated_iter->second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
read_color_data_primvar(mesh, pv, motionSampleTime);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
array = primvar_val.Cast<pxr::VtArray<T>>().template UncheckedGet<pxr::VtArray<T>>();
|
||||
return array;
|
||||
}
|
||||
|
||||
void USDMeshReader::read_color_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &color_primvar,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime)
|
||||
{
|
||||
if (!(mesh && color_primvar && color_primvar.HasValue())) {
|
||||
if (!(mesh && primvar && primvar.HasValue())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (primvar_varying_map_.find(color_primvar.GetPrimvarName()) == primvar_varying_map_.end()) {
|
||||
bool might_be_time_varying = color_primvar.ValueMightBeTimeVarying();
|
||||
primvar_varying_map_.insert(
|
||||
std::make_pair(color_primvar.GetPrimvarName(), might_be_time_varying));
|
||||
if (might_be_time_varying) {
|
||||
is_time_varying_ = true;
|
||||
}
|
||||
}
|
||||
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(primvar,
|
||||
motionSampleTime);
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> usd_colors;
|
||||
|
||||
if (!color_primvar.ComputeFlattened(&usd_colors, motionSampleTime)) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: couldn't compute values for color attribute '%s'",
|
||||
color_primvar.GetName().GetText());
|
||||
if (usd_colors.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::TfToken interp = color_primvar.GetInterpolation();
|
||||
pxr::TfToken interp = primvar.GetInterpolation();
|
||||
|
||||
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->totloop) ||
|
||||
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->totloop) ||
|
||||
@@ -502,11 +387,11 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
|
||||
{
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: color attribute value '%s' count inconsistent with interpolation type",
|
||||
color_primvar.GetName().GetText());
|
||||
primvar.GetName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
const StringRef color_primvar_name(color_primvar.GetBaseName().GetString());
|
||||
const StringRef primvar_name(primvar.GetBaseName().GetString());
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
|
||||
eAttrDomain color_domain = ATTR_DOMAIN_POINT;
|
||||
@@ -520,16 +405,16 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<ColorGeometry4f> color_data;
|
||||
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(color_primvar_name,
|
||||
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name,
|
||||
color_domain);
|
||||
if (!color_data) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: couldn't add color attribute '%s'",
|
||||
color_primvar.GetBaseName().GetText());
|
||||
primvar.GetBaseName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
if (ELEM(interp, pxr::UsdGeomTokens->constant, pxr::UsdGeomTokens->uniform)) {
|
||||
if (ELEM(interp, pxr::UsdGeomTokens->constant)) {
|
||||
/* For situations where there's only a single item, flood fill the object. */
|
||||
color_data.span.fill(
|
||||
ColorGeometry4f(usd_colors[0][0], usd_colors[0][1], usd_colors[0][2], 1.0f));
|
||||
@@ -605,6 +490,206 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh,
|
||||
color_data.finish();
|
||||
}
|
||||
|
||||
void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime)
|
||||
{
|
||||
const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString());
|
||||
|
||||
pxr::VtArray<pxr::GfVec2f> usd_uvs = get_prim_attribute_array<pxr::GfVec2f>(primvar,
|
||||
motionSampleTime);
|
||||
|
||||
if (usd_uvs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::TfToken varying_type = primvar.GetInterpolation();
|
||||
BLI_assert(ELEM(varying_type,
|
||||
pxr::UsdGeomTokens->vertex,
|
||||
pxr::UsdGeomTokens->faceVarying,
|
||||
pxr::UsdGeomTokens->varying));
|
||||
|
||||
if ((varying_type == pxr::UsdGeomTokens->faceVarying && usd_uvs.size() != mesh->totloop) ||
|
||||
(varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) ||
|
||||
(varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop))
|
||||
{
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: UV attribute value '%s' count inconsistent with interpolation type",
|
||||
primvar.GetName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
bke::SpanAttributeWriter<float2> uv_data = attributes.lookup_or_add_for_write_only_span<float2>(
|
||||
primvar_name, ATTR_DOMAIN_CORNER);
|
||||
|
||||
if (!uv_data) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Import: couldn't add UV attribute '%s'",
|
||||
primvar.GetBaseName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
if (varying_type == pxr::UsdGeomTokens->faceVarying ||
|
||||
varying_type == pxr::UsdGeomTokens->varying) {
|
||||
if (is_left_handed_) {
|
||||
/* Reverse the index order. */
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
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);
|
||||
uv_data.span[face.start() + j] = float2(usd_uvs[rev_index][0], usd_uvs[rev_index][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < uv_data.span.size(); ++i) {
|
||||
uv_data.span[i] = float2(usd_uvs[i][0], usd_uvs[i][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Handle vertex interpolation. */
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
BLI_assert(mesh->totvert == usd_uvs.size());
|
||||
for (int i = 0; i < uv_data.span.size(); ++i) {
|
||||
/* Get the vertex index for this corner. */
|
||||
int vi = corner_verts[i];
|
||||
uv_data.span[i] = float2(usd_uvs[vi][0], usd_uvs[vi][1]);
|
||||
}
|
||||
}
|
||||
|
||||
uv_data.finish();
|
||||
}
|
||||
|
||||
template<typename USDT, typename BlenderT> inline BlenderT convert_value(const USDT &value)
|
||||
{
|
||||
/* Default is no conversion. */
|
||||
return value;
|
||||
}
|
||||
|
||||
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<typename USDT, typename BlenderT>
|
||||
void USDMeshReader::copy_prim_array_to_blender_attribute(const Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime,
|
||||
MutableSpan<BlenderT> attribute)
|
||||
{
|
||||
const pxr::TfToken interp = primvar.GetInterpolation();
|
||||
pxr::VtArray<USDT> primvar_array = get_prim_attribute_array<USDT>(primvar, motionSampleTime);
|
||||
if (primvar_array.empty()) {
|
||||
WM_reportf(
|
||||
RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText());
|
||||
return;
|
||||
}
|
||||
|
||||
if (interp == pxr::UsdGeomTokens->constant) {
|
||||
/* For situations where there's only a single item, flood fill the object. */
|
||||
attribute.fill(convert_value<USDT, BlenderT>(primvar_array[0]));
|
||||
}
|
||||
else if (interp == pxr::UsdGeomTokens->faceVarying) {
|
||||
if (is_left_handed_) {
|
||||
/* Reverse the index order. */
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
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] = convert_value<USDT, BlenderT>(primvar_array[rev_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int64_t i : attribute.index_range()) {
|
||||
attribute[i] = convert_value<USDT, BlenderT>(primvar_array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
/* Assume direct one-to-one mapping. */
|
||||
if (primvar_array.size() == attribute.size()) {
|
||||
if constexpr (std::is_same_v<USDT, BlenderT>) {
|
||||
const Span<USDT> src(primvar_array.data(), primvar_array.size());
|
||||
attribute.copy_from(src);
|
||||
}
|
||||
else {
|
||||
for (const int64_t i : attribute.index_range()) {
|
||||
attribute[i] = convert_value<USDT, BlenderT>(primvar_array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDMeshReader::read_generic_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime)
|
||||
{
|
||||
const pxr::SdfValueTypeName sdf_type = primvar.GetTypeName();
|
||||
const pxr::TfToken varying_type = primvar.GetInterpolation();
|
||||
const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName());
|
||||
|
||||
const std::optional<eAttrDomain> domain = convert_usd_varying_to_blender(varying_type);
|
||||
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type);
|
||||
|
||||
if (!domain.has_value() || !type.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span(
|
||||
name.GetText(), *domain, *type);
|
||||
switch (*type) {
|
||||
case CD_PROP_FLOAT:
|
||||
copy_prim_array_to_blender_attribute<float>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<float>());
|
||||
break;
|
||||
case CD_PROP_INT32:
|
||||
copy_prim_array_to_blender_attribute<int32_t>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<int>());
|
||||
break;
|
||||
case CD_PROP_FLOAT2:
|
||||
copy_prim_array_to_blender_attribute<pxr::GfVec2f>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<float2>());
|
||||
break;
|
||||
case CD_PROP_FLOAT3:
|
||||
copy_prim_array_to_blender_attribute<pxr::GfVec3f>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<float3>());
|
||||
break;
|
||||
case CD_PROP_COLOR:
|
||||
copy_prim_array_to_blender_attribute<pxr::GfVec3f>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<ColorGeometry4f>());
|
||||
break;
|
||||
case CD_PROP_BOOL:
|
||||
copy_prim_array_to_blender_attribute<bool>(
|
||||
mesh, primvar, motionSampleTime, attribute.span.typed<bool>());
|
||||
break;
|
||||
default:
|
||||
WM_reportf(RPT_ERROR,
|
||||
"Generic primvar %s: invalid type %s",
|
||||
primvar.GetName().GetText(),
|
||||
sdf_type.GetAsToken().GetText());
|
||||
break;
|
||||
}
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTime)
|
||||
{
|
||||
pxr::VtIntArray corner_indices;
|
||||
@@ -682,7 +767,7 @@ void USDMeshReader::process_normals_face_varying(Mesh *mesh)
|
||||
const OffsetIndices faces = mesh->faces();
|
||||
for (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
for (int j = 0; j < face.size(); j++) {
|
||||
for (int j : face.index_range()) {
|
||||
int blender_index = face.start() + j;
|
||||
|
||||
int usd_index = face.start();
|
||||
@@ -769,23 +854,114 @@ void USDMeshReader::read_mesh_sample(ImportSettings *settings,
|
||||
process_normals_vertex_varying(mesh);
|
||||
}
|
||||
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
|
||||
read_uvs(mesh, motionSampleTime, new_mesh);
|
||||
}
|
||||
|
||||
/* Custom Data layers. */
|
||||
read_custom_data(settings, mesh, motionSampleTime);
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) ||
|
||||
(settings->read_flag & MOD_MESHSEQ_READ_COLOR) ||
|
||||
(settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES))
|
||||
{
|
||||
read_custom_data(settings, mesh, motionSampleTime, new_mesh);
|
||||
}
|
||||
}
|
||||
|
||||
void USDMeshReader::read_custom_data(const ImportSettings *settings,
|
||||
Mesh *mesh,
|
||||
const double motionSampleTime)
|
||||
const double motionSampleTime,
|
||||
const bool new_mesh)
|
||||
{
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
|
||||
read_color_data_all_primvars(mesh, motionSampleTime);
|
||||
if (!(mesh && mesh_prim_ && mesh->totloop > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Generic readers for custom data layers not listed above. */
|
||||
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
|
||||
|
||||
pxr::TfToken active_color_name;
|
||||
pxr::TfToken active_uv_set_name;
|
||||
|
||||
/* Convert primvars to custom layer data. */
|
||||
for (pxr::UsdGeomPrimvar &pv : primvars) {
|
||||
if (!pv.HasValue()) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"Skipping primvar %s, mesh %s -- no value",
|
||||
pv.GetName().GetText(),
|
||||
&mesh->id.name[2]);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxr::SdfValueTypeName type = pv.GetTypeName();
|
||||
const pxr::TfToken varying_type = pv.GetInterpolation();
|
||||
const pxr::TfToken name = pv.StripPrimvarsName(pv.GetPrimvarName());
|
||||
|
||||
/* To avoid unnecessarily reloading static primvars during animation,
|
||||
* early out if not first load and this primvar isn't animated. */
|
||||
if (!new_mesh && primvar_varying_map_.find(name) != primvar_varying_map_.end() &&
|
||||
!primvar_varying_map_.at(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Read Color primvars. */
|
||||
if (convert_usd_type_to_blender(type) == CD_PROP_COLOR) {
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
|
||||
/* 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_color_data_primvar(mesh, pv, motionSampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read UV primvars. */
|
||||
else if (ELEM(varying_type,
|
||||
pxr::UsdGeomTokens->vertex,
|
||||
pxr::UsdGeomTokens->faceVarying,
|
||||
pxr::UsdGeomTokens->varying) &&
|
||||
convert_usd_type_to_blender(type) == CD_PROP_FLOAT2)
|
||||
{
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
|
||||
/* Set the active uv set name to 'st', if a uv set primvar
|
||||
* with this name exists. Otherwise, use the name of the first
|
||||
* uv set primvar we find for the active uv set. */
|
||||
if (active_uv_set_name.IsEmpty() || name == usdtokens::st) {
|
||||
active_uv_set_name = name;
|
||||
}
|
||||
read_uv_data_primvar(mesh, pv, motionSampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read all other primvars. */
|
||||
else {
|
||||
if ((settings->read_flag & MOD_MESHSEQ_READ_ATTRIBUTES) != 0) {
|
||||
read_generic_data_primvar(mesh, pv, motionSampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
/* Record whether the primvar attribute might be time varying. */
|
||||
if (primvar_varying_map_.find(name) == primvar_varying_map_.end()) {
|
||||
bool might_be_time_varying = pv.ValueMightBeTimeVarying();
|
||||
primvar_varying_map_.insert(std::make_pair(name, might_be_time_varying));
|
||||
if (might_be_time_varying) {
|
||||
is_time_varying_ = true;
|
||||
}
|
||||
}
|
||||
} /* End primvar attribute loop. */
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
if (!active_uv_set_name.IsEmpty()) {
|
||||
int layer_index = CustomData_get_named_layer_index(
|
||||
&mesh->loop_data, CD_PROP_FLOAT2, active_uv_set_name.GetText());
|
||||
if (layer_index > -1) {
|
||||
CustomData_set_layer_active_index(&mesh->loop_data, CD_PROP_FLOAT2, layer_index);
|
||||
CustomData_set_layer_render_index(&mesh->loop_data, CD_PROP_FLOAT2, layer_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
|
||||
@@ -888,58 +1064,6 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
|
||||
is_left_handed_ = true;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI primvarsAPI(mesh_prim_);
|
||||
|
||||
std::vector<pxr::TfToken> uv_tokens;
|
||||
|
||||
/* Currently we only handle UV primvars. */
|
||||
if (params.read_flags & MOD_MESHSEQ_READ_UV) {
|
||||
|
||||
std::vector<pxr::UsdGeomPrimvar> primvars = primvarsAPI.GetPrimvars();
|
||||
|
||||
for (pxr::UsdGeomPrimvar p : primvars) {
|
||||
|
||||
pxr::TfToken name = p.GetPrimvarName();
|
||||
pxr::SdfValueTypeName type = p.GetTypeName();
|
||||
|
||||
bool is_uv = false;
|
||||
|
||||
/* Assume all UVs are stored in one of these primvar types */
|
||||
if (ELEM(type,
|
||||
pxr::SdfValueTypeNames->TexCoord2hArray,
|
||||
pxr::SdfValueTypeNames->TexCoord2fArray,
|
||||
pxr::SdfValueTypeNames->TexCoord2dArray))
|
||||
{
|
||||
is_uv = true;
|
||||
}
|
||||
/* In some cases, the st primvar is stored as float2 values. */
|
||||
else if (name == usdtokens::st && type == pxr::SdfValueTypeNames->Float2Array) {
|
||||
is_uv = true;
|
||||
}
|
||||
|
||||
if (is_uv) {
|
||||
|
||||
pxr::TfToken interp = p.GetInterpolation();
|
||||
|
||||
if (!ELEM(interp, pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->vertex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uv_tokens.push_back(p.GetBaseName());
|
||||
has_uvs_ = true;
|
||||
|
||||
/* Record whether the UVs might be time varying. */
|
||||
if (primvar_varying_map_.find(name) == primvar_varying_map_.end()) {
|
||||
bool might_be_time_varying = p.ValueMightBeTimeVarying();
|
||||
primvar_varying_map_.insert(std::make_pair(name, might_be_time_varying));
|
||||
if (might_be_time_varying) {
|
||||
is_time_varying_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mesh *active_mesh = existing_mesh;
|
||||
bool new_mesh = false;
|
||||
|
||||
@@ -953,10 +1077,6 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
|
||||
new_mesh = true;
|
||||
active_mesh = BKE_mesh_new_nomain_from_template(
|
||||
existing_mesh, positions_.size(), 0, face_counts_.size(), face_indices_.size());
|
||||
|
||||
for (pxr::TfToken token : uv_tokens) {
|
||||
add_customdata_cb(active_mesh, token.GetText(), CD_PROP_FLOAT2);
|
||||
}
|
||||
}
|
||||
|
||||
read_mesh_sample(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Modifications Copyright 2021 Tangent Animation and. NVIDIA Corporation. All rights reserved. */
|
||||
#pragma once
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "usd.h"
|
||||
@@ -67,7 +68,6 @@ class USDMeshReader : public USDGeomReader {
|
||||
std::map<pxr::SdfPath, int> *r_mat_map);
|
||||
|
||||
void read_mpolys(Mesh *mesh);
|
||||
void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false);
|
||||
void read_vertex_creases(Mesh *mesh, double motionSampleTime);
|
||||
|
||||
void read_mesh_sample(ImportSettings *settings,
|
||||
@@ -75,12 +75,26 @@ class USDMeshReader : public USDGeomReader {
|
||||
double motionSampleTime,
|
||||
bool new_mesh);
|
||||
|
||||
void read_custom_data(const ImportSettings *settings, Mesh *mesh, double motionSampleTime);
|
||||
void read_custom_data(const ImportSettings *settings,
|
||||
Mesh *mesh,
|
||||
double motionSampleTime,
|
||||
bool new_mesh);
|
||||
|
||||
void read_color_data_all_primvars(Mesh *mesh, const double motionSampleTime);
|
||||
void read_color_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &color_primvar,
|
||||
const double motionSampleTime);
|
||||
void read_uv_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime);
|
||||
void read_generic_data_primvar(Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime);
|
||||
|
||||
template<typename USDT, typename BlenderT>
|
||||
void copy_prim_array_to_blender_attribute(const Mesh *mesh,
|
||||
const pxr::UsdGeomPrimvar &primvar,
|
||||
const double motionSampleTime,
|
||||
MutableSpan<BlenderT> attribute);
|
||||
};
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
@@ -38,6 +39,8 @@
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
const pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
|
||||
|
||||
USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||
{
|
||||
}
|
||||
@@ -79,19 +82,236 @@ void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
|
||||
char *active_set_name = nullptr;
|
||||
const int active_uv_set_index = CustomData_get_render_layer_index(&mesh->loop_data,
|
||||
CD_PROP_FLOAT2);
|
||||
if (active_uv_set_index != -1) {
|
||||
active_set_name = mesh->loop_data.layers[active_uv_set_index].name;
|
||||
}
|
||||
|
||||
attributes.for_all(
|
||||
[&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) {
|
||||
/* Color data. */
|
||||
if (ELEM(meta_data.domain, ATTR_DOMAIN_CORNER, ATTR_DOMAIN_POINT) &&
|
||||
ELEM(meta_data.data_type, CD_PROP_BYTE_COLOR, CD_PROP_COLOR))
|
||||
/* Skipping "internal" Blender properties. Skipping
|
||||
* material_index as it's dealt with elsewhere. Skipping
|
||||
* edge domain because USD doesn't have a good
|
||||
* conversion for them. */
|
||||
if (attribute_id.name()[0] == '.' || attribute_id.is_anonymous() ||
|
||||
meta_data.domain == ATTR_DOMAIN_EDGE ||
|
||||
ELEM(attribute_id.name(), "position", "material_index"))
|
||||
{
|
||||
write_color_data(mesh, usd_mesh, attribute_id, meta_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* UV Data. */
|
||||
if (meta_data.domain == ATTR_DOMAIN_CORNER && meta_data.data_type == CD_PROP_FLOAT2) {
|
||||
if (usd_export_context_.export_params.export_uvmaps) {
|
||||
write_uv_data(mesh, usd_mesh, attribute_id, active_set_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Color data. */
|
||||
else if (ELEM(meta_data.domain, ATTR_DOMAIN_CORNER, ATTR_DOMAIN_POINT) &&
|
||||
ELEM(meta_data.data_type, CD_PROP_BYTE_COLOR, CD_PROP_COLOR))
|
||||
{
|
||||
if (usd_export_context_.export_params.export_mesh_colors) {
|
||||
write_color_data(mesh, usd_mesh, attribute_id, meta_data);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
write_generic_data(mesh, usd_mesh, attribute_id, meta_data);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static std::optional<pxr::SdfValueTypeName> convert_blender_type_to_usd(
|
||||
const eCustomDataType blender_type)
|
||||
{
|
||||
switch (blender_type) {
|
||||
case CD_PROP_FLOAT:
|
||||
return pxr::SdfValueTypeNames->FloatArray;
|
||||
case CD_PROP_INT8:
|
||||
case CD_PROP_INT32:
|
||||
return pxr::SdfValueTypeNames->IntArray;
|
||||
case CD_PROP_FLOAT2:
|
||||
return pxr::SdfValueTypeNames->Float2Array;
|
||||
case CD_PROP_FLOAT3:
|
||||
return pxr::SdfValueTypeNames->Float3Array;
|
||||
case CD_PROP_STRING:
|
||||
return pxr::SdfValueTypeNames->StringArray;
|
||||
case CD_PROP_BOOL:
|
||||
return pxr::SdfValueTypeNames->BoolArray;
|
||||
case CD_PROP_QUATERNION:
|
||||
return pxr::SdfValueTypeNames->QuatfArray;
|
||||
default:
|
||||
WM_reportf(RPT_WARNING, "Unsupported type for mesh data.");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static const std::optional<pxr::TfToken> convert_blender_domain_to_usd(
|
||||
const eAttrDomain blender_domain)
|
||||
{
|
||||
switch (blender_domain) {
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
return pxr::UsdGeomTokens->faceVarying;
|
||||
case ATTR_DOMAIN_POINT:
|
||||
return pxr::UsdGeomTokens->vertex;
|
||||
case ATTR_DOMAIN_FACE:
|
||||
return pxr::UsdGeomTokens->uniform;
|
||||
|
||||
/* Notice: Edge types are not supported in USD! */
|
||||
default:
|
||||
WM_reportf(RPT_WARNING, "Unsupported type for mesh data.");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BlenderT, typename USDT> inline USDT convert_value(const BlenderT &value);
|
||||
|
||||
template<> inline int32_t convert_value(const int8_t &value)
|
||||
{
|
||||
return int32_t(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::GfQuatf convert_value(const math::Quaternion &value)
|
||||
{
|
||||
return pxr::GfQuatf(value.x, value.y, value.z, value.w);
|
||||
}
|
||||
|
||||
template<typename BlenderT, typename USDT>
|
||||
void USDGenericMeshWriter::copy_blender_buffer_to_prim(const Span<BlenderT> buffer,
|
||||
const pxr::UsdTimeCode timecode,
|
||||
pxr::UsdGeomPrimvar attribute_pv)
|
||||
{
|
||||
pxr::VtArray<USDT> data;
|
||||
if constexpr (std::is_same_v<BlenderT, USDT>) {
|
||||
data.assign(buffer.begin(), buffer.end());
|
||||
}
|
||||
else {
|
||||
data.resize(buffer.size());
|
||||
for (const int64_t i : buffer.index_range()) {
|
||||
data[i] = convert_value<BlenderT, USDT>(buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!attribute_pv.HasValue() && timecode != pxr::UsdTimeCode::Default()) {
|
||||
attribute_pv.Set(data, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
else {
|
||||
attribute_pv.Set(data, timecode);
|
||||
}
|
||||
|
||||
const pxr::UsdAttribute &prim_attr = attribute_pv.GetAttr();
|
||||
usd_value_writer_.SetAttribute(prim_attr, pxr::VtValue(data), timecode);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_generic_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
const bke::AttributeMetaData &meta_data)
|
||||
{
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
const std::string name = attribute_id.name();
|
||||
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(name));
|
||||
const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh);
|
||||
|
||||
/* Varying type depends on original domain. */
|
||||
const std::optional<pxr::TfToken> prim_varying = convert_blender_domain_to_usd(meta_data.domain);
|
||||
const std::optional<pxr::SdfValueTypeName> prim_attr_type = convert_blender_type_to_usd(
|
||||
meta_data.data_type);
|
||||
|
||||
const GVArraySpan attribute = *mesh->attributes().lookup(
|
||||
attribute_id, meta_data.domain, meta_data.data_type);
|
||||
if (attribute.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prim_varying || !prim_attr_type) {
|
||||
WM_reportf(RPT_WARNING,
|
||||
"Mesh %s, Attribute %s cannot be converted to USD.",
|
||||
&mesh->id.name[2],
|
||||
attribute_id.name().data());
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdGeomPrimvar attribute_pv = pvApi.CreatePrimvar(
|
||||
primvar_name, *prim_attr_type, *prim_varying);
|
||||
|
||||
switch (meta_data.data_type) {
|
||||
case CD_PROP_FLOAT:
|
||||
copy_blender_buffer_to_prim<float, float>(attribute.typed<float>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_INT8:
|
||||
copy_blender_buffer_to_prim<int8_t, int32_t>(
|
||||
attribute.typed<int8_t>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_INT32:
|
||||
copy_blender_buffer_to_prim<int, int32_t>(attribute.typed<int>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_FLOAT2:
|
||||
copy_blender_buffer_to_prim<float2, pxr::GfVec2f>(
|
||||
attribute.typed<float2>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_FLOAT3:
|
||||
copy_blender_buffer_to_prim<float3, pxr::GfVec3f>(
|
||||
attribute.typed<float3>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_BOOL:
|
||||
copy_blender_buffer_to_prim<bool, bool>(attribute.typed<bool>(), timecode, attribute_pv);
|
||||
break;
|
||||
case CD_PROP_QUATERNION:
|
||||
copy_blender_buffer_to_prim<math::Quaternion, pxr::GfQuatf>(
|
||||
attribute.typed<math::Quaternion>(), timecode, attribute_pv);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unsupported type for mesh data.");
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_uv_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
const char *active_set_name)
|
||||
{
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh);
|
||||
|
||||
const blender::StringRef active_ref(active_set_name);
|
||||
|
||||
/* Because primvars don't have a notion of "active" for data like
|
||||
* UVs, but a specific UV set may be considered "active" by target
|
||||
* applications, the [ ---- ] is to name the active set "st". */
|
||||
const std::string name = active_set_name && (active_ref == attribute_id.name()) ?
|
||||
"st" :
|
||||
attribute_id.name();
|
||||
|
||||
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(name));
|
||||
|
||||
pxr::UsdGeomPrimvar uv_pv = pvApi.CreatePrimvar(
|
||||
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
|
||||
|
||||
const VArraySpan<float2> buffer = *mesh->attributes().lookup<float2>(attribute_id,
|
||||
ATTR_DOMAIN_CORNER);
|
||||
if (buffer.is_empty()) {
|
||||
return;
|
||||
}
|
||||
copy_blender_buffer_to_prim<float2, pxr::GfVec2f>(buffer, timecode, uv_pv);
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_color_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
@@ -110,36 +330,20 @@ void USDGenericMeshWriter::write_color_data(const Mesh *mesh,
|
||||
pxr::UsdGeomPrimvar colors_pv = pvApi.CreatePrimvar(
|
||||
primvar_name, pxr::SdfValueTypeNames->Color3fArray, prim_varying);
|
||||
|
||||
const VArray<ColorGeometry4f> attribute = *mesh->attributes().lookup_or_default<ColorGeometry4f>(
|
||||
attribute_id, meta_data.domain, {0.0f, 0.0f, 0.0f, 1.0f});
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> colors_data;
|
||||
|
||||
/* TODO: Thread the copy, like the obj exporter. */
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
for (size_t loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
|
||||
const ColorGeometry4f color = attribute.get(loop_idx);
|
||||
colors_data.push_back(pxr::GfVec3f(color.r, color.g, color.b));
|
||||
}
|
||||
break;
|
||||
|
||||
case ATTR_DOMAIN_POINT:
|
||||
for (const int point_index : attribute.index_range()) {
|
||||
const ColorGeometry4f color = attribute.get(point_index);
|
||||
colors_data.push_back(pxr::GfVec3f(color.r, color.g, color.b));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(0, "Invalid domain for mesh color data.");
|
||||
return;
|
||||
const VArraySpan<ColorGeometry4f> buffer = *mesh->attributes().lookup<ColorGeometry4f>(
|
||||
attribute_id, meta_data.domain);
|
||||
if (buffer.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
colors_pv.Set(colors_data, timecode);
|
||||
|
||||
const pxr::UsdAttribute &prim_colors_attr = colors_pv.GetAttr();
|
||||
usd_value_writer_.SetAttribute(prim_colors_attr, pxr::VtValue(colors_data), timecode);
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
case ATTR_DOMAIN_POINT:
|
||||
copy_blender_buffer_to_prim<ColorGeometry4f, pxr::GfVec3f>(buffer, timecode, colors_pv);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Invalid type for mesh color data.");
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
|
||||
@@ -176,45 +380,9 @@ struct USDMeshData {
|
||||
pxr::VtFloatArray corner_sharpnesses;
|
||||
};
|
||||
|
||||
void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
|
||||
{
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
|
||||
pxr::UsdGeomPrimvarsAPI primvarsAPI(usd_mesh.GetPrim());
|
||||
|
||||
const CustomData *ldata = &mesh->loop_data;
|
||||
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
|
||||
const CustomDataLayer *layer = &ldata->layers[layer_idx];
|
||||
if (layer->type != CD_PROP_FLOAT2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
|
||||
* The primvar name is the same as the UV Map name. This is to allow the standard name "st"
|
||||
* for texture coordinates by naming the UV Map as such, without having to guess which UV Map
|
||||
* is the "standard" one. */
|
||||
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
|
||||
pxr::UsdGeomPrimvar uv_coords_primvar = primvarsAPI.CreatePrimvar(
|
||||
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
|
||||
|
||||
const float2 *mloopuv = static_cast<const float2 *>(layer->data);
|
||||
pxr::VtArray<pxr::GfVec2f> uv_coords;
|
||||
for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
|
||||
uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].x, mloopuv[loop_idx].y));
|
||||
}
|
||||
|
||||
if (!uv_coords_primvar.HasValue()) {
|
||||
uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
|
||||
}
|
||||
const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
|
||||
usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
|
||||
}
|
||||
}
|
||||
|
||||
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||
{
|
||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||
pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
|
||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
||||
|
||||
@@ -300,10 +468,6 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||
attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
|
||||
}
|
||||
|
||||
if (usd_export_context_.export_params.export_uvmaps) {
|
||||
write_uv_maps(mesh, usd_mesh);
|
||||
}
|
||||
|
||||
write_custom_data(mesh, usd_mesh);
|
||||
|
||||
if (usd_export_context_.export_params.export_normals) {
|
||||
|
||||
@@ -34,15 +34,27 @@ class USDGenericMeshWriter : public USDAbstractWriter {
|
||||
void assign_materials(const HierarchyContext &context,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const MaterialFaceGroups &usd_face_groups);
|
||||
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||
void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||
|
||||
void write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||
void write_generic_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
const bke::AttributeMetaData &meta_data);
|
||||
void write_uv_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
const char *active_set_name);
|
||||
void write_color_data(const Mesh *mesh,
|
||||
pxr::UsdGeomMesh usd_mesh,
|
||||
const bke::AttributeIDRef &attribute_id,
|
||||
const bke::AttributeMetaData &meta_data);
|
||||
|
||||
template<typename BlenderT, typename USDT>
|
||||
void copy_blender_buffer_to_prim(const Span<BlenderT> buffer,
|
||||
const pxr::UsdTimeCode timecode,
|
||||
pxr::UsdGeomPrimvar attribute_pv);
|
||||
};
|
||||
|
||||
class USDMeshWriter : public USDGenericMeshWriter {
|
||||
|
||||
@@ -44,6 +44,7 @@ struct USDExportParams {
|
||||
bool export_hair = true;
|
||||
bool export_uvmaps = true;
|
||||
bool export_normals = true;
|
||||
bool export_mesh_colors = true;
|
||||
bool export_materials = true;
|
||||
bool selected_objects_only = false;
|
||||
bool visible_objects_only = true;
|
||||
|
||||
@@ -2238,6 +2238,9 @@ enum {
|
||||
* the mesh topology changes, but this heuristic sometimes fails. In these cases, users can
|
||||
* disable interpolation with this flag. */
|
||||
MOD_MESHSEQ_INTERPOLATE_VERTICES = (1 << 4),
|
||||
|
||||
/* Read animated custom attributes from point cache files. */
|
||||
MOD_MESHSEQ_READ_ATTRIBUTES = (1 << 5),
|
||||
};
|
||||
|
||||
typedef struct SDefBind {
|
||||
|
||||
Reference in New Issue
Block a user