Skip to content

Commit 8b4b0f0

Browse files
sea-bassbrentyi
andauthored
Support both visual and collision meshes when loading URDF (nerfstudio-project#487)
* Display visual and collision meshes when loading URDF * PR comments * Bump min version of robot_descriptions * Fix type annotation for trimesh scene * Revert 09_urdf_visualizer.py * Remove note about PR * Try fix trimesh annotation * pyright doesnt seem to know how to lint zip with strict kwarg * Sort imports * Handle some more edge cases * nits * incorporate feedback from @sea-bass * example nit --------- Co-authored-by: Brent Yi <yibrenth@gmail.com>
1 parent e83564b commit 8b4b0f0

File tree

6 files changed

+230
-64
lines changed

6 files changed

+230
-64
lines changed

examples_dev/09_urdf_visualizer.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def create_robot_control_sliders(
3434
) in viser_urdf.get_actuated_joint_limits().items():
3535
lower = lower if lower is not None else -np.pi
3636
upper = upper if upper is not None else np.pi
37-
initial_pos = 0.0 if lower < 0 and upper > 0 else (lower + upper) / 2.0
37+
initial_pos = 0.0 if lower < -0.1 and upper > 0.1 else (lower + upper) / 2.0
3838
slider = server.gui.add_slider(
3939
label=joint_name,
4040
min=lower,
@@ -66,16 +66,28 @@ def main(
6666
"anymal_c",
6767
"go2",
6868
] = "panda",
69+
load_meshes: bool = True,
70+
load_collision_meshes: bool = False,
6971
) -> None:
7072
# Start viser server.
7173
server = viser.ViserServer()
7274

7375
# Load URDF.
7476
#
7577
# This takes either a yourdfpy.URDF object or a path to a .urdf file.
78+
urdf = load_robot_description(
79+
robot_type + "_description",
80+
load_meshes=load_meshes,
81+
build_scene_graph=load_meshes,
82+
load_collision_meshes=load_collision_meshes,
83+
build_collision_scene_graph=load_collision_meshes,
84+
)
7685
viser_urdf = ViserUrdf(
7786
server,
78-
urdf_or_path=load_robot_description(robot_type + "_description"),
87+
urdf_or_path=urdf,
88+
load_meshes=load_meshes,
89+
load_collision_meshes=load_collision_meshes,
90+
collision_mesh_color_override=(1.0, 0.0, 0.0, 0.5),
7991
)
8092

8193
# Create sliders in GUI that help us move the robot joints.
@@ -84,10 +96,33 @@ def main(
8496
server, viser_urdf
8597
)
8698

99+
# Add visibility checkboxes.
100+
with server.gui.add_folder("Visibility"):
101+
show_meshes_cb = server.gui.add_checkbox(
102+
"Show meshes",
103+
viser_urdf.show_visual,
104+
)
105+
show_collision_meshes_cb = server.gui.add_checkbox(
106+
"Show collision meshes", viser_urdf.show_collision
107+
)
108+
109+
@show_meshes_cb.on_update
110+
def _(_):
111+
viser_urdf.show_visual = show_meshes_cb.value
112+
113+
@show_collision_meshes_cb.on_update
114+
def _(_):
115+
viser_urdf.show_collision = show_collision_meshes_cb.value
116+
117+
# Hide checkboxes if meshes are not loaded.
118+
show_meshes_cb.visible = load_meshes
119+
show_collision_meshes_cb.visible = load_collision_meshes
120+
87121
# Set initial robot configuration.
88122
viser_urdf.update_cfg(np.array(initial_config))
89123

90124
# Create grid.
125+
trimesh_scene = viser_urdf._urdf.scene or viser_urdf._urdf.collision_scene
91126
server.scene.add_grid(
92127
"/grid",
93128
width=2,
@@ -96,7 +131,7 @@ def main(
96131
0.0,
97132
0.0,
98133
# Get the minimum z value of the trimesh scene.
99-
viser_urdf._urdf.scene.bounds[0, 2],
134+
trimesh_scene.bounds[0, 2] if trimesh_scene is not None else 0.0,
100135
),
101136
)
102137

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ examples = [
6262
"torch>=1.13.1",
6363
"matplotlib>=3.7.1",
6464
"plotly>=5.21.0",
65-
"robot_descriptions>=1.10.0",
65+
"robot_descriptions>=1.18.0",
6666
"gdown>=4.6.6",
6767
"plyfile",
6868
"opencv-python",

src/viser/client/src/ControlPanel/Generated.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,23 @@ function GuiContainer({ containerUuid }: { containerUuid: string }) {
7979
);
8080
const out = (
8181
<Box pt="xs">
82-
{guiUuidOrderPairArray.map((pair) => (
83-
<GeneratedInput key={pair.uuid} guiUuid={pair.uuid} />
82+
{guiUuidOrderPairArray.map((pair, index) => (
83+
<GeneratedInput
84+
key={pair.uuid}
85+
guiUuid={pair.uuid}
86+
nextGuiUuid={guiUuidOrderPairArray[index + 1]?.uuid ?? null}
87+
/>
8488
))}
8589
</Box>
8690
);
8791
return out;
8892
}
8993

9094
/** A single generated GUI element. */
91-
function GeneratedInput(props: { guiUuid: string }) {
95+
function GeneratedInput(props: {
96+
guiUuid: string;
97+
nextGuiUuid: string | null;
98+
}) {
9299
const viewer = React.useContext(ViewerContext)!;
93100
const conf = viewer.useGui((state) => state.guiConfigFromUuid[props.guiUuid]);
94101
if (conf === undefined) {
@@ -97,7 +104,7 @@ function GeneratedInput(props: { guiUuid: string }) {
97104
}
98105
switch (conf.type) {
99106
case "GuiFolderMessage":
100-
return <FolderComponent {...conf} />;
107+
return <FolderComponent {...conf} nextGuiUuid={props.nextGuiUuid} />;
101108
case "GuiTabGroupMessage":
102109
return <TabGroupComponent {...conf} />;
103110
case "GuiMarkdownMessage":

src/viser/client/src/components/Folder.css.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,8 @@ export const folderWrapper = style({
66
position: "relative",
77
marginLeft: vars.spacing.xs,
88
marginRight: vars.spacing.xs,
9-
// If there's a GUI element above, we need more margin.
109
marginTop: vars.spacing.xs,
11-
// If there's a GUI element below, we need more margin.
12-
// Note: 0.5em is the vertical margin below general GUI elements.
13-
marginBottom: "1.2em",
14-
":last-child": {
15-
marginBottom: "0.5em",
16-
},
10+
marginBottom: vars.spacing.xs,
1711
paddingBottom: `calc(${vars.spacing.xs} - 0.5em)`,
1812
});
1913

src/viser/client/src/components/Folder.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ import { folderLabel, folderToggleIcon, folderWrapper } from "./Folder.css";
1010
export default function FolderComponent({
1111
uuid,
1212
props: { label, visible, expand_by_default },
13-
}: GuiFolderMessage) {
13+
nextGuiUuid,
14+
}: GuiFolderMessage & { nextGuiUuid: string | null }) {
1415
const viewer = React.useContext(ViewerContext)!;
1516
const [opened, { toggle }] = useDisclosure(expand_by_default);
1617
const guiIdSet = viewer.useGui(
1718
(state) => state.guiUuidSetFromContainerUuid[uuid],
1819
);
1920
const guiContext = React.useContext(GuiComponentContext)!;
2021
const isEmpty = guiIdSet === undefined || Object.keys(guiIdSet).length === 0;
22+
const nextGuiType = viewer.useGui((state) =>
23+
nextGuiUuid == null ? null : state.guiConfigFromUuid[nextGuiUuid]?.type,
24+
);
2125

2226
const ToggleIcon = opened ? IconChevronUp : IconChevronDown;
2327
if (!visible) return null;
2428
return (
25-
<Paper withBorder className={folderWrapper}>
29+
<Paper
30+
withBorder
31+
className={folderWrapper}
32+
mb={nextGuiType === "GuiFolderMessage" ? "md" : undefined}
33+
>
2634
<Paper
2735
className={folderLabel}
2836
style={{

0 commit comments

Comments
 (0)