summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/SFViewParamTree.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/SFViewParamTree.py')
-rw-r--r--silx/gui/plot3d/SFViewParamTree.py270
1 files changed, 261 insertions, 9 deletions
diff --git a/silx/gui/plot3d/SFViewParamTree.py b/silx/gui/plot3d/SFViewParamTree.py
index e67c17e..314e5a1 100644
--- a/silx/gui/plot3d/SFViewParamTree.py
+++ b/silx/gui/plot3d/SFViewParamTree.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2015-2018 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
@@ -102,7 +102,7 @@ class SubjectItem(qt.QStandardItem):
Overloaded method from QStandardItem. The pushData keyword tells
the item to push data to the subject if the role is equal to EditRole.
This is useful to let this method know if the setData method was called
- internaly or from the view.
+ internally or from the view.
:param value: the value ti set to data
:param role: role in the item
@@ -355,6 +355,183 @@ class HighlightColorItem(ColorItem):
return self.subject.getHighlightColor()
+class _LightDirectionAngleBaseItem(SubjectItem):
+ """Base class for directional light angle item."""
+ editable = True
+ persistent = True
+
+ def _init(self):
+ pass
+
+ def getSignals(self):
+ """Override to provide signals to listen"""
+ raise NotImplementedError("MUST be implemented in subclass")
+
+ def _pullData(self):
+ """Override in subclass to get current angle"""
+ raise NotImplementedError("MUST be implemented in subclass")
+
+ def _pushData(self, value, role=qt.Qt.UserRole):
+ """Override in subclass to set the angle"""
+ raise NotImplementedError("MUST be implemented in subclass")
+
+ def getEditor(self, parent, option, index):
+ editor = qt.QSlider(parent)
+ editor.setOrientation(qt.Qt.Horizontal)
+ editor.setMinimum(-90)
+ editor.setMaximum(90)
+ editor.setValue(self._pullData())
+
+ # Wrapping call in lambda is a workaround for PySide with Python 3
+ editor.valueChanged.connect(
+ lambda value: self._pushData(value))
+
+ return editor
+
+ def setEditorData(self, editor):
+ editor.setValue(self._pullData())
+ return True
+
+ def _setModelData(self, editor):
+ value = editor.value()
+ self._pushData(value)
+ return True
+
+
+class LightAzimuthAngleItem(_LightDirectionAngleBaseItem):
+ """Light direction azimuth angle item."""
+
+ def getSignals(self):
+ return self.subject.sigAzimuthAngleChanged
+
+ def _pullData(self):
+ return self.subject.getAzimuthAngle()
+
+ def _pushData(self, value, role=qt.Qt.UserRole):
+ self.subject.setAzimuthAngle(value)
+
+
+class LightAltitudeAngleItem(_LightDirectionAngleBaseItem):
+ """Light direction altitude angle item."""
+
+ def getSignals(self):
+ return self.subject.sigAltitudeAngleChanged
+
+ def _pullData(self):
+ return self.subject.getAltitudeAngle()
+
+ def _pushData(self, value, role=qt.Qt.UserRole):
+ self.subject.setAltitudeAngle(value)
+
+
+class _DirectionalLightProxy(qt.QObject):
+ """Proxy to handle directional light with angles rather than vector.
+ """
+
+ sigAzimuthAngleChanged = qt.Signal()
+ """Signal sent when the azimuth angle has changed."""
+
+ sigAltitudeAngleChanged = qt.Signal()
+ """Signal sent when altitude angle has changed."""
+
+ def __init__(self, light):
+ super(_DirectionalLightProxy, self).__init__()
+ self._light = light
+ light.addListener(self._directionUpdated)
+ self._azimuth = 0.
+ self._altitude = 0.
+
+ def getAzimuthAngle(self):
+ """Returns the signed angle in the horizontal plane.
+
+ Unit: degrees.
+ The 0 angle corresponds to the axis perpendicular to the screen.
+
+ :rtype: float
+ """
+ return self._azimuth
+
+ def getAltitudeAngle(self):
+ """Returns the signed vertical angle from the horizontal plane.
+
+ Unit: degrees.
+ Range: [-90, +90]
+
+ :rtype: float
+ """
+ return self._altitude
+
+ def setAzimuthAngle(self, angle):
+ """Set the horizontal angle.
+
+ :param float angle: Angle from -z axis in zx plane in degrees.
+ """
+ if angle != self._azimuth:
+ self._azimuth = angle
+ self._updateLight()
+ self.sigAzimuthAngleChanged.emit()
+
+ def setAltitudeAngle(self, angle):
+ """Set the horizontal angle.
+
+ :param float angle: Angle from -z axis in zy plane in degrees.
+ """
+ if angle != self._altitude:
+ self._altitude = angle
+ self._updateLight()
+ self.sigAltitudeAngleChanged.emit()
+
+ def _directionUpdated(self, *args, **kwargs):
+ """Handle light direction update in the scene"""
+ # Invert direction to manipulate the 'source' pointing to
+ # the center of the viewport
+ x, y, z = - self._light.direction
+
+ # Horizontal plane is plane xz
+ azimuth = numpy.degrees(numpy.arctan2(x, z))
+ altitude = numpy.degrees(numpy.pi/2. - numpy.arccos(y))
+
+ if (abs(azimuth - self.getAzimuthAngle()) > 0.01 and
+ abs(abs(altitude) - 90.) >= 0.001): # Do not update when at zenith
+ self.setAzimuthAngle(azimuth)
+
+ if abs(altitude - self.getAltitudeAngle()) > 0.01:
+ self.setAltitudeAngle(altitude)
+
+ def _updateLight(self):
+ """Update light direction in the scene"""
+ azimuth = numpy.radians(self._azimuth)
+ delta = numpy.pi/2. - numpy.radians(self._altitude)
+ z = - numpy.sin(delta) * numpy.cos(azimuth)
+ x = - numpy.sin(delta) * numpy.sin(azimuth)
+ y = - numpy.cos(delta)
+ self._light.direction = x, y, z
+
+
+class DirectionalLightGroup(SubjectItem):
+ """
+ Root Item for the directional light
+ """
+
+ def __init__(self,subject, *args):
+ self._light = _DirectionalLightProxy(
+ subject.getPlot3DWidget().viewport.light)
+
+ super(DirectionalLightGroup, self).__init__(subject, *args)
+
+ def _init(self):
+
+ nameItem = qt.QStandardItem('Azimuth')
+ nameItem.setEditable(False)
+ valueItem = LightAzimuthAngleItem(self._light)
+ self.appendRow([nameItem, valueItem])
+
+ nameItem = qt.QStandardItem('Altitude')
+ nameItem.setEditable(False)
+ valueItem = LightAltitudeAngleItem(self._light)
+ self.appendRow([nameItem, valueItem])
+
+
class BoundingBoxItem(SubjectItem):
"""Bounding box, axes labels and grid visibility item.
@@ -402,14 +579,20 @@ class ViewSettingsItem(qt.QStandardItem):
self.setEditable(False)
- classes = (BackgroundColorItem, ForegroundColorItem,
+ classes = (BackgroundColorItem,
+ ForegroundColorItem,
HighlightColorItem,
- BoundingBoxItem, OrientationIndicatorItem)
+ BoundingBoxItem,
+ OrientationIndicatorItem)
for cls in classes:
titleItem = qt.QStandardItem(cls.itemName)
titleItem.setEditable(False)
self.appendRow([titleItem, cls(subject)])
+ nameItem = DirectionalLightGroup(subject, 'Light Direction')
+ valueItem = qt.QStandardItem()
+ self.appendRow([nameItem, valueItem])
+
# Data information ############################################################
@@ -421,7 +604,7 @@ class DataChangedItem(SubjectItem):
def getSignals(self):
subject = self.subject
if subject:
- return subject.sigDataChanged
+ return subject.sigDataChanged, subject.sigTransformChanged
return None
def _init(self):
@@ -463,6 +646,17 @@ class ScaleItem(DataChangedItem):
return ((scale is not None) and str(scale)) or 'N/A'
+class MatrixItem(DataChangedItem):
+
+ def __init__(self, subject, row, *args):
+ self.__row = row
+ super(MatrixItem, self).__init__(subject, *args)
+
+ def _pullData(self):
+ matrix = self.subject.getTransformMatrix()
+ return str(matrix[self.__row])
+
+
class DataSetItem(qt.QStandardItem):
def __init__(self, subject, *args):
@@ -471,12 +665,27 @@ class DataSetItem(qt.QStandardItem):
self.setEditable(False)
- klasses = [DataTypeItem, DataShapeItem, OffsetItem, ScaleItem]
+ klasses = [DataTypeItem, DataShapeItem, OffsetItem]
for klass in klasses:
titleItem = qt.QStandardItem(klass.itemName)
titleItem.setEditable(False)
self.appendRow([titleItem, klass(subject)])
+ matrixItem = qt.QStandardItem('matrix')
+ matrixItem.setEditable(False)
+ valueItem = qt.QStandardItem()
+ self.appendRow([matrixItem, valueItem])
+
+ for row in range(3):
+ titleItem = qt.QStandardItem()
+ titleItem.setEditable(False)
+ valueItem = MatrixItem(subject, row)
+ matrixItem.appendRow([titleItem, valueItem])
+
+ titleItem = qt.QStandardItem(ScaleItem.itemName)
+ titleItem.setEditable(False)
+ self.appendRow([titleItem, ScaleItem(subject)])
+
# Isosurface ##################################################################
@@ -559,9 +768,17 @@ class IsoSurfaceLevelItem(SubjectItem):
return [subject.sigLevelChanged,
subject.sigVisibilityChanged]
+ def getEditor(self, parent, option, index):
+ return FloatEdit(parent)
+
def setEditorData(self, editor):
+ editor.setValue(self._pullData())
return False
+ def _setModelData(self, editor):
+ self._pushData(editor.value())
+ return True
+
def _pullData(self):
return self.subject.getLevel()
@@ -1004,9 +1221,10 @@ class PlaneOrientationItem(SubjectItem):
return [self.subject.getCutPlanes()[0].sigPlaneChanged]
def _pullData(self):
- currentNormal = self.subject.getCutPlanes()[0].getNormal()
+ currentNormal = self.subject.getCutPlanes()[0].getNormal(
+ coordinates='scene')
for _, text, _, normal in self._PLANE_ACTIONS:
- if numpy.array_equal(normal, currentNormal):
+ if numpy.allclose(normal, currentNormal):
return text
return ''
@@ -1023,7 +1241,7 @@ class PlaneOrientationItem(SubjectItem):
def __editorChanged(self, index):
normal = self._PLANE_ACTIONS[index][3]
plane = self.subject.getCutPlanes()[0]
- plane.setNormal(normal)
+ plane.setNormal(normal, coordinates='scene')
plane.moveToCenter()
def setEditorData(self, editor):
@@ -1069,6 +1287,35 @@ class PlaneInterpolationItem(SubjectItem):
self.subject.getCutPlanes()[0].setInterpolation(interpolation)
+class PlaneDisplayBelowMinItem(SubjectItem):
+ """Toggle whether to display or not values <= colormap min of the cut plane
+
+ Item is checkable
+ """
+
+ def _init(self):
+ display = self.subject.getCutPlanes()[0].getDisplayValuesBelowMin()
+ self.setCheckable(True)
+ self.setCheckState(
+ qt.Qt.Checked if display else qt.Qt.Unchecked)
+ self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False)
+
+ def getSignals(self):
+ return [self.subject.getCutPlanes()[0].sigTransparencyChanged]
+
+ def leftClicked(self):
+ checked = self.checkState() == qt.Qt.Checked
+ self._setDisplayValuesBelowMin(checked)
+
+ def _pullData(self):
+ display = self.subject.getCutPlanes()[0].getDisplayValuesBelowMin()
+ self._setDisplayValuesBelowMin(display)
+ return "Displayed" if display else "Hidden"
+
+ def _setDisplayValuesBelowMin(self, display):
+ self.subject.getCutPlanes()[0].setDisplayValuesBelowMin(display)
+
+
class PlaneColormapItem(ColormapBase):
"""
colormap name item.
@@ -1241,6 +1488,11 @@ class PlaneGroup(SubjectItem):
valueItem = PlaneMaxRangeItem(self.subject)
self.appendRow([nameItem, valueItem])
+ nameItem = qt.QStandardItem('Values<=Min')
+ nameItem.setEditable(False)
+ valueItem = PlaneDisplayBelowMinItem(self.subject)
+ self.appendRow([nameItem, valueItem])
+
class PlaneVisibleItem(SubjectItem):
"""