1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2013 David Braam
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode
from UM.Job import Job
import os
import struct
import numpy
use_numpystl = False
try:
import stl # numpy-stl lib
import stl.mesh
# Increase max count. (10 million should be okay-ish)
stl.stl.MAX_COUNT = 10000000
use_numpystl = True
except ImportError:
Logger.log("w", "Could not find numpy-stl, falling back to slower code.")
# We have our own fallback code.
class STLReader(MeshReader):
def __init__(self):
super(STLReader, self).__init__()
self._supported_extensions = [".stl"]
def load_file(self, file_name, mesh_builder, _use_numpystl = False):
if _use_numpystl:
self._loadWithNumpySTL(file_name, mesh_builder)
else:
f = open(file_name, "rb")
if not self._loadBinary(mesh_builder, f):
f.close()
f = open(file_name, "rt")
try:
self._loadAscii(mesh_builder, f)
except UnicodeDecodeError:
return None
f.close()
Job.yieldThread() # Yield somewhat to ensure the GUI has time to update a bit.
mesh_builder.calculateNormals(fast = True)
mesh_builder.setFileName(file_name)
## Decide if we need to use ascii or binary in order to read file
def read(self, file_name):
mesh_builder = MeshBuilder()
scene_node = SceneNode()
self.load_file(file_name, mesh_builder, _use_numpystl = use_numpystl)
mesh = mesh_builder.build()
if use_numpystl:
verts = mesh.getVertices()
# In some cases numpy stl reads incorrectly and the result is that the Z values are all 0
# Add new error cases if you find them.
if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]):
# Something may have gone wrong in numpy stl, start over without numpy stl
Logger.log("w", "All Z coordinates are the same using numpystl, trying again without numpy stl.")
mesh_builder = MeshBuilder()
self.load_file(file_name, mesh_builder, _use_numpystl = False)
mesh = mesh_builder.build()
verts = mesh.getVertices()
if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]):
Logger.log("e", "All Z coordinates are still the same without numpy stl... let's hope for the best")
if mesh_builder.getVertexCount() == 0:
Logger.log("d", "File did not contain valid data, unable to read.")
return None # We didn't load anything.
scene_node.setMeshData(mesh)
Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount())
return scene_node
def _swapColumns(self, array, frm, to):
array[:, [frm, to]] = array[:, [to, frm]]
def _loadWithNumpySTL(self, file_name, mesh_builder):
for loaded_data in stl.mesh.Mesh.from_multi_file(file_name):
vertices = numpy.resize(loaded_data.points.flatten(), (int(loaded_data.points.size / 3), 3))
# Invert values of second column
vertices[:, 1] *= -1
# Swap column 1 and 2 (We have a different coordinate system)
self._swapColumns(vertices, 1, 2)
mesh_builder.addVertices(vertices)
# Private
## Load the STL data from file by consdering the data as ascii.
# \param mesh The MeshData object where the data is written to.
# \param f The file handle
def _loadAscii(self, mesh_builder, f):
num_verts = 0
for lines in f:
for line in lines.split("\r"):
if "vertex" in line:
num_verts += 1
mesh_builder.reserveFaceCount(num_verts / 3)
f.seek(0, os.SEEK_SET)
vertex = 0
face = [None, None, None]
for lines in f:
for line in lines.split("\r"):
if "vertex" in line:
face[vertex] = line.split()[1:]
vertex += 1
if vertex == 3:
mesh_builder.addFaceByPoints(
float(face[0][0]), float(face[0][2]), -float(face[0][1]),
float(face[1][0]), float(face[1][2]), -float(face[1][1]),
float(face[2][0]), float(face[2][2]), -float(face[2][1])
)
vertex = 0
Job.yieldThread()
# Private
## Load the STL data from file by consdering the data as Binary.
# \param mesh The MeshData object where the data is written to.
# \param f The file handle
def _loadBinary(self, mesh_builder, f):
f.read(80) # Skip the header
num_faces = struct.unpack("<I", f.read(4))[0]
# On ascii files, the num_faces will be big, due to 4 ascii bytes being seen as an unsigned int.
if num_faces < 1 or num_faces > 1000000000:
return False
f.seek(0, os.SEEK_END)
file_size = f.tell()
f.seek(84, os.SEEK_SET)
if file_size < num_faces * 50 + 84:
return False
mesh_builder.reserveFaceCount(num_faces)
for idx in range(0, num_faces):
data = struct.unpack(b"<ffffffffffffH", f.read(50))
mesh_builder.addFaceByPoints(
data[3], data[5], -data[4],
data[6], data[8], -data[7],
data[9], data[11], -data[10]
)
Job.yieldThread()
return True
|