Source code for genesis.engine.entities.rigid_entity.rigid_geom

import os
import pickle as pkl
from typing import TYPE_CHECKING

import gstaichi as ti
import igl
import numpy as np
import skimage
import torch
import trimesh

import genesis as gs
import genesis.utils.geom as gu
import genesis.utils.mesh as mu
from genesis.repr_base import RBC
from genesis.utils.misc import tensor_to_array, ti_to_torch, DeprecationError

if TYPE_CHECKING:
    from genesis.engine.materials.rigid import Rigid as RigidMaterial
    from genesis.engine.mesh import Mesh
    from genesis.engine.solvers.rigid.rigid_solver import RigidSolver

    from .rigid_entity import RigidEntity
    from .rigid_link import RigidLink


NUM_VERTS_VISUAL_GEOM_AABB = 200


[docs]@ti.data_oriented class RigidGeom(RBC): """ A `RigidGeom` is the basic building block of a `RigidEntity` for collision checking. It is usually constructed from a single mesh. This can be accessed via `link.geoms`. """ def __init__( self, link: "RigidLink", idx, cell_start: int, vert_start: int, face_start: int, edge_start: int, verts_state_start: int, mesh: "Mesh", type: gs.GEOM_TYPE, friction: float, sol_params, init_pos, init_quat, needs_coup: bool, contype, conaffinity, center_init=None, data=None, ): self._link: "RigidLink" = link self._entity: "RigidEntity" = link.entity self._material: "RigidMaterial" = link.entity.material self._solver: "RigidSolver" = link.entity.solver self._mesh: "Mesh" = mesh self._uid = gs.UID() self._idx = idx self._type: gs.GEOM_TYPE = type self._friction: float = friction self._sol_params = sol_params self._needs_coup: bool = needs_coup self._contype = int(contype) self._conaffinity = int(conaffinity) self._is_convex: bool = mesh.is_convex self._cell_start: int = cell_start self._vert_start: int = vert_start self._face_start: int = face_start self._edge_start: int = edge_start self._verts_state_start: int = verts_state_start self._coup_softness: float = self._material.coup_softness self._coup_friction: float = self._material.coup_friction self._coup_restitution: float = self._material.coup_restitution self._init_pos: np.ndarray = init_pos self._init_quat: np.ndarray = init_quat # For heterogeneous simulation: which environments this geom is active in (None = all envs) self.active_envs_mask: torch.Tensor | None = None self.active_envs_idx: np.ndarray | None = None self._init_verts = mesh.verts self._init_faces = mesh.faces self._init_edges = mesh.get_unique_edges() self._init_normals = mesh.normals self._uvs = mesh.uvs self._surface = mesh.surface self._metadata = mesh.metadata if center_init is None: self._init_center_pos = np.repeat( self._init_verts.mean(0, keepdims=True), repeats=self._init_verts.shape[0], axis=0 ) else: self._init_center_pos = np.array(center_init) self._data = np.zeros([7]) if data is not None: self._data[: len(data)] = data # verts and faces for sdf genertaion if "sdf_mesh" in self._metadata: self._sdf_verts = np.ascontiguousarray(self._metadata["sdf_mesh"].vertices) self._sdf_faces = np.ascontiguousarray(self._metadata["sdf_mesh"].faces) else: self._sdf_verts = np.array(self._init_verts) self._sdf_faces = np.array(self._init_faces) if len(self._sdf_faces) > 50000: mesh_descr = f"({mesh.metadata['mesh_path']})" if "mesh_path" in mesh.metadata else "" gs.logger.warning( f"Beware that SDF pre-processing of mesh {mesh_descr} having more than 50000 vertices may take a very " "long time (>10min) and require large RAM allocation (>20Gb). Please either enable convexify or " "decimation. (see FileMorph options)" ) # collision mesh uses default color self._preprocess() def _build(self): pass def _preprocess(self): # compute file name via hashing for caching self._gsd_path = mu.get_gsd_path( self._init_verts, self._init_faces, self._material.sdf_cell_size, self._material.sdf_min_res, self._material.sdf_max_res, ) # loading pre-computed cache if available is_cached_loaded = False if os.path.exists(self._gsd_path): gs.logger.debug(f"Preprocessed file (`.gsd`) found in cache for geom idx {self._idx}.") try: with open(self._gsd_path, "rb") as file: gsd_dict = pkl.load(file) is_cached_loaded = True except (EOFError, ModuleNotFoundError, pkl.UnpicklingError, TypeError, MemoryError): gs.logger.info("Ignoring corrupted cache.") if not is_cached_loaded: with gs.logger.timer(f"Preprocessing geom idx ~~<{self._idx}>~~."): ######## sdf ######## lower = self._init_verts.min(axis=0) upper = self._init_verts.max(axis=0) center = (upper + lower) / 2.0 # NOTE: sdf size is from the center of the lower voxel cell to the center of the upper voxel cell # add padding. Adjust the cell size to keep resolution within bounds. padding_ratio = 0.2 grid_size = (upper - lower).max() * padding_ratio + (upper - lower) sdf_cell_size = gs.EPS + np.clip( self._material.sdf_cell_size, grid_size.max() / (self._material.sdf_max_res - 1), grid_size.min() / max(self._material.sdf_min_res - 1, 2), ) sdf_res = np.ceil(grid_size / sdf_cell_size).astype(gs.np_int) + 1 # round up to multiple of sdf_cell_size grid_size = (sdf_res - 1) * sdf_cell_size halfsize = grid_size / 2.0 voxel_lower = center - halfsize voxel_upper = center + halfsize x = np.linspace(voxel_lower[0], voxel_upper[0], sdf_res[0]) y = np.linspace(voxel_lower[1], voxel_upper[1], sdf_res[1]) z = np.linspace(voxel_lower[2], voxel_upper[2], sdf_res[2]) X, Y, Z = np.meshgrid(x, y, z, indexing="ij") query_points = np.stack([X, Y, Z], axis=-1).reshape((-1, 3)) sdf_val = self._compute_sd(query_points) sdf_closest_vert = self._compute_closest_verts(query_points) sdf_val = sdf_val.reshape(sdf_res) sdf_closest_vert = sdf_closest_vert.reshape(sdf_res) T_mesh_to_centered = np.eye(4) T_mesh_to_centered[:3, 3] = -center T_centered_to_sdf = np.eye(4) T_centered_to_sdf[:3, :3] *= (sdf_res - 1) / grid_size T_centered_to_sdf[:3, 3] = (sdf_res - 1) / 2 T_mesh_to_sdf = T_centered_to_sdf @ T_mesh_to_centered ######## sdf gradient ######## if self.type == gs.GEOM_TYPE.TERRAIN: # terrain uses finite difference for sdf gradient computation # dummy sdf_grad_delta = 0.0 sdf_grad = np.zeros([*sdf_res, 3]) else: sdf_grad_delta = sdf_cell_size * 1e-2 sdf_grad = self._compute_sd_grad(query_points, sdf_grad_delta).reshape([*sdf_res, 3]) ######## adjacency graph ######## all_vert_neighbors_list = trimesh.Trimesh( vertices=self._init_verts, faces=self._init_faces, process=False ).vertex_neighbors assert self.n_verts == len(all_vert_neighbors_list) vert_neighbor_start = list() vert_n_neighbors = list() vert_neighbors = list() n = 0 for vert_neighbors_list in all_vert_neighbors_list: n_vert_neighbors = len(vert_neighbors_list) vert_neighbors += vert_neighbors_list vert_neighbor_start.append(n) vert_n_neighbors.append(n_vert_neighbors) n += n_vert_neighbors vert_neighbors = np.array(vert_neighbors) vert_n_neighbors = np.array(vert_n_neighbors) vert_neighbor_start = np.array(vert_neighbor_start) # caching gsd_dict = { "sdf_res": sdf_res, "sdf_val": sdf_val, "sdf_grad": sdf_grad, "sdf_max": np.max(sdf_val), "sdf_grad_delta": sdf_grad_delta, "sdf_cell_size": sdf_cell_size, "sdf_closest_vert": sdf_closest_vert, "T_mesh_to_sdf": T_mesh_to_sdf, "vert_neighbors": vert_neighbors, "vert_n_neighbors": vert_n_neighbors, "vert_neighbor_start": vert_neighbor_start, } os.makedirs(os.path.dirname(self._gsd_path), exist_ok=True) with open(self._gsd_path, "wb") as file: pkl.dump(gsd_dict, file) self._sdf_res = gsd_dict["sdf_res"] self._sdf_val = gsd_dict["sdf_val"] self._sdf_grad = gsd_dict["sdf_grad"] self._sdf_max = gsd_dict["sdf_max"] self._sdf_cell_size = gsd_dict["sdf_cell_size"] self._sdf_grad_delta = gsd_dict["sdf_grad_delta"] self._sdf_closest_vert = gsd_dict["sdf_closest_vert"] self._T_mesh_to_sdf = gsd_dict["T_mesh_to_sdf"] self.vert_neighbors = gsd_dict["vert_neighbors"] self.vert_n_neighbors = gsd_dict["vert_n_neighbors"] self.vert_neighbor_start = gsd_dict["vert_neighbor_start"] def _compute_sd(self, query_points): signed_distance, *_ = igl.signed_distance(query_points, self._sdf_verts, self._sdf_faces) return signed_distance.astype(gs.np_float, copy=False) def _compute_closest_verts(self, query_points): _, closest_faces, *_ = igl.signed_distance(query_points, self._init_verts, self._init_faces) verts_ids = self._init_faces[closest_faces] verts_ids = verts_ids[ np.arange(len(query_points), dtype=gs.np_int), np.argmin(np.linalg.norm(self._init_verts[verts_ids] - query_points[:, None, :], axis=-1), axis=-1), ] return verts_ids def _compute_sd_grad(self, query_points, delta=5e-4): ######## sdf gradient via finite differencing ######## sd_val_xpos = self._compute_sd(query_points + np.array([delta, 0.0, 0.0])) sd_val_xneg = self._compute_sd(query_points - np.array([delta, 0.0, 0.0])) sd_val_ypos = self._compute_sd(query_points + np.array([0.0, delta, 0.0])) sd_val_yneg = self._compute_sd(query_points - np.array([0.0, delta, 0.0])) sd_val_zpos = self._compute_sd(query_points + np.array([0.0, 0.0, delta])) sd_val_zneg = self._compute_sd(query_points - np.array([0.0, 0.0, delta])) sd_grad_x = (sd_val_xpos - sd_val_xneg) / (2 * delta) sd_grad_y = (sd_val_ypos - sd_val_yneg) / (2 * delta) sd_grad_z = (sd_val_zpos - sd_val_zneg) / (2 * delta) sd_grad = np.stack([sd_grad_x, sd_grad_y, sd_grad_z], -1) return sd_grad
[docs] def get_trimesh(self): """ Get the geom's trimesh object. """ return self._mesh.trimesh
[docs] def get_sdf_trimesh(self, color=[1.0, 1.0, 0.6, 1.0]): """ Reconstruct trimesh object from sdf. """ if self.sdf_val.min() >= 0: gs.logger.warning("SDF is positive everywhere. Returning empty mesh.") return trimesh.Trimesh(vertices=[], faces=[]) else: vertices, faces, _, _ = skimage.measure.marching_cubes(self.sdf_val, level=0) vertices = (np.linalg.inv(self.T_mesh_to_sdf) @ np.hstack([vertices, np.ones([len(vertices), 1])]).T)[:3].T mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) mesh.visual = trimesh.visual.ColorVisuals(vertex_colors=np.tile(np.array([color]), [len(vertices), 1])) return mesh
[docs] def visualize_sdf( self, pos=None, T=None, color=(1.0, 1.0, 0.3, 1.0), show_axis=False, axis_color=(1.0, 0.0, 0.0, 1.0), axis_length=0.3, show_boundary=False, boundary_color=(0.0, 1.0, 0.0, 0.2), ): """ Visualizes the signed distance field (SDF) of the rigid geometry in the viewer. """ if self._solver.scene.viewer is None: gs.raise_exception("Viewer is not available.") if self._solver.n_envs > 0: gs.raise_exception("Batched scene does not support sdf visualization.") sdf_mesh = self.get_sdf_trimesh(color) if T is None: if pos is None: T = gu.trans_quat_to_T(tensor_to_array(self.get_pos()), tensor_to_array(self.get_quat())) else: T = gu.trans_to_T(np.array(pos)) else: T = np.array(T) self._solver.scene.draw_debug_mesh(sdf_mesh, T=T) if show_axis: axis_mesh = trimesh.creation.axis(origin_size=0.01, axis_radius=0.005, axis_length=axis_length) axis_mesh.visual = trimesh.visual.ColorVisuals( vertex_colors=np.tile(np.array([axis_color]), [len(axis_mesh.vertices), 1]) ) self._solver.scene.draw_debug_mesh(axis_mesh, T=T) if show_boundary: boundary_mesh = trimesh.creation.box(extents=[1, 1, 1]) boundary_mesh.vertices = gu.inv_transform_by_T( (boundary_mesh.vertices + 0.5) * (self.sdf_res - 1), self.T_mesh_to_sdf ) boundary_mesh.visual = trimesh.visual.ColorVisuals( vertex_colors=np.tile(np.array([boundary_color]), [len(boundary_mesh.vertices), 1]) ) self._solver.scene.draw_debug_mesh(boundary_mesh, T=T)
[docs] def set_friction(self, friction): """ Set the friction coefficient of this geometry. """ if friction < 0: gs.raise_exception("`friction` must be non-negative.") self._friction = friction if self._solver.is_built: self._solver.set_geom_friction(friction, self._idx)
# ------------------------------------------------------------------------------------ # -------------------------------- real-time state ----------------------------------- # ------------------------------------------------------------------------------------
[docs] @gs.assert_built def get_pos(self, envs_idx=None): """ Get the position of the geom in world frame. """ tensor = ti_to_torch(self._solver.geoms_state.pos, envs_idx, self._idx, transpose=True, copy=True)[..., 0, :] return tensor[0] if self._solver.n_envs == 0 else tensor
[docs] @gs.assert_built def get_quat(self, envs_idx=None): """ Get the quaternion of the geom in world frame. """ tensor = ti_to_torch(self._solver.geoms_state.quat, envs_idx, self._idx, transpose=True, copy=True)[..., 0, :] return tensor[0] if self._solver.n_envs == 0 else tensor
[docs] @gs.assert_built def get_verts(self): """ Get the vertices of the geom in world frame. """ self._solver.update_verts_for_geoms(self._idx) verts_idx = slice(self.verts_state_start, self.verts_state_end) if self.is_fixed and not self._entity._batch_fixed_verts: tensor = ti_to_torch(self._solver.fixed_verts_state.pos, verts_idx, copy=True) else: tensor = ti_to_torch(self._solver.free_verts_state.pos, None, verts_idx, transpose=True, copy=True) if self._solver.n_envs == 0: tensor = tensor[0] return tensor
[docs] @gs.assert_built def get_AABB(self): """ Get the axis-aligned bounding box (AABB) of the geom in world frame. """ verts = self.get_verts() return torch.stack((verts.min(dim=-2).values, verts.max(dim=-2).values), dim=-2)
[docs] def set_sol_params(self, sol_params): """ Set the solver parameters of this geometry. """ if self._solver.is_built: self._solver.set_sol_params(sol_params, geoms_idx=self._idx, envs_idx=None) else: self._sol_params = sol_params
@property def sol_params(self): """ Get the solver parameters of this geometry. """ if self._solver.is_built: return self._solver.get_sol_params(geoms_idx=self._idx, envs_idx=None)[0] return self._sol_params # ------------------------------------------------------------------------------------ # ----------------------------------- properties ------------------------------------- # ------------------------------------------------------------------------------------ @property def uid(self): """ Get the unique ID of the geom. """ return self._uid @property def idx(self) -> int: """ Get the global index of the geom in RigidSolver. """ return self._idx @property def type(self) -> gs.GEOM_TYPE: """ Get the type of the geom. """ return self._type @property def friction(self): """ Get the friction coefficient of the geom. """ return self._friction @property def data(self): """ Get the additional data of the geom. """ return self._data @property def metadata(self): """ Get the metadata of the geom. """ return self._metadata @property def link(self) -> "RigidLink": """ Get the link that the geom belongs to. """ return self._link @property def entity(self) -> "RigidEntity": """ Get the entity that the geom belongs to. """ return self._entity @property def solver(self) -> "RigidSolver": """ Get the solver that the geom belongs to.s """ return self._solver @property def is_convex(self) -> bool: """ Get whether the geom is convex. """ return self._is_convex @property def mesh(self) -> "Mesh": return self._mesh @property def needs_coup(self) -> bool: """ Get whether the geom needs coupling with other non-rigid entities. """ return self._needs_coup @property def contype(self) -> int: """ Get the contact type of the geometry for collision pair filtering. The two geoms are deemed "compatible" (i.e. collisions between them is allowed) if the 'contype' of one geom and the 'conaffinity' of the other geom have a common bit set to 1, i.e. `(geom1.contype & geom2.conaffinity) || (geom2.contype & geom1.conaffinity) == True`. This is a powerful mechanism borrowed from Open Dynamics Engine. """ return self._contype @property def conaffinity(self) -> int: """ Get the contact affinity of the geometry for collision pair filtering. See `contype` documentation for details. """ return self._conaffinity @property def coup_softness(self) -> float: """ Get the softness coefficient of the geom for coupling. """ return self._coup_softness @property def coup_friction(self) -> float: """ Get the friction coefficient of the geom for coupling. """ return self._coup_friction @property def coup_restitution(self) -> float: """ Get the restitution coefficient of the geom for coupling. """ return self._coup_restitution @property def init_pos(self) -> np.ndarray: """ Get the initial position of the geom. """ return self._init_pos @property def init_quat(self) -> np.ndarray: """ Get the initial quaternion of the geom. """ return self._init_quat @property def init_verts(self): """ Get the initial vertices of the geom. """ return self._init_verts @property def init_faces(self): """ Get the initial faces of the geom. """ return self._init_faces @property def init_edges(self): """ Get the initial edges of the geom. """ return self._init_edges @property def init_normals(self): """ Get the initial normals of the geom. """ return self._init_normals @property def init_center_pos(self): """ Get the initial center position of the geom. """ return self._init_center_pos @property def uvs(self): """ Get the UV coordinates of the geom. """ return self._uvs @property def surface(self): """ Get the surface object of the geom. """ return self._surface @property def gsd_path(self): """ Get the path to the preprocessed `.gsd` file. """ return self._gsd_path @property def sdf_res(self): """ Get the resolution of the geom's signed distance field (SDF). """ return self._sdf_res @property def sdf_val(self): """ Get the signed distance field (SDF) of the geom. """ return self._sdf_val @property def sdf_val_flattened(self): """ Get the flattened signed distance field (SDF) of the geom. """ return self._sdf_val.reshape((-1,)) @property def sdf_grad(self): """ Get the gradient of the geom's signed distance field (SDF). """ return self._sdf_grad @property def sdf_grad_flattened(self): """ Get the flattened gradient of the geom's signed distance field (SDF). """ return self._sdf_grad.reshape(-1, 3) @property def sdf_max(self): """ Get the maximum value of the geom's signed distance field (SDF). """ return self._sdf_max @property def sdf_cell_size(self): """ Get the cell size of the geom's signed distance field (SDF). """ return self._sdf_cell_size @property def sdf_grad_delta(self): """ Get the delta value for computing the gradient of the geom's signed distance field (SDF). """ return self._sdf_grad_delta @property def sdf_closest_vert(self): """ Get the closest vertex of each cell of the geom's signed distance field (SDF). """ return self._sdf_closest_vert @property def sdf_closest_vert_flattened(self): """ Get the flattened closest vertex of each cell of the geom's signed distance field (SDF). """ return self._sdf_closest_vert.reshape((-1,)) @property def T_mesh_to_sdf(self): """ Get the transformation matrix of the geom's mesh frame w.r.t its signed distance field (SDF) frame. """ return self._T_mesh_to_sdf @property def n_cells(self): """ Number of cells in the geom's signed distance field (SDF). """ return np.prod(self._sdf_res) @property def n_verts(self) -> int: """ Number of vertices of the geom. """ return len(self._init_verts) @property def n_faces(self) -> int: """ Number of faces of the geom. """ return len(self._init_faces) @property def n_edges(self): """ Number of edges of the geom. """ return len(self._init_edges) @property def cell_start(self): """ Get the starting index of the cells of the signed distance field (SDF) in the rigid solver. """ return self._cell_start @property def vert_start(self): """ Get the starting index of the geom's vertices in the rigid solver. """ return self._vert_start @property def face_start(self): """ Get the starting index of the geom's faces in the rigid solver. """ return self._face_start @property def edge_start(self): """ Get the starting index of the geom's edges in the rigid solver. """ return self._edge_start @property def verts_state_start(self): """ Get the starting index of the geom's vertices in the rigid solver. """ return self._verts_state_start @property def cell_end(self): """ Get the ending index of the cells of the signed distance field (SDF) in the rigid solver. """ return self.n_cells + self.cell_start @property def vert_end(self): """ Get the ending index of the geom's vertices in the rigid solver. """ return self.n_verts + self.vert_start @property def verts_state_end(self): """ Get the ending index of the geom's vertices in the rigid solver. """ return self.n_verts + self.verts_state_start @property def face_end(self): """ Get the ending index of the geom's faces in the rigid solver. """ return self.n_faces + self.face_start @property def edge_end(self): """ Get the ending index of the geom's edges in the rigid solver. """ return self.n_edges + self.edge_start @property def is_built(self): """ Whether the rigid entity the geom belongs to is built. """ return self.entity.is_built @property def is_free(self): raise DeprecationError("This property has been removed.") @property def is_fixed(self) -> bool: """ Whether this geom is fixed in the world. """ return self.link.is_fixed # ------------------------------------------------------------------------------------ # -------------------------------------- repr ---------------------------------------- # ------------------------------------------------------------------------------------ def _repr_brief(self): return f"{self._repr_type()}: {self._uid}, idx: {self._idx} (from entity {self._entity.uid}, link {self._link.uid})"
[docs]@ti.data_oriented class RigidVisGeom(RBC): """ A `RigidVisGeom` is a counterpart of `RigidGeom`, but for visualization purposes. This can be accessed via `link.vis_geoms`. """ def __init__( self, link, idx, vvert_start, vface_start, vmesh, init_pos, init_quat, ): self._link = link self._entity = link.entity self._material = link.entity.material self._solver = link.entity.solver self._vmesh = vmesh # Lazy-initialize low-res geometry because it is usually unused and may be slow to compute self._init_pos_tc = torch.from_numpy(init_pos).to(device=gs.device, dtype=gs.tc_float) self._init_quat_tc = torch.from_numpy(init_quat).to(device=gs.device, dtype=gs.tc_float) self._aabb_verts: torch.Tensor | None = None self._uid = gs.UID() self._idx = idx self._vvert_start = vvert_start self._vface_start = vface_start self._init_pos = init_pos self._init_quat = init_quat # For heterogeneous simulation: which environments this vgeom is active in (None = all envs) self.active_envs_mask: torch.Tensor | None = None self.active_envs_idx: np.ndarray | None = None self._init_vverts = vmesh.verts self._init_vfaces = vmesh.faces self._init_vnormals = vmesh.normals self._uvs = vmesh.uvs self._surface = vmesh.surface self._metadata = vmesh.metadata self._color = vmesh._color def _build(self): pass
[docs] def get_trimesh(self): """ Get trimesh object. """ return self._vmesh.trimesh
# ------------------------------------------------------------------------------------ # -------------------------------- real-time state ----------------------------------- # ------------------------------------------------------------------------------------
[docs] @gs.assert_built def get_pos(self, envs_idx=None): """ Get the position of the geom in world frame. """ tensor = ti_to_torch(self._solver.vgeoms_state.pos, envs_idx, self._idx, transpose=True, copy=True)[..., 0, :] return tensor[0] if self._solver.n_envs == 0 else tensor
[docs] @gs.assert_built def get_quat(self, envs_idx=None): """ Get the quaternion of the geom in world frame. """ tensor = ti_to_torch(self._solver.vgeoms_state.quat, envs_idx, self._idx, transpose=True, copy=True)[..., 0, :] return tensor[0] if self._solver.n_envs == 0 else tensor
[docs] @gs.assert_built def get_vAABB(self, envs_idx=None): """ Get the axis-aligned bounding box (AABB) of the geom in world frame. This method computes the bounding box of the geometry after aggressive decimation of its convex hull. This is usually sufficiently accurate (<1mm), while significantly improving runtime speed and reducing memory footprint. """ if self._aabb_verts is None: # Aggressiveness has been tuned to give sub-millimeter accuracy on Franka robot in random configurations aabb_mesh = self.vmesh.copy() aabb_mesh.convexify() aabb_mesh.decimate(decimate_face_num=NUM_VERTS_VISUAL_GEOM_AABB, decimate_aggressiveness=3, convexify=False) self._aabb_verts = torch.from_numpy(aabb_mesh.verts).to(dtype=gs.tc_float, device=gs.device) pos, quat = gu.transform_pos_quat_by_trans_quat( self._init_pos_tc, self._init_quat_tc, self.link.get_pos(envs_idx), self.link.get_quat(envs_idx) ) vverts_pos = pos[..., None, :] + gu.transform_by_quat(self._aabb_verts, quat[..., None, :]) return torch.stack((vverts_pos.min(dim=-2).values, vverts_pos.max(dim=-2).values), dim=-2)
# ------------------------------------------------------------------------------------ # ----------------------------------- properties ------------------------------------- # ------------------------------------------------------------------------------------ @property def uid(self): """ Get the unique ID of the vgeom. """ return self._uid @property def idx(self): """ Get the global index of the vgeom in RigidSolver. """ return self._idx @property def link(self): """ Get the link that the vgeom belongs to. """ return self._link @property def entity(self): """ Get the entity that the vgeom belongs to. """ return self._entity @property def vmesh(self): return self._vmesh @property def solver(self): """ Get the solver that the vgeom belongs to. """ return self._solver @property def metadata(self): """ Get the metadata of the vgeom. """ return self._metadata @property def init_pos(self): """ Get the initial position of the vgeom. """ return self._init_pos @property def init_quat(self): """ Get the initial quaternion of the vgeom. """ return self._init_quat @property def init_vverts(self): """ Get the initial vertices of the vgeom. """ return self._init_vverts @property def init_vfaces(self): """ Get the initial faces of the vgeom. """ return self._init_vfaces @property def init_vnormals(self): """ Get the initial normals of the vgeom. """ return self._init_vnormals @property def uvs(self): """ Get the UV coordinates of the vgeom. """ return self._uvs @property def surface(self): """ Get the surface object of the vgeom. """ return self._surface @property def n_vverts(self): """ Number of vertices of the vgeom. """ return len(self._init_vverts) @property def n_vfaces(self): """ Number of faces of the vgeom. """ return len(self._init_vfaces) @property def vvert_start(self): """ Get the starting index of the vgeom's vertices in the rigid solver. """ return self._vvert_start @property def vface_start(self): """ Get the starting index of the vgeom's faces in the rigid solver. """ return self._vface_start @property def vvert_end(self): """ Get the ending index of the vgeom's vertices in the rigid solver. """ return self.n_vverts + self.vvert_start @property def vface_end(self): """ Get the ending index of the vgeom's faces in the rigid solver. """ return self.n_vfaces + self.vface_start @property def is_built(self): """ Whether the rigid entity the vgeom belongs to is built. """ return self.entity.is_built @property def is_free(self): raise DeprecationError("This property has been removed.") @property def is_fixed(self) -> bool: """ Whether this vgeom is fixed in the world. """ return self.link.is_fixed # ------------------------------------------------------------------------------------ # -------------------------------------- repr ---------------------------------------- # ------------------------------------------------------------------------------------ def _repr_brief(self): return f"{self._repr_type()}: {self._uid}, idx: {self._idx} (from entity {self._entity.uid}, link {self._link.uid})"