Frequently Used Python Snippets

A collection of python utility snippets that can be used in the script editor or pure python applications

Create A Physics Scene

1
2
3
4
5
6
7
8
9
import omni
from pxr import Gf, Sdf, UsdPhysics

stage = omni.usd.get_context().get_stage()
# Add a physics scene prim to stage
scene = UsdPhysics.Scene.Define(stage, Sdf.Path("/World/physicsScene"))
# Set gravity vector
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(981.0)

The following can be added to set specific settings, in this case use CPU physics and the TGS solver

1
2
3
4
5
6
7
8
9
from pxr import PhysxSchema

PhysxSchema.PhysxSceneAPI.Apply(stage.GetPrimAtPath("/World/physicsScene"))
physxSceneAPI = PhysxSchema.PhysxSceneAPI.Get(stage, "/World/physicsScene")
physxSceneAPI.CreateEnableCCDAttr(True)
physxSceneAPI.CreateEnableStabilizationAttr(True)
physxSceneAPI.CreateEnableGPUDynamicsAttr(False)
physxSceneAPI.CreateBroadphaseTypeAttr("MBP")
physxSceneAPI.CreateSolverTypeAttr("TGS")

Adding a ground plane to a stage can be done via the following code: It creates a Z up plane with a size of 100 cm at a Z coordinate of -100

1
2
3
4
import omni
from pxr import PhysicsSchemaTools
stage = omni.usd.get_context().get_stage()
PhysicsSchemaTools.addGroundPlane(stage, "/World/groundPlane", "Z", 100, Gf.Vec3f(0, 0, -100), Gf.Vec3f(1.0))

Enable Physics And Collision For a Mesh

The script below assumes there is a physics scene in the stage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import omni
from omni.physx.scripts import utils

# Create a cube mesh in the stage
stage = omni.usd.get_context().get_stage()
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the prim
cube_prim = stage.GetPrimAtPath("/Cube")
# Enable physics on prim
# If a tighter collision approximation is desired use convexDecomposition instead of convexHull
utils.setRigidBody(cube_prim, "convexHull", False)

If a tighter collision approximation is desired use convexDecomposition

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import omni
from omni.physx.scripts import utils

# Create a cube mesh in the stage
stage = omni.usd.get_context().get_stage()
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the prim
cube_prim = stage.GetPrimAtPath("/Cube")
# Enable physics on prim
# If a tighter collision approximation is desired use convexDecomposition instead of convexHull
utils.setRigidBody(cube_prim, "convexDecomposition", False)

To verify that collision meshes have been successfully enabled, click the “eye” icon > “Show By Type” > “Physics Mesh” > “All”. This will show the collision meshes as pink outlines on the objects.

Set Mass Properties for a Mesh

The snippet below shows how to set the mass of a physics object. Density can also be specified as an alternative

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import omni
from pxr import UsdPhysics
from omni.physx.scripts import utils

stage = omni.usd.get_context().get_stage()
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the prim
cube_prim = stage.GetPrimAtPath(path)
# Make it a rigid body
utils.setRigidBody(cube_prim, "convexHull", False)

mass_api = UsdPhysics.MassAPI.Apply(cube_prim)
mass_api.CreateMassAttr(10)
### Alternatively set the density
mass_api.CreateDensityAttr(1000)

Traverse a stage and assign collision meshes to children

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import omni
from pxr import Usd, UsdGeom, Gf
from omni.physx.scripts import utils

stage = omni.usd.get_context().get_stage()

def add_cube(stage, path, size: float = 10, offset: Gf.Vec3d = Gf.Vec3d(0, 0, 0)):
    cubeGeom = UsdGeom.Cube.Define(stage, path)
    cubeGeom.CreateSizeAttr(size)
    cubeGeom.AddTranslateOp().Set(offset)

### The following prims are added for illustrative purposes
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Torus")
# all prims under AddCollision will get collisions assigned
add_cube(stage, "/World/Cube_0", offset=Gf.Vec3d(100, 100, 0))
# create a prim nested under without a parent
add_cube(stage, "/World/Nested/Cube", offset=Gf.Vec3d(100, 0, 100))
###

# Traverse all prims in the stage starting at this path
curr_prim = stage.GetPrimAtPath("/")

for prim in Usd.PrimRange(curr_prim):
    # only process shapes and meshes
    if (
        prim.IsA(UsdGeom.Cylinder)
        or prim.IsA(UsdGeom.Capsule)
        or prim.IsA(UsdGeom.Cone)
        or prim.IsA(UsdGeom.Sphere)
        or prim.IsA(UsdGeom.Cube)
    ):
        # use a ConvexHull for regular prims
        utils.setCollider(prim, approximationShape="convexHull")
    elif prim.IsA(UsdGeom.Mesh):
        # "None" will use the base triangle mesh if available
        # Can also use "convexDecomposition", "convexHull", "boundingSphere", "boundingCube"
        utils.setCollider(prim, approximationShape="None")
    pass
pass

Do Overlap Test

These snippets detect and report when objects overlap with a specified cubic/spherical region. The following is assumed: the stage contains a physics scene, all objects have collision meshes enabled, and the play button has been clicked.

The parameters: extent, origin and rotation (or origin and radius) define the cubic/spherical region to check overlap against. The output of the physX query is the number of objects that overlaps with this cubic/spherical region.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def check_overlap_box(self):
    # Defines a cubic region to check overlap with
    extent = carb.Float3(20.0, 20.0, 20.0)
    origin = carb.Float3(0.0, 0.0, 0.0)
    rotation = carb.Float4(0.0, 0.0, 1.0, 0.0)
    # physX query to detect number of hits for a cubic region
    numHits = get_physx_scene_query_interface().overlap_box(extent, origin, rotation, self.report_hit, False)
    # physX query to detect number of hits for a spherical region
    # numHits = get_physx_scene_query_interface().overlap_sphere(radius, origin, self.report_hit, False)
    self.kit.update()
    return numHits > 0:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import omni.physx
from omni.physx import get_physx_scene_query_interface
from pxr import UsdGeom, Gf, Vt
import carb

def report_hit(self, hit):
    # When a collision is detected, the object color changes to red.
    hitColor = Vt.Vec3fArray([Gf.Vec3f(180.0 / 255.0, 16.0 / 255.0, 0.0)])
    usdGeom = UsdGeom.Mesh.Get(self.stage, hit.rigid_body)
    usdGeom.GetDisplayColorAttr().Set(hitColor)
    return True

Do Raycast Test

This snippet detects the closest object that intersects with a specified ray. The following is assumed: the stage contains a physics scene, all objects have collision meshes enabled, and the play button has been clicked.

The parameters: origin, rayDir and distance define a ray along which a ray hit might be detected. The output of the query can be used to access the object’s reference, and its distance from the raycast origin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import omni.physx
from omni.physx import get_physx_scene_query_interface
from pxr import UsdGeom

def check_raycast(self):
    # Projects a raycast from 'origin', in the direction of 'rayDir', for a length of 'distance' cm
    # Parameters can be replaced with real-time position and orientation data  (e.g. of a camera)
    origin = carb.Float3(0.0, 0.0, 0.0)
    rayDir = carb.Float3(1.0, 0.0, 0.0)
    distance = 100.0
    # physX query to detect closest hit
    hit = get_physx_scene_query_interface().raycast_closest(origin, rayDir, distance)
    if(hit["hit"]):
        # Change object color to yellow and record distance from origin
        usdGeom = UsdGeom.Mesh.Get(self.stage, hit["rigidBody"])
        hitColor = Vt.Vec3fArray([Gf.Vec3f(255.0 / 255.0, 255.0 / 255.0, 0.0)])
        usdGeom.GetDisplayColorAttr().Set(hitColor)
        distance = hit["distance"]
        return usdGeom.GetPath().pathString, distance
    return None, 10000.0

Creating, Modifying, Assigning Materials

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import omni
from pxr import UsdShade, Sdf, Gf

mtl_created_list = []
# Create a new material using OmniGlass.mdl
omni.kit.commands.execute(
    "CreateAndBindMdlMaterialFromLibrary",
    mdl_name="OmniGlass.mdl",
    mtl_name="OmniGlass",
    mtl_created_list=mtl_created_list,
)
# Get reference to created material
stage = omni.usd.get_context().get_stage()
mtl_prim = stage.GetPrimAtPath(mtl_created_list[0])
# Set material inputs, these can be determined by looking at the .mdl file
# or by selecting the Shader attached to the Material in the stage window and looking at the details panel
omni.usd.create_material_input(mtl_prim, "glass_color", Gf.Vec3f(0, 1, 0), Sdf.ValueTypeNames.Color3f)
omni.usd.create_material_input(mtl_prim, "glass_ior", 1.0, Sdf.ValueTypeNames.Float)
# Create a prim to apply the material to
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the path to the prim
cube_prim = stage.GetPrimAtPath(path)
# Bind the material to the prim
cube_mat_shade = UsdShade.Material(mtl_prim)
UsdShade.MaterialBindingAPI(cube_prim).Bind(cube_mat_shade, UsdShade.Tokens.strongerThanDescendants)

Assigning a texture to a material that supports it can be done as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import omni
import carb
from pxr import UsdShade, Sdf

# Change the server to your Nucleus install, default is set to localhost in omni.isaac.sim.base.kit
default_server = carb.settings.get_settings().get("/isaac/nucleus/default")
mtl_created_list = []
# Create a new material using OmniPBR.mdl
omni.kit.commands.execute(
    "CreateAndBindMdlMaterialFromLibrary",
    mdl_name="OmniPBR.mdl",
    mtl_name="OmniPBR",
    mtl_created_list=mtl_created_list,
)
stage = omni.usd.get_context().get_stage()
mtl_prim = stage.GetPrimAtPath(mtl_created_list[0])
# Set material inputs, these can be determined by looking at the .mdl file
# or by selecting the Shader attached to the Material in the stage window and looking at the details panel
omni.usd.create_material_input(
    mtl_prim,
    "diffuse_texture",
    default_server + "/Isaac/Samples/DR/Materials/Textures/marble_tile.png",
    Sdf.ValueTypeNames.Asset,
)
# Create a prim to apply the material to
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the path to the prim
cube_prim = stage.GetPrimAtPath(path)
# Bind the material to the prim
cube_mat_shade = UsdShade.Material(mtl_prim)
UsdShade.MaterialBindingAPI(cube_prim).Bind(cube_mat_shade, UsdShade.Tokens.strongerThanDescendants)

Adding a transform matrix to a prim

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import omni
from pxr import Gf, UsdGeom

# Create a cube mesh in the stage
stage = omni.usd.get_context().get_stage()
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Get the prim and set its transform matrix
cube_prim = stage.GetPrimAtPath("/World/Cube")
xform = UsdGeom.Xformable(cube_prim)
transform = xform.AddTransformOp()
mat = Gf.Matrix4d()
mat.SetTranslateOnly(Gf.Vec3d(10.0,1.0,1.0))
mat.SetRotateOnly(Gf.Rotation(Gf.Vec3d(0,1,0), 290))
transform.Set(mat)

Align two USD prims

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import omni
from pxr import UsdGeom, Gf

stage = omni.usd.get_context().get_stage()
# Create a cube
result, path_a = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
prim_a = stage.GetPrimAtPath(path_a)
# change the cube pose
xform = UsdGeom.Xformable(prim_a)
transform = xform.AddTransformOp()
mat = Gf.Matrix4d()
mat.SetTranslateOnly(Gf.Vec3d(10.0, 1.0, 1.0))
mat.SetRotateOnly(Gf.Rotation(Gf.Vec3d(0, 1, 0), 290))
transform.Set(mat)
# Create a second cube
result, path_b = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
prim_b = stage.GetPrimAtPath(path_b)
# Get the transform of the first cube
pose = omni.usd.utils.get_world_transform_matrix(prim_a)
# Clear the transform on the second cube
xform = UsdGeom.Xformable(prim_b)
xform.ClearXformOpOrder()
# Set the pose of prim_b to that of prim_b
xform_op = xform.AddXformOp(UsdGeom.XformOp.TypeTransform, UsdGeom.XformOp.PrecisionDouble, "")
xform_op.Set(pose)

Get World Transform At Current Timestamp For Selected Prims

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import omni
from pxr import UsdGeom, Gf

usd_context = omni.usd.get_context()
stage = usd_context.get_stage()

#### For testing purposes we create and select a prim
#### This section can be removed if you already have a prim selected
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
cube_prim = stage.GetPrimAtPath(path)
# change the cube pose
xform = UsdGeom.Xformable(cube_prim)
transform = xform.AddTransformOp()
mat = Gf.Matrix4d()
mat.SetTranslateOnly(Gf.Vec3d(10.0, 1.0, 1.0))
mat.SetRotateOnly(Gf.Rotation(Gf.Vec3d(0, 1, 0), 290))
transform.Set(mat)
omni.usd.get_context().get_selection().set_prim_path_selected(path, True, True, True, False)
####

# Get list of selected primitives
selected_prims = usd_context.get_selection().get_selected_prim_paths()
# Get the current timecode
timeline = omni.timeline.get_timeline_interface()
timecode = timeline.get_current_time() * timeline.get_time_codes_per_seconds()
# Loop through all prims and print their transforms
for s in selected_prims:
    curr_prim = stage.GetPrimAtPath(s)
    print("Selected", s)
    pose = omni.usd.utils.get_world_transform_matrix(curr_prim, timecode)
    print("Matrix Form:", pose)
    print("Translation: ", pose.ExtractTranslation())
    q = pose.ExtractRotation().GetQuaternion()
    print(
        "Rotation: ", q.GetReal(), ",", q.GetImaginary()[0], ",", q.GetImaginary()[1], ",", q.GetImaginary()[2]
    )

Save current stage to USD

This can be useful if generating a stage in python and you want to store it to reload later to debugging

1
2
3
4
5
6
7
8
9
import omni
import carb

# Change server below to your nucleus install
default_server = carb.settings.get_settings().get("/isaac/nucleus/default")
# Create a prim
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cube")
# Change the path as needed
omni.usd.get_context().save_as_stage(default_server + "/Users/test/saved.usd", None)

Simple Async Task

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import asyncio
import omni

# Async task that pauses simulation once the incoming task is complete
async def pause_sim(task):
    done, pending = await asyncio.wait({task})
    if task in done:
        print("Waited until next frame, pausing")
        omni.timeline.get_timeline_interface().pause()

# Start simulation, then wait a frame and run the pause_sim task
omni.timeline.get_timeline_interface().play()
task = asyncio.ensure_future(omni.kit.app.get_app().next_update_async())
asyncio.ensure_future(pause_sim(task))

Multi-Camera

The below script will create multiple viewports, set active camera, resolution for each viewport and camera positions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import carb
import omni.kit
import numpy as np
import asyncio
import omni.syntheticdata._syntheticdata as gt
from PIL import Image

stage = omni.usd.get_context().get_stage()
settings_interface = carb.settings.acquire_settings_interface()
viewport_interface = omni.kit.viewport.get_viewport_interface()

# Create camera
camera_path = "/World/Camera"
camera_prim = stage.DefinePrim(camera_path, "Camera")

# Create cube
cube_path = "/World/Cube"
cube_prim = stage.DefinePrim(cube_path, "Cube")

# Create new viewport, set active camera and resolution
viewport_handle = viewport_interface.create_instance()
viewport_window = viewport_interface.get_viewport_window(viewport_handle)
viewport_window.set_active_camera(camera_path)
viewport_window.set_texture_resolution(500, 300)
viewport_window.set_window_pos(300, 500)
viewport_window.set_camera_position(camera_path, 0.0, 0.0, 50.0, True)
viewport_window.set_camera_target(camera_path, 0.0, 0.0, 0.0, True)

# Get existing viewport, set active camera
viewport_handle_2 = viewport_interface.get_instance('Viewport')
viewport_window_2 = viewport_interface.get_viewport_window(viewport_handle_2)
viewport_window_2.set_active_camera("OmniverseKit_Persp")
viewport_window_2.set_camera_position("/OmniverseKit_Persp", 50.0, 50.0, 50.0, True)
viewport_window_2.set_camera_target("/OmniverseKit_Persp", 0.0, 0.0, 0.0, True)

Next, to create, initialize RGB sensor, get ground truth and save to disk, append the below snippet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def save_rgb(rgb_data, file_name):
    rgb_image_data = np.frombuffer(rgb_data, dtype=np.uint8).reshape(*rgb_data.shape, -1)
    rgb_img = Image.fromarray(rgb_image_data, "RGBA")
    rgb_img.save(file_name+".png")

async def get_synthetic_data(sensor_dict):
    global count

    tick_count = 1
    # Sensor creation
    while True:
        flag = 0
        await omni.kit.app.get_app().next_update_async()
        for sensor_name, sensor_details in sensor_dict.items():
            if not sensor_details[1]:
                sensor_details[1] = syntheticdata_interface.create_sensor(sensor_details[0], sensor_details[2])
                flag = 1
        if flag == 0:
            break
        tick_count = tick_count + 1

    # Sensor initialization
    while True:
        flag = 0
        await omni.kit.app.get_app().next_update_async()
        for sensor_name, sensor_details in sensor_dict.items():
            if not syntheticdata_interface.is_sensor_initialized(sensor_details[1]):
                flag = 1
        if flag == 0:
            break
        tick_count = tick_count + 1

    # Get Sensor data
    rgb_data = None
    for sensor_name, sensor_details in sensor_dict.items():
        if "RGB" in sensor_name:
            sensor_data = syntheticdata_interface.get_sensor_host_uint32_texture_array(sensor_details[1])
            save_rgb(sensor_data, sensor_name)


syntheticdata_interface = gt.acquire_syntheticdata_interface()
rgb_sensor = syntheticdata_interface.create_sensor(gt.SensorType.Rgb, viewport_window)
rgb_sensor_2 = syntheticdata_interface.create_sensor(gt.SensorType.Rgb, viewport_window_2)

sensor_dict = {
                "RGB" : [gt.SensorType.Rgb, rgb_sensor, viewport_window],
                "RGB2" : [gt.SensorType.Rgb, rgb_sensor, viewport_window_2]
            }

asyncio.ensure_future(get_synthetic_data(sensor_dict))

Convert Asset to USD

The below script will convert a non-USD asset like OBJ/STL/FBX to USD.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import carb
import omni
import asyncio


async def convert_asset_to_usd(input_obj: str, output_usd: str):
    import omni.kit.asset_converter

    def progress_callback(progress, total_steps):
        pass

    converter_context = omni.kit.asset_converter.AssetConverterContext()
    # setup converter and flags
    # converter_context.ignore_material = False
    # converter_context.ignore_animation = False
    # converter_context.ignore_cameras = True
    # converter_context.single_mesh = True
    # converter_context.smooth_normals = True
    # converter_context.preview_surface = False
    # converter_context.support_point_instancer = False
    # converter_context.embed_mdl_in_usd = False
    # converter_context.use_meter_as_world_unit = True
    # converter_context.create_world_as_default_root_prim = False
    instance = omni.kit.asset_converter.get_instance()
    task = instance.create_converter_task(input_obj, output_usd, progress_callback, converter_context)
    success = await task.wait_until_finished()
    if not success:
        carb.log_error(task.get_status(), task.get_detailed_error())
    print("converting done")


asyncio.ensure_future(
    convert_asset_to_usd(
        "</path/to/mesh.obj>",
        "</path/to/mesh.usd>",
    )
)

The details about the optional import options in lines 13-23 can be found here.

Get Camera Parameters

The below script show how to get the camera parameters associated with a viewport.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import omni
from omni.syntheticdata import helpers
import math

stage = omni.usd.get_context().get_stage()
viewport = omni.kit.viewport.get_viewport_interface()

# acquire the viewport window
viewport_handle = viewport.get_instance("Viewport")
viewport_window = viewport.get_viewport_window(viewport_handle)
# Set viewport resolution, changes will occur on next frame
viewport_window.set_texture_resolution(512, 512)
width = 512
height = 512
aspect_ratio = width / height
# get camera prim attached to viewport
camera = stage.GetPrimAtPath(viewport_window.get_active_camera())
focal_length = camera.GetAttribute("focalLength").Get()
horiz_aperture = camera.GetAttribute("horizontalAperture").Get()
vert_aperture = camera.GetAttribute("verticalAperture").Get()
# Pixels are square so we can also do:
# vert_aperture = height / width * horiz_aperture
near, far = camera.GetAttribute("clippingRange").Get()
fov = 2 * math.atan(horiz_aperture / (2 * focal_length))

# helper to compute projection matrix
proj_mat = helpers.get_projection_matrix(fov, aspect_ratio, near, far)

# compute focal point and center
focal_x = height * focal_length / vert_aperture
focal_y = width * focal_length / horiz_aperture
center_x = height * 0.5
center_y = width * 0.5

Get Size of a Mesh

The snippet below shows how to get the size of a mesh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import omni
from pxr import Usd, UsdGeom, Gf

stage = omni.usd.get_context().get_stage()
result, path = omni.kit.commands.execute("CreateMeshPrimCommand", prim_type="Cone")
# Get the prim
prim = stage.GetPrimAtPath(path)
# Get the size
bbox_cache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), includedPurposes=[UsdGeom.Tokens.default_])
bbox_cache.Clear()
prim_bbox = bbox_cache.ComputeWorldBound(prim)
prim_range = prim_bbox.ComputeAlignedRange()
prim_size = prim_range.GetSize()
print(prim_size)