282 lines
8.9 KiB
Python
282 lines
8.9 KiB
Python
"""
|
|
widget.py
|
|
-------------
|
|
|
|
A widget which can visualize trimesh.Scene objects in a glooey window.
|
|
|
|
Check out an example in `examples/widget.py`
|
|
"""
|
|
import glooey
|
|
import numpy as np
|
|
import pyglet
|
|
from pyglet import gl
|
|
|
|
from .. import rendering
|
|
from .trackball import Trackball
|
|
from .windowed import geometry_hash
|
|
from .windowed import SceneViewer
|
|
|
|
|
|
class SceneGroup(pyglet.graphics.Group):
|
|
|
|
def __init__(
|
|
self,
|
|
rect,
|
|
scene,
|
|
background=None,
|
|
pixel_per_point=(1, 1),
|
|
parent=None,
|
|
):
|
|
super().__init__(parent)
|
|
self.rect = rect
|
|
self.scene = scene
|
|
|
|
if background is None:
|
|
background = [.99, .99, .99, 1.0]
|
|
self._background = background
|
|
|
|
self._pixel_per_point = pixel_per_point
|
|
|
|
def _set_view(self):
|
|
left = int(self._pixel_per_point[0] * self.rect.left)
|
|
bottom = int(self._pixel_per_point[1] * self.rect.bottom)
|
|
width = int(self._pixel_per_point[0] * self.rect.width)
|
|
height = int(self._pixel_per_point[1] * self.rect.height)
|
|
|
|
gl.glPushAttrib(gl.GL_ENABLE_BIT)
|
|
gl.glEnable(gl.GL_SCISSOR_TEST)
|
|
gl.glScissor(left, bottom, width, height)
|
|
|
|
self._mode = (gl.GLint)()
|
|
gl.glGetIntegerv(gl.GL_MATRIX_MODE, self._mode)
|
|
self._viewport = (gl.GLint * 4)()
|
|
gl.glGetIntegerv(gl.GL_VIEWPORT, self._viewport)
|
|
|
|
gl.glViewport(left, bottom, width, height)
|
|
gl.glMatrixMode(gl.GL_PROJECTION)
|
|
gl.glPushMatrix()
|
|
gl.glLoadIdentity()
|
|
near = 0.01
|
|
far = 1000.
|
|
gl.gluPerspective(self.scene.camera.fov[1], width / height, near, far)
|
|
gl.glMatrixMode(gl.GL_MODELVIEW)
|
|
|
|
def _unset_view(self):
|
|
gl.glMatrixMode(gl.GL_PROJECTION)
|
|
gl.glPopMatrix()
|
|
gl.glMatrixMode(self._mode.value)
|
|
gl.glViewport(
|
|
self._viewport[0],
|
|
self._viewport[1],
|
|
self._viewport[2],
|
|
self._viewport[3],
|
|
)
|
|
|
|
gl.glPopAttrib()
|
|
|
|
def set_state(self):
|
|
self._set_view()
|
|
|
|
SceneViewer._gl_set_background(self._background)
|
|
SceneViewer._gl_enable_depth(self.scene.camera)
|
|
SceneViewer._gl_enable_color_material()
|
|
SceneViewer._gl_enable_blending()
|
|
SceneViewer._gl_enable_smooth_lines()
|
|
SceneViewer._gl_enable_lighting(self.scene)
|
|
|
|
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
|
|
|
|
gl.glPushMatrix()
|
|
gl.glLoadIdentity()
|
|
gl.glMultMatrixf(
|
|
rendering.matrix_to_gl(np.linalg.inv(self.scene.camera_transform)))
|
|
|
|
def unset_state(self):
|
|
gl.glPopMatrix()
|
|
|
|
SceneViewer._gl_unset_background()
|
|
|
|
self._unset_view()
|
|
|
|
|
|
class MeshGroup(pyglet.graphics.Group):
|
|
|
|
def __init__(self, transform=None, texture=None, parent=None):
|
|
super().__init__(parent)
|
|
if transform is None:
|
|
transform = np.eye(4)
|
|
self.transform = transform
|
|
self.texture = texture
|
|
|
|
def set_state(self):
|
|
gl.glPushMatrix()
|
|
gl.glMultMatrixf(rendering.matrix_to_gl(self.transform))
|
|
|
|
if self.texture:
|
|
gl.glEnable(self.texture.target)
|
|
gl.glBindTexture(self.texture.target, self.texture.id)
|
|
|
|
def unset_state(self):
|
|
if self.texture:
|
|
gl.glDisable(self.texture.target)
|
|
|
|
gl.glPopMatrix()
|
|
|
|
|
|
class SceneWidget(glooey.Widget):
|
|
|
|
def __init__(self, scene, **kwargs):
|
|
super().__init__()
|
|
self.scene = scene
|
|
self._scene_group = None
|
|
|
|
# key is node_name
|
|
self.mesh_group = {}
|
|
|
|
# key is geometry_name
|
|
self.vertex_list = {}
|
|
self.vertex_list_hash = {}
|
|
self.textures = {}
|
|
|
|
self._initial_camera_transform = self.scene.camera_transform.copy()
|
|
self.reset_view()
|
|
|
|
self._background = kwargs.pop('background', None)
|
|
if kwargs:
|
|
raise TypeError('unexpected kwargs: {}'.format(kwargs))
|
|
|
|
@property
|
|
def scene_group(self):
|
|
if self._scene_group is None:
|
|
pixel_per_point = (np.array(self.window.get_viewport_size()) /
|
|
np.array(self.window.get_size()))
|
|
self._scene_group = SceneGroup(
|
|
rect=self.rect,
|
|
scene=self.scene,
|
|
background=self._background,
|
|
pixel_per_point=pixel_per_point,
|
|
parent=self.group,
|
|
)
|
|
return self._scene_group
|
|
|
|
def clear(self):
|
|
self._scene_group = None
|
|
self.mesh_group = {}
|
|
while self.vertex_list:
|
|
_, vertex = self.vertex_list.popitem()
|
|
vertex.delete()
|
|
self.vertex_list_hash = {}
|
|
self.textures = {}
|
|
|
|
def reset_view(self):
|
|
self.view = {
|
|
'ball': Trackball(
|
|
pose=self._initial_camera_transform,
|
|
size=self.scene.camera.resolution,
|
|
scale=self.scene.scale,
|
|
target=self.scene.centroid)}
|
|
self.scene.camera_transform[...] = self.view['ball'].pose
|
|
|
|
def do_claim(self):
|
|
return 0, 0
|
|
|
|
def do_regroup(self):
|
|
if not self.vertex_list:
|
|
return
|
|
|
|
node_names = self.scene.graph.nodes_geometry
|
|
for node_name in node_names:
|
|
transform, geometry_name = self.scene.graph[node_name]
|
|
if geometry_name not in self.vertex_list:
|
|
continue
|
|
vertex_list = self.vertex_list[geometry_name]
|
|
|
|
if node_name in self.mesh_group:
|
|
mesh_group = self.mesh_group[node_name]
|
|
else:
|
|
mesh_group = MeshGroup(
|
|
transform=transform,
|
|
texture=self.textures.get(geometry_name),
|
|
parent=self.scene_group)
|
|
self.mesh_group[node_name] = mesh_group
|
|
self.batch.migrate(
|
|
vertex_list,
|
|
gl.GL_TRIANGLES,
|
|
mesh_group,
|
|
self.batch)
|
|
|
|
def do_draw(self):
|
|
resolution = (self.rect.width, self.rect.height)
|
|
if not (resolution == self.scene.camera.resolution).all():
|
|
self.scene.camera.resolution = resolution
|
|
|
|
node_names = self.scene.graph.nodes_geometry
|
|
for node_name in node_names:
|
|
transform, geometry_name = self.scene.graph[node_name]
|
|
geometry = self.scene.geometry[geometry_name]
|
|
self._update_node(node_name, geometry_name, geometry, transform)
|
|
|
|
def do_undraw(self):
|
|
if not self.vertex_list:
|
|
return
|
|
for vertex_list in self.vertex_list.values():
|
|
vertex_list.delete()
|
|
self._scene_group = None
|
|
self.mesh_group = {}
|
|
self.vertex_list = {}
|
|
self.vertex_list_hash = {}
|
|
self.textures = {}
|
|
|
|
def on_mouse_press(self, x, y, buttons, modifiers):
|
|
SceneViewer.on_mouse_press(self, x, y, buttons, modifiers)
|
|
self._draw()
|
|
|
|
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
|
|
# detect a drag across widgets
|
|
x_prev = x - dx
|
|
y_prev = y - dy
|
|
left, bottom = self.rect.left, self.rect.bottom
|
|
width, height = self.rect.width, self.rect.height
|
|
if not (left < x_prev <= left + width) or \
|
|
not (bottom < y_prev <= bottom + height):
|
|
self.view['ball'].down(np.array([x, y]))
|
|
|
|
SceneViewer.on_mouse_drag(self, x, y, dx, dy, buttons, modifiers)
|
|
self._draw()
|
|
|
|
def on_mouse_scroll(self, x, y, dx, dy):
|
|
SceneViewer.on_mouse_scroll(self, x, y, dx, dy)
|
|
self._draw()
|
|
|
|
def _update_node(self, node_name, geometry_name, geometry, transform):
|
|
geometry_hash_new = geometry_hash(geometry)
|
|
if self.vertex_list_hash.get(geometry_name) != geometry_hash_new:
|
|
# if geometry has texture defined convert it to opengl form
|
|
if hasattr(geometry, 'visual') and hasattr(
|
|
geometry.visual, 'material'):
|
|
tex = rendering.material_to_texture(geometry.visual.material)
|
|
if tex is not None:
|
|
self.textures[geometry_name] = tex
|
|
|
|
if node_name in self.mesh_group:
|
|
mesh_group = self.mesh_group[node_name]
|
|
mesh_group.transform = transform
|
|
mesh_group.texture = self.textures.get(geometry_name)
|
|
else:
|
|
mesh_group = MeshGroup(
|
|
transform=transform,
|
|
texture=self.textures.get(geometry_name),
|
|
parent=self.scene_group)
|
|
self.mesh_group[node_name] = mesh_group
|
|
|
|
if self.vertex_list_hash.get(geometry_name) != geometry_hash_new:
|
|
if geometry_name in self.vertex_list:
|
|
self.vertex_list[geometry_name].delete()
|
|
|
|
# convert geometry to constructor args
|
|
args = rendering.convert_to_vertexlist(geometry, group=mesh_group)
|
|
# create the indexed vertex list
|
|
self.vertex_list[geometry_name] = self.batch.add_indexed(*args)
|
|
# save the MD5 of the geometry
|
|
self.vertex_list_hash[geometry_name] = geometry_hash_new
|