3636#include " boost/algorithm/string/erase.hpp"
3737#include " boost/algorithm/string/replace.hpp"
3838#include " boost/algorithm/string/predicate.hpp"
39+ #include " boost/container/flat_set.hpp"
40+ #include " boost/container/small_vector.hpp"
3941
4042IECORE_PUSH_DEFAULT_VISIBILITY
4143#include " pxr/usd/usdGeom/primvar.h"
@@ -50,14 +52,15 @@ using namespace pxr;
5052namespace
5153{
5254
53- static const pxr::TfToken g_cortexPrimitiveVariableMetadataToken ( " cortex_isConstantPrimitiveVariable" );
54- static const pxr::TfToken g_cortexPrimitiveVariableMetadataTokenDeprecated ( " IECOREUSD_CONSTANT_PRIMITIVE_VARIABLE" );
55- static const std::string g_primVarPrefix = " primvars:" ;
56- static const std::string g_primVarUserPrefix = " primvars:user:" ;
57- static const std::string g_renderPrefix = " render:" ;
58- static const std::string g_riPrefix = " ri:" ;
59- static const std::string g_riAttributesPrefix = " ri:attributes:" ;
60- static const std::string g_userPrefix = " user:" ;
55+ const pxr::TfToken g_cortexPrimitiveVariableMetadataToken ( " cortex_isConstantPrimitiveVariable" );
56+ const pxr::TfToken g_cortexPrimitiveVariableMetadataTokenDeprecated ( " IECOREUSD_CONSTANT_PRIMITIVE_VARIABLE" );
57+ const std::string g_primVarPrefix = " primvars:" ;
58+ const std::string g_primVarUserPrefix = " primvars:user:" ;
59+ const std::string g_renderPrefix = " render:" ;
60+ const std::string g_riPrefix = " ri:" ;
61+ const std::string g_riAttributesPrefix = " ri:attributes:" ;
62+ const std::string g_userPrefix = " user:" ;
63+ const boost::container::flat_set<std::string> g_shaderTypes = { " surface" , " displacement" , " light" , " volume" };
6164
6265bool writeConformantRenderManAttributes ()
6366{
@@ -68,8 +71,93 @@ bool writeConformantRenderManAttributes()
6871 return false ;
6972}
7073
74+ using Names = boost::container::small_vector<AttributeAlgo::Name, 2 >;
75+
76+ // Given a Cortex attribute name, return the ways it might be represented in
77+ // USD. This is a one-to-many mapping, to account for legacy representations
78+ // in old files. The first name returned should be used for writing new files,
79+ // and the other names are used as fallbacks when reading.
80+ Names usdNames ( std::string name )
81+ {
82+ if ( boost::starts_with ( name, g_riPrefix ) )
83+ {
84+ const std::string potentialShaderType = name.substr ( g_riPrefix.size () );
85+ if ( g_shaderTypes.count ( potentialShaderType ) )
86+ {
87+ return { { pxr::TfToken ( name ), true } };
88+ }
89+ const AttributeAlgo::Name conformantName = { pxr::TfToken ( g_riAttributesPrefix + name.substr ( g_riPrefix.size () ) ), true };
90+ const AttributeAlgo::Name legacyName = { pxr::TfToken ( name ), false };
91+ if ( writeConformantRenderManAttributes () )
92+ {
93+ return { conformantName, legacyName };
94+ }
95+ else
96+ {
97+ return { legacyName, conformantName };
98+ }
99+ }
100+
101+ bool isPrimvar = false ;
102+
103+ // The long term plan is to convert only "render:" prefixed attributes to primvars, and it will
104+ // be the client's responsibility to ensure everything important gets prefixed with "render:".
105+ // But for the moment, Gaffer doesn't do this yet, so we support the two most important prefixes
106+ // for Gaffer currently: "user:" and "ai:".
107+ // / \todo I don't think the `render:` plan is working out - it may well be better to just map
108+ // / all Cortex attributes to primvars.
109+ if ( boost::starts_with ( name, " render:" ) || boost::starts_with ( name, " user:" ) || boost::starts_with ( name, " ai:" ) )
110+ {
111+ isPrimvar = true ;
112+
113+ // Strip the "render:" prefix from when writing attributes as primitive variables
114+ if ( boost::starts_with ( name, g_renderPrefix ) )
115+ {
116+ name = name.substr ( 7 );
117+ }
118+ }
119+
120+ if ( name == " ai:disp_map" )
121+ {
122+ // Special case where the whole name is different, not just prefix
123+ name = " arnold:displacement" ;
124+ }
125+ else
126+ {
127+ size_t colonPos = name.find ( " :" );
128+ if ( colonPos != std::string::npos )
129+ {
130+ std::string prefix = name.substr ( 0 , colonPos );
131+ std::string newPrefix;
132+ // Translate prefixes. Currently ai -> arnold is the only mapping supported
133+ if ( prefix == " ai" )
134+ {
135+ newPrefix = " arnold" ;
136+ }
137+
138+ if ( newPrefix.size () )
139+ {
140+ name = newPrefix + name.substr ( colonPos );
141+ }
142+ }
143+ }
144+
145+ Names result;
146+ if ( isPrimvar )
147+ {
148+ result.push_back ( { TfToken ( name ), true } );
149+ }
150+ if ( name.find ( ' :' ) != std::string::npos )
151+ {
152+ // We add this one even when the primary version is a primvar, as a
153+ // fallback to legacy files from a time when we wrote attributes.
154+ result.push_back ( { TfToken ( name ), false } );
155+ }
156+ return result;
71157}
72158
159+ } // namespace
160+
73161bool IECoreUSD::AttributeAlgo::isCortexAttribute ( const pxr::UsdGeomPrimvar &primVar )
74162{
75163 if ( primVar.GetInterpolation () != pxr::UsdGeomTokens->constant )
@@ -133,56 +221,16 @@ pxr::TfToken IECoreUSD::AttributeAlgo::cortexPrimitiveVariableMetadataTokenDepre
133221
134222IECoreUSD::AttributeAlgo::Name IECoreUSD::AttributeAlgo::nameToUSD ( std::string name )
135223{
136- if ( boost::starts_with ( name, g_riPrefix ) && writeConformantRenderManAttributes () )
137- {
138- return { pxr::TfToken ( g_riAttributesPrefix + name.substr ( g_riPrefix.size () ) ), true };
139- }
140-
141- bool isPrimvar = false ;
142-
143- // The long term plan is to convert only "render:" prefixed attributes to primvars, and it will
144- // be the client's responsibility to ensure everything important gets prefixed with "render:".
145- // But for the moment, Gaffer doesn't do this yet, so we support the two most important prefixes
146- // for Gaffer currently: "user:" and "ai:".
147- // / \todo I don't think the `render:` plan is working out - it may well be better to just map
148- // / all Cortex attributes to primvars.
149- if ( boost::starts_with ( name, " render:" ) || boost::starts_with ( name, " user:" ) || boost::starts_with ( name, " ai:" ) )
224+ const Names names = usdNames ( name );
225+ if ( names.size () )
150226 {
151- isPrimvar = true ;
152-
153- // Strip the "render:" prefix from when writing attributes as primitive variables
154- if ( boost::starts_with ( name, g_renderPrefix ) )
155- {
156- name = name.substr ( 7 );
157- }
227+ return names.front ();
158228 }
159-
160- if ( name == " ai:disp_map" )
161- {
162- // Special case where the whole name is different, not just prefix
163- name = " arnold:displacement" ;
164- }
165- else
166- {
167- size_t colonPos = name.find ( " :" );
168- if ( colonPos != std::string::npos )
169- {
170- std::string prefix = name.substr ( 0 , colonPos );
171- std::string newPrefix;
172- // Translate prefixes. Currently ai -> arnold is the only mapping supported
173- if ( prefix == " ai" )
174- {
175- newPrefix = " arnold" ;
176- }
177-
178- if ( newPrefix.size () )
179- {
180- name = newPrefix + name.substr ( colonPos );
181- }
182- }
183- }
184-
185- return { TfToken ( name ), isPrimvar };
229+ // This is necessary because `USDScene` calls `nameToUSD()` when writing
230+ // materials, and `surface` etc aren't handled by the above.
231+ // / \todo It would likely be better if the material-writing code wasn't
232+ // / mixed up with this.
233+ return { pxr::TfToken ( name ), false };
186234}
187235
188236IECore::InternedString IECoreUSD::AttributeAlgo::nameFromUSD ( IECoreUSD::AttributeAlgo::Name name )
@@ -234,27 +282,27 @@ IECore::InternedString IECoreUSD::AttributeAlgo::nameFromUSD( IECoreUSD::Attribu
234282
235283UsdAttribute IECoreUSD::AttributeAlgo::findUSDAttribute ( const pxr::UsdPrim &prim, std::string cortexName )
236284{
237- AttributeAlgo::Name n = AttributeAlgo::nameToUSD ( cortexName );
238- if ( n.isPrimvar )
285+ for ( const auto &n : usdNames ( cortexName ) )
239286 {
240- if ( pxr::UsdGeomPrimvar primvar = pxr::UsdGeomPrimvarsAPI ( prim ). GetPrimvar ( n. name ) )
287+ if ( n. isPrimvar )
241288 {
242- if ( isCortexAttribute ( primvar ) )
289+ if ( pxr::UsdGeomPrimvar primvar = pxr::UsdGeomPrimvarsAPI ( prim ). GetPrimvar ( n. name ) )
243290 {
244- return primvar.GetAttr ();
291+ if ( isCortexAttribute ( primvar ) )
292+ {
293+ return primvar.GetAttr ();
294+ }
245295 }
246296 }
247- }
248-
249- // In theory, this should be able to be an else. But for the moment, for attributes that should be written
250- // to a primvar, we try reading them from an attribute if we can't find them in a primvar. This provides
251- // some backwards compatibility with files from before we started writing to primvars, and might provide
252- // compatibility with other USD authors, maybe?
253- if ( pxr::UsdAttribute attribute = prim.GetAttribute ( n.name ) )
254- {
255- if ( attribute.GetName ().GetString ().find ( " :" ) != std::string::npos && attribute.IsCustom () )
297+ else
256298 {
257- return attribute;
299+ if ( pxr::UsdAttribute attribute = prim.GetAttribute ( n.name ) )
300+ {
301+ if ( attribute.IsCustom () )
302+ {
303+ return attribute;
304+ }
305+ }
258306 }
259307 }
260308
0 commit comments