diff --git a/omnigibson/objects/dataset_object.py b/omnigibson/objects/dataset_object.py index 8b5330a3a..7e6af6161 100644 --- a/omnigibson/objects/dataset_object.py +++ b/omnigibson/objects/dataset_object.py @@ -118,16 +118,6 @@ def __init__( assert len(available_models) > 0, f"No available models found for category {category}!" model = random.choice(available_models) - # If the model is in BAD_CLOTH_MODELS, raise an error for now -- this is a model that's unstable and needs to be fixed - # TODO: Remove this once the asset is fixed! - from omnigibson.utils.bddl_utils import BAD_CLOTH_MODELS - - if prim_type == PrimType.CLOTH and model in BAD_CLOTH_MODELS.get(category, dict()): - raise ValueError( - f"Cannot create cloth object category: {category}, model: {model} because it is " - f"currently broken ): This will be fixed in the next release!" - ) - self._model = model usd_path = self.get_usd_path(category=category, model=model, dataset_type=dataset_type) diff --git a/omnigibson/systems/micro_particle_system.py b/omnigibson/systems/micro_particle_system.py index cca211d6d..68eee9c72 100644 --- a/omnigibson/systems/micro_particle_system.py +++ b/omnigibson/systems/micro_particle_system.py @@ -31,18 +31,16 @@ # Create settings for this module m = create_module_macros(module_path=__file__) - -# TODO: Tune these default values! # TODO (eric): figure out whether one offset can fit all m.MAX_CLOTH_PARTICLES = 20000 # Comes from a limitation in physx - do not increase m.CLOTH_PARTICLE_CONTACT_OFFSET = 0.0075 m.CLOTH_REMESHING_ERROR_THRESHOLD = 0.05 -m.CLOTH_STRETCH_STIFFNESS = 10000.0 -m.CLOTH_BEND_STIFFNESS = 200.0 -m.CLOTH_SHEAR_STIFFNESS = 100.0 -m.CLOTH_DAMPING = 0.2 +m.CLOTH_STRETCH_STIFFNESS = 100.0 +m.CLOTH_BEND_STIFFNESS = 50.0 +m.CLOTH_SHEAR_STIFFNESS = 70.0 +m.CLOTH_DAMPING = 0.02 m.CLOTH_FRICTION = 0.4 -m.CLOTH_DRAG = 0.001 +m.CLOTH_DRAG = 0.02 m.CLOTH_LIFT = 0.003 m.MIN_PARTICLE_CONTACT_OFFSET = 0.005 # Minimum particle contact offset for physical micro particles m.FLUID_PARTICLE_PARTICLE_DISTANCE_SCALE = 0.8 # How much overlap expected between fluid particles at rest @@ -1715,6 +1713,27 @@ def clothify_mesh_prim(self, mesh_prim, remesh=True, particle_distance=None): ms.meshing_isotropic_explicit_remeshing( iterations=5, adaptive=True, targetlen=pymeshlab.AbsoluteValue(particle_distance) ) + # Make sure the clothes is watertight + ms.meshing_repair_non_manifold_edges() + ms.meshing_repair_non_manifold_vertices() + + # If the cloth has multiple pieces, only keep the largest one + ms.generate_splitting_by_connected_components(delete_source_mesh=True) + if len(ms) > 1: + log.warning( + f"The cloth mesh has {len(ms)} disconnected pieces. To simplify, we only keep the mesh with largest face number." + ) + biggest_face_num = 0 + for split_mesh in ms: + face_num = split_mesh.face_number() + if face_num > biggest_face_num: + biggest_face_num = face_num + new_ms = pymeshlab.MeshSet() + for split_mesh in ms: + if split_mesh.face_number() == biggest_face_num: + new_ms.add_mesh(split_mesh) + ms = new_ms + avg_edge_percentage_mismatch = abs( 1.0 - particle_distance / ms.get_geometric_measures()["avg_edge_length"] ) diff --git a/state_fold.py b/state_fold.py new file mode 100644 index 000000000..6ca97b4dd --- /dev/null +++ b/state_fold.py @@ -0,0 +1,125 @@ +from omnigibson.utils.constants import PrimType +from omnigibson.object_states import Folded, Unfolded +from omnigibson.macros import gm +import numpy as np + +import omnigibson as og +import json +import torch + +# Make sure object states and GPU dynamics are enabled (GPU dynamics needed for cloth) +gm.ENABLE_OBJECT_STATES = True +gm.USE_GPU_DYNAMICS = True + + +def main(random_selection=False, headless=False, short_exec=False): + """ + Demo of cloth objects that can potentially be folded. + """ + og.log.info(f"Demo {__file__}\n " + "*" * 80 + "\n Description:\n" + main.__doc__ + "*" * 80) + + cloth_category_models = [ + ("hoodie", "agftpm"), + ] + + for cloth in cloth_category_models: + category = cloth[0] + model = cloth[1] + if model != "agftpm": + continue + print(f"\nCategory: {category}, Model: {model}!!!!!!!!!!!!!!!!!!!!!!!!!!") + # Create the scene config to load -- empty scene + custom cloth object + cfg = { + "scene": { + "type": "Scene", + }, + "objects": [ + { + "type": "DatasetObject", + "name": model, + "category": category, + "model": model, + # "bounding_box": [0.897, 0.568, 0.012], + "prim_type": PrimType.CLOTH, + "abilities": {"cloth": {}}, + # "position": [0, 0, 0.5], + "orientation": [0.7071, 0.0, 0.7071, 0.0], + }, + ], + } + + # Create the environment + env = og.Environment(configs=cfg) + + # Grab object references + carpet = env.scene.object_registry("name", model) + objs = [carpet] + + # Set viewer camera + og.sim.viewer_camera.set_position_orientation( + position=np.array([0.46382895, -2.66703958, 1.22616824]), + orientation=np.array([0.58779174, -0.00231237, -0.00318273, 0.80900271]), + ) + + for _ in range(100): + og.sim.step() + + print("\nCloth state:\n") + + if not short_exec: + # Get particle positions + increments = 100 + obj = objs[0] + + # Fold - first stage + pos = np.asarray(obj.root_link.compute_particle_positions()) + y_min = np.min(pos[:, 1]) + y_max = np.max(pos[:, 1]) + y_mid_bottom = y_min + (y_max - y_min) / 3 + y_mid_top = y_min + 2 * (y_max - y_min) / 3 + bottom_indices = np.where(pos[:, 1] < y_mid_bottom)[0] + bottom_start = np.copy(pos[bottom_indices]) + bottom_end = np.copy(bottom_start) + bottom_end[:, 1] = 2 * y_mid_bottom - bottom_end[:, 1] # Mirror bottom to the above of y_mid_bottom + for ctrl_pts in np.linspace(bottom_start, bottom_end, increments): + obj.root_link.set_particle_positions(torch.tensor(ctrl_pts), idxs=bottom_indices) + og.sim.step() + + # Fold - second stage + pos = np.asarray(obj.root_link.compute_particle_positions()) + y_min = np.min(pos[:, 1]) + y_max = np.max(pos[:, 1]) + y_mid_bottom = y_min + (y_max - y_min) / 3 + y_mid_top = y_min + 2 * (y_max - y_min) / 3 + top_indices = np.where(pos[:, 1] > y_mid_top)[0] + top_start = np.copy(pos[top_indices]) + top_end = np.copy(top_start) + top_end[:, 1] = 2 * y_mid_top - top_end[:, 1] # Mirror top to the below of y_mid_top + for ctrl_pts in np.linspace(top_start, top_end, increments): + obj.root_link.set_particle_positions(torch.tensor(ctrl_pts), idxs=top_indices) + og.sim.step() + + # Fold - third stage + pos = np.asarray(obj.root_link.compute_particle_positions()) + x_min = np.min(pos[:, 0]) + x_max = np.max(pos[:, 0]) + x_mid = (x_min + x_max) / 2 + indices = np.argsort(pos, axis=0)[:, 0][-len(pos) // 2 :] + start = np.copy(pos[indices]) + end = np.copy(start) + end[:, 0] = 2 * x_mid - end[:, 0] + for ctrl_pts in np.linspace(start, end, increments): + obj.root_link.set_particle_positions(torch.tensor(ctrl_pts), idxs=indices) + og.sim.step() + + while True: + print(f"\nCategory: {category}, Model: {model}!!!!!!!!!!!!!!!!!!!!!!!!!!") + env.step(np.array([])) + + # Shut down env at the end + print() + env.close() + + +if __name__ == "__main__": + main() diff --git a/state_squeeze.py b/state_squeeze.py new file mode 100644 index 000000000..a650f632a --- /dev/null +++ b/state_squeeze.py @@ -0,0 +1,157 @@ +from omnigibson.utils.constants import PrimType +from omnigibson.object_states import Folded, Unfolded +from omnigibson.macros import gm +import numpy as np + +import omnigibson as og +import omnigibson.lazy as lazy +import json +import torch +import time +from omnigibson.prims.xform_prim import XFormPrim +import omnigibson.utils.transform_utils as T + +# Make sure object states and GPU dynamics are enabled (GPU dynamics needed for cloth) +gm.ENABLE_OBJECT_STATES = True +gm.USE_GPU_DYNAMICS = True + + +# Set up the 4 walls +def generate_box(box_half_extent=torch.tensor([1, 1, 1], dtype=torch.float32), visualize_wall=False): + # Temp function to generate the walls for squeezing the cloth + # The floor plane already exists + # We just need to generate the side planes + plane_centers = ( + torch.tensor( + [ + [1, 0, 1], + [0, 1, 1], + [-1, 0, 1], + [0, -1, 1], + ] + ) + * box_half_extent + ) + plane_prims = [] + plane_motions = [] + for i, pc in enumerate(plane_centers): + plane = lazy.omni.isaac.core.objects.ground_plane.GroundPlane( + prim_path=f"/World/plane_{i}", + name=f"plane_{i}", + z_position=0, + size=box_half_extent[2].item(), + color=None, + visible=visualize_wall, + ) + + plane_as_prim = XFormPrim( + relative_prim_path=f"/plane_{i}", + name=plane.name, + ) + plane_as_prim.load(None) + + # Build the plane orientation from the plane normal + horiz_dir = pc - torch.tensor([0, 0, box_half_extent[2]]) + plane_z = -1 * horiz_dir / torch.norm(horiz_dir) + plane_x = torch.tensor([0, 0, 1], dtype=torch.float32) + plane_y = torch.cross(plane_z, plane_x) + plane_mat = torch.stack([plane_x, plane_y, plane_z], dim=1) + plane_quat = T.mat2quat(plane_mat) + plane_as_prim.set_position_orientation(pc, plane_quat) + + plane_prims.append(plane_as_prim) + plane_motions.append(plane_z) + return plane_prims, plane_motions + + +def main(visualize_wall=False): + """ + Demo of cloth objects that can be wall squeezed + """ + + cloth_category_models = [ + ("bandana", "wbhliu"), + ] + # cloth_category_models = [ + # ("hoodie", "agftpm"), + # ] + + for cloth in cloth_category_models: + category = cloth[0] + model = cloth[1] + print(f"\nCategory: {category}, Model: {model}!!!!!!!!!!!!!!!!!!!!!!!!!!") + # Create the scene config to load -- empty scene + custom cloth object + cfg = { + "scene": { + "type": "Scene", + }, + "objects": [ + { + "type": "DatasetObject", + "name": model, + "category": category, + "model": model, + "prim_type": PrimType.CLOTH, + "abilities": {"cloth": {}}, + }, + ], + } + + # Create the environment + env = og.Environment(configs=cfg) + + plane_prims, plane_motions = generate_box(visualize_wall=visualize_wall) + + # Grab object references + carpet = env.scene.object_registry("name", model) + objs = [carpet] + + # Set viewer camera + og.sim.viewer_camera.set_position_orientation( + position=np.array([0.46382895, -2.66703958, 1.22616824]), + orientation=np.array([0.58779174, -0.00231237, -0.00318273, 0.80900271]), + ) + + for _ in range(100): + og.sim.step() + + # Calculate end positions for all walls + end_positions = [] + for i in range(4): + plane_prim = plane_prims[i] + position = plane_prim.get_position() + end_positions.append(np.array(position) + np.array(plane_motions[i])) + + increments = 1000 + for step in range(increments): + # Move all walls a small amount + for i in range(4): + plane_prim = plane_prims[i] + current_pos = np.linspace(plane_prim.get_position(), end_positions[i], increments)[step] + plane_prim.set_position_orientation(position=current_pos) + + og.sim.step() + + # Check cloth height + cloth_positions = objs[0].root_link.compute_particle_positions() + # import pdb; pdb.set_trace() + max_height = torch.max(cloth_positions[:, 2]) + + # Get distance between facing walls (assuming walls 0-2 and 1-3 are facing pairs) + wall_dist_1 = torch.linalg.norm(plane_prims[0].get_position() - plane_prims[2].get_position()) + wall_dist_2 = torch.linalg.norm(plane_prims[1].get_position() - plane_prims[3].get_position()) + min_wall_dist = min(wall_dist_1, wall_dist_2) + + # Stop if cloth height exceeds wall distance + if max_height > min_wall_dist: + print(f"Stopping: Cloth height ({max_height:.3f}) exceeds wall distance ({min_wall_dist:.3f})") + break + + # TODO: save the cloth in the squeezed state + + # Shut down env at the end + env.close() + + +if __name__ == "__main__": + main()