Skip to content

Commit 4a14f16

Browse files
committed
made c-extension vs. python+numpy selectable
1 parent 6fe3257 commit 4a14f16

File tree

4 files changed

+96
-13
lines changed

4 files changed

+96
-13
lines changed

examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
A = A[:, :, 0] + 1.*A[:,:, 3] # Compose some elements from RGBA to give depth
2222
A = gaussian_filter(A, 2) # smoothing
2323
numpy2stl(A, "examples/OpenMDAO-logo.stl",
24-
scale=0.05, mask_val=1., solid=True)
24+
scale=0.05, mask_val=1., min_thickness_percent=0.005, solid=True)
2525

2626
text = ("$\oint_{\Gamma} (A\, dx + B\, dy) = \iint_{U} \left(\\frac{\partial "
2727
"B}{\partial x} - \\frac{\partial A}{\partial y}\\right)\ dxdy$ \n\n "

stl_tools/cwrapped.c

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

stl_tools/numpy2stl.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import struct
22
import numpy as np
3-
3+
from itertools import product
44
try:
55
from .cwrapped import tessellate
66
c_lib = True
@@ -61,7 +61,6 @@ def roll2d(image, shifts):
6161

6262

6363
def numpy2stl(A, fn, scale=0.1, mask_val=None, ascii=False,
64-
calc_normals=False,
6564
max_width=235.,
6665
max_depth=140.,
6766
max_height=150.,
@@ -85,8 +84,6 @@ def numpy2stl(A, fn, scale=0.1, mask_val=None, ascii=False,
8584
8685
ascii (bool) - sets the STL format to ascii or binary (default)
8786
88-
calc_normals (bool) - sets whether surface normals are calculated or not
89-
9087
max_width, max_depth, max_height (floats) - maximum size of the stl
9188
object (in mm). Match this to
9289
the dimensions of a 3D printer
@@ -104,15 +101,90 @@ def numpy2stl(A, fn, scale=0.1, mask_val=None, ascii=False,
104101

105102
m, n = A.shape
106103
if n >= m:
104+
# rotate to best fit a printing platform
107105
A = np.rot90(A, k=3)
108106
m, n = n, m
109107
A = scale * (A - A.min())
110108

111109
if not mask_val:
112110
mask_val = A.min() - 1.
113-
A = np.ascontiguousarray(A, dtype=float)
114111

115-
facets = np.asarray(tessellate(A, mask_val, min_thickness_percent, solid))
112+
if c_lib and not force_python: # try to use c library
113+
# needed for memoryviews
114+
A = np.ascontiguousarray(A, dtype=float)
115+
116+
facets = np.asarray(tessellate(A, mask_val, min_thickness_percent,
117+
solid))
118+
# center on platform
119+
facets[:, 3::3] += -m / 2
120+
facets[:, 4::3] += -n / 2
121+
122+
else: # use python + numpy
123+
facets = []
124+
mask = np.zeros((m, n))
125+
print("Creating top mesh...")
126+
for i, k in product(range(m - 1), range(n - 1)):
127+
128+
this_pt = np.array([i - m / 2., k - n / 2., A[i, k]])
129+
top_right = np.array([i - m / 2., k + 1 - n / 2., A[i, k + 1]])
130+
bottom_left = np.array([i + 1. - m / 2., k - n / 2., A[i + 1, k]])
131+
bottom_right = np.array(
132+
[i + 1. - m / 2., k + 1 - n / 2., A[i + 1, k + 1]])
133+
134+
n1, n2 = np.zeros(3), np.zeros(3)
135+
136+
if (this_pt[-1] > mask_val and top_right[-1] > mask_val and
137+
bottom_left[-1] > mask_val):
138+
139+
facet = np.concatenate([n1, top_right, this_pt, bottom_right])
140+
mask[i, k] = 1
141+
mask[i, k + 1] = 1
142+
mask[i + 1, k] = 1
143+
facets.append(facet)
144+
145+
if (this_pt[-1] > mask_val and bottom_right[-1] > mask_val and
146+
bottom_left[-1] > mask_val):
147+
148+
facet = np.concatenate(
149+
[n2, bottom_right, this_pt, bottom_left])
150+
facets.append(facet)
151+
mask[i, k] = 1
152+
mask[i + 1, k + 1] = 1
153+
mask[i + 1, k] = 1
154+
facets = np.array(facets)
155+
156+
if solid:
157+
print("Computed edges...")
158+
edge_mask = np.sum([roll2d(mask, (i, k))
159+
for i, k in product([-1, 0, 1], repeat=2)],
160+
axis=0)
161+
edge_mask[np.where(edge_mask == 9.)] = 0.
162+
edge_mask[np.where(edge_mask != 0.)] = 1.
163+
edge_mask[0::m - 1, :] = 1.
164+
edge_mask[:, 0::n - 1] = 1.
165+
X, Y = np.where(edge_mask == 1.)
166+
locs = zip(X - m / 2., Y - n / 2.)
167+
168+
zvals = facets[:, 5::3]
169+
zmin, zthickness = zvals.min(), zvals.ptp()
170+
171+
minval = zmin - min_thickness_percent * zthickness
172+
173+
bottom = []
174+
print("Extending edges, creating bottom...")
175+
for i, facet in enumerate(facets):
176+
if (facet[3], facet[4]) in locs:
177+
facets[i][5] = minval
178+
if (facet[6], facet[7]) in locs:
179+
facets[i][8] = minval
180+
if (facet[9], facet[10]) in locs:
181+
facets[i][11] = minval
182+
this_bottom = np.concatenate(
183+
[facet[:3], facet[6:8], [minval], facet[3:5], [minval],
184+
facet[9:11], [minval]])
185+
bottom.append(this_bottom)
186+
187+
facets = np.concatenate([facets, bottom])
116188

117189
xsize = facets[:, 3::3].ptp()
118190
if xsize > max_width:

stl_tools/test/test_stl.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ def test_text2array(self):
3333

3434
def test_png(self):
3535
""" Tests creation of an STL from a PNG.
36-
Covers the text2array function.
36+
Covers the numpy2stl function.
3737
"""
3838
output_name = "OUT_.stl"
3939
# test ascii output
40-
A = 100*np.random.randn(64, 64)
40+
A = 100 * np.random.randn(64, 64)
4141
numpy2stl(A, output_name, scale=0.05, mask_val=3., ascii=True)
4242
assert os.path.exists(output_name)
4343
assert os.stat(output_name).st_size > 1e5
@@ -48,10 +48,21 @@ def test_png(self):
4848
assert os.stat(output_name).st_size > 1e5
4949
os.remove(output_name)
5050

51-
def test_calc_normals(self):
51+
def test_png_force_py(self):
52+
""" Tests creation of an STL from a PNG.
53+
Covers the pure-python section of the numpy2stl function.
54+
"""
5255
output_name = "OUT_.stl"
53-
A = 100*np.random.randn(64, 64)
54-
numpy2stl(A, output_name, scale=0.05, mask_val=3., calc_normals=True)
56+
# test ascii output
57+
A = 100 * np.random.randn(64, 64)
58+
numpy2stl(A, output_name, scale=0.05, mask_val=3., ascii=True,
59+
force_python=True)
60+
assert os.path.exists(output_name)
61+
assert os.stat(output_name).st_size > 1e5
62+
63+
# test binary output
64+
numpy2stl(A, output_name, scale=0.05, mask_val=3.,
65+
force_python=True)
5566
assert os.path.exists(output_name)
5667
assert os.stat(output_name).st_size > 1e5
5768
os.remove(output_name)

0 commit comments

Comments
 (0)