USD IO: Generic Attributes Support #109518

Merged
Michael Kowalski merged 34 commits from CharlesWardlaw/blender:feature/generic_attributes into main 2023-08-11 23:47:24 +02:00
9 changed files with 709 additions and 350 deletions

View File

@ -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,
CharlesWardlaw marked this conversation as resolved Outdated

Mesh Color Attributes -> mesh color attributes

Same below. Described here: https://wiki.blender.org/wiki/Human_Interface_Guidelines/Writing_Style

`Mesh Color Attributes` -> `mesh color attributes` Same below. Described here: https://wiki.blender.org/wiki/Human_Interface_Guidelines/Writing_Style

Missed the "same below" part in the description for "read_mesh_attributes"

Missed the "same below" part in the description for `"read_mesh_attributes"`
"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");
CharlesWardlaw marked this conversation as resolved Outdated
/home/hans/blender-git/blender/source/blender/editors/io/io_usd.cc:413:14: warning: unused variable ‘read_mesh_attributes’ [-Wunused-variable]
  413 |   const bool read_mesh_attributes = RNA_boolean_get(op->ptr, "read_mesh_attributes");
      |              ^~~~~~~~~~~~~~~~~~~~
``` /home/hans/blender-git/blender/source/blender/editors/io/io_usd.cc:413:14: warning: unused variable ‘read_mesh_attributes’ [-Wunused-variable] 413 | 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,

View File

@ -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

View File

@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
CharlesWardlaw marked this conversation as resolved Outdated

Missing license and copyright

Missing license and copyright
Review

Looks like this header should be added to source/blender/io/usd/CMakeLists.txt, right before intern/usd_hierarchy_iterator.h I guess.

Looks like this header should be added to `source/blender/io/usd/CMakeLists.txt`, right before `intern/usd_hierarchy_iterator.h` I guess.
*
CharlesWardlaw marked this conversation as resolved Outdated

Blender code uses #pragma once rather than include guards

Blender code uses `#pragma once` rather than include guards
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
CharlesWardlaw marked this conversation as resolved Outdated

Compiler error here without this too #include "BLI_hash.hh"

Compiler error here without this too `#include "BLI_hash.hh"`
#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

View File

@ -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);
Review

@CharlesWardlaw Can we also handle USD Vector3 types here?

    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);
@CharlesWardlaw Can we also handle USD Vector3 types here? ``` 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->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);
CharlesWardlaw marked this conversation as resolved Outdated

Should return std::optional<eCustomDataType>

Should return `std::optional<eCustomDataType>`
map.add_new(pxr::SdfValueTypeNames->Color3hArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->Color3dArray, CD_PROP_COLOR);
CharlesWardlaw marked this conversation as resolved Outdated

Typically blender::Map is preferred over std::unordered_map in Blender code. In the ValueTypeName header it also mentions that the strings shouldn't be used for comparison. Suggest this instead:

  static const Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
    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->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 *ptr = type_map.lookup_ptr(usd_type);

Map is made aware of the hash with this above in the Blender namespace:

namespace blender {
template<> struct DefaultHash<pxr::SdfValueTypeName> {
  uint64_t operator()(const pxr::SdfValueTypeName &value) const
  {
    return value.GetHash();
  }
};
}  // namespace blender
Typically `blender::Map` is preferred over `std::unordered_map` in Blender code. In the `ValueTypeName` header it also mentions that the strings shouldn't be used for comparison. Suggest this instead: ``` static const Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() { 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->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 *ptr = type_map.lookup_ptr(usd_type); ``` `Map` is made aware of the hash with this above in the Blender namespace: ``` namespace blender { template<> struct DefaultHash<pxr::SdfValueTypeName> { uint64_t operator()(const pxr::SdfValueTypeName &value) const { return value.GetHash(); } }; } // namespace blender ```

Changed; added new header usd_hash_types.h so the hashes required by blender::Map can be shared across translation units.

Changed; added new header usd_hash_types.h so the hashes required by blender::Map can be shared across translation units.
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! */
CharlesWardlaw marked this conversation as resolved Outdated

Should return std::optional<eAttrDomain> rather than using 0 as an error value

Should return `std::optional<eAttrDomain>` rather than using 0 as an error value
return map;
CharlesWardlaw marked this conversation as resolved Outdated

Pass strings with StringRef, this function currently duplicates the string argument

Pass strings with `StringRef`, this function currently duplicates the string argument
}();
const eAttrDomain *value = domain_map.lookup_ptr(usd_domain);
Review

Looks like most error messages in Blender don't end with a period, might as well be consistent here and above.

Looks like most error messages in Blender don't end with a period, might as well be consistent here and above.
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;
CharlesWardlaw marked this conversation as resolved Outdated

Better to leave these name changes out of this PR. Cleanup and functional changes should be separate.

Better to leave these name changes out of this PR. Cleanup and functional changes should be separate.

Changed back.

Changed back.
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());
CharlesWardlaw marked this conversation as resolved Outdated

Declare the variable and assign its value in the same statement

Declare the variable and assign its value in the same statement
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,
CharlesWardlaw marked this conversation as resolved Outdated

extra semicolon

extra semicolon
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))
Review

Capitalize UV

Capitalize `UV`
{
WM_reportf(RPT_WARNING,
"USD Import: UV attribute value '%s' count inconsistent with interpolation type",
CharlesWardlaw marked this conversation as resolved Outdated

Declare variable and assign its value in the same statement

Declare variable and assign its value in the same statement
primvar.GetName().GetText());
return;
}
CharlesWardlaw marked this conversation as resolved Outdated

Missing clang format

Missing clang format
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'",
CharlesWardlaw marked this conversation as resolved Outdated

Call SpanAttributeWriter variables "attribute" rather than "buffer", that's just more consistent with elsewhere in Blender and more aligned with the purpose of the type. If the span was passed separately, might make more sense to call that a "buffer".

Call `SpanAttributeWriter` variables "attribute" rather than "buffer", that's just more consistent with elsewhere in Blender and more aligned with the purpose of the type. If the span was passed separately, might make more sense to call _that_ a "buffer".

Renamed, and renamed functions to _attribute.

Renamed, and renamed functions to _attribute.
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()) {
CharlesWardlaw marked this conversation as resolved Outdated

IndexRange handles a bit of this itself.

for (const int i : face.index_range()) {
   const int rev_index = face.last(i);
`IndexRange` handles a bit of this itself. ``` for (const int i : face.index_range()) { const int rev_index = face.last(i); ```
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. */
Review

If you'd like, this pattern is handled in the array_utils header--
array_utils::gather(Span(usd_uvs.data(), usd_uvs.size()), corner_verts, uv_data.span);
It'll also be multithreaded that way

If you'd like, this pattern is handled in the `array_utils` header-- `array_utils::gather(Span(usd_uvs.data(), usd_uvs.size()), corner_verts, uv_data.span);` It'll also be multithreaded that way
Review

I can make this change in a future patch, looking at threading more cohesively. For now, the variant that compiled:

array_utils::gather(Span(usd_uvs.data(), usd_uvs.size()), IndexMask(usd_uvs.size()), uv_data.span, 4096);

seems to need some other define to satisfy the template.

I can make this change in a future patch, looking at threading more cohesively. For now, the variant that compiled: ```array_utils::gather(Span(usd_uvs.data(), usd_uvs.size()), IndexMask(usd_uvs.size()), uv_data.span, 4096);``` seems to need some other define to satisfy the template.
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;
}
CharlesWardlaw marked this conversation as resolved Outdated

Missing a return after this maybe?

Missing a return after this maybe?

They all fall off the end with the if / else clauses-- no return needed, I think.

They all fall off the end with the if / else clauses-- no return needed, I think.
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];
CharlesWardlaw marked this conversation as resolved Outdated

Same comment here about IndexRange handling the reversing

Same comment here about `IndexRange` handling the reversing
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]);
}
}
}
CharlesWardlaw marked this conversation as resolved Outdated

A bunch of unused variables here

A bunch of unused variables here
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());

I think it makes sense to structure this code a bit differently to avoid duplication and remove the need for helper functions that obfuscate the process:

  1. Retrieve the generic-type Blender attribute writer outside of the switch statement.
  2. Inside the switch statement, call one function that returns a VtArray of the pxr type
  3. Also inside the switch statement, call a function that takes VtArray and MutableSpan<T> arguments to convert from USD to Blender arrays. You can retrieve a typed span from a generic one with the .typed<T>() method.
  4. Implement that conversion function with a separate template function that converts a single value of the USD type to the Blender type

I think this way there will be no more code than necessary, and the process will be easier to read.

I think it makes sense to structure this code a bit differently to avoid duplication and remove the need for helper functions that obfuscate the process: 1. Retrieve the generic-type Blender attribute writer outside of the switch statement. 2. Inside the switch statement, call one function that returns a `VtArray` of the `pxr` type 3. Also inside the switch statement, call a function that takes `VtArray` and `MutableSpan<T>` arguments to convert from USD to Blender arrays. You can retrieve a typed span from a generic one with the `.typed<T>()` method. 4. Implement that conversion function with a separate template function that converts a single value of the USD type to the Blender type I think this way there will be no more code than necessary, and the process will be easier to read.
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;
CharlesWardlaw marked this conversation as resolved Outdated

For consistency, no need for the blank lines here too. The switch cases are all doing the same thing, there isn't much reason to separate them.

For consistency, no need for the blank lines here too. The switch cases are all doing the same thing, there isn't much reason to separate them.
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)
{
CharlesWardlaw marked this conversation as resolved Outdated

Looks like this report should be removed?

Looks like this report should be removed?
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) {
CharlesWardlaw marked this conversation as resolved Outdated

Looks like this could use convert_usd_type_to_blender

Looks like this could use `convert_usd_type_to_blender`
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. */
CharlesWardlaw marked this conversation as resolved Outdated

Might as well use convert_blender_domain_to_usd to remove the duplication here too?

Might as well use `convert_blender_domain_to_usd` to remove the duplication here too?

In this case no-- the conversions don't necessarily match up and it could cause false positives.

In this case no-- the conversions don't necessarily match up and it could cause false positives.
if (active_uv_set_name.IsEmpty() || name == usdtokens::st) {
CharlesWardlaw marked this conversation as resolved Outdated

Could you check convert_usd_type_to_blender and the domain conversion function instead of listing the types here again?

Could you check `convert_usd_type_to_blender` and the domain conversion function instead of listing the types here again?
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(

View File

@ -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

View File

@ -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;
}
CharlesWardlaw marked this conversation as resolved Outdated

Wouldn't it make more sense to skip all edge attributes here? That's why sharp_edge and crease_edge are unsupported here, right? (though I think crease_edge is copied elsewhere in the exporter already)

Wouldn't it make more sense to skip all edge attributes here? That's why `sharp_edge` and `crease_edge` are unsupported here, right? (though I think `crease_edge` is copied elsewhere in the exporter already)

I'm not sure tbh. If that's what you'd prefer I can do.

I'm not sure tbh. If that's what you'd prefer I can do.