USD IO: Move to the new Mesh Attributes API for Colors #105347

Merged
Sybren A. Stüvel merged 11 commits from CharlesWardlaw/blender:feature/usd_colors into main 2023-04-14 11:05:38 +02:00
5 changed files with 214 additions and 61 deletions
Showing only changes of commit 151e8b4224 - Show all commits

View File

@ -326,7 +326,7 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"overwrite_textures",
false,
"Overwrite Textures",
"Allow overwriting existing texture files when exporting textures");
"Overwrite existing files when exporting textures");
RNA_def_boolean(ot->srna,
"relative_paths",
@ -612,7 +612,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
RNA_def_boolean(ot->srna, "read_mesh_uvs", true, "UV Coordinates", "Read mesh UV coordinates");
RNA_def_boolean(
ot->srna, "read_mesh_colors", false, "Color Attributes", "Read mesh color attributes");
ot->srna, "read_mesh_colors", true, "Color Attributes", "Read mesh color attributes");
RNA_def_string(ot->srna,
"prim_path_mask",

View File

@ -25,6 +25,9 @@
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_windowmanager_types.h"
#include "WM_api.h"
#include "MEM_guardedalloc.h"
@ -35,6 +38,7 @@
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdGeom/subset.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include <iostream>
@ -412,100 +416,154 @@ void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bo
}
}
void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
void USDMeshReader::read_color_data_all(Mesh *mesh, const double motionSampleTime)
{
if (!(mesh && mesh_prim_ && mesh->totloop > 0)) {
return;
}
/* Early out if we read the display color before and if this attribute isn't animated. */
if (primvar_varying_map_.find(usdtokens::displayColor) != primvar_varying_map_.end() &&
!primvar_varying_map_.at(usdtokens::displayColor)) {
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
/* Convert color primvars to custom layer data. */
for (pxr::UsdGeomPrimvar &pv : primvars) {
pxr::SdfValueTypeName type = pv.GetTypeName();
if (!ELEM(type,
pxr::SdfValueTypeNames->Color3hArray,
pxr::SdfValueTypeNames->Color3fArray,
pxr::SdfValueTypeNames->Color3dArray)) {
continue;
}
pxr::TfToken name = pv.GetPrimvarName();
/* 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(mesh, pv, motionSampleTime);
}
}
void USDMeshReader::read_color_data(Mesh *mesh,
const pxr::UsdGeomPrimvar &color_primvar,
const double motionSampleTime)
{
if (!(mesh && color_primvar && color_primvar.HasValue())) {
return;
}
pxr::UsdGeomPrimvar color_primvar = mesh_prim_.GetDisplayColorPrimvar();
if (!color_primvar.HasValue()) {
return;
}
pxr::TfToken interp = color_primvar.GetInterpolation();
if (interp == pxr::UsdGeomTokens->varying) {
std::cerr << "WARNING: Unsupported varying interpolation for display colors\n" << std::endl;
return;
}
if (primvar_varying_map_.find(usdtokens::displayColor) == primvar_varying_map_.end()) {
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(usdtokens::displayColor, might_be_time_varying));
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> display_colors;
pxr::VtArray<pxr::GfVec3f> usd_colors;
if (!color_primvar.ComputeFlattened(&display_colors, motionSampleTime)) {
std::cerr << "WARNING: Couldn't compute display colors\n" << std::endl;
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());
return;
}
if ((interp == pxr::UsdGeomTokens->faceVarying && display_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->vertex && display_colors.size() != mesh->totvert) ||
(interp == pxr::UsdGeomTokens->constant && display_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && display_colors.size() != mesh->totpoly)) {
std::cerr << "WARNING: display colors count mismatch\n" << std::endl;
pxr::TfToken interp = color_primvar.GetInterpolation();
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->vertex && usd_colors.size() != mesh->totvert) ||
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->totpoly)) {
WM_reportf(RPT_WARNING,
"USD Import: color attribute value '%s' count inconsistent with interpolation type",
color_primvar.GetName().GetText());
return;
}
void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_PROP_BYTE_COLOR);
const StringRef color_primvar_name(color_primvar.GetBaseName().GetString());
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();

For a future patch (because I feel it's more important to land this patch & improve upon it than to keep delaying further), I think it might be better to structure this something like:

int expected_size(const pxr::TfToken interp, const Mesh *mesh) {
  switch (interp) {
  case pxr::UsdGeomTokens->faceVarying:
  case pxr::UsdGeomTokens->varying:
    return mesh->totloop;
  case pxr::UsdGeomTokens->vertex:
    return mesh->totvert;
  case pxr::UsdGeomTokens->constant:
    return 1;
  case pxr::UsdGeomTokens->uniform:
    return mesh->totpoly;
  default:
    return 0;
  }
}

...

const int expect = expected_size(interp, mesh)
if (expect && usd_colors.size() != expected_size(interp, mesh)) { ... }

(update: also see my comment below about a way to take this even further)

For a future patch (because I feel it's more important to land this patch & improve upon it than to keep delaying further), I think it might be better to structure this something like: ```cpp int expected_size(const pxr::TfToken interp, const Mesh *mesh) { switch (interp) { case pxr::UsdGeomTokens->faceVarying: case pxr::UsdGeomTokens->varying: return mesh->totloop; case pxr::UsdGeomTokens->vertex: return mesh->totvert; case pxr::UsdGeomTokens->constant: return 1; case pxr::UsdGeomTokens->uniform: return mesh->totpoly; default: return 0; } } ... const int expect = expected_size(interp, mesh) if (expect && usd_colors.size() != expected_size(interp, mesh)) { ... } ``` (update: also see my comment below about a way to take this even further)
if (!cd_ptr) {
std::cerr << "WARNING: Couldn't add displayColors custom data.\n";
eAttrDomain color_domain = ATTR_DOMAIN_POINT;
if (ELEM(interp,
pxr::UsdGeomTokens->varying,
pxr::UsdGeomTokens->faceVarying,
pxr::UsdGeomTokens->uniform)) {
color_domain = ATTR_DOMAIN_CORNER;
}
bke::SpanAttributeWriter<ColorGeometry4f> color_data;
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(color_primvar_name,
color_domain);
if (!color_data) {
WM_reportf(RPT_WARNING,
"USD Import: couldn't add color attribute '%s'",
color_primvar.GetBaseName().GetText());
return;
}
MLoopCol *colors = static_cast<MLoopCol *>(cd_ptr);
/* Check for situations that allow for a straight-forward copy by index. */
if ((ELEM(interp, pxr::UsdGeomTokens->vertex)) ||
(color_domain == ATTR_DOMAIN_CORNER && !is_left_handed_)) {
for (int i = 0; i < usd_colors.size(); i++) {
ColorGeometry4f color = ColorGeometry4f(
usd_colors[i][0], usd_colors[i][1], usd_colors[i][2], 1.0f);
color_data.span[i] = color;
}
}
const Span<MPoly> polys = mesh->polys();
const Span<MLoop> loops = mesh->loops();
for (const int i : polys.index_range()) {
const MPoly &poly = polys[i];
for (int j = 0; j < poly.totloop; ++j) {
int loop_index = poly.loopstart + j;
/* Special case: expand uniform color into corner color.
* Uniforms in USD come through as single colors, face-varying. Since Blender does not
* support this particular combination for paintable color attributes, we convert the type
* here to make sure that the user gets the same visual result.

For the code below, a refactor would be in order. The handling of the interpolation type is now interleaved through various conditionals, and that makes things hard to follow / extend.

Instead of my suggestion above, it could be nice to have a strategy pattern, where each interpolation type extends an abstract base class. The selection of the concrete class would be done with a switch like above, and then that class can handle the 'how many entries are expected?' queries as well as handling the actual filling of the mesh data.

For the code below, a refactor would be in order. The handling of the interpolation type is now interleaved through various conditionals, and that makes things hard to follow / extend. Instead of my suggestion above, it could be nice to have a strategy pattern, where each interpolation type extends an abstract base class. The selection of the concrete class would be done with a `switch` like above, and then that class can handle the 'how many entries are expected?' queries as well as handling the actual filling of the mesh data.
* */
else if (ELEM(interp, pxr::UsdGeomTokens->uniform)) {
for (int i = 0; i < usd_colors.size(); i++) {
const ColorGeometry4f color = ColorGeometry4f(
usd_colors[i][0], usd_colors[i][1], usd_colors[i][2], 1.0f);
color_data.span[i * 4] = color;
color_data.span[i * 4 + 1] = color;
color_data.span[i * 4 + 2] = color;
color_data.span[i * 4 + 3] = color;
}
}
/* Default for constant varying interpolation. */
int usd_index = 0;
else {
/* Loop-level data, but left-handed, requires a bit of a swizzle. */
const Span<MPoly> polys = mesh->polys();
if (interp == pxr::UsdGeomTokens->vertex) {
usd_index = loops[loop_index].v;
}
else if (interp == pxr::UsdGeomTokens->faceVarying) {
usd_index = poly.loopstart;
for (const MPoly &poly : polys) {
for (int j = 0; j < poly.totloop; ++j) {
/* Default for constant varying interpolation. */
int usd_index = poly.loopstart;
if (is_left_handed_) {
usd_index += poly.totloop - 1 - j;
}
else {
usd_index += j;
}
}
else if (interp == pxr::UsdGeomTokens->uniform) {
/* Uniform varying uses the poly index. */
usd_index = i;
}
if (usd_index >= display_colors.size()) {
continue;
}
if (usd_index >= usd_colors.size()) {
continue;
}
colors[loop_index].r = unit_float_to_uchar_clamp(display_colors[usd_index][0]);
colors[loop_index].g = unit_float_to_uchar_clamp(display_colors[usd_index][1]);
colors[loop_index].b = unit_float_to_uchar_clamp(display_colors[usd_index][2]);
colors[loop_index].a = unit_float_to_uchar_clamp(1.0);
ColorGeometry4f color = ColorGeometry4f(
usd_colors[usd_index][0], usd_colors[usd_index][1], usd_colors[usd_index][2], 1.0f);
color_data.span[usd_index] = color;
}
}
}
color_data.finish();
}
void USDMeshReader::read_vertex_creases(Mesh *mesh, const double motionSampleTime)
@ -676,9 +734,19 @@ void USDMeshReader::read_mesh_sample(ImportSettings *settings,
read_uvs(mesh, motionSampleTime, new_mesh);
}
/* Custom Data layers. */
read_custom_data(settings, mesh, motionSampleTime);
}
void USDMeshReader::read_custom_data(const ImportSettings *settings,
Mesh *mesh,
const double motionSampleTime)
{
if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) {
read_colors(mesh, motionSampleTime);
read_color_data_all(mesh, motionSampleTime);
}
/* TODO: Generic readers for custom data layers not listed above. */
}
void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,

View File

@ -66,13 +66,19 @@ class USDMeshReader : public USDGeomReader {
void read_mpolys(Mesh *mesh);
void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false);
void read_colors(Mesh *mesh, double motionSampleTime);
void read_vertex_creases(Mesh *mesh, double motionSampleTime);
void read_mesh_sample(ImportSettings *settings,
Mesh *mesh,
double motionSampleTime,
bool new_mesh);
void read_custom_data(const ImportSettings *settings,
Mesh *mesh,
double motionSampleTime);
void read_color_data_all(Mesh *mesh, double motionSampleTime);
CharlesWardlaw marked this conversation as resolved Outdated

Now there is ..._data and ..._data_all. It's not immediately clear from the naming how these relate. read_color_data_primvar and read_color_data_all_primvars would be better, but if you feel that's getting too long, documentation of what they do would already help a lot.

Now there is `..._data` and `..._data_all`. It's not immediately clear from the naming how these relate. `read_color_data_primvar` and `read_color_data_all_primvars` would be better, but if you feel that's getting too long, documentation of what they do would already help a lot.
void read_color_data(Mesh *mesh, const pxr::UsdGeomPrimvar &color_primvar, double motionSampleTime);
};
} // namespace blender::io::usd

View File

@ -14,7 +14,6 @@
#include "BLI_math_vector_types.hh"
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
@ -32,6 +31,8 @@
#include "DNA_object_fluidsim_types.h"
#include "DNA_particle_types.h"
#include "WM_api.h"
#include <iostream>
namespace blender::io::usd {
@ -73,6 +74,72 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
}
}
void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
{
const bke::AttributeAccessor attributes = mesh->attributes();
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)) {
write_color_data(mesh, usd_mesh, attribute_id, meta_data);
}
return true;
});
}
void USDGenericMeshWriter::write_color_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 pxr::TfToken prim_varying = meta_data.domain == ATTR_DOMAIN_CORNER ?
pxr::UsdGeomTokens->faceVarying :
pxr::UsdGeomTokens->vertex;
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;
}
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);
}
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
{
BKE_id_free(nullptr, mesh);
@ -233,6 +300,9 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
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) {
write_normals(mesh, usd_mesh);
}

View File

@ -4,6 +4,8 @@
#include "usd_writer_abstract.h"
#include "BKE_attribute.hh"
#include <pxr/usd/usdGeom/mesh.h>
namespace blender::io::usd {
@ -34,6 +36,13 @@ class USDGenericMeshWriter : public USDAbstractWriter {
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);
CharlesWardlaw marked this conversation as resolved Outdated

At the moment write_custom_data() only writes color data, meaning that this function is strongly related to write_color_data(). Remove the empty line between the two to show this relation more visually.

At the moment `write_custom_data()` only writes color data, meaning that this function is strongly related to `write_color_data()`. Remove the empty line between the two to show this relation more visually.
void write_color_data(const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data);
};
class USDMeshWriter : public USDGenericMeshWriter {