Source code for sapien.wrapper.actor_builder

from .. import pysapien as sapien
from dataclasses import dataclass
import numpy as np
import typing
from typing import List, Union, Dict, Any, Tuple, Literal

from .coacd import do_coacd


[docs] def preprocess_mesh_file(filename: str): """ Process input mesh file to a SAPIEN supported format Args: filename: input mesh file Returns: filename for the generated file or original filename """ from .geometry.usd import convert_usd_to_glb if any(filename.lower().endswith(s) for s in [".usd", ".usda", ".usdc", ".usdz"]): glb_filename = filename + ".sapien.glb" convert_usd_to_glb(filename, glb_filename) return glb_filename return filename
[docs] @dataclass class CollisionShapeRecord: type: Literal[ "convex_mesh", "multiple_convex_meshes", "nonconvex_mesh", "plane", "box", "capsule", "sphere", "cylinder", ] # for mesh type filename: str = "" # mesh & box scale: Tuple = (1, 1, 1) # circle & capsule radius: float = 1 # capsule length: float = 1 material: Union[sapien.physx.PhysxMaterial, None] = None pose: sapien.Pose = sapien.Pose() density: float = 1000 patch_radius: float = 0 min_patch_radius: float = 0 is_trigger: bool = False decomposition: str = "none" decomposition_params: Union[Dict[str, Any], None] = None
[docs] @dataclass class VisualShapeRecord: type: Literal["file", "plane", "box", "capsule", "sphere", "cylinder"] filename: str = "" scale: tuple = (1, 1, 1) radius: float = 1 length: float = 1 material: Union[ sapien.render.RenderMaterial, None ] = None # None is only used for mesh pose: sapien.Pose = sapien.Pose() name: str = ""
Vec3 = Tuple
[docs] class ActorBuilder: def __init__(self): self.collision_records: List[CollisionShapeRecord] = [] self.visual_records: List[VisualShapeRecord] = [] self.use_density = True self.collision_groups = [1, 1, 0, 0] self.scene = None self.physx_body_type = "dynamic" self.name = "" self._mass = 0 self._cmass_local_pose = sapien.Pose() self._inertia = np.zeros(3) self._auto_inertial = True self.initial_pose = sapien.Pose()
[docs] def set_initial_pose(self, pose): self.initial_pose = pose return self
[docs] def set_mass_and_inertia(self, mass, cmass_local_pose, inertia): self._mass = mass self._cmass_local_pose = cmass_local_pose self._inertia = inertia self._auto_inertial = False return self
[docs] def set_name(self, name): self.name = name return self
[docs] def set_scene(self, scene: sapien.Scene): self.scene = scene return self
[docs] def set_physx_body_type(self, type): assert type in ["dynamic", "static", "kinematic"] self.physx_body_type = type return self
[docs] def build_render_component(self): component = sapien.render.RenderBodyComponent() for r in self.visual_records: if r.type != "file": assert isinstance(r.material, sapien.render.RenderMaterial) else: assert r.material is None or isinstance( r.material, sapien.render.RenderMaterial ) if r.type == "plane": shape = sapien.render.RenderShapePlane(r.scale, r.material) elif r.type == "box": shape = sapien.render.RenderShapeBox(r.scale, r.material) elif r.type == "sphere": shape = sapien.render.RenderShapeSphere(r.radius, r.material) elif r.type == "capsule": shape = sapien.render.RenderShapeCapsule(r.radius, r.length, r.material) elif r.type == "cylinder": shape = sapien.render.RenderShapeCylinder( r.radius, r.length, r.material ) elif r.type == "file": shape = sapien.render.RenderShapeTriangleMesh( preprocess_mesh_file(r.filename), r.scale, r.material ) if r.scale[0] * r.scale[1] * r.scale[2] < 0: shape.set_front_face("clockwise") else: raise Exception(f"invalid visual shape type [{r.type}]") shape.local_pose = r.pose shape.name = r.name component.attach(shape) return component
[docs] def build_physx_component(self, link_parent=None): for r in self.collision_records: assert isinstance(r.material, sapien.physx.PhysxMaterial) if self.physx_body_type == "dynamic": component = sapien.physx.PhysxRigidDynamicComponent() elif self.physx_body_type == "kinematic": component = sapien.physx.PhysxRigidDynamicComponent() component.kinematic = True elif self.physx_body_type == "static": component = sapien.physx.PhysxRigidStaticComponent() elif self.physx_body_type == "link": component = sapien.physx.PhysxArticulationLinkComponent(link_parent) else: raise Exception(f"invalid physx body type [{self.physx_body_type}]") for r in self.collision_records: try: if r.type == "plane": shape = sapien.physx.PhysxCollisionShapePlane( material=r.material, ) shapes = [shape] elif r.type == "box": shape = sapien.physx.PhysxCollisionShapeBox( half_size=r.scale, material=r.material ) shapes = [shape] elif r.type == "capsule": shape = sapien.physx.PhysxCollisionShapeCapsule( radius=r.radius, half_length=r.length, material=r.material, ) shapes = [shape] elif r.type == "cylinder": shape = sapien.physx.PhysxCollisionShapeCylinder( radius=r.radius, half_length=r.length, material=r.material, ) shapes = [shape] elif r.type == "sphere": shape = sapien.physx.PhysxCollisionShapeSphere( radius=r.radius, material=r.material, ) shapes = [shape] elif r.type == "convex_mesh": shape = sapien.physx.PhysxCollisionShapeConvexMesh( filename=preprocess_mesh_file(r.filename), scale=r.scale, material=r.material, ) shapes = [shape] elif r.type == "nonconvex_mesh": shape = sapien.physx.PhysxCollisionShapeTriangleMesh( filename=preprocess_mesh_file(r.filename), scale=r.scale, material=r.material, ) shapes = [shape] elif r.type == "multiple_convex_meshes": if r.decomposition == "coacd": params = r.decomposition_params if params is None: params = dict() filename = do_coacd(preprocess_mesh_file(r.filename), **params) else: filename = preprocess_mesh_file(r.filename) shapes = sapien.physx.PhysxCollisionShapeConvexMesh.load_multiple( filename=filename, scale=r.scale, material=r.material, ) else: raise RuntimeError(f"invalid collision shape type [{r.type}]") except RuntimeError as e: # ignore runtime error (e.g., failed to cooke mesh) continue for shape in shapes: shape.local_pose = r.pose shape.set_collision_groups(self.collision_groups) shape.set_density(r.density) shape.set_patch_radius(r.patch_radius) shape.set_min_patch_radius(r.min_patch_radius) component.attach(shape) if not self._auto_inertial and self.physx_body_type != "kinematic": component.mass = self._mass component.cmass_local_pose = self._cmass_local_pose component.inertia = self._inertia return component
[docs] def build_entity(self): entity = sapien.Entity() if self.visual_records: entity.add_component(self.build_render_component()) entity.add_component(self.build_physx_component()) entity.name = self.name return entity
[docs] def build(self, name=None): if name is not None: self.set_name(name) if self.scene is None: raise Exception( "you need to set the scene of the actor builder by calling the set_scene method" ) entity = self.build_entity() entity.name = self.name entity.pose = self.initial_pose # set pose before adding to scene self.scene.add_entity(entity) return entity
[docs] def build_kinematic(self, name=""): self.set_physx_body_type("kinematic") return self.build(name=name)
[docs] def build_static(self, name=""): self.set_physx_body_type("static") return self.build(name=name)
[docs] def add_plane_collision( self, pose: sapien.Pose = sapien.Pose(), material: Union[sapien.physx.PhysxMaterial, None] = None, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="plane", pose=pose, material=material, density=0, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_box_collision( self, pose: sapien.Pose = sapien.Pose(), half_size: Vec3 = (1, 1, 1), material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="box", pose=pose, scale=half_size, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_capsule_collision( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, half_length: float = 1, material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="capsule", pose=pose, radius=radius, length=half_length, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_cylinder_collision( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, half_length: float = 1, material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="cylinder", pose=pose, radius=radius, length=half_length, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_sphere_collision( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="sphere", pose=pose, radius=radius, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_convex_collision_from_file( self, filename, pose: sapien.Pose = sapien.Pose(), scale: Vec3 = (1, 1, 1), material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="convex_mesh", filename=filename, pose=pose, scale=scale, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_multiple_convex_collisions_from_file( self, filename, pose: sapien.Pose = sapien.Pose(), scale: Vec3 = (1, 1, 1), material: Union[sapien.physx.PhysxMaterial, None] = None, density: float = 1000, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, decomposition: typing.Literal["none", "coacd"] = "none", decomposition_params=dict(), ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="multiple_convex_meshes", filename=filename, pose=pose, scale=scale, material=material, density=density, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, decomposition=decomposition, decomposition_params=decomposition_params, ) ) return self
[docs] def add_nonconvex_collision_from_file( self, filename: str, pose: sapien.Pose = sapien.Pose(), scale: Vec3 = (1, 1, 1), material: Union[sapien.physx.PhysxMaterial, None] = None, patch_radius: float = 0, min_patch_radius: float = 0, is_trigger: bool = False, ): if material is None: material = sapien.physx.get_default_material() self.collision_records.append( CollisionShapeRecord( type="nonconvex_mesh", filename=filename, pose=pose, scale=scale, material=material, density=0, patch_radius=patch_radius, min_patch_radius=min_patch_radius, is_trigger=is_trigger, ) ) return self
[docs] def add_plane_visual( self, pose: sapien.Pose = sapien.Pose(), scale: Vec3 = (1, 1, 1), material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is None: material = sapien.render.RenderMaterial() if not isinstance(material, sapien.render.RenderMaterial): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="plane", pose=pose, scale=scale, material=material, name=name ) ) return self
[docs] def add_box_visual( self, pose: sapien.Pose = sapien.Pose(), half_size: Vec3 = (1, 1, 1), material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is None: material = sapien.render.RenderMaterial() if not isinstance(material, sapien.render.RenderMaterial): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="box", pose=pose, scale=half_size, material=material, name=name ) ) return self
[docs] def add_capsule_visual( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, half_length: float = 1, material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is None: material = sapien.render.RenderMaterial() if not isinstance(material, sapien.render.RenderMaterial): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="capsule", pose=pose, radius=radius, length=half_length, material=material, name=name, ) ) return self
[docs] def add_cylinder_visual( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, half_length: float = 1, material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is None: material = sapien.render.RenderMaterial() if not isinstance(material, sapien.render.RenderMaterial): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="cylinder", pose=pose, radius=radius, length=half_length, material=material, name=name, ) ) return self
[docs] def add_sphere_visual( self, pose: sapien.Pose = sapien.Pose(), radius: float = 1, material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is None: material = sapien.render.RenderMaterial() if not isinstance(material, sapien.render.RenderMaterial): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="sphere", pose=pose, radius=radius, material=material, name=name, ) ) return self
[docs] def add_visual_from_file( self, filename: str, pose: sapien.Pose = sapien.Pose(), scale: Vec3 = (1, 1, 1), material: Union[sapien.render.RenderMaterial, None, Vec3] = None, name: str = "", ): if material is not None and not isinstance( material, sapien.render.RenderMaterial ): material = sapien.render.RenderMaterial(base_color=(*material[:3], 1)) self.visual_records.append( VisualShapeRecord( type="file", filename=filename, pose=pose, scale=scale, material=material, name=name, ) ) return self