diff options
Diffstat (limited to 'silx/gui/plot3d/items/mesh.py')
-rw-r--r-- | silx/gui/plot3d/items/mesh.py | 176 |
1 files changed, 150 insertions, 26 deletions
diff --git a/silx/gui/plot3d/items/mesh.py b/silx/gui/plot3d/items/mesh.py index 12a3941..21936ea 100644 --- a/silx/gui/plot3d/items/mesh.py +++ b/silx/gui/plot3d/items/mesh.py @@ -29,13 +29,19 @@ from __future__ import absolute_import __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "15/11/2017" +__date__ = "17/07/2018" + +import logging import numpy -from ..scene import primitives -from .core import DataItem3D, ItemChangedType +from ..scene import primitives, utils from ..scene.transform import Rotate +from .core import DataItem3D, ItemChangedType +from ._pick import PickingResult + + +_logger = logging.getLogger(__name__) class Mesh(DataItem3D): @@ -56,11 +62,7 @@ class Mesh(DataItem3D): copy=True): """Set mesh geometry data. - Supported drawing modes are: - - - For points: 'points' - - For lines: 'lines', 'line_strip', 'loop' - - For triangles: 'triangles', 'triangle_strip', 'fan' + Supported drawing modes are: 'triangles', 'triangle_strip', 'fan' :param numpy.ndarray position: Position (x, y, z) of each vertex as a (N, 3) array @@ -73,7 +75,7 @@ class Mesh(DataItem3D): self._getScenePrimitive().children = [] # Remove any previous mesh if position is None or len(position) == 0: - self._mesh = 0 + self._mesh = None else: self._mesh = primitives.Mesh3D( position, color, normal, mode=mode, copy=copy) @@ -145,6 +147,72 @@ class Mesh(DataItem3D): """ return self._mesh.drawMode + 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] + """ + rayObject = context.getPickingSegment(frame=self._getScenePrimitive()) + if rayObject is None: # No picking outside viewport + return None + rayObject = rayObject[:, :3] + + positions = self.getPositionData(copy=False) + if positions.size == 0: + return None + + mode = self.getDrawMode() + if mode == 'triangles': + triangles = positions.reshape(-1, 3, 3) + + elif mode == 'triangle_strip': + # Expand strip + triangles = numpy.empty((len(positions) - 2, 3, 3), + dtype=positions.dtype) + triangles[:, 0] = positions[:-2] + triangles[:, 1] = positions[1:-1] + triangles[:, 2] = positions[2:] + + elif mode == 'fan': + # Expand fan + triangles = numpy.empty((len(positions) - 2, 3, 3), + dtype=positions.dtype) + triangles[:, 0] = positions[0] + triangles[:, 1] = positions[1:-1] + triangles[:, 2] = positions[2:] + + else: + _logger.warning("Unsupported draw mode: %s" % mode) + return None + + trianglesIndices, t, barycentric = utils.segmentTrianglesIntersection( + rayObject, triangles) + + if len(trianglesIndices) == 0: + return None + + points = t.reshape(-1, 1) * (rayObject[1] - rayObject[0]) + rayObject[0] + + # Get vertex index from triangle index and closest point in triangle + closest = numpy.argmax(barycentric, axis=1) + + if mode == 'triangles': + indices = trianglesIndices * 3 + closest + + elif mode == 'triangle_strip': + indices = trianglesIndices + closest + + elif mode == 'fan': + indices = trianglesIndices + closest # For corners 1 and 2 + indices[closest == 0] = 0 # For first corner (common) + + return PickingResult(self, + positions=points, + indices=indices, + fetchdata=self.getPositionData) + class _CylindricalVolume(DataItem3D): """Class that represents a volume with a rotational symmetry along z @@ -155,6 +223,18 @@ class _CylindricalVolume(DataItem3D): def __init__(self, parent=None): DataItem3D.__init__(self, parent=parent) self._mesh = None + self._nbFaces = 0 + + def getPosition(self, copy=True): + """Get primitive positions. + + :param bool copy: + True (default) to get a copy, + False to get internal representation (do not modify!). + :return: Position of the primitives as a (N, 3) array. + :rtype: numpy.ndarray + """ + raise NotImplementedError("Must be implemented in subclass") def _setData(self, position, radius, height, angles, color, flatFaces, rotation): @@ -173,30 +253,31 @@ class _CylindricalVolume(DataItem3D): self._getScenePrimitive().children = [] # Remove any previous mesh if position is None or len(position) == 0: - self._mesh = 0 + self._mesh = None + self._nbFaces = 0 else: + self._nbFaces = len(angles) - 1 + volume = numpy.empty(shape=(len(angles) - 1, 12, 3), dtype=numpy.float32) normal = numpy.empty(shape=(len(angles) - 1, 12, 3), dtype=numpy.float32) for i in range(0, len(angles) - 1): - """ - c6 - /\ - / \ - / \ - c4|------|c5 - | \ | - | \ | - | \ | - | \ | - c2|------|c3 - \ / - \ / - \/ - c1 - """ + # c6 + # /\ + # / \ + # / \ + # c4|------|c5 + # | \ | + # | \ | + # | \ | + # | \ | + # c2|------|c3 + # \ / + # \ / + # \/ + # c1 c1 = numpy.array([0, 0, -height/2]) c1 = rotation.transformPoint(c1) c2 = numpy.array([radius * numpy.cos(angles[i]), @@ -266,6 +347,49 @@ class _CylindricalVolume(DataItem3D): self.sigItemChanged.emit(ItemChangedType.DATA) + 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] + """ + if self._mesh is None or self._nbFaces == 0: + return None + + rayObject = context.getPickingSegment(frame=self._getScenePrimitive()) + if rayObject is None: # No picking outside viewport + return None + rayObject = rayObject[:, :3] + + positions = self._mesh.getAttribute('position', copy=False) + triangles = positions.reshape(-1, 3, 3) # 'triangle' draw mode + + trianglesIndices, t = utils.segmentTrianglesIntersection( + rayObject, triangles)[:2] + + if len(trianglesIndices) == 0: + return None + + # Get object index from triangle index + indices = trianglesIndices // (4 * self._nbFaces) + + # Select closest intersection point for each primitive + indices, firstIndices = numpy.unique(indices, return_index=True) + t = t[firstIndices] + + # Resort along t as result of numpy.unique is not sorted by t + sortedIndices = numpy.argsort(t) + t = t[sortedIndices] + indices = indices[sortedIndices] + + points = t.reshape(-1, 1) * (rayObject[1] - rayObject[0]) + rayObject[0] + + return PickingResult(self, + positions=points, + indices=indices, + fetchdata=self.getPosition) + class Box(_CylindricalVolume): """Description of a box. |