Skip to content

Commit 3c86490

Browse files
authored
Merge pull request #1537 from johnhaddon/firstClassPointInstancer
PointInstancer class
2 parents 1873e36 + 292e7db commit 3c86490

23 files changed

Lines changed: 1016 additions & 85 deletions

Changes

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
10.7.x.x (relative to 10.7.0.0a10)
22
========
33

4+
Features
5+
--------
6+
7+
- PointInstancer : Added class to provide first-class encoding of point instancers.
8+
9+
Improvements
10+
------------
11+
12+
- PrimitiveVariable : Added Python bindings for `IndexedView` class.
13+
- USDScene : Added support for writing `IECoreScene::PointInstancer` as `UsdGeomPointInstancer`.
14+
415
Fixes
516
-----
617

718
- RunTimeTyped : Fixed unnecessary overhead of `runTimeCast()` and `isInstanceOf()` for subclasses implemented in Python. The GIL is now never needed when the cast is to a C++ type.
19+
- PrimitiveVariable : Made IndexedView `begin()` and `end()` const.
20+
- PointsAlgo :
21+
- Fixed loss of blind data in `deletePoints()`.
22+
- Fixed loss of primitive type in `deletePoints()` (if processing a derived class of PointsPrimitive).
823

924
Breaking Changes
1025
----------------
1126

1227
- ClassParameter, ClassVectorParameter : Replaced statically allocated TypeIds with ids allocated dynamically by `IECore.registerRunTimeTyped()`. If the id is needed from C++, it can be queried at runtime with `RunTimeTyped::typeIdFromTypeName()`.
1328
- RunTimeTyped : Removed `typId` argument from `registerRunTimeTyped()` Python function. TypeIds are now always allocated dynamically for Python classes.
29+
- USD :
30+
- Removed support for `IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES` environment variable. Prototype paths are now always specified as relative where possible.
31+
- Removed `./` prefix from relative prototype paths.
32+
- UsdGeomPointInstancer `inactiveIds` are now merged into the `invisibleIds` primitive variable on loading, instead of being loaded separately.
1433

1534
Build
1635
-----

contrib/IECoreUSD/include/IECoreUSD/PrimitiveAlgo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECore
6262
IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECoreScene::PrimitiveVariable &primitiveVariable, const pxr::UsdGeomGprim &gprim, pxr::UsdTimeCode time );
6363
/// As above, but redirects "P", "N" etc to the relevant attributes of `pointBased`.
6464
IECOREUSD_API void writePrimitiveVariable( const std::string &name, const IECoreScene::PrimitiveVariable &primitiveVariable, pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time );
65+
66+
/// Expands an IndexedView into a VtArray.
67+
template<typename T>
68+
IECOREUSD_API pxr::VtValue toUSDExpanded( const IECoreScene::PrimitiveVariable::IndexedView<T> &view );
6569
/// Equivalent to `DataAlgo::toUSD( primitiveVariable.expandedData() )`, but avoiding
6670
/// the creation of the temporary expanded data.
6771
IECOREUSD_API pxr::VtValue toUSDExpanded( const IECoreScene::PrimitiveVariable &primitiveVariable, bool arrayRequired = false );
@@ -89,4 +93,6 @@ IECOREUSD_API IECoreScene::PrimitiveVariable::Interpolation fromUSD( pxr::TfToke
8993

9094
} // namespace IECoreUSD
9195

96+
#include "IECoreUSD/PrimitiveAlgo.inl"
97+
9298
#endif // IECOREUSD_PRIMITIVEALGO_H
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright (c) 2026, Cinesite VFX Ltd. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
//
12+
// * Redistributions in binary form must reproduce the above copyright
13+
// notice, this list of conditions and the following disclaimer in the
14+
// documentation and/or other materials provided with the distribution.
15+
//
16+
// * Neither the name of Image Engine Design nor the names of any
17+
// other contributors to this software may be used to endorse or
18+
// promote products derived from this software without specific prior
19+
// written permission.
20+
//
21+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22+
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23+
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24+
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
25+
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26+
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27+
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28+
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29+
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30+
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31+
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
//
33+
//////////////////////////////////////////////////////////////////////////
34+
35+
#ifndef IECOREUSD_PRIMITIVEALGO_INL
36+
#define IECOREUSD_PRIMITIVEALGO_INL
37+
38+
#include "IECoreUSD/DataAlgo.h"
39+
40+
IECORE_PUSH_DEFAULT_VISIBILITY
41+
#include "pxr/base/gf/quatf.h"
42+
#include "pxr/base/gf/quatd.h"
43+
IECORE_POP_DEFAULT_VISIBILITY
44+
45+
namespace IECoreUSD::PrimitiveAlgo
46+
{
47+
48+
template<typename T>
49+
pxr::VtValue toUSDExpanded( const IECoreScene::PrimitiveVariable::IndexedView<T> &view )
50+
{
51+
using USDType = typename CortexTypeTraits<T>::USDType;
52+
pxr::VtArray<USDType> array;
53+
array.reserve( view.size() );
54+
for( const auto &e : view )
55+
{
56+
array.push_back( DataAlgo::toUSD( static_cast<const T &>( e ) ) );
57+
}
58+
59+
return pxr::VtValue( array );
60+
}
61+
62+
} // namespace IECoreUSD::PrimitiveAlgo
63+
64+
#endif // IECOREUSD_PRIMITIVEALGO_INL

contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@
3636
#include "IECoreUSD/ObjectAlgo.h"
3737
#include "IECoreUSD/PrimitiveAlgo.h"
3838

39-
#include "IECoreScene/PointsPrimitive.h"
39+
#include "IECoreScene/PointInstancer.h"
4040

4141
IECORE_PUSH_DEFAULT_VISIBILITY
4242
#include "pxr/usd/usdGeom/pointInstancer.h"
4343
IECORE_POP_DEFAULT_VISIBILITY
4444

45+
#include "boost/algorithm/string/predicate.hpp"
46+
#include "boost/container/flat_set.hpp"
47+
4548
using namespace IECore;
4649
using namespace IECoreScene;
4750
using namespace IECoreUSD;
@@ -53,28 +56,15 @@ using namespace IECoreUSD;
5356
namespace
5457
{
5558

56-
bool checkEnvFlag( const char *envVar, bool def )
57-
{
58-
const char *value = getenv( envVar );
59-
if( value )
60-
{
61-
return std::string( value ) != "0";
62-
}
63-
else
64-
{
65-
return def;
66-
}
67-
}
68-
6959
IECore::ObjectPtr readPointInstancer( pxr::UsdGeomPointInstancer &pointInstancer, pxr::UsdTimeCode time, const Canceller *canceller )
7060
{
7161
pxr::VtVec3fArray pointsData;
7262
pointInstancer.GetPositionsAttr().Get( &pointsData, time );
7363
Canceller::check( canceller );
7464
IECore::V3fVectorDataPtr positionData = DataAlgo::fromUSD( pointsData );
7565

76-
positionData->setInterpretation( GeometricData::Point );
77-
IECoreScene::PointsPrimitivePtr newPoints = new IECoreScene::PointsPrimitive( positionData );
66+
IECoreScene::PointInstancerPtr newPoints = new IECoreScene::PointInstancer( positionData->readable().size() );
67+
newPoints->setPosition( positionData );
7868

7969
// Per point attributes
8070

@@ -99,29 +89,49 @@ IECore::ObjectPtr readPointInstancer( pxr::UsdGeomPointInstancer &pointInstancer
9989
Canceller::check( canceller );
10090
PrimitiveAlgo::readPrimitiveVariable( pointInstancer.GetAngularVelocitiesAttr(), time, newPoints.get(), "angularVelocity" );
10191

92+
// Inactive and invisible IDs. These both do the same thing - prevent specific instances
93+
// from rendering. Inactive IDs are metadata-based and therefore can't be animated.
94+
// Invisible IDs are attribute-based and therefore can be animated. Since we're converting
95+
// to Cortex PrimitiveVariables, the distinction is irrelevant - all PrimitiveVariables can
96+
// be animated. Our PointInstancer therefore just has invisible IDs, which we merge both
97+
// USD properties into.
98+
99+
IECore::Int64VectorDataPtr invisibleIds;
102100
if( pointInstancer.GetInvisibleIdsAttr().HasAuthoredValue() )
103101
{
104-
DataPtr cortexInvisIds = DataAlgo::fromUSD( pointInstancer.GetInvisibleIdsAttr(), time, true );
105-
if( cortexInvisIds )
106-
{
107-
newPoints->variables["invisibleIds"] = IECoreScene::PrimitiveVariable(
108-
PrimitiveVariable::Constant, cortexInvisIds
109-
);
110-
}
102+
invisibleIds = IECore::runTimeCast<IECore::Int64VectorData>(
103+
DataAlgo::fromUSD( pointInstancer.GetInvisibleIdsAttr(), time, true )
104+
);
111105
}
112106

113107
pxr::SdfInt64ListOp inactiveIdsListOp;
114108
if( pointInstancer.GetPrim().GetMetadata( pxr::UsdGeomTokens->inactiveIds, &inactiveIdsListOp ) )
115109
{
116-
newPoints->variables["inactiveIds"] = IECoreScene::PrimitiveVariable(
117-
PrimitiveVariable::Constant,
118-
new IECore::Int64VectorData( inactiveIdsListOp.GetExplicitItems() )
119-
);
110+
const std::vector<int64_t> &inactiveIds = inactiveIdsListOp.GetExplicitItems();
111+
if( inactiveIds.size() )
112+
{
113+
if( invisibleIds )
114+
{
115+
invisibleIds->writable().insert(
116+
invisibleIds->writable().end(),
117+
inactiveIds.begin(), inactiveIds.end()
118+
);
119+
std::sort( invisibleIds->writable().begin(), invisibleIds->writable().end() );
120+
invisibleIds->writable().erase(
121+
std::unique( invisibleIds->writable().begin(), invisibleIds->writable().end() ),
122+
invisibleIds->writable().end()
123+
);
124+
}
125+
else
126+
{
127+
invisibleIds = new Int64VectorData( inactiveIds );
128+
}
129+
}
120130
}
121131

122-
// Prototype paths
132+
newPoints->setInvisibleIDs( invisibleIds );
123133

124-
const static bool g_relativePrototypes = checkEnvFlag( "IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES", false );
134+
// Prototype paths
125135

126136
pxr::SdfPathVector targets;
127137
Canceller::check( canceller );
@@ -134,17 +144,13 @@ IECore::ObjectPtr readPointInstancer( pxr::UsdGeomPointInstancer &pointInstancer
134144
prototypeRoots.reserve( targets.size() );
135145
for( const auto &t : targets )
136146
{
137-
if( !g_relativePrototypes || !t.HasPrefix( primPath ) )
147+
if( !t.HasPrefix( primPath ) )
138148
{
139149
prototypeRoots.push_back( t.GetString() );
140150
}
141151
else
142152
{
143-
// The ./ prefix shouldn't be necessary - we want to just use the absence of a leading
144-
// slash to indicate relative paths. We can remove the prefix here once we deprecate the
145-
// GAFFERSCENE_INSTANCER_EXPLICIT_ABSOLUTE_PATHS env var and have Gaffer always require a leading
146-
// slash for absolute paths.
147-
prototypeRoots.push_back( "./" + t.MakeRelativePath( primPath ).GetString() );
153+
prototypeRoots.push_back( t.MakeRelativePath( primPath ).GetString() );
148154
}
149155
}
150156

@@ -178,3 +184,100 @@ bool pointInstancerMightBeTimeVarying( pxr::UsdGeomPointInstancer &instancer )
178184
ObjectAlgo::ReaderDescription<pxr::UsdGeomPointInstancer> g_pointInstancerReaderDescription( pxr::TfToken( "PointInstancer" ), readPointInstancer, pointInstancerMightBeTimeVarying );
179185

180186
} // namespace
187+
188+
//////////////////////////////////////////////////////////////////////////
189+
// Writing
190+
//////////////////////////////////////////////////////////////////////////
191+
192+
namespace
193+
{
194+
195+
const boost::container::flat_set<std::string> g_exportedAsAttributes = {
196+
"prototypeRoots", "prototypeIndex", "P", "scale", "orientation",
197+
"id", "invisibleIds"
198+
};
199+
200+
bool writePointInstancer( const IECoreScene::PointInstancer *instancer, const pxr::UsdStagePtr &stage, const pxr::SdfPath &path, pxr::UsdTimeCode time )
201+
{
202+
auto usdInstancer = pxr::UsdGeomPointInstancer::Define( stage, path );
203+
204+
// Export primitive variables with special meaning to attributes
205+
// of the UsdGeomPointInstancer.
206+
207+
if( auto prototypes = instancer->getPrototypes() )
208+
{
209+
pxr::SdfPathVector targets;
210+
targets.reserve( prototypes.size() );
211+
for( const auto &prototype : prototypes )
212+
{
213+
pxr::SdfPath prototypePath;
214+
if( boost::starts_with( prototype, "./" ) )
215+
{
216+
prototypePath = pxr::SdfPath( prototype.substr( 2 ) );
217+
}
218+
else
219+
{
220+
prototypePath = pxr::SdfPath( prototype );
221+
}
222+
if( !prototypePath.IsAbsolutePath() )
223+
{
224+
prototypePath = prototypePath.MakeAbsolutePath( path );
225+
}
226+
targets.push_back( prototypePath );
227+
}
228+
usdInstancer.CreatePrototypesRel().SetTargets( targets );
229+
}
230+
231+
if( auto prototypeIndex = instancer->getPrototypeIndex() )
232+
{
233+
usdInstancer.CreateProtoIndicesAttr().Set( PrimitiveAlgo::toUSDExpanded( prototypeIndex ), time );
234+
}
235+
236+
if( auto position = instancer->getPosition() )
237+
{
238+
usdInstancer.CreatePositionsAttr().Set( PrimitiveAlgo::toUSDExpanded( position ), time );
239+
}
240+
241+
if( auto orientation = instancer->getOrientation() )
242+
{
243+
// USD uses `half` for orientation, but Cortex only has a data type for
244+
// `float` quaternions, so convert.
245+
pxr::VtArray<pxr::GfQuath> usdOrientation;
246+
usdOrientation.reserve( orientation.size() );
247+
for( const auto &o : orientation )
248+
{
249+
usdOrientation.push_back( pxr::GfQuath( DataAlgo::toUSD( o ) ) );
250+
}
251+
usdInstancer.CreateOrientationsAttr().Set( usdOrientation, time );
252+
}
253+
254+
if( auto scale = instancer->getScale() )
255+
{
256+
usdInstancer.CreateScalesAttr().Set( PrimitiveAlgo::toUSDExpanded( scale ), time );
257+
}
258+
259+
if( auto id = instancer->getID() )
260+
{
261+
usdInstancer.CreateIdsAttr().Set( PrimitiveAlgo::toUSDExpanded( id ), time );
262+
}
263+
264+
if( auto invisibleIds = instancer->getInvisibleIDs() )
265+
{
266+
usdInstancer.CreateInvisibleIdsAttr().Set( PrimitiveAlgo::toUSDExpanded( invisibleIds ), time );
267+
}
268+
269+
for( const auto &[name, primitiveVariable] : instancer->variables )
270+
{
271+
if( g_exportedAsAttributes.count( name ) )
272+
{
273+
continue;
274+
}
275+
PrimitiveAlgo::writePrimitiveVariable( name, primitiveVariable, pxr::UsdGeomPrimvarsAPI( usdInstancer ), time );
276+
}
277+
278+
return true;
279+
}
280+
281+
ObjectAlgo::WriterDescription<PointInstancer> g_pointInstancerWriterDescription( writePointInstancer );
282+
283+
} // namespace

contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,8 @@ struct VtValueFromExpandedData
153153
template<typename T>
154154
VtValue operator()( const IECore::TypedData<vector<T>> *data, const IECore::IntVectorData *indices, typename std::enable_if<!std::is_void<typename CortexTypeTraits<T>::USDType>::value>::type *enabler = nullptr ) const
155155
{
156-
using USDType = typename CortexTypeTraits<T>::USDType;
157-
using ArrayType = VtArray<USDType>;
158-
ArrayType array;
159-
array.reserve( indices->readable().size() );
160-
// Using universal reference (`&&`) for iteration for compatibility with the
161-
// non-standard proxy returned by `vector<bool>`.
162-
for( auto &&e : PrimitiveVariable::IndexedView<T>( data->readable(), &indices->readable() ) )
163-
{
164-
array.push_back( DataAlgo::toUSD( static_cast<const T &>( e ) ) );
165-
}
166-
return VtValue( array );
156+
PrimitiveVariable::IndexedView<T> view( data->readable(), &indices->readable() );
157+
return IECoreUSD::PrimitiveAlgo::toUSDExpanded( view );
167158
}
168159

169160
VtValue operator()( const IECore::Data *data, const IECore::IntVectorData *indices ) const

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ using PrimPredicate = bool (pxr::UsdPrim::*)() const;
265265
boost::container::flat_map<pxr::TfToken, PrimPredicate> g_schemaTypeSetPredicates = {
266266
{ pxr::TfToken( "__cameras" ), &pxr::UsdPrim::IsA<pxr::UsdGeomCamera> },
267267
{ pxr::TfToken( "__lights" ), &pxr::UsdPrim::HasAPI<pxr::UsdLuxLightAPI> },
268+
/// \todo This was introduced before we had `IECoreScene::PointInstancer`, to allow
269+
/// us to tell which `IECoreScene::PointsPrimitives` came from UsdGeomPointInstancer.
270+
/// We'll need to keep it for backwards compatibility for a while, but it might make
271+
/// sense to remove it at some point.
268272
{ pxr::TfToken( "usd:pointInstancers" ), &pxr::UsdPrim::IsA<pxr::UsdGeomPointInstancer> }
269273
};
270274

0 commit comments

Comments
 (0)