added mesh surface arrows

This commit is contained in:
ykcheungab
2026-06-19 21:22:28 +08:00
parent 2e5c047d1e
commit 258948bcb7
20 changed files with 951 additions and 194 deletions
+75 -6
View File
@@ -10,6 +10,7 @@ from orisys.finger.mesh import UVSurfaceMesh
from orisys.finger.viewer import flow_magnitude_vertex_colors, scalar_map_vertex_colors
from .contact_arrow import ContactArrowOverlay
from .flow_arrow import FlowArrowOverlay
from .mesh_camera import (
apply_filament_sun_light,
apply_shade_factors,
@@ -64,6 +65,16 @@ def _vertex_colors(mesh, *, color_mode, threshold, value_min, value_max, flow=No
)
def _background_rgba01(color: tuple[int, int, int]) -> list[float]:
r, g, b = (int(color[0]), int(color[1]), int(color[2]))
return [r / 255.0, g / 255.0, b / 255.0, 1.0]
def _background_rgb01(color: tuple[int, int, int]) -> np.ndarray:
return np.asarray(_background_rgba01(color)[:3], dtype=np.float64)
class _FilamentOffscreenBackend:
"""Filament OffscreenRenderer (Linux headless / platforms with EGL)."""
@@ -81,7 +92,9 @@ class _FilamentOffscreenBackend:
lookat: np.ndarray,
show_contact_arrow: bool,
mesh_lighting=None,
mesh_background: tuple[int, int, int] = (0, 0, 0),
contact_arrow_style=None,
flow_arrow_style=None,
):
import open3d as o3d
@@ -96,6 +109,7 @@ class _FilamentOffscreenBackend:
self.world_to_cam = world_to_cam
self.lookat = lookat
self.mesh_lighting = mesh_lighting
self.mesh_background = mesh_background
self._shade_factors = (
precompute_shade_factors(mesh.rest_vertices, mesh.triangles, mesh_lighting)
if mesh_lighting is not None
@@ -106,8 +120,9 @@ class _FilamentOffscreenBackend:
enabled=show_contact_arrow,
style=contact_arrow_style,
)
self._flow_arrow = FlowArrowOverlay(mesh, style=flow_arrow_style)
self._renderer = o3d.visualization.rendering.OffscreenRenderer(self.width, self.height)
self._renderer.scene.set_background([0.05, 0.05, 0.08, 1.0])
self._renderer.scene.set_background(_background_rgba01(self.mesh_background))
if mesh_lighting is not None:
apply_filament_sun_light(
self._renderer,
@@ -117,6 +132,7 @@ class _FilamentOffscreenBackend:
self._material = _make_lit_material(o3d)
self._mesh_added = False
self._arrow_added = False
self._flow_arrow_added = False
self._apply_camera()
def _apply_camera(self) -> None:
@@ -138,7 +154,7 @@ class _FilamentOffscreenBackend:
self.width = width
self.height = height
self._renderer = self._o3d.visualization.rendering.OffscreenRenderer(self.width, self.height)
self._renderer.scene.set_background([0.05, 0.05, 0.08, 1.0])
self._renderer.scene.set_background(_background_rgba01(self.mesh_background))
if self.mesh_lighting is not None:
apply_filament_sun_light(
self._renderer,
@@ -148,9 +164,19 @@ class _FilamentOffscreenBackend:
self._material = _make_lit_material(self._o3d)
self._mesh_added = False
self._arrow_added = False
self._flow_arrow_added = False
self._apply_camera()
def render(self, *, flow=None, scalar_map=None, centroid=None, centroid_found=False, fnormal=0.0):
def render(
self,
*,
flow=None,
scalar_map=None,
centroid=None,
centroid_found=False,
fnormal=0.0,
valid_mask=None,
):
o3d = self._o3d
colors = _vertex_colors(
self.mesh,
@@ -185,6 +211,21 @@ class _FilamentOffscreenBackend:
self._renderer.scene.remove_geometry("contact_arrow")
self._arrow_added = False
flow_result = self._flow_arrow.compute(flow, valid_mask=valid_mask)
if flow_result is not None and self._flow_arrow.arrow_mesh is not None:
flow_vertices, flow_triangles, flow_colors = flow_result
if len(flow_vertices) > 0:
flow_mesh = _mesh_to_o3d(o3d, flow_vertices, flow_triangles, flow_colors)
if not self._flow_arrow_added:
self._renderer.scene.add_geometry("flow_arrows", flow_mesh, self._material)
self._flow_arrow_added = True
else:
self._renderer.scene.remove_geometry("flow_arrows")
self._renderer.scene.add_geometry("flow_arrows", flow_mesh, self._material)
elif self._flow_arrow_added:
self._renderer.scene.remove_geometry("flow_arrows")
self._flow_arrow_added = False
return _numpy_o3d_image_to_rgb(self._renderer.render_to_image())
def close(self) -> None:
@@ -208,7 +249,9 @@ class _HiddenVisualizerBackend:
lookat: np.ndarray,
show_contact_arrow: bool,
mesh_lighting=None,
mesh_background: tuple[int, int, int] = (0, 0, 0),
contact_arrow_style=None,
flow_arrow_style=None,
):
import open3d as o3d
@@ -223,6 +266,7 @@ class _HiddenVisualizerBackend:
self.world_to_cam = world_to_cam
self.lookat = lookat
self.mesh_lighting = mesh_lighting
self.mesh_background = mesh_background
self._shade_factors = (
precompute_shade_factors(mesh.rest_vertices, mesh.triangles, mesh_lighting)
if mesh_lighting is not None
@@ -233,6 +277,7 @@ class _HiddenVisualizerBackend:
enabled=show_contact_arrow,
style=contact_arrow_style,
)
self._flow_arrow = FlowArrowOverlay(mesh, style=flow_arrow_style)
self.vis = None
self.tri_mesh = None
self._create_window()
@@ -268,9 +313,12 @@ class _HiddenVisualizerBackend:
if self._contact_arrow.arrow_mesh is not None:
self.vis.add_geometry(self._contact_arrow.arrow_mesh)
if self._flow_arrow.enabled and self._flow_arrow.arrow_mesh is not None:
self.vis.add_geometry(self._flow_arrow.arrow_mesh)
render_option = self.vis.get_render_option()
render_option.mesh_show_back_face = True
render_option.background_color = np.array([0.05, 0.05, 0.08])
render_option.background_color = _background_rgb01(self.mesh_background)
if hasattr(render_option, "light_on"):
render_option.light_on = False
@@ -298,7 +346,16 @@ class _HiddenVisualizerBackend:
self.height = height
self._create_window()
def render(self, *, flow=None, scalar_map=None, centroid=None, centroid_found=False, fnormal=0.0):
def render(
self,
*,
flow=None,
scalar_map=None,
centroid=None,
centroid_found=False,
fnormal=0.0,
valid_mask=None,
):
o3d = self._o3d
colors = _vertex_colors(
self.mesh,
@@ -320,6 +377,12 @@ class _HiddenVisualizerBackend:
self._contact_arrow.apply(arrow_vertices, arrow_colors)
self.vis.update_geometry(self._contact_arrow.arrow_mesh)
flow_result = self._flow_arrow.compute(flow, valid_mask=valid_mask)
if flow_result is not None and self._flow_arrow.arrow_mesh is not None:
flow_vertices, flow_triangles, flow_colors = flow_result
self._flow_arrow.apply(flow_vertices, flow_triangles, flow_colors)
self.vis.update_geometry(self._flow_arrow.arrow_mesh)
self.vis.poll_events()
self.vis.update_renderer()
return _numpy_o3d_image_to_rgb(self.vis.capture_screen_float_buffer(do_render=False))
@@ -340,7 +403,7 @@ def _create_backend(mesh: UVSurfaceMesh, **kwargs):
return _HiddenVisualizerBackend(mesh, **kwargs)
class OffscreenFingerMeshRenderer:
"""Render finger UV mesh + contact arrow to an RGB image for Qt embedding."""
"""Render finger UV mesh + contact/flow arrows to an RGB image for Qt embedding."""
def __init__(
self,
@@ -356,7 +419,9 @@ class OffscreenFingerMeshRenderer:
lookat: np.ndarray | None = None,
show_contact_arrow: bool = True,
mesh_lighting=None,
mesh_background: tuple[int, int, int] = (0, 0, 0),
contact_arrow_style=None,
flow_arrow_style=None,
):
lookat = lookat if lookat is not None else mesh.rest_vertices.mean(axis=0)
self._backend = _create_backend(
@@ -371,7 +436,9 @@ class OffscreenFingerMeshRenderer:
lookat=lookat,
show_contact_arrow=show_contact_arrow,
mesh_lighting=mesh_lighting,
mesh_background=mesh_background,
contact_arrow_style=contact_arrow_style,
flow_arrow_style=flow_arrow_style,
)
@property
@@ -393,6 +460,7 @@ class OffscreenFingerMeshRenderer:
centroid=None,
centroid_found: bool = False,
fnormal: float = 0.0,
valid_mask=None,
) -> np.ndarray:
return self._backend.render(
flow=flow,
@@ -400,6 +468,7 @@ class OffscreenFingerMeshRenderer:
centroid=centroid,
centroid_found=centroid_found,
fnormal=fnormal,
valid_mask=valid_mask,
)
def close(self) -> None: