From cebdc9244c019224846cb8d2668080fe386a6adc Mon Sep 17 00:00:00 2001 From: Alexandre Marie Date: Mon, 17 Dec 2018 12:28:24 +0100 Subject: New upstream version 0.9.0+dfsg --- silx/gui/plot3d/items/core.py | 201 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 179 insertions(+), 22 deletions(-) (limited to 'silx/gui/plot3d/items/core.py') diff --git a/silx/gui/plot3d/items/core.py b/silx/gui/plot3d/items/core.py index e549e59..0aefced 100644 --- a/silx/gui/plot3d/items/core.py +++ b/silx/gui/plot3d/items/core.py @@ -41,6 +41,7 @@ from ... import qt from ...plot.items import ItemChangedType from .. import scene from ..scene import axes, primitives, transform +from ._pick import PickContext @enum.unique @@ -219,6 +220,53 @@ class Item3D(qt.QObject): self._setForegroundColor( widget.getForegroundColor().getRgbF()) + # picking + + def _pick(self, context): + """Implement picking on this item. + + :param PickContext context: Current picking context + :return: Data indices at picked position or None + :rtype: Union[None,PickingResult] + """ + if (self.isVisible() and + context.isEnabled() and + context.isItemPickable(self) and + self._pickFastCheck(context)): + return self._pickFull(context) + return None + + def _pickFastCheck(self, context): + """Approximate item pick test (e.g., bounding box-based picking). + + :param PickContext context: Current picking context + :return: True if item might be picked + :rtype: bool + """ + primitive = self._getScenePrimitive() + + positionNdc = context.getNDCPosition() + if positionNdc is None: # No picking outside viewport + return False + + bounds = primitive.bounds(transformed=False, dataBounds=False) + if bounds is None: # primitive has no bounds + return False + + bounds = primitive.objectToNDCTransform.transformBounds(bounds) + + return (bounds[0, 0] <= positionNdc[0] <= bounds[1, 0] and + bounds[0, 1] <= positionNdc[1] <= bounds[1, 1]) + + def _pickFull(self, context): + """Perform precise picking in this item at given widget position. + + :param PickContext context: Current picking context + :return: Object holding the results or None + :rtype: Union[None,PickingResult] + """ + return None + class DataItem3D(Item3D): """Base class representing a data item with transform in the scene. @@ -256,12 +304,14 @@ class DataItem3D(Item3D): self._rotationCenter = 0., 0., 0. - self._getScenePrimitive().transforms = [ + self.__transforms = transform.TransformList([ self._translate, self._rotateForwardTranslation, self._rotate, self._rotateBackwardTranslation, - self._transformObjectToRotate] + self._transformObjectToRotate]) + + self._getScenePrimitive().transforms = self.__transforms def _updated(self, event=None): """Handle MixIn class updates. @@ -274,6 +324,13 @@ class DataItem3D(Item3D): # Transformations + def _getSceneTransforms(self): + """Return TransformList corresponding to current transforms + + :rtype: TransformList + """ + return self.__transforms + def setScale(self, sx=1., sy=1., sz=1.): """Set the scale of the item in the scene. @@ -452,7 +509,92 @@ class DataItem3D(Item3D): self._updated(Item3DChangedType.BOUNDING_BOX_VISIBLE) -class _BaseGroupItem(DataItem3D): +class BaseNodeItem(DataItem3D): + """Base class for data item having children (e.g., group, 3d volume).""" + + def __init__(self, parent=None, group=None): + """Base class representing a group of items in the scene. + + :param parent: The View widget this item belongs to. + :param Union[GroupBBox, None] group: + The scene group to use for rendering + """ + DataItem3D.__init__(self, parent=parent, group=group) + + def getItems(self): + """Returns the list of items currently present in the group. + + :rtype: tuple + """ + raise NotImplementedError('getItems must be implemented in subclass') + + def visit(self, included=True): + """Generator visiting the group content. + + It traverses the group sub-tree in a top-down left-to-right way. + + :param bool included: True (default) to include self in visit + """ + if included: + yield self + for child in self.getItems(): + yield child + if hasattr(child, 'visit'): + for item in child.visit(included=False): + yield item + + def pickItems(self, x, y, condition=None): + """Iterator over picked items in the group at given position. + + Each picked item yield a :class:`PickingResult` object + holding the picking information. + + It traverses the group sub-tree in a left-to-right top-down way. + + :param int x: X widget device pixel coordinate + :param int y: Y widget device pixel coordinate + :param callable condition: Optional test called for each item + checking whether to process it or not. + """ + viewport = self._getScenePrimitive().viewport + if viewport is None: + raise RuntimeError( + 'Cannot perform picking: Item not attached to a widget') + + context = PickContext(x, y, viewport, condition) + for result in self._pickItems(context): + yield result + + def _pickItems(self, context): + """Implement :meth:`pickItems` + + :param PickContext context: Current picking context + """ + if not self.isVisible() or not context.isEnabled(): + return # empty iterator + + # Use a copy to discard context changes once this returns + context = context.copy() + + if not self._pickFastCheck(context): + return # empty iterator + + result = self._pick(context) + if result is not None: + yield result + + for child in self.getItems(): + if isinstance(child, BaseNodeItem): + for result in child._pickItems(context): + yield result # Flatten result + + else: + result = child._pick(context) + if result is not None: + yield result + + +class _BaseGroupItem(BaseNodeItem): """Base class for group of items sharing a common transform.""" sigItemAdded = qt.Signal(object) @@ -474,9 +616,16 @@ class _BaseGroupItem(DataItem3D): :param Union[GroupBBox, None] group: The scene group to use for rendering """ - DataItem3D.__init__(self, parent=parent, group=group) + BaseNodeItem.__init__(self, parent=parent, group=group) self._items = [] + def _getGroupPrimitive(self): + """Returns the group for which to handle children. + + This allows this group to be different from the primitive. + """ + return self._getScenePrimitive() + def addItem(self, item, index=None): """Add an item to the group @@ -493,11 +642,11 @@ class _BaseGroupItem(DataItem3D): item.setParent(self) if index is None: - self._getScenePrimitive().children.append( + self._getGroupPrimitive().children.append( item._getScenePrimitive()) self._items.append(item) else: - self._getScenePrimitive().children.insert( + self._getGroupPrimitive().children.insert( index, item._getScenePrimitive()) self._items.insert(index, item) self.sigItemAdded.emit(item) @@ -518,7 +667,7 @@ class _BaseGroupItem(DataItem3D): if item not in self.getItems(): raise ValueError("Item3D not in group: %s" % str(item)) - self._getScenePrimitive().children.remove(item._getScenePrimitive()) + self._getGroupPrimitive().children.remove(item._getScenePrimitive()) self._items.remove(item) item.setParent(None) self.sigItemRemoved.emit(item) @@ -528,21 +677,6 @@ class _BaseGroupItem(DataItem3D): for item in self.getItems(): self.removeItem(item) - def visit(self, included=True): - """Generator visiting the group content. - - It traverses the group sub-tree in a top-down left-to-right way. - - :param bool included: True (default) to include self in visit - """ - if included: - yield self - for child in self.getItems(): - yield child - if hasattr(child, 'visit'): - for item in child.visit(included=False): - yield item - class GroupItem(_BaseGroupItem): """Group of items sharing a common transform.""" @@ -620,3 +754,26 @@ class GroupWithAxesItem(_BaseGroupItem): return self._Labels((labelledAxes.xlabel, labelledAxes.ylabel, labelledAxes.zlabel)) + + +class RootGroupWithAxesItem(GroupWithAxesItem): + """Special group with axes item for root of the scene. + + Uses 2 groups so that axes take transforms into account. + """ + + def __init__(self, parent=None): + super(RootGroupWithAxesItem, self).__init__(parent) + self.__group = scene.Group() + self.__group.transforms = self._getSceneTransforms() + + groupWithAxes = self._getScenePrimitive() + groupWithAxes.transforms = [] # Do not apply transforms here + groupWithAxes.children.append(self.__group) + + def _getGroupPrimitive(self): + """Returns the group for which to handle children. + + This allows this group to be different from the primitive. + """ + return self.__group -- cgit v1.2.3