- Fix USD metersPerUnit/upAxis for IS 5.0.0 (no longer auto-compensated) - Batch fix all Aligned_obj.usd, table, and art USD files with backups - Fix DomeLight rotation to Z-axis only (prevent tilted environment map) - Fix scene reuse across episodes (arena_file caching, task clearing, prim guard) - Add migration tools: scan_usd_metadata.py, fix_usd_metadata.py - Add migration guide: migerate/migerate.md - Add nvidia-curobo to .gitignore - Fix sort_the_rubbish config: obj_0 -> obj_1 (obj_0 does not exist) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
776 lines
34 KiB
Python
776 lines
34 KiB
Python
import glob
|
|
import os
|
|
import random
|
|
from copy import deepcopy
|
|
from typing import Dict
|
|
|
|
import numpy as np
|
|
import yaml
|
|
from core.cameras import CustomCamera
|
|
from core.objects import get_object_cls
|
|
from core.robots import get_robot_cls
|
|
from core.tasks.base_task import register_task
|
|
from core.utils.dr import update_articulated_objs, update_rigid_objs, update_scenes
|
|
from core.utils.language import update_language
|
|
from core.utils.layout import optimize_2d_manip_layout
|
|
from core.utils.region_sampler import RandomRegionSampler
|
|
from core.utils.scene_utils import deactivate_selected_prims
|
|
from core.utils.transformation_utils import get_orientation
|
|
from core.utils.visual_distractor import set_distractors
|
|
from omegaconf import DictConfig
|
|
from omni.isaac.core.materials import PreviewSurface
|
|
from omni.isaac.core.prims import RigidContactView, XFormPrim
|
|
from omni.isaac.core.scenes.scene import Scene
|
|
from omni.isaac.core.tasks import BaseTask
|
|
from omni.isaac.core.utils.prims import (
|
|
delete_prim,
|
|
get_prim_at_path,
|
|
is_prim_path_valid,
|
|
)
|
|
from omni.isaac.core.utils.stage import get_current_stage
|
|
from omni.physx.scripts import particleUtils
|
|
from pxr import Gf, PhysxSchema, Sdf, Usd, UsdGeom, UsdLux, UsdShade, Vt
|
|
from scipy.spatial.transform import Rotation as R
|
|
|
|
|
|
@register_task
|
|
class BananaBaseTask(BaseTask):
|
|
def __init__(
|
|
self,
|
|
cfg: DictConfig,
|
|
):
|
|
super().__init__(name=cfg["name"], offset=cfg["offset"])
|
|
self.cfg = cfg
|
|
self._render = cfg.get("render", True)
|
|
self.asset_root = os.path.abspath(self.cfg["asset_root"])
|
|
self.root_prim_path = os.path.join("/World", f"task_{cfg['task_id']}")
|
|
self.robots = {}
|
|
self.cameras = {}
|
|
self.cameras_info = {}
|
|
self.objects = {}
|
|
self.distractors = {}
|
|
self.fixtures = {}
|
|
self.visuals = {}
|
|
self.stage = get_current_stage()
|
|
self.random_region_list = self.cfg.get("random_region_list", [])
|
|
self.current_id = 0
|
|
|
|
self.first_set_fluid = True
|
|
self.particleSystemPath = None
|
|
self.particlesPath = None
|
|
self.particlesPbdMaterialPath = None
|
|
self.particlesVisualMaterialPath = None
|
|
self._defaultFluidPath = Sdf.Path("/World/task_0/fulid")
|
|
|
|
def set_up_scene(self, scene: Scene) -> None:
|
|
# Skip re-initialization if scene is already set up (repeated reset() calls)
|
|
if getattr(self, '_scene_initialized', False):
|
|
self._task_objects = {}
|
|
self._task_objects |= (
|
|
getattr(self, 'fixtures', {}) |
|
|
getattr(self, 'objects', {}) |
|
|
getattr(self, 'robots', {}) |
|
|
getattr(self, 'cameras', {})
|
|
)
|
|
return
|
|
self._scene_initialized = True
|
|
super().set_up_scene(scene)
|
|
self._set_envmap()
|
|
self.cfg = update_scenes(self.cfg)
|
|
for cfg in self.cfg["arena"]["fixtures"]:
|
|
self.fixtures[cfg["name"]] = self._load_obj(cfg)
|
|
if cfg["target_class"] == "ConveyorObject":
|
|
self.conveyor_velocity = cfg["linear_velocity"][0]
|
|
|
|
self.cfg = update_rigid_objs(self.cfg)
|
|
self.cfg = update_articulated_objs(self.cfg)
|
|
|
|
for cfg in self.cfg["objects"]:
|
|
self.objects[cfg["name"]] = self._load_obj(cfg)
|
|
|
|
for cfg in self.cfg["robots"]:
|
|
self._load_robot(cfg)
|
|
for cfg in self.cfg["cameras"]:
|
|
self._load_camera(cfg)
|
|
if cfg.get("apply_randomization", False):
|
|
self._perturb_camera(
|
|
self.cameras[cfg["name"]],
|
|
cfg,
|
|
max_translation_noise=cfg.get("max_translation_noise", 0.05),
|
|
max_orientation_noise=cfg.get("max_orientation_noise", 10.0),
|
|
)
|
|
|
|
self._task_objects |= self.fixtures | self.objects | self.robots | self.cameras
|
|
|
|
# Initialize object regions according to region configs
|
|
self._set_regions()
|
|
|
|
optimize_2d_manip_layout(self.cfg["objects"], self.cfg["regions"], self.objects)
|
|
|
|
# Object collision filtering (mainly for dynamicpick)
|
|
self.ignore_objects = [obj["name"] for obj in self.cfg["objects"]]
|
|
self.pickcontact_views = self._set_pickcontact_view(self.cfg)
|
|
self.artcontact_views = self._set_artcontact_view(self.cfg)
|
|
|
|
# Set up visual distrator (if exists)
|
|
if self.cfg.get("distractors", None):
|
|
cfgs = self._create_distractor_cfg()
|
|
for cfg in cfgs:
|
|
self.distractors[cfg["name"]] = self._load_obj(cfg)
|
|
|
|
self.cfg["mem_distractors"] = cfgs
|
|
set_distractors(
|
|
self.objects,
|
|
self.distractors,
|
|
self._task_objects[self.cfg["distractors"]["target"]],
|
|
self.cfg["distractors"],
|
|
cfgs,
|
|
)
|
|
|
|
# Update language
|
|
self.language_instruction, self.detailed_language_instruction = update_language(self.cfg)
|
|
|
|
def individual_reset(self):
|
|
self.current_id += 1
|
|
# Update table and scene pair
|
|
scene_update_freq = self.cfg["arena"].get("update_freq", 10)
|
|
if self.current_id % scene_update_freq == 0:
|
|
self.cfg = update_scenes(self.cfg)
|
|
for cfg in self.cfg["arena"]["fixtures"]:
|
|
if cfg.get("apply_randomization", False):
|
|
delete_prim(self.fixtures[cfg["name"]].prim_path)
|
|
self.fixtures[cfg["name"]] = self._load_obj(cfg)
|
|
self._task_objects[cfg["name"]] = self.fixtures[cfg["name"]]
|
|
|
|
# Update objects
|
|
self.cfg = update_rigid_objs(self.cfg)
|
|
self.cfg = update_articulated_objs(self.cfg)
|
|
for cfg in self.cfg["objects"]:
|
|
if cfg.get("apply_randomization", False):
|
|
delete_prim(os.path.dirname(self.objects[cfg["name"]].prim_path))
|
|
self.objects[cfg["name"]] = self._load_obj(cfg)
|
|
self._task_objects[cfg["name"]] = self.objects[cfg["name"]]
|
|
|
|
optimize_2d_manip_layout(self.cfg["objects"], self.cfg["regions"], self.objects)
|
|
self.pickcontact_views = self._set_pickcontact_view(self.cfg)
|
|
self.artcontact_views = self._set_artcontact_view(self.cfg)
|
|
|
|
def individual_reset_from_mem(self):
|
|
for cfg in self.cfg["arena"]["fixtures"]:
|
|
if cfg.get("apply_randomization", False):
|
|
delete_prim(self.fixtures[cfg["name"]].prim_path)
|
|
self.fixtures[cfg["name"]] = self._load_obj(cfg)
|
|
self._task_objects[cfg["name"]] = self.fixtures[cfg["name"]]
|
|
|
|
# Update objects
|
|
for cfg in self.cfg["objects"]:
|
|
if cfg.get("apply_randomization", False):
|
|
delete_prim(os.path.dirname(self.objects[cfg["name"]].prim_path))
|
|
self.objects[cfg["name"]] = self._load_obj(cfg)
|
|
self._task_objects[cfg["name"]] = self.objects[cfg["name"]]
|
|
|
|
optimize_2d_manip_layout(self.cfg["objects"], self.cfg["regions"], self.objects)
|
|
self.pickcontact_views = self._set_pickcontact_view(self.cfg)
|
|
self.artcontact_views = self._set_artcontact_view(self.cfg)
|
|
|
|
def individual_randomize(self):
|
|
# Randomize objects in regions
|
|
self._set_regions()
|
|
|
|
# Update envmap, fixture textures, and camera poses
|
|
self._set_envmap()
|
|
self._set_fixture_textures()
|
|
self._set_camera_poses()
|
|
|
|
# Set up visual distractor (if exists, resample and update mem)
|
|
if self.cfg.get("distractors", None):
|
|
for obj in self.distractors.values():
|
|
delete_prim(os.path.dirname(obj.prim_path))
|
|
|
|
self.distractors = {}
|
|
cfgs = self._create_distractor_cfg()
|
|
self.cfg["mem_distractors"] = cfgs
|
|
# use_mem=False → also calls set_distractors for new random placement
|
|
self._rebuild_distractors(cfgs, use_mem=False)
|
|
|
|
# Update language
|
|
self.language_instruction, self.detailed_language_instruction = update_language(self.cfg)
|
|
|
|
def individual_randomize_from_mem(self):
|
|
# Randomize objects in regions (re-sample placements)
|
|
self._set_regions()
|
|
|
|
# Update envmap, fixture textures, and camera poses
|
|
self._set_envmap()
|
|
self._set_fixture_textures()
|
|
self._set_camera_poses()
|
|
|
|
# Rebuild visual distractors from mem (no new random placement)
|
|
if self.cfg.get("mem_distractors", None) and self.cfg.get("distractors", None):
|
|
for obj in self.distractors.values():
|
|
delete_prim(os.path.dirname(obj.prim_path))
|
|
|
|
self.distractors = {}
|
|
cfgs = self.cfg["mem_distractors"]
|
|
# use_mem=True → only rebuild objects, do not call set_distractors
|
|
self._rebuild_distractors(cfgs, use_mem=True)
|
|
|
|
# Update language
|
|
self.language_instruction, self.detailed_language_instruction = update_language(self.cfg)
|
|
|
|
def post_reset(self):
|
|
for _, robot in self.robots.items():
|
|
robot.initialize()
|
|
for cfg in self.cfg["objects"]:
|
|
if cfg["target_class"] == "ArticulatedObject":
|
|
self.objects[cfg["name"]].initialize()
|
|
|
|
all_views = [
|
|
contact_view
|
|
for views_dict in (self.pickcontact_views, self.artcontact_views)
|
|
for lr_views in views_dict.values()
|
|
for contact_views in lr_views.values()
|
|
for contact_view in contact_views.values()
|
|
]
|
|
|
|
for view in all_views:
|
|
view.initialize()
|
|
|
|
def apply_action(self, action: Dict[str, Dict[str, np.ndarray]]):
|
|
for name in action.keys():
|
|
self.robots[name].apply_action(**action[name])
|
|
|
|
def get_observations(self):
|
|
obs = {
|
|
"robots": {},
|
|
"objects": {},
|
|
"cameras": {},
|
|
}
|
|
for name, robot in self.robots.items():
|
|
obs["robots"][name] = robot.get_observations()
|
|
for name, obj in self.objects.items():
|
|
obs["objects"][name] = obj.get_observations()
|
|
if self._render:
|
|
for name, camera in self.cameras.items():
|
|
obs["cameras"][name] = camera.get_observations()
|
|
return obs
|
|
|
|
# Load robot, camera and objects
|
|
def _load_robot(self, cfg):
|
|
robot = get_robot_cls(cfg["target_class"])(
|
|
self.asset_root,
|
|
self.root_prim_path,
|
|
cfg,
|
|
)
|
|
orientation = get_orientation(cfg.get("euler"), cfg.get("quaternion"))
|
|
robot.set_local_pose(
|
|
translation=cfg.get("translation", [0.0, 0.0, 0.0]),
|
|
orientation=orientation,
|
|
)
|
|
robot.set_local_scale(cfg.get("scale", [1.0, 1.0, 1.0]))
|
|
self.robots[cfg["name"]] = robot
|
|
|
|
def _load_camera(self, cfg):
|
|
cameras_root = os.path.join(self.root_prim_path, "cameras")
|
|
camera_prim_path = os.path.join(cameras_root, cfg["name"], "camera")
|
|
camera_reference_path = os.path.join(self.root_prim_path, cfg["parent"]) if cfg["parent"] else ""
|
|
|
|
if not is_prim_path_valid(os.path.join(cameras_root, cfg["name"])):
|
|
for path in [cameras_root, os.path.join(cameras_root, cfg["name"])]:
|
|
xform = XFormPrim(prim_path=path)
|
|
xform.set_local_pose(translation=[0.0, 0.0, 0.0], orientation=[1.0, 0.0, 0.0, 0.0])
|
|
|
|
# Load camera params from external file if camera_file is specified
|
|
camera_file_path = cfg["camera_file"]
|
|
with open(camera_file_path, "r", encoding="utf-8") as f:
|
|
camera_params = yaml.safe_load(f)
|
|
cfg = dict(cfg)
|
|
cfg["params"] = camera_params
|
|
|
|
# Use a single generic camera implementation.
|
|
camera = CustomCamera(
|
|
cfg=cfg,
|
|
prim_path=camera_prim_path,
|
|
root_prim_path=cameras_root,
|
|
reference_path=camera_reference_path,
|
|
name=cfg["name"],
|
|
)
|
|
|
|
camera.set_local_pose(
|
|
translation=cfg["translation"],
|
|
orientation=cfg["orientation"],
|
|
camera_axes=cfg["camera_axes"],
|
|
)
|
|
|
|
self.cameras[cfg["name"]] = camera
|
|
self.cameras_info[cfg["name"]] = {
|
|
"translation": deepcopy(camera.get_local_pose()[0]),
|
|
"orientation": deepcopy(camera.get_local_pose()[1]),
|
|
}
|
|
|
|
def _load_obj(self, cfg: DictConfig):
|
|
"""Create and initialize any object based on cfg['target_class']."""
|
|
target_class = cfg.get("target_class") or cfg["target_class"]
|
|
obj_cls = get_object_cls(target_class)
|
|
|
|
# Decide root prim and constructor args
|
|
root_prim_path = self.root_prim_path
|
|
ctor_args = [self.asset_root]
|
|
|
|
if target_class == "XFormObject" and cfg.get("parent_obj", None):
|
|
root_prim_path = self.objects[cfg["parent_obj"]].prim_path
|
|
|
|
ctor_args.append(root_prim_path)
|
|
|
|
if target_class == "ConveyorObject":
|
|
ctor_args.append(self.stage)
|
|
|
|
ctor_args.append(cfg)
|
|
obj = obj_cls(*ctor_args)
|
|
|
|
# Optional texture (for non-shape objects)
|
|
if cfg.get("texture") and target_class not in ("ShapeObject",):
|
|
obj.apply_texture(self.asset_root, cfg.get("texture"))
|
|
|
|
orientation = get_orientation(cfg.get("euler"), cfg.get("quaternion"))
|
|
obj.set_local_pose(translation=cfg.get("translation"), orientation=orientation)
|
|
obj.set_local_scale(cfg.get("scale", [1.0, 1.0, 1.0]))
|
|
# [LOAD_DBG] Print after set_local_pose
|
|
try:
|
|
from pxr import UsdGeom
|
|
_xf = UsdGeom.Xformable(get_prim_at_path(obj.prim_path))
|
|
for _op in _xf.GetOrderedXformOps():
|
|
print(f"[LOAD_DBG] {cfg['name']} after_set_local_pose: {_op.GetName()} = {_op.Get()}", flush=True)
|
|
except Exception as _e:
|
|
print(f"[LOAD_DBG] {cfg['name']} after_set_local_pose error: {_e}", flush=True)
|
|
obj.set_visibility(cfg.get("visible", True))
|
|
|
|
# Extra behavior per type
|
|
if target_class == "ArticulatedObject":
|
|
obj.get_joint_position(self.stage)
|
|
elif target_class == "ShapeObject":
|
|
material = PreviewSurface(
|
|
prim_path="/World/Materials/Red",
|
|
color=np.array(cfg.get("color", np.array([1, 0, 0]))),
|
|
)
|
|
obj.apply_visual_material(material)
|
|
|
|
# Special handling for scene object (only for general rigid/geometry)
|
|
if target_class in ("RigidObject", "GeometryObject") and obj.name == "scene":
|
|
deactivate_selected_prims(
|
|
obj.prim, ["pan", "hearth", "ceiling", "__default_setting", "other", "microwave"], ["light"]
|
|
)
|
|
|
|
return obj
|
|
|
|
# Set
|
|
def _set_artcontact_view(self, cfg):
|
|
artcontact_views = {}
|
|
for cfg_skill_dict in cfg["skills"]:
|
|
for robot_name, robot_skill_list in cfg_skill_dict.items():
|
|
for lr_skill_dict in robot_skill_list:
|
|
for lr_name, lr_skill_list in lr_skill_dict.items():
|
|
for lr_skill in lr_skill_list:
|
|
if lr_skill.get("name") == "open" or lr_skill.get("name") == "close":
|
|
if robot_name not in artcontact_views:
|
|
artcontact_views[robot_name] = {}
|
|
if lr_name not in artcontact_views[robot_name]:
|
|
artcontact_views[robot_name][lr_name] = {}
|
|
|
|
object_name = lr_skill["objects"][0]
|
|
robot = self.robots[robot_name]
|
|
filter_paths_expr = (
|
|
robot.fl_filter_paths_expr if lr_name == "left" else robot.fr_filter_paths_expr
|
|
)
|
|
forbid_collision_paths = (
|
|
robot.fl_forbid_collision_paths
|
|
if lr_name == "left"
|
|
else robot.fr_forbid_collision_paths
|
|
)
|
|
if (object_name + "_fingers_link") not in artcontact_views[robot_name][lr_name]:
|
|
artcontact_views[robot_name][lr_name][
|
|
object_name + "_fingers_link"
|
|
] = RigidContactView(
|
|
prim_paths_expr=self._task_objects[object_name].object_link_path,
|
|
filter_paths_expr=filter_paths_expr,
|
|
)
|
|
if (object_name + "_fingers_base") not in artcontact_views[robot_name][lr_name]:
|
|
artcontact_views[robot_name][lr_name][
|
|
object_name + "_fingers_base"
|
|
] = RigidContactView(
|
|
prim_paths_expr=self._task_objects[object_name].object_base_path,
|
|
filter_paths_expr=filter_paths_expr,
|
|
)
|
|
|
|
if (object_name + "_forbid_collision") not in artcontact_views[robot_name][lr_name]:
|
|
artcontact_views[robot_name][lr_name][
|
|
object_name + "_forbid_collision"
|
|
] = RigidContactView(
|
|
prim_paths_expr=self._task_objects[object_name].object_prim_path
|
|
+ "/instance/*",
|
|
filter_paths_expr=forbid_collision_paths,
|
|
)
|
|
|
|
return artcontact_views
|
|
|
|
def _set_pickcontact_view(self, cfg):
|
|
pickcontact_views = {}
|
|
for cfg_skill_dict in cfg["skills"]:
|
|
for robot_name, robot_skill_list in cfg_skill_dict.items():
|
|
for lr_skill_dict in robot_skill_list:
|
|
for lr_name, lr_skill_list in lr_skill_dict.items():
|
|
for lr_skill in lr_skill_list:
|
|
if "pick" in lr_skill.get("name"):
|
|
if robot_name not in pickcontact_views:
|
|
pickcontact_views[robot_name] = {}
|
|
if lr_name not in pickcontact_views[robot_name]:
|
|
pickcontact_views[robot_name][lr_name] = {}
|
|
object_name = lr_skill["objects"][0]
|
|
prim_paths_expr = self.objects[object_name].prim_path
|
|
robot = self.robots[robot_name]
|
|
filter_paths_expr = (
|
|
robot.fl_filter_paths_expr if lr_name == "left" else robot.fr_filter_paths_expr
|
|
)
|
|
if object_name not in pickcontact_views[robot_name][lr_name]:
|
|
pickcontact_views[robot_name][lr_name][object_name] = RigidContactView(
|
|
prim_paths_expr=prim_paths_expr, filter_paths_expr=filter_paths_expr
|
|
)
|
|
return pickcontact_views
|
|
|
|
def _set_regions(self):
|
|
"""Randomize object poses according to region configs."""
|
|
random_region_list = deepcopy(self.random_region_list)
|
|
for cfg in self.cfg["regions"]:
|
|
obj = self._task_objects[cfg["object"]]
|
|
tgt = self._task_objects[cfg["target"]]
|
|
if "sub_tgt_prim" in cfg:
|
|
tgt = XFormPrim(prim_path=tgt.prim_path + cfg["sub_tgt_prim"])
|
|
if "priority" in cfg:
|
|
if cfg["priority"]:
|
|
idx = random.choice(cfg["priority"])
|
|
else:
|
|
idx = random.randint(0, len(random_region_list) - 1)
|
|
random_config = (random_region_list.pop(idx))["random_config"]
|
|
sampler_fn = getattr(RandomRegionSampler, cfg["random_type"])
|
|
pose = sampler_fn(obj, tgt, **random_config)
|
|
obj.set_local_pose(*pose)
|
|
elif "container" in cfg:
|
|
container = self._task_objects[cfg["container"]]
|
|
obj_trans = container.get_local_pose()[0]
|
|
x_bias = random.choice(container.gap) if container.gap else 0
|
|
obj_trans[0] += x_bias
|
|
obj_trans[2] += cfg["z_init"]
|
|
obj_ori = obj.get_local_pose()[1]
|
|
obj.set_local_pose(obj_trans, obj_ori)
|
|
elif "target2" in cfg:
|
|
tgt2 = self._task_objects[cfg["target2"]]
|
|
sampler_fn = getattr(RandomRegionSampler, cfg["random_type"])
|
|
pose = sampler_fn(obj, tgt, tgt2, **cfg["random_config"])
|
|
obj.set_local_pose(*pose)
|
|
else:
|
|
sampler_fn = getattr(RandomRegionSampler, cfg["random_type"])
|
|
pose = sampler_fn(obj, tgt, **cfg["random_config"])
|
|
obj.set_local_pose(*pose)
|
|
|
|
def _set_fixture_textures(self):
|
|
"""Apply or randomize textures for arena fixtures (table, floor, background)."""
|
|
for cfg in self.cfg["arena"]["fixtures"]:
|
|
if cfg.get("texture"):
|
|
self.fixtures[cfg["name"]].apply_texture(self.asset_root, cfg.get("texture"))
|
|
|
|
def _set_camera_poses(self):
|
|
"""Randomize camera poses according to camera configs."""
|
|
for cfg in self.cfg["cameras"]:
|
|
if cfg.get("apply_randomization", False):
|
|
self._perturb_camera(
|
|
self.cameras[cfg["name"]],
|
|
cfg,
|
|
max_translation_noise=cfg.get("max_translation_noise", 0.05),
|
|
max_orientation_noise=cfg.get("max_orientation_noise", 10.0),
|
|
)
|
|
|
|
def _set_envmap(self):
|
|
"""Randomize or reset the environment map (HDR dome light)."""
|
|
cfg = self.cfg["env_map"]
|
|
if cfg.get("light_type", "DomeLight") == "DomeLight":
|
|
envmap_hdr_path_list = glob.glob(os.path.join(self.asset_root, cfg["envmap_lib"], "*.hdr"))
|
|
envmap_hdr_path_list.sort()
|
|
if cfg.get("apply_randomization", False):
|
|
envmap_id = random.randint(0, len(envmap_hdr_path_list) - 1)
|
|
intensity = random.uniform(cfg["intensity_range"][0], cfg["intensity_range"][1])
|
|
rotation = [0.0, 0.0, random.uniform(cfg["rotation_range"][0], cfg["rotation_range"][1])]
|
|
else:
|
|
envmap_id = 0
|
|
intensity = 1000.0
|
|
rotation = [0.0, 0.0, 0.0]
|
|
dome_prim_path = f"{self.root_prim_path}/DomeLight"
|
|
envmap_hdr_path = envmap_hdr_path_list[envmap_id]
|
|
|
|
if not is_prim_path_valid(dome_prim_path):
|
|
self.dome_light_prim = UsdLux.DomeLight.Define(self.stage, dome_prim_path)
|
|
UsdGeom.Xformable(self.dome_light_prim).AddRotateXYZOp().Set((rotation[0], rotation[1], rotation[2]))
|
|
else:
|
|
self.dome_light_prim.GetOrderedXformOps()[0].Set((rotation[0], rotation[1], rotation[2]))
|
|
self.dome_light_prim.GetIntensityAttr().Set(intensity)
|
|
self.dome_light_prim.GetTextureFileAttr().Set(envmap_hdr_path)
|
|
|
|
def _set_fluid(self):
|
|
# Particle params
|
|
self.particleContactOffset = self.cfg["fluid"].get("particleContactOffset", 0.005)
|
|
self.particleSpacing = self.particleContactOffset * self.cfg["fluid"].get("spacing_scale", 1.2)
|
|
|
|
offset = self._get_container_center()
|
|
numParticlesX = self.cfg["fluid"].get("numParticlesX", 7)
|
|
numParticlesY = self.cfg["fluid"].get("numParticlesY", 7)
|
|
numParticlesZ = self.cfg["fluid"].get("numParticlesZ", 450)
|
|
lower_x = (numParticlesX - 1) * self.particleSpacing * -0.5 + offset[0].item()
|
|
lower_y = (numParticlesY - 1) * self.particleSpacing * -0.5 + offset[1].item()
|
|
lower_z = (
|
|
(numParticlesZ - 1) * self.particleSpacing * -0.5 + offset[2].item()
|
|
if self.cfg["fluid"].get("center_z", False)
|
|
else offset[2].item()
|
|
)
|
|
z_offset = self.cfg["fluid"].get("z_offset", 0.0)
|
|
lower_z += z_offset
|
|
lower = Gf.Vec3f(lower_x, lower_y, lower_z)
|
|
|
|
positions, velocities = particleUtils.create_particles_grid(
|
|
lower, self.particleSpacing, numParticlesX, numParticlesY, numParticlesZ
|
|
)
|
|
widths = [self.particleSpacing] * len(positions)
|
|
|
|
positions = Vt.Vec3fArray(positions)
|
|
velocities = Vt.Vec3fArray(velocities)
|
|
widths = Vt.FloatArray(widths)
|
|
|
|
if self.first_set_fluid:
|
|
# Particle system
|
|
self.particleSystemPath = self._defaultFluidPath.AppendChild("particleSystem0")
|
|
|
|
self.particle_system = particleUtils.add_physx_particle_system(
|
|
stage=self.stage,
|
|
particle_system_path=self.particleSystemPath,
|
|
particle_system_enabled=True,
|
|
simulation_owner=None,
|
|
# contact_offset=self.particleContactOffset,
|
|
# rest_offset=self.particleContactOffset * 0.99,
|
|
particle_contact_offset=self.particleContactOffset,
|
|
# solid_rest_offset=self.particleContactOffset * 0.99,
|
|
# fluid_rest_offset=self.particleContactOffset * 0.99 * 0.6,
|
|
enable_ccd=True,
|
|
solver_position_iterations=16,
|
|
max_depenetration_velocity=None,
|
|
wind=None,
|
|
max_neighborhood=96,
|
|
neighborhood_scale=1.01,
|
|
max_velocity=self.cfg["fluid"].get("max_velocity", 0.8),
|
|
global_self_collision_enabled=True,
|
|
non_particle_collision_enabled=None,
|
|
)
|
|
particleUtils.add_physx_particle_isosurface(self.stage, self.particleSystemPath)
|
|
|
|
smoothingAPI = PhysxSchema.PhysxParticleSmoothingAPI.Apply(self.particle_system.GetPrim())
|
|
smoothingAPI.CreateParticleSmoothingEnabledAttr().Set(True)
|
|
smoothingAPI.CreateStrengthAttr().Set(50.0)
|
|
|
|
self.particlesPath = self._defaultFluidPath.AppendChild("particles")
|
|
|
|
self.stage.SetInterpolationType(Usd.InterpolationTypeLinear)
|
|
|
|
self.particles = particleUtils.add_physx_particleset_points(
|
|
stage=self.stage,
|
|
path=self.particlesPath,
|
|
positions_list=positions,
|
|
velocities_list=velocities,
|
|
widths_list=widths,
|
|
particle_system_path=self.particleSystemPath,
|
|
self_collision=True,
|
|
fluid=True,
|
|
particle_group=0,
|
|
particle_mass=self.cfg["fluid"].get("mass", 0.000000),
|
|
density=self.cfg["fluid"].get("density", 0.000000),
|
|
)
|
|
|
|
self.particlesPbdMaterialPath = self._defaultFluidPath.AppendChild("pdbMaterial")
|
|
|
|
self.particlesVisualMaterialPath = self._defaultFluidPath.AppendChild("visualMaterial")
|
|
|
|
particleUtils.add_pbd_particle_material(
|
|
stage=self.stage,
|
|
path=self.particlesPbdMaterialPath,
|
|
cohesion=0.01,
|
|
drag=0,
|
|
lift=0,
|
|
damping=0,
|
|
friction=0.1,
|
|
surface_tension=0.0074,
|
|
viscosity=0.0000017,
|
|
vorticity_confinement=0,
|
|
)
|
|
particlesPbdMaterial_prim = get_prim_at_path(self.particlesPbdMaterialPath)
|
|
material = UsdShade.Material(particlesPbdMaterial_prim)
|
|
|
|
particleSystem_prim = get_prim_at_path(self.particleSystemPath)
|
|
binding_api = UsdShade.MaterialBindingAPI.Apply(particleSystem_prim)
|
|
binding_api.Bind(material)
|
|
|
|
material = self._create_colored_material(
|
|
self.stage,
|
|
self.particlesVisualMaterialPath,
|
|
color=self.cfg["fluid"].get("color", [1.0, 1.0, 1.0]),
|
|
emissiveColor=self.cfg["fluid"].get("emissiveColor", [0.0, 0.0, 0.0]),
|
|
opacity=self.cfg["fluid"].get("opacity", 1),
|
|
)
|
|
binding_api.Bind(material)
|
|
|
|
self.first_set_fluid = False
|
|
|
|
else:
|
|
self.particles.GetPointsAttr().Set(positions)
|
|
self.particles.GetVelocitiesAttr().Set(velocities)
|
|
self.particles.GetWidthsAttr().Set(widths)
|
|
|
|
particles_prim = self.stage.GetPrimAtPath(self.particlesPath)
|
|
if particles_prim:
|
|
purpose_attr = particles_prim.CreateAttribute("purpose", Sdf.ValueTypeNames.Token)
|
|
purpose_attr.Set("proxy")
|
|
|
|
return self.particles
|
|
|
|
# Utilities
|
|
def _perturb_camera(self, camera, cfg, max_translation_noise=0.05, max_orientation_noise=10.0):
|
|
translation = np.array(cfg["translation"])
|
|
orientation = np.array(cfg["orientation"])
|
|
|
|
random_direction = np.random.randn(3)
|
|
random_direction /= np.linalg.norm(random_direction)
|
|
random_distance = np.random.uniform(0, max_translation_noise)
|
|
perturbed_translation = translation + random_direction * random_distance
|
|
|
|
original_rot = R.from_quat(orientation, scalar_first=True)
|
|
random_axis = np.random.randn(3)
|
|
random_axis /= np.linalg.norm(random_axis)
|
|
random_angle_deg = np.random.uniform(-max_orientation_noise, max_orientation_noise)
|
|
random_angle_rad = np.radians(random_angle_deg)
|
|
perturbation_rot = R.from_rotvec(random_axis * random_angle_rad)
|
|
perturbed_rot = perturbation_rot * original_rot
|
|
perturbed_orientation = perturbed_rot.as_quat(scalar_first=True)
|
|
|
|
camera.set_local_pose(
|
|
translation=perturbed_translation,
|
|
orientation=perturbed_orientation,
|
|
camera_axes=cfg["camera_axes"],
|
|
)
|
|
|
|
def _create_distractor_cfg(self):
|
|
distractors_cfg = self.cfg["distractors"]
|
|
|
|
# Collect all available distractor asset paths
|
|
distractor_paths = glob.glob(
|
|
os.path.join(self.asset_root, distractors_cfg["path"], "*", "*", "*.usd") # category # subcategory
|
|
)
|
|
distractor_paths.sort()
|
|
|
|
# Categories already used by main objects in the scene
|
|
current_categories = {obj_cfg["path"].split("/")[-3] for obj_cfg in self.cfg["objects"]}
|
|
|
|
# Optional: categories to be excluded from distractors via config
|
|
# Example in config:
|
|
# distractors:
|
|
# exclude_categories: ["omniobject3d-shoe", "omniobject3d-book"]
|
|
# exclude_keywords: ["shoe", "book"]
|
|
excluded_categories = set(distractors_cfg.get("exclude_categories", []))
|
|
exclude_keywords = [k.lower() for k in distractors_cfg.get("exclude_keywords", [])]
|
|
|
|
filtered_distractors = []
|
|
for path in distractor_paths:
|
|
category = path.split("/")[-3]
|
|
category_lower = category.lower()
|
|
|
|
# Skip if category is already used by main objects
|
|
if category in current_categories:
|
|
continue
|
|
|
|
# Skip if category is explicitly excluded
|
|
if category in excluded_categories:
|
|
continue
|
|
|
|
# Skip if any keyword appears in the category name (case-insensitive)
|
|
if any(kw in category_lower for kw in exclude_keywords):
|
|
continue
|
|
|
|
filtered_distractors.append(path)
|
|
|
|
num_samples = random.randint(
|
|
distractors_cfg["min_num"], min(distractors_cfg["max_num"], len(filtered_distractors))
|
|
)
|
|
filtered_distractors = random.sample(filtered_distractors, num_samples)
|
|
|
|
cfgs = []
|
|
for path in filtered_distractors:
|
|
tmp_cfg = {}
|
|
tmp_cfg["name"] = "distractors" + "/" + path.split("/")[-2]
|
|
tmp_cfg["name"] = (tmp_cfg["name"]).replace("-", "_")
|
|
tmp_cfg["path"] = path.replace(self.asset_root, "")
|
|
tmp_cfg["target_class"] = distractors_cfg.get("target_class", "RigidObject") # "RigidObject"
|
|
tmp_cfg["prim_path_child"] = distractors_cfg.get("prim_path_child", "Aligned") # "Aligned"
|
|
tmp_cfg["translation"] = distractors_cfg.get("translation", [0.0, 0.0, 0.0])
|
|
tmp_cfg["scale"] = distractors_cfg.get("scale", [1.0, 1.0, 1.0])
|
|
tmp_category = path.split("/")[-3]
|
|
tmp_cfg["category"] = tmp_category
|
|
tmp_cfg = DictConfig(tmp_cfg)
|
|
cfgs.append(tmp_cfg)
|
|
|
|
return cfgs
|
|
|
|
def _rebuild_distractors(self, cfgs, use_mem: bool):
|
|
"""Rebuild distractor objects from a list of configs.
|
|
|
|
- If use_mem is True, only rebuild objects (no new random placement via set_distractors).
|
|
- If use_mem is False, also call set_distractors to (re)sample placements.
|
|
"""
|
|
for cfg in cfgs:
|
|
if cfg["target_class"] == "RigidObject":
|
|
self.distractors[cfg["name"]] = self._load_obj(cfg)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
if (not use_mem) and self.cfg.get("distractors", None):
|
|
set_distractors(
|
|
self.objects,
|
|
self.distractors,
|
|
self._task_objects[self.cfg["distractors"]["target"]],
|
|
self.cfg["distractors"],
|
|
cfgs,
|
|
)
|
|
|
|
def _get_container_center(self):
|
|
container_name = self.cfg["fluid"]["container_name"]
|
|
container = self.objects[container_name]
|
|
container_trans, _ = container.get_world_pose()
|
|
|
|
return container_trans
|
|
|
|
def _create_colored_material(
|
|
self, stage, material_path, color=(1.0, 0.0, 0.0), emissiveColor=(0.0, 0.0, 0.0), opacity=1.0
|
|
):
|
|
material_prim = stage.DefinePrim(material_path, "Material")
|
|
material = UsdShade.Material(material_prim)
|
|
|
|
shader_path = f"{material_path}/PreviewSurface"
|
|
shader_prim = stage.DefinePrim(shader_path, "Shader")
|
|
shader = UsdShade.Shader(shader_prim)
|
|
|
|
shader.CreateIdAttr("UsdPreviewSurface")
|
|
|
|
shader.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(*color))
|
|
shader.CreateInput("emissiveColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(*emissiveColor))
|
|
|
|
shader.CreateInput("metallic", Sdf.ValueTypeNames.Float).Set(0.0)
|
|
shader.CreateInput("roughness", Sdf.ValueTypeNames.Float).Set(0.4)
|
|
shader.CreateInput("opacity", Sdf.ValueTypeNames.Float).Set(opacity)
|
|
|
|
material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), "surface")
|
|
|
|
return material
|