Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix cloth loading problems: caused by non-watertight, and potential multiple pieces #1063

Merged
merged 6 commits into from
Feb 5, 2025
10 changes: 0 additions & 10 deletions omnigibson/objects/dataset_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
33 changes: 26 additions & 7 deletions omnigibson/systems/micro_particle_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]
)
Expand Down
125 changes: 125 additions & 0 deletions state_fold.py
Original file line number Diff line number Diff line change
@@ -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()
157 changes: 157 additions & 0 deletions state_squeeze.py
Original file line number Diff line number Diff line change
@@ -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()