From 955534e84877c7cc9330dab8af835ac998945258 Mon Sep 17 00:00:00 2001 From: Alex Fuller Date: Tue, 28 Apr 2026 23:26:08 -0600 Subject: [PATCH] IECoreUSD : GeomSubsets support. --- Changes | 3 + .../include/IECoreUSD/PrimitiveAlgo.h | 6 + .../IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp | 231 +++++++++++++++++- contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp | 196 ++++++++++++--- contrib/IECoreUSD/src/IECoreUSD/USDScene.h | 4 +- .../IECoreUSD/test/IECoreUSD/USDSceneTest.py | 144 +++++++++++ .../test/IECoreUSD/data/geomSubset.usda | 228 +++++++++++++++++ 7 files changed, 776 insertions(+), 36 deletions(-) create mode 100644 contrib/IECoreUSD/test/IECoreUSD/data/geomSubset.usda diff --git a/Changes b/Changes index c4c7300af6..3df21d031f 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,10 @@ 10.7.x.x (relative to 10.7.0.0a9) ======== +Features +-------- +- IECoreUSD : GeomSubsets support for meshes and material binds. 10.7.0.0a9 (relative to 10.7.0.0a8) ========== diff --git a/contrib/IECoreUSD/include/IECoreUSD/PrimitiveAlgo.h b/contrib/IECoreUSD/include/IECoreUSD/PrimitiveAlgo.h index 1ef5ab0830..d196d59f37 100644 --- a/contrib/IECoreUSD/include/IECoreUSD/PrimitiveAlgo.h +++ b/contrib/IECoreUSD/include/IECoreUSD/PrimitiveAlgo.h @@ -62,6 +62,8 @@ IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECore IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECoreScene::PrimitiveVariable &primitiveVariable, const pxr::UsdGeomGprim &gprim, pxr::UsdTimeCode time ); /// As above, but redirects "P", "N" etc to the relevant attributes of `pointBased`. IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECoreScene::PrimitiveVariable &primitiveVariable, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time ); +/// Writes a PrimitiveVariable with "geomSubset:" to multiple UsdGeomSubsets found in the indexed StringVectorData name. +IECOREUSD_API void writeGeomSubsets( const pxr::TfToken &familyName, const IECoreScene::PrimitiveVariable &primitiveVariable, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time ); /// Equivalent to `DataAlgo::toUSD( primitiveVariable.expandedData() )`, but avoiding /// the creation of the temporary expanded data. IECOREUSD_API pxr::VtValue toUSDExpanded( const IECoreScene::PrimitiveVariable &primitiveVariable, bool arrayRequired = false ); @@ -77,6 +79,8 @@ IECOREUSD_API void readPrimitiveVariables( const pxr::UsdGeomPrimvarsAPI &primva IECOREUSD_API void readPrimitiveVariables( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode timeCode, IECoreScene::Primitive *primitive, const IECore::Canceller *canceller = nullptr ); /// Reads the value for `attribute`, adding it as a primitive variable with the specified `name` and `interpolation`. IECOREUSD_API void readPrimitiveVariable( const pxr::UsdAttribute &attribute, pxr::UsdTimeCode timeCode, IECoreScene::Primitive *primitive, const std::string &name, IECoreScene::PrimitiveVariable::Interpolation interpolation = IECoreScene::PrimitiveVariable::Vertex ); +/// Reads all compatible UsdGeomSubsets, adding them to `primitive` under `geomSubset:familyName` with the UsdGeomSubset name in that family into an indexed StringVectorData. +IECOREUSD_API void readGeomSubsets( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode timeCode, IECoreScene::Primitive *primitive, const IECore::Canceller *canceller = nullptr ); /// Returns true if any of the primitive variables might be animated. IECOREUSD_API bool primitiveVariablesMightBeTimeVarying( const pxr::UsdGeomPrimvarsAPI &primvarsAPI ); /// Returns true if any of the primitive variables might be animated, including the @@ -84,6 +88,8 @@ IECOREUSD_API bool primitiveVariablesMightBeTimeVarying( const pxr::UsdGeomPrimv IECOREUSD_API bool primitiveVariablesMightBeTimeVarying( const pxr::UsdGeomPointBased &pointBased ); /// Converts interpolation from USD. IECOREUSD_API IECoreScene::PrimitiveVariable::Interpolation fromUSD( pxr::TfToken interpolationToken ); +/// Converts element type from UsdGeomSubset to interpolation. +IECOREUSD_API IECoreScene::PrimitiveVariable::Interpolation elementTypeFromUSD( pxr::TfToken elementType ); } // namespace PrimitiveAlgo diff --git a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp index 95a6cd15c7..f90841c3fa 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp @@ -46,6 +46,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY #include "pxr/base/gf/matrix4f.h" #include "pxr/base/gf/matrix4d.h" #include "pxr/usd/usdGeom/mesh.h" +#include "pxr/usd/usdGeom/subset.h" #include "pxr/usd/usdSkel/animQuery.h" #include "pxr/usd/usdSkel/bindingAPI.h" #include "pxr/usd/usdSkel/blendShapeQuery.h" @@ -56,6 +57,10 @@ IECORE_PUSH_DEFAULT_VISIBILITY #include "pxr/usd/usdSkel/utils.h" IECORE_POP_DEFAULT_VISIBILITY +#include "boost/algorithm/string/classification.hpp" +#include "boost/algorithm/string/predicate.hpp" +#include "boost/algorithm/string/split.hpp" + #include "fmt/ostream.h" using namespace std; @@ -68,6 +73,47 @@ using namespace IECoreUSD; // Writing primitive variables ////////////////////////////////////////////////////////////////////////// +namespace { + +pxr::TfToken toUSDElementType( const pxr::UsdPrim &prim ) +{ + if( prim.IsA() ) + { + return pxr::UsdGeomTokens->face; + } + return pxr::TfToken(); +} + +void writeGeomSubsetIndices( const pxr::TfToken &familyName, const StringVectorData *data, const IntVectorData *indicesData, const pxr::TfToken &elementType, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time ) +{ + int32_t subsetNameIdx = 0; + pxr::TfToken familyType = pxr::UsdGeomTokens->partition; + + for( const std::string &subsetName : data->readable() ) + { + if( subsetName.empty() && subsetNameIdx == 0 ) + { + familyType = pxr::UsdGeomTokens->nonOverlapping; + subsetNameIdx++; + continue; + } + pxr::VtIntArray usdIndices; + int32_t idx = 0; + for( const int32_t &faceIdx : indicesData->readable() ) + { + if( faceIdx == subsetNameIdx ) + { + usdIndices.push_back( idx ); + } + idx++; + } + pxr::UsdGeomSubset::CreateGeomSubset( pointBased, pxr::TfToken( subsetName ), elementType, usdIndices, familyName, familyType ); + ++subsetNameIdx; + } +} + +}; // namespace + void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const IECoreScene::PrimitiveVariable &primitiveVariable, pxr::UsdGeomPrimvar &primVar, pxr::UsdTimeCode time ) { const pxr::TfToken usdInterpolation = toUSD( primitiveVariable.interpolation ); @@ -124,6 +170,35 @@ void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const std::string &name, writePrimitiveVariable( name, primitiveVariable, pxr::UsdGeomPrimvarsAPI( gPrim.GetPrim() ), time ); } +void IECoreUSD::PrimitiveAlgo::writeGeomSubsets( const pxr::TfToken &familyName, const IECoreScene::PrimitiveVariable &primitiveVariable, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time ) +{ + if( !primitiveVariable.indices ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "No index data for UsdGeomSubset family {}", familyName.GetString() ); + return; + } + + if( pointBased.GetPrim().IsA() && + primitiveVariable.interpolation != IECoreScene::PrimitiveVariable::Uniform ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "Invalid Interpolation {} for UsdGeomSubset family {}", primitiveVariable.interpolation, familyName.GetString() ); + return; + } + + const pxr::TfToken elementType = toUSDElementType( pointBased.GetPrim() ); + + if( elementType.IsEmpty() ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "Invalid elementType for GeomSubset family {}", familyName.GetString() ); + return; + } + + if( const StringVectorData *data = static_cast( primitiveVariable.data.get() ) ) + { + writeGeomSubsetIndices( familyName, data, primitiveVariable.indices.get(), elementType, pointBased, time ); + } +} + void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const std::string &name, const IECoreScene::PrimitiveVariable &value, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time ) { if( name == "P" ) @@ -138,6 +213,15 @@ void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const std::string &name, { pointBased.CreateAccelerationsAttr().Set( PrimitiveAlgo::toUSDExpanded( value ), time ); } + else if( boost::algorithm::starts_with( name, "geomSubset:" ) ) + { + std::vector split; + boost::algorithm::split( split, name, boost::algorithm::is_any_of( ":" ) ); + if( split.size() > 1 ) + { + writeGeomSubsets( pxr::TfToken( split[1] ), value, pointBased, time ); + } + } else { writePrimitiveVariable( name, value, static_cast( pointBased ), time ); @@ -498,6 +582,74 @@ bool skelAnimMightBeTimeVarying( const pxr::UsdPrim &prim ) return animQuery.JointTransformsMightBeTimeVarying() || animQuery.BlendShapeWeightsMightBeTimeVarying(); } +bool geomSubsetsMightBeTimeVarying( const pxr::UsdGeomPointBased &pointBased ) +{ + for( const pxr::UsdGeomSubset &geomSubset : pxr::UsdGeomSubset::GetAllGeomSubsets( pointBased ) ) + { + // We don't check ElementType here as it is considered invalid if it is TimeVarying. + if( geomSubset.GetIndicesAttr().ValueMightBeTimeVarying() ) + { + return true; + } + if( geomSubset.GetFamilyNameAttr().ValueMightBeTimeVarying() ) + { + return true; + } + } + return false; +} + +void readGeomSubsetNamesAndIndices( + const pxr::UsdGeomPointBased &pointBased, + const pxr::TfToken &elementType, + const pxr::TfToken &familyName, + pxr::UsdTimeCode time, + const IECoreScene::Primitive *primitive, + std::vector &geomSubsetNames, + std::vector &geomSubsetIndices, + const Canceller *canceller +) +{ + int32_t geomSubsetIdx = 0; + + const pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pointBased, familyName ); + const pxr::VtIntArray unassignedIndices = pxr::UsdGeomSubset::GetUnassignedIndices( pointBased, elementType, familyName, time ); + if( unassignedIndices.size() && familyType == pxr::UsdGeomTokens->uniform ) + { + // This shouldn't happen based on the ValidateFamily functions above, but leaving in here if it does happen. + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "FamilyName: {} familyType: {} has unassigned indices.", familyName.GetString(), familyType.GetString() ); + } + + const auto &geomSubsets = pxr::UsdGeomSubset::GetGeomSubsets( pointBased, elementType, familyName ); + geomSubsetNames.resize( unassignedIndices.size() || familyType == pxr::UsdGeomTokens->nonOverlapping ? geomSubsets.size() + 1 : geomSubsets.size() ); + geomSubsetIndices.resize( primitive->variableSize( IECoreUSD::PrimitiveAlgo::elementTypeFromUSD( elementType ) ) ); + + if( unassignedIndices.size() || familyType == pxr::UsdGeomTokens->nonOverlapping ) + { + Canceller::check( canceller ); + geomSubsetNames[geomSubsetIdx] = std::string(); + for( const int &idx : unassignedIndices ) + { + geomSubsetIndices[idx] = geomSubsetIdx; + } + ++geomSubsetIdx; + } + + for( const pxr::UsdGeomSubset &geomSubset : geomSubsets ) + { + Canceller::check( canceller ); + geomSubsetNames[geomSubsetIdx] = geomSubset.GetPrim().GetName().GetString(); + pxr::UsdAttribute indicesAttr = geomSubset.GetIndicesAttr(); + pxr::VtIntArray geomIndices; + indicesAttr.Get( &geomIndices, time ); + for( const int &idx : geomIndices ) + { + geomSubsetIndices[idx] = geomSubsetIdx; + } + ++geomSubsetIdx; + } +} + } // namespace void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPrimvarsAPI &primvarsAPI, pxr::UsdTimeCode time, IECoreScene::Primitive *primitive, const Canceller *canceller ) @@ -562,7 +714,72 @@ void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPrimvar primitive->variables.erase( it ); } } +} + +void IECoreUSD::PrimitiveAlgo::readGeomSubsets( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time, IECoreScene::Primitive *primitive, const Canceller *canceller ) +{ + // We're only interested in a subset of element types based on UsdGeom types. + const pxr::TfToken elementType = toUSDElementType( pointBased.GetPrim() ); + + if( elementType.IsEmpty() ) + { + if( pxr::UsdGeomSubset::GetGeomSubsets( pointBased ).size() ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "Prim {} with UsdGeomSubsets has unsupported elementType - skipping", pointBased.GetPrim().GetName().GetString() ); + } + return; + } + + // Collect all valid family names. + std::set validGeomSubsetFamilyNames; + // UsdGeomSubsets without a familyName always default to familyType unrestricted so we don't bother accessing those. + for( const auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pointBased, elementType ) ) + { + if( !geomSubset.GetFamilyNameAttr().HasAuthoredValue() ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "Prim {} has UsdGeomSubsets without familyName - skipping", pointBased.GetPrim().GetName().GetString() ); + break; + } + } + + for( const auto &familyName : pxr::UsdGeomSubset::GetAllGeomSubsetFamilyNames( pointBased ) ) + { + std::string reason; + pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pointBased, familyName ); + if( familyType == pxr::UsdGeomTokens->unrestricted ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "UsdGeomSubset family: {} familyType: {} not supported - skipping", familyName.GetString(), familyType.GetString() ); + continue; + } + else if( !pxr::UsdGeomSubset::ValidateFamily( pointBased, elementType, familyName, &reason ) ) + { + IECore::msg( IECore::MessageHandler::Level::Warning, "IECoreUSD::PrimitiveAlgo", "UsdGeomSubset family: {} not supported - {}", familyName.GetString(), reason ); + continue; + } + else + { + validGeomSubsetFamilyNames.emplace( familyName ); + } + } + + // Convert family GeomSubsets to indexed string primitive variables under geomSubset or geomSubset:. + for( const auto &familyName : validGeomSubsetFamilyNames ) + { + Canceller::check( canceller ); + + std::vector geomSubsetNames; + std::vector geomSubsetIndices; + readGeomSubsetNamesAndIndices( pointBased, elementType, familyName, time, primitive, geomSubsetNames, geomSubsetIndices, canceller ); + + const std::string geomSubsetPrimvarName = std::string( "geomSubset:" ) + familyName.GetString(); + + const StringVectorDataPtr data = new StringVectorData( geomSubsetNames ); + const IntVectorDataPtr indices = new IntVectorData( geomSubsetIndices ); + + // Note: Using the pointsBased points attribute for the error print log. + addPrimitiveVariableIfValid( primitive, geomSubsetPrimvarName, IECoreScene::PrimitiveVariable( IECoreUSD::PrimitiveAlgo::elementTypeFromUSD( elementType ), data, indices ), pointBased.GetPointsAttr() ); + } } void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time, IECoreScene::Primitive *primitive, const Canceller *canceller ) @@ -589,6 +806,8 @@ void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPointBa Canceller::check( canceller ); readPrimitiveVariable( pointBased.GetAccelerationsAttr(), time, primitive, "acceleration" ); + + readGeomSubsets( pointBased, time, primitive, canceller ); } void IECoreUSD::PrimitiveAlgo::readPrimitiveVariable( const pxr::UsdAttribute &attribute, pxr::UsdTimeCode timeCode, IECoreScene::Primitive *primitive, const std::string &name, IECoreScene::PrimitiveVariable::Interpolation interpolation ) @@ -619,7 +838,8 @@ bool IECoreUSD::PrimitiveAlgo::primitiveVariablesMightBeTimeVarying( const pxr:: pointBased.GetVelocitiesAttr().ValueMightBeTimeVarying() || pointBased.GetAccelerationsAttr().ValueMightBeTimeVarying() || primitiveVariablesMightBeTimeVarying( pxr::UsdGeomPrimvarsAPI( pointBased.GetPrim() ) ) || - skelAnimMightBeTimeVarying( pointBased.GetPrim() ) + skelAnimMightBeTimeVarying( pointBased.GetPrim() ) || + geomSubsetsMightBeTimeVarying( pointBased ) ; } @@ -648,3 +868,12 @@ IECoreScene::PrimitiveVariable::Interpolation IECoreUSD::PrimitiveAlgo::fromUSD( return IECoreScene::PrimitiveVariable::Invalid; } + +IECoreScene::PrimitiveVariable::Interpolation IECoreUSD::PrimitiveAlgo::elementTypeFromUSD( pxr::TfToken elementType ) +{ + if( elementType == pxr::UsdGeomTokens->face ) + { + return IECoreScene::PrimitiveVariable::Uniform; + } + return IECoreScene::PrimitiveVariable::Invalid; +} diff --git a/contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp b/contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp index 24dbfcab1e..4a7450b0de 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp @@ -63,6 +63,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY #include "pxr/usd/usdGeom/primvar.h" #include "pxr/usd/usdGeom/primvarsAPI.h" #include "pxr/usd/usdGeom/scope.h" +#include "pxr/usd/usdGeom/subset.h" #include "pxr/usd/usdGeom/tokens.h" #include "pxr/usd/usdGeom/xform.h" #include "pxr/usd/usdLux/lightAPI.h" @@ -493,26 +494,45 @@ void populateMaterial( pxr::UsdShadeMaterial &mat, const boost::container::flat_ } } -std::tuple materialOutputAndPurpose( const std::string &attributeName ) +std::tuple materialOutputAndPurpose( const std::string &attributeName ) { + std::string attrName = attributeName; + pxr::TfToken geomSubset = pxr::TfToken(); + size_t pos = attributeName.find( "geomSubset:" ); + if( pos != std::string::npos ) + { + attrName = attributeName.substr(0, pos - 1); + geomSubset = pxr::TfToken( attributeName.substr( pos + 11 ) ); + } + for( const auto &purpose : { pxr::UsdShadeTokens->preview, pxr::UsdShadeTokens->full } ) { if( - boost::ends_with( attributeName, purpose.GetString() ) && - attributeName.size() > purpose.GetString().size() + boost::ends_with( attrName, purpose.GetString() ) && + attrName.size() > purpose.GetString().size() ) { - size_t colonIndex = attributeName.size() - purpose.GetString().size() - 1; - if( attributeName[colonIndex] == ':' ) + size_t colonIndex = attrName.size() - purpose.GetString().size() - 1; + if( attrName[colonIndex] == ':' ) { return std::make_tuple( - AttributeAlgo::nameToUSD( attributeName.substr( 0, colonIndex ) ).name, - pxr::TfToken( attributeName.substr( colonIndex + 1 ) ) + AttributeAlgo::nameToUSD( attrName.substr( 0, colonIndex ) ).name, + pxr::TfToken( attrName.substr( colonIndex + 1 ) ), + geomSubset ); } } } - return { AttributeAlgo::nameToUSD( attributeName ).name, pxr::UsdShadeTokens->allPurpose }; + return { AttributeAlgo::nameToUSD( attrName ).name, pxr::UsdShadeTokens->allPurpose, geomSubset }; +} + +pxr::TfToken geomSubsetElementTypeForMaterial( const pxr::UsdPrim &prim ) +{ + if( prim.IsA() ) + { + return pxr::UsdGeomTokens->face; + } + return pxr::TfToken(); } using ShaderNetworkCacheKey = std::pair; @@ -927,30 +947,50 @@ USDScene::~USDScene() materialContainer.GetPrim().SetMetadata( g_metadataAutoMaterials, true ); } - for( const auto &[purpose, material] : m_materials ) + for( const auto &[purpose, materialSubsets] : m_materials ) { - // Use a hash to identify the combination of shaders in this material. - IECore::MurmurHash materialHash; - for( const auto &[output, shaderNetwork] : material ) + for( const auto &[materialSubset, material] : materialSubsets ) { - materialHash.append( output ); - materialHash.append( shaderNetwork->Object::hash() ); - } - pxr::TfToken matName( "material_" + materialHash.toString() ); + // Use a hash to identify the combination of shaders in this material. + IECore::MurmurHash materialHash; + for( const auto &[output, shaderNetwork] : material ) + { + materialHash.append( output ); + materialHash.append( shaderNetwork->Object::hash() ); + } + pxr::TfToken matName( "material_" + materialHash.toString() ); - // Write the material if it hasn't been written already. - pxr::SdfPath matPath = materialContainer.GetPrim().GetPath().AppendChild( matName ); - pxr::UsdShadeMaterial mat = pxr::UsdShadeMaterial::Get( materialContainer.GetPrim().GetStage(), matPath ); - if( !mat ) - { - mat = pxr::UsdShadeMaterial::Define( materialContainer.GetPrim().GetStage(), matPath ); - populateMaterial( mat, material ); - } + // Write the material if it hasn't been written already. + pxr::SdfPath matPath = materialContainer.GetPrim().GetPath().AppendChild( matName ); + pxr::UsdShadeMaterial mat = pxr::UsdShadeMaterial::Get( materialContainer.GetPrim().GetStage(), matPath ); + if( !mat ) + { + mat = pxr::UsdShadeMaterial::Define( materialContainer.GetPrim().GetStage(), matPath ); + populateMaterial( mat, material ); + } - // Bind the material to this location - pxr::UsdShadeMaterialBindingAPI::Apply( m_location->prim ).Bind( - mat, pxr::UsdShadeTokens->fallbackStrength, purpose - ); + // Bind the material to GeomSubset or directly to this location + if( materialSubset.IsEmpty() ) + { + pxr::UsdShadeMaterialBindingAPI::Apply( m_location->prim ).Bind( + mat, pxr::UsdShadeTokens->fallbackStrength, purpose + ); + } + else + { + pxr::TfToken elementType = geomSubsetElementTypeForMaterial( m_location->prim ); + for( auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pxr::UsdGeomImageable( m_location->prim ), elementType, pxr::UsdShadeTokens->materialBind ) ) + { + if( geomSubset.GetPrim().GetName() == materialSubset ) + { + pxr::UsdShadeMaterialBindingAPI::Apply( geomSubset.GetPrim() ).Bind( + mat, pxr::UsdShadeTokens->fallbackStrength, purpose + ); + break; + } + } + } + } } } catch( std::exception &e ) @@ -1114,14 +1154,38 @@ bool USDScene::hasAttribute( const SceneInterface::Name &name ) const } else { - const auto &[output, purpose] = materialOutputAndPurpose( name.string() ); - if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( m_location->prim, purpose ) ) + const auto &[output, purpose, geomSubsetName] = materialOutputAndPurpose( name.string() ); + if( !geomSubsetName.IsEmpty() ) + { + pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pxr::UsdGeomImageable( m_location->prim ), pxr::UsdShadeTokens->materialBind ); + if( familyType == pxr::UsdGeomTokens->unrestricted ) + { + return false; + } + pxr::TfToken elementType = geomSubsetElementTypeForMaterial( m_location->prim ); + for( auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pxr::UsdGeomImageable( m_location->prim ), elementType, pxr::UsdShadeTokens->materialBind ) ) + { + if( geomSubset.GetPrim().GetName() == geomSubsetName ) + { + if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( geomSubset.GetPrim(), purpose ) ) + { + if( pxr::UsdShadeOutput o = mat.GetOutput( output ) ) + { + return ShaderAlgo::canReadShaderNetwork( o ); + } + } + break; + } + } + } + else if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( m_location->prim, purpose ) ) { if( pxr::UsdShadeOutput o = mat.GetOutput( output ) ) { return ShaderAlgo::canReadShaderNetwork( o ); } } + return false; } } @@ -1193,6 +1257,31 @@ void USDScene::attributeNames( SceneInterface::NameList &attrs ) const attrs.push_back( attrName ); } } + pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pxr::UsdGeomImageable( m_location->prim ), pxr::UsdShadeTokens->materialBind ); + if( familyType != pxr::UsdGeomTokens->unrestricted ) + { + pxr::TfToken elementType = geomSubsetElementTypeForMaterial( m_location->prim ); + for( auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pxr::UsdGeomImageable( m_location->prim ), elementType, pxr::UsdShadeTokens->materialBind ) ) + { + if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( geomSubset.GetPrim(), purpose ) ) + { + for( pxr::UsdShadeOutput &o : mat.GetOutputs( /* onlyAuthored = */ true ) ) + { + if( !ShaderAlgo::canReadShaderNetwork( o ) ) + { + continue; + } + InternedString attrName = AttributeAlgo::nameFromUSD( { o.GetBaseName() , false } ); + if( !purpose.IsEmpty() ) + { + attrName = attrName.string() + ":" + purpose.GetString(); + } + attrName = attrName.string() + ":geomSubset:" + geomSubset.GetPrim().GetName().GetString(); + attrs.push_back( attrName ); + } + } + } + } } } @@ -1273,8 +1362,31 @@ ConstObjectPtr USDScene::readAttribute( const SceneInterface::Name &name, double } else { - const auto &[output, purpose] = materialOutputAndPurpose( name.string() ); - if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( m_location->prim, purpose ) ) + const auto &[output, purpose, geomSubsetName] = materialOutputAndPurpose( name.string() ); + if( !geomSubsetName.IsEmpty() ) + { + pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pxr::UsdGeomImageable( m_location->prim ), pxr::UsdShadeTokens->materialBind ); + if( familyType == pxr::UsdGeomTokens->unrestricted ) + { + return nullptr; + } + pxr::TfToken elementType = geomSubsetElementTypeForMaterial( m_location->prim ); + for( auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pxr::UsdGeomImageable( m_location->prim ), elementType, pxr::UsdShadeTokens->materialBind ) ) + { + if( geomSubset.GetPrim().GetName() == geomSubsetName ) + { + if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( geomSubset.GetPrim(), purpose ) ) + { + if( pxr::UsdShadeOutput o = mat.GetOutput( output ) ) + { + return m_root->readShaderNetwork( o, m_root->timeCode( time ) ); + } + } + break; + } + } + } + else if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( m_location->prim, purpose ) ) { if( pxr::UsdShadeOutput o = mat.GetOutput( output ) ) { @@ -1350,8 +1462,8 @@ void USDScene::writeAttribute( const SceneInterface::Name &name, const Object *a } else { - const auto &[output, purpose] = materialOutputAndPurpose( name.string() ); - m_materials[purpose][output] = shaderNetwork; + const auto &[output, purpose, geomSubset] = materialOutputAndPurpose( name.string() ); + m_materials[purpose][geomSubset][output] = shaderNetwork; } } else if( name.string() == "gaffer:globals" ) @@ -1761,6 +1873,22 @@ void USDScene::attributesHash( double time, IECore::MurmurHash &h ) const } haveMaterials = true; } + pxr::TfToken familyType = pxr::UsdGeomSubset::GetFamilyType( pxr::UsdGeomImageable( m_location->prim ), pxr::UsdShadeTokens->materialBind ); + if( familyType != pxr::UsdGeomTokens->unrestricted ) + { + for( auto &geomSubset : pxr::UsdGeomSubset::GetGeomSubsets( pxr::UsdGeomImageable( m_location->prim ), geomSubsetElementTypeForMaterial( m_location->prim ), pxr::UsdShadeTokens->materialBind ) ) + { + if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( geomSubset.GetPrim(), purpose ) ) + { + append( mat.GetPrim().GetPath(), h ); + for( pxr::UsdShadeOutput &o : mat.GetOutputs( /* onlyAuthored = */ true ) ) + { + mightBeTimeVarying = mightBeTimeVarying || m_root->shaderNetworkMightBeTimeVarying( o ); + } + } + haveMaterials = true; + } + } } if( haveAttributes || haveMaterials ) diff --git a/contrib/IECoreUSD/src/IECoreUSD/USDScene.h b/contrib/IECoreUSD/src/IECoreUSD/USDScene.h index cee778e1ec..8fc8687b44 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/USDScene.h +++ b/contrib/IECoreUSD/src/IECoreUSD/USDScene.h @@ -123,8 +123,10 @@ class USDScene : public IECoreScene::SceneInterface // Contains all the shader networks for a single material, mapping from material output // (e.g. "surface", "displacement" etc) to the shading network that drives that output. using MaterialNetworks = boost::container::flat_map; + // Contains the materials to be bound for a geom subset. + using MaterialSubset = boost::container::flat_map; // Contains the materials to be bound for this location, indexed by purpose. - using Materials = boost::container::flat_map; + using Materials = boost::container::flat_map; Materials m_materials; }; diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 0a6c86995e..31a5c5ae54 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -4798,5 +4798,149 @@ def testMaterialTerminalFromSubgraph( self ) : self.assertEqual( shaderNetwork.outputShader().name, "LamaSurface" ) + def testReadGeomSubsets( self ) : + + root = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/geomSubset.usda", IECore.IndexedIO.OpenMode.Read ) + + subsetNonOverlapping = root.child( "meshSubsetNonOverlapping" ) + self.assertTrue( subsetNonOverlapping.hasObject() ) + meshSubsetNonOverlapping = subsetNonOverlapping.readObject( 0.0 ) + self.assertTrue( "surface" in subsetNonOverlapping.attributeNames() ) + self.assertTrue( "surface:geomSubset:redFace" in subsetNonOverlapping.attributeNames() ) + self.assertFalse( "surface:geomSubset:greenFace" in subsetNonOverlapping.attributeNames() ) + self.assertTrue( "surface:geomSubset:blueFace" in subsetNonOverlapping.attributeNames() ) + self.assertIsInstance( meshSubsetNonOverlapping["geomSubset:materialBind"].data, IECore.StringVectorData ) + self.assertEqual( meshSubsetNonOverlapping["geomSubset:materialBind"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.Uniform ) + self.assertTrue( meshSubsetNonOverlapping["geomSubset:materialBind"].indices ) + self.assertEqual( meshSubsetNonOverlapping["geomSubset:materialBind"].data, IECore.StringVectorData(["", "redFace", "blueFace"]) ) + self.assertEqual( meshSubsetNonOverlapping["geomSubset:materialBind"].indices, IECore.IntVectorData([1, 1, 0, 0, 2, 2]) ) + # "Fancy" GeomSubset familyName which doesn't do anything for material binds but should come in. + self.assertEqual( meshSubsetNonOverlapping["geomSubset:fancy"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.Uniform ) + self.assertTrue( meshSubsetNonOverlapping["geomSubset:fancy"].indices ) + self.assertEqual( meshSubsetNonOverlapping["geomSubset:fancy"].data, IECore.StringVectorData(["fancy1", "fancy2"]) ) + self.assertEqual( meshSubsetNonOverlapping["geomSubset:fancy"].indices, IECore.IntVectorData([0, 1, 1, 0, 1, 0]) ) + # GeomSubsets without a familyName shouldn't come through as they're always set to "unrestricted" familyType + self.assertFalse( "geomSubset" in meshSubsetNonOverlapping ) + + subsetPartition = root.child( "meshSubsetPartition" ) + self.assertTrue( subsetPartition.hasObject() ) + meshSubsetPartition = subsetPartition.readObject( 0.0 ) + self.assertFalse( "surface" in subsetPartition.attributeNames() ) + self.assertTrue( "surface:geomSubset:redFace" in subsetPartition.attributeNames() ) + self.assertTrue( "surface:geomSubset:greenFace" in subsetPartition.attributeNames() ) + self.assertTrue( "surface:geomSubset:blueFace" in subsetPartition.attributeNames() ) + self.assertIsInstance( meshSubsetPartition["geomSubset:materialBind"].data, IECore.StringVectorData ) + self.assertEqual( meshSubsetPartition["geomSubset:materialBind"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.Uniform ) + self.assertTrue( meshSubsetPartition["geomSubset:materialBind"].indices ) + self.assertEqual( meshSubsetPartition["geomSubset:materialBind"].data, IECore.StringVectorData(["redFace", "greenFace", "blueFace"]) ) + self.assertEqual( meshSubsetPartition["geomSubset:materialBind"].indices, IECore.IntVectorData([0, 0, 1, 1, 2, 2]) ) + # "Fancy" GeomSubset familyName which doesn't do anything for material binds but should come in. + self.assertEqual( meshSubsetPartition["geomSubset:fancy"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.Uniform ) + self.assertTrue( meshSubsetPartition["geomSubset:fancy"].indices ) + self.assertEqual( meshSubsetPartition["geomSubset:fancy"].data, IECore.StringVectorData(["", "fancy1", "fancy2"]) ) + self.assertEqual( meshSubsetPartition["geomSubset:fancy"].indices, IECore.IntVectorData([1, 2, 2, 1, 0, 0]) ) + # GeomSubsets without a familyName shouldn't come through as they're always set to "unrestricted" familyType + self.assertFalse( "geomSubset" in meshSubsetPartition ) + + # Unsupported familyType "unrestricted" + subsetUnrestricted = root.child( "meshSubsetUnrestricted" ) + self.assertTrue( subsetUnrestricted.hasObject() ) + meshSubsetUnrestricted = subsetUnrestricted.readObject( 0.0 ) + self.assertTrue( "surface" in subsetUnrestricted.attributeNames() ) + self.assertFalse( "surface:geomSubset:redFace" in subsetUnrestricted.attributeNames() ) + self.assertFalse( "surface:geomSubset:greenFace" in subsetUnrestricted.attributeNames() ) + self.assertFalse( "surface:geomSubset:blueFace" in subsetUnrestricted.attributeNames() ) + self.assertFalse( "geomSubset:materialBind" in meshSubsetUnrestricted ) + + def testWriteGeomSubsets( self ) : + + fileName = os.path.join( self.temporaryDirectory(), "test.usda" ) + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write ) + + mats = [] + for c in [ imath.Color3f( 1, 0, 0 ), imath.Color3f( 0, 1, 0 ), imath.Color3f( 0, 0, 1 ) ] : + surface = IECoreScene.Shader( "UsdPreviewSurface", "surface" ) + surface.parameters["diffuseColor"] = IECore.Color3fData( c ) + network = IECoreScene.ShaderNetwork() + network.addShader( "surface", surface ) + network.setOutput( IECoreScene.ShaderNetwork.Parameter( "surface", "surface" ) ) + mats.append( network ) + + meshSubsetNonOverlapping = IECoreScene.MeshPrimitive.createBox( imath.Box3f ( imath.V3f( 0, 0, 0 ), imath.V3f( 1, 1, 1 ) ) ) + meshSubsetNonOverlapping["geomSubset:materialBind"] = IECoreScene.PrimitiveVariable( + IECoreScene.PrimitiveVariable.Interpolation.Uniform, + IECore.StringVectorData( ["", "redFace", "blueFace"] ), + IECore.IntVectorData( [ 1, 1, 0, 0, 2, 2 ] ) + ) + subsetNonOverlapping = root.createChild( "meshSubsetNonOverlapping" ) + subsetNonOverlapping.writeObject( meshSubsetNonOverlapping, 0 ) + subsetNonOverlapping.writeAttribute( "surface", mats[1], 0 ) + subsetNonOverlapping.writeAttribute( "surface:geomSubset:redFace", mats[0], 0 ) + subsetNonOverlapping.writeAttribute( "surface:geomSubset:blueFace", mats[2], 0 ) + + meshSubsetPartition = IECoreScene.MeshPrimitive.createBox( imath.Box3f ( imath.V3f( 0, 0, 0 ), imath.V3f( 1, 1, 1 ) ) ) + meshSubsetPartition["geomSubset:materialBind"] = IECoreScene.PrimitiveVariable( + IECoreScene.PrimitiveVariable.Interpolation.Uniform, + IECore.StringVectorData( ["redFace", "greenFace", "blueFace"] ), + IECore.IntVectorData( [ 0, 0, 1, 1, 2, 2 ] ) + ) + subsetPartition = root.createChild( "meshSubsetPartition" ) + subsetPartition.writeObject( meshSubsetPartition, 0 ) + subsetPartition.writeAttribute( "surface:geomSubset:redFace", mats[0], 0 ) + subsetPartition.writeAttribute( "surface:geomSubset:greenFace", mats[1], 0 ) + subsetPartition.writeAttribute( "surface:geomSubset:blueFace", mats[2], 0 ) + + del root, subsetNonOverlapping, subsetPartition + + # Now read in the UsdGeomSubsets + + stage = pxr.Usd.Stage.Open( fileName ) + + primSubsetNonOverlapping = stage.GetPrimAtPath( "/meshSubsetNonOverlapping" ) + imageableSubsetNonOverlapping = pxr.UsdGeom.Imageable.Get( stage, primSubsetNonOverlapping.GetPath() ) + primvarsAPI = pxr.UsdGeom.PrimvarsAPI( primSubsetNonOverlapping ) + self.assertFalse( primvarsAPI.GetPrimvar( "geomSubset:materialBind" ) ) + subsets = pxr.UsdGeom.Subset.GetGeomSubsets( imageableSubsetNonOverlapping, pxr.UsdGeom.Tokens.face, pxr.UsdShade.Tokens.materialBind ) + self.assertEqual( len( subsets ), 2 ) + self.assertEqual( subsets[0].GetPrim().GetName(), "redFace" ) + self.assertEqual( subsets[1].GetPrim().GetName(), "blueFace" ) + for subset in subsets : + self.assertTrue( subset.GetIndicesAttr().HasAuthoredValue() ) + self.assertIsInstance( pxr.UsdShade.MaterialBindingAPI.ComputeBoundMaterials( [ subset.GetPrim() ] )[0][0], pxr.UsdShade.Material ) + self.assertEqual( subset.GetElementTypeAttr().Get( 0.0 ), pxr.UsdGeom.Tokens.face ) + self.assertEqual( subset.GetFamilyNameAttr().Get( 0.0 ), pxr.UsdShade.Tokens.materialBind ) + self.assertIsInstance( pxr.UsdShade.MaterialBindingAPI.ComputeBoundMaterials( [ primSubsetNonOverlapping ] )[0][0], pxr.UsdShade.Material ) + self.assertEqual( subsets[0].GetIndicesAttr().Get( 0.0 ), pxr.Vt.IntArray( [0, 1] ) ) + self.assertEqual( subsets[1].GetIndicesAttr().Get( 0.0 ), pxr.Vt.IntArray( [4, 5] ) ) + self.assertEqual( pxr.UsdGeom.Subset.GetFamilyType( imageableSubsetNonOverlapping, pxr.UsdShade.Tokens.materialBind ), pxr.UsdGeom.Tokens.nonOverlapping ) + + primSubsetPartition = stage.GetPrimAtPath( "/meshSubsetPartition" ) + imageableSubsetPartition = pxr.UsdGeom.Imageable.Get( stage, primSubsetPartition.GetPath() ) + primvarsAPI = pxr.UsdGeom.PrimvarsAPI( primSubsetPartition ) + self.assertFalse( primvarsAPI.GetPrimvar( "geomSubset:materialBind" ) ) + subsets = pxr.UsdGeom.Subset.GetGeomSubsets( imageableSubsetPartition, pxr.UsdGeom.Tokens.face, pxr.UsdShade.Tokens.materialBind ) + self.assertEqual( len( subsets ), 3 ) + self.assertEqual( subsets[0].GetPrim().GetName(), "redFace" ) + self.assertEqual( subsets[1].GetPrim().GetName(), "greenFace" ) + self.assertEqual( subsets[2].GetPrim().GetName(), "blueFace" ) + for subset in subsets : + self.assertTrue( subset.GetIndicesAttr().HasAuthoredValue() ) + self.assertIsInstance( pxr.UsdShade.MaterialBindingAPI.ComputeBoundMaterials( [ subset.GetPrim() ] )[0][0], pxr.UsdShade.Material ) + self.assertEqual( subset.GetElementTypeAttr().Get( 0.0 ), pxr.UsdGeom.Tokens.face ) + self.assertEqual( subset.GetFamilyNameAttr().Get( 0.0 ), pxr.UsdShade.Tokens.materialBind ) + self.assertEqual( subsets[0].GetIndicesAttr().Get( 0.0 ), pxr.Vt.IntArray( [0, 1] ) ) + self.assertEqual( subsets[1].GetIndicesAttr().Get( 0.0 ), pxr.Vt.IntArray( [2, 3] ) ) + self.assertEqual( subsets[2].GetIndicesAttr().Get( 0.0 ), pxr.Vt.IntArray( [4, 5] ) ) + self.assertEqual( pxr.UsdGeom.Subset.GetFamilyType( imageableSubsetPartition, pxr.UsdShade.Tokens.materialBind ), pxr.UsdGeom.Tokens.partition ) + + # And that we can load them back in successfully. + + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) + meshSubsetNonOverlapping2 = root.child( "meshSubsetNonOverlapping" ).readObject( 0.0 ) + meshSubsetPartition2 = root.child( "meshSubsetPartition" ).readObject( 0.0 ) + self.assertEqual( meshSubsetNonOverlapping, meshSubsetNonOverlapping2 ) + self.assertEqual( meshSubsetPartition, meshSubsetPartition2 ) + + if __name__ == "__main__": unittest.main() diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/geomSubset.usda b/contrib/IECoreUSD/test/IECoreUSD/data/geomSubset.usda new file mode 100644 index 0000000000..e7fd64e146 --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/geomSubset.usda @@ -0,0 +1,228 @@ +#usda 1.0 +( + defaultPrim = "meshSubsetNonOverlapping" + endTimeCode = 1 + startTimeCode = 1 + upAxis = "Y" +) + +def Mesh "meshSubsetNonOverlapping" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + + rel material:binding = + + uniform token subsetFamily:materialBind:familyType = "nonOverlapping" + + uniform token subsetFamily:fancy:familyType = "partition" + + def GeomSubset "redFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [0, 1] + rel material:binding = + } + + def GeomSubset "blueFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [4, 5] + rel material:binding = + } + + def GeomSubset "noFamily1" + { + uniform token elementType = "face" + int[] indices = [0, 1] + } + + def GeomSubset "noFamily2" + { + uniform token elementType = "face" + int[] indices = [4, 5] + } + + def GeomSubset "fancy1" + { + uniform token elementType = "face" + uniform token familyName = "fancy" + int[] indices = [0, 3, 5] + } + + def GeomSubset "fancy2" + { + uniform token elementType = "face" + uniform token familyName = "fancy" + int[] indices = [1, 2, 4] + } +} + +def Mesh "meshSubsetPartition" +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + + uniform token subsetFamily:materialBind:familyType = "partition" + + uniform token subsetFamily:fancy:familyType = "nonOverlapping" + + def GeomSubset "redFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [0, 1] + rel material:binding = + } + + def GeomSubset "greenFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [2, 3] + rel material:binding = + } + + def GeomSubset "blueFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [4, 5] + rel material:binding = + } + + def GeomSubset "noFamily1" + { + uniform token elementType = "face" + int[] indices = [0, 1, 2, 3] + } + + def GeomSubset "noFamily2" + { + uniform token elementType = "face" + int[] indices = [4, 5] + } + + def GeomSubset "fancy1" + { + uniform token elementType = "face" + uniform token familyName = "fancy" + int[] indices = [0, 3] + } + + def GeomSubset "fancy2" + { + uniform token elementType = "face" + uniform token familyName = "fancy" + int[] indices = [1, 2] + } +} + +def Mesh "meshSubsetUnrestricted" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + + rel material:binding = + + uniform token subsetFamily:materialBind:familyType = "unrestricted" + + def GeomSubset "redFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [0, 1, 2, 3] + rel material:binding = + } + + def GeomSubset "greenFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [2, 3] + rel material:binding = + } + + def GeomSubset "blueFace" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [2, 3, 4, 5] + rel material:binding = + } +} + +def Scope "materials" ( + cortex_autoMaterials = true +) +{ + def Material "redMat" + { + + token outputs:surface.connect = + + def Shader "surface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor = (1.0, 0.0, 0.0) + token outputs:surface + } + } + + def Material "greenMat" + { + + token outputs:surface.connect = + + def Shader "surface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor = (0.0, 1.0, 0.0) + token outputs:surface + } + } + + def Material "blueMat" + { + + token outputs:surface.connect = + + def Shader "surface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor = (0.0, 0.0, 1.0) + token outputs:surface + } + } +}