fix: update pygfx integration for current API

- Use rendercanvas.qt.RenderCanvas instead of wgpu.gui.qt.WgpuCanvas
- Fix camera position API: use camera.local.position instead of camera.position.set()
- Fix light position API: use light.local.position
- Fix PygfxRenderObject to properly inherit from RenderObject
- Change add_mesh/add_wireframe/add_points/add_grid/add_axes to return string ID
- Update base Renderer class to return str instead of RenderObject
- Add custom compute_normals() function for mesh normals
This commit is contained in:
bklronin
2026-03-14 09:06:38 +01:00
parent d7ebbf45d5
commit d52106a48a
2 changed files with 73 additions and 49 deletions
+12 -12
View File
@@ -97,7 +97,7 @@ class Renderer(ABC):
faces: np.ndarray,
color: Tuple[float, float, float] = (0.2, 0.4, 0.8),
name: Optional[str] = None,
) -> RenderObject:
) -> str:
"""
Add a mesh to the scene.
@@ -108,7 +108,7 @@ class Renderer(ABC):
name: Optional name for the object
Returns:
RenderObject representing the mesh
String ID of the mesh
"""
pass
@@ -120,7 +120,7 @@ class Renderer(ABC):
color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
line_width: float = 1.0,
name: Optional[str] = None,
) -> RenderObject:
) -> str:
"""
Add a wireframe to the scene.
@@ -132,7 +132,7 @@ class Renderer(ABC):
name: Optional name for the object
Returns:
RenderObject representing the wireframe
String ID of the wireframe
"""
pass
@@ -143,7 +143,7 @@ class Renderer(ABC):
color: Tuple[float, float, float] = (1.0, 0.0, 0.0),
size: float = 5.0,
name: Optional[str] = None,
) -> RenderObject:
) -> str:
"""
Add points to the scene.
@@ -154,7 +154,7 @@ class Renderer(ABC):
name: Optional name for the object
Returns:
RenderObject representing the points
String ID of the points
"""
pass
@@ -166,7 +166,7 @@ class Renderer(ABC):
color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
line_width: float = 1.0,
name: Optional[str] = None,
) -> RenderObject:
) -> str:
"""
Add line segments to the scene.
@@ -178,7 +178,7 @@ class Renderer(ABC):
name: Optional name for the object
Returns:
RenderObject representing the lines
String ID of the lines
"""
pass
@@ -312,13 +312,13 @@ class Renderer(ABC):
size: float = 100.0,
divisions: int = 10,
color: Tuple[float, float, float] = (0.3, 0.3, 0.3),
) -> RenderObject:
"""Add a reference grid."""
) -> str:
"""Add a reference grid. Returns the grid ID."""
pass
@abstractmethod
def add_axes(self, size: float = 10.0, visible: bool = True) -> RenderObject:
"""Add coordinate axes."""
def add_axes(self, size: float = 10.0, visible: bool = True) -> str:
"""Add coordinate axes. Returns the axes ID."""
pass
@abstractmethod
+61 -37
View File
@@ -55,9 +55,17 @@ class PygfxRenderObject(RenderObject):
geometry: Any = None
material: Any = None
def __post_init__(self) -> None:
if self.scene_node is not None:
self._scene_node = self.scene_node
def __init__(
self,
scene_node: Any = None,
geometry: Any = None,
material: Any = None,
name: Optional[str] = None,
):
super().__init__(name=name)
self.scene_node = scene_node
self.geometry = geometry
self.material = material
class PygfxRenderer(Renderer):
@@ -84,15 +92,15 @@ class PygfxRenderer(Renderer):
"""Initialize pygfx with Qt widget."""
try:
import pygfx as gfx
from wgpu.gui.qt import WgpuCanvas
from rendercanvas.qt import RenderCanvas
from PySide6.QtWidgets import QVBoxLayout
self._canvas = WgpuCanvas(parent=parent_widget)
self._canvas = RenderCanvas(parent=parent_widget)
self._renderer = gfx.renderers.WgpuRenderer(self._canvas)
self._scene = gfx.Scene()
self._camera = gfx.PerspectiveCamera(50, 16 / 9)
self._camera.position.set(100, 100, 100)
self._camera.local.position = (100, 100, 100)
self._controller = gfx.OrbitController(self._camera, register_events=self._renderer)
@@ -110,6 +118,9 @@ class PygfxRenderer(Renderer):
except Exception as e:
print(f"Failed to initialize pygfx: {e}")
import traceback
traceback.print_exc()
return False
def _setup_lighting(self) -> None:
@@ -120,11 +131,11 @@ class PygfxRenderer(Renderer):
self._scene.add(ambient)
directional = gfx.DirectionalLight(intensity=1.0)
directional.position.set(100, 100, 100)
directional.local.position = (100, 100, 100)
self._scene.add(directional)
fill = gfx.DirectionalLight(intensity=0.5)
fill.position.set(-100, 50, 50)
fill.local.position = (-100, 50, 50)
self._scene.add(fill)
def _add_grid(self) -> None:
@@ -151,9 +162,10 @@ class PygfxRenderer(Renderer):
faces: np.ndarray,
color: Tuple[float, float, float] = (0.2, 0.4, 0.8),
name: Optional[str] = None,
) -> PygfxRenderObject:
"""Add a mesh to the scene."""
) -> str:
"""Add a mesh to the scene. Returns the mesh ID."""
import pygfx as gfx
import uuid
vertices = np.asarray(vertices, dtype=np.float32)
faces = np.asarray(faces, dtype=np.int32)
@@ -167,11 +179,12 @@ class PygfxRenderer(Renderer):
mesh = gfx.Mesh(geometry, material)
self._scene.add(mesh)
obj = PygfxRenderObject(name=name, scene_node=mesh, geometry=geometry, material=material)
mesh_id = name or f"mesh_{uuid.uuid4().hex[:8]}"
obj = PygfxRenderObject(name=mesh_id, scene_node=mesh, geometry=geometry, material=material)
obj.color = RenderColor(*color)
self._objects.append(obj)
return obj
return mesh_id
def add_wireframe(
self,
@@ -180,9 +193,10 @@ class PygfxRenderer(Renderer):
color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
line_width: float = 1.0,
name: Optional[str] = None,
) -> PygfxRenderObject:
"""Add a wireframe to the scene."""
) -> str:
"""Add a wireframe to the scene. Returns the wireframe ID."""
import pygfx as gfx
import uuid
positions: List[List[float]] = []
for edge in edges:
@@ -197,11 +211,14 @@ class PygfxRenderer(Renderer):
lines = gfx.Line(geometry, material)
self._scene.add(lines)
obj = PygfxRenderObject(name=name, scene_node=lines, geometry=geometry, material=material)
wireframe_id = name or f"wireframe_{uuid.uuid4().hex[:8]}"
obj = PygfxRenderObject(
name=wireframe_id, scene_node=lines, geometry=geometry, material=material
)
obj.color = RenderColor(*color)
self._objects.append(obj)
return obj
return wireframe_id
def add_points(
self,
@@ -209,9 +226,10 @@ class PygfxRenderer(Renderer):
color: Tuple[float, float, float] = (1.0, 0.0, 0.0),
size: float = 5.0,
name: Optional[str] = None,
) -> PygfxRenderObject:
"""Add points to the scene."""
) -> str:
"""Add points to the scene. Returns the points ID."""
import pygfx as gfx
import uuid
points_arr = np.asarray(points, dtype=np.float32)
@@ -221,13 +239,14 @@ class PygfxRenderer(Renderer):
points_obj = gfx.Points(geometry, material)
self._scene.add(points_obj)
points_id = name or f"points_{uuid.uuid4().hex[:8]}"
obj = PygfxRenderObject(
name=name, scene_node=points_obj, geometry=geometry, material=material
name=points_id, scene_node=points_obj, geometry=geometry, material=material
)
obj.color = RenderColor(*color)
self._objects.append(obj)
return obj
return points_id
def add_lines(
self,
@@ -236,9 +255,10 @@ class PygfxRenderer(Renderer):
color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
line_width: float = 1.0,
name: Optional[str] = None,
) -> PygfxRenderObject:
"""Add line segments to the scene."""
) -> str:
"""Add line segments to the scene. Returns the lines ID."""
import pygfx as gfx
import uuid
positions = np.hstack([start_points, end_points]).flatten()
positions = positions.reshape(-1, 3).astype(np.float32)
@@ -249,11 +269,14 @@ class PygfxRenderer(Renderer):
lines = gfx.Line(geometry, material)
self._scene.add(lines)
obj = PygfxRenderObject(name=name, scene_node=lines, geometry=geometry, material=material)
lines_id = name or f"lines_{uuid.uuid4().hex[:8]}"
obj = PygfxRenderObject(
name=lines_id, scene_node=lines, geometry=geometry, material=material
)
obj.color = RenderColor(*color)
self._objects.append(obj)
return obj
return lines_id
def remove_object(self, obj: RenderObject) -> bool:
"""Remove an object from the scene."""
@@ -319,15 +342,14 @@ class PygfxRenderer(Renderer):
up: Tuple[float, float, float] = (0, 0, 1),
) -> None:
"""Set camera position and orientation."""
self._camera.position.set(*position)
self._camera.look_at(*target)
self._camera.up.set(*up)
self._camera.local.position = position
self._camera.look_at(target)
def get_camera_position(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Get camera position, target, and up vector."""
pos = np.array(self._camera.position.to_array())
pos = np.array(self._camera.local.position)
target = np.array([0, 0, 0])
up = np.array(self._camera.up.to_array())
up = np.array(self._camera.local.up)
return pos, target, up
def fit_camera(self, padding: float = 1.1) -> None:
@@ -348,8 +370,8 @@ class PygfxRenderer(Renderer):
center = (min_pos + max_pos) / 2
size = np.linalg.norm(max_pos - min_pos) * padding
self._camera.position.set(center[0] + size, center[1] + size, center[2] + size)
self._camera.look_at(*center)
self._camera.local.position = (center[0] + size, center[1] + size, center[2] + size)
self._camera.look_at(tuple(center))
def set_camera_perspective(
self, fov: float = 50.0, near: float = 0.1, far: float = 10000.0
@@ -392,8 +414,8 @@ class PygfxRenderer(Renderer):
size: float = 100.0,
divisions: int = 10,
color: Tuple[float, float, float] = (0.3, 0.3, 0.3),
) -> PygfxRenderObject:
"""Add a reference grid."""
) -> str:
"""Add a reference grid. Returns the grid ID."""
import pygfx as gfx
grid = gfx.GridHelper(
@@ -402,10 +424,11 @@ class PygfxRenderer(Renderer):
self._scene.add(grid)
obj = PygfxRenderObject(name="grid", scene_node=grid)
return obj
self._objects.append(obj)
return "grid"
def add_axes(self, size: float = 10.0, visible: bool = True) -> PygfxRenderObject:
"""Add coordinate axes."""
def add_axes(self, size: float = 10.0, visible: bool = True) -> str:
"""Add coordinate axes. Returns the axes ID."""
import pygfx as gfx
axes = gfx.AxesHelper(size=size)
@@ -413,7 +436,8 @@ class PygfxRenderer(Renderer):
self._scene.add(axes)
obj = PygfxRenderObject(name="axes", scene_node=axes)
return obj
self._objects.append(obj)
return "axes"
def get_screen_size(self) -> Tuple[int, int]:
"""Get the screen size in pixels."""