yapCAD 0.2.5__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/__init__.py +8 -8
- yapcad/combine.py +83 -371
- yapcad/drawable.py +44 -3
- yapcad/ezdxf_drawable.py +1 -1
- yapcad/geom.py +214 -276
- yapcad/geom3d.py +975 -55
- yapcad/geom3d_util.py +541 -0
- yapcad/geom_util.py +817 -0
- yapcad/geometry.py +442 -50
- 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 +10 -5
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info}/METADATA +110 -39
- yapcad-0.3.1.dist-info/RECORD +27 -0
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info}/WHEEL +1 -1
- yapCAD-0.2.5.dist-info/RECORD +0 -17
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info/licenses}/AUTHORS.rst +0 -0
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info/licenses}/LICENSE +0 -0
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info/licenses}/LICENSE.txt +0 -0
- {yapCAD-0.2.5.dist-info → yapcad-0.3.1.dist-info}/top_level.txt +0 -0
yapcad/geom3d.py
CHANGED
@@ -3,60 +3,980 @@
|
|
3
3
|
## Richard W. DeVaul
|
4
4
|
|
5
5
|
from yapcad.geom import *
|
6
|
+
from yapcad.geom_util import *
|
7
|
+
from yapcad.xform import *
|
8
|
+
from functools import reduce
|
9
|
+
from yapcad.triangulator import triangulate_polygon
|
6
10
|
|
7
|
-
## the geometric representations of point, line, arc, poly, and
|
8
|
-
## geomlist provided by yapcad.geom are suitable for representing
|
9
|
-
## zero- or one-dimensional figures embedded in a three-dimensional
|
10
|
-
## space. And while there is no requirement that the representations
|
11
|
-
## provided by yapcad.geom (such as an arc, line, or polyline) lie in
|
12
|
-
## the XY plane, many of the functions in yapcad.geom are explicitly
|
13
|
-
## intended to perform computational geometry operations on XY-planar
|
14
|
-
## entities.
|
15
|
-
|
16
|
-
## Further, while a closed figure described by yapcad.geom may
|
17
|
-
## implicitly bound a two-dimensional face, there is little support
|
18
|
-
## for working with two-dimensional surfaces provided by that module.
|
19
|
-
## There is no direct support of any kind for working with
|
20
|
-
## three-dimemnsional volumes in yapcad.geom.
|
21
|
-
|
22
|
-
## in this module, we define the concept of a parametric
|
23
|
-
## two-dimensional surface and three-dimensional volume, and provide
|
24
|
-
## implicit and explicit geometry operations for working with them.
|
25
|
-
|
26
|
-
## The goal of the geom3d.yapcad module is to allow for the
|
27
|
-
## construction of two-dimensinal surfaces and three-dimensional
|
28
|
-
## geometry for the purposes of modeling, computational geometry, and
|
29
|
-
## rendering. Specifically, we wish to support the following:
|
30
|
-
|
31
|
-
## (1) Support the implicit representation of three-dimensional
|
32
|
-
## geometry, and the performance of constructive solid geometry
|
33
|
-
## operations on this implicit geometry (union, intersection,
|
34
|
-
## difference) to produce more complex implicit three dimensional
|
35
|
-
## forms.
|
36
|
-
|
37
|
-
## (2) Support the implicit representation of two dimensional
|
38
|
-
## surfaces, such as a planar surface specified by three points, or
|
39
|
-
## the surface of a three-dimensional object like a sphere, and allow
|
40
|
-
## for computational geometry operations on these surfaces, such as
|
41
|
-
## intersection operations, to produce explicit one-dimensional
|
42
|
-
## objects, such as lines, arcs, etc.
|
43
|
-
|
44
|
-
## (3) support the conversion of an implicit two-dimensional surface
|
45
|
-
## to an explicit, teselated triangluar geometry that may be easily
|
46
|
-
## rendered using a conventional 3D graphics rendering pipline, such
|
47
|
-
## as OpenGL
|
48
|
-
|
49
|
-
## (4) Support for the conversion of implicit three-dimenaional
|
50
|
-
## constructive solid geometry into an explicit, contiguous closed
|
51
|
-
## surface representation using the marching cubes algortihm, or any
|
52
|
-
## other user-specified conversion algoritm, for the purposes of
|
53
|
-
## interactive 3D rendering and conversion to 3D CAM formats, such as
|
54
|
-
## STL.
|
55
|
-
|
56
|
-
## Structure for 2D/3D geometry:
|
57
|
-
|
58
|
-
## geom3d in tuple( <type>, <transform>, <primary representation>,
|
59
|
-
## <sampled representation>, <rendering hints> )
|
60
|
-
## where:
|
61
|
-
## type in tuple ('surface' | 'solid' | 'transform', <subtype>)
|
62
11
|
|
12
|
+
"""
|
13
|
+
==========================================================
|
14
|
+
geom3d -- functional 3D geometry representation for yapCAD
|
15
|
+
==========================================================
|
16
|
+
|
17
|
+
The geometric representations of point, line, arc, poly, and
|
18
|
+
geomlist provided by ``yapcad.geom`` are suitable for representing
|
19
|
+
zero- or one-dimensional figures embedded in a three-dimensional
|
20
|
+
space. And while there is no requirement that the representations
|
21
|
+
provided by ``yapcad.geom`` (such as an arc, line, or polyline) lie in
|
22
|
+
the XY plane, many of the functions in yapcad.geom are explicitly
|
23
|
+
intended to perform computational geometry operations on XY-planar
|
24
|
+
entities.
|
25
|
+
|
26
|
+
Further, while a closed figure described by ``yapcad.geom`` may
|
27
|
+
implicitly bound a two-dimensional face, there is no direct support
|
28
|
+
for working with two-dimensional surfaces provided by that module.
|
29
|
+
There is no direct support of any kind for working with
|
30
|
+
three-dimemnsional volumes in ``yapcad.geom``.
|
31
|
+
|
32
|
+
In this module we specify representations for two-dimensional surfaces
|
33
|
+
and bounded three-dimensional volumes, and provide tools for working
|
34
|
+
with them in implicit, parametric form as well as in explicit,
|
35
|
+
triangulated form.
|
36
|
+
|
37
|
+
The goal of the ``geom3d.yapcad module`` is to allow for the
|
38
|
+
construction of two-dimensinal surfaces and three-dimensional
|
39
|
+
geometry for the purposes of modeling, computational geometry, and
|
40
|
+
rendering. Specifically, we wish to support the following:
|
41
|
+
|
42
|
+
(1) Support the implicit representation of three-dimensional
|
43
|
+
geometry, and the performance of constructive solid geometry
|
44
|
+
operations on this implicit geometry (union, intersection,
|
45
|
+
difference) to produce more complex implicit three dimensional
|
46
|
+
forms.
|
47
|
+
|
48
|
+
(2) Support the implicit representation of two dimensional
|
49
|
+
surfaces, such as a planar surface specified by three points, or
|
50
|
+
the surface of a three-dimensional object like a sphere, and allow
|
51
|
+
for computational geometry operations on these surfaces, such as
|
52
|
+
intersection operations, to produce explicit one-dimensional
|
53
|
+
objects, such as lines, arcs, etc.
|
54
|
+
|
55
|
+
(3) support the conversion of an implicit two-dimensional surface
|
56
|
+
to an explicit, teselated triangluar geometry that may be easily
|
57
|
+
rendered using a conventional 3D graphics rendering pipline, such
|
58
|
+
as OpenGL
|
59
|
+
|
60
|
+
(4) Support for the conversion of implicit three-dimenaional
|
61
|
+
constructive solid geometry into an explicit, contiguous closed
|
62
|
+
surface representation using the marching cubes algortihm, or any
|
63
|
+
other user-specified conversion algoritm, for the purposes of
|
64
|
+
interactive 3D rendering and conversion to 3D CAM formats, such as
|
65
|
+
STL.
|
66
|
+
|
67
|
+
Structures for 2D/3D geometry
|
68
|
+
=============================
|
69
|
+
|
70
|
+
surfaces
|
71
|
+
--------
|
72
|
+
|
73
|
+
``surface = ['surface',vertices,normals,faces,boundary,holes]``, where:
|
74
|
+
|
75
|
+
``vertices`` is a list of ``yapcad.geom`` points,
|
76
|
+
|
77
|
+
``normals`` is a list of ``yapcad.geom`` direction vectors
|
78
|
+
of the same length as ``vertices``,
|
79
|
+
|
80
|
+
``faces`` is the list of faces, which is to say lists
|
81
|
+
of three indices that refer to the vertices of the triangle
|
82
|
+
that represents each face,
|
83
|
+
|
84
|
+
``boundary`` is a list of indices for the vertices that form
|
85
|
+
the outer perimeter of the surface, or [] if the surface
|
86
|
+
has no boundary, such as that of a torus or sphere
|
87
|
+
|
88
|
+
``holes`` is a (potentially zero-length) list of lists of
|
89
|
+
holes, each of which is a non-zero list of three or more
|
90
|
+
indices of vertices that form the perimeter of any holes in
|
91
|
+
the surface.
|
92
|
+
|
93
|
+
A surface has an inside and an outside. To deterine which side of a
|
94
|
+
surface a point lies on, find the closest face and determine if the
|
95
|
+
point lies on the positive or negative side of the face.
|
96
|
+
|
97
|
+
Solids
|
98
|
+
------
|
99
|
+
|
100
|
+
To represent a completely bounded space (a space for which any point
|
101
|
+
can be unambiguously determined to be inside or outside) there is the
|
102
|
+
``solid`` representation. A solid is composed of zero or more
|
103
|
+
surfaces, and may be completely empty, as empty solids are legal
|
104
|
+
products of constructive solid geometry operations like intersection
|
105
|
+
and difference.
|
106
|
+
|
107
|
+
The gurantee, which is not enforced by the representation, is that for
|
108
|
+
any point inside the bounding box of the solid, and for any point
|
109
|
+
chosen outside the bounding box of the solid, a line drawn between the
|
110
|
+
two will have either even (or zero), or odd point intersections (as
|
111
|
+
opposed to tangent intersections), regardless of the choice of the
|
112
|
+
outside-the-bounding-box point.
|
113
|
+
|
114
|
+
Solids have optional associated metadata about the material properties
|
115
|
+
of the solid and about how it was constructed.
|
116
|
+
|
117
|
+
For example, material properties might include OpenGL-type rendering
|
118
|
+
data, mechanical properties, or a reference to a material dictionary
|
119
|
+
that includes both.
|
120
|
+
|
121
|
+
Construction meta-data might include the model-file and material-file
|
122
|
+
name from which the geometry was loaded, the polygon from which the
|
123
|
+
solid was extruded (and associated extrusion parameters), or the
|
124
|
+
function call with parameters for algorithmically-generated geometry.
|
125
|
+
|
126
|
+
``solid = ['solid', surfaces, material, construction ]``, where:
|
127
|
+
|
128
|
+
``surfaces`` is a list of surfaces with contiguous boundaries
|
129
|
+
that completely encloses an interior space,
|
130
|
+
|
131
|
+
``material`` is a list of domain-specific representation of
|
132
|
+
the material properties of the solid, which may be empty.
|
133
|
+
This information may be used for rendering or simulating
|
134
|
+
the properties of the solid.
|
135
|
+
|
136
|
+
``construction`` is a list that contains information about
|
137
|
+
how the solid was constructed, and may be empty
|
138
|
+
|
139
|
+
|
140
|
+
Assembly
|
141
|
+
--------
|
142
|
+
|
143
|
+
Assemblies are lists of elements, which are solids or assemblies, in
|
144
|
+
which each list has an associated geometric transformation.
|
145
|
+
|
146
|
+
``assembly = ['assembly', transform, elementlist]``, where:
|
147
|
+
|
148
|
+
``transform = [xformF, xformR]``, a pair of forward and inverse
|
149
|
+
transformation matricies such that ``xformF`` * ``xformR`` =
|
150
|
+
the identity matrix.
|
151
|
+
|
152
|
+
``elementlist = [element0, elememnt1, ... ]``, in which each
|
153
|
+
list element is either a valid ``solid`` or ``assembly``.
|
154
|
+
|
155
|
+
"""
|
156
|
+
|
157
|
+
def signedPlaneDistance(p,p0,n):
|
158
|
+
"""Given a point on the plane ``p0``, and the unit-length plane
|
159
|
+
normal ``n``, determine the signed distance to the plane.
|
160
|
+
"""
|
161
|
+
return dot(sub(p,p0),n)
|
162
|
+
|
163
|
+
def tri2p0n(face,basis=False):
|
164
|
+
"""Given ``face``, a non-degenrate poly of length 3, return the center
|
165
|
+
point and normal of the face, otherwise known as the Hessian
|
166
|
+
Normal Form: https://mathworld.wolfram.com/HessianNormalForm.html
|
167
|
+
|
168
|
+
In addition, if ``basis==True``, calculate an orthnormal basis
|
169
|
+
vectors implied by the triangle, with the x' vector aligned with
|
170
|
+
the p1-p2 edge, the z' vector as the nornal vector, and the y'
|
171
|
+
vector as -1 * x' x z', and return the transformation matrix that
|
172
|
+
will transform a point in world coordinates to a point in the
|
173
|
+
orthonormal coordinate system with the origin at the center
|
174
|
+
point. Return that transformation matrix and its inverse.
|
175
|
+
|
176
|
+
"""
|
177
|
+
# import pdb ; pdb.set_trace()
|
178
|
+
p1=face[0]
|
179
|
+
p2=face[1]
|
180
|
+
p3=face[2]
|
181
|
+
p0 = scale3(add(p1,add(p2,p3)),1.0/3.0)
|
182
|
+
v1=sub(p2,p1)
|
183
|
+
v2=sub(p3,p2)
|
184
|
+
c= cross(v1,v2)
|
185
|
+
m = mag(c)
|
186
|
+
if m < epsilon:
|
187
|
+
raise ValueError('degenerate face in tri2p0n')
|
188
|
+
n= scale3(c,1.0/m)
|
189
|
+
n[3] = 0.0 #direction vectors lie in the w=0 hyperplane
|
190
|
+
if not basis:
|
191
|
+
return [p0,n]
|
192
|
+
else:
|
193
|
+
# compute orthonormal basis vectors
|
194
|
+
x=scale3(v1,1.0/mag(v1))
|
195
|
+
z=n
|
196
|
+
y=scale3(cross(x,z),-1)
|
197
|
+
# direction vectors lie in the w=0 hyperplane
|
198
|
+
x[3] = y[3] = z[3] = 0.0
|
199
|
+
# build the rotation matrix
|
200
|
+
T = [x,
|
201
|
+
y,
|
202
|
+
z,
|
203
|
+
[0,0,0,1]]
|
204
|
+
rm = Matrix(T)
|
205
|
+
# build translation matrix
|
206
|
+
tm = Translation(scale3(p0,-1))
|
207
|
+
# make composed matrix
|
208
|
+
forward = rm.mul(tm)
|
209
|
+
# make inverse matrix
|
210
|
+
rm.trans=True
|
211
|
+
inverse = Translation(p0).mul(rm)
|
212
|
+
return [p0,n,forward,inverse]
|
213
|
+
|
214
|
+
|
215
|
+
def signedFaceDistance(p,face):
|
216
|
+
"""given a test point ``p`` and a three-point face ``face``, determine
|
217
|
+
the minimum signed distance of the test point from the face. Any
|
218
|
+
point lying in the positive normal direction or on the surface of
|
219
|
+
the plane will result in a zero or positive distace. Any point
|
220
|
+
lying in the negative normal direction from the surface of the
|
221
|
+
plane will result in a negative distance.
|
222
|
+
|
223
|
+
"""
|
224
|
+
p0,n = tuple(tri2p0n(face))
|
225
|
+
d = sub(p,face[0])
|
226
|
+
m = -dot(d,n) # negative of distance of p from plane
|
227
|
+
a = add(p,scale3(n,m)) # projection of point p into plane
|
228
|
+
|
229
|
+
# create a coordinate system based on the first face edge and the
|
230
|
+
# plane normal
|
231
|
+
v1 = sub(face[1],face[0])
|
232
|
+
v2 = sub(face[2],face[0])
|
233
|
+
vx = scale3(v1,1.0/mag(v1))
|
234
|
+
vy = scale3(cross(vx,n),-1)
|
235
|
+
vz = n
|
236
|
+
p1=[0,0,0,1]
|
237
|
+
p2=[mag(v1),0,0,1]
|
238
|
+
p3=[dot(v2,vx),dot(v2,vy),0,1]
|
239
|
+
aa= sub(a,face[0])
|
240
|
+
aa=[dot(aa,vx),dot(aa,vy),0,1]
|
241
|
+
|
242
|
+
#barycentric coordinates for derermining if projected point falls
|
243
|
+
#inside face
|
244
|
+
lam1,lam2,lam3 = tuple(barycentricXY(aa,p1,p2,p3))
|
245
|
+
inside = ( (lam1 >= 0.0 and lam1 <= 1.0) and
|
246
|
+
(lam2 >= 0.0 and lam2 <= 1.0) and
|
247
|
+
(lam3 >= 0.0 and lam3 <= 1.0) )
|
248
|
+
|
249
|
+
ind = 0 # in-plane distance is zero if projected point inside
|
250
|
+
# triangle
|
251
|
+
if not inside:
|
252
|
+
d1 = linePointXY([p1,p2],aa,distance=True)
|
253
|
+
d2 = linePointXY([p2,p3],aa,distance=True)
|
254
|
+
d3 = linePointXY([p3,p1],aa,distance=True)
|
255
|
+
ind = min(d1,d2,d3) #in-plane distance is smallest distance from each edge
|
256
|
+
|
257
|
+
if close(m,0): # point lies in plane
|
258
|
+
return ind
|
259
|
+
else:
|
260
|
+
dist = sqrt(m*m+ind*ind) # total distance is hypotenuse of
|
261
|
+
# right-triangle of in-plane and
|
262
|
+
# out-plane distance
|
263
|
+
return copysign(dist,-1*m)
|
264
|
+
|
265
|
+
def linePlaneIntersect(lne,plane="xy",inside=True):
|
266
|
+
"""Function to calculate the intersection of a line and a plane.
|
267
|
+
|
268
|
+
``line`` is specified in the usual way as two points. ``plane``
|
269
|
+
is either specified symbolicly as one of ``["xy","yz","xz"]``, a
|
270
|
+
list of three points, or a planar coordinate system in the form of
|
271
|
+
``[p0,n,forward,reverse]``, where ``p0`` specifies the origin in
|
272
|
+
world coordinates, ``n`` is the normal (equivalent to the ``z``
|
273
|
+
vector), and ``forward`` and ``reverse`` are transformation
|
274
|
+
matricies that map from world into local and local into world
|
275
|
+
coordinates respectively.
|
276
|
+
|
277
|
+
Returns ``False`` if the line and plane do not intersect, or if
|
278
|
+
``inside==True`` and the point of intersection is outside the line
|
279
|
+
interval. Returns the point of intersection otherwise.
|
280
|
+
|
281
|
+
NOTE: if plane is specified as three points, then setting
|
282
|
+
``inside=True`` will also force a check to see if the intersection
|
283
|
+
point falls within the specified triangle.
|
284
|
+
|
285
|
+
"""
|
286
|
+
|
287
|
+
def lineCardinalPlaneIntersect(lne,idx,inside=True):
|
288
|
+
if close(lne[0][idx]-lne[1][idx],0.0): #degenerate
|
289
|
+
return False
|
290
|
+
# (1-u)*l[0][idx] + u*l[1][idx] = 0.0
|
291
|
+
# u*(l[1][idx]-l[0][idx]) = l[0][idx]
|
292
|
+
# u = l[0][idx]/(l[1][idx]-l[0][idx])
|
293
|
+
u = lne[0][idx]/(lne[0][idx]-lne[1][idx])
|
294
|
+
if inside and (u < 0.0 or u > 1.0):
|
295
|
+
return False
|
296
|
+
else:
|
297
|
+
return sampleline(lne,u)
|
298
|
+
|
299
|
+
# is the plane specified symbolicaly?
|
300
|
+
trangle = False
|
301
|
+
idx = -1
|
302
|
+
if plane=="xy":
|
303
|
+
idx=2
|
304
|
+
elif plane=="yz":
|
305
|
+
idx=0
|
306
|
+
elif plane=="xz":
|
307
|
+
idx=1
|
308
|
+
|
309
|
+
if idx > -1:
|
310
|
+
return lineCardinalPlaneIntersect(lne,idx,inside)
|
311
|
+
else:
|
312
|
+
if istriangle(plane):
|
313
|
+
triangle = True
|
314
|
+
tri = plane
|
315
|
+
plane = tri2p0n(plane,basis=True)
|
316
|
+
tri2 = [ plane[2].mul(tri[0]),
|
317
|
+
plane[2].mul(tri[1]),
|
318
|
+
plane[2].mul(tri[2]) ]
|
319
|
+
else:
|
320
|
+
raise ValueError('non-plane passed to linePlaneIntersect')
|
321
|
+
# otherwise assume that plane is a valid planar basis
|
322
|
+
|
323
|
+
# transform into basis with plane at z=0
|
324
|
+
l2 = [plane[2].mul(lne[0]),plane[2].mul(lne[1])]
|
325
|
+
p = lineCardinalPlaneIntersect(l2,2,inside)
|
326
|
+
if not p:
|
327
|
+
return False
|
328
|
+
else:
|
329
|
+
if triangle and not isInsideTriangleXY(p,tri2):
|
330
|
+
return False
|
331
|
+
return plane[3].mul(p)
|
332
|
+
|
333
|
+
|
334
|
+
def triTriIntersect(t1,t2,inside=True,inPlane=False,basis=None):
|
335
|
+
|
336
|
+
"""Function to compute the intersection of two triangles. Returns
|
337
|
+
``False`` if no intersection, a line (a list of two points) if the
|
338
|
+
planes do not overlap and there is a linear intersection, and a
|
339
|
+
polygon (list of three or more points) if the triangles are
|
340
|
+
co-planar and overlap.
|
341
|
+
|
342
|
+
If ``inside == True`` (default) return line-segment or poly
|
343
|
+
intersection that falls inside both bounded triangles, otherwise
|
344
|
+
return a line segment that lies on the infinite linear
|
345
|
+
intersection of two planes, or False if planes are degenerate.
|
346
|
+
|
347
|
+
If ``inPlane==True``, return the intersection as a poly in the
|
348
|
+
planar coordinate system implied by ``t1``, or in the planar
|
349
|
+
coordinate system specified by ``basis``
|
350
|
+
|
351
|
+
If ``basis`` is not ``False``, it should be planar coordinate
|
352
|
+
system in the form of ``[p0,n,forward,reverse]``, where ``p0``
|
353
|
+
specifies the origin in world coordinates, ``n`` is the normal
|
354
|
+
(equivalent to the ``z`` vector), and ``forward`` and ``reverse``
|
355
|
+
are transformation matricies that map from world into local and
|
356
|
+
local into world coordinates respectively.
|
357
|
+
|
358
|
+
NOTE: when ``basis`` is True and ``inPlane`` is False, it is
|
359
|
+
assumed that ``basis`` is a planar basis computed by tri2p0n
|
360
|
+
coplanar with ``t1``.
|
361
|
+
|
362
|
+
"""
|
363
|
+
if not basis:
|
364
|
+
#create basis from t1
|
365
|
+
basis = tri2p0n(t1,basis=True)
|
366
|
+
p01,n1,tfor,tinv = tuple(basis)
|
367
|
+
|
368
|
+
p02,n2 = tuple(tri2p0n(t2,basis=False))
|
369
|
+
|
370
|
+
#transform both triangles into new coordinate system
|
371
|
+
t1p = list(map(lambda x: tfor.mul(x),t1))
|
372
|
+
t2p = list(map(lambda x: tfor.mul(x),t2))
|
373
|
+
|
374
|
+
# check for coplanar case
|
375
|
+
if (abs(t2p[0][2]) <= epsilon and abs(t2p[1][2]) <= epsilon
|
376
|
+
and abs(t2p[2][2]) <= epsilon):
|
377
|
+
if not inside:
|
378
|
+
if inPlane:
|
379
|
+
return t2p
|
380
|
+
else:
|
381
|
+
return t2
|
382
|
+
else: # return poly that is in-plane intersection
|
383
|
+
intr = combineglist(t1p,t2p,'intersection')
|
384
|
+
if len(intr) < 1: #no intersection
|
385
|
+
return False
|
386
|
+
else:
|
387
|
+
if inPlane:
|
388
|
+
return intr
|
389
|
+
else:
|
390
|
+
return transform(intr,tinv)
|
391
|
+
# not coplanar, check to see if planes are parallel
|
392
|
+
if vclose(n1,n2):
|
393
|
+
return False #yep, degenerate
|
394
|
+
|
395
|
+
if inside:
|
396
|
+
# check to see if t2p lies entirely above or below the z=0 plane
|
397
|
+
if ((t2p[0][2] > epsilon and t2p[1][2] > epsilon and t2p[2][2] > epsilon)
|
398
|
+
or
|
399
|
+
(t2p[0][2] < -epsilon and t2p[1][2] < -epsilon and
|
400
|
+
t2p[2][2] < -epsilon)):
|
401
|
+
return False
|
402
|
+
# linear intersection. Figure out which two of three lines
|
403
|
+
# cross the z=0 plane
|
404
|
+
|
405
|
+
# this should work whether or not the intersection is
|
406
|
+
# inside t2.
|
407
|
+
ip1 = linePlaneIntersect([t2p[0],t2p[1]],"xy",False)
|
408
|
+
ip2 = linePlaneIntersect([t2p[1],t2p[2]],"xy",False)
|
409
|
+
ip3 = linePlaneIntersect([t2p[2],t2p[0]],"xy",False)
|
410
|
+
|
411
|
+
a=ip1
|
412
|
+
b=ip2
|
413
|
+
if not a:
|
414
|
+
a=ip3
|
415
|
+
if not b:
|
416
|
+
b=ip3
|
417
|
+
if inPlane:
|
418
|
+
return [a,b]
|
419
|
+
else:
|
420
|
+
return [tinv.mul(a),tinv.mul(b)]
|
421
|
+
|
422
|
+
def surface(*args):
|
423
|
+
"""given a surface or a list of surface parameters as arguments,
|
424
|
+
return a conforming surface representation. Checks arguments
|
425
|
+
for data-type correctness.
|
426
|
+
|
427
|
+
"""
|
428
|
+
if args==[]:
|
429
|
+
# empty surface
|
430
|
+
return ['surface',[],[],[],[],[] ]
|
431
|
+
if len(args) == 1:
|
432
|
+
# one argument, produce a deep copy of surface (it that is
|
433
|
+
# what it is)
|
434
|
+
if issurface(args[0],fast=False):
|
435
|
+
return deepcopy(args[0])
|
436
|
+
if len(args) >= 3 and len(args) <= 6:
|
437
|
+
vrts = args[0]
|
438
|
+
nrms = args[1]
|
439
|
+
facs = args[2]
|
440
|
+
bndr = []
|
441
|
+
hle = []
|
442
|
+
metadata = None
|
443
|
+
|
444
|
+
if not (isinstance(vrts,list) and isinstance(nrms,list)
|
445
|
+
and isinstance(facs,list) and len(vrts) == len(nrms)):
|
446
|
+
raise ValueError('bad arguments to surface')
|
447
|
+
|
448
|
+
extras = list(args[3:])
|
449
|
+
for item in extras:
|
450
|
+
if isinstance(item, dict):
|
451
|
+
if metadata is not None:
|
452
|
+
raise ValueError('multiple metadata dictionaries passed to surface')
|
453
|
+
metadata = item
|
454
|
+
elif isinstance(item, list):
|
455
|
+
if not bndr:
|
456
|
+
bndr = item
|
457
|
+
elif not hle:
|
458
|
+
hle = item
|
459
|
+
else:
|
460
|
+
raise ValueError('too many list arguments passed to surface')
|
461
|
+
else:
|
462
|
+
raise ValueError('bad arguments to surface')
|
463
|
+
|
464
|
+
surf = ['surface',vrts,nrms,facs,bndr,hle]
|
465
|
+
if metadata is not None:
|
466
|
+
if not isinstance(metadata, dict):
|
467
|
+
raise ValueError('surface metadata must be a dict')
|
468
|
+
surf.append(metadata)
|
469
|
+
if issurface(surf,fast=False):
|
470
|
+
return surf
|
471
|
+
raise ValueError('bad arguments to surface')
|
472
|
+
|
473
|
+
def surfacebbox(s):
|
474
|
+
"""return bounding box for surface"""
|
475
|
+
if not issurface(s):
|
476
|
+
raise ValueError('bad surface passed to surfacebbox')
|
477
|
+
return polybbox(s[1])
|
478
|
+
|
479
|
+
def issurface(s,fast=True):
|
480
|
+
"""
|
481
|
+
Check to see if ``s`` is a valid surface.
|
482
|
+
"""
|
483
|
+
def filterInds(inds,verts):
|
484
|
+
l = len(verts)
|
485
|
+
if l < 3:
|
486
|
+
return False
|
487
|
+
return (len(list(filter(lambda x: not (isinstance(x,int) or
|
488
|
+
x < 0 or x >= l),
|
489
|
+
inds))) == 0)
|
490
|
+
|
491
|
+
if not isinstance(s,list) or s[0] != 'surface' or len(s) not in (6,7):
|
492
|
+
return False
|
493
|
+
if fast:
|
494
|
+
return True
|
495
|
+
else:
|
496
|
+
verts=s[1]
|
497
|
+
norms=s[2]
|
498
|
+
faces=s[3]
|
499
|
+
boundary= s[4]
|
500
|
+
holes= s[5]
|
501
|
+
metadata = s[6] if len(s) == 7 else None
|
502
|
+
if (not ispoly(verts) or
|
503
|
+
not isdirectlist(norms) or
|
504
|
+
len(verts) != len(norms)):
|
505
|
+
return False
|
506
|
+
l = len(verts)
|
507
|
+
if (len(list(filter(lambda x: not len(x) == 3, faces))) > 0):
|
508
|
+
return False
|
509
|
+
if not filterInds(reduce( (lambda x,y: x + y),faces),verts):
|
510
|
+
return False
|
511
|
+
if not filterInds(boundary,verts):
|
512
|
+
return False
|
513
|
+
if len(holes)>0:
|
514
|
+
for h in holes:
|
515
|
+
if not filterInds(h,verts):
|
516
|
+
return False
|
517
|
+
if metadata is not None and not isinstance(metadata, dict):
|
518
|
+
return False
|
519
|
+
return True
|
520
|
+
|
521
|
+
## save pointers to the yapcad.geom transformation functions
|
522
|
+
geom_rotate = rotate
|
523
|
+
geom_translate = translate
|
524
|
+
geom_scale = scale
|
525
|
+
geom_mirror = mirror
|
526
|
+
|
527
|
+
def rotatesurface(s,ang,cent=point(0,0,0),axis=point(0,0,1.0),mat=False):
|
528
|
+
""" return a rotated copy of the surface"""
|
529
|
+
if close(ang,0.0):
|
530
|
+
return deepcopy(s)
|
531
|
+
if not mat: # if matrix isn't pre-specified, calculate it
|
532
|
+
if vclose(cent,point(0,0,0)):
|
533
|
+
mat = xform.Rotation(axis,ang)
|
534
|
+
else:
|
535
|
+
mat = xform.Translation(cent)
|
536
|
+
mat = mat.mul(xform.Rotation(axis,ang))
|
537
|
+
mat = mat.mul(xform.Translation(cent,inverse=True))
|
538
|
+
s2 = deepcopy(s)
|
539
|
+
#import pdb ; pdb.set_trace()
|
540
|
+
s2[1] = geom_rotate(s2[1],ang,cent,axis,mat)
|
541
|
+
s2[2] = geom_rotate(s2[2],ang,cent,axis,mat)
|
542
|
+
return s2
|
543
|
+
|
544
|
+
def translatesurface(s,delta):
|
545
|
+
""" return a translated copy of the surface"""
|
546
|
+
if vclose(delta,point(0,0,0)):
|
547
|
+
return deepcopy(s)
|
548
|
+
s2 = deepcopy(s)
|
549
|
+
for i in range(len(s2[1])):
|
550
|
+
s2[1][i] = add(s2[1][i],delta)
|
551
|
+
return s2
|
552
|
+
|
553
|
+
def mirrorsurface(s,plane):
|
554
|
+
"""return a mirrored version of a surface. Currently, the following
|
555
|
+
values of "plane" are allowed: 'xz', 'yz', xy'. Generalized
|
556
|
+
arbitrary reflection plane specification will be added in the
|
557
|
+
future.
|
558
|
+
|
559
|
+
Note that this is a full surface geometry reflection, and not
|
560
|
+
simply a normal reverser.
|
561
|
+
"""
|
562
|
+
s2 = deepcopy(s)
|
563
|
+
s2[1] = mirror(s[1],plane)
|
564
|
+
s2[2] = mirror(s[2],plane)
|
565
|
+
s2[3] = list(map(lambda x: [x[0],x[2],x[1]],s2[3]))
|
566
|
+
return s2
|
567
|
+
|
568
|
+
def reversesurface(s):
|
569
|
+
""" return a nomal-reversed copy of the surface """
|
570
|
+
s2 = deepcopy(s)
|
571
|
+
s2[2] = list(map(lambda x: scale4(x,-1.0),s2[2]))
|
572
|
+
s2[3] = list(map(lambda x: [x[0],x[2],x[1]],s2[3]))
|
573
|
+
return s2
|
574
|
+
|
575
|
+
def solid(*args):
|
576
|
+
"""given a solid or a list of solid parameters as arguments,
|
577
|
+
return a conforming solid representation. Checks arguments
|
578
|
+
for data-type correctness.
|
579
|
+
|
580
|
+
"""
|
581
|
+
if args==[] or (len(args) == 1 and args[0] == []):
|
582
|
+
# empty solid, which is legal because we must support
|
583
|
+
# empty results of CSG operations, etc.
|
584
|
+
return ['solid',[],[],[] ]
|
585
|
+
|
586
|
+
# check for "copy constructor" case
|
587
|
+
if len(args) == 1 and issolid(args[0],fast=False):
|
588
|
+
# one argument, it's a solid. produce a deep copy of solid
|
589
|
+
return deepcopy(args[0])
|
590
|
+
|
591
|
+
# OK, step through arguments
|
592
|
+
if len(args) >= 1 and len(args) <= 4:
|
593
|
+
if not isinstance(args[0],list):
|
594
|
+
raise ValueError('bad arguments to solid')
|
595
|
+
for srf in args[0]:
|
596
|
+
if not issurface(srf):
|
597
|
+
raise ValueError('bad arguments to solid')
|
598
|
+
|
599
|
+
surfaces = args[0]
|
600
|
+
material = []
|
601
|
+
construction = []
|
602
|
+
metadata = None
|
603
|
+
|
604
|
+
for item in args[1:]:
|
605
|
+
if isinstance(item, dict):
|
606
|
+
if metadata is not None:
|
607
|
+
raise ValueError('multiple metadata dictionaries passed to solid')
|
608
|
+
metadata = item
|
609
|
+
elif isinstance(item, list):
|
610
|
+
if material == []:
|
611
|
+
material = item
|
612
|
+
elif construction == []:
|
613
|
+
construction = item
|
614
|
+
else:
|
615
|
+
raise ValueError('too many list arguments passed to solid')
|
616
|
+
else:
|
617
|
+
raise ValueError('bad arguments to solid')
|
618
|
+
|
619
|
+
sld = ['solid', surfaces, material, construction]
|
620
|
+
if metadata is not None:
|
621
|
+
sld.append(metadata)
|
622
|
+
return sld
|
623
|
+
|
624
|
+
raise ValueError('bad arguments to solid')
|
625
|
+
|
626
|
+
|
627
|
+
|
628
|
+
def issolid(s,fast=True):
|
629
|
+
|
630
|
+
"""
|
631
|
+
Check to see if ``s`` is a solid. NOTE: this function only determines
|
632
|
+
if th data structure is correct, it does not verify that the collection
|
633
|
+
of surfaces completely bounds a volume of space without holes
|
634
|
+
"""
|
635
|
+
|
636
|
+
if not isinstance(s,list) or s[0] != 'solid' or len(s) not in (4,5):
|
637
|
+
return False
|
638
|
+
if fast:
|
639
|
+
return True
|
640
|
+
else:
|
641
|
+
|
642
|
+
for surface in s[1]:
|
643
|
+
if not issurface(surface,fast=fast):
|
644
|
+
return False
|
645
|
+
if not (isinstance(s[2],list) and isinstance(s[3],list)):
|
646
|
+
return False
|
647
|
+
if len(s) == 5 and not isinstance(s[4], dict):
|
648
|
+
return False
|
649
|
+
return True
|
650
|
+
|
651
|
+
def solidbbox(sld):
|
652
|
+
if not issolid(sld):
|
653
|
+
raise ValueError('bad argument to solidbbox')
|
654
|
+
|
655
|
+
box = []
|
656
|
+
for s in sld[1]:
|
657
|
+
box = surfacebbox(s + box)
|
658
|
+
|
659
|
+
return box
|
660
|
+
|
661
|
+
def translatesolid(x,delta):
|
662
|
+
if not issolid(x):
|
663
|
+
raise ValueError('bad solid passed to translatesolid')
|
664
|
+
s2 = deepcopy(x)
|
665
|
+
surfs = []
|
666
|
+
for s in x[1]:
|
667
|
+
surfs.append(translatesurface(s,delta))
|
668
|
+
s2[1] = surfs
|
669
|
+
return s2
|
670
|
+
|
671
|
+
def rotatesolid(x,ang,cent=point(0,0,0),axis=point(0,0,1.0),mat=False):
|
672
|
+
if not issolid(x):
|
673
|
+
raise ValueError('bad solid passed to rotatesolid')
|
674
|
+
s2 = deepcopy(x)
|
675
|
+
surfs=[]
|
676
|
+
for s in x[1]:
|
677
|
+
surfs.append(rotatesurface(s,ang,cent=cent,axis=axis,mat=mat))
|
678
|
+
s2[1] = surfs
|
679
|
+
return s2
|
680
|
+
|
681
|
+
def mirrorsolid(x,plane,preserveNormal=True):
|
682
|
+
if not issolid(x):
|
683
|
+
raise ValueError('bad solid passed to mirrorsolid')
|
684
|
+
s2 = deepcopy(x)
|
685
|
+
surfs=[]
|
686
|
+
for s in x[1]:
|
687
|
+
surf = mirrorsurface(s,plane)
|
688
|
+
if preserveNormal and False:
|
689
|
+
surf = reversesurface(surf)
|
690
|
+
surfs.append(surf)
|
691
|
+
s2[1] = surfs
|
692
|
+
return s2
|
693
|
+
|
694
|
+
def normfunc(tri):
|
695
|
+
"""
|
696
|
+
utility funtion to compute normals for a flat facet triangle
|
697
|
+
"""
|
698
|
+
v1 = sub(tri[1],tri[0])
|
699
|
+
v2 = sub(tri[2],tri[1])
|
700
|
+
d = cross(v1,v2)
|
701
|
+
n = scale3(d,1.0/mag(d))
|
702
|
+
n[3] = 0.0 # direction vectors lie in the w=0 hyperplane
|
703
|
+
return n,n,n
|
704
|
+
|
705
|
+
def addTri2Surface(tri,s,check=False,nfunc=normfunc):
|
706
|
+
"""
|
707
|
+
Add triangle ``tri`` (a list of three points) to a surface ``s``,
|
708
|
+
returning the updated surface. *NOTE:* There is no enforcement of
|
709
|
+
contiguousness or coplainarity -- this function will add any triangle.
|
710
|
+
"""
|
711
|
+
|
712
|
+
def addVert(p,n,vrts,nrms):
|
713
|
+
for i in range(len(vrts)):
|
714
|
+
if vclose(p,vrts[i]):
|
715
|
+
return i,vrts,nrms
|
716
|
+
vrts.append(p)
|
717
|
+
nrms.append(n)
|
718
|
+
return len(vrts)-1,vrts,nrms
|
719
|
+
|
720
|
+
if check and (not issurface(s) or not istriangle(tri)):
|
721
|
+
raise ValueError(f'bad arguments to addTri2Surface({tri},{s})')
|
722
|
+
|
723
|
+
vrts = s[1]
|
724
|
+
nrms = s[2]
|
725
|
+
faces = s[3]
|
726
|
+
boundary = s[4]
|
727
|
+
holes = s[5]
|
728
|
+
|
729
|
+
n1,n2,n3 = nfunc(tri)
|
730
|
+
i1,vrts,nrms = addVert(tri[0],n1,vrts,nrms)
|
731
|
+
i2,vrts,nrms = addVert(tri[1],n2,vrts,nrms)
|
732
|
+
i3,vrts,nrms = addVert(tri[2],n3,vrts,nrms)
|
733
|
+
faces.append([i1,i2,i3])
|
734
|
+
|
735
|
+
return ['surface',vrts,nrms,faces,boundary,holes]
|
736
|
+
|
737
|
+
|
738
|
+
def surfacearea(surf):
|
739
|
+
"""
|
740
|
+
given a surface, return the surface area
|
741
|
+
"""
|
742
|
+
area = 0.0
|
743
|
+
vertices = surf[1]
|
744
|
+
faces= surf[3]
|
745
|
+
for f in faces:
|
746
|
+
area += triarea(vertices[f[0]],
|
747
|
+
vertices[f[1]],
|
748
|
+
vertices[f[2]])
|
749
|
+
|
750
|
+
return area
|
751
|
+
|
752
|
+
def surf2lines(surf):
|
753
|
+
"""
|
754
|
+
convert a surface representation to a non-redundant set of lines
|
755
|
+
for line-based rendering purposes
|
756
|
+
"""
|
757
|
+
|
758
|
+
drawn = []
|
759
|
+
|
760
|
+
verts = surf[1]
|
761
|
+
norms = surf[2]
|
762
|
+
faces = surf[3]
|
763
|
+
|
764
|
+
lines = []
|
765
|
+
|
766
|
+
def inds2key(i1,i2):
|
767
|
+
if i1 <= i2:
|
768
|
+
return f"{i1}-{i2}"
|
769
|
+
else:
|
770
|
+
return f"{i2}-{i1}"
|
771
|
+
|
772
|
+
def addLine(i1,i2,lines):
|
773
|
+
key = inds2key(i1,i2)
|
774
|
+
if not key in drawn:
|
775
|
+
lines.append(line(verts[i1],
|
776
|
+
verts[i2]))
|
777
|
+
drawn.append(key)
|
778
|
+
return lines
|
779
|
+
|
780
|
+
for f in faces:
|
781
|
+
lines = addLine(f[0],f[1],lines)
|
782
|
+
lines = addLine(f[1],f[2],lines)
|
783
|
+
lines = addLine(f[2],f[0],lines)
|
784
|
+
|
785
|
+
return lines
|
786
|
+
|
787
|
+
def poly2surface(ply,holepolys=[],minlen=0.5,minarea=0.0001,
|
788
|
+
checkclosed=False,basis=None):
|
789
|
+
|
790
|
+
"""Given ``ply``, a coplanar polygon, return the triangulated surface
|
791
|
+
representation of that polygon and its boundary. If ``holepolys``
|
792
|
+
is not the empty list, treat each polygon in that list as a hole
|
793
|
+
in ``ply``. If ``checkclosed`` is true, make sure ``ply`` and all
|
794
|
+
members of ``holepolys`` are a vaid, closed, coplanar polygons.
|
795
|
+
if ``box`` exists, use it as the bounding box.
|
796
|
+
|
797
|
+
if ``basis`` exists, use it as the planar coordinate basis to
|
798
|
+
transform the poly into the z=0 plane.
|
799
|
+
|
800
|
+
Returns surface and boundary
|
801
|
+
|
802
|
+
"""
|
803
|
+
|
804
|
+
if len(ply) < 3:
|
805
|
+
raise ValueError(f'poly must be at least length 3, got {len(ply)}')
|
806
|
+
|
807
|
+
if not basis:
|
808
|
+
v0 = sub(ply[1],ply[0])
|
809
|
+
v1 = None
|
810
|
+
for i in range(2,len(ply)):
|
811
|
+
v1 = sub(ply[i],ply[1])
|
812
|
+
if mag(cross(v0,v1)) > epsilon:
|
813
|
+
break
|
814
|
+
if not v1:
|
815
|
+
raise ValueError(f'degenerate poly passed to poly2surface')
|
816
|
+
basis = tri2p0n([ply[0],ply[1],ply[i]],basis=True)
|
817
|
+
|
818
|
+
ply2 = list(map(lambda x: basis[2].mul(x),ply))
|
819
|
+
holes2 = []
|
820
|
+
for hole in holepolys:
|
821
|
+
holes2.append(list(map(lambda x: basis[2].mul(x), hole)))
|
822
|
+
|
823
|
+
surf,bnd = poly2surfaceXY(ply2,holes2,minlen,minarea,checkclosed)
|
824
|
+
|
825
|
+
verts2 = list(map(lambda x: basis[3].mul(x),surf[1]))
|
826
|
+
norm2 = list(map(lambda x: basis[3].mul([x[0],x[1],x[2],0]),surf[2]))
|
827
|
+
bnd2 = list(map(lambda x: basis[3].mul(x),bnd))
|
828
|
+
|
829
|
+
surf[1]=verts2
|
830
|
+
surf[2]=norm2
|
831
|
+
|
832
|
+
return surf,bnd2
|
833
|
+
|
834
|
+
def poly2surfaceXY(ply,holepolys=[],minlen=0.5,minarea=0.0001,
|
835
|
+
checkclosed=False,box=None):
|
836
|
+
"""Given ``ply``, return a triangulated XY surface (holes supported)."""
|
837
|
+
|
838
|
+
if checkclosed:
|
839
|
+
polys = holepolys + [ply]
|
840
|
+
if not isgeomlistXYPlanar(polys):
|
841
|
+
raise ValueError('non-XY-coplanar arguments')
|
842
|
+
for p in polys:
|
843
|
+
if not ispolygonXY(p):
|
844
|
+
raise ValueError(f'{p} is not a closed polygon')
|
845
|
+
|
846
|
+
if not box:
|
847
|
+
box = bbox(ply)
|
848
|
+
|
849
|
+
def _normalize_loop(poly):
|
850
|
+
pts = [point(p) for p in poly]
|
851
|
+
if pts and dist(pts[0], pts[-1]) <= epsilon:
|
852
|
+
pts = pts[:-1]
|
853
|
+
return pts
|
854
|
+
|
855
|
+
outer_loop = _normalize_loop(ply)
|
856
|
+
if len(outer_loop) < 3:
|
857
|
+
raise ValueError('degenerate polygon passed to poly2surfaceXY')
|
858
|
+
|
859
|
+
hole_loops = [_normalize_loop(loop) for loop in holepolys]
|
860
|
+
|
861
|
+
triangles = triangulate_polygon([(p[0], p[1]) for p in outer_loop],
|
862
|
+
[[(q[0], q[1]) for q in loop]
|
863
|
+
for loop in hole_loops])
|
864
|
+
|
865
|
+
def makeboundary(poly,vertices,normals):
|
866
|
+
bndry = []
|
867
|
+
i = len(vertices)
|
868
|
+
for p in poly:
|
869
|
+
vertices.append(point(p))
|
870
|
+
normals.append([0,0,1,0])
|
871
|
+
bndry.append(i)
|
872
|
+
i+=1
|
873
|
+
return bndry,vertices,normals
|
874
|
+
|
875
|
+
vrts=[]
|
876
|
+
nrms=[]
|
877
|
+
faces=[]
|
878
|
+
boundary=[]
|
879
|
+
holes=[]
|
880
|
+
|
881
|
+
boundary,vrts,nrms = makeboundary(outer_loop,vrts,nrms)
|
882
|
+
for loop in hole_loops:
|
883
|
+
hole,vrts,nrms = makeboundary(loop,vrts,nrms)
|
884
|
+
holes.append(hole)
|
885
|
+
|
886
|
+
surf=['surface',vrts,nrms,faces,boundary,holes]
|
887
|
+
|
888
|
+
def _signed_triangle(tri):
|
889
|
+
(x1,y1),(x2,y2),(x3,y3) = tri
|
890
|
+
return ((x2 - x1)*(y3 - y1) - (x3 - x1)*(y2 - y1)) / 2.0
|
891
|
+
|
892
|
+
outer_area = sum(outer_loop[i][0]*outer_loop[(i+1)%len(outer_loop)][1]
|
893
|
+
- outer_loop[(i+1)%len(outer_loop)][0]*outer_loop[i][1]
|
894
|
+
for i in range(len(outer_loop)))
|
895
|
+
orientation = 1 if outer_area >= 0 else -1
|
896
|
+
|
897
|
+
for tri in triangles:
|
898
|
+
tri_points = [point(x, y, 0, 1) for (x, y) in tri]
|
899
|
+
area = _signed_triangle(tri)
|
900
|
+
if area * orientation < 0:
|
901
|
+
tri_points[1], tri_points[2] = tri_points[2], tri_points[1]
|
902
|
+
area = -area
|
903
|
+
if abs(area) > minarea:
|
904
|
+
surf = addTri2Surface(tri_points,surf,
|
905
|
+
nfunc=lambda x: ([0,0,1,0],
|
906
|
+
[0,0,1,0],
|
907
|
+
[0,0,1,0]))
|
908
|
+
|
909
|
+
return surf,[]
|
910
|
+
|
911
|
+
### updated, surface- and solid-aware generalized geometry functions
|
912
|
+
|
913
|
+
# length -- scalar length doesn't make sense for sufface or solid
|
914
|
+
|
915
|
+
geom_center = center
|
916
|
+
def center(x):
|
917
|
+
"""Return the point corresponding to the center of surface, solid, or
|
918
|
+
figure x.
|
919
|
+
|
920
|
+
"""
|
921
|
+
if issurface(x):
|
922
|
+
box = surfacebbox(x)
|
923
|
+
return scale3(add(box[0],box[1]),0.5)
|
924
|
+
elif issolid(x):
|
925
|
+
box = solidbbox(x)
|
926
|
+
return scale3(add(box[0],box[1]),0.5)
|
927
|
+
else:
|
928
|
+
return geom_center(x)
|
929
|
+
|
930
|
+
geom_bbox = bbox
|
931
|
+
def bbox(x):
|
932
|
+
"""Given a figure, surface, or solid x, return the three-dimensional
|
933
|
+
bounding box of that entity."""
|
934
|
+
if issolid(x):
|
935
|
+
return solidbbox(x)
|
936
|
+
elif issurface(x):
|
937
|
+
return surfacebbox(x)
|
938
|
+
else:
|
939
|
+
return geom_bbox(x)
|
940
|
+
|
941
|
+
# sample -- doesn't make sense for surface or solid
|
942
|
+
# unsample -- doesn't make sense for surface or solid
|
943
|
+
# segment -- doesn't make sense for surface or solid
|
944
|
+
# isnsideXY -- doesn't make sense for a suface or solid
|
945
|
+
|
946
|
+
def translate(x,delta):
|
947
|
+
""" return a translated version of the surface, solid, or figure"""
|
948
|
+
if issolid(x):
|
949
|
+
return translatesolid(x,delta)
|
950
|
+
elif issurface(x):
|
951
|
+
return translatesurface(x,delta)
|
952
|
+
else:
|
953
|
+
return geom_translate(x,delta)
|
954
|
+
|
955
|
+
def rotate(x,ang,cent=point(0,0),axis=point(0,0,1.0),mat=False):
|
956
|
+
""" return a rotated version of the surface, solid, or figure"""
|
957
|
+
if issolid(x):
|
958
|
+
return rotatesolid(x,ang,cent=cent,axis=axis,mat=mat)
|
959
|
+
elif issurface(x):
|
960
|
+
return rotatesurface(x,ang,cent=cent,axis=axis,mat=mat)
|
961
|
+
else:
|
962
|
+
return geom_rotate(x,ang,cent=cent,axis=axis,mat=mat)
|
963
|
+
|
964
|
+
|
965
|
+
def mirror(x,plane):
|
966
|
+
"""
|
967
|
+
return a mirrored version of a figure. Currently, the following
|
968
|
+
values of "plane" are allowed: 'xz', 'yz', xy'. Generalized
|
969
|
+
arbitrary reflection plane specification will be added in the
|
970
|
+
future.
|
971
|
+
|
972
|
+
NOTE: this operation will reverse the sign of the area of ``x`` if
|
973
|
+
x is a closed polyline or geometry list
|
974
|
+
"""
|
975
|
+
if issolid(x):
|
976
|
+
return mirrorsolid(x,plane)
|
977
|
+
elif issurface(x):
|
978
|
+
return mirrorsurface(x,plane)
|
979
|
+
else:
|
980
|
+
return geom_mirror(x,plane)
|
981
|
+
|
982
|
+
|