yapCAD 0.3.0__py2.py3-none-any.whl → 0.3.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yapcad/combine.py +82 -376
- yapcad/drawable.py +44 -3
- yapcad/ezdxf_drawable.py +1 -1
- yapcad/geom.py +204 -264
- yapcad/geom3d.py +975 -55
- yapcad/geom3d_util.py +541 -0
- yapcad/geom_util.py +817 -0
- yapcad/geometry.py +441 -49
- yapcad/geometry_checks.py +112 -0
- yapcad/geometry_utils.py +115 -0
- yapcad/io/__init__.py +5 -0
- yapcad/io/stl.py +83 -0
- yapcad/mesh.py +46 -0
- yapcad/metadata.py +109 -0
- yapcad/octtree.py +627 -0
- yapcad/poly.py +153 -299
- yapcad/pyglet_drawable.py +597 -61
- yapcad/triangulator.py +103 -0
- yapcad/xform.py +0 -1
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/METADATA +92 -38
- yapcad-0.3.1.dist-info/RECORD +27 -0
- yapcad-0.3.0.dist-info/RECORD +0 -17
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/WHEEL +0 -0
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/licenses/LICENSE.txt +0 -0
- {yapcad-0.3.0.dist-info → yapcad-0.3.1.dist-info}/top_level.txt +0 -0
yapcad/geom3d_util.py
ADDED
@@ -0,0 +1,541 @@
|
|
1
|
+
## geom3d_util, additional 3D geometry support for yapCAD
|
2
|
+
## started on Mon Feb 15 20:23:57 PST 2021 Richard W. DeVaul
|
3
|
+
|
4
|
+
from yapcad.geom import *
|
5
|
+
from yapcad.geom_util import *
|
6
|
+
from yapcad.xform import *
|
7
|
+
from yapcad.geom3d import *
|
8
|
+
|
9
|
+
import math
|
10
|
+
|
11
|
+
"""
|
12
|
+
==================================================
|
13
|
+
Utility functions to support 3D geometry in yapCAD
|
14
|
+
==================================================
|
15
|
+
|
16
|
+
This module is mostly a collection of
|
17
|
+
parametric soids and surfaces, and supporting functions.
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
|
22
|
+
def sphere2cartesian(lat,lon,rad):
|
23
|
+
"""
|
24
|
+
Utility function to convert spherical polar coordinates to
|
25
|
+
cartesian coordinates for a sphere centered at the origin.
|
26
|
+
``lat`` -- latitude
|
27
|
+
``lon`` -- longitude
|
28
|
+
``rad`` -- sphere radius
|
29
|
+
|
30
|
+
returns a ``yapcad.geom`` point
|
31
|
+
"""
|
32
|
+
if lat == 90:
|
33
|
+
return [0,0,rad,1]
|
34
|
+
elif lat == -90:
|
35
|
+
return [0,0,-rad,1]
|
36
|
+
else:
|
37
|
+
latr = (((lat+90)%180)-90)*pi2/360.0
|
38
|
+
lonr = (lon%360)*pi2/360.0
|
39
|
+
|
40
|
+
smallrad = math.cos(latr)*rad
|
41
|
+
z = math.sin(latr)*rad
|
42
|
+
x = math.cos(lonr)*smallrad
|
43
|
+
y = math.sin(lonr)*smallrad
|
44
|
+
return [x,y,z,1]
|
45
|
+
|
46
|
+
## icosohedron-specific function, generate intial geometry
|
47
|
+
def makeIcoPoints(center,radius):
|
48
|
+
"""
|
49
|
+
Procedure to generate the verticies of an icosohedron with the specified
|
50
|
+
``center`` and ``radius``.
|
51
|
+
"""
|
52
|
+
points = []
|
53
|
+
normals = []
|
54
|
+
p = sphere2cartesian(90,0,radius)
|
55
|
+
n = scale3(p,1.0/mag(p))
|
56
|
+
n[3] = 0.0
|
57
|
+
points.append(p)
|
58
|
+
normals.append(n)
|
59
|
+
for i in range(10):
|
60
|
+
sgn = 1
|
61
|
+
if i%2 == 0:
|
62
|
+
sgn =-1
|
63
|
+
lat = math.atan(0.5)*360.0*sgn/pi2
|
64
|
+
lon = i*36.0
|
65
|
+
p = sphere2cartesian(lat,lon,radius)
|
66
|
+
n = scale3(p,1.0/mag(p))
|
67
|
+
n[3] = 0.0
|
68
|
+
points.append(p)
|
69
|
+
normals.append(n)
|
70
|
+
|
71
|
+
p = sphere2cartesian(-90,0,radius)
|
72
|
+
n = scale3(p,1.0/mag(p))
|
73
|
+
n[3] = 0.0
|
74
|
+
points.append(p)
|
75
|
+
normals.append(n)
|
76
|
+
return list(map( lambda x: add(x,center),points)),normals
|
77
|
+
|
78
|
+
# face indices for icosahedron
|
79
|
+
icaIndices = [ [1,11,3],[3,11,5],[5,11,7],[7,11,9],[9,11,1],
|
80
|
+
[2,1,3],[2,3,4],[4,3,5],[4,5,6],[6,5,7],[6,7,8],[8,7,9],[8,9,10],[10,9,1],[10,1,2],
|
81
|
+
[0,2,4],[0,4,6],[0,6,8],[0,8,10],[0,10,2] ]
|
82
|
+
|
83
|
+
vertexHash = {}
|
84
|
+
def addVertex(nv,nn,verts,normals):
|
85
|
+
"""
|
86
|
+
Utility function that takes a vertex and associated normal and a
|
87
|
+
list of corresponding vertices and normals, and returns an index
|
88
|
+
that corresponds to the given vertex/normal. If the vertex
|
89
|
+
doesn't exist in the list, the lists are updated to include it and
|
90
|
+
the corresponding normal.
|
91
|
+
|
92
|
+
returns the index, and the (potentiall updated) lists
|
93
|
+
"""
|
94
|
+
global vertexHash
|
95
|
+
if len(verts) == 0:
|
96
|
+
vertexHash = {}
|
97
|
+
|
98
|
+
found = False
|
99
|
+
vkey = f"{nv[0]:.2f}{nv[1]:.2f}{nv[2]:.2f}"
|
100
|
+
if vkey in vertexHash:
|
101
|
+
found = True
|
102
|
+
inds = vertexHash[vkey]
|
103
|
+
for i in inds:
|
104
|
+
if vclose(nv,verts[i]):
|
105
|
+
return i,verts,normals
|
106
|
+
|
107
|
+
verts.append(nv)
|
108
|
+
normals.append(nn)
|
109
|
+
i = len(verts)-1
|
110
|
+
if found:
|
111
|
+
vertexHash[vkey] += [ i ]
|
112
|
+
else:
|
113
|
+
vertexHash[vkey] = [ i ]
|
114
|
+
return i,verts,normals
|
115
|
+
|
116
|
+
def subdivide(f,verts,normals,rad):
|
117
|
+
"""
|
118
|
+
Given a face (a list of three vertex indices), a list of vertices,
|
119
|
+
normals, and a radius, subdivide that face into four new faces and
|
120
|
+
update the lists of vertices and normals accordingly.
|
121
|
+
|
122
|
+
return the updated vertex and normal lists, and a list of the four
|
123
|
+
new faces.
|
124
|
+
"""
|
125
|
+
|
126
|
+
ind1 = f[0]
|
127
|
+
ind2 = f[1]
|
128
|
+
ind3 = f[2]
|
129
|
+
v1 = verts[ind1]
|
130
|
+
v2 = verts[ind2]
|
131
|
+
v3 = verts[ind3]
|
132
|
+
n1 = normals[ind1]
|
133
|
+
n2 = normals[ind2]
|
134
|
+
n3 = normals[ind3]
|
135
|
+
va = add(v1,v2)
|
136
|
+
vb = add(v2,v3)
|
137
|
+
vc = add(v3,v1)
|
138
|
+
ma = rad/mag(va)
|
139
|
+
mb = rad/mag(vb)
|
140
|
+
mc = rad/mag(vc)
|
141
|
+
va = scale3(va,ma)
|
142
|
+
vb = scale3(vb,mb)
|
143
|
+
vc = scale3(vc,mc)
|
144
|
+
|
145
|
+
na = add4(n1,n2)
|
146
|
+
na = scale4(na,1.0/mag(na))
|
147
|
+
nb = add4(n2,n3)
|
148
|
+
nb = scale4(nb,1.0/mag(nb))
|
149
|
+
nc = add4(n3,n1)
|
150
|
+
nc = scale4(nc,1.0/mag(nc))
|
151
|
+
|
152
|
+
inda,verts,normals = addVertex(va,na,verts,normals)
|
153
|
+
indb,verts,normals = addVertex(vb,nb,verts,normals)
|
154
|
+
indc,verts,normals = addVertex(vc,nc,verts,normals)
|
155
|
+
|
156
|
+
f1 = [ind1,inda,indc]
|
157
|
+
f2 = [inda,ind2,indb]
|
158
|
+
f3 = [indb,ind3,indc]
|
159
|
+
f4 = [inda,indb,indc]
|
160
|
+
|
161
|
+
return verts,normals, [f1,f2,f3,f4]
|
162
|
+
|
163
|
+
# make the sphere, return a surface representation
|
164
|
+
def sphereSurface(diameter,center=point(0,0,0),depth=2):
|
165
|
+
rad = diameter/2
|
166
|
+
## subdivision works only when center is origin, so we add
|
167
|
+
## any center offset after we subdivide
|
168
|
+
verts,normals = makeIcoPoints(point(0,0,0),rad)
|
169
|
+
faces = icaIndices
|
170
|
+
|
171
|
+
for i in range(depth):
|
172
|
+
ff = []
|
173
|
+
for f in faces:
|
174
|
+
verts, norms, newfaces = subdivide(f,verts,normals,rad)
|
175
|
+
ff+=newfaces
|
176
|
+
faces = ff
|
177
|
+
|
178
|
+
if not vclose(center,point(0,0,0)):
|
179
|
+
verts = list(map(lambda x: add(x,center),verts))
|
180
|
+
return ['surface',verts,normals,faces,[],[]]
|
181
|
+
|
182
|
+
# make sphere, return solid representation
|
183
|
+
def sphere(diameter,center=point(0,0,0),depth=2):
|
184
|
+
call = f"yapcad.geom3d_util.sphere({diameter},center={center},depth={depth})"
|
185
|
+
return solid( [ sphereSurface(diameter,center,depth)],
|
186
|
+
[],['procedure',call] )
|
187
|
+
|
188
|
+
|
189
|
+
def rectangularPlane(length,width,center=point(0,0,0)):
|
190
|
+
""" return a rectangular surface with the normals oriented in the
|
191
|
+
positive z direction """
|
192
|
+
c = center
|
193
|
+
l = point(length,0,0)
|
194
|
+
w = point(0,width,0)
|
195
|
+
p0 = add(c,(scale3(add(l,w),-0.5)))
|
196
|
+
p1 = add(p0,l)
|
197
|
+
p2 = add(p1,w)
|
198
|
+
p3 = add(p0,w)
|
199
|
+
n = vect(0,0,1,0)
|
200
|
+
|
201
|
+
surf = surface( [p0,p1,p2,p3],[n,n,n,n],
|
202
|
+
[[0,1,2],
|
203
|
+
[2,3,0]])
|
204
|
+
|
205
|
+
return surf
|
206
|
+
|
207
|
+
# make a rectangular prism from six surfaces, return a solid
|
208
|
+
def prism(length,width,height,center=point(0,0,0)):
|
209
|
+
"""make a rectangular prism solid composed of six independent
|
210
|
+
faces"""
|
211
|
+
call = f"yapcad.geom3d_util.prism({length},{width},{height},{center})"
|
212
|
+
|
213
|
+
l2 = length/2
|
214
|
+
w2 = width/2
|
215
|
+
h2 = height/2
|
216
|
+
|
217
|
+
topS = rectangularPlane(length,width,point(0,0,h2))
|
218
|
+
bottomS = rotatesurface(topS,180,axis=point(1,0,0))
|
219
|
+
frontS = rectangularPlane(length,height,point(0,0,w2))
|
220
|
+
frontS = rotatesurface(frontS,90,axis=point(1,0,0))
|
221
|
+
backS = rotatesurface(frontS,180)
|
222
|
+
rightS = rectangularPlane(height,width,point(0,0,l2))
|
223
|
+
rightS = rotatesurface(rightS,90,axis=point(0,1,0))
|
224
|
+
leftS = rotatesurface(rightS,180)
|
225
|
+
|
226
|
+
surfaces = [topS,bottomS,frontS,backS,rightS,leftS]
|
227
|
+
if not vclose(center,[0,0,0,1]):
|
228
|
+
surfaces = list(map(lambda x: translatesurface(x,center),surfaces))
|
229
|
+
|
230
|
+
sol = solid(surfaces,
|
231
|
+
[],
|
232
|
+
['procedure',call])
|
233
|
+
return sol
|
234
|
+
|
235
|
+
def circleSurface(center,radius,angr=10,zup=True):
|
236
|
+
"""make a circular surface centered at ``center`` lying in the XY
|
237
|
+
plane with normals pointing in the positive z direction if ``zup
|
238
|
+
== True``, negative z otherwise"""
|
239
|
+
|
240
|
+
if angr < 1 or angr > 45:
|
241
|
+
raise ValueError('angular resolution must be between 1 and 45 degrees')
|
242
|
+
|
243
|
+
samples = round(360.0/angr)
|
244
|
+
angr = 360.0/samples
|
245
|
+
basep=[center]
|
246
|
+
|
247
|
+
for i in range(samples):
|
248
|
+
theta = i*angr*pi2/360.0
|
249
|
+
pp = [math.cos(theta)*radius,math.sin(theta)*radius,0.0,1.0]
|
250
|
+
pp = add(pp,center)
|
251
|
+
basep.append(pp)
|
252
|
+
|
253
|
+
|
254
|
+
basef=[]
|
255
|
+
|
256
|
+
rng = range(1,len(basep))
|
257
|
+
if not zup:
|
258
|
+
rng = reversed(rng)
|
259
|
+
|
260
|
+
ll = len(basep)-1
|
261
|
+
for i in rng:
|
262
|
+
face = []
|
263
|
+
if zup:
|
264
|
+
face = [0,i,1+i%ll]
|
265
|
+
else:
|
266
|
+
face = [0,1+i%ll,i]
|
267
|
+
|
268
|
+
basef.append(face)
|
269
|
+
|
270
|
+
z=-1
|
271
|
+
if zup:
|
272
|
+
z=1
|
273
|
+
n = vect(0,0,z,0)
|
274
|
+
basen= [ n ] * len(basep)
|
275
|
+
|
276
|
+
return surface(basep,basen,basef)
|
277
|
+
|
278
|
+
def conic(baser,topr,height, center=point(0,0,0),angr=10):
|
279
|
+
|
280
|
+
"""Make a conic frustum splid, center is center of first 'base'
|
281
|
+
circle, main axis aligns with positive z. This function can be
|
282
|
+
used to make a cone, a conic frustum, or a cylinder, depending on
|
283
|
+
the parameters.
|
284
|
+
|
285
|
+
``baser`` is the radius of the base, must be greater than
|
286
|
+
zero (epsilon).
|
287
|
+
|
288
|
+
``topr`` is the radius of the top, may be zero or
|
289
|
+
positive. If ``0 <= topr < epsilon``, then the top
|
290
|
+
is treated as a single point.
|
291
|
+
|
292
|
+
``height`` is distance from base to top, must be greater than
|
293
|
+
epsilon.
|
294
|
+
|
295
|
+
``center`` is the location of the center of the base.
|
296
|
+
|
297
|
+
``angr`` is the requested angular resolution in degrees for
|
298
|
+
sampling circles. Actual angular resolution will be
|
299
|
+
``360/round(360/angr)``
|
300
|
+
|
301
|
+
"""
|
302
|
+
call = f"yapcad.geom3d_util.conic({baser},{topr},{height},{center},{angr})"
|
303
|
+
if baser < epsilon:
|
304
|
+
raise ValueError('bad base radius for conic')
|
305
|
+
base = arc(center,baser)
|
306
|
+
|
307
|
+
toppoint = False
|
308
|
+
if topr < 0:
|
309
|
+
raise ValueError('bad top radius for conic')
|
310
|
+
if topr < epsilon:
|
311
|
+
toppoint = True
|
312
|
+
|
313
|
+
if height < epsilon:
|
314
|
+
raise ValueError('bad height in conic')
|
315
|
+
|
316
|
+
baseS = circleSurface(center,baser,zup=False)
|
317
|
+
baseV = baseS[1]
|
318
|
+
ll = len(baseV)
|
319
|
+
|
320
|
+
if not toppoint:
|
321
|
+
topS = circleSurface(add(center,point(0,0,height)),
|
322
|
+
topr,zup=True)
|
323
|
+
topV = topS[1]
|
324
|
+
cylV = baseV[1:] + topV[1:]
|
325
|
+
ll = ll-1
|
326
|
+
baseN = []
|
327
|
+
topN = []
|
328
|
+
cylF = []
|
329
|
+
for i in range(ll):
|
330
|
+
p0 = cylV[(i-1)%ll]
|
331
|
+
p1 = cylV[(i+1)%ll]
|
332
|
+
p2 = cylV[ll+i]
|
333
|
+
|
334
|
+
cylF.append([i,(i+1)%ll,ll+(i+1)%ll])
|
335
|
+
cylF.append([i,ll+(i+1)%ll,ll+i])
|
336
|
+
|
337
|
+
pp,n0 = tri2p0n([p0,p1,p2])
|
338
|
+
|
339
|
+
baseN.append(n0)
|
340
|
+
topN.append(n0)
|
341
|
+
|
342
|
+
cylN = baseN+topN
|
343
|
+
|
344
|
+
cylS = surface(cylV,cylN,cylF)
|
345
|
+
|
346
|
+
return solid([baseS,cylS,topS],[],
|
347
|
+
['procedure',call])
|
348
|
+
else:
|
349
|
+
topP = add(center,point(0,0,height))
|
350
|
+
conV = [ topP ] + baseV
|
351
|
+
ll = len(conV)
|
352
|
+
conN = [[0,0,1,0]]
|
353
|
+
conF = []
|
354
|
+
|
355
|
+
for i in range(1,ll):
|
356
|
+
p0= conV[0]
|
357
|
+
p1= conV[(i-1)%ll]
|
358
|
+
p2= conV[(i+1)%ll]
|
359
|
+
|
360
|
+
conF.append([0,i,(i+1)%ll])
|
361
|
+
pp,n0 = tri2p0n([p0,p1,p2])
|
362
|
+
|
363
|
+
conN.append(n0)
|
364
|
+
|
365
|
+
conS = surface(conV,conN,conF)
|
366
|
+
|
367
|
+
return solid([baseS,conS],[],
|
368
|
+
['procedure',call])
|
369
|
+
|
370
|
+
def makeRevolutionSurface(contour,zStart,zEnd,steps,arcSamples=36):
|
371
|
+
"""
|
372
|
+
Take a countour (any function z->y mapped over the interval
|
373
|
+
|
374
|
+
``zStart`` and ``zEnd`` and produce the surface of revolution
|
375
|
+
around the z axis. Sample ``steps`` contours of the function,
|
376
|
+
which in turn are turned into circles sampled `arcSamples`` times.
|
377
|
+
"""
|
378
|
+
|
379
|
+
sV=[]
|
380
|
+
sN=[]
|
381
|
+
sF=[]
|
382
|
+
zRange = zEnd-zStart
|
383
|
+
zD = zRange/steps
|
384
|
+
|
385
|
+
degStep = 360.0/arcSamples
|
386
|
+
radStep = pi2/arcSamples
|
387
|
+
for i in range(steps):
|
388
|
+
z = i*zD+zStart
|
389
|
+
r0 = contour(z)
|
390
|
+
r1 = contour(z+zD)
|
391
|
+
if r0 < epsilon*10:
|
392
|
+
r0 = epsilon*10
|
393
|
+
if r1 < epsilon*10:
|
394
|
+
r1 = epsilon*10
|
395
|
+
for j in range(arcSamples):
|
396
|
+
a0 = (j-1)*radStep
|
397
|
+
a1 = j*radStep
|
398
|
+
a2 = (j+1)*radStep
|
399
|
+
|
400
|
+
p0 = [math.cos(a0)*r0,math.sin(a0)*r0,z,1.0]
|
401
|
+
p1 = [math.cos(a1)*r0,math.sin(a1)*r0,z,1.0]
|
402
|
+
p2 = [math.cos(a2)*r0,math.sin(a2)*r0,z,1.0]
|
403
|
+
|
404
|
+
pp1 = [math.cos(a1)*r1,math.sin(a1)*r1,z+zD,1.0]
|
405
|
+
pp2 = [math.cos(a2)*r1,math.sin(a2)*r1,z+zD,1.0]
|
406
|
+
|
407
|
+
p,n = tri2p0n([p0,p2,pp1])
|
408
|
+
|
409
|
+
k1,sV,sN = addVertex(p1,n,sV,sN)
|
410
|
+
k2,sV,sN = addVertex(p2,n,sV,sN)
|
411
|
+
k3,sV,sN = addVertex(pp2,n,sV,sN)
|
412
|
+
k4,sV,sN = addVertex(pp1,n,sV,sN)
|
413
|
+
sF.append([k1,k2,k3])
|
414
|
+
sF.append([k1,k3,k4])
|
415
|
+
|
416
|
+
return surface(sV,sN,sF)
|
417
|
+
|
418
|
+
def contour(poly,distance,direction, samples,scalefunc= lambda x: (1,1,1)):
|
419
|
+
"""take a closed polygon and apply a scaling function defined on the
|
420
|
+
interval 0,1 that returns a tuple of x,y,z scaling values. For
|
421
|
+
each of ``samples`` number of samples, translate in ``direction``
|
422
|
+
direction and scale the contour according to ``scalefunc()``,
|
423
|
+
producing a surface."""
|
424
|
+
if not ispolygon(poly):
|
425
|
+
raise ValueError('invalid polygon passed to contour')
|
426
|
+
if samples < 2:
|
427
|
+
raise ValueError('number of samples must be 2 or greater')
|
428
|
+
if not close(mag(direction),1.0):
|
429
|
+
raise ValueError('bad direction vector')
|
430
|
+
if distance <= epsilon:
|
431
|
+
raise ValueError('bad distance passed to contour')
|
432
|
+
|
433
|
+
raise NotImplemented("this function is a work in progress")
|
434
|
+
|
435
|
+
p0 = poly
|
436
|
+
p1 = []
|
437
|
+
|
438
|
+
u = 0.0
|
439
|
+
scle = scalefunc(u)
|
440
|
+
sctx = Scale(scle[0],scle[1],scle[2])
|
441
|
+
ply = list(map(lambda p: sctx.mul(p),poly))
|
442
|
+
surf = poly2surface(ply)
|
443
|
+
s1 = reversesurface(surf)
|
444
|
+
|
445
|
+
u = 1.0
|
446
|
+
scle = scalefunc(u)
|
447
|
+
sctx = Scale(scle[0],scle[1],scle[2])
|
448
|
+
ply2 = list(map(lambda p: sctx.mul(p),poly))
|
449
|
+
surf2 = poly2surface(ply2)
|
450
|
+
s2 = translatesurface(surf2,scale4(direction,distance))
|
451
|
+
|
452
|
+
vrts = surf[1]
|
453
|
+
nrms = surf[2]
|
454
|
+
facs = surf[3]
|
455
|
+
bndr = surf[4]
|
456
|
+
|
457
|
+
vrts1 = list(map(lambda i: vrts[i],bndr))
|
458
|
+
for ii in range(1,samples):
|
459
|
+
u = ii/samples
|
460
|
+
|
461
|
+
stripF = []
|
462
|
+
#vrts2 = list(map(lambda i:
|
463
|
+
for i in range(len(bndr)):
|
464
|
+
j0 = bndry1[(i-1)%len(bndry1)]
|
465
|
+
j1 = bndry1[i]
|
466
|
+
j2 = bndry1[(i+1)%len(bndry1)]
|
467
|
+
j3 = j2+len(s2[1])
|
468
|
+
j4 = j1+len(s2[1])
|
469
|
+
p0 = stripV[j0]
|
470
|
+
p1 = stripV[j2]
|
471
|
+
p2 = stripV[j3]
|
472
|
+
try:
|
473
|
+
pp,n0 = tri2p0n([p0,p1,p2])
|
474
|
+
except ValueError:
|
475
|
+
# bad face, skip
|
476
|
+
continue
|
477
|
+
stripN[j1]=n0
|
478
|
+
stripN[j4]=n0
|
479
|
+
stripF.append([j1,j2,j3])
|
480
|
+
stripF.append([j1,j3,j4])
|
481
|
+
|
482
|
+
|
483
|
+
|
484
|
+
|
485
|
+
def extrude(surf,distance,direction=vect(0,0,1,0)):
|
486
|
+
|
487
|
+
""" Take a surface and extrude it in the specified direction to
|
488
|
+
create a solid. Return the solid. """
|
489
|
+
call = f"yapcad.geom3d_util.extrude({surf},{distance},{direction})"
|
490
|
+
|
491
|
+
if not issurface(surf):
|
492
|
+
raise ValueError('invalid surface passed to extrude')
|
493
|
+
|
494
|
+
if distance <= epsilon:
|
495
|
+
raise ValueError('bad distance passed to extrude')
|
496
|
+
|
497
|
+
s1 = translatesurface(surf,scale4(direction,distance))
|
498
|
+
s2 = reversesurface(surf)
|
499
|
+
|
500
|
+
loops = []
|
501
|
+
if s2[4]:
|
502
|
+
loops.append(s2[4])
|
503
|
+
loops.extend([loop for loop in s2[5] if loop])
|
504
|
+
|
505
|
+
stripV = s2[1] + s1[1] # vertices for the edge strips
|
506
|
+
stripN = [vect(0, 0, 1, 0)] * len(stripV) # placeholder normals
|
507
|
+
stripF: list[list[int]] = []
|
508
|
+
offset = len(s2[1])
|
509
|
+
|
510
|
+
for bndry in loops:
|
511
|
+
if len(bndry) < 2:
|
512
|
+
continue
|
513
|
+
for i in range(len(bndry)):
|
514
|
+
j0 = bndry[(i - 1) % len(bndry)]
|
515
|
+
j1 = bndry[i]
|
516
|
+
j2 = bndry[(i + 1) % len(bndry)]
|
517
|
+
j3 = j2 + offset
|
518
|
+
j4 = j1 + offset
|
519
|
+
p0 = stripV[j0]
|
520
|
+
p1 = stripV[j2]
|
521
|
+
p2 = stripV[j3]
|
522
|
+
try:
|
523
|
+
pp, n0 = tri2p0n([p0, p1, p2])
|
524
|
+
except ValueError:
|
525
|
+
continue
|
526
|
+
stripN[j1] = n0
|
527
|
+
stripN[j4] = n0
|
528
|
+
stripF.append([j1, j2, j3])
|
529
|
+
stripF.append([j1, j3, j4])
|
530
|
+
|
531
|
+
#import pdb ; pdb.set_trace()
|
532
|
+
strip = surface(stripV,stripN,stripF)
|
533
|
+
|
534
|
+
return solid([s2,strip,s1],
|
535
|
+
[],
|
536
|
+
['procedure',call])
|
537
|
+
|
538
|
+
|
539
|
+
|
540
|
+
|
541
|
+
|