Skip to content

Commit bda69c3

Browse files
authored
Update rendering related functors (#34)
1 parent 3e9df73 commit bda69c3

File tree

9 files changed

+182
-16
lines changed

9 files changed

+182
-16
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ jobs:
5353
cd ${GITHUB_WORKSPACE}/docs
5454
echo "Start Building docs..."
5555
make html
56-
- name: Upload pkg
56+
- name: Upload docs artifact
57+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
5758
uses: actions/upload-pages-artifact@v3
5859
with:
5960
path: ${{ github.workspace }}/docs/build/html

embodichain/data/enum.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@
1414
# limitations under the License.
1515
# ----------------------------------------------------------------------------
1616

17-
from enum import Enum
17+
from enum import Enum, IntEnum
18+
19+
20+
class SemanticMask(IntEnum):
21+
"""
22+
SemanticMask is an enumeration representing different semantic regions in an image or scene.
23+
24+
Attributes:
25+
BACKGROUND (int): Represents the background region (value: 0).
26+
FOREGROUND (int): Represents the foreground objects (value: 1).
27+
ROBOT (int): Represents the robot region (value: 2).
28+
"""
29+
30+
BACKGROUND = 0
31+
FOREGROUND = 1
32+
ROBOT = 2
1833

1934

2035
class EndEffector(Enum):

embodichain/lab/gym/envs/managers/observations.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def compute_semantic_mask(
116116
Returns:
117117
A tensor of shape (num_envs, height, width) representing the semantic mask.
118118
"""
119+
from embodichain.data.enum import SemanticMask
119120

120121
sensor: Union[Camera, StereoCamera] = env.sim.get_sensor(entity_cfg.uid)
121122
if sensor.cfg.enable_mask is False:
@@ -160,7 +161,19 @@ def compute_semantic_mask(
160161

161162
background_mask = ~(robot_mask | foreground_mask).squeeze_(-1)
162163

163-
return torch.stack([robot_mask, background_mask, foreground_mask], dim=-1)
164+
masks = [None, None, None]
165+
masks_ids = [member.value for member in SemanticMask]
166+
assert len(masks) == len(
167+
masks_ids
168+
), "Different length of mask slots and SemanticMask Enum {}.".format(masks_ids)
169+
mask_id_to_label = {
170+
SemanticMask.BACKGROUND.value: background_mask,
171+
SemanticMask.FOREGROUND.value: foreground_mask,
172+
SemanticMask.ROBOT.value: robot_mask,
173+
}
174+
for mask_id in masks_ids:
175+
masks[mask_id] = mask_id_to_label[mask_id]
176+
return torch.stack(masks, dim=-1)
164177

165178

166179
class compute_exteroception(Functor):

embodichain/lab/gym/envs/managers/randomization/rendering.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -379,16 +379,19 @@ def __init__(self, cfg: FunctorCfg, env: EmbodiedEnv):
379379
if self.entity_cfg.uid == "default_plane":
380380
pass
381381
else:
382-
self.entity: Union[RigidObject, Articulation] = env.sim.get_asset(
383-
self.entity_cfg.uid
384-
)
385-
386-
if not isinstance(self.entity, (RigidObject, Articulation)):
387-
raise ValueError(
388-
f"Randomization functor 'randomize_visual_material' not supported for asset: '{self.entity_cfg.uid}'"
389-
f" with type: '{type(self.entity)}'."
382+
if self.entity_cfg.uid not in env.sim.asset_uids:
383+
self.entity = None
384+
else:
385+
self.entity: Union[RigidObject, Articulation] = env.sim.get_asset(
386+
self.entity_cfg.uid
390387
)
391388

389+
if not isinstance(self.entity, (RigidObject, Articulation)):
390+
raise ValueError(
391+
f"Randomization functor 'randomize_visual_material' not supported for asset: '{self.entity_cfg.uid}'"
392+
f" with type: '{type(self.entity)}'."
393+
)
394+
392395
# TODO: Maybe need to consider two cases:
393396
# 1. the texture folder is very large, and we don't want to load all the textures into memory.
394397
# 2. the texture is generated on the fly.
@@ -483,9 +486,7 @@ def _randomize_mat_inst(
483486
getattr(mat_inst, f"set_{key}")(value[idx].item())
484487

485488
# randomize texture or base color based on the probability.
486-
if random_texture_prob <= 0.0 or len(self.textures) == 0:
487-
return
488-
if random.random() < random_texture_prob:
489+
if random.random() < random_texture_prob and len(self.textures) != 0:
489490
self._randomize_texture(mat_inst)
490491
else:
491492
# set a random base color instead.
@@ -507,6 +508,9 @@ def __call__(
507508
):
508509
from embodichain.lab.sim.utility import is_rt_enabled
509510

511+
if self.entity_cfg.uid != "default_plane" and self.entity is None:
512+
return
513+
510514
# resolve environment ids
511515
if env_ids is None:
512516
env_ids = torch.arange(env.num_envs, device="cpu")

embodichain/lab/sim/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@
1616

1717
from .material import VisualMaterialCfg, VisualMaterial, VisualMaterialInst
1818
from .common import BatchEntity
19+
1920
from .sim_manager import *
21+
22+
from .utility.dynamic_pybind import init_dynamic_pybind
23+
24+
init_dynamic_pybind()

embodichain/lab/sim/cfg.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,12 @@ class ArticulationCfg(ObjectBaseCfg):
953953
build_pk_chain: bool = True
954954
"""Whether to build pytorch-kinematics chain for forward kinematics and jacobian computation."""
955955

956+
compute_uv: bool = False
957+
"""Whether to compute the UV mapping for the articulation link.
958+
959+
Currently, the uv mapping is computed for each link with projection uv mapping method.
960+
"""
961+
956962

957963
@configclass
958964
class RobotCfg(ArticulationCfg):
@@ -1008,7 +1014,7 @@ def from_dict(cls, init_dict: Dict[str, Union[str, float, tuple]]) -> RobotCfg:
10081014
setattr(
10091015
cfg, key, attr.from_dict(value)
10101016
) # Call from_dict on the attribute
1011-
elif "class_type" in value:
1017+
elif isinstance(value, dict) and "class_type" in value:
10121018
setattr(
10131019
cfg,
10141020
key,

embodichain/lab/sim/objects/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,51 @@
2626
from .robot import Robot, RobotCfg
2727
from .light import Light, LightCfg
2828
from .gizmo import Gizmo
29+
30+
31+
from dexsim.engine import RenderBody
32+
import numpy as np
33+
34+
35+
def set_projective_uv(self: RenderBody, proj_direct: np.ndarray = None):
36+
"""Set projective uv mapping to render body.
37+
38+
Args:
39+
proj_direct (np.ndarray, optional). UV project direction. Default to be None, using svd.
40+
"""
41+
import numpy as np
42+
import open3d as o3d
43+
from dexsim.kit.meshproc import get_mesh_auto_uv
44+
45+
n_mesh = self.get_mesh_count()
46+
if n_mesh <= 0:
47+
return
48+
n_vert_list = []
49+
verts = np.empty((0, 3), dtype=np.float32)
50+
faces = np.empty((0, 3), dtype=np.int32)
51+
# gather all vertices
52+
for i in range(n_mesh):
53+
mesh_verts = self.get_vertices(mesh_id=i)
54+
n_vert_list.append(mesh_verts.shape[0])
55+
verts = np.vstack((verts, mesh_verts))
56+
57+
mesh_faces = self.get_triangles(mesh_id=i)
58+
faces = np.vstack((faces, mesh_faces))
59+
if (verts.shape[0] == 0) or (faces.shape[0] == 0):
60+
return
61+
# project uv for all vertices
62+
mesh_o3dt = o3d.t.geometry.TriangleMesh()
63+
mesh_o3dt.vertex.positions = o3d.core.Tensor(verts, dtype=o3d.core.Dtype.Float32)
64+
mesh_o3dt.triangle.indices = o3d.core.Tensor(faces, dtype=o3d.core.Dtype.Int32)
65+
is_success, vert_uvs = get_mesh_auto_uv(mesh_o3dt, proj_direct)
66+
67+
# set uv mapping for each mesh
68+
start_idx = 0
69+
for i in range(n_mesh):
70+
mesh_vert_uvs = vert_uvs[start_idx : start_idx + n_vert_list[i], :]
71+
self.set_uv_mapping(uvs=mesh_vert_uvs, mesh_id=i)
72+
start_idx += n_vert_list[i]
73+
74+
75+
# bind this method to dexsim.engine.RenderBody
76+
RenderBody.set_projective_uv = set_projective_uv
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ----------------------------------------------------------------------------
2+
# Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ----------------------------------------------------------------------------
16+
17+
import dexsim
18+
import numpy as np
19+
20+
from dexsim.engine import RenderBody
21+
22+
23+
def set_projective_uv(self: RenderBody, proj_direct: np.ndarray | None = None) -> None:
24+
"""Set projective uv mapping to render body.
25+
26+
Args:
27+
proj_direct: UV project direction. Default to be None, using svd.
28+
"""
29+
import open3d as o3d
30+
from dexsim.kit.meshproc import get_mesh_auto_uv
31+
32+
n_mesh = self.get_mesh_count()
33+
if n_mesh <= 0:
34+
return
35+
n_vert_list = []
36+
verts = np.empty((0, 3), dtype=np.float32)
37+
faces = np.empty((0, 3), dtype=np.int32)
38+
# gather all vertices
39+
for i in range(n_mesh):
40+
mesh_verts = self.get_vertices(mesh_id=i)
41+
n_vert_list.append(mesh_verts.shape[0])
42+
verts = np.vstack((verts, mesh_verts))
43+
44+
mesh_faces = self.get_triangles(mesh_id=i)
45+
faces = np.vstack((faces, mesh_faces))
46+
if (verts.shape[0] == 0) or (faces.shape[0] == 0):
47+
return
48+
# project uv for all vertices
49+
mesh_o3dt = o3d.t.geometry.TriangleMesh()
50+
mesh_o3dt.vertex.positions = o3d.core.Tensor(verts, dtype=o3d.core.Dtype.Float32)
51+
mesh_o3dt.triangle.indices = o3d.core.Tensor(faces, dtype=o3d.core.Dtype.Int32)
52+
is_success, vert_uvs = get_mesh_auto_uv(mesh_o3dt, proj_direct)
53+
54+
# set uv mapping for each mesh
55+
start_idx = 0
56+
for i in range(n_mesh):
57+
mesh_vert_uvs = vert_uvs[start_idx : start_idx + n_vert_list[i], :]
58+
self.set_uv_mapping(uvs=mesh_vert_uvs, mesh_id=i)
59+
start_idx += n_vert_list[i]
60+
61+
62+
def init_dynamic_pybind() -> None:
63+
"""Initialize dynamic pybind interface."""
64+
65+
RenderBody.set_projective_uv = set_projective_uv

embodichain/lab/sim/utility/sim_utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def get_drive_type(drive_pros):
100100
else:
101101
logger.log_error(f"Unknow drive type {drive_type}")
102102

103-
for art in arts:
103+
for i, art in enumerate(arts):
104104
art.set_body_scale(cfg.body_scale)
105105
art.set_physical_attr(cfg.attrs.attr())
106106
art.set_articulation_flag(ArticulationFlag.FIX_BASE, cfg.fix_base)
@@ -118,6 +118,15 @@ def get_drive_type(drive_pros):
118118
inertia = np.maximum(inertia, 1e-4)
119119
physical_body.set_mass_space_inertia_tensor(inertia)
120120

121+
if i == 0 and cfg.compute_uv:
122+
render_body = art.get_render_body(name)
123+
if render_body:
124+
render_body.set_projective_uv()
125+
126+
# TODO: will crash when exit if not explicitly delete.
127+
# This may due to the destruction of render body order when exiting.
128+
del render_body
129+
121130

122131
def is_rt_enabled() -> bool:
123132
"""Check if Ray Tracing rendering backend is enabled in the default dexsim world.

0 commit comments

Comments
 (0)