|
16 | 16 | from pxr import Gf, Usd, UsdGeom, UsdPhysics |
17 | 17 |
|
18 | 18 | import isaaclab.sim as sim_utils |
| 19 | +from isaaclab import cloner |
19 | 20 | from isaaclab.physics import PhysicsEvent |
20 | 21 | from isaaclab.sim.views.base_frame_view import BaseFrameView |
21 | 22 | from isaaclab.sim.views.usd_frame_view import UsdFrameView |
@@ -430,6 +431,7 @@ def _initialize_impl(self, physx: Any) -> None: |
430 | 431 | self._pose_buf = wp.zeros((1, 7), dtype=wp.float32, device=self._device) |
431 | 432 | binding_paths = [] |
432 | 433 |
|
| 434 | + world_sites = self._expand_world_sites_from_clone_plan(xform_cache) if not binding_paths else [] |
433 | 435 | # 5. Detect clone_usd=False expansion: binding row count > number of matched USD prims. |
434 | 436 | # Replace per-prim arrays with one entry per binding row, all derived from the env_0 template. |
435 | 437 | if binding_paths and len(binding_paths) > len(self._prims): |
@@ -466,6 +468,14 @@ def _initialize_impl(self, physx: Any) -> None: |
466 | 468 | parent_site_local.append(template_parent_site_local) |
467 | 469 | synthetic_prim_paths.append(synthetic_path) |
468 | 470 | self._synthetic_prim_paths: list[str] | None = synthetic_prim_paths |
| 471 | + self._prims = [self._prims[0]] * len(binding_paths) |
| 472 | + elif world_sites: |
| 473 | + _, self._prims, per_prim_site_local, parent_site_local, synthetic_paths = map( |
| 474 | + list, zip(*world_sites, strict=True) |
| 475 | + ) |
| 476 | + per_prim_ancestor = [None] * len(world_sites) |
| 477 | + parent_ancestor = [None] * len(world_sites) |
| 478 | + self._synthetic_prim_paths = synthetic_paths |
469 | 479 | else: |
470 | 480 | self._synthetic_prim_paths = None |
471 | 481 |
|
@@ -497,6 +507,52 @@ def _initialize_impl(self, physx: Any) -> None: |
497 | 507 | self._local_pos_ta = ProxyArray(self._local_pos_buf) |
498 | 508 | self._local_quat_ta = ProxyArray(self._local_quat_buf) |
499 | 509 |
|
| 510 | + def _expand_world_sites_from_clone_plan( |
| 511 | + self, xform_cache: UsdGeom.XformCache |
| 512 | + ) -> list[tuple[int, Usd.Prim, list[float], list[float], str]]: |
| 513 | + """Return row-ordered source prims and projected poses for source-only world sites.""" |
| 514 | + sim = sim_utils.SimulationContext.instance() |
| 515 | + plan = sim.get_clone_plan() if sim is not None else None |
| 516 | + matches = tuple(cloner.query.iter_sources(plan, self._prim_path)) if plan is not None else () |
| 517 | + if sum(len(env_ids) for _, _, _, env_ids in matches) <= len(self._prims): |
| 518 | + return [] |
| 519 | + |
| 520 | + records: list[tuple[int, Usd.Prim, list[float], list[float], str]] = [] |
| 521 | + for source_root, destination_template, source_path, env_ids in matches: |
| 522 | + source_prim = self._stage.GetPrimAtPath(source_path) |
| 523 | + if not source_prim.IsValid(): |
| 524 | + source_prim = sim_utils.find_first_matching_prim(source_path, self._stage) |
| 525 | + if source_prim is None or not source_prim.IsValid(): |
| 526 | + raise RuntimeError(f"OvPhysxFrameView could not resolve source prim {source_path!r}.") |
| 527 | + |
| 528 | + source_prim_path = source_prim.GetPath().pathString |
| 529 | + suffix = cloner.path.relative_to(source_prim_path, source_root) |
| 530 | + if suffix is None: |
| 531 | + raise RuntimeError(f"OvPhysxFrameView source prim {source_prim_path!r} is not under {source_root!r}.") |
| 532 | + source_world = xform_cache.GetLocalToWorldTransform(source_prim) |
| 533 | + source_parent_world = xform_cache.GetLocalToWorldTransform(source_prim.GetParent()) |
| 534 | + |
| 535 | + for env_id in env_ids: |
| 536 | + destination_root = destination_template.format(env_id) |
| 537 | + source_anchor_path, destination_anchor_path = source_root, destination_root |
| 538 | + destination_anchor = self._stage.GetPrimAtPath(destination_anchor_path) |
| 539 | + while not destination_anchor.IsValid() and destination_anchor_path != "/": |
| 540 | + source_anchor_path = source_anchor_path.rsplit("/", 1)[0] or "/" |
| 541 | + destination_anchor_path = destination_anchor_path.rsplit("/", 1)[0] or "/" |
| 542 | + destination_anchor = self._stage.GetPrimAtPath(destination_anchor_path) |
| 543 | + |
| 544 | + source_anchor = self._stage.GetPrimAtPath(source_anchor_path) |
| 545 | + if not source_anchor.IsValid() or not destination_anchor.IsValid(): |
| 546 | + raise RuntimeError(f"OvPhysxFrameView could not project {source_prim_path!r} into env {env_id}.") |
| 547 | + source_inverse = xform_cache.GetLocalToWorldTransform(source_anchor).GetInverse() |
| 548 | + destination_world = xform_cache.GetLocalToWorldTransform(destination_anchor) |
| 549 | + site_world = _gf_matrix_to_xform7(source_world * source_inverse * destination_world) |
| 550 | + parent_world = _gf_matrix_to_xform7(source_parent_world * source_inverse * destination_world) |
| 551 | + records.append((env_id, source_prim, site_world, parent_world, destination_root + suffix)) |
| 552 | + |
| 553 | + records.sort(key=lambda record: record[0]) |
| 554 | + return records |
| 555 | + |
500 | 556 | def _resolve_rigid_body_ancestor( |
501 | 557 | self, |
502 | 558 | prim: Usd.Prim, |
@@ -564,11 +620,10 @@ def _env_wildcardify(path: str) -> str: |
564 | 620 |
|
565 | 621 | @property |
566 | 622 | def prims(self) -> list[Usd.Prim]: |
567 | | - """List of USD prims discovered for this view. |
| 623 | + """List of one authored USD prim per site. |
568 | 624 |
|
569 | | - Under ``clone_usd=False`` scenes only ``env_0`` carries USD prims, so |
570 | | - this list may be shorter than :attr:`count`. Use :attr:`prim_paths` to |
571 | | - get one path per site (env-substituted for non-env_0 sites). |
| 625 | + Source-only clones repeat their source prim handle so the list stays aligned with |
| 626 | + the view count; prim_paths contains their logical destination paths. |
572 | 627 | """ |
573 | 628 | return self._prims |
574 | 629 |
|
@@ -873,6 +928,7 @@ def set_visibility(self, visibility, indices: wp.array | None = None) -> None: |
873 | 928 |
|
874 | 929 | def _gf_matrix_to_xform7(mat: Gf.Matrix4d) -> list[float]: |
875 | 930 | """Convert a ``Gf.Matrix4d`` to ``[tx, ty, tz, qx, qy, qz, qw]``.""" |
| 931 | + mat.Orthonormalize() |
876 | 932 | t = mat.ExtractTranslation() |
877 | 933 | q = mat.ExtractRotationQuat() |
878 | 934 | imag = q.GetImaginary() |
|
0 commit comments