summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/items/mesh.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/items/mesh.py')
-rw-r--r--silx/gui/plot3d/items/mesh.py176
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.