@@ -253,7 +253,7 @@ def _invert_xform(xform: Sequence[float] | np.ndarray) -> np.ndarray:
253253 return np .concatenate ([- _quat_rotate (quat_inv , xform [:3 ]), quat_inv ])
254254
255255
256- def replicate_builder_mapping (
256+ def _replicate_builder_mapping (
257257 builder : ModelBuilder ,
258258 sources : Sequence [str ],
259259 mapping : torch .Tensor ,
@@ -264,8 +264,13 @@ def replicate_builder_mapping(
264264 source_site_indices : dict [int , dict [str , list [int ]]] | None = None ,
265265 env_root_sites : dict [str , wp .transform ] | None = None ,
266266 per_world_builder_hooks : Sequence [Callable [[ModelBuilder , int , list [float ], list [float ]], None ]] = (),
267- ) -> tuple [dict [str , list [list [int ]]], list [wp .transform ]]:
268- """Replicate source builders into per-env Newton worlds."""
267+ record_offsets : bool = False ,
268+ ) -> tuple [dict [str , list [list [int ]]], list [wp .transform ], dict [int , tuple [int , int , int ]]]:
269+ """Replicate source builders into per-env Newton worlds.
270+
271+ With ``record_offsets`` the (body, shape, joint) counts of ``builder`` at the moment each source
272+ lands in world 0 are recorded per source row; they locate a source's entities in the model.
273+ """
269274 source_site_indices = source_site_indices or {}
270275 env_root_sites = env_root_sites or {}
271276 num_worlds = mapping .size (1 )
@@ -300,7 +305,7 @@ def replicate_builder_mapping(
300305 base_shape = builder .shape_count
301306 stride = source_builder .shape_count
302307 # World 0's single source lands right after the global content.
303- world0_offsets = {0 : (builder .body_count , builder .shape_count , builder .joint_count )}
308+ world0_offsets = {0 : (builder .body_count , builder .shape_count , builder .joint_count )} if record_offsets else {}
304309 source_xform_inv = _invert_xform (xforms_np [0 ])
305310 xforms = _compose_world_xforms (positions_np , quaternions_np , source_xform_inv )
306311 builder .replicate (source_builder , num_worlds , xforms = xforms )
@@ -351,7 +356,7 @@ def replicate_builder_mapping(
351356 for row in rows_per_world [col ]:
352357 source_builder = source_builders [sources [row ]]
353358 offset = builder .shape_count
354- if col == 0 :
359+ if col == 0 and record_offsets :
355360 world0_offsets [row ] = (builder .body_count , builder .shape_count , builder .joint_count )
356361 builder .add_builder (source_builder , xform = source_xforms [row , col ])
357362
@@ -376,6 +381,65 @@ def replicate_builder_mapping(
376381)
377382
378383
384+ def replicate_builder_mapping (
385+ builder : ModelBuilder ,
386+ sources : Sequence [str ],
387+ mapping : torch .Tensor ,
388+ positions : torch .Tensor ,
389+ quaternions : torch .Tensor ,
390+ source_builders : dict [str , ModelBuilder ],
391+ * ,
392+ source_site_indices : dict [int , dict [str , list [int ]]] | None = None ,
393+ env_root_sites : dict [str , wp .transform ] | None = None ,
394+ per_world_builder_hooks : Sequence [Callable [[ModelBuilder , int , list [float ], list [float ]], None ]] = (),
395+ ) -> tuple [dict [str , list [list [int ]]], list [wp .transform ]]:
396+ """Replicate source builders into per-env Newton worlds."""
397+ local_site_map , world_xforms , _ = _replicate_builder_mapping (
398+ builder ,
399+ sources ,
400+ mapping ,
401+ positions ,
402+ quaternions ,
403+ source_builders ,
404+ source_site_indices = source_site_indices ,
405+ env_root_sites = env_root_sites ,
406+ per_world_builder_hooks = per_world_builder_hooks ,
407+ )
408+ return local_site_map , world_xforms
409+
410+
411+ def replicate_builder_mapping_with_provenance (
412+ builder : ModelBuilder ,
413+ sources : Sequence [str ],
414+ mapping : torch .Tensor ,
415+ positions : torch .Tensor ,
416+ quaternions : torch .Tensor ,
417+ source_builders : dict [str , ModelBuilder ],
418+ * ,
419+ source_site_indices : dict [int , dict [str , list [int ]]] | None = None ,
420+ env_root_sites : dict [str , wp .transform ] | None = None ,
421+ per_world_builder_hooks : Sequence [Callable [[ModelBuilder , int , list [float ], list [float ]], None ]] = (),
422+ ) -> tuple [dict [str , list [list [int ]]], list [wp .transform ], dict [int , tuple [int , int , int ]]]:
423+ """Same as :func:`replicate_builder_mapping`, additionally returning each source's landing offsets.
424+
425+ The third element maps a source row to the (body, shape, joint) index at which that source's
426+ entities start in world 0, which :func:`merge_import_results` needs to lift the importer's
427+ source-local provenance onto the replicated model.
428+ """
429+ return _replicate_builder_mapping (
430+ builder ,
431+ sources ,
432+ mapping ,
433+ positions ,
434+ quaternions ,
435+ source_builders ,
436+ source_site_indices = source_site_indices ,
437+ env_root_sites = env_root_sites ,
438+ per_world_builder_hooks = per_world_builder_hooks ,
439+ record_offsets = True ,
440+ )
441+
442+
379443def rename_builder_labels (
380444 builder : ModelBuilder ,
381445 sources : Sequence [str ],
0 commit comments