Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: New Julia
Views: 239
1
import struct
2
import numpy as np
3
from itertools import product
4
#try:
5
# from .cwrapped import tessellate
6
# c_lib = True
7
#except ImportError:
8
# c_lib = False
9
c_lib = False
10
11
12
ASCII_FACET = """ facet normal {face[0]:e} {face[1]:e} {face[2]:e}
13
outer loop
14
vertex {face[3]:e} {face[4]:e} {face[5]:e}
15
vertex {face[6]:e} {face[7]:e} {face[8]:e}
16
vertex {face[9]:e} {face[10]:e} {face[11]:e}
17
endloop
18
endfacet"""
19
20
BINARY_HEADER = "80sI"
21
BINARY_FACET = "12fH"
22
23
24
def _build_binary_stl(facets):
25
"""returns a string of binary binary data for the stl file"""
26
27
lines = [struct.pack(BINARY_HEADER, b'Binary STL Writer', len(facets)), ]
28
for facet in facets:
29
facet = list(facet)
30
facet.append(0) # need to pad the end with a unsigned short byte
31
lines.append(struct.pack(BINARY_FACET, *facet))
32
return lines
33
34
35
def _build_ascii_stl(facets):
36
"""returns a list of ascii lines for the stl file """
37
38
lines = ['solid ffd_geom', ]
39
for facet in facets:
40
lines.append(ASCII_FACET.format(face=facet))
41
lines.append('endsolid ffd_geom')
42
return lines
43
44
45
def writeSTL(facets, file_name, ascii=False):
46
"""writes an ASCII or binary STL file"""
47
48
f = open(file_name, 'wb')
49
if ascii:
50
lines = _build_ascii_stl(facets)
51
lines_ = "\n".join(lines).encode("UTF-8")
52
f.write(lines_)
53
else:
54
data = _build_binary_stl(facets)
55
data = b"".join(data)
56
f.write(data)
57
58
f.close()
59
60
61
def roll2d(image, shifts):
62
return np.roll(np.roll(image, shifts[0], axis=0), shifts[1], axis=1)
63
64
65
def numpy2stl(A, fn, scale=0.1, mask_val=None, ascii=False,
66
max_width=235.,
67
max_depth=140.,
68
max_height=150.,
69
solid=False,
70
rotate=True,
71
min_thickness_percent=0.1,
72
force_python=False):
73
"""
74
Reads a numpy array, and outputs an STL file
75
Inputs:
76
A (ndarray) - an 'm' by 'n' 2D numpy array
77
fn (string) - filename to use for STL file
78
Optional input:
79
scale (float) - scales the height (surface) of the
80
resulting STL mesh. Tune to match needs
81
mask_val (float) - any element of the inputted array that is less
82
than this value will not be included in the mesh.
83
default renders all vertices (x > -inf for all float x)
84
ascii (bool) - sets the STL format to ascii or binary (default)
85
max_width, max_depth, max_height (floats) - maximum size of the stl
86
object (in mm). Match this to
87
the dimensions of a 3D printer
88
platform
89
solid (bool): sets whether to create a solid geometry (with sides and
90
a bottom) or not.
91
min_thickness_percent (float) : when creating the solid bottom face, this
92
multiplier sets the minimum thickness in
93
the final geometry (shallowest interior
94
point to bottom face), as a percentage of
95
the thickness of the model computed up to
96
that point.
97
Returns: (None)
98
"""
99
100
m, n = A.shape
101
if n >= m and rotate:
102
# rotate to best fit a printing platform
103
A = np.rot90(A, k=3)
104
m, n = n, m
105
A = scale * (A - A.min())
106
107
if not mask_val:
108
mask_val = A.min() - 1.
109
110
if c_lib and not force_python: # try to use c library
111
# needed for memoryviews
112
A = np.ascontiguousarray(A, dtype=float)
113
114
facets = np.asarray(tessellate(A, mask_val, min_thickness_percent,
115
solid))
116
# center on platform
117
facets[:, 3::3] += -m / 2
118
facets[:, 4::3] += -n / 2
119
120
else: # use python + numpy
121
facets = []
122
mask = np.zeros((m, n))
123
print("Creating top mesh...")
124
for i, k in product(range(m - 1), range(n - 1)):
125
126
this_pt = np.array([i - m / 2., k - n / 2., A[i, k]])
127
top_right = np.array([i - m / 2., k + 1 - n / 2., A[i, k + 1]])
128
bottom_left = np.array([i + 1. - m / 2., k - n / 2., A[i + 1, k]])
129
bottom_right = np.array(
130
[i + 1. - m / 2., k + 1 - n / 2., A[i + 1, k + 1]])
131
132
n1, n2 = np.zeros(3), np.zeros(3)
133
134
if (this_pt[-1] > mask_val and top_right[-1] > mask_val and
135
bottom_left[-1] > mask_val):
136
137
facet = np.concatenate([n1, top_right, this_pt, bottom_right])
138
mask[i, k] = 1
139
mask[i, k + 1] = 1
140
mask[i + 1, k] = 1
141
facets.append(facet)
142
143
if (this_pt[-1] > mask_val and bottom_right[-1] > mask_val and
144
bottom_left[-1] > mask_val):
145
146
facet = np.concatenate(
147
[n2, bottom_right, this_pt, bottom_left])
148
facets.append(facet)
149
mask[i, k] = 1
150
mask[i + 1, k + 1] = 1
151
mask[i + 1, k] = 1
152
facets = np.array(facets)
153
154
if solid:
155
print("Computed edges...")
156
edge_mask = np.sum([roll2d(mask, (i, k))
157
for i, k in product([-1, 0, 1], repeat=2)],
158
axis=0)
159
edge_mask[np.where(edge_mask == 9.)] = 0.
160
edge_mask[np.where(edge_mask != 0.)] = 1.
161
edge_mask[0::m - 1, :] = 1.
162
edge_mask[:, 0::n - 1] = 1.
163
X, Y = np.where(edge_mask == 1.)
164
locs = zip(X - m / 2., Y - n / 2.)
165
166
zvals = facets[:, 5::3]
167
zmin, zthickness = zvals.min(), zvals.ptp()
168
169
minval = zmin - min_thickness_percent * zthickness
170
171
bottom = []
172
print("Extending edges, creating bottom...")
173
for i, facet in enumerate(facets):
174
if (facet[3], facet[4]) in locs:
175
facets[i][5] = minval
176
if (facet[6], facet[7]) in locs:
177
facets[i][8] = minval
178
if (facet[9], facet[10]) in locs:
179
facets[i][11] = minval
180
this_bottom = np.concatenate(
181
[facet[:3], facet[6:8], [minval], facet[3:5], [minval],
182
facet[9:11], [minval]])
183
bottom.append(this_bottom)
184
185
facets = np.concatenate([facets, bottom])
186
187
xsize = facets[:, 3::3].ptp()
188
if xsize > max_width:
189
facets = facets * float(max_width) / xsize
190
191
ysize = facets[:, 4::3].ptp()
192
if ysize > max_depth:
193
facets = facets * float(max_depth) / ysize
194
195
zsize = facets[:, 5::3].ptp()
196
if zsize > max_height:
197
facets = facets * float(max_height) / zsize
198
199
writeSTL(facets, fn, ascii=ascii)
200
201