mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-28 23:04:34 +08:00
Merge pull request #6278 from Ultimaker/feature_ply
Add plug-in to read PLY files
This commit is contained in:
commit
6015f4f3f9
@ -13,6 +13,6 @@ TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
|
||||
Icon=cura-icon
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;text/x-gcode;
|
||||
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;text/x-gcode;application/x-amf;application/x-ply;application/x-ctm;model/vnd.collada+xml;model/gltf-binary;model/gltf+json;model/vnd.collada+xml+zip;
|
||||
Categories=Graphics;
|
||||
Keywords=3D;Printing;Slicer;
|
||||
|
152
plugins/TrimeshReader/TrimeshReader.py
Normal file
152
plugins/TrimeshReader/TrimeshReader.py
Normal file
@ -0,0 +1,152 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V., fieldOfView
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# The _toMeshData function is taken from the AMFReader class which was built by fieldOfView.
|
||||
|
||||
from typing import Any, List, Union, TYPE_CHECKING
|
||||
import numpy # To create the mesh data.
|
||||
import os.path # To create the mesh name for the resulting mesh.
|
||||
import trimesh # To load the files into a Trimesh.
|
||||
|
||||
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices # To construct meshes from the Trimesh data.
|
||||
from UM.Mesh.MeshReader import MeshReader # The plug-in type we're extending.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType # To add file types that we can open.
|
||||
from UM.Scene.GroupDecorator import GroupDecorator # Added to the parent node if we load multiple nodes at once.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator # Added to the resulting scene node.
|
||||
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator # Added to group nodes if we load multiple nodes at once.
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode # To create a node in the scene after reading the file.
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator # Added to the resulting scene node.
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
## Class that leverages Trimesh to import files.
|
||||
class TrimeshReader(MeshReader):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._supported_extensions = [".ctm", ".dae", ".gltf", ".glb", ".ply", ".zae"]
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ctm",
|
||||
comment = "Open Compressed Triangle Mesh",
|
||||
suffixes = ["ctm"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/vnd.collada+xml",
|
||||
comment = "COLLADA Digital Asset Exchange",
|
||||
suffixes = ["dae"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/gltf-binary",
|
||||
comment = "glTF Binary",
|
||||
suffixes = ["glb"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/gltf+json",
|
||||
comment = "glTF Embedded JSON",
|
||||
suffixes = ["gltf"]
|
||||
)
|
||||
)
|
||||
# Trimesh seems to have a bug when reading .off files.
|
||||
#MimeTypeDatabase.addMimeType(
|
||||
# MimeType(
|
||||
# name = "application/x-off",
|
||||
# comment = "Geomview Object File Format",
|
||||
# suffixes = ["off"]
|
||||
# )
|
||||
#)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ply", # Wikipedia lists the MIME type as "text/plain" but that won't do as it's not unique to PLY files.
|
||||
comment = "Stanford Triangle Format",
|
||||
suffixes = ["ply"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/vnd.collada+xml+zip",
|
||||
comment = "Compressed COLLADA Digital Asset Exchange",
|
||||
suffixes = ["zae"]
|
||||
)
|
||||
)
|
||||
|
||||
## Reads a file using Trimesh.
|
||||
# \param file_name The file path. This is assumed to be one of the file
|
||||
# types that Trimesh can read. It will not be checked again.
|
||||
# \return A scene node that contains the file's contents.
|
||||
def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
|
||||
mesh_or_scene = trimesh.load(file_name)
|
||||
meshes = [] # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
|
||||
if isinstance(mesh_or_scene, trimesh.Trimesh):
|
||||
meshes = [mesh_or_scene]
|
||||
elif isinstance(mesh_or_scene, trimesh.Scene):
|
||||
meshes = [mesh for mesh in mesh_or_scene.geometry.values()]
|
||||
|
||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
nodes = [] # type: List[SceneNode]
|
||||
for mesh in meshes:
|
||||
if not isinstance(mesh, trimesh.Trimesh): # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
|
||||
continue
|
||||
mesh.merge_vertices()
|
||||
mesh.remove_unreferenced_vertices()
|
||||
mesh.fix_normals()
|
||||
mesh_data = self._toMeshData(mesh)
|
||||
|
||||
file_base_name = os.path.basename(file_name)
|
||||
new_node = CuraSceneNode()
|
||||
new_node.setMeshData(mesh_data)
|
||||
new_node.setSelectable(True)
|
||||
new_node.setName(file_base_name if len(meshes) == 1 else "{file_base_name} {counter}".format(file_base_name = file_base_name, counter = str(len(nodes) + 1)))
|
||||
new_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
new_node.addDecorator(SliceableObjectDecorator())
|
||||
nodes.append(new_node)
|
||||
|
||||
if len(nodes) == 1:
|
||||
return nodes[0]
|
||||
# Add all nodes to a group so they stay together.
|
||||
group_node = CuraSceneNode()
|
||||
group_node.addDecorator(GroupDecorator())
|
||||
group_node.addDecorator(ConvexHullDecorator())
|
||||
group_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
for node in nodes:
|
||||
node.setParent(group_node)
|
||||
return group_node
|
||||
|
||||
## Converts a Trimesh to Uranium's MeshData.
|
||||
# \param tri_node A Trimesh containing the contents of a file that was
|
||||
# just read.
|
||||
# \return Mesh data from the Trimesh in a way that Uranium can understand
|
||||
# it.
|
||||
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
|
||||
tri_faces = tri_node.faces
|
||||
tri_vertices = tri_node.vertices
|
||||
|
||||
indices = []
|
||||
vertices = []
|
||||
|
||||
index_count = 0
|
||||
face_count = 0
|
||||
for tri_face in tri_faces:
|
||||
face = []
|
||||
for tri_index in tri_face:
|
||||
vertices.append(tri_vertices[tri_index])
|
||||
face.append(index_count)
|
||||
index_count += 1
|
||||
indices.append(face)
|
||||
face_count += 1
|
||||
|
||||
vertices = numpy.asarray(vertices, dtype = numpy.float32)
|
||||
indices = numpy.asarray(indices, dtype = numpy.int32)
|
||||
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
|
||||
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
|
||||
return mesh_data
|
46
plugins/TrimeshReader/__init__.py
Normal file
46
plugins/TrimeshReader/__init__.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2019 Ultimaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import TrimeshReader
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "ctm",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Open Compressed Triangle Mesh")
|
||||
},
|
||||
{
|
||||
"extension": "dae",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "COLLADA Digital Asset Exchange")
|
||||
},
|
||||
{
|
||||
"extension": "glb",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Binary")
|
||||
},
|
||||
{
|
||||
"extension": "gltf",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Embedded JSON")
|
||||
},
|
||||
# Trimesh seems to have a bug when reading OFF files.
|
||||
#{
|
||||
# "extension": "off",
|
||||
# "description": i18n_catalog.i18nc("@item:inlistbox", "Geomview Object File Format")
|
||||
#},
|
||||
{
|
||||
"extension": "ply",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Stanford Triangle Format")
|
||||
},
|
||||
{
|
||||
"extension": "zae",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Compressed COLLADA Digital Asset Exchange")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return {"mesh_reader": TrimeshReader.TrimeshReader()}
|
7
plugins/TrimeshReader/plugin.json
Normal file
7
plugins/TrimeshReader/plugin.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Trimesh Reader",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading model files.",
|
||||
"api": "6.0.0"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user