Пример #1
0
class MeshData:
    def __init__(self, vertices=None, normals=None, indices=None, colors=None, uvs=None, file_name=None,
                 center_position=None, zero_position=None, type = MeshType.faces, attributes=None):
        self._vertices = NumPyUtil.immutableNDArray(vertices)
        self._normals = NumPyUtil.immutableNDArray(normals)
        self._indices = NumPyUtil.immutableNDArray(indices)
        self._colors = NumPyUtil.immutableNDArray(colors)
        self._uvs = NumPyUtil.immutableNDArray(uvs)
        self._vertex_count = len(self._vertices) if self._vertices is not None else 0
        self._face_count = len(self._indices) if self._indices is not None else 0
        self._type = type
        self._file_name = file_name  # type: str
        # original center position
        self._center_position = center_position
        # original zero position, is changed after transformation
        if zero_position is not None:
            self._zero_position = zero_position
        else:
            self._zero_position = Vector(0, 0, 0) # type: Vector
        self._convex_hull = None    # type: Optional[scipy.spatial.ConvexHull]
        self._convex_hull_vertices = None  # type: Optional[numpy.ndarray]
        self._convex_hull_lock = threading.Lock()

        self._attributes = {}
        if attributes is not None:
            for key, attribute in attributes.items():
                new_value = {}
                for attribute_key, attribute_value in attribute.items():
                    if attribute_key == "value":
                        new_value["value"] = NumPyUtil.immutableNDArray(attribute_value)
                    else:
                        new_value[attribute_key] = attribute_value
                self._attributes[key] = new_value

    ## Create a new MeshData with specified changes
    #   \return \type{MeshData}
    def set(self, vertices=Reuse, normals=Reuse, indices=Reuse, colors=Reuse, uvs=Reuse, file_name=Reuse,
            center_position=Reuse, zero_position=Reuse, attributes=Reuse) -> "MeshData":
        vertices = vertices if vertices is not Reuse else self._vertices
        normals = normals if normals is not Reuse else self._normals
        indices = indices if indices is not Reuse else self._indices
        colors = colors if colors is not Reuse else self._colors
        uvs = uvs if uvs is not Reuse else self._uvs
        file_name = file_name if file_name is not Reuse else self._file_name
        center_position = center_position if center_position is not Reuse else self._center_position
        zero_position = zero_position if zero_position is not Reuse else self._zero_position
        attributes = attributes if attributes is not Reuse else self._attributes

        return MeshData(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,
                        file_name=file_name, center_position=center_position, zero_position=zero_position, attributes=attributes)

    def getHash(self):
        m = hashlib.sha256()
        m.update(self.getVerticesAsByteArray())
        return m.hexdigest()

    def getCenterPosition(self) -> Vector:
        return self._center_position

    def getZeroPosition(self) -> Vector:
        return self._zero_position

    def getType(self):
        return self._type

    def getFaceCount(self) -> int:
        return self._face_count

    ##  Get the array of vertices
    def getVertices(self) -> numpy.ndarray:
        return self._vertices

    ##  Get the number of vertices
    def getVertexCount(self) -> int:
        return self._vertex_count

    ##  Get a vertex by index
    def getVertex(self, index):
        try:
            return self._vertices[index]
        except IndexError:
            return None

    ##  Return whether this mesh has vertex normals.
    def hasNormals(self) -> bool:
        return self._normals is not None

    ##  Return the list of vertex normals.
    def getNormals(self) -> numpy.ndarray:
        return self._normals

    ##  Return whether this mesh has indices.
    def hasIndices(self) -> bool:
        return self._indices is not None

    ##  Get the array of indices
    #   \return \type{numpy.ndarray}
    def getIndices(self) -> numpy.ndarray:
        return self._indices

    def hasColors(self) -> bool:
        return self._colors is not None

    def getColors(self) -> numpy.ndarray:
        return self._colors

    def hasUVCoordinates(self) -> bool:
        return self._uvs is not None

    def getFileName(self) -> str:
        return self._file_name

    ##  Transform the meshdata, center and zero position by given Matrix
    #   \param transformation 4x4 homogenous transformation matrix
    def getTransformed(self, transformation: Matrix) -> "MeshData":
        if self._vertices is not None:
            transformed_vertices = transformVertices(self._vertices, transformation)
            transformed_normals = transformNormals(self._normals, transformation) if self._normals is not None else None

            transformation_matrix = transformation.getTransposed()
            if self._center_position is not None:
                center_position = self._center_position.multiply(transformation_matrix)
            else:
                center_position = Reuse
            zero_position = self._zero_position.multiply(transformation_matrix)

            return self.set(vertices=transformed_vertices, normals=transformed_normals, center_position=center_position, zero_position=zero_position)
        else:
            return MeshData(vertices = self._vertices)

    ##  Get the extents of this mesh.
    #
    #   \param matrix The transformation matrix from model to world coordinates.
    def getExtents(self, matrix: Optional[Matrix] = None) -> Optional[AxisAlignedBox]:
        if self._vertices is None:
            return None

        data = numpy.pad(self.getConvexHullVertices(), ((0, 0), (0, 1)), "constant", constant_values=(0.0, 1.0))

        if matrix is not None:
            transposed = matrix.getTransposed().getData()
            data = data.dot(transposed)
            data += transposed[:, 3]
            data = data[:, 0:3]

        min = data.min(axis=0)
        max = data.max(axis=0)

        return AxisAlignedBox(minimum=Vector(min[0], min[1], min[2]), maximum=Vector(max[0], max[1], max[2]))

    ##  Get all vertices of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per vertex.
    def getVerticesAsByteArray(self) -> Optional[bytes]:
        if self._vertices is None:
            return None
        return self._vertices.tostring()

    ##  Get all normals of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per normal.
    def getNormalsAsByteArray(self) -> Optional[bytes]:
        if self._normals is None:
            return None
        return self._normals.tostring()

    ##  Get all indices as a bytearray
    #
    #   \return A bytearray object with 3 ints per face.
    def getIndicesAsByteArray(self) -> Optional[bytes]:
        if self._indices is None:
            return None
        return self._indices.tostring()

    def getColorsAsByteArray(self) -> Optional[bytes]:
        if self._colors is None:
            return None
        return self._colors.tostring()

    def getUVCoordinatesAsByteArray(self) -> Optional[bytes]:
        if self._uvs is None:
            return None
        return self._uvs.tostring()

    #######################################################################
    # Convex hull handling
    #######################################################################
    def _computeConvexHull(self):
        points = self.getVertices()
        if points is None:
            return
        self._convex_hull = approximateConvexHull(points, MAXIMUM_HULL_VERTICES_COUNT)

    ##  Gets the Convex Hull of this mesh
    #
    #    \return \type{scipy.spatial.ConvexHull}
    def getConvexHull(self) -> Optional[scipy.spatial.ConvexHull]:
        with self._convex_hull_lock:
            if self._convex_hull is None:
                self._computeConvexHull()
            return self._convex_hull

    ##  Gets the convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullVertices(self) -> numpy.ndarray:
        if self._convex_hull_vertices is None:
            convex_hull = self.getConvexHull()
            self._convex_hull_vertices = numpy.take(convex_hull.points, convex_hull.vertices, axis=0)
        return self._convex_hull_vertices

    ##  Gets transformed convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullTransformedVertices(self, transformation: Matrix) -> Optional[numpy.ndarray]:
        vertices = self.getConvexHullVertices()
        if vertices is not None:
            return transformVertices(vertices, transformation)
        else:
            return None

    def hasAttribute(self, key: str) -> bool:
        return key in self._attributes

    ##  the return value is a dict with at least keys opengl_name, opengl_type, value
    def getAttribute(self, key: str):
        return self._attributes[key]

    ##  Return attribute names in alphabetical order
    #   The sorting assures that the order is always the same.
    def attributeNames(self) -> List[str]:
        result = list(self._attributes.keys())
        result.sort()
        return result

    def toString(self) -> str:
        return "MeshData(_vertices=" + str(self._vertices) + ", _normals=" + str(self._normals) + ", _indices=" + \
               str(self._indices) + ", _colors=" + str(self._colors) + ", _uvs=" + str(self._uvs) + ", _attributes=" + \
               str(self._attributes.keys()) + ") "
Пример #2
0
class MeshData:
    def __init__(self,
                 vertices=None,
                 normals=None,
                 indices=None,
                 colors=None,
                 uvs=None,
                 file_name=None,
                 center_position=None,
                 zero_position=None,
                 type=MeshType.faces,
                 attributes=None):
        self._application = None  # Initialize this later otherwise unit tests break

        self._vertices = NumPyUtil.immutableNDArray(vertices)
        self._normals = NumPyUtil.immutableNDArray(normals)
        self._indices = NumPyUtil.immutableNDArray(indices)
        self._colors = NumPyUtil.immutableNDArray(colors)
        self._uvs = NumPyUtil.immutableNDArray(uvs)
        self._vertex_count = len(
            self._vertices) if self._vertices is not None else 0
        self._face_count = len(
            self._indices) if self._indices is not None else 0
        self._type = type
        self._file_name = file_name  # type: Optional[str]
        # original center position
        self._center_position = center_position
        # original zero position, is changed after transformation
        if zero_position is not None:
            self._zero_position = zero_position
        else:
            self._zero_position = Vector(0, 0, 0)  # type: Vector
        self._convex_hull = None  # type: Optional[scipy.spatial.ConvexHull]
        self._convex_hull_vertices = None  # type: Optional[numpy.ndarray]
        self._convex_hull_lock = threading.Lock()

        self._attributes = {}
        if attributes is not None:
            for key, attribute in attributes.items():
                new_value = {}
                for attribute_key, attribute_value in attribute.items():
                    if attribute_key == "value":
                        new_value["value"] = NumPyUtil.immutableNDArray(
                            attribute_value)
                    else:
                        new_value[attribute_key] = attribute_value
                self._attributes[key] = new_value

    ##  Triggered when this file is deleted.
    #
    #   The file will then no longer be watched for changes.
    def __del__(self):
        if self._file_name:
            if self._application:
                self._application.getController().getScene().removeWatchedFile(
                    self._file_name)

    ## Create a new MeshData with specified changes
    #   \return \type{MeshData}
    def set(self,
            vertices=Reuse,
            normals=Reuse,
            indices=Reuse,
            colors=Reuse,
            uvs=Reuse,
            file_name=Reuse,
            center_position=Reuse,
            zero_position=Reuse,
            attributes=Reuse) -> "MeshData":
        vertices = vertices if vertices is not Reuse else self._vertices
        normals = normals if normals is not Reuse else self._normals
        indices = indices if indices is not Reuse else self._indices
        colors = colors if colors is not Reuse else self._colors
        uvs = uvs if uvs is not Reuse else self._uvs
        file_name = file_name if file_name is not Reuse else self._file_name
        center_position = center_position if center_position is not Reuse else self._center_position
        zero_position = zero_position if zero_position is not Reuse else self._zero_position
        attributes = attributes if attributes is not Reuse else self._attributes

        return MeshData(vertices=vertices,
                        normals=normals,
                        indices=indices,
                        colors=colors,
                        uvs=uvs,
                        file_name=file_name,
                        center_position=center_position,
                        zero_position=zero_position,
                        attributes=attributes)

    def getHash(self):
        m = hashlib.sha256()
        m.update(self.getVerticesAsByteArray())
        return m.hexdigest()

    def getCenterPosition(self) -> Vector:
        return self._center_position

    def getZeroPosition(self) -> Vector:
        return self._zero_position

    def getType(self):
        return self._type

    def getFaceCount(self) -> int:
        return self._face_count

    ##  Get the array of vertices
    def getVertices(self) -> numpy.ndarray:
        return self._vertices

    ##  Get the number of vertices
    def getVertexCount(self) -> int:
        return self._vertex_count

    ##  Get a vertex by index
    def getVertex(self, index):
        try:
            return self._vertices[index]
        except IndexError:
            return None

    ##  Return whether this mesh has vertex normals.
    def hasNormals(self) -> bool:
        return self._normals is not None

    ##  Return the list of vertex normals.
    def getNormals(self) -> numpy.ndarray:
        return self._normals

    ##  Return whether this mesh has indices.
    def hasIndices(self) -> bool:
        return self._indices is not None

    ##  Get the array of indices
    #   \return \type{numpy.ndarray}
    def getIndices(self) -> numpy.ndarray:
        return self._indices

    def hasColors(self) -> bool:
        return self._colors is not None

    def getColors(self) -> numpy.ndarray:
        return self._colors

    def hasUVCoordinates(self) -> bool:
        return self._uvs is not None

    def getFileName(self) -> Optional[str]:
        return self._file_name

    ##  Transform the meshdata, center and zero position by given Matrix
    #   \param transformation 4x4 homogenous transformation matrix
    def getTransformed(self, transformation: Matrix) -> "MeshData":
        if self._vertices is not None:
            transformed_vertices = transformVertices(self._vertices,
                                                     transformation)
            transformed_normals = transformNormals(
                self._normals,
                transformation) if self._normals is not None else None

            transformation_matrix = transformation.getTransposed()
            if self._center_position is not None:
                center_position = self._center_position.multiply(
                    transformation_matrix)
            else:
                center_position = Reuse
            zero_position = self._zero_position.multiply(transformation_matrix)

            return self.set(vertices=transformed_vertices,
                            normals=transformed_normals,
                            center_position=center_position,
                            zero_position=zero_position)
        else:
            return MeshData(vertices=self._vertices)

    ##  Get the extents of this mesh.
    #
    #   \param matrix The transformation matrix from model to world coordinates.
    def getExtents(self,
                   matrix: Optional[Matrix] = None
                   ) -> Optional[AxisAlignedBox]:
        if self._vertices is None:
            return None

        if matrix is not None:
            data = self.getConvexHullTransformedVertices(matrix)
        else:
            data = self.getConvexHullVertices()

        if data is None:
            return None

        min = data.min(axis=0)
        max = data.max(axis=0)

        return AxisAlignedBox(minimum=Vector(min[0], min[1], min[2]),
                              maximum=Vector(max[0], max[1], max[2]))

    ##  Get all vertices of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per vertex.
    def getVerticesAsByteArray(self) -> Optional[bytes]:
        if self._vertices is None:
            return None
        return self._vertices.tostring()

    ##  Get all normals of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per normal.
    def getNormalsAsByteArray(self) -> Optional[bytes]:
        if self._normals is None:
            return None
        return self._normals.tostring()

    ##  Get all indices as a bytearray
    #
    #   \return A bytearray object with 3 ints per face.
    def getIndicesAsByteArray(self) -> Optional[bytes]:
        if self._indices is None:
            return None
        return self._indices.tostring()

    def getColorsAsByteArray(self) -> Optional[bytes]:
        if self._colors is None:
            return None
        return self._colors.tostring()

    def getUVCoordinatesAsByteArray(self) -> Optional[bytes]:
        if self._uvs is None:
            return None
        return self._uvs.tostring()

    #######################################################################
    # Convex hull handling
    #######################################################################
    def _computeConvexHull(self):
        points = self.getVertices()
        if points is None:
            return
        self._convex_hull = approximateConvexHull(points,
                                                  MAXIMUM_HULL_VERTICES_COUNT)

    ##  Gets the Convex Hull of this mesh
    #
    #    \return \type{scipy.spatial.ConvexHull}
    def getConvexHull(self) -> Optional[scipy.spatial.ConvexHull]:
        with self._convex_hull_lock:
            if self._convex_hull is None:
                self._computeConvexHull()
            return self._convex_hull

    ##  Gets the convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullVertices(self) -> Optional[numpy.ndarray]:
        if self._convex_hull_vertices is None:
            convex_hull = self.getConvexHull()
            if convex_hull is None:
                return None
            self._convex_hull_vertices = numpy.take(convex_hull.points,
                                                    convex_hull.vertices,
                                                    axis=0)
        return self._convex_hull_vertices

    ##  Gets transformed convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullTransformedVertices(
            self, transformation: Matrix) -> Optional[numpy.ndarray]:
        vertices = self.getConvexHullVertices()
        if vertices is not None:
            return transformVertices(vertices, transformation)
        else:
            return None

    ##  Gets the plane the supplied face lies in. The resultant plane is specified by a point and a normal.
    #
    #   \param face_id \type{int} The index of the face (not the flattened indices).
    #   \return \type{Tuple[numpy.ndarray, numpy.ndarray]} A plane, the 1st vector is the center, the 2nd the normal.
    def getFacePlane(self,
                     face_id: int) -> Tuple[numpy.ndarray, numpy.ndarray]:
        if not self._indices or len(self._indices) == 0:
            base_index = face_id * 3
            v_a = self._vertices[base_index]
            v_b = self._vertices[base_index + 1]
            v_c = self._vertices[base_index + 2]
        else:
            v_a = self._vertices[self._indices[face_id][0]]
            v_b = self._vertices[self._indices[face_id][1]]
            v_c = self._vertices[self._indices[face_id][2]]
        in_point = (v_a + v_b + v_c) / 3.0
        face_normal = numpy.cross(v_b - v_a, v_c - v_a)
        return in_point, face_normal

    def hasAttribute(self, key: str) -> bool:
        return key in self._attributes

    ##  the return value is a dict with at least keys opengl_name, opengl_type, value
    def getAttribute(self, key: str):
        return self._attributes[key]

    ##  Return attribute names in alphabetical order
    #   The sorting assures that the order is always the same.
    def attributeNames(self) -> List[str]:
        result = list(self._attributes.keys())
        result.sort()
        return result

    def toString(self) -> str:
        return "MeshData(_vertices=" + str(self._vertices) + ", _normals=" + str(self._normals) + ", _indices=" + \
               str(self._indices) + ", _colors=" + str(self._colors) + ", _uvs=" + str(self._uvs) + ", _attributes=" + \
               str(self._attributes.keys()) + ") "
Пример #3
0
class MeshData:
    """Class to hold a list of verts and possibly how (and if) they are connected.

    This class stores three numpy arrays that contain the data for a mesh. Vertices
    are stored as a two-dimensional array of floats with the rows being individual
    vertices and the three columns being the X, Y and Z components of the vertices.
    Normals are stored in the same manner and kept in sync with the vertices. Indices
    are stored as a two-dimensional array of integers with the rows being the individual
    faces and the three columns being the indices that refer to the individual vertices.

    attributes: a dict with {"value", "opengl_type", "opengl_name"} type in vector2f, vector3f, uniforms, ...
    """
    def __init__(self,
                 vertices=None,
                 normals=None,
                 indices=None,
                 colors=None,
                 uvs=None,
                 file_name=None,
                 center_position=None,
                 zero_position=None,
                 type=MeshType.faces,
                 attributes=None) -> None:
        self._application = None  # Initialize this later otherwise unit tests break

        self._vertices = NumPyUtil.immutableNDArray(vertices)
        self._normals = NumPyUtil.immutableNDArray(normals)
        self._indices = NumPyUtil.immutableNDArray(indices)
        self._colors = NumPyUtil.immutableNDArray(colors)
        self._uvs = NumPyUtil.immutableNDArray(uvs)
        self._vertex_count = len(
            self._vertices) if self._vertices is not None else 0
        self._face_count = len(
            self._indices) if self._indices is not None else 0
        self._type = type
        self._file_name = file_name  # type: Optional[str]
        # original center position
        self._center_position = center_position
        # original zero position, is changed after transformation
        if zero_position is not None:
            self._zero_position = zero_position
        else:
            self._zero_position = Vector(0, 0, 0)
        self._convex_hull = None  # type: Optional[scipy.spatial.ConvexHull]
        self._convex_hull_vertices = None  # type: Optional[numpy.ndarray]
        self._convex_hull_lock = threading.Lock()

        self._attributes = {}  # type: Dict[str, Any]
        if attributes is not None:
            for key, attribute in attributes.items():
                new_value = {}
                for attribute_key, attribute_value in attribute.items():
                    if attribute_key == "value":
                        new_value["value"] = NumPyUtil.immutableNDArray(
                            attribute_value)
                    else:
                        new_value[attribute_key] = attribute_value
                self._attributes[key] = new_value

    def __del__(self):
        """Triggered when this file is deleted.

        The file will then no longer be watched for changes.
        """

        if self._file_name:
            if self._application:
                self._application.getController().getScene().removeWatchedFile(
                    self._file_name)

    def set(self,
            vertices=Reuse,
            normals=Reuse,
            indices=Reuse,
            colors=Reuse,
            uvs=Reuse,
            file_name=Reuse,
            center_position=Reuse,
            zero_position=Reuse,
            attributes=Reuse) -> "MeshData":
        """Create a new MeshData with specified changes

        :return: :type{MeshData}
        """

        vertices = vertices if vertices is not Reuse else self._vertices
        normals = normals if normals is not Reuse else self._normals
        indices = indices if indices is not Reuse else self._indices
        colors = colors if colors is not Reuse else self._colors
        uvs = uvs if uvs is not Reuse else self._uvs
        file_name = file_name if file_name is not Reuse else self._file_name
        center_position = center_position if center_position is not Reuse else self._center_position
        zero_position = zero_position if zero_position is not Reuse else self._zero_position
        attributes = attributes if attributes is not Reuse else self._attributes

        return MeshData(vertices=vertices,
                        normals=normals,
                        indices=indices,
                        colors=colors,
                        uvs=uvs,
                        file_name=file_name,
                        center_position=center_position,
                        zero_position=zero_position,
                        attributes=attributes)

    def getHash(self):
        m = hashlib.sha256()
        m.update(self.getVerticesAsByteArray())
        return m.hexdigest()

    def getCenterPosition(self) -> Vector:
        return self._center_position

    def getZeroPosition(self) -> Vector:
        return self._zero_position

    def getType(self):
        return self._type

    def getFaceCount(self) -> int:
        return self._face_count

    def getVertices(self) -> numpy.ndarray:
        """Get the array of vertices"""

        return self._vertices

    def getVertexCount(self) -> int:
        """Get the number of vertices"""

        return self._vertex_count

    def getVertex(self, index):
        """Get a vertex by index"""

        try:
            return self._vertices[index]
        except IndexError:
            return None

    def hasNormals(self) -> bool:
        """Return whether this mesh has vertex normals."""

        return self._normals is not None

    def getNormals(self) -> numpy.ndarray:
        """Return the list of vertex normals."""

        return self._normals

    def hasIndices(self) -> bool:
        """Return whether this mesh has indices."""

        return self._indices is not None

    def getIndices(self) -> numpy.ndarray:
        """Get the array of indices

        :return: :type{numpy.ndarray}
        """

        if self._indices is not None and self._indices.dtype != "int32":
            self._indices = numpy.asarray(self._indices, dtype=numpy.int32)
        return self._indices

    def hasColors(self) -> bool:
        return self._colors is not None

    def getColors(self) -> numpy.ndarray:
        return self._colors

    def hasUVCoordinates(self) -> bool:
        return self._uvs is not None

    def getFileName(self) -> Optional[str]:
        return self._file_name

    def getTransformed(self, transformation: Matrix) -> "MeshData":
        """Transform the meshdata, center and zero position by given Matrix

        :param transformation: 4x4 homogeneous transformation matrix
        """

        if self._vertices is not None:
            transformed_vertices = transformVertices(self._vertices,
                                                     transformation)
            transformed_normals = transformNormals(
                self._normals,
                transformation) if self._normals is not None else None

            transformation_matrix = transformation.getTransposed()
            if self._center_position is not None:
                center_position = self._center_position.multiply(
                    transformation_matrix)
            else:
                center_position = Reuse
            zero_position = self._zero_position.multiply(transformation_matrix)

            return self.set(vertices=transformed_vertices,
                            normals=transformed_normals,
                            center_position=center_position,
                            zero_position=zero_position)
        else:
            return MeshData(vertices=self._vertices)

    def getExtents(self,
                   matrix: Optional[Matrix] = None
                   ) -> Optional[AxisAlignedBox]:
        """Get the extents of this mesh.

        :param matrix: The transformation matrix from model to world coordinates.
        """

        if self._vertices is None:
            return None

        if matrix is not None:
            data = self.getConvexHullTransformedVertices(matrix)
        else:
            data = self.getConvexHullVertices()

        if data is None:
            return None

        min = data.min(axis=0)
        max = data.max(axis=0)

        return AxisAlignedBox(minimum=Vector(min[0], min[1], min[2]),
                              maximum=Vector(max[0], max[1], max[2]))

    def getVerticesAsByteArray(self) -> Optional[bytes]:
        """Get all vertices of this mesh as a bytearray

        :return: A bytearray object with 3 floats per vertex.
        """

        if self._vertices is None:
            return None
        return self._vertices.tostring()

    def getNormalsAsByteArray(self) -> Optional[bytes]:
        """Get all normals of this mesh as a bytearray

        :return: A bytearray object with 3 floats per normal.
        """

        if self._normals is None:
            return None
        return self._normals.tostring()

    def getIndicesAsByteArray(self) -> Optional[bytes]:
        """Get all indices as a bytearray

        :return: A bytearray object with 3 ints per face.
        """

        if self._indices is None:
            return None
        return self._indices.tostring()

    def getColorsAsByteArray(self) -> Optional[bytes]:
        if self._colors is None:
            return None
        return self._colors.tostring()

    def getUVCoordinatesAsByteArray(self) -> Optional[bytes]:
        if self._uvs is None:
            return None
        return self._uvs.tostring()

    def _computeConvexHull(self) -> None:
        """Convex hull handling"""

        points = self.getVertices()
        if points is None:
            return
        self._convex_hull = approximateConvexHull(points,
                                                  MAXIMUM_HULL_VERTICES_COUNT)

    def getConvexHull(self) -> Optional[scipy.spatial.ConvexHull]:
        """Gets the Convex Hull of this mesh

        :return: :type{scipy.spatial.ConvexHull}
        """

        with self._convex_hull_lock:
            if self._convex_hull is None:
                self._computeConvexHull()
            return self._convex_hull

    def getConvexHullVertices(self) -> Optional[numpy.ndarray]:
        """Gets the convex hull points

        :return: :type{numpy.ndarray} the vertices which describe the convex hull
        """

        if self._convex_hull_vertices is None:
            convex_hull = self.getConvexHull()
            if convex_hull is None:
                return None
            self._convex_hull_vertices = numpy.take(convex_hull.points,
                                                    convex_hull.vertices,
                                                    axis=0)
        return self._convex_hull_vertices

    def getConvexHullTransformedVertices(
            self, transformation: Matrix) -> Optional[numpy.ndarray]:
        """Gets transformed convex hull points

        :return: :type{numpy.ndarray} the vertices which describe the convex hull
        """

        vertices = self.getConvexHullVertices()
        if vertices is not None:
            return transformVertices(vertices, transformation)
        else:
            return None

    def getFacePlane(self,
                     face_id: int) -> Tuple[numpy.ndarray, numpy.ndarray]:
        """Gets the plane the supplied face lies in. The resultant plane is specified by a point and a normal.

        :param face_id: :type{int} The index of the face (not the flattened indices).
        :return: :type{Tuple[numpy.ndarray, numpy.ndarray]} A plane, the 1st vector is the center, the 2nd the normal.
        """

        v_a, v_b, v_c = self.getFaceNodes(face_id)
        in_point = (v_a + v_b + v_c) / 3.0
        face_normal = numpy.cross(v_b - v_a, v_c - v_a)
        return in_point, face_normal

    def getFaceNodes(
            self, face_id: int
    ) -> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]:
        """Gets the node vectors of the supplied face.

        :param face_id: :type{int} The index of the face (not the flattened indices).
        :return: :type{Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]} Tuple of all three local vectors. 
        """

        if self._indices is None or len(self._indices) == 0:
            base_index = face_id * 3
            v_a = self._vertices[base_index]
            v_b = self._vertices[base_index + 1]
            v_c = self._vertices[base_index + 2]
        else:
            v_a = self._vertices[self._indices[face_id][0]]
            v_b = self._vertices[self._indices[face_id][1]]
            v_c = self._vertices[self._indices[face_id][2]]
        return v_a, v_b, v_c

    def hasAttribute(self, key: str) -> bool:
        return key in self._attributes

    def getAttribute(self, key: str):
        """the return value is a dict with at least keys opengl_name, opengl_type, value"""

        return self._attributes[key]

    def attributeNames(self) -> List[str]:
        """Return attribute names in alphabetical order

        The sorting assures that the order is always the same.
        """
        if not self._attributes:
            return []

        result = list(self._attributes.keys())
        result.sort()
        return result

    def invertNormals(self) -> None:
        if self._normals is not None:
            mirror = Matrix()
            mirror.setToIdentity()
            mirror.scaleByFactor(-1.0)
            self._normals = transformNormals(self._normals, mirror)
        if self._indices is not None:
            new_indices = []
            for face in self._indices:
                new_indices.append([face[1], face[0], face[2]])
            self._indices = NumPyUtil.immutableNDArray(new_indices)
            self._indices_byte_array = None
        else:
            new_vertices = []
            num_vertices = len(self._vertices)
            for i in range(0, num_vertices, 3):
                new_vertices.append(self._vertices[i + 1])
                new_vertices.append(self._vertices[i])
                new_vertices.append(self._vertices[i + 2])
            self._vertices = NumPyUtil.immutableNDArray(new_vertices)

    def toString(self) -> str:
        return "MeshData(_vertices=" + str(self._vertices) + ", _normals=" + str(self._normals) + ", _indices=" + \
               str(self._indices) + ", _colors=" + str(self._colors) + ", _uvs=" + str(self._uvs) + ", _attributes=" + \
               str(self._attributes.keys()) + ") "
Пример #4
0
class MeshData:
    def __init__(self, vertices=None, normals=None, indices=None, colors=None, uvs=None, file_name=None,
                 center_position=None, zero_position=None, type = MeshType.faces, attributes=None):
        self._application = None  # Initialize this later otherwise unit tests break

        self._vertices = NumPyUtil.immutableNDArray(vertices)
        self._normals = NumPyUtil.immutableNDArray(normals)
        self._indices = NumPyUtil.immutableNDArray(indices)
        self._colors = NumPyUtil.immutableNDArray(colors)
        self._uvs = NumPyUtil.immutableNDArray(uvs)
        self._vertex_count = len(self._vertices) if self._vertices is not None else 0
        self._face_count = len(self._indices) if self._indices is not None else 0
        self._type = type
        self._file_name = file_name  # type: Optional[str]
        # original center position
        self._center_position = center_position
        # original zero position, is changed after transformation
        if zero_position is not None:
            self._zero_position = zero_position
        else:
            self._zero_position = Vector(0, 0, 0) # type: Vector
        self._convex_hull = None    # type: Optional[scipy.spatial.ConvexHull]
        self._convex_hull_vertices = None  # type: Optional[numpy.ndarray]
        self._convex_hull_lock = threading.Lock()

        self._attributes = {}
        if attributes is not None:
            for key, attribute in attributes.items():
                new_value = {}
                for attribute_key, attribute_value in attribute.items():
                    if attribute_key == "value":
                        new_value["value"] = NumPyUtil.immutableNDArray(attribute_value)
                    else:
                        new_value[attribute_key] = attribute_value
                self._attributes[key] = new_value

    ##  Triggered when this file is deleted.
    #
    #   The file will then no longer be watched for changes.
    def __del__(self):
        if self._file_name:
            if self._application:
                self._application.getController().getScene().removeWatchedFile(self._file_name)

    ## Create a new MeshData with specified changes
    #   \return \type{MeshData}
    def set(self, vertices=Reuse, normals=Reuse, indices=Reuse, colors=Reuse, uvs=Reuse, file_name=Reuse,
            center_position=Reuse, zero_position=Reuse, attributes=Reuse) -> "MeshData":
        vertices = vertices if vertices is not Reuse else self._vertices
        normals = normals if normals is not Reuse else self._normals
        indices = indices if indices is not Reuse else self._indices
        colors = colors if colors is not Reuse else self._colors
        uvs = uvs if uvs is not Reuse else self._uvs
        file_name = file_name if file_name is not Reuse else self._file_name
        center_position = center_position if center_position is not Reuse else self._center_position
        zero_position = zero_position if zero_position is not Reuse else self._zero_position
        attributes = attributes if attributes is not Reuse else self._attributes

        return MeshData(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,
                        file_name=file_name, center_position=center_position, zero_position=zero_position, attributes=attributes)

    def getHash(self):
        m = hashlib.sha256()
        m.update(self.getVerticesAsByteArray())
        return m.hexdigest()

    def getCenterPosition(self) -> Vector:
        return self._center_position

    def getZeroPosition(self) -> Vector:
        return self._zero_position

    def getType(self):
        return self._type

    def getFaceCount(self) -> int:
        return self._face_count

    ##  Get the array of vertices
    def getVertices(self) -> numpy.ndarray:
        return self._vertices

    ##  Get the number of vertices
    def getVertexCount(self) -> int:
        return self._vertex_count

    ##  Get a vertex by index
    def getVertex(self, index):
        try:
            return self._vertices[index]
        except IndexError:
            return None

    ##  Return whether this mesh has vertex normals.
    def hasNormals(self) -> bool:
        return self._normals is not None

    ##  Return the list of vertex normals.
    def getNormals(self) -> numpy.ndarray:
        return self._normals

    ##  Return whether this mesh has indices.
    def hasIndices(self) -> bool:
        return self._indices is not None

    ##  Get the array of indices
    #   \return \type{numpy.ndarray}
    def getIndices(self) -> numpy.ndarray:
        return self._indices

    def hasColors(self) -> bool:
        return self._colors is not None

    def getColors(self) -> numpy.ndarray:
        return self._colors

    def hasUVCoordinates(self) -> bool:
        return self._uvs is not None

    def getFileName(self) -> Optional[str]:
        return self._file_name

    ##  Transform the meshdata, center and zero position by given Matrix
    #   \param transformation 4x4 homogenous transformation matrix
    def getTransformed(self, transformation: Matrix) -> "MeshData":
        if self._vertices is not None:
            transformed_vertices = transformVertices(self._vertices, transformation)
            transformed_normals = transformNormals(self._normals, transformation) if self._normals is not None else None

            transformation_matrix = transformation.getTransposed()
            if self._center_position is not None:
                center_position = self._center_position.multiply(transformation_matrix)
            else:
                center_position = Reuse
            zero_position = self._zero_position.multiply(transformation_matrix)

            return self.set(vertices=transformed_vertices, normals=transformed_normals, center_position=center_position, zero_position=zero_position)
        else:
            return MeshData(vertices = self._vertices)

    ##  Get the extents of this mesh.
    #
    #   \param matrix The transformation matrix from model to world coordinates.
    def getExtents(self, matrix: Optional[Matrix] = None) -> Optional[AxisAlignedBox]:
        if self._vertices is None:
            return None

        if matrix is not None:
            data = self.getConvexHullTransformedVertices(matrix)
        else:
            data = self.getConvexHullVertices()

        if data is None:
            return None

        min = data.min(axis=0)
        max = data.max(axis=0)

        return AxisAlignedBox(minimum=Vector(min[0], min[1], min[2]), maximum=Vector(max[0], max[1], max[2]))

    ##  Get all vertices of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per vertex.
    def getVerticesAsByteArray(self) -> Optional[bytes]:
        if self._vertices is None:
            return None
        return self._vertices.tostring()

    ##  Get all normals of this mesh as a bytearray
    #
    #   \return A bytearray object with 3 floats per normal.
    def getNormalsAsByteArray(self) -> Optional[bytes]:
        if self._normals is None:
            return None
        return self._normals.tostring()

    ##  Get all indices as a bytearray
    #
    #   \return A bytearray object with 3 ints per face.
    def getIndicesAsByteArray(self) -> Optional[bytes]:
        if self._indices is None:
            return None
        return self._indices.tostring()

    def getColorsAsByteArray(self) -> Optional[bytes]:
        if self._colors is None:
            return None
        return self._colors.tostring()

    def getUVCoordinatesAsByteArray(self) -> Optional[bytes]:
        if self._uvs is None:
            return None
        return self._uvs.tostring()

    #######################################################################
    # Convex hull handling
    #######################################################################
    def _computeConvexHull(self):
        points = self.getVertices()
        if points is None:
            return
        self._convex_hull = approximateConvexHull(points, MAXIMUM_HULL_VERTICES_COUNT)

    ##  Gets the Convex Hull of this mesh
    #
    #    \return \type{scipy.spatial.ConvexHull}
    def getConvexHull(self) -> Optional[scipy.spatial.ConvexHull]:
        with self._convex_hull_lock:
            if self._convex_hull is None:
                self._computeConvexHull()
            return self._convex_hull

    ##  Gets the convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullVertices(self) -> Optional[numpy.ndarray]:
        if self._convex_hull_vertices is None:
            convex_hull = self.getConvexHull()
            if convex_hull is None:
                return None
            self._convex_hull_vertices = numpy.take(convex_hull.points, convex_hull.vertices, axis=0)
        return self._convex_hull_vertices

    ##  Gets transformed convex hull points
    #
    #   \return \type{numpy.ndarray} the vertices which describe the convex hull
    def getConvexHullTransformedVertices(self, transformation: Matrix) -> Optional[numpy.ndarray]:
        vertices = self.getConvexHullVertices()
        if vertices is not None:
            return transformVertices(vertices, transformation)
        else:
            return None

    def hasAttribute(self, key: str) -> bool:
        return key in self._attributes

    ##  the return value is a dict with at least keys opengl_name, opengl_type, value
    def getAttribute(self, key: str):
        return self._attributes[key]

    ##  Return attribute names in alphabetical order
    #   The sorting assures that the order is always the same.
    def attributeNames(self) -> List[str]:
        result = list(self._attributes.keys())
        result.sort()
        return result

    def toString(self) -> str:
        return "MeshData(_vertices=" + str(self._vertices) + ", _normals=" + str(self._normals) + ", _indices=" + \
               str(self._indices) + ", _colors=" + str(self._colors) + ", _uvs=" + str(self._uvs) + ", _attributes=" + \
               str(self._attributes.keys()) + ") "