USD: Read and write custom attributes for Curves

Add support for reading and writing custom `Curves` attributes.

This allows us to roundtrip Blender's Hair grooms containing UVs and
other attribute data. Note that animated attribute values are not
supported with this change.

This will also address #120042

Pull Request: https://projects.blender.org/blender/blender/pulls/121928
This commit is contained in:
Jesse Yurkovich
2024-05-25 22:23:40 +02:00
committed by Jesse Yurkovich
parent d3dbd7bdd0
commit 21db0daa4e
6 changed files with 306 additions and 1 deletions

View File

@@ -6,11 +6,14 @@
#include "usd_reader_curve.hh"
#include "usd.hh"
#include "usd_attribute_utils.hh"
#include "usd_hash_types.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "BLI_index_range.hh"
#include "BLI_math_vector_types.hh"
@@ -20,8 +23,10 @@
#include <pxr/base/vt/types.h>
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
namespace blender::io::usd {
static inline float3 to_float3(pxr::GfVec3f vec3f)
{
return float3(vec3f.data());
@@ -105,6 +110,27 @@ static CurveType get_curve_type(pxr::TfToken type, pxr::TfToken basis)
return CURVE_TYPE_POLY;
}
static const std::optional<bke::AttrDomain> convert_usd_interp_to_blender(
const pxr::TfToken usd_domain)
{
static const blender::Map<pxr::TfToken, bke::AttrDomain> domain_map = []() {
blender::Map<pxr::TfToken, bke::AttrDomain> map;
map.add_new(pxr::UsdGeomTokens->vertex, bke::AttrDomain::Point);
map.add_new(pxr::UsdGeomTokens->varying, bke::AttrDomain::Point);
map.add_new(pxr::UsdGeomTokens->constant, bke::AttrDomain::Curve);
map.add_new(pxr::UsdGeomTokens->uniform, bke::AttrDomain::Curve);
return map;
}();
const bke::AttrDomain *value = domain_map.lookup_ptr(usd_domain);
if (value == nullptr) {
return std::nullopt;
}
return *value;
}
void USDCurvesReader::create_object(Main *bmain, const double /*motionSampleTime*/)
{
curve_ = static_cast<Curves *>(BKE_curves_add(bmain, name_.c_str()));
@@ -258,6 +284,41 @@ void USDCurvesReader::read_curve_sample(Curves *curves_id, const double motionSa
radii.finish();
}
read_custom_data(curves, motionSampleTime);
}
void USDCurvesReader::read_custom_data(bke::CurvesGeometry &curves,
const double motionSampleTime) const
{
pxr::UsdGeomPrimvarsAPI pv_api(curve_prim_);
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
for (const pxr::UsdGeomPrimvar &pv : primvars) {
if (!pv.HasValue()) {
continue;
}
const pxr::SdfValueTypeName pv_type = pv.GetTypeName();
const pxr::TfToken pv_interp = pv.GetInterpolation();
const std::optional<bke::AttrDomain> domain = convert_usd_interp_to_blender(pv_interp);
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(pv_type);
if (!domain.has_value() || !type.has_value()) {
const pxr::TfToken pv_name = pv.StripPrimvarsName(pv.GetPrimvarName());
BKE_reportf(reports(),
RPT_WARNING,
"Primvar '%s' (interpolation %s, type %s) cannot be converted to Blender",
pv_name.GetText(),
pv_interp.GetText(),
pv_type.GetAsToken().GetText());
continue;
}
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
copy_primvar_to_blender_attribute(pv, motionSampleTime, *type, *domain, {}, attributes);
}
}
void USDCurvesReader::read_geometry(bke::GeometrySet &geometry_set,

View File

@@ -13,7 +13,8 @@
struct Curves;
namespace blender::bke {
struct GeometrySet;
}
class CurvesGeometry;
} // namespace blender::bke
namespace blender::io::usd {
@@ -43,6 +44,8 @@ class USDCurvesReader : public USDGeomReader {
void read_geometry(bke::GeometrySet &geometry_set,
USDMeshReadParams params,
const char **err_str) override;
void read_custom_data(bke::CurvesGeometry &curves, const double motionSampleTime) const;
};
} // namespace blender::io::usd

View File

@@ -3,18 +3,25 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <numeric>
#include <string>
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/curves.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/primvar.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include "usd_attribute_utils.hh"
#include "usd_hierarchy_iterator.hh"
#include "usd_writer_curves.hh"
#include "BLI_array_utils.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_span.hh"
#include "BLI_virtual_array.hh"
#include "BKE_attribute.hh"
#include "BKE_curve_legacy_convert.hh"
@@ -354,6 +361,119 @@ void USDCurvesWriter::set_writer_attributes(pxr::UsdGeomCurves &usd_curves,
}
}
static std::optional<pxr::TfToken> convert_blender_domain_to_usd(
const bke::AttrDomain blender_domain, bool is_bezier)
{
switch (blender_domain) {
case bke::AttrDomain::Point:
return is_bezier ? pxr::UsdGeomTokens->varying : pxr::UsdGeomTokens->vertex;
case bke::AttrDomain::Curve:
return pxr::UsdGeomTokens->uniform;
default:
return std::nullopt;
}
}
void USDCurvesWriter::write_generic_data(const bke::CurvesGeometry &curves,
const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data,
const pxr::UsdGeomCurves &usd_curves)
{
const CurveType curve_type = CurveType(curves.curve_types().first());
const bool is_bezier = curve_type == CURVE_TYPE_BEZIER;
const std::optional<pxr::TfToken> pv_interp = convert_blender_domain_to_usd(meta_data.domain,
is_bezier);
const std::optional<pxr::SdfValueTypeName> pv_type = convert_blender_type_to_usd(
meta_data.data_type);
if (!pv_interp || !pv_type) {
BKE_reportf(this->reports(),
RPT_WARNING,
"Attribute '%s' (Blender domain %d, type %d) cannot be converted to USD",
std::string(attribute_id.name()).c_str(),
meta_data.domain,
meta_data.data_type);
return;
}
const GVArray attribute = *curves.attributes().lookup(
attribute_id, meta_data.domain, meta_data.data_type);
if (attribute.is_empty()) {
return;
}
const pxr::UsdTimeCode timecode = get_export_time_code();
const pxr::TfToken pv_name(pxr::TfMakeValidIdentifier(attribute_id.name()));
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
pxr::UsdGeomPrimvar pv_attr = pv_api.CreatePrimvar(pv_name, *pv_type, *pv_interp);
copy_blender_attribute_to_primvar(
attribute, meta_data.data_type, timecode, pv_attr, usd_value_writer_);
}
void USDCurvesWriter::write_uv_data(const bke::CurvesGeometry &curves,
const bke::AttributeIDRef &attribute_id,
const pxr::UsdGeomCurves &usd_curves)
{
const VArray<float2> buffer = *curves.attributes().lookup<float2>(attribute_id,
bke::AttrDomain::Curve);
if (buffer.is_empty()) {
return;
}
const pxr::UsdTimeCode timecode = get_export_time_code();
const pxr::TfToken pv_name(pxr::TfMakeValidIdentifier(attribute_id.name()));
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(usd_curves);
pxr::UsdGeomPrimvar pv_uv = pv_api.CreatePrimvar(
pv_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->uniform);
copy_blender_buffer_to_primvar<float2, pxr::GfVec2f>(buffer, timecode, pv_uv, usd_value_writer_);
}
void USDCurvesWriter::write_custom_data(const bke::CurvesGeometry &curves,
pxr::UsdGeomCurves &usd_curves)
{
const bke::AttributeAccessor attributes = curves.attributes();
attributes.for_all(
[&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) {
/* Skip "internal" Blender properties and attributes dealt with elsewhere. */
const StringRef attr_name = attribute_id.name();
if (attr_name[0] == '.' || attribute_id.is_anonymous() ||
ELEM(attr_name,
"position",
"radius",
"resolution",
"id",
"curve_type",
"handle_left",
"handle_right",
"handle_type_left",
"handle_type_right"))
{
return true;
}
/* Spline UV data */
if (meta_data.domain == bke::AttrDomain::Curve && meta_data.data_type == CD_PROP_FLOAT2) {
if (usd_export_context_.export_params.export_uvmaps) {
this->write_uv_data(curves, attribute_id, usd_curves);
}
}
/* Everything else. */
else {
this->write_generic_data(curves, attribute_id, meta_data, usd_curves);
}
return true;
});
}
void USDCurvesWriter::do_write(HierarchyContext &context)
{
Curves *curves_id;
@@ -481,6 +601,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context)
assign_materials(context, *usd_curves);
write_custom_data(curves, *usd_curves);
auto prim = usd_curves->GetPrim();
write_id_properties(prim, curves_id->id, timecode);
}

View File

@@ -9,6 +9,12 @@
#include <pxr/usd/usdGeom/curves.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
namespace blender::bke {
class AttributeIDRef;
struct AttributeMetaData;
class CurvesGeometry;
} // namespace blender::bke
namespace blender::io::usd {
/* Writer for writing Curves data as USD curves. */
@@ -38,6 +44,18 @@ class USDCurvesWriter final : public USDAbstractWriter {
const pxr::VtArray<double> &knots,
const pxr::VtArray<int> &orders,
const pxr::UsdTimeCode timecode);
void write_generic_data(const bke::CurvesGeometry &curves,
const bke::AttributeIDRef &attribute_id,
const bke::AttributeMetaData &meta_data,
const pxr::UsdGeomCurves &usd_curves);
void write_uv_data(const bke::CurvesGeometry &curves,
const bke::AttributeIDRef &attribute_id,
const pxr::UsdGeomCurves &usd_curves);
void write_custom_data(const blender::bke::CurvesGeometry &curves,
pxr::UsdGeomCurves &usd_curves);
};
} // namespace blender::io::usd