@@ -134,43 +134,7 @@ def set_world_poses(self, positions=None, orientations=None, indices=None):
134134 if not self ._use_fabric :
135135 self ._usd_view .set_world_poses (positions , orientations , indices )
136136 return
137-
138- if not self ._fabric_initialized :
139- self ._initialize_fabric ()
140-
141- self ._prepare_for_reuse ()
142-
143- indices_wp = self ._resolve_indices_wp (indices )
144- count = indices_wp .shape [0 ]
145-
146- dummy = wp .zeros ((0 , 3 ), dtype = wp .float32 , device = self ._device )
147- positions_wp = _to_float32_2d (positions ) if positions is not None else dummy
148- orientations_wp = (
149- _to_float32_2d (orientations )
150- if orientations is not None
151- else wp .zeros ((0 , 4 ), dtype = wp .float32 , device = self ._device )
152- )
153-
154- wp .launch (
155- kernel = fabric_utils .compose_fabric_transformation_matrix_from_warp_arrays ,
156- dim = count ,
157- inputs = [
158- self ._fabric_world_matrices ,
159- positions_wp ,
160- orientations_wp ,
161- dummy ,
162- False ,
163- False ,
164- False ,
165- indices_wp ,
166- self ._view_to_fabric ,
167- ],
168- device = self ._fabric_device ,
169- )
170- wp .synchronize ()
171-
172- self ._fabric_hierarchy .update_world_xforms ()
173- self ._fabric_usd_sync_done = True
137+ self ._compose_fabric_transform (positions = positions , orientations = orientations , indices = indices )
174138
175139 def get_world_poses (self , indices : wp .array | None = None ) -> tuple [ProxyArray , ProxyArray ]:
176140 if not self ._use_fabric :
@@ -229,7 +193,15 @@ def set_scales(self, scales, indices=None):
229193 if not self ._use_fabric :
230194 self ._usd_view .set_scales (scales , indices )
231195 return
196+ self ._compose_fabric_transform (scales = scales , indices = indices )
197+
198+ def _compose_fabric_transform (self , positions = None , orientations = None , scales = None , indices = None ):
199+ """Write the given subset of (position, orientation, scale) into Fabric in one kernel launch.
232200
201+ Components left as ``None`` are skipped via empty input arrays — the kernel reads them
202+ from the existing Fabric matrix. Always invokes :meth:`_prepare_for_reuse` exactly once
203+ per write, even when multiple components are updated together.
204+ """
233205 if not self ._fabric_initialized :
234206 self ._initialize_fabric ()
235207
@@ -238,17 +210,19 @@ def set_scales(self, scales, indices=None):
238210 indices_wp = self ._resolve_indices_wp (indices )
239211 count = indices_wp .shape [0 ]
240212
241- dummy3 = wp .zeros ((0 , 3 ), dtype = wp .float32 , device = self ._device )
242- dummy4 = wp .zeros ((0 , 4 ), dtype = wp .float32 , device = self ._device )
243- scales_wp = _to_float32_2d (scales )
213+ empty3 = wp .zeros ((0 , 3 ), dtype = wp .float32 , device = self ._device )
214+ empty4 = wp .zeros ((0 , 4 ), dtype = wp .float32 , device = self ._device )
215+ positions_wp = _to_float32_2d (positions ) if positions is not None else empty3
216+ orientations_wp = _to_float32_2d (orientations ) if orientations is not None else empty4
217+ scales_wp = _to_float32_2d (scales ) if scales is not None else empty3
244218
245219 wp .launch (
246220 kernel = fabric_utils .compose_fabric_transformation_matrix_from_warp_arrays ,
247221 dim = count ,
248222 inputs = [
249223 self ._fabric_world_matrices ,
250- dummy3 ,
251- dummy4 ,
224+ positions_wp ,
225+ orientations_wp ,
252226 scales_wp ,
253227 False ,
254228 False ,
@@ -332,10 +306,11 @@ def _rebuild_fabric_arrays(self) -> None:
332306 pattern (via ``_usd_view.count``) and does not change when Fabric rearranges its
333307 internal memory layout. The assertion below guards this invariant.
334308 """
335- assert self .count == self ._default_view_indices .shape [0 ], (
336- f"Prim count changed ({ self .count } vs { self ._default_view_indices .shape [0 ]} ). "
337- "Fabric topology change added/removed tracked prims — full re-initialization required."
338- )
309+ if self .count != self ._default_view_indices .shape [0 ]:
310+ raise RuntimeError (
311+ f"Prim count changed ({ self .count } vs { self ._default_view_indices .shape [0 ]} ). "
312+ "Fabric topology change added/removed tracked prims — full re-initialization required."
313+ )
339314 self ._view_to_fabric = wp .zeros ((self .count ,), dtype = wp .uint32 , device = self ._fabric_device )
340315 self ._fabric_to_view = wp .fabricarray (self ._fabric_selection , self ._view_index_attr )
341316
@@ -424,19 +399,20 @@ def _initialize_fabric(self) -> None:
424399 def _sync_fabric_from_usd_once (self ) -> None :
425400 """Sync Fabric world matrices from USD once, on the first read.
426401
427- ``set_world_poses`` and ``set_scales`` each set ``_fabric_usd_sync_done``
428- themselves, so no explicit flag assignment is needed here.
402+ Combines position/orientation/scale into a single Fabric write so
403+ :meth:`_prepare_for_reuse` (and its underlying ``PrepareForReuse``) is invoked
404+ exactly once across the full sync.
429405 """
430406 if not self ._fabric_initialized :
431407 self ._initialize_fabric ()
432408
433409 positions_usd_ta , orientations_usd_ta = self ._usd_view .get_world_poses ()
434- positions_usd = positions_usd_ta .warp
435- orientations_usd = orientations_usd_ta .warp
436410 scales_usd = self ._usd_view .get_scales ()
437-
438- self .set_world_poses (positions_usd , orientations_usd )
439- self .set_scales (scales_usd )
411+ self ._compose_fabric_transform (
412+ positions = positions_usd_ta .warp ,
413+ orientations = orientations_usd_ta .warp ,
414+ scales = scales_usd ,
415+ )
440416
441417 def _resolve_indices_wp (self , indices : wp .array | None ) -> wp .array :
442418 """Resolve view indices as a Warp uint32 array."""
0 commit comments