@@ -164,12 +164,13 @@ def _capture_fabric_scales(
164164
165165@wp .kernel (enable_backward = False )
166166def _set_fabric_transforms (
167- fabric_transforms : wp .fabricarray (dtype = wp .mat44d ),
167+ fabric_world_transforms : wp .fabricarray (dtype = wp .mat44d ),
168+ fabric_local_transforms : wp .fabricarray (dtype = wp .mat44d ),
168169 newton_indices : wp .fabricarray (dtype = wp .uint32 ),
169170 newton_body_q : wp .array (ndim = 1 , dtype = wp .transformf ),
170171 body_scales : wp .array (dtype = wp .vec3f ),
171172):
172- """Write Newton body poses to Fabric world matrices with their initialized scale.
173+ """Write Newton body poses to Fabric local matrices with their initialized scale.
173174
174175 For each Fabric prim at thread ``i``, reads the Newton body transform at
175176 ``newton_body_q[newton_indices[i]]`` and combines its translation and rotation
@@ -181,15 +182,12 @@ def _set_fabric_transforms(
181182 idx = int (newton_indices [i ])
182183 transform = newton_body_q [idx ]
183184 scale = body_scales [idx ]
184- fabric_transforms [i ] = wp .mat44d (
185- wp .transpose (
186- wp .transform_compose (
187- wp .transform_get_translation (transform ),
188- wp .transform_get_rotation (transform ),
189- scale ,
190- )
185+ world_transform = wp .transpose (
186+ wp .mat44d (
187+ wp .transform_compose (wp .transform_get_translation (transform ), wp .transform_get_rotation (transform ), scale )
191188 )
192189 )
190+ fabric_local_transforms [i ] = world_transform * wp .inverse (fabric_world_transforms [i ]) * fabric_local_transforms [i ]
193191
194192
195193@wp .kernel (enable_backward = False )
@@ -488,9 +486,13 @@ class NewtonManager(PhysicsManager):
488486 # USD/Fabric sync
489487 _newton_stage_path = None
490488 _usdrt_stage = None
489+ _fabric_hierarchy = None
491490 _newton_index_attr = "newton:index"
492- # Body-indexed world scales captured before Newton first overwrites Fabric transforms.
491+ # Body-indexed world scales captured before Newton first updates Fabric transforms.
493492 _fabric_body_scales : wp .array | None = None
493+ # Persistent query and arrays: selection, world matrix, local matrix, and Newton index.
494+ _fabric_body_sync : tuple [Any , Any , Any , Any ] | None = None
495+ _fabric_body_binding_count : int = 0
494496 _clone_physics_only = False
495497 _transforms_dirty : bool = False
496498 _transforms_may_change_on_graph_replay : bool = False
@@ -504,9 +506,6 @@ class NewtonManager(PhysicsManager):
504506 _newton_particle_count_attr = "newton:particleCount"
505507 _particle_visual_prims : dict [str , _ParticleVisualPrim ] = {}
506508
507- # Cached after the first fabric sync that probes IFabricHierarchy GPU APIs.
508- _use_fabric_gpu_hierarchy : bool | None = None
509-
510509 # Set to True after sync_transforms_to_usd() successfully writes body positions for
511510 # the first time in each simulation session. Reset to False in clear(). Polled by
512511 # test drain helpers to know when the GPU has propagated the newton:index Fabric
@@ -687,15 +686,8 @@ def sync_transforms_to_usd(cls) -> None:
687686 Uses ``wp.fabricarray`` directly (no ``isaacsim.physics.newton`` extension needed).
688687 On the first successful sync, a Warp kernel captures each initialized Fabric
689688 world scale by Newton body index. The pose kernel then combines that scale with
690- ``state_0.body_q[newton_index[i]]`` and writes the corresponding ``mat44d`` to
691- ``omni:fabric:worldMatrix`` for each prim.
692-
693- When ``IFabricHierarchy.update_world_xforms_gpu_with_options`` is
694- available the method mirrors PhysX's ``DirectGpuHelper`` pattern: pause
695- Fabric change tracking, write transforms, resume tracking, then run the
696- GPU hierarchy update with ``RIGID_BODY | FORCE_UPDATE`` so Newton-authored
697- world matrices stay authoritative on rigid-body prims. Otherwise it
698- falls back to the CPU ``update_world_xforms()`` path.
689+ ``state_0.body_q[newton_index[i]]``, derives the corresponding local matrix,
690+ and lets Fabric propagate the hierarchy on the GPU.
699691 """
700692 if cls ._usdrt_stage is None or cls ._model is None or cls ._state_0 is None :
701693 return
@@ -704,95 +696,73 @@ def sync_transforms_to_usd(cls) -> None:
704696 try :
705697 import usdrt
706698
707- fabric_hierarchy = None
708- gpu_opts_cls = None
709- if hasattr (usdrt , "hierarchy" ):
710- fabric_hierarchy = usdrt .hierarchy .IFabricHierarchy ().get_fabric_hierarchy (
711- cls ._usdrt_stage .GetFabricId (), cls ._usdrt_stage .GetStageIdAsStageId ()
712- )
713- gpu_opts_cls = getattr (usdrt .hierarchy , "FabricHierarchyGpuUpdateOptions" , None )
714-
715- if cls ._use_fabric_gpu_hierarchy is None and hasattr (usdrt , "hierarchy" ):
716- # Probe the pybind class once so a transient null hierarchy handle does
717- # not permanently disable the GPU path for the session.
718- NewtonManager ._use_fabric_gpu_hierarchy = gpu_opts_cls is not None and hasattr (
719- usdrt .hierarchy .IFabricHierarchy , "update_world_xforms_gpu_with_options"
720- )
721- if cls ._use_fabric_gpu_hierarchy :
722- logger .info ("Fabric GPU transform hierarchy enabled via IFabricHierarchy" )
723- else :
724- logger .info ("Fabric GPU transform hierarchy unavailable; falling back to update_world_xforms()" )
725-
726- use_gpu_hierarchy = bool (
727- cls ._use_fabric_gpu_hierarchy and fabric_hierarchy is not None and gpu_opts_cls is not None
728- )
729-
730- # Pause hierarchy change tracking BEFORE SelectPrims.
731- # SelectPrims with ReadWrite access calls getAttributeArrayGpu
732- # internally, which marks Fabric buffers dirty. If tracking is
733- # still active at that point the hierarchy records the change and
734- # Kit's updateWorldXforms will do an expensive connectivity
735- # rebuild every frame. PhysX avoids this via ScopedUSDRT which
736- # pauses tracking before any Fabric writes.
737- if use_gpu_hierarchy :
738- fabric_hierarchy .track_world_xform_changes (False )
739- fabric_hierarchy .track_local_xform_changes (False )
740-
741- try :
699+ topology_changed = False
700+ fabric_sync = cls ._fabric_body_sync
701+ if fabric_sync is None :
742702 selection = cls ._usdrt_stage .SelectPrims (
743703 require_attrs = [
744- (usdrt .Sdf .ValueTypeNames .Matrix4d , "omni:fabric:worldMatrix" , usdrt .Usd .Access .ReadWrite ),
704+ (usdrt .Sdf .ValueTypeNames .Matrix4d , "omni:fabric:worldMatrix" , usdrt .Usd .Access .Read ),
705+ (
706+ usdrt .Sdf .ValueTypeNames .Matrix4d ,
707+ "omni:fabric:localMatrix" ,
708+ usdrt .Usd .Access .ReadWrite ,
709+ ),
745710 (usdrt .Sdf .ValueTypeNames .UInt , cls ._newton_index_attr , usdrt .Usd .Access .Read ),
746711 ],
747712 device = str (PhysicsManager ._device ),
748713 )
749- if selection .GetCount () == 0 :
750- # The newton:index attribute is written CPU-side by start_simulation() but
751- # GPU propagation is deferred. Keep _transforms_dirty=True so the next
752- # pre_render() retries once initialize_solver() has completed (FK delegate
753- # bound) and body_q holds valid values.
754- if cls ._eval_fk is _eval_fk_unbound :
714+ else :
715+ selection , fabric_world_transforms , fabric_local_transforms , newton_indices = fabric_sync
716+ topology_changed = selection .PrepareForReuse ()
717+
718+ if fabric_sync is None or topology_changed :
719+ if selection .GetCount () != cls ._fabric_body_binding_count :
720+ NewtonManager ._fabric_body_sync = None
721+ # CPU-authored newton:index attributes may not have reached Fabric yet.
722+ if selection .GetCount () == 0 and cls ._eval_fk is _eval_fk_unbound :
755723 NewtonManager ._transforms_dirty = False
756724 return
757-
758- fabric_transforms = wp .fabricarray (selection , "omni:fabric:worldMatrix " )
725+ fabric_world_transforms = wp . fabricarray ( selection , "omni:fabric:worldMatrix" )
726+ fabric_local_transforms = wp .fabricarray (selection , "omni:fabric:localMatrix " )
759727 newton_indices = wp .fabricarray (selection , cls ._newton_index_attr )
760- if cls . _fabric_body_scales is None :
761- NewtonManager . _fabric_body_scales = wp . empty (
762- cls . _model . body_count ,
763- dtype = wp . vec3f ,
764- device = PhysicsManager . _device ,
765- )
766- wp . launch (
767- _capture_fabric_scales ,
768- dim = newton_indices . shape [ 0 ] ,
769- inputs = [ fabric_transforms , newton_indices , cls . _fabric_body_scales ] ,
770- device = PhysicsManager ._device ,
771- )
728+ NewtonManager . _fabric_body_sync = (
729+ selection ,
730+ fabric_world_transforms ,
731+ fabric_local_transforms ,
732+ newton_indices ,
733+ )
734+ if cls . _fabric_body_scales is None :
735+ NewtonManager . _fabric_body_scales = wp . empty (
736+ cls . _model . body_count ,
737+ dtype = wp . vec3f ,
738+ device = PhysicsManager ._device ,
739+ )
772740 wp .launch (
773- _set_fabric_transforms ,
741+ _capture_fabric_scales ,
774742 dim = newton_indices .shape [0 ],
775- inputs = [fabric_transforms , newton_indices , cls . _state_0 . body_q , cls ._fabric_body_scales ],
743+ inputs = [fabric_world_transforms , newton_indices , cls ._fabric_body_scales ],
776744 device = PhysicsManager ._device ,
777745 )
778- wp .synchronize_device (PhysicsManager ._device )
746+ wp .launch (
747+ _set_fabric_transforms ,
748+ dim = newton_indices .shape [0 ],
749+ inputs = [
750+ fabric_world_transforms ,
751+ fabric_local_transforms ,
752+ newton_indices ,
753+ cls ._state_0 .body_q ,
754+ cls ._fabric_body_scales ,
755+ ],
756+ device = PhysicsManager ._device ,
757+ )
779758
780- NewtonManager ._newton_fabric_ready = True
781- NewtonManager ._transforms_dirty = False
759+ wp .synchronize_stream (PhysicsManager ._device )
760+ if not cls ._fabric_hierarchy .update_world_xforms_gpu (not topology_changed ):
761+ raise RuntimeError ("Fabric GPU hierarchy propagation failed." )
762+ wp .synchronize_device (PhysicsManager ._device )
782763
783- if use_gpu_hierarchy :
784- # RIGID_BODY: inverse-propagate on PhysicsRigidBodyAPI buckets
785- # (keep Newton world matrices, derive local). FORCE_UPDATE:
786- # bypass the change-listener dirty check after tracking pause.
787- fabric_hierarchy .update_world_xforms_gpu_with_options (
788- gpu_opts_cls .RIGID_BODY | gpu_opts_cls .FORCE_UPDATE
789- )
790- elif fabric_hierarchy is not None :
791- fabric_hierarchy .update_world_xforms ()
792- finally :
793- if use_gpu_hierarchy :
794- fabric_hierarchy .track_world_xform_changes (True )
795- fabric_hierarchy .track_local_xform_changes (True )
764+ NewtonManager ._newton_fabric_ready = True
765+ NewtonManager ._transforms_dirty = False
796766 except Exception :
797767 logger .exception ("[NewtonManager] sync_transforms_to_usd FAILED" )
798768
@@ -1123,7 +1093,6 @@ def clear(cls):
11231093 NewtonManager ._visualization_stop_callback = None
11241094 if callback is not None :
11251095 callback .deregister ()
1126- NewtonManager ._use_fabric_gpu_hierarchy = None
11271096 NewtonManager ._newton_fabric_ready = False
11281097 NewtonManager ._builder = None
11291098 NewtonManager ._model = None
@@ -1168,7 +1137,10 @@ def clear(cls):
11681137 NewtonManager ._sensor_bvh_shape_flags = ShapeFlags .VISIBLE
11691138 NewtonManager ._newton_stage_path = None
11701139 NewtonManager ._usdrt_stage = None
1140+ NewtonManager ._fabric_hierarchy = None
11711141 NewtonManager ._fabric_body_scales = None
1142+ NewtonManager ._fabric_body_sync = None
1143+ NewtonManager ._fabric_body_binding_count = 0
11721144 NewtonManager ._transforms_dirty = False
11731145 NewtonManager ._transforms_may_change_on_graph_replay = False
11741146 NewtonManager ._particles_dirty = False
@@ -1656,10 +1628,13 @@ def start_simulation(cls) -> None:
16561628 if body_bindings is None :
16571629 # Non-replicated Newton stages do not pass through NewtonReplicateContext.
16581630 body_bindings = [(body_path , i ) for i , body_path in enumerate (body_paths )]
1631+ NewtonManager ._fabric_body_sync = None
1632+ NewtonManager ._fabric_body_binding_count = len (body_bindings )
16591633
16601634 fabric_hierarchy = usdrt .hierarchy .IFabricHierarchy ().get_fabric_hierarchy (
16611635 cls ._usdrt_stage .GetFabricId (), cls ._usdrt_stage .GetStageIdAsStageId ()
16621636 )
1637+ NewtonManager ._fabric_hierarchy = fabric_hierarchy
16631638
16641639 NewtonManager ._initialize_fabric_body_prims (cls ._usdrt_stage , fabric_hierarchy , usdrt , body_bindings )
16651640 NewtonManager ._initialize_fabric_cable_prims (cls ._usdrt_stage , fabric_hierarchy , usdrt )
@@ -1690,9 +1665,7 @@ def _initialize_fabric_body_prims(stage, fabric_hierarchy, usdrt, body_bindings:
16901665
16911666 prim .CreateAttribute (NewtonManager ._newton_index_attr , usdrt .Sdf .ValueTypeNames .UInt , custom = True )
16921667 prim .GetAttribute (NewtonManager ._newton_index_attr ).Set (body_index )
1693- # Tag with PhysicsRigidBodyAPI so FabricHierarchyGpuUpdateOptions.RIGID_BODY
1694- # applies Inverse propagation (preserves Newton's world transforms and derives
1695- # local) instead of Forward.
1668+ # Generated Xforms need the same rigid-body identity as authored body prims.
16961669 prim .AddAppliedSchema ("PhysicsRigidBodyAPI" )
16971670
16981671 fabric_hierarchy .update_world_xforms ()
0 commit comments