Cloning: Replicate Environments#

This tutorial shows how to use the clone API to replicate sub-sections of a USD scene. Cloning creates copies in the internal physics representation (not USD prims), optimized for large-scale parallel simulation.

Prerequisites#

Code Language#

def main():
    # Initialize PhysX SDK
    physx = PhysX(device="cpu")

    # Load USD scene containing /World/envs/env0
    script_dir = Path(__file__).resolve().parent
    usd_path = script_dir / ".." / "data" / "basic_simulation.usda"

    print(f"Loading USD scene: {usd_path}")
    usd_handle, _ = physx.add_usd(str(usd_path))
    physx.wait_all()

    # Clone env0 to create env1, env2, env3
    targets = ["/World/envs/env1", "/World/envs/env2", "/World/envs/env3"]
    print(f"Cloning /World/envs/env0 to {len(targets)} targets...")
    physx.clone("/World/envs/env0", targets)
    physx.wait_all()
    print(f"  Created {len(targets)} clones successfully")

    # Create a tensor binding to read rigid body poses across all environments
    # The pattern matches the "table" rigid body in every cloned environment
    pose_binding = physx.create_tensor_binding(
        pattern="/World/envs/env*/table",
        tensor_type=OVPHYSX_TENSOR_RIGID_BODY_POSE_F32,
    )
    print(f"  Rigid body binding: count={pose_binding.count}, shape={pose_binding.shape}")

    # Run simulation with all environments
    print("Running 10 simulation steps...")
    dt = 1.0 / 60.0
    for i in range(10):
        physx.step(dt, i * dt)
    physx.wait_all()
    print("  All steps completed")

    # Read poses from all environments after simulation
    poses = np.zeros(pose_binding.shape, dtype=np.float32)
    pose_binding.read(poses)
    for env_idx in range(poses.shape[0]):
        px, py, pz = poses[env_idx, 0:3]
        print(f"  env{env_idx}: pos=({px:.4f}, {py:.4f}, {pz:.4f})")

    # Clean up
    pose_binding.destroy()
    physx.remove_usd(usd_handle)
    physx.release()

    print("Done.")
static int wait_op_success(ovphysx_handle_t handle, ovphysx_enqueue_result_t res, uint64_t timeout_ns) {
  if (res.status != OVPHYSX_API_SUCCESS) {
    if (res.error.length > 0) {
      ovphysx_destroy_error(res.error);
    }
    return 0;
  }
  if (res.error.length > 0) {
    ovphysx_destroy_error(res.error);
  }
  ovphysx_op_wait_result_t wait_result = {0};
  ovphysx_result_t wait_res = ovphysx_wait_op(handle, res.op_index, timeout_ns, &wait_result);
  if (wait_result.errors && wait_result.num_errors > 0) {
    ovphysx_destroy_errors(wait_result.errors, wait_result.num_errors);
  }
  int success = wait_res.status == OVPHYSX_API_SUCCESS;
  if (wait_res.error.length > 0) {
    ovphysx_destroy_error(wait_res.error);
  }
  return success;
}

int main() {
  printf("=== ovphysx Clone Example (C API) ===\n\n");

  // Initialize create args with defaults
  ovphysx_create_args create_args = OVPHYSX_CREATE_ARGS_DEFAULT;

  // Create PhysX instance
  printf("Creating PhysX instance...\n");
  ovphysx_handle_t handle = 0;
  ovphysx_result_t create_res = ovphysx_create_instance(&create_args, &handle);
  if (create_res.status != OVPHYSX_API_SUCCESS) {
    fprintf(stderr, "Failed to create PhysX instance\n");
    if (create_res.error.length > 0) {
      ovphysx_destroy_error(create_res.error);
    }
    return 1;
  }
  if (create_res.error.length > 0) {
    ovphysx_destroy_error(create_res.error);
  }
  printf("  [OK] PhysX instance created\n\n");

  // Load USD scene with physics content
  printf("Loading USD scene...\n");
  ovphysx_usd_handle_t usd_handle = 0;
  ovphysx_enqueue_result_t load_res = ovphysx_add_usd(handle, ovphysx_cstr(OVPHYSX_TEST_DATA "/basic_simulation.usda"), ovphysx_cstr(""), &usd_handle);
  if (!wait_op_success(handle, load_res, 10ULL * 1000 * 1000 * 1000)) {
    fprintf(stderr, "USD loading failed or timed out\n");
    ovphysx_destroy_instance(handle);
    return 1;
  }
  printf("  [OK] USD scene loaded\n\n");

  // Clone the environment (source: env0, targets: env1, env2, env3)
  printf("Cloning /World/envs/env0 to create env1, env2, env3...\n");
  const char* clone_targets[] = {
    "/World/envs/env1",
    "/World/envs/env2",
    "/World/envs/env3"
  };
  enum { NUM_TARGETS = 3 };

  ovphysx_string_t target_strings[NUM_TARGETS];
  for (uint32_t i = 0; i < NUM_TARGETS; ++i) {
    target_strings[i] = ovphysx_cstr(clone_targets[i]);
  }

  ovphysx_enqueue_result_t clone_res = ovphysx_clone(
      handle,
      ovphysx_cstr("/World/envs/env0"),
      target_strings,
      NUM_TARGETS);
  if (!wait_op_success(handle, clone_res, 10ULL * 1000 * 1000 * 1000)) {
    fprintf(stderr, "Clone operation failed or timed out\n");
    ovphysx_destroy_instance(handle);
    return 1;
  }
  printf("  [OK] Created 3 clones successfully\n\n");

  // Run a few simulation steps to verify clones work correctly
  printf("Running simulation with clones (10 steps)...\n");
  for (int i = 0; i < 10; i++) {
    ovphysx_enqueue_result_t step_res = ovphysx_step(handle, 1.0f/60.0f, (float)i / 60.0f);
    if (!wait_op_success(handle, step_res, 10ULL * 1000 * 1000 * 1000)) {
      fprintf(stderr, "Failed to run simulation step %d\n", i);
      ovphysx_destroy_instance(handle);
      return 1;
    }
  }
  printf("  [OK] All 10 simulation steps completed successfully\n\n");

  // Clean up
  printf("Cleaning up...\n");

Result#

After this tutorial, you can replicate environments via the clone API and simulate all copies together.