Skip to content

Commit cd74fbf

Browse files
committed
chore: New release 2.7.1
1 parent f7eab30 commit cd74fbf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2285
-540
lines changed

examples/nova_sdk/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
numpy>=1.20.0
22
python-dotenv>=1.1.0
33
wandelbots-nova>=2.0.0
4-
wandelbots-isaacsim-api>=2.0.2
4+
wandelbots-isaacsim-api>=2.7.0

examples/nova_sdk/viewer.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,23 @@
3030
viewer=trajectory_utils.TrajectoryViewer(
3131
trajectory_success=trajectory_utils.TrajectoryPlanResultConfiguration(
3232
name="PlannedTrajectory",
33-
options=isaac_sim_api.models.TrajectoryOptions(color=[0, 255, 0], width=20),
33+
parent_prim_path="/World",
34+
options=isaac_sim_api.models.TrajectoryOptions(
35+
color=isaac_sim_api.models.Color([0, 255, 0]),
36+
width=isaac_sim_api.models.Width([20]),
37+
),
3438
optimization=trajectory_utils.TrajectoryDrawOptimization(
3539
min_time_delta_seconds=0.1,
3640
min_pose_distance_millimeters=10.0,
3741
),
3842
),
3943
trajectory_failure=trajectory_utils.TrajectoryPlanResultConfiguration(
4044
name="PlanningFailed",
41-
options=isaac_sim_api.models.TrajectoryOptions(color=[255, 0, 0], width=20),
45+
parent_prim_path="/World",
46+
options=isaac_sim_api.models.TrajectoryOptions(
47+
color=isaac_sim_api.models.Color([255, 0, 0]),
48+
width=isaac_sim_api.models.Width([20]),
49+
),
4250
optimization=trajectory_utils.TrajectoryDrawOptimization(
4351
min_time_delta_seconds=0.1,
4452
min_pose_distance_millimeters=10.0,

exts/wandelbots.omni/config/extension.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ reloadable = true
33
order = 1000 # Make sure all .dlls are loaded before this extension
44

55
[package]
6-
version = "2.4.6"
6+
version = "2.7.1"
77
category = "Simulation"
88
title = "Wandelbots NOVA"
99
description = "Simulate your robotics cell running on Wandelbots NOVA."
@@ -42,6 +42,7 @@ icon = "data/icon.png"
4242
"omni.services.transport.client.http_async" = {}
4343
"omni.kit.test" = {}
4444
"omni.graph" = {}
45+
"wandelbots.usd" = { version = "1.1.3" }
4546
"omni.kit.clipboard" = {}
4647

4748

exts/wandelbots.omni/docs/CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog - Wandelbots NOVA x Nvidia Isaac Sim
22

3+
## 2.7.1 (2025-09-18)
4+
5+
### Bug Fixes
6+
7+
* Added python version to prebundle path
8+
9+
## 2.7.0 (2025-09-16)
10+
11+
### Features
12+
13+
* Added option to define width/color of individual trajectory points
14+
15+
## 2.6.0 (2025-09-15)
16+
17+
### Features
18+
19+
* **CIS-1948:** Merged ghost objects to single mesh. Materials are now stored next to scene
20+
21+
## 2.5.0 (2025-09-11)
22+
23+
### Features
24+
25+
* **RPS-1733:** Added support for NOVA OpenUSD schema
26+
327
## 2.4.6 (2025-09-09)
428

529
### Bug Fixes
3.15 KB
Loading

exts/wandelbots.omni/wandelbots/omni/core/collision/collision_export_service.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import omni.timeline
99
from pxr import Sdf
1010
import wandelbots.omni.core.collision.shapes as collision_shapes
11+
from wandelbots.omni.utils.prims import Pose, PrimUtils, WSPose
1112

1213

1314
class SphereSweepParameters(pydantic.BaseModel):
@@ -80,7 +81,10 @@ def __del__(self):
8081
physx_bindings.release_physx_property_query_interface(self._physx_query)
8182

8283
def collision_sweep(
83-
self, sweep_args: SweepParameters, stage: Usd.Stage = None
84+
self,
85+
sweep_args: SweepParameters,
86+
stage: Usd.Stage = None,
87+
reference_prim_pose: Pose = None,
8488
) -> dict[str, collision_shapes.Collider]:
8589
if not omni.timeline.get_timeline_interface().is_playing():
8690
raise RuntimeError(
@@ -199,8 +203,19 @@ def on_sweep_hit(hit: physx_bindings.SweepHit) -> bool:
199203
)
200204
else:
201205
raise ValueError(f"Unknown sweep type: {sweep_args.sweep_type}")
202-
203-
return colliders
206+
return {
207+
prim_path: collision_shapes.Collider(
208+
shape=collider.shape,
209+
pose=PrimUtils.get_relative_pose(
210+
reference_prim_pose,
211+
WSPose(pose=collider.pose.position + collider.pose.orientation),
212+
).to_nova_pose()
213+
if reference_prim_pose
214+
else collider.pose,
215+
)
216+
for prim_path, collider in colliders.items()
217+
if collider is not None
218+
}
204219

205220

206221
_collision_export_service = CollisionExportService()

exts/wandelbots.omni/wandelbots/omni/datatypes.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ def to_nova_pose(self) -> nova_models.Pose:
2020
orientation=self.pose[3:],
2121
)
2222

23+
def __str__(self):
24+
return "(" + ", ".join([f"{round(x, 3)}" for x in self.pose]) + ")"
25+
2326

2427
class QuatPose(BaseModel):
2528
pose: conlist(float, min_length=7, max_length=7) = Field(
2629
[0, 0, 0, 1, 0, 0, 0], description="Pose in quaternion vector format (WS)"
2730
)
2831

32+
def __str__(self):
33+
return "(" + ", ".join([f"{round(x, 3)}" for x in self.pose]) + ")"
34+
2935

3036
class Auth0Credentials(BaseModel):
3137
host: str
@@ -125,7 +131,12 @@ class GhostObject(BaseModel):
125131
COORDINATE_SYSTEM = Literal["world", "local"]
126132
ROTATION_TYPES = Literal["cartesian", "quaternions"]
127133

128-
GHOST_MATERIAL_MDL_FILE = str(pathlib.Path(__file__).parent / "assets" / "wb_ghost.mdl")
134+
PROJECT_MDL_DIR = pathlib.Path(".wandelbots") / "mdl"
135+
136+
GHOST_MATERIAL_MDL_EXT_FILE = pathlib.Path(__file__).parent / "assets" / "wb_ghost.mdl"
137+
GHOST_MATERIAL_MDL_PROJECT_FILE = PROJECT_MDL_DIR / "wb_ghost.mdl"
138+
139+
129140
SHADER_IDENTIFIER = "GhostTeaching"
130141

131142
GIZMO_USD_FILE = str(pathlib.Path(__file__).parent / "assets" / "gizmo.usd")

exts/wandelbots.omni/wandelbots/omni/extension.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from wandelbots.omni.utils.dependencies import remove_extension_packages
1818
from wandelbots.omni.base import omniservice_base_app
1919
from wandelbots.omni.environment import host_database
20+
from wandelbots.omni.ui.schema.schema_extension_ui import SchemaExtensionUI
2021
from wandelbots.omni.io import get_io_stream_service, IOStreamService
2122
from wandelbots.omni.manipulators import get_motion_group_service, MotionGroupService
2223
from wandelbots.omni.utils.base import get_current_version
@@ -31,6 +32,7 @@
3132

3233
class OmniService(omni.ext.IExt):
3334
def on_startup(self, ext_id) -> None:
35+
self.schema_extension = SchemaExtensionUI()
3436
carb.log_info("Mounting /omniservice")
3537

3638
# Collect services to bind them to the timeline state
@@ -123,6 +125,9 @@ def on_shutdown(self) -> None:
123125

124126
remove_menu_items(self._menu_items, self.menu_item_name)
125127

128+
if self.instance_list_window:
129+
self.instance_list_window.close()
130+
126131
asyncio.get_event_loop().create_task(
127132
OmniService._async_shutdown(
128133
self.motion_group_service, self.io_stream_service
@@ -136,6 +141,9 @@ def on_shutdown(self) -> None:
136141
carb.log_verbose("Stopping timeline")
137142
self.timeline.stop()
138143

144+
self.schema_extension.unregister()
145+
self.schema_extension = None
146+
139147
def _create_menu(self, ext_id):
140148
self._menu_items = [
141149
make_menu_item_description(

exts/wandelbots.omni/wandelbots/omni/instances/instances_service.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,26 @@
1818
NOVAControllerData,
1919
)
2020
from wandelbots.omni.environment import instance_store
21-
22-
from wandelbots.omni.manipulators.motion_group_service import (
21+
from wandelbots.omni.manipulators import (
22+
is_prim_motion_group,
2323
get_motion_group_service,
2424
MotionGroupConfiguration,
2525
MotionStreamConfiguration,
2626
)
27+
from pxr import Usd
28+
29+
try:
30+
import isaacsim.core.utils.stage as stage_utils
31+
except ImportError:
32+
import omni.isaac.core.utils.stage as stage_utils # type: ignore
2733

2834

2935
class NOVAInstancesService:
3036
def __init__(self):
3137
self._cloud_instances: list[NOVACloudInstance] = []
3238
self._custom_instances: list[NOVACustomInstance] = []
3339
self._instances_api = NOVAInstancesAPI()
34-
self._connected_motion_groups: dict[str, MotionStreamConfiguration] = {}
40+
self._connected_motion_groups: dict[str, MotionGroupConfiguration] = {}
3541
self._selected_articulations: dict[str, str] = {}
3642

3743
def get_selected_articulation(self, identifier: str) -> Optional[str]:
@@ -115,22 +121,22 @@ def _disconnect_motion_groups(self, instance: NOVAInstance):
115121
carb.log_verbose(
116122
f"Checking motion group at {prim_path} for instance {instance.host}"
117123
)
118-
motion_group = motion_group_service.get_motion_group(prim_path)
124+
motion_group = motion_group_service.get_motion_group_configuration(
125+
prim_path
126+
)
119127
if not motion_group:
120128
continue
121129

122130
if motion_group.motion_stream_configuration.host == instance.host:
123131
carb.log_verbose(
124-
f"Removing motion group {motion_group.name} at {prim_path} for instance {instance.host}"
132+
f"Removing motion group {prim_path} for instance {instance.host}"
125133
)
126134
try:
127135
motion_group_service.remove_motion_group(prim_path)
128136
identifier = motion_group.identifier
129137
self.remove_from_connected_motion_group(identifier)
130138
except Exception as e:
131-
carb.log_error(
132-
f"Failed to remove motion group {motion_group.name} at {prim_path}: {e}"
133-
)
139+
carb.log_error(f"Failed to remove motion group {prim_path}: {e}")
134140

135141
def delete_motion_group(
136142
self,
@@ -175,6 +181,7 @@ def create_motion_group_from_nova(
175181
use_external_joint_stream: bool,
176182
callback: Optional[callable] = None,
177183
):
184+
prim: Usd.Prim = stage_utils.get_current_stage().GetPrimAtPath(prim_path)
178185
try:
179186
if not prim_path:
180187
carb.log_warn("No articulation path provided for motion group creation")
@@ -191,18 +198,16 @@ def create_motion_group_from_nova(
191198
f"Creating motion group '{motion_group_name}' assigned to '{prim_path}'"
192199
)
193200

194-
if (
195-
motion_group_service.get_motion_group_by_prim_path(prim_path)
196-
is not None
197-
):
198-
carb.log_warn(f"Articulation at {prim_path} is already connected.")
199-
callback(False, "Articulation is already connected.")
200-
return
201+
if is_prim_motion_group(prim):
202+
carb.log_info(
203+
f"Articulation at {prim_path} is already connected. Updating configuration to selected motion group"
204+
)
201205

202206
motion_stream_config = MotionStreamConfiguration(
203207
host=instance.host,
204208
secure_connection=instance.is_secure_connection,
205209
cell=controller.cell_name,
210+
controller=controller.name,
206211
motion_group=motion_group_name,
207212
use_external_joint_stream=use_external_joint_stream,
208213
)
@@ -282,8 +287,36 @@ def is_signed_in(self) -> bool:
282287
return auth_token != "" and auth_token is not None
283288

284289
@property
285-
def connected_motion_groups(self) -> dict[str, MotionStreamConfiguration]:
290+
def connected_motion_groups(self) -> dict[str, MotionGroupConfiguration]:
286291
return self._connected_motion_groups
287292

288293
def handle_authentication_error(self):
289294
invalidate_auth_token()
295+
296+
def find_connected_motion_group_by(
297+
self,
298+
prim_path: str = None,
299+
host: str = None,
300+
secured: bool = None,
301+
cell: str = None,
302+
controller: str = None,
303+
motion_group: str = None,
304+
) -> list[MotionGroupConfiguration]:
305+
results = []
306+
for connected_motion_group in self._connected_motion_groups.values():
307+
if prim_path and connected_motion_group.prim_path != prim_path:
308+
continue
309+
stream_config = connected_motion_group.motion_stream_configuration
310+
if host and stream_config.host != host:
311+
continue
312+
if secured is not None and stream_config.secure_connection != secured:
313+
continue
314+
if cell and stream_config.cell != cell:
315+
continue
316+
if controller and stream_config.controller != controller:
317+
continue
318+
if motion_group and stream_config.motion_group != motion_group:
319+
continue
320+
results.append(connected_motion_group)
321+
322+
return results
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
from .motion_stream_configuration import MotionStreamConfiguration
22
from .motion_stream_connector import MotionStreamConnector
3-
from .motion_group import MotionGroup, MotionGroupConfiguration
3+
from .motion_group import (
4+
MotionGroup,
5+
MotionGroupConfiguration,
6+
is_prim_motion_group,
7+
get_motion_group_configuration_from_prim,
8+
)
49
from .motion_group_service import (
510
MotionGroupService,
611
get_motion_group_service,
712
)
8-
from .utils import get_scene_articulation_roots
13+
from .utils import get_scene_motion_group_prim_paths
914

1015

1116
__all__ = [
1217
"MotionStreamConfiguration",
1318
"MotionGroupService",
14-
"MotionStreamConfiguration",
1519
"MotionStreamConnector",
1620
"MotionGroup",
1721
"MotionGroupConfiguration",
1822
"get_motion_group_service",
19-
"get_scene_articulation_roots",
23+
"get_scene_motion_group_prim_paths",
24+
"is_prim_motion_group",
25+
"get_motion_group_configuration_from_prim",
2026
]

0 commit comments

Comments
 (0)