diff options
Diffstat (limited to 'silx/gui/plot3d/SFViewParamTree.py')
-rw-r--r-- | silx/gui/plot3d/SFViewParamTree.py | 1817 |
1 files changed, 0 insertions, 1817 deletions
diff --git a/silx/gui/plot3d/SFViewParamTree.py b/silx/gui/plot3d/SFViewParamTree.py deleted file mode 100644 index 4e179fc..0000000 --- a/silx/gui/plot3d/SFViewParamTree.py +++ /dev/null @@ -1,1817 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-2020 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 tree widget to set/view parameters of a ScalarFieldView. -""" - -from __future__ import absolute_import - -__authors__ = ["D. N."] -__license__ = "MIT" -__date__ = "24/04/2018" - -import logging -import sys -import weakref - -import numpy - -from silx.gui import qt -from silx.gui.icons import getQIcon -from silx.gui.colors import Colormap -from silx.gui.widgets.FloatEdit import FloatEdit - -from .ScalarFieldView import Isosurface - - -_logger = logging.getLogger(__name__) - - -class ModelColumns(object): - NameColumn, ValueColumn, ColumnMax = range(3) - ColumnNames = ['Name', 'Value'] - - -class SubjectItem(qt.QStandardItem): - """ - Base class for observers items. - - Subclassing: - ------------ - The following method can/should be reimplemented: - - _init - - _pullData - - _pushData - - _setModelData - - _subjectChanged - - getEditor - - getSignals - - leftClicked - - queryRemove - - setEditorData - - Also the following attributes are available: - - editable - - persistent - - :param subject: object that this item will be observing. - """ - - editable = False - """ boolean: set to True to make the item editable. """ - - persistent = False - """ - boolean: set to True to make the editor persistent. - See : Qt.QAbstractItemView.openPersistentEditor - """ - - def __init__(self, subject, *args): - - super(SubjectItem, self).__init__(*args) - - self.setEditable(self.editable) - - self.__subject = None - self.subject = subject - - def setData(self, value, role=qt.Qt.UserRole, pushData=True): - """ - 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 - internally or from the view. - - :param value: the value ti set to data - :param role: role in the item - :param pushData: if True push value in the existing data. - """ - if role == qt.Qt.EditRole and pushData: - setValue = self._pushData(value, role) - if setValue != value: - value = setValue - super(SubjectItem, self).setData(value, role) - - @property - def subject(self): - """The subject this item is observing""" - return None if self.__subject is None else self.__subject() - - @subject.setter - def subject(self, subject): - if self.__subject is not None: - raise ValueError('Subject already set ' - ' (subject change not supported).') - if subject is None: - self.__subject = None - else: - self.__subject = weakref.ref(subject) - if subject is not None: - self._init() - self._connectSignals() - - def _connectSignals(self): - """ - Connects the signals. Called when the subject is set. - """ - - def gen_slot(_sigIdx): - def slotfn(*args, **kwargs): - self._subjectChanged(signalIdx=_sigIdx, - args=args, - kwargs=kwargs) - return slotfn - - if self.__subject is not None: - self.__slots = slots = [] - - signals = self.getSignals() - - if signals: - if not isinstance(signals, (list, tuple)): - signals = [signals] - for sigIdx, signal in enumerate(signals): - slot = gen_slot(sigIdx) - signal.connect(slot) - slots.append((signal, slot)) - - def _disconnectSignals(self): - """ - Disconnects all subject's signal - """ - if self.__slots: - for signal, slot in self.__slots: - try: - signal.disconnect(slot) - except TypeError: - pass - - def _enableRow(self, enable): - """ - Set the enabled state for this cell, or for the whole row - if this item has a parent. - - :param bool enable: True if we wan't to enable the cell - """ - parent = self.parent() - model = self.model() - if model is None or parent is None: - # no parent -> no siblings - self.setEnabled(enable) - return - - for col in range(model.columnCount()): - sibling = parent.child(self.row(), col) - sibling.setEnabled(enable) - - ################################################################# - # Overloadable methods - ################################################################# - - def getSignals(self): - """ - Returns the list of this items subject's signals that - this item will be listening to. - - :return: list. - """ - return None - - def _subjectChanged(self, signalIdx=None, args=None, kwargs=None): - """ - Called when one of the signals is triggered. Default implementation - just calls _pullData, compares the result to the current value stored - as Qt.EditRole, and stores the new value if it is different. It also - stores its str representation as Qt.DisplayRole - - :param signalIdx: index of the triggered signal. The value passed - is the same as the signal position in the list returned by - SubjectItem.getSignals. - :param args: arguments received from the signal - :param kwargs: keyword arguments received from the signal - """ - data = self._pullData() - if data == self.data(qt.Qt.EditRole): - return - self.setData(data, role=qt.Qt.DisplayRole, pushData=False) - self.setData(data, role=qt.Qt.EditRole, pushData=False) - - def _pullData(self): - """ - Pulls data from the subject. - - :return: subject data - """ - return None - - def _pushData(self, value, role=qt.Qt.UserRole): - """ - Pushes data to the subject and returns the actual value that was stored - - :return: the value that was stored - """ - return value - - def _init(self): - """ - Called when the subject is set. - :return: - """ - self._subjectChanged() - - def getEditor(self, parent, option, index): - """ - Returns the editor widget used to edit this item's data. The arguments - are the one passed to the QStyledItemDelegate.createEditor method. - - :param parent: the Qt parent of the editor - :param option: - :param index: - :return: - """ - return None - - def setEditorData(self, editor): - """ - This is called by the View's delegate just before the editor is shown, - its purpose it to setup the editors contents. Return False to use - the delegate's default behaviour. - - :param editor: - :return: - """ - return True - - def _setModelData(self, editor): - """ - This is called by the View's delegate just before the editor is closed, - its allows this item to update itself with data from the editor. - - :param editor: - :return: - """ - return False - - def queryRemove(self, view=None): - """ - This is called by the view to ask this items if it (the view) can - remove it. Return True to let the view know that the item can be - removed. - - :param view: - :return: - """ - return False - - def leftClicked(self): - """ - This method is called by the view when the item's cell if left clicked. - - :return: - """ - pass - - -# View settings ############################################################### - -class ColorItem(SubjectItem): - """color item.""" - editable = True - persistent = True - - def getEditor(self, parent, option, index): - editor = QColorEditor(parent) - editor.color = self.getColor() - - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.sigColorChanged.connect( - lambda color: self._editorSlot(color)) - return editor - - def _editorSlot(self, color): - self.setData(color, qt.Qt.EditRole) - - def _pushData(self, value, role=qt.Qt.UserRole): - self.setColor(value) - return self.getColor() - - def _pullData(self): - self.getColor() - - def setColor(self, color): - """Override to implement actual color setter""" - pass - - -class BackgroundColorItem(ColorItem): - itemName = 'Background' - - def setColor(self, color): - self.subject.setBackgroundColor(color) - - def getColor(self): - return self.subject.getBackgroundColor() - - -class ForegroundColorItem(ColorItem): - itemName = 'Foreground' - - def setColor(self, color): - self.subject.setForegroundColor(color) - - def getColor(self): - return self.subject.getForegroundColor() - - -class HighlightColorItem(ColorItem): - itemName = 'Highlight' - - def setColor(self, color): - self.subject.setHighlightColor(color) - - def getColor(self): - 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(int(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(int(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. - - Item is checkable. - """ - itemName = 'Bounding Box' - - def _init(self): - visible = self.subject.isBoundingBoxVisible() - self.setCheckable(True) - self.setCheckState(qt.Qt.Checked if visible else qt.Qt.Unchecked) - - def leftClicked(self): - checked = (self.checkState() == qt.Qt.Checked) - if checked != self.subject.isBoundingBoxVisible(): - self.subject.setBoundingBoxVisible(checked) - - -class OrientationIndicatorItem(SubjectItem): - """Orientation indicator visibility item. - - Item is checkable. - """ - itemName = 'Axes indicator' - - def _init(self): - plot3d = self.subject.getPlot3DWidget() - visible = plot3d.isOrientationIndicatorVisible() - self.setCheckable(True) - self.setCheckState(qt.Qt.Checked if visible else qt.Qt.Unchecked) - - def leftClicked(self): - plot3d = self.subject.getPlot3DWidget() - checked = (self.checkState() == qt.Qt.Checked) - if checked != plot3d.isOrientationIndicatorVisible(): - plot3d.setOrientationIndicatorVisible(checked) - - -class ViewSettingsItem(qt.QStandardItem): - """Viewport settings""" - - def __init__(self, subject, *args): - - super(ViewSettingsItem, self).__init__(*args) - - self.setEditable(False) - - classes = (BackgroundColorItem, - ForegroundColorItem, - HighlightColorItem, - 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 ############################################################ - -class DataChangedItem(SubjectItem): - """ - Base class for items listening to ScalarFieldView.sigDataChanged - """ - - def getSignals(self): - subject = self.subject - if subject: - return subject.sigDataChanged, subject.sigTransformChanged - return None - - def _init(self): - self._subjectChanged() - - -class DataTypeItem(DataChangedItem): - itemName = 'dtype' - - def _pullData(self): - data = self.subject.getData(copy=False) - return ((data is not None) and str(data.dtype)) or 'N/A' - - -class DataShapeItem(DataChangedItem): - itemName = 'size' - - def _pullData(self): - data = self.subject.getData(copy=False) - if data is None: - return 'N/A' - else: - return str(list(reversed(data.shape))) - - -class OffsetItem(DataChangedItem): - itemName = 'offset' - - def _pullData(self): - offset = self.subject.getTranslation() - return ((offset is not None) and str(offset)) or 'N/A' - - -class ScaleItem(DataChangedItem): - itemName = 'scale' - - def _pullData(self): - scale = self.subject.getScale() - 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): - - super(DataSetItem, self).__init__(*args) - - self.setEditable(False) - - 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 ################################################################## - -class IsoSurfaceRootItem(SubjectItem): - """ - Root (i.e : column index 0) Isosurface item. - """ - - def __init__(self, subject, normalization, *args): - self._isoLevelSliderNormalization = normalization - super(IsoSurfaceRootItem, self).__init__(subject, *args) - - def getSignals(self): - subject = self.subject - return [subject.sigColorChanged, - subject.sigVisibilityChanged] - - def _subjectChanged(self, signalIdx=None, args=None, kwargs=None): - if signalIdx == 0: - color = self.subject.getColor() - self.setData(color, qt.Qt.DecorationRole) - elif signalIdx == 1: - visible = args[0] - self.setCheckState((visible and qt.Qt.Checked) or qt.Qt.Unchecked) - - def _init(self): - self.setCheckable(True) - - isosurface = self.subject - color = isosurface.getColor() - visible = isosurface.isVisible() - self.setData(color, qt.Qt.DecorationRole) - self.setCheckState((visible and qt.Qt.Checked) or qt.Qt.Unchecked) - - nameItem = qt.QStandardItem('Level') - sliderItem = IsoSurfaceLevelSlider(self.subject, - self._isoLevelSliderNormalization) - self.appendRow([nameItem, sliderItem]) - - nameItem = qt.QStandardItem('Color') - nameItem.setEditable(False) - valueItem = IsoSurfaceColorItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Opacity') - nameItem.setTextAlignment(qt.Qt.AlignLeft | qt.Qt.AlignTop) - nameItem.setEditable(False) - valueItem = IsoSurfaceAlphaItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem() - nameItem.setEditable(False) - valueItem = IsoSurfaceAlphaLegendItem(self.subject) - valueItem.setEditable(False) - self.appendRow([nameItem, valueItem]) - - def queryRemove(self, view=None): - buttons = qt.QMessageBox.Ok | qt.QMessageBox.Cancel - ans = qt.QMessageBox.question(view, - 'Remove isosurface', - 'Remove the selected iso-surface?', - buttons=buttons) - if ans == qt.QMessageBox.Ok: - sfview = self.subject.parent() - if sfview: - sfview.removeIsosurface(self.subject) - return False - return False - - def leftClicked(self): - checked = (self.checkState() == qt.Qt.Checked) - visible = self.subject.isVisible() - if checked != visible: - self.subject.setVisible(checked) - - -class IsoSurfaceLevelItem(SubjectItem): - """ - Base class for the isosurface level items. - """ - editable = True - - def getSignals(self): - subject = self.subject - 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() - - def _pushData(self, value, role=qt.Qt.UserRole): - self.subject.setLevel(value) - return self.subject.getLevel() - - -class _IsoLevelSlider(qt.QSlider): - """QSlider used for iso-surface level with linear scale""" - - def __init__(self, parent, subject, normalization): - super(_IsoLevelSlider, self).__init__(parent=parent) - self.subject = subject - - if normalization == 'arcsinh': - self.__norm = numpy.arcsinh - self.__invNorm = numpy.sinh - elif normalization == 'linear': - self.__norm = lambda x: x - self.__invNorm = lambda x: x - else: - raise ValueError( - "Unsupported normalization %s", normalization) - - self.sliderReleased.connect(self.__sliderReleased) - - self.subject.sigLevelChanged.connect(self.setLevel) - self.subject.parent().sigDataChanged.connect(self.__dataChanged) - - def setLevel(self, level): - """Set slider from iso-surface level""" - dataRange = self.subject.parent().getDataRange() - - if dataRange is not None: - min_ = self.__norm(dataRange[0]) - max_ = self.__norm(dataRange[-1]) - - width = max_ - min_ - if width > 0: - sliderWidth = self.maximum() - self.minimum() - sliderPosition = sliderWidth * (self.__norm(level) - min_) / width - self.setValue(int(sliderPosition)) - - def __dataChanged(self): - """Handles data update to refresh slider range if needed""" - self.setLevel(self.subject.getLevel()) - - def __sliderReleased(self): - value = self.value() - dataRange = self.subject.parent().getDataRange() - if dataRange is not None: - min_ = self.__norm(dataRange[0]) - max_ = self.__norm(dataRange[-1]) - width = max_ - min_ - sliderWidth = self.maximum() - self.minimum() - level = min_ + width * value / sliderWidth - self.subject.setLevel(self.__invNorm(level)) - - -class IsoSurfaceLevelSlider(IsoSurfaceLevelItem): - """ - Isosurface level item with a slider editor. - """ - nTicks = 1000 - persistent = True - - def __init__(self, subject, normalization): - self.normalization = normalization - super(IsoSurfaceLevelSlider, self).__init__(subject) - - def getEditor(self, parent, option, index): - editor = _IsoLevelSlider(parent, self.subject, self.normalization) - editor.setOrientation(qt.Qt.Horizontal) - editor.setMinimum(0) - editor.setMaximum(self.nTicks) - - editor.setSingleStep(1) - - editor.setLevel(self.subject.getLevel()) - return editor - - def setEditorData(self, editor): - return True - - def _setModelData(self, editor): - return True - - -class IsoSurfaceColorItem(SubjectItem): - """ - Isosurface color item. - """ - editable = True - persistent = True - - def getSignals(self): - return self.subject.sigColorChanged - - def getEditor(self, parent, option, index): - editor = QColorEditor(parent) - color = self.subject.getColor() - color.setAlpha(255) - editor.color = color - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.sigColorChanged.connect( - lambda color: self.__editorChanged(color)) - return editor - - def __editorChanged(self, color): - color.setAlpha(self.subject.getColor().alpha()) - self.subject.setColor(color) - - def _pushData(self, value, role=qt.Qt.UserRole): - self.subject.setColor(value) - return self.subject.getColor() - - -class QColorEditor(qt.QWidget): - """ - QColor editor. - """ - sigColorChanged = qt.Signal(object) - - color = property(lambda self: qt.QColor(self.__color)) - - @color.setter - def color(self, color): - self._setColor(color) - self.__previousColor = color - - def __init__(self, *args, **kwargs): - super(QColorEditor, self).__init__(*args, **kwargs) - layout = qt.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - button = qt.QToolButton() - icon = qt.QIcon(qt.QPixmap(32, 32)) - button.setIcon(icon) - layout.addWidget(button) - button.clicked.connect(self.__showColorDialog) - layout.addStretch(1) - - self.__color = None - self.__previousColor = None - - def sizeHint(self): - return qt.QSize(0, 0) - - def _setColor(self, qColor): - button = self.findChild(qt.QToolButton) - pixmap = qt.QPixmap(32, 32) - pixmap.fill(qColor) - button.setIcon(qt.QIcon(pixmap)) - self.__color = qColor - - def __showColorDialog(self): - dialog = qt.QColorDialog(parent=self) - if sys.platform == 'darwin': - # Use of native color dialog on macos might cause problems - dialog.setOption(qt.QColorDialog.DontUseNativeDialog, True) - - self.__previousColor = self.__color - dialog.setAttribute(qt.Qt.WA_DeleteOnClose) - dialog.setModal(True) - dialog.currentColorChanged.connect(self.__colorChanged) - dialog.finished.connect(self.__dialogClosed) - dialog.show() - - def __colorChanged(self, color): - self.__color = color - self._setColor(color) - self.sigColorChanged.emit(color) - - def __dialogClosed(self, result): - if result == qt.QDialog.Rejected: - self.__colorChanged(self.__previousColor) - self.__previousColor = None - - -class IsoSurfaceAlphaItem(SubjectItem): - """ - Isosurface alpha item. - """ - editable = True - persistent = True - - def _init(self): - pass - - def getSignals(self): - return self.subject.sigColorChanged - - def getEditor(self, parent, option, index): - editor = qt.QSlider(parent) - editor.setOrientation(qt.Qt.Horizontal) - editor.setMinimum(0) - editor.setMaximum(255) - - color = self.subject.getColor() - editor.setValue(color.alpha()) - - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.valueChanged.connect( - lambda value: self.__editorChanged(value)) - - return editor - - def __editorChanged(self, value): - color = self.subject.getColor() - color.setAlpha(value) - self.subject.setColor(color) - - def setEditorData(self, editor): - return True - - def _setModelData(self, editor): - return True - - -class IsoSurfaceAlphaLegendItem(SubjectItem): - """Legend to place under opacity slider""" - - editable = False - persistent = True - - def getEditor(self, parent, option, index): - layout = qt.QHBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(qt.QLabel('0')) - layout.addStretch(1) - layout.addWidget(qt.QLabel('1')) - - editor = qt.QWidget(parent) - editor.setLayout(layout) - return editor - - -class IsoSurfaceCount(SubjectItem): - """ - Item displaying the number of isosurfaces. - """ - - def getSignals(self): - subject = self.subject - return [subject.sigIsosurfaceAdded, subject.sigIsosurfaceRemoved] - - def _pullData(self): - return len(self.subject.getIsosurfaces()) - - -class IsoSurfaceAddRemoveWidget(qt.QWidget): - - sigViewTask = qt.Signal(str) - """Signal for the tree view to perform some task""" - - def __init__(self, parent, item): - super(IsoSurfaceAddRemoveWidget, self).__init__(parent) - self._item = item - layout = qt.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - addBtn = qt.QToolButton(self) - addBtn.setText('+') - addBtn.setToolButtonStyle(qt.Qt.ToolButtonTextOnly) - layout.addWidget(addBtn) - addBtn.clicked.connect(self.__addClicked) - - removeBtn = qt.QToolButton(self) - removeBtn.setText('-') - removeBtn.setToolButtonStyle(qt.Qt.ToolButtonTextOnly) - layout.addWidget(removeBtn) - removeBtn.clicked.connect(self.__removeClicked) - - layout.addStretch(1) - - def __addClicked(self): - sfview = self._item.subject - if not sfview: - return - dataRange = sfview.getDataRange() - if dataRange is None: - dataRange = [0, 1] - - sfview.addIsosurface( - numpy.mean((dataRange[0], dataRange[-1])), '#0000FF') - - def __removeClicked(self): - self.sigViewTask.emit('remove_iso') - - -class IsoSurfaceAddRemoveItem(SubjectItem): - """ - Item displaying a simple QToolButton allowing to add an isosurface. - """ - persistent = True - - def getEditor(self, parent, option, index): - return IsoSurfaceAddRemoveWidget(parent, self) - - -class IsoSurfaceGroup(SubjectItem): - """ - Root item for the list of isosurface items. - """ - - def __init__(self, subject, normalization, *args): - self._isoLevelSliderNormalization = normalization - super(IsoSurfaceGroup, self).__init__(subject, *args) - - def getSignals(self): - subject = self.subject - return [subject.sigIsosurfaceAdded, subject.sigIsosurfaceRemoved] - - def _subjectChanged(self, signalIdx=None, args=None, kwargs=None): - if signalIdx == 0: - if len(args) >= 1: - isosurface = args[0] - if not isinstance(isosurface, Isosurface): - raise ValueError('Expected an isosurface instance.') - self.__addIsosurface(isosurface) - else: - raise ValueError('Expected an isosurface instance.') - elif signalIdx == 1: - if len(args) >= 1: - isosurface = args[0] - if not isinstance(isosurface, Isosurface): - raise ValueError('Expected an isosurface instance.') - self.__removeIsosurface(isosurface) - else: - raise ValueError('Expected an isosurface instance.') - - def __addIsosurface(self, isosurface): - valueItem = IsoSurfaceRootItem( - subject=isosurface, - normalization=self._isoLevelSliderNormalization) - nameItem = IsoSurfaceLevelItem(subject=isosurface) - self.insertRow(max(0, self.rowCount() - 1), [valueItem, nameItem]) - - def __removeIsosurface(self, isosurface): - for row in range(self.rowCount()): - child = self.child(row) - subject = getattr(child, 'subject', None) - if subject == isosurface: - self.takeRow(row) - break - - def _init(self): - nameItem = IsoSurfaceAddRemoveItem(self.subject) - valueItem = qt.QStandardItem() - valueItem.setEditable(False) - self.appendRow([nameItem, valueItem]) - - subject = self.subject - isosurfaces = subject.getIsosurfaces() - for isosurface in isosurfaces: - self.__addIsosurface(isosurface) - - -# Cutting Plane ############################################################### - -class ColormapBase(SubjectItem): - """ - Mixin class for colormap items. - """ - - def getSignals(self): - return [self.subject.getCutPlanes()[0].sigColormapChanged] - - -class PlaneMinRangeItem(ColormapBase): - """ - colormap minVal item. - Editor is a QLineEdit with a QDoubleValidator - """ - editable = True - - def _pullData(self): - colormap = self.subject.getCutPlanes()[0].getColormap() - auto = colormap.isAutoscale() - if auto == self.isEnabled(): - self._enableRow(not auto) - return colormap.getVMin() - - def _pushData(self, value, role=qt.Qt.UserRole): - self._setVMin(value) - - def _setVMin(self, value): - colormap = self.subject.getCutPlanes()[0].getColormap() - vMin = value - vMax = colormap.getVMax() - - if vMax is not None and value > vMax: - vMin = vMax - vMax = value - colormap.setVRange(vMin, vMax) - - def getEditor(self, parent, option, index): - return FloatEdit(parent) - - def setEditorData(self, editor): - editor.setValue(self._pullData()) - return True - - def _setModelData(self, editor): - value = editor.value() - self._setVMin(value) - return True - - -class PlaneMaxRangeItem(ColormapBase): - """ - colormap maxVal item. - Editor is a QLineEdit with a QDoubleValidator - """ - editable = True - - def _pullData(self): - colormap = self.subject.getCutPlanes()[0].getColormap() - auto = colormap.isAutoscale() - if auto == self.isEnabled(): - self._enableRow(not auto) - return self.subject.getCutPlanes()[0].getColormap().getVMax() - - def _setVMax(self, value): - colormap = self.subject.getCutPlanes()[0].getColormap() - vMin = colormap.getVMin() - vMax = value - if vMin is not None and value < vMin: - vMax = vMin - vMin = value - colormap.setVRange(vMin, vMax) - - def getEditor(self, parent, option, index): - return FloatEdit(parent) - - def setEditorData(self, editor): - editor.setText(str(self._pullData())) - return True - - def _setModelData(self, editor): - value = editor.value() - self._setVMax(value) - return True - - -class PlaneOrientationItem(SubjectItem): - """ - Plane orientation item. - Editor is a QComboBox. - """ - editable = True - - _PLANE_ACTIONS = ( - ('3d-plane-normal-x', 'Plane 0', - 'Set plane perpendicular to red axis', (1., 0., 0.)), - ('3d-plane-normal-y', 'Plane 1', - 'Set plane perpendicular to green axis', (0., 1., 0.)), - ('3d-plane-normal-z', 'Plane 2', - 'Set plane perpendicular to blue axis', (0., 0., 1.)), - ) - - def getSignals(self): - return [self.subject.getCutPlanes()[0].sigPlaneChanged] - - def _pullData(self): - currentNormal = self.subject.getCutPlanes()[0].getNormal( - coordinates='scene') - for _, text, _, normal in self._PLANE_ACTIONS: - if numpy.allclose(normal, currentNormal): - return text - return '' - - def getEditor(self, parent, option, index): - editor = qt.QComboBox(parent) - for iconName, text, tooltip, normal in self._PLANE_ACTIONS: - editor.addItem(getQIcon(iconName), text) - - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.currentIndexChanged[int].connect( - lambda index: self.__editorChanged(index)) - return editor - - def __editorChanged(self, index): - normal = self._PLANE_ACTIONS[index][3] - plane = self.subject.getCutPlanes()[0] - plane.setNormal(normal, coordinates='scene') - plane.moveToCenter() - - def setEditorData(self, editor): - currentText = self._pullData() - index = 0 - for normIdx, (_, text, _, _) in enumerate(self._PLANE_ACTIONS): - if text == currentText: - index = normIdx - break - editor.setCurrentIndex(index) - return True - - def _setModelData(self, editor): - return True - - -class PlaneInterpolationItem(SubjectItem): - """Toggle cut plane interpolation method: nearest or linear. - - Item is checkable - """ - - def _init(self): - interpolation = self.subject.getCutPlanes()[0].getInterpolation() - self.setCheckable(True) - self.setCheckState( - qt.Qt.Checked if interpolation == 'linear' else qt.Qt.Unchecked) - self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False) - - def getSignals(self): - return [self.subject.getCutPlanes()[0].sigInterpolationChanged] - - def leftClicked(self): - checked = self.checkState() == qt.Qt.Checked - self._setInterpolation('linear' if checked else 'nearest') - - def _pullData(self): - interpolation = self.subject.getCutPlanes()[0].getInterpolation() - self._setInterpolation(interpolation) - return interpolation[0].upper() + interpolation[1:] - - def _setInterpolation(self, interpolation): - 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. - Editor is a QComboBox - """ - editable = True - - listValues = ['gray', 'reversed gray', - 'temperature', 'red', - 'green', 'blue', - 'viridis', 'magma', 'inferno', 'plasma'] - - def getEditor(self, parent, option, index): - editor = qt.QComboBox(parent) - editor.addItems(self.listValues) - - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.currentIndexChanged[int].connect( - lambda index: self.__editorChanged(index)) - - return editor - - def __editorChanged(self, index): - colormapName = self.listValues[index] - colormap = self.subject.getCutPlanes()[0].getColormap() - colormap.setName(colormapName) - - def setEditorData(self, editor): - colormapName = self.subject.getCutPlanes()[0].getColormap().getName() - try: - index = self.listValues.index(colormapName) - except ValueError: - _logger.error('Unsupported colormap: %s', colormapName) - else: - editor.setCurrentIndex(index) - return True - - def _setModelData(self, editor): - self.__editorChanged(editor.currentIndex()) - return True - - def _pullData(self): - return self.subject.getCutPlanes()[0].getColormap().getName() - - -class PlaneAutoScaleItem(ColormapBase): - """ - colormap autoscale item. - Item is checkable. - """ - - def _init(self): - colorMap = self.subject.getCutPlanes()[0].getColormap() - self.setCheckable(True) - self.setCheckState((colorMap.isAutoscale() and qt.Qt.Checked) - or qt.Qt.Unchecked) - self.setData(self._pullData(), role=qt.Qt.DisplayRole, pushData=False) - - def leftClicked(self): - checked = (self.checkState() == qt.Qt.Checked) - self._setAutoScale(checked) - - def _setAutoScale(self, auto): - view3d = self.subject - colormap = view3d.getCutPlanes()[0].getColormap() - - if auto != colormap.isAutoscale(): - if auto: - vMin = vMax = None - else: - dataRange = view3d.getDataRange() - if dataRange is None: - vMin = vMax = None - else: - vMin, vMax = dataRange[0], dataRange[-1] - colormap.setVRange(vMin, vMax) - - def _pullData(self): - auto = self.subject.getCutPlanes()[0].getColormap().isAutoscale() - self._setAutoScale(auto) - if auto: - data = 'Auto' - else: - data = 'User' - return data - - -class NormalizationNode(ColormapBase): - """ - colormap normalization item. - Item is a QComboBox. - """ - editable = True - listValues = list(Colormap.NORMALIZATIONS) - - def getEditor(self, parent, option, index): - editor = qt.QComboBox(parent) - editor.addItems(self.listValues) - - # Wrapping call in lambda is a workaround for PySide with Python 3 - editor.currentIndexChanged[int].connect( - lambda index: self.__editorChanged(index)) - - return editor - - def __editorChanged(self, index): - colorMap = self.subject.getCutPlanes()[0].getColormap() - normalization = self.listValues[index] - self.subject.getCutPlanes()[0].setColormap(name=colorMap.getName(), - norm=normalization, - vmin=colorMap.getVMin(), - vmax=colorMap.getVMax()) - - def setEditorData(self, editor): - normalization = self.subject.getCutPlanes()[0].getColormap().getNormalization() - index = self.listValues.index(normalization) - editor.setCurrentIndex(index) - return True - - def _setModelData(self, editor): - self.__editorChanged(editor.currentIndex()) - return True - - def _pullData(self): - return self.subject.getCutPlanes()[0].getColormap().getNormalization() - - -class PlaneGroup(SubjectItem): - """ - Root Item for the plane items. - """ - def _init(self): - valueItem = qt.QStandardItem() - valueItem.setEditable(False) - nameItem = PlaneVisibleItem(self.subject, 'Visible') - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Colormap') - nameItem.setEditable(False) - valueItem = PlaneColormapItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Normalization') - nameItem.setEditable(False) - valueItem = NormalizationNode(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Orientation') - nameItem.setEditable(False) - valueItem = PlaneOrientationItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Interpolation') - nameItem.setEditable(False) - valueItem = PlaneInterpolationItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Autoscale') - nameItem.setEditable(False) - valueItem = PlaneAutoScaleItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Min') - nameItem.setEditable(False) - valueItem = PlaneMinRangeItem(self.subject) - self.appendRow([nameItem, valueItem]) - - nameItem = qt.QStandardItem('Max') - nameItem.setEditable(False) - 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): - """ - Plane visibility item. - Item is checkable. - """ - def _init(self): - plane = self.subject.getCutPlanes()[0] - self.setCheckable(True) - self.setCheckState((plane.isVisible() and qt.Qt.Checked) - or qt.Qt.Unchecked) - - def leftClicked(self): - plane = self.subject.getCutPlanes()[0] - checked = (self.checkState() == qt.Qt.Checked) - if checked != plane.isVisible(): - plane.setVisible(checked) - if plane.isVisible(): - plane.moveToCenter() - - -# Tree ######################################################################## - -class ItemDelegate(qt.QStyledItemDelegate): - """ - Delegate for the QTreeView filled with SubjectItems. - """ - - sigDelegateEvent = qt.Signal(str) - - def __init__(self, parent=None): - super(ItemDelegate, self).__init__(parent) - - def createEditor(self, parent, option, index): - item = index.model().itemFromIndex(index) - if item: - if isinstance(item, SubjectItem): - editor = item.getEditor(parent, option, index) - if editor: - editor.setAutoFillBackground(True) - if hasattr(editor, 'sigViewTask'): - editor.sigViewTask.connect(self.__viewTask) - return editor - - editor = super(ItemDelegate, self).createEditor(parent, - option, - index) - return editor - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - - def setEditorData(self, editor, index): - item = index.model().itemFromIndex(index) - if item: - if isinstance(item, SubjectItem) and item.setEditorData(editor): - return - super(ItemDelegate, self).setEditorData(editor, index) - - def setModelData(self, editor, model, index): - item = index.model().itemFromIndex(index) - if isinstance(item, SubjectItem) and item._setModelData(editor): - return - super(ItemDelegate, self).setModelData(editor, model, index) - - def __viewTask(self, task): - self.sigDelegateEvent.emit(task) - - -class TreeView(qt.QTreeView): - """ - TreeView displaying the SubjectItems for the ScalarFieldView. - """ - - def __init__(self, parent=None): - super(TreeView, self).__init__(parent) - self.__openedIndex = None - self._isoLevelSliderNormalization = 'linear' - - self.setIconSize(qt.QSize(16, 16)) - - header = self.header() - if hasattr(header, 'setSectionResizeMode'): # Qt5 - header.setSectionResizeMode(qt.QHeaderView.ResizeToContents) - else: # Qt4 - header.setResizeMode(qt.QHeaderView.ResizeToContents) - - delegate = ItemDelegate() - self.setItemDelegate(delegate) - delegate.sigDelegateEvent.connect(self.__delegateEvent) - self.setSelectionBehavior(qt.QAbstractItemView.SelectRows) - self.setSelectionMode(qt.QAbstractItemView.SingleSelection) - - self.clicked.connect(self.__clicked) - - def setSfView(self, sfView): - """ - Sets the ScalarFieldView this view is controlling. - - :param sfView: A `ScalarFieldView` - """ - model = qt.QStandardItemModel() - model.setColumnCount(ModelColumns.ColumnMax) - model.setHorizontalHeaderLabels(['Name', 'Value']) - - item = qt.QStandardItem() - item.setEditable(False) - model.appendRow([ViewSettingsItem(sfView, 'Style'), item]) - - item = qt.QStandardItem() - item.setEditable(False) - model.appendRow([DataSetItem(sfView, 'Data'), item]) - - item = IsoSurfaceCount(sfView) - item.setEditable(False) - model.appendRow([IsoSurfaceGroup(sfView, - self._isoLevelSliderNormalization, - 'Isosurfaces'), - item]) - - item = qt.QStandardItem() - item.setEditable(False) - model.appendRow([PlaneGroup(sfView, 'Cutting Plane'), item]) - - self.setModel(model) - - def setModel(self, model): - """ - Reimplementation of the QTreeView.setModel method. It connects the - rowsRemoved signal and opens the persistent editors. - - :param qt.QStandardItemModel model: the model - """ - - prevModel = self.model() - if prevModel: - self.__openPersistentEditors(qt.QModelIndex(), False) - try: - prevModel.rowsRemoved.disconnect(self.rowsRemoved) - except TypeError: - pass - - super(TreeView, self).setModel(model) - model.rowsRemoved.connect(self.rowsRemoved) - self.__openPersistentEditors(qt.QModelIndex()) - - def __openPersistentEditors(self, parent=None, openEditor=True): - """ - Opens or closes the items persistent editors. - - :param qt.QModelIndex parent: starting index, or None if the whole tree - is to be considered. - :param bool openEditor: True to open the editors, False to close them. - """ - model = self.model() - - if not model: - return - - if not parent or not parent.isValid(): - parent = self.model().invisibleRootItem().index() - - if openEditor: - meth = self.openPersistentEditor - else: - meth = self.closePersistentEditor - - curParent = parent - children = [model.index(row, 0, curParent) - for row in range(model.rowCount(curParent))] - - columnCount = model.columnCount() - - while len(children) > 0: - curParent = children.pop(-1) - - children.extend([model.index(row, 0, curParent) - for row in range(model.rowCount(curParent))]) - - for colIdx in range(columnCount): - sibling = model.sibling(curParent.row(), - colIdx, - curParent) - item = model.itemFromIndex(sibling) - if isinstance(item, SubjectItem) and item.persistent: - meth(sibling) - - def rowsAboutToBeRemoved(self, parent, start, end): - """ - Reimplementation of the QTreeView.rowsAboutToBeRemoved. Closes all - persistent editors under parent. - - :param qt.QModelIndex parent: Parent index - :param int start: Start index from parent index (inclusive) - :param int end: End index from parent index (inclusive) - """ - self.__openPersistentEditors(parent, False) - super(TreeView, self).rowsAboutToBeRemoved(parent, start, end) - - def rowsRemoved(self, parent, start, end): - """ - Called when QTreeView.rowsRemoved is emitted. Opens all persistent - editors under parent. - - :param qt.QModelIndex parent: Parent index - :param int start: Start index from parent index (inclusive) - :param int end: End index from parent index (inclusive) - """ - super(TreeView, self).rowsRemoved(parent, start, end) - self.__openPersistentEditors(parent, True) - - def rowsInserted(self, parent, start, end): - """ - Reimplementation of the QTreeView.rowsInserted. Opens all persistent - editors under parent. - - :param qt.QModelIndex parent: Parent index - :param int start: Start index from parent index - :param int end: End index from parent index - """ - self.__openPersistentEditors(parent, False) - super(TreeView, self).rowsInserted(parent, start, end) - self.__openPersistentEditors(parent) - - def keyReleaseEvent(self, event): - """ - Reimplementation of the QTreeView.keyReleaseEvent. - At the moment only Key_Delete is handled. It calls the selected item's - queryRemove method, and deleted the item if needed. - - :param qt.QKeyEvent event: A key event - """ - - # TODO : better filtering - key = event.key() - modifiers = event.modifiers() - - if key == qt.Qt.Key_Delete and modifiers == qt.Qt.NoModifier: - self.__removeIsosurfaces() - - super(TreeView, self).keyReleaseEvent(event) - - def __removeIsosurfaces(self): - model = self.model() - selected = self.selectedIndexes() - items = [] - # WARNING : the selection mode is set to single, so we re not - # supposed to have more than one item here. - # Multiple selection deletion has not been tested. - # Watch out for index invalidation - for index in selected: - leftIndex = model.sibling(index.row(), 0, index) - leftItem = model.itemFromIndex(leftIndex) - if isinstance(leftItem, SubjectItem) and leftItem not in items: - items.append(leftItem) - - isos = [item for item in items if isinstance(item, IsoSurfaceRootItem)] - if isos: - for iso in isos: - if iso.queryRemove(self): - parentItem = iso.parent() - parentItem.removeRow(iso.row()) - else: - qt.QMessageBox.information( - self, - 'Remove isosurface', - 'Select an iso-surface to remove it') - - def __clicked(self, index): - """ - Called when the QTreeView.clicked signal is emitted. Calls the item's - leftClick method. - - :param qt.QIndex index: An index - """ - item = self.model().itemFromIndex(index) - if isinstance(item, SubjectItem): - item.leftClicked() - - def __delegateEvent(self, task): - if task == 'remove_iso': - self.__removeIsosurfaces() - - def setIsoLevelSliderNormalization(self, normalization): - """Set the normalization for iso level slider - - This MUST be called *before* :meth:`setSfView` to have an effect. - - :param str normalization: Either 'linear' or 'arcsinh' - """ - assert normalization in ('linear', 'arcsinh') - self._isoLevelSliderNormalization = normalization |