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:
committed by
Jesse Yurkovich
parent
d3dbd7bdd0
commit
21db0daa4e
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user