diff --git a/omnigibson/envs/env_base.py b/omnigibson/envs/env_base.py index 61db0dc0f..107ed1216 100644 --- a/omnigibson/envs/env_base.py +++ b/omnigibson/envs/env_base.py @@ -558,7 +558,7 @@ def reset(self): og.sim.step() # Grab and return observations - obs, obs_info = self.get_obs() + obs, _ = self.get_obs() if self._loaded: # Sanity check to make sure received observations match expected observation space @@ -598,7 +598,7 @@ def reset(self): raise ValueError("Observation space does not match returned observations!") - return obs, obs_info + return obs @property def episode_steps(self): diff --git a/omnigibson/examples/learning/navigation_policy_demo.py b/omnigibson/examples/learning/navigation_policy_demo.py index a8f91e290..d8a4f07d6 100644 --- a/omnigibson/examples/learning/navigation_policy_demo.py +++ b/omnigibson/examples/learning/navigation_policy_demo.py @@ -28,9 +28,9 @@ except ModuleNotFoundError: og.log.error("torch, stable-baselines3, or tensorboard is not installed. " "See which packages are missing, and then run the following for any missing packages:\n" - "pip install torch\n" - "pip install stable-baselines3==1.7.0\n" + "pip install stable-baselines3[extra]\n" "pip install tensorboard\n" + "pip install shimmy>=0.2.1\n" "Also, please update gym to >=0.26.1 after installing sb3: pip install gym>=0.26.1") exit(1) diff --git a/omnigibson/examples/object_states/attachment_demo.py b/omnigibson/examples/object_states/attachment_demo.py index 3718ab4e0..175121b2d 100644 --- a/omnigibson/examples/object_states/attachment_demo.py +++ b/omnigibson/examples/object_states/attachment_demo.py @@ -32,24 +32,31 @@ def main(random_selection=False, headless=False, short_exec=False): name="shelf_back_panel", category="shelf_back_panel", model="gjsnrt", - bounding_box=[0.8, 2.02, 0.02], position=[0, 0, 0.01], + fixed_base=True, abilities={"attachable": {}}, )) idx += 1 - xs = [-0.4, 0.4] - for i in range(2): - obj_cfgs.append(dict( - type="DatasetObject", - name=f"shelf_side_{i}", - category="shelf_side", - model="bxfkjj", - bounding_box=[0.03, 2.02, 0.26], - position=[xs[i], 0, base_z + delta_z * idx], - abilities={"attachable": {}}, - )) - idx += 1 + obj_cfgs.append(dict( + type="DatasetObject", + name=f"shelf_side_left", + category="shelf_side", + model="bxfkjj", + position=[-0.4, 0, base_z + delta_z * idx], + abilities={"attachable": {}}, + )) + idx += 1 + + obj_cfgs.append(dict( + type="DatasetObject", + name=f"shelf_side_right", + category="shelf_side", + model="yujrmw", + position=[0.4, 0, base_z + delta_z * idx], + abilities={"attachable": {}}, + )) + idx += 1 ys = [-0.93, -0.61, -0.29, 0.03, 0.35, 0.68] for i in range(6): @@ -58,7 +65,6 @@ def main(random_selection=False, headless=False, short_exec=False): name=f"shelf_shelf_{i}", category="shelf_shelf", model="ymtnqa", - bounding_box=[0.74, 0.023, 0.26], position=[0, ys[i], base_z + delta_z * idx], abilities={"attachable": {}}, )) @@ -69,7 +75,6 @@ def main(random_selection=False, headless=False, short_exec=False): name="shelf_top_0", category="shelf_top", model="pfiole", - bounding_box=[0.74, 0.04, 0.26], position=[0, 1.0, base_z + delta_z * idx], abilities={"attachable": {}}, )) @@ -80,7 +85,6 @@ def main(random_selection=False, headless=False, short_exec=False): name=f"shelf_baseboard", category="shelf_baseboard", model="hlhneo", - bounding_box=[0.742, 0.067, 0.02], position=[0, -0.97884506, base_z + delta_z * idx], abilities={"attachable": {}}, )) @@ -102,12 +106,17 @@ def main(random_selection=False, headless=False, short_exec=False): shelf_baseboard = og.sim.scene.object_registry("name", "shelf_baseboard") shelf_baseboard.set_position_orientation([0, -0.979, 0.26], [0, 0, 0, 1]) shelf_baseboard.keep_still() - shelf_baseboard.set_linear_velocity([-0.2, 0, 0]) - + shelf_baseboard.set_linear_velocity(np.array([-0.2, 0, 0])) + + shelf_side_left = og.sim.scene.object_registry("name", "shelf_side_left") + shelf_side_left.set_position_orientation([-0.4, 0.0, 0.2], [0, 0, 0, 1]) + shelf_side_left.keep_still() + input("\n\nShelf parts fall to their correct poses and get automatically attached to the back panel.\n" - "You can try to drag the shelf to hit the floor to break it apart. Press [ENTER] to continue.\n") + "You can try to drag (Shift + Left-CLICK + Drag) parts of the shelf to break it apart (you may need to zoom out and drag with a larger force).\n" + "Press [ENTER] to continue.\n") - for _ in range(1000): + for _ in range(5000): og.sim.step() og.shutdown() diff --git a/omnigibson/examples/object_states/dicing_demo.py b/omnigibson/examples/object_states/dicing_demo.py index 6e92d601a..92e955ae0 100644 --- a/omnigibson/examples/object_states/dicing_demo.py +++ b/omnigibson/examples/object_states/dicing_demo.py @@ -42,7 +42,7 @@ def main(random_selection=False, headless=False, short_exec=False): category="table_knife", model="lrdmpf", bounding_box=[0.401, 0.044, 0.009], - position=[0, 0, 10.0], + position=[0, 0, 20.0], ) light0_cfg = dict( diff --git a/omnigibson/examples/object_states/object_state_texture_demo.py b/omnigibson/examples/object_states/object_state_texture_demo.py index dd28e988b..74a0cdd76 100644 --- a/omnigibson/examples/object_states/object_state_texture_demo.py +++ b/omnigibson/examples/object_states/object_state_texture_demo.py @@ -12,21 +12,13 @@ def main(): - # Create the scene config to load -- empty scene plus a light and a cabinet + # Create the scene config to load -- empty scene plus a cabinet cfg = { "scene": { "type": "Scene", "floor_plane_visible": True, }, "objects": [ - { - "type": "LightObject", - "name": "light", - "light_type": "Sphere", - "radius": 0.01, - "intensity": 1e8, - "position": [-2.0, -2.0, 1.0], - }, { "type": "DatasetObject", "name": "cabinet", diff --git a/omnigibson/examples/object_states/particle_applier_remover_demo.py b/omnigibson/examples/object_states/particle_applier_remover_demo.py index 6433ff323..83ddf5680 100644 --- a/omnigibson/examples/object_states/particle_applier_remover_demo.py +++ b/omnigibson/examples/object_states/particle_applier_remover_demo.py @@ -100,16 +100,6 @@ def main(random_selection=False, headless=False, short_exec=False): } } - # Define objects to load: a light, table, and cloth - light_cfg = dict( - type="LightObject", - name="light", - light_type="Sphere", - radius=0.01, - intensity=1e8, - position=[-2.0, -2.0, 2.0], - ) - table_cfg = dict( type="DatasetObject", name="table", @@ -124,7 +114,7 @@ def main(random_selection=False, headless=False, short_exec=False): "scene": { "type": "Scene", }, - "objects": [light_cfg, table_cfg], + "objects": [table_cfg], } # Sanity check inputs: Remover + Adjacency + Fluid will not work because we are using a visual_only @@ -148,7 +138,7 @@ def main(random_selection=False, headless=False, short_exec=False): name="modifier", category="dishtowel", model="dtfspn", - bounding_box=[0.341, 0.466, 0.07], + bounding_box=[0.34245, 0.46798, 0.07], visual_only=method_type == "Projection", # Non-fluid adjacency requires the object to have collision geoms active abilities=abilities, ) @@ -203,9 +193,9 @@ def main(random_selection=False, headless=False, short_exec=False): # Move object in square around table deltas = [ - [150, np.array([-0.01, 0, 0])], + [130, np.array([-0.01, 0, 0])], [60, np.array([0, -0.01, 0])], - [150, np.array([0.01, 0, 0])], + [130, np.array([0.01, 0, 0])], [60, np.array([0, 0.01, 0])], ] for t, delta in deltas: diff --git a/omnigibson/examples/object_states/sample_kinematics_demo.py b/omnigibson/examples/object_states/sample_kinematics_demo.py index 4d8f87a25..0bd22592f 100644 --- a/omnigibson/examples/object_states/sample_kinematics_demo.py +++ b/omnigibson/examples/object_states/sample_kinematics_demo.py @@ -49,7 +49,7 @@ def main(random_selection=False, headless=False, short_exec=False): name=f"plate{i}", category="plate", model="iawoof", - bounding_box=np.array([0.25, 0.25, 0.05]), + bounding_box=np.array([0.20, 0.20, 0.05]), ) for i in range(2)] apple_cfgs = [dict( diff --git a/omnigibson/examples/object_states/slicing_demo.py b/omnigibson/examples/object_states/slicing_demo.py index 500db990b..98c0bb21b 100644 --- a/omnigibson/examples/object_states/slicing_demo.py +++ b/omnigibson/examples/object_states/slicing_demo.py @@ -40,7 +40,7 @@ def main(random_selection=False, headless=False, short_exec=False): category="table_knife", model="lrdmpf", bounding_box=[0.401, 0.044, 0.009], - position=[0, 0, 10.0], + position=[0, 0, 20.0], ) light0_cfg = dict( diff --git a/omnigibson/examples/objects/load_object_selector.py b/omnigibson/examples/objects/load_object_selector.py index 97001cefd..01530ac46 100644 --- a/omnigibson/examples/objects/load_object_selector.py +++ b/omnigibson/examples/objects/load_object_selector.py @@ -39,7 +39,6 @@ def main(random_selection=False, headless=False, short_exec=False): name="obj", category=obj_category, model=obj_model, - bounding_box=avg_category_spec.get(obj_category), position=[0, 0, 50.0], ) diff --git a/omnigibson/examples/robots/advanced/ik_example.py b/omnigibson/examples/robots/advanced/ik_example.py index f37bff93c..54695933c 100644 --- a/omnigibson/examples/robots/advanced/ik_example.py +++ b/omnigibson/examples/robots/advanced/ik_example.py @@ -38,32 +38,32 @@ def main(random_selection=False, headless=False, short_exec=False): programmatic_pos = True # Import scene and robot (Fetch) - scene = Scene() - og.sim.import_scene(scene) - - # Update the viewer camera's pose so that it points towards the robot - og.sim.viewer_camera.set_position_orientation( - position=np.array([4.32248, -5.74338, 6.85436]), - orientation=np.array([0.39592, 0.13485, 0.29286, 0.85982]), - ) - + scene_cfg = {"type": "Scene"} # Create Fetch robot # Note that since we only care about IK functionality, we fix the base (this also makes the robot more stable) # (any object can also have its fixed_base attribute set to True!) # Note that since we're going to be setting joint position targets, we also need to make sure the robot's arm joints # (which includes the trunk) are being controlled using joint positions - robot = Fetch( - prim_path="/World/robot", - name="robot", - fixed_base=True, - controller_config={ + robot_cfg = { + "type": "Fetch", + "fixed_base": True, + "controller_config": { "arm_0": { - "name": "JointController", + "name": "NullJointController", "motor_type": "position", } } + } + cfg = dict(scene=scene_cfg, robots=[robot_cfg]) + env = og.Environment(configs=cfg) + + # Update the viewer camera's pose so that it points towards the robot + og.sim.viewer_camera.set_position_orientation( + position=np.array([4.32248, -5.74338, 6.85436]), + orientation=np.array([0.39592, 0.13485, 0.29286, 0.85982]), ) - og.sim.import_object(robot) + + robot = env.robots[0] # Set robot base at the origin robot.set_position_orientation(np.array([0, 0, 0]), np.array([0, 0, 0, 1])) @@ -73,6 +73,9 @@ def main(random_selection=False, headless=False, short_exec=False): og.sim.step() # Make sure none of the joints are moving robot.keep_still() + # Since this demo aims to showcase how users can directly control the robot with IK, + # we will need to disable the built-in controllers in OmniGibson + robot.control_enabled = False # Create the IK solver -- note that we are controlling both the trunk and the arm since both are part of the # controllable kinematic chain for the end-effector! @@ -91,7 +94,12 @@ def execute_ik(pos, quat=None, max_iter=100): joint_pos = ik_solver.solve( target_pos=pos, target_quat=quat, + tolerance_pos=0.002, + tolerance_quat=0.01, + weight_pos=20.0, + weight_quat=0.05, max_iterations=max_iter, + initial_joint_pos=robot.get_joint_positions()[control_idx], ) if joint_pos is not None: og.log.info("Solution found. Setting new arm configuration.") @@ -185,7 +193,7 @@ def print_message(): print("Move the marker to a desired position to query IK and press ENTER") print("W/S: move marker further away or closer to the robot") print("A/D: move marker to the left or the right of the robot") - print("T/G: move marker up and down") + print("UP/DOWN: move marker up and down") print("ESC: quit") diff --git a/omnigibson/examples/robots/all_robots_visualizer.py b/omnigibson/examples/robots/all_robots_visualizer.py index 9ab893273..522f384f0 100644 --- a/omnigibson/examples/robots/all_robots_visualizer.py +++ b/omnigibson/examples/robots/all_robots_visualizer.py @@ -57,6 +57,8 @@ def main(random_selection=False, headless=False, short_exec=False): # Then apply random actions for a bit for _ in range(30): action = np.random.uniform(-1, 1, robot.action_dim) + if robot_name == "Tiago": + action[robot.base_action_idx] = np.random.uniform(-0.1, 0.1, len(robot.base_action_idx)) for _ in range(10): env.step(action) diff --git a/omnigibson/object_states/__init__.py b/omnigibson/object_states/__init__.py index ed9df0b6a..36642bda3 100644 --- a/omnigibson/object_states/__init__.py +++ b/omnigibson/object_states/__init__.py @@ -20,10 +20,12 @@ from omnigibson.object_states.overlaid import Overlaid from omnigibson.object_states.particle_modifier import ParticleRemover, ParticleApplier from omnigibson.object_states.particle_source_or_sink import ParticleSource, ParticleSink +from omnigibson.object_states.particle import ParticleRequirement from omnigibson.object_states.pose import Pose from omnigibson.object_states.robot_related_states import IsGrasping, ObjectsInFOVOfRobot from omnigibson.object_states.saturated import Saturated from omnigibson.object_states.slicer_active import SlicerActive +from omnigibson.object_states.sliceable import SliceableRequirement from omnigibson.object_states.temperature import Temperature from omnigibson.object_states.toggle import ToggledOn from omnigibson.object_states.touching import Touching diff --git a/omnigibson/object_states/attached_to.py b/omnigibson/object_states/attached_to.py index fbbce2aac..46abc5a63 100644 --- a/omnigibson/object_states/attached_to.py +++ b/omnigibson/object_states/attached_to.py @@ -27,8 +27,8 @@ m.DEFAULT_POSITION_THRESHOLD = 0.05 # 5cm m.DEFAULT_ORIENTATION_THRESHOLD = np.deg2rad(5.0) # 5 degrees m.DEFAULT_JOINT_TYPE = JointType.JOINT_FIXED -m.DEFAULT_BREAK_FORCE = 10000 # Newton -m.DEFAULT_BREAK_TORQUE = 10000 # Newton-Meter +m.DEFAULT_BREAK_FORCE = 1000 # Newton +m.DEFAULT_BREAK_TORQUE = 1000 # Newton-Meter class AttachedTo(RelativeObjectState, BooleanStateMixin, ContactSubscribedStateMixin, JointBreakSubscribedStateMixin, LinkBasedStateMixin): diff --git a/omnigibson/object_states/factory.py b/omnigibson/object_states/factory.py index 99f240022..272f6b534 100644 --- a/omnigibson/object_states/factory.py +++ b/omnigibson/object_states/factory.py @@ -1,31 +1,38 @@ import networkx as nx +from collections import namedtuple from omnigibson.object_states.kinematics_mixin import KinematicsMixin from omnigibson.object_states import * -_ABILITY_TO_STATE_MAPPING = { - "robot": [IsGrasping, ObjectsInFOVOfRobot], - "attachable": [AttachedTo], - "particleApplier": [ParticleApplier], - "particleRemover": [ParticleRemover], - "particleSource": [ParticleSource], - "particleSink": [ParticleSink], - "coldSource": [HeatSourceOrSink], - "cookable": [Cooked, Burnt], - "coverable": [Covered], - "freezable": [Frozen], - "heatable": [Heated], - "heatSource": [HeatSourceOrSink], - "meltable": [MaxTemperature], - "mixingTool": [], - "openable": [Open], - "flammable": [OnFire], - "saturable": [Saturated], - "sliceable": [], - "slicer": [SlicerActive], - "toggleable": [ToggledOn], - "cloth": [Folded, Unfolded, Overlaid, Draped], - "fillable": [Filled, Contains], +# states: list of ObjectBaseState +# requirements: list of ObjectBaseRequirement +AbilityDependencies = namedtuple("AbilityDependencies", ("states", "requirements")) + +# Maps ability name to list of Object States and / or Ability Requirements that determine +# whether the given ability can be instantiated for a requested object +_ABILITY_DEPENDENCIES = { + "robot": AbilityDependencies(states=[IsGrasping, ObjectsInFOVOfRobot], requirements=[]), + "attachable": AbilityDependencies(states=[AttachedTo], requirements=[]), + "particleApplier": AbilityDependencies(states=[ParticleApplier], requirements=[ParticleRequirement]), + "particleRemover": AbilityDependencies(states=[ParticleRemover], requirements=[ParticleRequirement]), + "particleSource": AbilityDependencies(states=[ParticleSource], requirements=[ParticleRequirement]), + "particleSink": AbilityDependencies(states=[ParticleSink], requirements=[ParticleRequirement]), + "coldSource": AbilityDependencies(states=[HeatSourceOrSink], requirements=[]), + "cookable": AbilityDependencies(states=[Cooked, Burnt], requirements=[]), + "coverable": AbilityDependencies(states=[Covered], requirements=[]), + "freezable": AbilityDependencies(states=[Frozen], requirements=[]), + "heatable": AbilityDependencies(states=[Heated], requirements=[]), + "heatSource": AbilityDependencies(states=[HeatSourceOrSink], requirements=[]), + "meltable": AbilityDependencies(states=[MaxTemperature], requirements=[]), + "mixingTool": AbilityDependencies(states=[], requirements=[]), + "openable": AbilityDependencies(states=[Open], requirements=[]), + "flammable": AbilityDependencies(states=[OnFire], requirements=[]), + "saturable": AbilityDependencies(states=[Saturated], requirements=[]), + "sliceable": AbilityDependencies(states=[], requirements=[SliceableRequirement]), + "slicer": AbilityDependencies(states=[SlicerActive], requirements=[]), + "toggleable": AbilityDependencies(states=[ToggledOn], requirements=[]), + "cloth": AbilityDependencies(states=[Folded, Unfolded, Overlaid, Draped], requirements=[]), + "fillable": AbilityDependencies(states=[Filled, Contains], requirements=[]), } _DEFAULT_STATE_SET = frozenset( @@ -118,9 +125,15 @@ def get_state_name(state): def get_states_for_ability(ability): - if ability not in _ABILITY_TO_STATE_MAPPING: + if ability not in _ABILITY_DEPENDENCIES: return [] - return _ABILITY_TO_STATE_MAPPING[ability] + return _ABILITY_DEPENDENCIES[ability].states + + +def get_requirements_for_ability(ability): + if ability not in _ABILITY_DEPENDENCIES: + return [] + return _ABILITY_DEPENDENCIES[ability].requirements def get_state_dependency_graph(states=None): diff --git a/omnigibson/object_states/object_state_base.py b/omnigibson/object_states/object_state_base.py index a16ec6963..5d1af901f 100644 --- a/omnigibson/object_states/object_state_base.py +++ b/omnigibson/object_states/object_state_base.py @@ -8,7 +8,52 @@ REGISTERED_OBJECT_STATES = dict() -class BaseObjectState(Serializable, Registerable, Recreatable, ABC): +class BaseObjectRequirement: + """ + Base ObjectRequirement class. This allows for sanity checking a given asset / BaseObject to check whether a set + of conditions are met or not. This can be useful for sanity checking dependencies for properties such as requested + abilities or object states. + """ + + @classmethod + def is_compatible(cls, obj, **kwargs): + """ + Determines whether this requirement is compatible with object @obj or not (i.e.: whether this requirement is + satisfied by @obj given other constructor arguments **kwargs). + + NOTE: Must be implemented by subclass. + + Args: + obj (StatefulObject): Object whose compatibility with this state should be checked + + Returns: + 2-tuple: + - bool: Whether the given object is compatible with this requirement or not + - None or str: If not compatible, the reason why it is not compatible. Otherwise, None + """ + raise NotImplementedError + + @classmethod + def is_compatible_asset(cls, prim, **kwargs): + """ + Determines whether this requirement is compatible with prim @prim or not (i.e.: whether this requirement is + satisfied by @prim given other constructor arguments **kwargs). + This is a useful check to evaluate an object's USD that hasn't been explicitly imported into OmniGibson yet. + + NOTE: Must be implemented by subclass + + Args: + prim (Usd.Prim): Object prim whose compatibility with this requirement should be checked + + Returns: + 2-tuple: + - bool: Whether the given prim is compatible with this requirement or not + - None or str: If not compatible, the reason why it is not compatible. Otherwise, None + """ + raise NotImplementedError + + +class BaseObjectState(BaseObjectRequirement, Serializable, Registerable, Recreatable, ABC): """ Base ObjectState class. Do NOT inherit from this class directly - use either AbsoluteObjectState or RelativeObjectState. @@ -50,20 +95,6 @@ def __init__(self, obj): @classmethod def is_compatible(cls, obj, **kwargs): - """ - Determines whether this object state is compatible with object @obj or not (i.e.: whether the state can be - successfully instantiated with @self.obj given other constructor arguments **kwargs. - - NOTE: Can be further extended by subclass - - Args: - obj (StatefulObject): Object whose compatibility with this state should be checked - - Returns: - 2-tuple: - - bool: Whether the given object is compatible with this object state or not - - None or str: If not compatible, the reason why it is not compatible. Otherwise, None - """ # Make sure all required dependencies are included in this object's state dictionary for dep in cls.get_dependencies(): if dep not in obj.states: @@ -78,22 +109,6 @@ def is_compatible(cls, obj, **kwargs): @classmethod def is_compatible_asset(cls, prim, **kwargs): - """ - Determines whether this object state is compatible with object with corresponding prim @prim or not - (i.e.: whether the state can be successfully instantiated with @self.obj given other constructor - arguments **kwargs. This is a useful check to evaluate an object's USD that hasn't been explicitly imported - into OmniGibson yet. - - NOTE: Can be further extended by subclass - - Args: - prim (Usd.Prim): Object prim whose compatibility with this state should be checked - - Returns: - 2-tuple: - - bool: Whether the given object is compatible with this object state or not - - None or str: If not compatible, the reason why it is not compatible. Otherwise, None - """ # Make sure all required kwargs are specified default_kwargs = inspect.signature(cls.__init__).parameters for kwarg, val in default_kwargs.items(): diff --git a/omnigibson/object_states/particle.py b/omnigibson/object_states/particle.py new file mode 100644 index 000000000..f125265ad --- /dev/null +++ b/omnigibson/object_states/particle.py @@ -0,0 +1,16 @@ +import numpy as np +from omnigibson.object_states.object_state_base import BaseObjectRequirement + + +class ParticleRequirement(BaseObjectRequirement): + """ + Class for sanity checking objects that requires particle systems + """ + + @classmethod + def is_compatible(cls, obj, **kwargs): + from omnigibson.macros import gm + if not gm.USE_GPU_DYNAMICS: + return False, f"Particle systems are not enabled when GPU dynamics is off." + + return True, None diff --git a/omnigibson/object_states/sliceable.py b/omnigibson/object_states/sliceable.py new file mode 100644 index 000000000..aa6be7b10 --- /dev/null +++ b/omnigibson/object_states/sliceable.py @@ -0,0 +1,30 @@ +import numpy as np +from omnigibson.object_states.object_state_base import BaseObjectRequirement + + +class SliceableRequirement(BaseObjectRequirement): + """ + Class for sanity checking objects that request the "sliceable" ability + """ + + @classmethod + def is_compatible(cls, obj, **kwargs): + # Avoid circular imports + from omnigibson.objects.dataset_object import DatasetObject + # Make sure object is dataset object + if not isinstance(obj, DatasetObject): + return False, f"Only compatible with DatasetObject, but {obj} is of type {type(obj)}" + # Check to make sure object parts are properly annotated in this object's metadata + if not obj.metadata["object_parts"]: + return False, f"Missing required metadata 'object_parts'." + + return True, None + + @classmethod + def is_compatible_asset(cls, prim, **kwargs): + # Check to make sure object parts are properly annotated in this object's metadata + metadata = prim.GetCustomData().get("metadata", dict()) + if not metadata.get("object_parts", None): + return False, f"Missing required metadata 'object_parts'." + + return True, None diff --git a/omnigibson/objects/controllable_object.py b/omnigibson/objects/controllable_object.py index 2e9bacf50..f51fc6124 100644 --- a/omnigibson/objects/controllable_object.py +++ b/omnigibson/objects/controllable_object.py @@ -87,6 +87,7 @@ def __init__( self._last_action = None self._controllers = None self.dof_names_ordered = None + self._control_enabled = True # Run super init super().__init__( @@ -314,11 +315,23 @@ def apply_action(self, action): controller.update_goal(command=action[idx : idx + controller.command_dim], control_dict=self.get_control_dict()) # Update idx idx += controller.command_dim + + @property + def control_enabled(self): + return self._control_enabled + + @control_enabled.setter + def control_enabled(self, value): + self._control_enabled = value def step(self): """ Takes a controller step across all controllers and deploys the computed control signals onto the object. """ + # Skip if we don't have control enabled + if not self.control_enabled: + return + # Skip this step if our articulation view is not valid if self._articulation_view_direct is None or not self._articulation_view_direct.initialized: return diff --git a/omnigibson/objects/object_base.py b/omnigibson/objects/object_base.py index d9cea7cd6..673f33f5c 100644 --- a/omnigibson/objects/object_base.py +++ b/omnigibson/objects/object_base.py @@ -137,7 +137,7 @@ def _post_load(self): # The custom scaling / fixed joints requirement is needed because omniverse complains about scaling that # occurs with respect to fixed joints, as omni will "snap" bodies together otherwise scale = np.ones(3) if self._load_config["scale"] is None else np.array(self._load_config["scale"]) - if self.n_joints == 0 and (np.all(np.isclose(scale, 1.0, atol=1e-3)) or self.n_fixed_joints == 0) and (self._load_config["kinematic_only"] != False): + if self.n_joints == 0 and (np.all(np.isclose(scale, 1.0, atol=1e-3)) or self.n_fixed_joints == 0) and (self._load_config["kinematic_only"] != False) and not self.has_attachment_points: kinematic_only = True # Validate that we didn't make a kinematic-only decision that does not match diff --git a/omnigibson/objects/stateful_object.py b/omnigibson/objects/stateful_object.py index 1cfecc238..20bc82003 100644 --- a/omnigibson/objects/stateful_object.py +++ b/omnigibson/objects/stateful_object.py @@ -11,6 +11,7 @@ from omnigibson.object_states.factory import ( get_default_states, get_state_name, + get_requirements_for_ability, get_states_for_ability, get_states_by_dependency_order, get_texture_change_states, @@ -43,6 +44,20 @@ m.STEAM_EMITTER_HEIGHT_RATIO = 0.6 # z-height of generated steam relative to its object's native height, range [0, inf) m.FIRE_EMITTER_HEIGHT_RATIO = 0.4 # z-height of generated fire relative to its object's native height, range [0, inf) +class FlowEmitterLayerRegistry: + """ + Registry for flow emitter layers. This is used to ensure that all flow emitters are placed on unique layers, so that + they do not interfere with each other. + """ + def __init__(self): + self._layer = 0 + + def __call__(self): + self._layer += 1 + return self._layer + +LAYER_REGISTRY = FlowEmitterLayerRegistry() + class StatefulObject(BaseObject): """Objects that support object states.""" @@ -197,9 +212,22 @@ def prepare_object_states(self): # Map the state type (class) to ability name and params if gm.ENABLE_OBJECT_STATES: - for ability, params in self._abilities.items(): - for state_type in get_states_for_ability(ability): - states_info[state_type] = {"ability": ability, "params": state_type.postprocess_ability_params(params)} + for ability in tuple(self._abilities.keys()): + # First, sanity check all ability requirements + compatible = True + for requirement in get_requirements_for_ability(ability): + compatible, reason = requirement.is_compatible(obj=self) + if not compatible: + # Print out warning and pop ability + log.warning(f"Ability '{ability}' is incompatible with obj {self.name}, " + f"because requirement {requirement.__name__} was not met. Reason: {reason}") + self._abilities.pop(ability) + break + if compatible: + params = self._abilities[ability] + for state_type in get_states_for_ability(ability): + states_info[state_type] = {"ability": ability, + "params": state_type.postprocess_ability_params(params)} # Add the dependencies into the list, too, and sort based on the dependency chain # Must iterate over explicit tuple since dictionary changes size mid-iteration @@ -306,6 +334,8 @@ def _create_emitter_apis(self, emitter_type): colormap = stage.DefinePrim(flowOffscreen_prim_path + "/colormap", "FlowRayMarchColormapParams") self._emitters[emitter_type] = emitter + + layer_number = LAYER_REGISTRY() # Update emitter general settings. emitter.CreateAttribute("enabled", lazy.pxr.Sdf.ValueTypeNames.Bool, False).Set(False) @@ -314,6 +344,10 @@ def _create_emitter_apis(self, emitter_type): emitter.CreateAttribute("coupleRateFuel", lazy.pxr.Sdf.ValueTypeNames.Float, False).Set(emitter_config["coupleRateFuel"]) emitter.CreateAttribute("coupleRateVelocity", lazy.pxr.Sdf.ValueTypeNames.Float, False).Set(2.0) emitter.CreateAttribute("velocity", lazy.pxr.Sdf.ValueTypeNames.Float3, False).Set((0, 0, 0)) + emitter.CreateAttribute("layer", lazy.pxr.Sdf.ValueTypeNames.Int, False).Set(layer_number) + simulate.CreateAttribute("layer", lazy.pxr.Sdf.ValueTypeNames.Int, False).Set(layer_number) + offscreen.CreateAttribute("layer", lazy.pxr.Sdf.ValueTypeNames.Int, False).Set(layer_number) + renderer.CreateAttribute("layer", lazy.pxr.Sdf.ValueTypeNames.Int, False).Set(layer_number) advection.CreateAttribute("buoyancyPerTemp", lazy.pxr.Sdf.ValueTypeNames.Float, False).Set(emitter_config["buoyancyPerTemp"]) advection.CreateAttribute("burnPerTemp", lazy.pxr.Sdf.ValueTypeNames.Float, False).Set(emitter_config["burnPerTemp"]) advection.CreateAttribute("gravity", lazy.pxr.Sdf.ValueTypeNames.Float3, False).Set(emitter_config["gravity"]) @@ -394,8 +428,8 @@ def update_visuals(self): if state_type in get_fire_states(): emitter_enabled[EmitterType.FIRE] |= state.get_value() - for emitter_type in emitter_enabled: - self.set_emitter_enabled(emitter_type, emitter_enabled[emitter_type]) + for emitter_type in emitter_enabled: + self.set_emitter_enabled(emitter_type, emitter_enabled[emitter_type]) texture_change_states.sort(key=lambda s: get_texture_change_priority()[s.__class__]) object_state = texture_change_states[-1] if len(texture_change_states) > 0 else None diff --git a/omnigibson/prims/entity_prim.py b/omnigibson/prims/entity_prim.py index c3294ba8e..71bf6bee0 100644 --- a/omnigibson/prims/entity_prim.py +++ b/omnigibson/prims/entity_prim.py @@ -478,6 +478,20 @@ def links(self): """ return self._links + @cached_property + def has_attachment_points(self): + """ + Returns: + bool: Whether this object has any attachment points + """ + children = list(self.prim.GetChildren()) + while children: + child_prim = children.pop() + children.extend(child_prim.GetChildren()) + if "attachment" in child_prim.GetName(): + return True + return False + def _compute_articulation_tree(self): """ Get a graph of the articulation tree, where nodes are link names and edges diff --git a/omnigibson/robots/tiago.py b/omnigibson/robots/tiago.py index bd8c45249..69f026eaa 100644 --- a/omnigibson/robots/tiago.py +++ b/omnigibson/robots/tiago.py @@ -709,7 +709,7 @@ def set_position_orientation(self, position=None, orientation=None): # TODO: Reconsider the need for this. Why can't these behaviors be unified? Does the joint really need to move? # If the simulator is playing, set the 6 base joints to achieve the desired pose of base_footprint link frame - if og.sim.is_playing(): + if og.sim.is_playing() and self.initialized: # Find the relative transformation from base_footprint_link ("base_footprint") frame to root_link # ("base_footprint_x") frame. Assign it to the 6 1DoF joints that control the base. # Note that the 6 1DoF joints are originated from the root_link ("base_footprint_x") frame. diff --git a/omnigibson/scenes/scene_base.py b/omnigibson/scenes/scene_base.py index 04aa2f0ac..6d196f735 100644 --- a/omnigibson/scenes/scene_base.py +++ b/omnigibson/scenes/scene_base.py @@ -222,7 +222,10 @@ def _load_objects_from_scene_file(self): # Create desired systems for system_name in init_systems: - get_system(system_name) + if gm.USE_GPU_DYNAMICS: + get_system(system_name) + else: + log.warning(f"System {system_name} is not supported without GPU dynamics! Skipping...") # Iterate over all scene info, and instantiate object classes linked to the objects found on the stage # accordingly diff --git a/omnigibson/sensors/vision_sensor.py b/omnigibson/sensors/vision_sensor.py index 819d499ff..b53cdb55c 100644 --- a/omnigibson/sensors/vision_sensor.py +++ b/omnigibson/sensors/vision_sensor.py @@ -238,6 +238,8 @@ def _initialize(self): # Initialize sensors self.initialize_sensors(names=self._modalities) + for _ in range(3): + render() def initialize_sensors(self, names): """Initializes a raw sensor in the simulation. @@ -344,9 +346,9 @@ def _remap_instance_segmentation(self, img, id_to_labels, semantic_img, semantic # Hacky way to get the particles of MacroVisual/PhysicalParticleSystem # Remap instance segmentation and instance segmentation ID labels to system name if "Particle" in prim_name: - system_name = prim_name.split("Particle")[0] - assert system_name in REGISTERED_SYSTEMS, f"System name {system_name} is not in the registered systems!" - value = system_name + category_name = prim_name.split("Particle")[0] + assert category_name in REGISTERED_SYSTEMS, f"System name {category_name} is not in the registered systems!" + value = category_name else: # Remap instance segmentation labels to object name if not id: @@ -373,10 +375,15 @@ def _remap_instance_segmentation(self, img, id_to_labels, semantic_img, semantic if str(key) not in id_to_labels: semantic_label = semantic_img.flatten()[img_idx] assert semantic_label in semantic_labels, f"Semantic map value {semantic_label} is not in the semantic labels!" - system_name = semantic_labels[semantic_label] - assert system_name in REGISTERED_SYSTEMS, f"System name {system_name} is not in the registered systems!" - value = system_name - self._register_instance(value, id=id) + category_name = semantic_labels[semantic_label] + if category_name in REGISTERED_SYSTEMS: + value = category_name + self._register_instance(value, id=id) + # If the category name is not in the registered systems, + # which happens because replicator sometimes returns segmentation map and id_to_labels that are not in sync, + # we will label this as "unlabelled" for now + else: + value = "unlabelled" replicator_mapping[key] = value registry = VisionSensor.INSTANCE_ID_REGISTRY if id else VisionSensor.INSTANCE_REGISTRY @@ -488,6 +495,9 @@ def camera_parameters(self): # Add the camera params modality if it doesn't already exist if "camera_params" not in self._annotators: self.initialize_sensors(names="camera_params") + # Requires 3 render updates for camera params annotator to decome active + for _ in range(3): + render() # Grab and return the parameters return self._annotators["camera_params"].get_data() diff --git a/omnigibson/tasks/point_navigation_task.py b/omnigibson/tasks/point_navigation_task.py index 62b57f273..b1ad9ff1d 100644 --- a/omnigibson/tasks/point_navigation_task.py +++ b/omnigibson/tasks/point_navigation_task.py @@ -239,7 +239,7 @@ def _sample_initial_pose_and_goal_pos(self, env, max_trials=100): robot=env.robots[self._robot_idn]) _, dist = env.scene.get_shortest_path(self._floor, initial_pos[:2], goal_pos[:2], entire_path=False, robot=env.robots[self._robot_idn]) # If a path range is specified, make sure distance is valid - if self._path_range is None or self._path_range[0] < dist < self._path_range[1]: + if dist is not None and (self._path_range is None or self._path_range[0] < dist < self._path_range[1]): in_range_dist = True break # Notify if we weren't able to get a valid start / end point sampled in the requested range diff --git a/omnigibson/transition_rules.py b/omnigibson/transition_rules.py index 463077c24..c5ebf2fea 100644 --- a/omnigibson/transition_rules.py +++ b/omnigibson/transition_rules.py @@ -907,10 +907,6 @@ def transition(cls, object_candidates): # Object parts offset annotation are w.r.t the base link of the whole object. pos, orn = sliceable_obj.get_position_orientation() - # If it has no parts, silently fail - if not sliceable_obj.metadata["object_parts"]: - continue - # Load object parts for i, part in enumerate(sliceable_obj.metadata["object_parts"].values()): # List of dicts gets replaced by {'0':dict, '1':dict, ...} diff --git a/omnigibson/utils/asset_utils.py b/omnigibson/utils/asset_utils.py index 8bd5eca03..ca11e6fdf 100644 --- a/omnigibson/utils/asset_utils.py +++ b/omnigibson/utils/asset_utils.py @@ -216,7 +216,7 @@ def get_all_object_category_models(category): def get_all_object_category_models_with_abilities(category, abilities): """ - Get all object models from @category whose assets are properly annotated with necessary metalinks to support + Get all object models from @category whose assets are properly annotated with necessary requirements to support abilities @abilities Args: @@ -226,44 +226,52 @@ def get_all_object_category_models_with_abilities(category, abilities): models Returns: - list of str: all object models belonging to @category which are properly annotated with necessary metalinks + list of str: all object models belonging to @category which are properly annotated with necessary requirements to support the requested list of @abilities """ # Avoid circular imports from omnigibson.objects.dataset_object import DatasetObject - from omnigibson.object_states.factory import get_states_for_ability - from omnigibson.object_states.link_based_state_mixin import LinkBasedStateMixin + from omnigibson.object_states.factory import get_requirements_for_ability, get_states_for_ability # Get all valid models all_models = get_all_object_category_models(category=category) # Generate all object states required per object given the requested set of abilities - state_types_and_params = [(state_type, params) for ability, params in abilities.items() - for state_type in get_states_for_ability(ability)] - for state_type, _ in state_types_and_params: - # Add each state's dependencies, too. Note that only required dependencies are added. - for dependency in state_type.get_dependencies(): - if all(other_state != dependency for other_state, _ in state_types_and_params): - state_types_and_params.append((dependency, dict())) + abilities_info = {ability: [(state_type, params) for state_type in get_states_for_ability(ability)] + for ability, params in abilities.items()} # Get mapping for class init kwargs state_init_default_kwargs = dict() - for state_type, _ in state_types_and_params: - default_kwargs = inspect.signature(state_type.__init__).parameters - state_init_default_kwargs[state_type] = \ - {kwarg: val.default for kwarg, val in default_kwargs.items() - if kwarg != "self" and val.default != inspect._empty} + + for ability, state_types_and_params in abilities_info.items(): + for state_type, _ in state_types_and_params: + # Add each state's dependencies, too. Note that only required dependencies are added. + for dependency in state_type.get_dependencies(): + if all(other_state != dependency for other_state, _ in state_types_and_params): + state_types_and_params.append((dependency, dict())) + + for state_type, _ in state_types_and_params: + default_kwargs = inspect.signature(state_type.__init__).parameters + state_init_default_kwargs[state_type] = \ + {kwarg: val.default for kwarg, val in default_kwargs.items() + if kwarg != "self" and val.default != inspect._empty} # Iterate over all models and sanity check each one, making sure they satisfy all the requested @abilities valid_models = [] - def supports_state_types(states_and_params, obj_prim): - # Check all link states - for state_type, params in states_and_params: - kwargs = deepcopy(state_init_default_kwargs[state_type]) - kwargs.update(params) - if not state_type.is_compatible_asset(prim=obj_prim, **kwargs)[0]: - return False + def supports_abilities(info, obj_prim): + for ability, states_and_params in info.items(): + # Check ability requirements + for requirement in get_requirements_for_ability(ability): + if not requirement.is_compatible_asset(prim=obj_prim)[0]: + return False + + # Check all link states + for state_type, params in states_and_params: + kwargs = deepcopy(state_init_default_kwargs[state_type]) + kwargs.update(params) + if not state_type.is_compatible_asset(prim=obj_prim, **kwargs)[0]: + return False return True for model in all_models: @@ -272,7 +280,7 @@ def supports_state_types(states_and_params, obj_prim): with decrypted(usd_path) as fpath: stage = lazy.pxr.Usd.Stage.Open(fpath) prim = stage.GetDefaultPrim() - if supports_state_types(state_types_and_params, prim): + if supports_abilities(abilities_info, prim): valid_models.append(model) return valid_models diff --git a/tests/test_object_states.py b/tests/test_object_states.py index ab9faa268..4adb2a99a 100644 --- a/tests/test_object_states.py +++ b/tests/test_object_states.py @@ -676,7 +676,7 @@ def test_toggled_on(): assert stove.states[ToggledOn].set_value(False) assert not stove.states[ToggledOn].get_value() - +@pytest.mark.skip(reason="skipping attachment for now") @og_test def test_attached_to(): shelf_back_panel = og.sim.scene.object_registry("name", "shelf_back_panel")