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.py281
1 files changed, 205 insertions, 76 deletions
diff --git a/silx/gui/plot3d/items/mesh.py b/silx/gui/plot3d/items/mesh.py
index 21936ea..d3f5e38 100644
--- a/silx/gui/plot3d/items/mesh.py
+++ b/silx/gui/plot3d/items/mesh.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2017-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2017-2019 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
@@ -35,17 +35,18 @@ __date__ = "17/07/2018"
import logging
import numpy
-from ..scene import primitives, utils
+from ..scene import primitives, utils, function
from ..scene.transform import Rotate
from .core import DataItem3D, ItemChangedType
+from .mixins import ColormapMixIn
from ._pick import PickingResult
_logger = logging.getLogger(__name__)
-class Mesh(DataItem3D):
- """Description of mesh.
+class _MeshBase(DataItem3D):
+ """Base class for :class:`Mesh' and :class:`ColormapMesh`.
:param parent: The View widget this item belongs to.
"""
@@ -54,48 +55,22 @@ class Mesh(DataItem3D):
DataItem3D.__init__(self, parent=parent)
self._mesh = None
- def setData(self,
- position,
- color,
- normal=None,
- mode='triangles',
- copy=True):
- """Set mesh geometry data.
-
- Supported drawing modes are: 'triangles', 'triangle_strip', 'fan'
+ def _setMesh(self, mesh):
+ """Set mesh primitive
- :param numpy.ndarray position:
- Position (x, y, z) of each vertex as a (N, 3) array
- :param numpy.ndarray color: Colors for each point or a single color
- :param numpy.ndarray normal: Normals for each point or None (default)
- :param str mode: The drawing mode.
- :param bool copy: True (default) to copy the data,
- False to use as is (do not modify!).
+ :param Union[None,Geometry] mesh: The scene primitive
"""
self._getScenePrimitive().children = [] # Remove any previous mesh
- if position is None or len(position) == 0:
- self._mesh = None
- else:
- self._mesh = primitives.Mesh3D(
- position, color, normal, mode=mode, copy=copy)
+ self._mesh = mesh
+ if self._mesh is not None:
self._getScenePrimitive().children.append(self._mesh)
- self.sigItemChanged.emit(ItemChangedType.DATA)
+ self._updated(ItemChangedType.DATA)
- def getData(self, copy=True):
- """Get the mesh geometry.
-
- :param bool copy:
- True (default) to get a copy,
- False to get internal representation (do not modify!).
- :return: The positions, colors, normals and mode
- :rtype: tuple of numpy.ndarray
- """
- return (self.getPositionData(copy=copy),
- self.getColorData(copy=copy),
- self.getNormalData(copy=copy),
- self.getDrawMode())
+ def _getMesh(self):
+ """Returns the underlying Mesh scene primitive"""
+ return self._mesh
def getPositionData(self, copy=True):
"""Get the mesh vertex positions.
@@ -106,38 +81,38 @@ class Mesh(DataItem3D):
:return: The (x, y, z) positions as a (N, 3) array
:rtype: numpy.ndarray
"""
- if self._mesh is None:
+ if self._getMesh() is None:
return numpy.empty((0, 3), dtype=numpy.float32)
else:
- return self._mesh.getAttribute('position', copy=copy)
+ return self._getMesh().getAttribute('position', copy=copy)
- def getColorData(self, copy=True):
- """Get the mesh vertex colors.
+ def getNormalData(self, copy=True):
+ """Get the mesh vertex normals.
:param bool copy:
True (default) to get a copy,
False to get internal representation (do not modify!).
- :return: The RGBA colors as a (N, 4) array or a single color
- :rtype: numpy.ndarray
+ :return: The normals as a (N, 3) array, a single normal or None
+ :rtype: Union[numpy.ndarray,None]
"""
- if self._mesh is None:
- return numpy.empty((0, 4), dtype=numpy.float32)
+ if self._getMesh() is None:
+ return None
else:
- return self._mesh.getAttribute('color', copy=copy)
+ return self._getMesh().getAttribute('normal', copy=copy)
- def getNormalData(self, copy=True):
- """Get the mesh vertex normals.
+ def getIndices(self, copy=True):
+ """Get the vertex indices.
:param bool copy:
True (default) to get a copy,
False to get internal representation (do not modify!).
- :return: The normals as a (N, 3) array, a single normal or None
- :rtype: numpy.ndarray or None
+ :return: The vertex indices as an array or None.
+ :rtype: Union[numpy.ndarray,None]
"""
- if self._mesh is None:
+ if self._getMesh() is None:
return None
else:
- return self._mesh.getAttribute('normal', copy=copy)
+ return self._getMesh().getIndices(copy=copy)
def getDrawMode(self):
"""Get mesh rendering mode.
@@ -145,7 +120,7 @@ class Mesh(DataItem3D):
:return: The drawing mode of this primitive
:rtype: str
"""
- return self._mesh.drawMode
+ return self._getMesh().drawMode
def _pickFull(self, context):
"""Perform precise picking in this item at given widget position.
@@ -164,28 +139,34 @@ class Mesh(DataItem3D):
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:]
+ vertexIndices = self.getIndices(copy=False)
+ if vertexIndices is not None: # Expand indices
+ positions = utils.unindexArrays(mode, vertexIndices, positions)[0]
+ triangles = positions.reshape(-1, 3, 3)
else:
- _logger.warning("Unsupported draw mode: %s" % mode)
- return None
+ 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)
@@ -208,12 +189,160 @@ class Mesh(DataItem3D):
indices = trianglesIndices + closest # For corners 1 and 2
indices[closest == 0] = 0 # For first corner (common)
+ if vertexIndices is not None:
+ # Convert from indices in expanded triangles to input vertices
+ indices = vertexIndices[indices]
+
return PickingResult(self,
positions=points,
indices=indices,
fetchdata=self.getPositionData)
+class Mesh(_MeshBase):
+ """Description of mesh.
+
+ :param parent: The View widget this item belongs to.
+ """
+
+ def __init__(self, parent=None):
+ _MeshBase.__init__(self, parent=parent)
+
+ def setData(self,
+ position,
+ color,
+ normal=None,
+ mode='triangles',
+ indices=None,
+ copy=True):
+ """Set mesh geometry data.
+
+ Supported drawing modes are: 'triangles', 'triangle_strip', 'fan'
+
+ :param numpy.ndarray position:
+ Position (x, y, z) of each vertex as a (N, 3) array
+ :param numpy.ndarray color: Colors for each point or a single color
+ :param Union[numpy.ndarray,None] normal: Normals for each point or None (default)
+ :param str mode: The drawing mode.
+ :param Union[List[int],None] indices:
+ Array of vertex indices or None to use arrays directly.
+ :param bool copy: True (default) to copy the data,
+ False to use as is (do not modify!).
+ """
+ assert mode in ('triangles', 'triangle_strip', 'fan')
+ if position is None or len(position) == 0:
+ mesh = None
+ else:
+ mesh = primitives.Mesh3D(
+ position, color, normal, mode=mode, indices=indices, copy=copy)
+ self._setMesh(mesh)
+
+ def getData(self, copy=True):
+ """Get the mesh geometry.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get internal representation (do not modify!).
+ :return: The positions, colors, normals and mode
+ :rtype: tuple of numpy.ndarray
+ """
+ return (self.getPositionData(copy=copy),
+ self.getColorData(copy=copy),
+ self.getNormalData(copy=copy),
+ self.getDrawMode())
+
+ def getColorData(self, copy=True):
+ """Get the mesh vertex colors.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get internal representation (do not modify!).
+ :return: The RGBA colors as a (N, 4) array or a single color
+ :rtype: numpy.ndarray
+ """
+ if self._getMesh() is None:
+ return numpy.empty((0, 4), dtype=numpy.float32)
+ else:
+ return self._getMesh().getAttribute('color', copy=copy)
+
+
+class ColormapMesh(_MeshBase, ColormapMixIn):
+ """Description of mesh which color is defined by scalar and a colormap.
+
+ :param parent: The View widget this item belongs to.
+ """
+
+ def __init__(self, parent=None):
+ _MeshBase.__init__(self, parent=parent)
+ ColormapMixIn.__init__(self, function.Colormap())
+
+ def setData(self,
+ position,
+ value,
+ normal=None,
+ mode='triangles',
+ indices=None,
+ copy=True):
+ """Set mesh geometry data.
+
+ Supported drawing modes are: 'triangles', 'triangle_strip', 'fan'
+
+ :param numpy.ndarray position:
+ Position (x, y, z) of each vertex as a (N, 3) array
+ :param numpy.ndarray value: Data value for each vertex.
+ :param Union[numpy.ndarray,None] normal: Normals for each point or None (default)
+ :param str mode: The drawing mode.
+ :param Union[List[int],None] indices:
+ Array of vertex indices or None to use arrays directly.
+ :param bool copy: True (default) to copy the data,
+ False to use as is (do not modify!).
+ """
+ assert mode in ('triangles', 'triangle_strip', 'fan')
+ if position is None or len(position) == 0:
+ mesh = None
+ else:
+ mesh = primitives.ColormapMesh3D(
+ position=position,
+ value=numpy.array(value, copy=False).reshape(-1, 1), # Make it a 2D array
+ colormap=self._getSceneColormap(),
+ normal=normal,
+ mode=mode,
+ indices=indices,
+ copy=copy)
+ self._setMesh(mesh)
+
+ # Store data range info
+ ColormapMixIn._setRangeFromData(self, self.getValueData(copy=False))
+
+ def getData(self, copy=True):
+ """Get the mesh geometry.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get internal representation (do not modify!).
+ :return: The positions, values, normals and mode
+ :rtype: tuple of numpy.ndarray
+ """
+ return (self.getPositionData(copy=copy),
+ self.getValueData(copy=copy),
+ self.getNormalData(copy=copy),
+ self.getDrawMode())
+
+ def getValueData(self, copy=True):
+ """Get the mesh vertex values.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get internal representation (do not modify!).
+ :return: Array of data values
+ :rtype: numpy.ndarray
+ """
+ if self._getMesh() is None:
+ return numpy.empty((0,), dtype=numpy.float32)
+ else:
+ return self._getMesh().getAttribute('value', copy=copy)
+
+
class _CylindricalVolume(DataItem3D):
"""Class that represents a volume with a rotational symmetry along z
@@ -345,7 +474,7 @@ class _CylindricalVolume(DataItem3D):
vertices, color, normals, mode='triangles', copy=False)
self._getScenePrimitive().children.append(self._mesh)
- self.sigItemChanged.emit(ItemChangedType.DATA)
+ self._updated(ItemChangedType.DATA)
def _pickFull(self, context):
"""Perform precise picking in this item at given widget position.