summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/Plot3DWidget.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/Plot3DWidget.py')
-rw-r--r--silx/gui/plot3d/Plot3DWidget.py460
1 files changed, 0 insertions, 460 deletions
diff --git a/silx/gui/plot3d/Plot3DWidget.py b/silx/gui/plot3d/Plot3DWidget.py
deleted file mode 100644
index f512cd8..0000000
--- a/silx/gui/plot3d/Plot3DWidget.py
+++ /dev/null
@@ -1,460 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides a Qt widget embedding an OpenGL scene."""
-
-from __future__ import absolute_import
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "24/04/2018"
-
-
-import enum
-import logging
-
-from silx.gui import qt
-from silx.gui.colors import rgba
-from . import actions
-
-from ...utils.enum import Enum as _Enum
-from ..utils.image import convertArrayToQImage
-
-from .. import _glutils as glu
-from .scene import interaction, primitives, transform
-from . import scene
-
-import numpy
-
-
-_logger = logging.getLogger(__name__)
-
-
-class _OverviewViewport(scene.Viewport):
- """A scene displaying the orientation of the data in another scene.
-
- :param Camera camera: The camera to track.
- """
-
- _SIZE = 100
- """Size in pixels of the overview square"""
-
- def __init__(self, camera=None):
- super(_OverviewViewport, self).__init__()
- self.size = self._SIZE, self._SIZE
- self.background = None # Disable clear
-
- self.scene.transforms = [transform.Scale(2.5, 2.5, 2.5)]
-
- # Add a point to draw the background (in a group with depth mask)
- backgroundPoint = primitives.ColorPoints(
- x=0., y=0., z=0.,
- color=(1., 1., 1., 0.5),
- size=self._SIZE)
- backgroundPoint.marker = 'o'
- noDepthGroup = primitives.GroupNoDepth(mask=True, notest=True)
- noDepthGroup.children.append(backgroundPoint)
- self.scene.children.append(noDepthGroup)
-
- axes = primitives.Axes()
- self.scene.children.append(axes)
-
- if camera is not None:
- camera.addListener(self._cameraChanged)
-
- def _cameraChanged(self, source):
- """Listen to camera in other scene for transformation updates.
-
- Sync the overview camera to point in the same direction
- but from a sphere centered on origin.
- """
- position = -12. * source.extrinsic.direction
- self.camera.extrinsic.position = position
-
- self.camera.extrinsic.setOrientation(
- source.extrinsic.direction, source.extrinsic.up)
-
-
-class Plot3DWidget(glu.OpenGLWidget):
- """OpenGL widget with a 3D viewport and an overview."""
-
- sigInteractiveModeChanged = qt.Signal()
- """Signal emitted when the interactive mode has changed
- """
-
- sigStyleChanged = qt.Signal(str)
- """Signal emitted when the style of the scene has changed
-
- It provides the updated property.
- """
-
- sigSceneClicked = qt.Signal(float, float)
- """Signal emitted when the scene is clicked with the left mouse button.
-
- It provides the (x, y) clicked mouse position
- """
-
- @enum.unique
- class FogMode(_Enum):
- """Different mode to render the scene with fog"""
-
- NONE = 'none'
- """No fog effect"""
-
- LINEAR = 'linear'
- """Linear fog through the whole scene"""
-
- def __init__(self, parent=None, f=qt.Qt.WindowFlags()):
- self._firstRender = True
-
- super(Plot3DWidget, self).__init__(
- parent,
- alphaBufferSize=8,
- depthBufferSize=0,
- stencilBufferSize=0,
- version=(2, 1),
- f=f)
-
- self.setAutoFillBackground(False)
- self.setMouseTracking(True)
-
- self.setFocusPolicy(qt.Qt.StrongFocus)
- self._copyAction = actions.io.CopyAction(parent=self, plot3d=self)
- self.addAction(self._copyAction)
-
- self._updating = False # True if an update is requested
-
- # Main viewport
- self.viewport = scene.Viewport()
-
- self._sceneScale = transform.Scale(1., 1., 1.)
- self.viewport.scene.transforms = [self._sceneScale,
- transform.Translate(0., 0., 0.)]
-
- # Overview area
- self.overview = _OverviewViewport(self.viewport.camera)
-
- self.setBackgroundColor((0.2, 0.2, 0.2, 1.))
-
- # Window describing on screen area to render
- self._window = scene.Window(mode='framebuffer')
- self._window.viewports = [self.viewport, self.overview]
- self._window.addListener(self._redraw)
-
- self.eventHandler = None
- self.setInteractiveMode('rotate')
-
- def __clickHandler(self, *args):
- """Handle interaction state machine click"""
- x, y = args[0][:2]
- self.sigSceneClicked.emit(x, y)
-
- def setInteractiveMode(self, mode):
- """Set the interactive mode.
-
- :param str mode: The interactive mode: 'rotate', 'pan' or None
- """
- if mode == self.getInteractiveMode():
- return
-
- if mode is None:
- self.eventHandler = None
-
- elif mode == 'rotate':
- self.eventHandler = interaction.RotateCameraControl(
- self.viewport,
- orbitAroundCenter=False,
- mode='position',
- scaleTransform=self._sceneScale,
- selectCB=self.__clickHandler)
-
- elif mode == 'pan':
- self.eventHandler = interaction.PanCameraControl(
- self.viewport,
- orbitAroundCenter=False,
- mode='position',
- scaleTransform=self._sceneScale,
- selectCB=self.__clickHandler)
-
- elif isinstance(mode, interaction.StateMachine):
- self.eventHandler = mode
-
- else:
- raise ValueError('Unsupported interactive mode %s', str(mode))
-
- if (self.eventHandler is not None and
- qt.QApplication.keyboardModifiers() & qt.Qt.ControlModifier):
- self.eventHandler.handleEvent('keyPress', qt.Qt.Key_Control)
-
- self.sigInteractiveModeChanged.emit()
-
- def getInteractiveMode(self):
- """Returns the interactive mode in use.
-
- :rtype: str
- """
- if self.eventHandler is None:
- return None
- if isinstance(self.eventHandler, interaction.RotateCameraControl):
- return 'rotate'
- elif isinstance(self.eventHandler, interaction.PanCameraControl):
- return 'pan'
- else:
- return None
-
- def setProjection(self, projection):
- """Change the projection in use.
-
- :param str projection: In 'perspective', 'orthographic'.
- """
- if projection == 'orthographic':
- projection = transform.Orthographic(size=self.viewport.size)
- elif projection == 'perspective':
- projection = transform.Perspective(fovy=30.,
- size=self.viewport.size)
- else:
- raise RuntimeError('Unsupported projection: %s' % projection)
-
- self.viewport.camera.intrinsic = projection
- self.viewport.resetCamera()
-
- def getProjection(self):
- """Return the current camera projection mode as a str.
-
- See :meth:`setProjection`
- """
- projection = self.viewport.camera.intrinsic
- if isinstance(projection, transform.Orthographic):
- return 'orthographic'
- elif isinstance(projection, transform.Perspective):
- return 'perspective'
- else:
- raise RuntimeError('Unknown projection in use')
-
- def setBackgroundColor(self, color):
- """Set the background color of the OpenGL view.
-
- :param color: RGB color of the isosurface: name, #RRGGBB or RGB values
- :type color:
- QColor, str or array-like of 3 or 4 float in [0., 1.] or uint8
- """
- color = rgba(color)
- if color != self.viewport.background:
- self.viewport.background = color
- self.sigStyleChanged.emit('backgroundColor')
-
- def getBackgroundColor(self):
- """Returns the RGBA background color (QColor)."""
- return qt.QColor.fromRgbF(*self.viewport.background)
-
- def setFogMode(self, mode):
- """Set the kind of fog to use for the whole scene.
-
- :param Union[str,FogMode] mode: The mode to use
- :raise ValueError: If mode is not supported
- """
- mode = self.FogMode.from_value(mode)
- if mode != self.getFogMode():
- self.viewport.fog.isOn = mode is self.FogMode.LINEAR
- self.sigStyleChanged.emit('fogMode')
-
- def getFogMode(self):
- """Returns the kind of fog in use
-
- :return: The kind of fog in use
- :rtype: FogMode
- """
- if self.viewport.fog.isOn:
- return self.FogMode.LINEAR
- else:
- return self.FogMode.NONE
-
- def isOrientationIndicatorVisible(self):
- """Returns True if the orientation indicator is displayed.
-
- :rtype: bool
- """
- return self.overview in self._window.viewports
-
- def setOrientationIndicatorVisible(self, visible):
- """Set the orientation indicator visibility.
-
- :param bool visible: True to show
- """
- visible = bool(visible)
- if visible != self.isOrientationIndicatorVisible():
- if visible:
- self._window.viewports = [self.viewport, self.overview]
- else:
- self._window.viewports = [self.viewport]
- self.sigStyleChanged.emit('orientationIndicatorVisible')
-
- def centerScene(self):
- """Position the center of the scene at the center of rotation."""
- self.viewport.resetCamera()
-
- def resetZoom(self, face='front'):
- """Reset the camera position to a default.
-
- :param str face: The direction the camera is looking at:
- side, front, back, top, bottom, right, left.
- Default: front.
- """
- self.viewport.camera.extrinsic.reset(face=face)
- self.centerScene()
-
- def _redraw(self, source=None):
- """Viewport listener to require repaint"""
- if not self._updating:
- self._updating = True # Mark that an update is requested
- self.update() # Queued repaint (i.e., asynchronous)
-
- def sizeHint(self):
- return qt.QSize(400, 300)
-
- def initializeGL(self):
- pass
-
- def paintGL(self):
- # In case paintGL is called by the system and not through _redraw,
- # Mark as updating.
- self._updating = True
-
- # Update near and far planes only if viewport needs refresh
- if self.viewport.dirty:
- self.viewport.adjustCameraDepthExtent()
-
- self._window.render(self.context(), self.getDevicePixelRatio())
-
- if self._firstRender: # TODO remove this ugly hack
- self._firstRender = False
- self.centerScene()
- self._updating = False
-
- def resizeGL(self, width, height):
- width *= self.getDevicePixelRatio()
- height *= self.getDevicePixelRatio()
- self._window.size = width, height
- self.viewport.size = self._window.size
- overviewWidth, overviewHeight = self.overview.size
- self.overview.origin = width - overviewWidth, height - overviewHeight
-
- def grabGL(self):
- """Renders the OpenGL scene into a numpy array
-
- :returns: OpenGL scene RGB rasterization
- :rtype: QImage
- """
- if not self.isValid():
- _logger.error('OpenGL 2.1 not available, cannot save OpenGL image')
- height, width = self._window.shape
- image = numpy.zeros((height, width, 3), dtype=numpy.uint8)
-
- else:
- self.makeCurrent()
- image = self._window.grab(self.context())
-
- return convertArrayToQImage(image)
-
- def wheelEvent(self, event):
- xpixel = event.x() * self.getDevicePixelRatio()
- ypixel = event.y() * self.getDevicePixelRatio()
- if hasattr(event, 'delta'): # Qt4
- angle = event.delta() / 8.
- else: # Qt5
- angle = event.angleDelta().y() / 8.
- event.accept()
-
- if self.eventHandler is not None and angle != 0 and self.isValid():
- self.makeCurrent()
- self.eventHandler.handleEvent('wheel', xpixel, ypixel, angle)
-
- def keyPressEvent(self, event):
- keyCode = event.key()
- # No need to accept QKeyEvent
-
- converter = {
- qt.Qt.Key_Left: 'left',
- qt.Qt.Key_Right: 'right',
- qt.Qt.Key_Up: 'up',
- qt.Qt.Key_Down: 'down'
- }
- direction = converter.get(keyCode, None)
- if direction is not None:
- if event.modifiers() == qt.Qt.ControlModifier:
- self.viewport.camera.rotate(direction)
- elif event.modifiers() == qt.Qt.ShiftModifier:
- self.viewport.moveCamera(direction)
- else:
- self.viewport.orbitCamera(direction)
-
- else:
- if (keyCode == qt.Qt.Key_Control and
- self.eventHandler is not None and
- self.isValid()):
- self.eventHandler.handleEvent('keyPress', keyCode)
-
- # Key not handled, call base class implementation
- super(Plot3DWidget, self).keyPressEvent(event)
-
- def keyReleaseEvent(self, event):
- """Catch Ctrl key release"""
- keyCode = event.key()
- if (keyCode == qt.Qt.Key_Control and
- self.eventHandler is not None and
- self.isValid()):
- self.eventHandler.handleEvent('keyRelease', keyCode)
- super(Plot3DWidget, self).keyReleaseEvent(event)
-
- # Mouse events #
- _MOUSE_BTNS = {1: 'left', 2: 'right', 4: 'middle'}
-
- def mousePressEvent(self, event):
- xpixel = event.x() * self.getDevicePixelRatio()
- ypixel = event.y() * self.getDevicePixelRatio()
- btn = self._MOUSE_BTNS[event.button()]
- event.accept()
-
- if self.eventHandler is not None and self.isValid():
- self.makeCurrent()
- self.eventHandler.handleEvent('press', xpixel, ypixel, btn)
-
- def mouseMoveEvent(self, event):
- xpixel = event.x() * self.getDevicePixelRatio()
- ypixel = event.y() * self.getDevicePixelRatio()
- event.accept()
-
- if self.eventHandler is not None and self.isValid():
- self.makeCurrent()
- self.eventHandler.handleEvent('move', xpixel, ypixel)
-
- def mouseReleaseEvent(self, event):
- xpixel = event.x() * self.getDevicePixelRatio()
- ypixel = event.y() * self.getDevicePixelRatio()
- btn = self._MOUSE_BTNS[event.button()]
- event.accept()
-
- if self.eventHandler is not None and self.isValid():
- self.makeCurrent()
- self.eventHandler.handleEvent('release', xpixel, ypixel, btn)