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/octtree.py
ADDED
@@ -0,0 +1,627 @@
|
|
1
|
+
## quadtree and octtree representations for yapCAD geometry
|
2
|
+
## Born on 15 December 2020
|
3
|
+
## Copyright (c) 2020 Richard DeVaul
|
4
|
+
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation files
|
7
|
+
# (the "Software"), to deal in the Software without restriction,
|
8
|
+
# including without limitation the rights to use, copy, modify, merge,
|
9
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
10
|
+
# and to permit persons to whom the Software is furnished to do so,
|
11
|
+
# subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
20
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
21
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
22
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
|
25
|
+
"""quadtree and octtree representations for yapCAD geometry"""
|
26
|
+
|
27
|
+
from yapcad.geom import *
|
28
|
+
|
29
|
+
# determine if two bounding boxes overlap. This is a more challenging
|
30
|
+
# problem than it may appear at first glance. It is not enough to
|
31
|
+
# test the corner points of one box to see if they fall inside the
|
32
|
+
# other, as illustrated by the following cases:
|
33
|
+
|
34
|
+
# case 1: +--------------+
|
35
|
+
# inside | box1 |
|
36
|
+
# | +--------+ |
|
37
|
+
# | | box2 | |
|
38
|
+
# | +--------+ |
|
39
|
+
# +--------------+
|
40
|
+
|
41
|
+
# No corner points of box1 lie inside box2, no lines intersect.
|
42
|
+
|
43
|
+
# case 2: +------+
|
44
|
+
# cross | box1 |
|
45
|
+
# +-------+------+------+
|
46
|
+
# | box2 | | |
|
47
|
+
# +-------+------+------+
|
48
|
+
# | |
|
49
|
+
# +------+
|
50
|
+
|
51
|
+
# No corner point of either box lie inside the other, projected lines
|
52
|
+
# intersect.
|
53
|
+
|
54
|
+
def boxoverlap2(bbx1,bbx2,dim3=True):
|
55
|
+
"""Determine if two bounding boxes overlap. if dim3==True, treat the
|
56
|
+
bounding boxes as 3D, otherwise treat them as co-planar 2D boxes.
|
57
|
+
|
58
|
+
First, check to see if the maximum coordinates of one box are
|
59
|
+
smaller than the minimum coordinates of the other, or vice versa.
|
60
|
+
If so, no overlap is possible; return False
|
61
|
+
|
62
|
+
if overlap is possible by test #1, check for the box-in-box
|
63
|
+
special case for each box. If so, return True
|
64
|
+
|
65
|
+
Finally, for the 2D case: determine if horizontal lines of box1
|
66
|
+
intersect with vertical lines of box2, and vice versa. If any
|
67
|
+
intersections found, return True, else return False
|
68
|
+
|
69
|
+
For the 3D case, project the boxes into the XY, YZ, and XZ planes,
|
70
|
+
and perform the 2D lines intersection check, as above. Return
|
71
|
+
True if and only if intersections are reported for each
|
72
|
+
projection, otherwise return False
|
73
|
+
"""
|
74
|
+
|
75
|
+
# check minmax
|
76
|
+
if ((bbx1[1][0] < bbx2[0][0] and
|
77
|
+
bbx1[1][1] < bbx2[0][1] and
|
78
|
+
(not dim3 or bbx1[1][2] < bbx2[0][2])) or
|
79
|
+
(bbx2[1][0] < bbx1[0][0] and
|
80
|
+
bbx2[1][1] < bbx1[0][1] and
|
81
|
+
(not dim3 or bbx2[1][2] < bbx1[0][2]))):
|
82
|
+
return False # no overlap possible
|
83
|
+
|
84
|
+
# check for box-in-box
|
85
|
+
if ((bbx1[0][0] >= bbx2[0][0] and bbx1[1][0] <= bbx2[1][0] and
|
86
|
+
bbx1[0][1] >= bbx2[0][1] and bbx1[1][1] <= bbx2[1][1] and
|
87
|
+
(not dim3 or (bbx1[0][2] >= bbx2[0][2]
|
88
|
+
and bbx1[1][2] <= bbx2[1][2]))) or
|
89
|
+
(bbx2[0][0] >= bbx1[0][0] and bbx2[1][0] <= bbx1[1][0] and
|
90
|
+
bbx2[0][1] >= bbx1[0][1] and bbx2[1][1] <= bbx1[1][1] and
|
91
|
+
(not dim3 or (bbx2[0][2] >= bbx1[0][2]
|
92
|
+
and bbx2[1][2] <= bbx1[1][2])))):
|
93
|
+
return True
|
94
|
+
|
95
|
+
def int2D(bb1,bb2,plane='XY'):
|
96
|
+
"""utility function for 2D box line intersection finding"""
|
97
|
+
i = 0
|
98
|
+
j = 0
|
99
|
+
if plane == 'XY':
|
100
|
+
i = 0
|
101
|
+
j = 1
|
102
|
+
elif plane == 'YZ':
|
103
|
+
i = 1
|
104
|
+
j = 2
|
105
|
+
elif plane == 'XZ':
|
106
|
+
i = 0
|
107
|
+
j = 2
|
108
|
+
else:
|
109
|
+
raise ValueError('bad plane in int2D')
|
110
|
+
|
111
|
+
def intHV(hln,vln):
|
112
|
+
""" do a horizontal and vertial line intersect? """
|
113
|
+
minx=hln[0][0]
|
114
|
+
maxx=hln[1][0]
|
115
|
+
if minx>maxx:
|
116
|
+
swap = minx ; minx = maxx; maxx = swap
|
117
|
+
|
118
|
+
if vln[0][0] < minx or vln[0][0] > maxx:
|
119
|
+
return False
|
120
|
+
miny=vln[0][1]
|
121
|
+
maxy=vln[1][1]
|
122
|
+
if miny > maxy:
|
123
|
+
swap = miny ; miny = maxy ; maxy = swap
|
124
|
+
if miny > hln[0][1] or maxy < hln[0][1]:
|
125
|
+
return False
|
126
|
+
return True
|
127
|
+
|
128
|
+
# check for projected box-in-box
|
129
|
+
if ((bb1[0][i] >= bb2[0][i] and bb1[1][i] <= bb2[1][i] and
|
130
|
+
bb1[0][j] >= bb2[0][j] and bb1[1][j] <= bb2[1][j])
|
131
|
+
or
|
132
|
+
(bb2[0][i] >= bb1[0][i] and bb2[1][i] <= bb1[1][i] and
|
133
|
+
bb2[0][j] >= bb1[0][j] and bb2[1][j] <= bb1[1][j])):
|
134
|
+
return True
|
135
|
+
|
136
|
+
# check for projected line intersections
|
137
|
+
|
138
|
+
len1 = bb1[1][i] - bb1[0][i] # length
|
139
|
+
wid1 = bb1[1][j] - bb1[0][j] # width
|
140
|
+
len2 = bb2[1][i] - bb2[0][i] # length
|
141
|
+
wid2 = bb2[1][j] - bb2[0][j] # width
|
142
|
+
|
143
|
+
p0 = point(bb1[0][i],bb1[0][j])
|
144
|
+
p1 = add(p0,point(len1,0))
|
145
|
+
p2 = add(p1,point(0,wid1))
|
146
|
+
p3 = add(p0,point(0,wid1))
|
147
|
+
|
148
|
+
p4 = point(bb2[0][i],bb2[0][j])
|
149
|
+
p5 = add(p4,point(len2,0))
|
150
|
+
p6 = add(p5,point(0,wid2))
|
151
|
+
p7 = add(p4,point(0,wid2))
|
152
|
+
|
153
|
+
box1 = [[p0,p1],
|
154
|
+
[p1,p2],
|
155
|
+
[p2,p3],
|
156
|
+
[p3,p0]]
|
157
|
+
box2 = [[p4,p5],
|
158
|
+
[p5,p6],
|
159
|
+
[p6,p7],
|
160
|
+
[p7,p4]]
|
161
|
+
if intHV(box1[0],box2[1]):
|
162
|
+
return True
|
163
|
+
if intHV(box1[0],box2[3]):
|
164
|
+
return True
|
165
|
+
if intHV(box1[2],box2[1]):
|
166
|
+
return True
|
167
|
+
if intHV(box1[2],box2[3]):
|
168
|
+
return True
|
169
|
+
|
170
|
+
if intHV(box2[0],box1[1]):
|
171
|
+
return True
|
172
|
+
if intHV(box2[0],box1[3]):
|
173
|
+
return True
|
174
|
+
if intHV(box2[2],box1[1]):
|
175
|
+
return True
|
176
|
+
if intHV(box2[2],box1[3]):
|
177
|
+
return True
|
178
|
+
# if lineLineIntersectXY(box1[0],box2[1]):
|
179
|
+
# return True
|
180
|
+
# if lineLineIntersectXY(box1[0],box2[3]):
|
181
|
+
# return True
|
182
|
+
# if lineLineIntersectXY(box1[2],box2[1]):
|
183
|
+
# return True
|
184
|
+
# if lineLineIntersectXY(box1[2],box2[3]):
|
185
|
+
# return True
|
186
|
+
|
187
|
+
# if lineLineIntersectXY(box2[0],box1[1]):
|
188
|
+
# return True
|
189
|
+
# if lineLineIntersectXY(box2[0],box1[3]):
|
190
|
+
# return True
|
191
|
+
# if lineLineIntersectXY(box2[2],box1[1]):
|
192
|
+
# return True
|
193
|
+
# if lineLineIntersectXY(box2[2],box1[3]):
|
194
|
+
# return True
|
195
|
+
|
196
|
+
# for l1 in box1:
|
197
|
+
# for l2 in box2:
|
198
|
+
# if lineLineIntersectXY(l1,l2):
|
199
|
+
# return True
|
200
|
+
return False
|
201
|
+
|
202
|
+
# do projeted box line intersection tests
|
203
|
+
return (int2D(bbx1,bbx2,'XY') and
|
204
|
+
(not dim3 or int2D(bbx1,bbx2,'YZ')) and
|
205
|
+
(not dim3 or int2D(bbx1,bbx2,'XZ')))
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
def boxoverlap(bbx1,bbx2,dim3=True):
|
210
|
+
|
211
|
+
"""determine if two bounding boxes overlap"""
|
212
|
+
minx = bbx1[0][0]
|
213
|
+
miny = bbx1[0][1]
|
214
|
+
minz = bbx1[0][2]
|
215
|
+
|
216
|
+
len1 = bbx1[1][0] - bbx1[0][0] # length
|
217
|
+
wid1 = bbx1[1][1] - bbx1[0][1] # width
|
218
|
+
hei1 = bbx1[1][2] - bbx1[0][2] # height
|
219
|
+
|
220
|
+
p0=bbx1[0]
|
221
|
+
p1=add(p0,point(len1,0,0))
|
222
|
+
p2=add(p1,point(0,wid1,0))
|
223
|
+
p3=add(p0,point(0,wid1,0))
|
224
|
+
|
225
|
+
points = [p0,p1,p2,p3]
|
226
|
+
|
227
|
+
if not dim3:
|
228
|
+
for p in points:
|
229
|
+
if isinsidebbox2D(bbx2,p):
|
230
|
+
return True
|
231
|
+
return False
|
232
|
+
|
233
|
+
p4=add(p0,point(0,0,hei1))
|
234
|
+
p5=add(p4,point(len1,0,0))
|
235
|
+
p6=bbx1[1]
|
236
|
+
p7=add(p4,point(0,wid1,0))
|
237
|
+
|
238
|
+
points += [p4,p5,p6,p7]
|
239
|
+
#print("boxoverlap points; ",vstr(points))
|
240
|
+
#print("boxoverlap bbx2: ",vstr(bbx2))
|
241
|
+
for p in points:
|
242
|
+
if isinsidebbox(bbx2,p):
|
243
|
+
return True
|
244
|
+
return False
|
245
|
+
|
246
|
+
def bbox2oct(bbx,refbox,center):
|
247
|
+
"""
|
248
|
+
Utility function to take a bounding box representation and assign
|
249
|
+
it to zero or more octants.
|
250
|
+
|
251
|
+
box2oct(bbx,refbox,center)
|
252
|
+
|
253
|
+
bbx: 3D bounding box to assign
|
254
|
+
refbox: reference 3D bounding box
|
255
|
+
center: center point for purposes of assignment
|
256
|
+
|
257
|
+
returns (potentially empty) list of octants, numbered 0 to 7
|
258
|
+
"""
|
259
|
+
# print("bbox2oct :: bbx: ",vstr(bbx)," refbox: ",vstr(refbox)," center: ",vstr(center))
|
260
|
+
rlist = []
|
261
|
+
if not boxoverlap2(refbox,bbx):
|
262
|
+
return rlist # no overlap
|
263
|
+
|
264
|
+
if bbx[0][2] < center[2]:
|
265
|
+
rlist += bbox2quad(bbx,refbox,center)
|
266
|
+
|
267
|
+
if bbx[1][2] >= center[2]:
|
268
|
+
rlist += list(map(lambda x: x+4, bbox2quad(bbx,refbox,center)))
|
269
|
+
|
270
|
+
return rlist
|
271
|
+
|
272
|
+
def bbox2quad(bbx,refbox,center):
|
273
|
+
"""Utility Function to take a bounding box representation and assign
|
274
|
+
it to zero or more quads
|
275
|
+
|
276
|
+
box2quad(bbx,refbox,center)
|
277
|
+
|
278
|
+
bbx: 2D bounding box to assign
|
279
|
+
refbox: reference 2D bounding box
|
280
|
+
center: center point for purposes of assignment
|
281
|
+
|
282
|
+
returns (potentially empty) list of quadrants, numbered 0 to 3
|
283
|
+
"""
|
284
|
+
rlist = []
|
285
|
+
if not boxoverlap2(refbox,bbx,dim3=False):
|
286
|
+
return rlist # no overlap
|
287
|
+
|
288
|
+
if bbx[0][0] < center[0]:
|
289
|
+
if bbx[0][1] < center[1]:
|
290
|
+
rlist.append(2)
|
291
|
+
if bbx[1][1] >= center[1]:
|
292
|
+
rlist.append(1)
|
293
|
+
if bbx[1][0] >= center[0]:
|
294
|
+
if bbx[0][1] < center[1]:
|
295
|
+
rlist.append(3)
|
296
|
+
if bbx[1][1] >= center[1]:
|
297
|
+
rlist.append(0)
|
298
|
+
return rlist
|
299
|
+
|
300
|
+
|
301
|
+
def box2boxes(bbox,center,n,type='centersplit',elm=[]):
|
302
|
+
"""Function to take a bounding box (2d or 3D) and return a quad- or
|
303
|
+
octtree decomposition of the box based on the value of center
|
304
|
+
point, the type of split, and (potentially) the list of bounding
|
305
|
+
boxes to be divvied up.
|
306
|
+
"""
|
307
|
+
def boxmid(box):
|
308
|
+
p0 = box[0]
|
309
|
+
p1 = box[1]
|
310
|
+
return scale3(add(p0,p1),0.5)
|
311
|
+
|
312
|
+
# print("box2boxes :: bbox: ",vstr(bbox)," center: ",vstr(center))
|
313
|
+
if not isinsidebbox(bbox,center):
|
314
|
+
raise ValueError('center point does not lie inside the bounding box')
|
315
|
+
if type != 'centersplit':
|
316
|
+
raise NotImplementedError('we are only doing center splits for now')
|
317
|
+
if not n in (4,8):
|
318
|
+
raise ValueError('bad tree dimension')
|
319
|
+
|
320
|
+
cx = center[0]
|
321
|
+
cy = center[1]
|
322
|
+
cz = center[2]
|
323
|
+
|
324
|
+
maxx = bbox[1][0]
|
325
|
+
maxy = bbox[1][1]
|
326
|
+
maxz = bbox[1][2]
|
327
|
+
minx = bbox[0][0]
|
328
|
+
miny = bbox[0][1]
|
329
|
+
minz = bbox[0][2]
|
330
|
+
|
331
|
+
if (maxx-minx < epsilon or
|
332
|
+
maxy-miny < epsilon or
|
333
|
+
(n == 8 and maxz-minz < epsilon)) :
|
334
|
+
raise ValueError('zero dimension box')
|
335
|
+
|
336
|
+
if n == 4:
|
337
|
+
z0 = 0
|
338
|
+
z1 = 0
|
339
|
+
else:
|
340
|
+
z0 = minz
|
341
|
+
z1 = cz
|
342
|
+
|
343
|
+
box1 = [point(cx,cy,z0),
|
344
|
+
point(maxx,maxy,z1)]
|
345
|
+
box2 = [point(minx,cy,z0),
|
346
|
+
point(cx,maxy,z1)]
|
347
|
+
box3 = [point(minx,miny,z0),
|
348
|
+
point(cx,cy,z1)]
|
349
|
+
box4 = [point(cx,miny,z0),
|
350
|
+
point(maxx,cy,z1)]
|
351
|
+
|
352
|
+
r1 = [ [box1, boxmid(box1)],
|
353
|
+
[box2, boxmid(box2)],
|
354
|
+
[box3, boxmid(box3)],
|
355
|
+
[box4, boxmid(box4)] ]
|
356
|
+
|
357
|
+
if n == 4:
|
358
|
+
return r1
|
359
|
+
else:
|
360
|
+
z0 = cz
|
361
|
+
z1 = maxz
|
362
|
+
|
363
|
+
box5 = [point(cx,cy,z0),
|
364
|
+
point(maxx,maxy,z1)]
|
365
|
+
box6 = [point(minx,cy,z0),
|
366
|
+
point(cx,maxy,z1)]
|
367
|
+
box7 = [point(minx,miny,z0),
|
368
|
+
point(cx,cy,z1)]
|
369
|
+
box8 = [point(cx,miny,z0),
|
370
|
+
point(maxx,cy,z1)]
|
371
|
+
|
372
|
+
r2 = [ [box5, boxmid(box5)],
|
373
|
+
[box6, boxmid(box6)],
|
374
|
+
[box7, boxmid(box7)],
|
375
|
+
[box8, boxmid(box8)] ]
|
376
|
+
|
377
|
+
return r1 + r2
|
378
|
+
|
379
|
+
def bboxdim(box):
|
380
|
+
""" return length, width, and height of a bounding box"""
|
381
|
+
|
382
|
+
length = box[1][0] - box[0][0]
|
383
|
+
width = box[1][1] - box[0][1]
|
384
|
+
height = box[1][2] - box[0][2]
|
385
|
+
return [length, width, height]
|
386
|
+
|
387
|
+
def untag(tglist):
|
388
|
+
"""remove the (optional) tags that can be associated with elements in
|
389
|
+
a geometry list."""
|
390
|
+
glist = []
|
391
|
+
for e in tglist:
|
392
|
+
if isinstance(e,tuple):
|
393
|
+
glist += [ e[0] ]
|
394
|
+
else:
|
395
|
+
glist += [ e ]
|
396
|
+
return glist
|
397
|
+
|
398
|
+
class NTree():
|
399
|
+
|
400
|
+
"""Generalized n-tree representation for yapCAD geometry"""
|
401
|
+
|
402
|
+
def __init__(self,n=8,geom=None,center=None,
|
403
|
+
mindim=None,maxdepth=None):
|
404
|
+
|
405
|
+
if not n in [4,8]:
|
406
|
+
raise ValueError('only quad- or octtrees supported')
|
407
|
+
|
408
|
+
self.__n = n
|
409
|
+
self.__depth = 0
|
410
|
+
if mindim:
|
411
|
+
if isinstance(mindim,(int,float)) and mindim > epsilon:
|
412
|
+
self.__mindim = mindim
|
413
|
+
else:
|
414
|
+
raise ValueError('bad mindim value: '+str(mindim))
|
415
|
+
else:
|
416
|
+
self.__mindim = 1.0 # minimum dimension for tree element
|
417
|
+
|
418
|
+
if maxdepth:
|
419
|
+
if isinstance(maxdepth,int) and maxdepth > 0:
|
420
|
+
self.__maxdepth = maxdepth
|
421
|
+
else:
|
422
|
+
raise ValueError('bad max depth value: '+str(maxdepth))
|
423
|
+
elif self.__n == 4:
|
424
|
+
self.__maxdepth = 8
|
425
|
+
else: # n=8
|
426
|
+
self.__maxdepth = 7
|
427
|
+
|
428
|
+
self.__geom=[]
|
429
|
+
if geom:
|
430
|
+
ugeom = untag(geom)
|
431
|
+
if isgeomlist(ugeom):
|
432
|
+
self.__bbox= bbox(ugeom)
|
433
|
+
self.__geom= geom
|
434
|
+
else:
|
435
|
+
raise ValueError('geom must be valid geometry list, or None')
|
436
|
+
|
437
|
+
self.__center = None
|
438
|
+
if center:
|
439
|
+
if not ispoint(center):
|
440
|
+
raise ValueError('center must be a valid point')
|
441
|
+
self.__center = center
|
442
|
+
else:
|
443
|
+
if self.__geom != []:
|
444
|
+
self.updateCenter()
|
445
|
+
|
446
|
+
self.__tree = []
|
447
|
+
self.__update=True
|
448
|
+
|
449
|
+
def __repr__(self):
|
450
|
+
return 'NTree(n={},depth={},mindim={},\n geom={},\n tree={})'.format(self.__n,self.__depth,self.__mindim,vstr(self.__geom),vstr(self.__tree))
|
451
|
+
|
452
|
+
def addElement(self,element,tag=None):
|
453
|
+
""" add a geometry element to the collection, don't update
|
454
|
+
the tree -- yet """
|
455
|
+
if not isgeomlist(element):
|
456
|
+
raise ValueError('bad element passed to addElement')
|
457
|
+
if not tag:
|
458
|
+
gl = [ element ]
|
459
|
+
else:
|
460
|
+
gl = [ (element,tag) ]
|
461
|
+
self.__geom += gl
|
462
|
+
|
463
|
+
self.__update=True
|
464
|
+
|
465
|
+
|
466
|
+
def updateCenter(self,center=None):
|
467
|
+
"""specify or compute new geometric center (or split poit) for tree,
|
468
|
+
flag tree for rebuilding.
|
469
|
+
|
470
|
+
"""
|
471
|
+
if self.__geom == []: #nothing to do
|
472
|
+
return
|
473
|
+
|
474
|
+
if not center:
|
475
|
+
self.__center = scale3(add(self.__bbox[0],
|
476
|
+
self.__bbox[1]),0.5)
|
477
|
+
else:
|
478
|
+
if ispoint(center):
|
479
|
+
self.__center = center
|
480
|
+
else:
|
481
|
+
raise ValueError('bad center point passed to updateCenter')
|
482
|
+
self.__update = True
|
483
|
+
|
484
|
+
|
485
|
+
def updateTree(self):
|
486
|
+
"""
|
487
|
+
build the tree from the current contents of the self.__geom list
|
488
|
+
"""
|
489
|
+
if not self.__update or self.__geom == []:
|
490
|
+
# nothing to do
|
491
|
+
return
|
492
|
+
self.__update = False
|
493
|
+
|
494
|
+
# striptags for bbox
|
495
|
+
ugeom = untag(self.__geom)
|
496
|
+
self.__bbox = bbox(ugeom)
|
497
|
+
if not self.__center:
|
498
|
+
self.updateCenter()
|
499
|
+
|
500
|
+
bxlist = list(map(lambda x: bbox(x), ugeom))
|
501
|
+
bxidxlist = []
|
502
|
+
for i in range(len(bxlist)):
|
503
|
+
bxidxlist.append([i,bxlist[i]])
|
504
|
+
|
505
|
+
self.__elem_idx_bbox = bxidxlist
|
506
|
+
|
507
|
+
if not self.__center:
|
508
|
+
self.updateCenter()
|
509
|
+
|
510
|
+
# recursively build the tree
|
511
|
+
def recurse(box,center,elements,depth=0):
|
512
|
+
# print("recurse :: box: ",vstr(box)," center: ",vstr(center))
|
513
|
+
bbdim = bboxdim(box)
|
514
|
+
if depth > self.__depth:
|
515
|
+
self.__depth = depth
|
516
|
+
|
517
|
+
if elements==[] or elements==None:
|
518
|
+
return []
|
519
|
+
elif (len(elements) <= self.__n or
|
520
|
+
depth > self.__maxdepth or
|
521
|
+
bbdim[0] < self.__mindim or
|
522
|
+
bbdim[1] < self.__mindim or
|
523
|
+
(self.__n == 8 and bbdim[2] < self.__mindim)):
|
524
|
+
|
525
|
+
return [ 'e', box, center ] + list(map(lambda x:
|
526
|
+
x[0], elements))
|
527
|
+
else:
|
528
|
+
boxlist = ['b'] + box2boxes(box,center,self.__n)
|
529
|
+
|
530
|
+
# print("boxlist: ",vstr(boxlist))
|
531
|
+
# print("elements: ",vstr(elements))
|
532
|
+
for e in elements:
|
533
|
+
func = None
|
534
|
+
box = e[1]
|
535
|
+
if self.__n == 8:
|
536
|
+
func = bbox2oct
|
537
|
+
else:
|
538
|
+
func = bbox2quad
|
539
|
+
ind = func(box,box,center)
|
540
|
+
# print("ind: ",ind," box: ",box," box: ",box," center: ",center)
|
541
|
+
#print ("e[0]: ",e[0], " e[1]: ",vstr(e[1])," ind: ",ind)
|
542
|
+
# print ("box: ",vstr(box))
|
543
|
+
for j in ind:
|
544
|
+
boxlist[j+1].append(e)
|
545
|
+
|
546
|
+
for i in range(1,len(boxlist)):
|
547
|
+
boxlist[i] = recurse(boxlist[i][0],boxlist[i][1],
|
548
|
+
boxlist[i][2:],depth+1)
|
549
|
+
return boxlist
|
550
|
+
|
551
|
+
self.__tree = recurse(self.__bbox, self.__center,
|
552
|
+
bxidxlist)
|
553
|
+
# print("bxidxlist: ",vstr(bxidxlist))
|
554
|
+
# print("self.__tree",self.__tree)
|
555
|
+
|
556
|
+
return
|
557
|
+
|
558
|
+
@property
|
559
|
+
def depth(self):
|
560
|
+
return self.__depth
|
561
|
+
|
562
|
+
@depth.setter
|
563
|
+
def depth(self,n):
|
564
|
+
raise ValueError("can't set tree depth")
|
565
|
+
|
566
|
+
@property
|
567
|
+
def maxdepth(self):
|
568
|
+
return self.__maxdepth
|
569
|
+
|
570
|
+
@maxdepth.setter
|
571
|
+
def maxdepth(self,d):
|
572
|
+
if not isinstance(d,int) or d < 1:
|
573
|
+
raise ValueError('bad maxdepth value: '+str(d))
|
574
|
+
else:
|
575
|
+
self.__maxdepth = d
|
576
|
+
|
577
|
+
@property
|
578
|
+
def mindim(self):
|
579
|
+
return self.__mindim
|
580
|
+
|
581
|
+
@mindim.setter
|
582
|
+
def mindim(self,d):
|
583
|
+
if not isinstance(d,(float,int)) or d < epsilon:
|
584
|
+
raise ValueError('bad mindim value: '+str(d))
|
585
|
+
else:
|
586
|
+
self.__mindim = d
|
587
|
+
|
588
|
+
def getElements(self,bbox):
|
589
|
+
"""return a list of geometry elements with bounding boxes that
|
590
|
+
overalp the provided bounding box, or the empty list if none.
|
591
|
+
|
592
|
+
"""
|
593
|
+
if self.__update:
|
594
|
+
self.updateTree()
|
595
|
+
|
596
|
+
bxidxlist = self.__elem_idx_bbox
|
597
|
+
|
598
|
+
self.mxd = 0
|
599
|
+
|
600
|
+
def recurse(subtree,depth=0):
|
601
|
+
indices = []
|
602
|
+
if depth > self.mxd:
|
603
|
+
self.mxd = depth
|
604
|
+
|
605
|
+
if subtree == None or subtree == []:
|
606
|
+
return []
|
607
|
+
elif subtree[0] == 'e':
|
608
|
+
if boxoverlap2(subtree[1],bbox,dim3 = (self.__n==8)):
|
609
|
+
for ind in subtree[3:]:
|
610
|
+
box = bxidxlist[ind][1]
|
611
|
+
if boxoverlap2(bbox,box,dim3 = (self.__n==8)):
|
612
|
+
indices.append(ind)
|
613
|
+
else:
|
614
|
+
for boxlist in subtree[1:]:
|
615
|
+
if len(subtree) == 1:
|
616
|
+
print('subtree ',subtree)
|
617
|
+
if len(boxlist) > 0:
|
618
|
+
indices += recurse(boxlist,depth+1)
|
619
|
+
return indices
|
620
|
+
|
621
|
+
idx = set(recurse(self.__tree))
|
622
|
+
|
623
|
+
#print("unique indices: ",idx)
|
624
|
+
|
625
|
+
elements = list(map(lambda x: self.__geom[x], list(idx)))
|
626
|
+
|
627
|
+
return elements
|