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/geom_util.py ADDED
@@ -0,0 +1,817 @@
1
+ ## utilty functions companion to yapcad.geom
2
+ ## Born on 26 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
+ from yapcad.geom import *
26
+ import math
27
+ import random
28
+
29
+ """
30
+ Less-essential yapCAD utility functions to support the creation and
31
+ operations on yapcad.geom figures.
32
+
33
+ """
34
+
35
+
36
+ def _reverse_element(element):
37
+ """Return a geometry element with its orientation reversed."""
38
+
39
+ return reverseGeomList([element])[0]
40
+
41
+
42
+ def _poly_signed_area(points):
43
+ """Compute signed area of a closed polygon represented by ``points``."""
44
+
45
+ if not points:
46
+ return 0.0
47
+ total = 0.0
48
+ for i in range(1, len(points)):
49
+ x1, y1 = points[i-1][0], points[i-1][1]
50
+ x2, y2 = points[i][0], points[i][1]
51
+ total += (x1 * y2) - (x2 * y1)
52
+ return 0.5 * total
53
+
54
+
55
+ def _stitch_geomlist(gl, tol):
56
+ """Group line/arc primitives from ``gl`` into contiguous loops."""
57
+
58
+ segments = [g for g in gl if isline(g) or isarc(g)]
59
+ others = [g for g in gl if not (isline(g) or isarc(g))]
60
+
61
+ if not segments:
62
+ return [], others
63
+
64
+ nodes = []
65
+
66
+ def node_id(p):
67
+ for idx, existing in enumerate(nodes):
68
+ if dist(existing, p) <= tol:
69
+ return idx
70
+ nodes.append(point(p))
71
+ return len(nodes) - 1
72
+
73
+ edges = []
74
+ adjacency = {}
75
+
76
+ for idx, seg in enumerate(segments):
77
+ start = sample(seg, 0.0)
78
+ end = sample(seg, 1.0)
79
+ s_id = node_id(start)
80
+ e_id = node_id(end)
81
+ edges.append({'geom': seg, 'start': s_id, 'end': e_id, 'used': False})
82
+ adjacency.setdefault(s_id, []).append(idx)
83
+ adjacency.setdefault(e_id, []).append(idx)
84
+
85
+ loops = []
86
+
87
+ for idx in range(len(edges)):
88
+ edge = edges[idx]
89
+ if edge['used']:
90
+ continue
91
+ loop = []
92
+ current_idx = idx
93
+ start_node = edge['start']
94
+ prev_node = start_node
95
+ while True:
96
+ edge = edges[current_idx]
97
+ if edge['used']:
98
+ break
99
+ if edge['start'] != prev_node:
100
+ if dist(nodes[edge['end']], nodes[prev_node]) <= tol:
101
+ edge['geom'] = _reverse_element(edge['geom'])
102
+ edge['start'], edge['end'] = edge['end'], edge['start']
103
+ elif dist(nodes[edge['start']], nodes[prev_node]) > tol:
104
+ break
105
+ loop.append(edge['geom'])
106
+ edge['used'] = True
107
+ prev_node = edge['end']
108
+ if len(loop) > 1 and dist(nodes[prev_node], nodes[start_node]) <= tol:
109
+ break
110
+ next_idx = None
111
+ for cand_idx in adjacency.get(prev_node, []):
112
+ if edges[cand_idx]['used']:
113
+ continue
114
+ next_idx = cand_idx
115
+ break
116
+ if next_idx is None:
117
+ break
118
+ current_idx = next_idx
119
+ if loop:
120
+ loops.append(loop)
121
+
122
+ return loops, others
123
+
124
+
125
+ def _finalize_poly_points(points, snap_tol):
126
+ if not points:
127
+ return []
128
+
129
+ ply = [point(points[0])]
130
+ for p in points[1:]:
131
+ if dist(p, ply[-1]) > epsilon:
132
+ ply.append(point(p))
133
+
134
+ if dist(ply[0], ply[-1]) > epsilon:
135
+ if dist(ply[0], ply[-1]) <= snap_tol:
136
+ ply[-1] = point(ply[0])
137
+ else:
138
+ ply.append(point(ply[0]))
139
+ else:
140
+ ply[-1] = point(ply[0])
141
+
142
+ return ply
143
+
144
+
145
+ def _convert_geom_sequence(seq, minang, minlen, snap_tol):
146
+ ply = []
147
+ lastpoint = None
148
+ nested_holes = []
149
+
150
+ def _snap(p, last):
151
+ pp = point(p)
152
+ if last is not None and dist(pp, last) <= snap_tol:
153
+ return point(last)
154
+ return pp
155
+
156
+ def addpoint(p, last, force=False):
157
+ candidate = _snap(p, last)
158
+ if force or last is None or dist(candidate, last) > minlen:
159
+ last = point(candidate)
160
+ ply.append(last)
161
+ return last
162
+
163
+ for element in seq:
164
+ if ispoint(element):
165
+ continue
166
+ elif isline(element):
167
+ lastpoint = addpoint(element[0], lastpoint, force=True)
168
+ lastpoint = addpoint(element[1], lastpoint, force=True)
169
+ elif isarc(element):
170
+ firstpoint = None
171
+ for ang in range(0, 360, max(1, round(minang))):
172
+ p = polarSampleArc(element, float(ang), inside=True)
173
+ if not p:
174
+ break
175
+ if firstpoint is None:
176
+ firstpoint = p
177
+ lastpoint = addpoint(p, lastpoint)
178
+ if iscircle(element):
179
+ lastpoint = addpoint(firstpoint, lastpoint)
180
+ else:
181
+ endp = sample(element, 1.0)
182
+ if endp:
183
+ lastpoint = addpoint(endp, lastpoint, force=True)
184
+ elif ispoly(element):
185
+ for p in element:
186
+ lastpoint = addpoint(p, lastpoint, force=True)
187
+ elif isgeomlist(element):
188
+ sub_outer, sub_holes = geomlist2poly_with_holes(element, minang, minlen, checkcont=False)
189
+ if sub_outer:
190
+ for p in sub_outer:
191
+ lastpoint = addpoint(p, lastpoint, force=True)
192
+ nested_holes.extend(sub_holes)
193
+ else:
194
+ raise ValueError(f'bad object in list passed to geomlist2poly: {element}')
195
+
196
+ if not ply:
197
+ return [], nested_holes
198
+
199
+ cleaned = [ply[0]]
200
+ for p in ply[1:]:
201
+ if dist(p, cleaned[-1]) > epsilon:
202
+ cleaned.append(point(p))
203
+
204
+ if dist(cleaned[0], cleaned[-1]) > epsilon:
205
+ if dist(cleaned[0], cleaned[-1]) <= snap_tol:
206
+ cleaned[-1] = point(cleaned[0])
207
+ else:
208
+ cleaned.append(point(cleaned[0]))
209
+ else:
210
+ cleaned[-1] = point(cleaned[0])
211
+
212
+ return cleaned, nested_holes
213
+
214
+
215
+ def geomlist2poly_with_holes(gl, minang=5.0, minlen=0.25, checkcont=False):
216
+
217
+ if checkcont and not iscontinuousgeomlist(gl):
218
+ raise ValueError('non-continuous geometry list passed to geomlist2poly')
219
+
220
+ snap_tol = max(epsilon * 10, minlen * 0.1)
221
+
222
+ positive_candidates = []
223
+ negative_candidates = []
224
+
225
+ segments = []
226
+
227
+ for element in gl:
228
+ if ispoint(element):
229
+ continue
230
+ elif isline(element) or isarc(element):
231
+ segments.append(element)
232
+ elif ispoly(element):
233
+ poly = _finalize_poly_points(element, snap_tol)
234
+ area = _poly_signed_area(poly)
235
+ if area >= 0:
236
+ positive_candidates.append(poly)
237
+ else:
238
+ negative_candidates.append(poly)
239
+ elif isgeomlist(element):
240
+ outer, holes = geomlist2poly_with_holes(element, minang, minlen, checkcont)
241
+ if outer:
242
+ area = _poly_signed_area(outer)
243
+ if area >= 0:
244
+ positive_candidates.append(outer)
245
+ else:
246
+ negative_candidates.append(outer)
247
+ for h in holes:
248
+ area = _poly_signed_area(h)
249
+ if area >= 0:
250
+ positive_candidates.append(h)
251
+ else:
252
+ negative_candidates.append(h)
253
+ else:
254
+ raise ValueError(f'bad object in list passed to geomlist2poly: {element}')
255
+
256
+ if segments:
257
+ loops, _ = _stitch_geomlist(segments, snap_tol)
258
+ for loop in loops:
259
+ poly, holes = _convert_geom_sequence(loop, minang, minlen, snap_tol)
260
+ if poly:
261
+ area = _poly_signed_area(poly)
262
+ if area >= 0:
263
+ positive_candidates.append(poly)
264
+ else:
265
+ negative_candidates.append(poly)
266
+ for h in holes:
267
+ area = _poly_signed_area(h)
268
+ if area >= 0:
269
+ positive_candidates.append(h)
270
+ else:
271
+ negative_candidates.append(h)
272
+
273
+ if not positive_candidates and not negative_candidates:
274
+ return [], []
275
+
276
+ all_candidates = positive_candidates + negative_candidates
277
+ outer = max(all_candidates, key=lambda pts: abs(_poly_signed_area(pts)))
278
+ outer_area = _poly_signed_area(outer)
279
+ orientation = 1 if outer_area >= 0 else -1
280
+
281
+ holes = []
282
+
283
+ for pts in all_candidates:
284
+ if pts is outer:
285
+ continue
286
+ area = _poly_signed_area(pts)
287
+ if orientation * area < 0:
288
+ holes.append(pts)
289
+ continue
290
+ centroid = _polygon_centroid_xy(pts)
291
+ if _point_in_polygon_xy(outer, centroid):
292
+ holes.append(pts)
293
+
294
+ return outer, holes
295
+
296
+
297
+ def _polygon_centroid_xy(pts):
298
+ if not pts:
299
+ return (0.0, 0.0)
300
+ sx = sy = 0.0
301
+ count = 0
302
+ for p in pts:
303
+ sx += p[0]
304
+ sy += p[1]
305
+ count += 1
306
+ if count == 0:
307
+ return (0.0, 0.0)
308
+ return (sx / count, sy / count)
309
+
310
+
311
+ def _point_in_polygon_xy(poly, pt):
312
+ x, y = pt
313
+ inside = False
314
+ if not poly:
315
+ return False
316
+ j = len(poly) - 1
317
+ for i in range(len(poly)):
318
+ xi, yi = poly[i][0], poly[i][1]
319
+ xj, yj = poly[j][0], poly[j][1]
320
+ intersects = ((yi > y) != (yj > y)) and (
321
+ x < (xj - xi) * (y - yi) / (yj - yi + 1e-16) + xi)
322
+ if intersects:
323
+ inside = not inside
324
+ j = i
325
+ return inside
326
+
327
+ def randomPoints(bbox,numpoints):
328
+ """Given a 3D bounding box and a number of points to generate,
329
+ return a list of uniformly generated random points within the
330
+ bounding box"""
331
+
332
+ points = []
333
+ minx = bbox[0][0]
334
+ maxx = bbox[1][0]
335
+ miny = bbox[0][1]
336
+ maxy = bbox[1][1]
337
+ minz = bbox[0][2]
338
+ maxz = bbox[1][2]
339
+ rangex = maxx-minx
340
+ rangey = maxy-miny
341
+ rangez = maxz-minz
342
+ for i in range(numpoints):
343
+ points.append(point(random.random()*rangex+minx,
344
+ random.random()*rangey+miny,
345
+ random.random()*rangez+minz))
346
+ return points
347
+
348
+ def randomCenterInBox(bbox,r):
349
+ """given a bounding box and a radius, generate a random center point
350
+ such that a circle with the specified radius and center point falls
351
+ completely inside the box
352
+
353
+ """
354
+
355
+ minx = bbox[0][0]+r
356
+ maxx = bbox[1][0]-r
357
+ miny = bbox[0][1]+r
358
+ maxy = bbox[1][1]-r
359
+ minz = bbox[0][2]+r
360
+ maxz = bbox[1][2]-r
361
+ rangex = maxx-minx
362
+ rangey = maxy-miny
363
+ rangez = maxz-minz
364
+ x = point(random.random()*rangex+minx,
365
+ random.random()*rangey+miny,
366
+ random.random()*rangez+minz)
367
+ return x
368
+
369
+
370
+ def randomArc(bbox,minr=0.0,maxr=10.0,circle=False):
371
+ """given a bounding box and a minimum and maximum radius, generate an
372
+ arc with random center and radius that falls within the bounding
373
+ box. If ``circle==False``, use randomly generated start and end
374
+ angles, otherwise generate only full circles
375
+
376
+ """
377
+ radr = maxr-minr
378
+ r = random.random()*radr+minr
379
+ start = 0
380
+ end = 360
381
+ if not circle:
382
+ start = random.random()*360.0
383
+ end = start + random.random()*360.0
384
+
385
+ x = randomCenterInBox(bbox,r)
386
+ return arc(x,r,start,end)
387
+
388
+ def randomPoly(bbox,numpoints=10,minr = 1.0,maxr = 10.0):
389
+ """given a bounding box, a number of vertices, and a minimum and
390
+ maximum radius, generate a simple polygon that completely lies
391
+ inside the bounding box, whose vertices are evenly spaced by angle
392
+ around a center point, with randomly chosen radius between
393
+ ``minr`` and ``maxr``
394
+ """
395
+
396
+ angles = []
397
+ rads = []
398
+ ang = 0.0
399
+ rr = maxr-minr
400
+ for i in range(numpoints):
401
+ a = random.random()
402
+ angles.append(ang)
403
+ ang = ang + a
404
+ # print("a: ",a," ang: ",ang)
405
+ rads.append(random.random()*rr+minr)
406
+
407
+ sf = pi2/ang
408
+
409
+ points = []
410
+ x = randomCenterInBox(bbox,maxr)
411
+
412
+ for i in range(numpoints):
413
+ p = [cos(angles[i]*sf)*rads[i],
414
+ sin(angles[i]*sf)*rads[i],0,1]
415
+ points.append(add(p,x))
416
+
417
+ return points + [ points[0] ]
418
+
419
+ def makeLineSpiral(center, turnRad, # radius after one full turn
420
+ turns, # number of turns
421
+ dstep = 10.0, # sampling resolution in degrees
422
+ minlen = 0.25): # minimum distance between points
423
+ """given a center point, the increase in radius per turn, the number
424
+ of turns, and the angular resolution of the approximation,
425
+ generate a yapcqad.geom poly approximation of the spiral
426
+
427
+ """
428
+
429
+ # make a spiral of points
430
+ spiral = []
431
+ rstep = turnRad*dstep/360.0
432
+
433
+ def addpoint(p,lastpoint):
434
+ if not lastpoint or dist(p,lastpoint) > minlen:
435
+ lastpoint = p
436
+ spiral.append(p)
437
+ return lastpoint
438
+
439
+ lastpoint = None
440
+ for i in range(round(360*turns/dstep)):
441
+ ang = i * dstep*pi2/360.0
442
+ r = i * rstep
443
+ p = add(center,
444
+ point(math.cos(ang)*r,math.sin(ang)*r))
445
+ lastpoint = addpoint(p,lastpoint)
446
+ return spiral
447
+
448
+ def makeArcSpiral(center, turnRad, # radius after one full turn
449
+ turns, # number of turns
450
+ dstep = 45): # sampling resolution in degrees
451
+ """given a center point, the increase in radius per turn, the number
452
+ of turns, and the angular resolution of the approximation,
453
+ generate an approximation of the spiral using circular arcs as
454
+ segments instead of straightlines. Return a yapcqad.geom geomlist
455
+ approximation of the spiral
456
+
457
+ """
458
+ spPoints = makeLineSpiral(center,turnRad,turns,dstep)
459
+
460
+ arcs=[]
461
+ for i in range(1,len(spPoints)):
462
+ p0 = spPoints[i-1]
463
+ p1 = spPoints[i]
464
+ direct = sub(p1,p0)
465
+ orth = scale3(orthoXY(direct),-1.1)
466
+ mid = scale3(add(p0,p1),0.5)
467
+ cent = add(mid,orth)
468
+ r= dist(cent,p0)
469
+ r2 = dist(cent,p1)
470
+ assert close(r,r2)
471
+ circ = arc(cent,r)
472
+ u1 = unsamplearc(circ,p0)
473
+ u2 = unsamplearc(circ,p1)
474
+ a = segmentarc(circ,u1,u2)
475
+ arcs.append(a)
476
+ return arcs
477
+
478
+ def polarSampleArc(c,ang,inside=True):
479
+ """given an arc ``c`` , sample that arc at ``ang`` degrees from the
480
+ start of the arc, proceeding clockwise. If ``samplereverse`` is
481
+ true, sample the arc at ``-ang`` degrees from the end, proceeding
482
+ counterclockwise. If ``c`` is not a complete circle and the
483
+ specified ``ang`` falls outside the closed [start,end] interval
484
+ and ``inside == True``, return ``False``, otherwise return the
485
+ sampled value.
486
+
487
+ """
488
+ p = c[0]
489
+ r = c[1][0]
490
+ start=c[1][1]
491
+ end=c[1][2]
492
+ samplereverse = (c[1][3] == -2)
493
+ circle = (start == 0 and end == 360)
494
+
495
+ if not circle:
496
+ start = start % 360.0
497
+ end = end % 360.0
498
+ if end < start:
499
+ end += 360.0
500
+ if inside and ang > (end - start):
501
+ return False
502
+
503
+ if samplereverse:
504
+ start=end
505
+ ang *= -1
506
+
507
+ srad = (start+ang)*pi2/360.0
508
+ q = scale3(vect(cos(srad),sin(srad)),r)
509
+ return add(p,q)
510
+
511
+
512
+ def triarea(p1,p2,p3):
513
+ """
514
+ utility function to return the area of a triangle
515
+ """
516
+ v1=sub(p2,p1)
517
+ v2=sub(p3,p1)
518
+ cp = cross(v1,v2)
519
+ return mag(cp)/2
520
+
521
+ def geomlist2poly(gl,minang=5.0,minlen=0.25,checkcont=False):
522
+ outer, _ = geomlist2poly_with_holes(gl, minang, minlen, checkcont)
523
+ return outer
524
+
525
+
526
+
527
+ def combineglist(g1,g2,operation):
528
+ """function to perform set operations on geometry lists.
529
+
530
+ ``g1`` and ``g2`` are closed figure geometry lists.
531
+ ``operation`` is one of ``["union","intersection","difference"]``
532
+
533
+ result is a potentially zero-length geometry list representing the
534
+ result, which may or may not be simply connected, but should
535
+ always be a closed figure.
536
+
537
+ """
538
+
539
+ def poly2lines(pol):
540
+ l = []
541
+ for i in range(1,len(pol)):
542
+ l.append([pol[i-1],pol[i]])
543
+ return l
544
+
545
+ if ispoly(g1):
546
+ g1 = poly2lines(g1)
547
+
548
+ if ispoly(g2):
549
+ g2 = poly2lines(g2)
550
+
551
+ # if not (isclosedgeomlist(g1) and isclosedgeomlist(g2)):
552
+ # raise ValueError("bad arguments passed to combineglist")
553
+
554
+ if not operation in ["union","intersection","difference"]:
555
+ raise ValueError("bad operation specified for combineglist")
556
+
557
+ bbox1 = bbox(g1)
558
+ bbox2 = bbox(g2)
559
+ try :
560
+ inter = intersectXY(g1,g2,params=True)
561
+ except ValueError:
562
+ print("had a problem intersecting following geometries:")
563
+ print("g1: ",g1)
564
+ print("g2: ",g2)
565
+ raise
566
+
567
+ if inter != False and (inter[0] == False or inter[1] == False):
568
+ raise ValueError('bad intersection list: ',inter)
569
+ # Given parameter values that define two potential sub-arcs of
570
+ # a figure, determine which sub-arc is valid by looking for
571
+ # intersections between these parameter values. In the
572
+ # condition where u2 is smaller than u1, it's possible that
573
+ # this represents an arc in the counter-clockwise direction
574
+ # between u2 and u1. It also could represent a clockwise arc
575
+ # that "wraps around" from u1 to (u2+1). We will check for
576
+ # values x1, x2 in ilist that are u2 < x1 < u1 and u1 < x2 <
577
+ # (u2+1.0). The existance of x1 or x2 rules out the
578
+ # corresponding arc. If neither x1 nor x2 exists, we bias
579
+ # towards the counter-clockwise arc.
580
+
581
+ def between(u1,u2,ilist):
582
+ if len(ilist) < 2:
583
+ raise ValueError('bad ilist')
584
+
585
+ if u1 > u2 :
586
+ if len(ilist) == 2:
587
+ return u1,u2+1.0,False
588
+ x1s = list(filter(lambda x: x > u2 and x < u1,ilist))
589
+ x2s = list(filter(lambda x: x > u1 or x < u2,ilist))
590
+ l1 = len(x1s)
591
+ l2 = len(x2s)
592
+ #print("u1: ",u1," u2: ",u2," ilist: ",ilist," x1s: ",x1s," x2s: ",x2s)
593
+ #if l1 > 0 and l2 > 0:
594
+ # print('WARNING: intersections on both sides')
595
+
596
+ if l1 > l2:
597
+ # print("AA")
598
+ if True or operation == 'union':
599
+ return u1,u2+1.0,False
600
+ else:
601
+ return u2,u1,True
602
+ else:
603
+ # print("A-")
604
+ return u2,u1,True
605
+
606
+ else:
607
+ if len(ilist) == 2:
608
+ return u1,u2,False
609
+ x1s = list(filter(lambda x: x > u1 and x < u2,ilist))
610
+ x2s = list(filter(lambda x: x > u2 or x < u1,ilist))
611
+ l1 = len(x1s)
612
+ l2 = len(x2s)
613
+ #print("u1: ",u1," u2: ",u2," ilist: ",ilist," x1s: ",x1s," x2s: ",x2s)
614
+ #if l1 > 0 and l2 > 0:
615
+ # print('WARNING: intersections on both sides')
616
+
617
+ if l1 > l2:
618
+ # print("BB")
619
+
620
+ if True or operation == 'union':
621
+ return u2,u1+1,False
622
+ else:
623
+ return u1,u2,False
624
+ else:
625
+ # print("B-")
626
+ return u1,u2,False
627
+
628
+
629
+ ## utility to perform combination on one "segment"
630
+ def cmbin(g1,g2,itr):
631
+ if not isgeomlist(g1):
632
+ g1 = [ g1 ]
633
+
634
+ if not isgeomlist(g2):
635
+ g2 = [ g2 ]
636
+
637
+ g1s = itr[0][0]
638
+ g1e = itr[0][1]
639
+ g2s = itr[1][0]
640
+ g2e = itr[1][1]
641
+
642
+ seg = []
643
+ ZLEN1=close(g1s,g1e)
644
+ ZLEN2=close(g2s,g2e)
645
+
646
+ g1reverse=False
647
+ g2reverse=False
648
+
649
+ if True or operation == 'difference':
650
+ if g1e < g1s:
651
+ g1e+=1.0
652
+ #g1s,g1e,g1reverse = between(g1s,g1e,inter[0])
653
+ #import pdb ; pdb.set_trace()
654
+ g2s,g2e,g2reverse = between(g2s,g2e,inter[1])
655
+ g2reverse = False
656
+ else:
657
+ if g1e < g1s:
658
+ g1e+=1.0
659
+ if g2e < g2s:
660
+ g2e += 1.0
661
+
662
+ #p1=sample(g1,((g1s+g1e)/2)%1.0)
663
+
664
+ p1inside=0
665
+ for i in range(5):
666
+ u = (i+1)/6.0
667
+ p = sample(g1,(u*g1e+(1.0-u)*g1s)%1.0)
668
+ if isinsideXY(g2,p):
669
+ p1inside=p1inside+1
670
+
671
+ p2inside = 0
672
+ for i in range(5):
673
+ u = (i+1)/6.0
674
+ p = sample(g2,(u*g2e+(1.0-u)*g2s)%1.0)
675
+ if isinsideXY(g1,p):
676
+ p2inside=p2inside+1
677
+
678
+ #if p1inside > 0 and p2inside > 0:
679
+ # print("warning: inside test succeeded for both p1s and p2s: ",
680
+ # p1inside," ",p2inside)
681
+
682
+ #if p1inside == 0 and p2inside == 0:
683
+ # print("warning: inside test failed for both p1s and p2s")
684
+
685
+ #p2=sample(g2,((g2s+g2e)/2)%1.0)
686
+
687
+ if ZLEN1 and ZLEN2:
688
+ print ('both segments zero length')
689
+ return []
690
+ elif ZLEN2 and not ZLEN1:
691
+ print ('zero length segment 2')
692
+ if operation=='union':
693
+ return segmentgeomlist(g1,g1s,g1e,closed=True)
694
+ elif operation=='difference':
695
+ return []
696
+ else: #intersection
697
+ return []
698
+ elif ZLEN1 and not ZLEN2:
699
+ print ('zero length segment 1')
700
+ if operation=='union':
701
+ if g2e < g2s:
702
+ g2e += 1.0
703
+ return segmentgeomlist(g2,g2s,g2e,closed=True)
704
+ else: # difference or intersection
705
+ return []
706
+
707
+ if operation == 'union':
708
+ #if isinsideXY(g2,p1):
709
+ if p1inside > p2inside:
710
+ # if g2e < g2s:
711
+ # g2e += 1.0
712
+ seg += segmentgeomlist(g2,g2s,g2e,closed=True)
713
+ else:
714
+ seg += segmentgeomlist(g1,g1s,g1e,closed=True)
715
+ elif operation == 'intersection':
716
+ #if isinsideXY(g2,p1):
717
+ if p1inside > p2inside:
718
+ seg += segmentgeomlist(g1,g1s,g1e,closed=True)
719
+ else:
720
+ # if g2e < g2s:
721
+ # g2e += 1.0
722
+ #seg += segmentgeomlist(g2,g2s,g2e,reverse=g2reverse)
723
+ seg += segmentgeomlist(g2,g2s,g2e,closed=True)
724
+ elif operation == 'difference':
725
+ s = []
726
+ #if isinsideXY(g2,p1):
727
+ if p1inside > p2inside:
728
+ pass
729
+ else:
730
+ # print("rsort: ",vstr(inter))
731
+ seg += segmentgeomlist(g1,g1s,g1e,closed=True)
732
+ # print("g2s: ",g2s," g2e: ",g2e," g2reverse: ",g2reverse)
733
+ s = segmentgeomlist(g2,g2s,g2e,closed=True,reverse=g2reverse)
734
+ s = reverseGeomList(s)
735
+ seg += s
736
+ if len(inter[0]) > 2:
737
+ pass
738
+ #combineDebugGL.append(s)
739
+ # print("seg: ",vstr(seg))
740
+
741
+ return seg
742
+
743
+ ## utility function to sort intersections into non-decreasing
744
+ ## order
745
+ def rsort(il):
746
+ nl = []
747
+ rl = []
748
+ rr = []
749
+ for i in range(len(il[0])):
750
+ nl.append([il[0][i],il[1][i]])
751
+ nl.sort(key=lambda x: x[0])
752
+ for i in range(len(nl)):
753
+ rl.append(nl[i][0])
754
+ rr.append(nl[i][1])
755
+ return [rl,rr]
756
+
757
+ if inter == False: # disjoint, but bounding boxes might be
758
+ # null or one could be inside the other
759
+ if not bbox1 and bbox2: # g1 is empty, but g2 contains geometry
760
+ if operation=='union':
761
+ return g2
762
+ else:
763
+ return []
764
+ if bbox1 and not bbox2: # g2 is empty, but g1 isn't
765
+ if operation=='union' or operation=='difference':
766
+ return g1
767
+ if not bbox1 and not bbox2: # no geometry at all
768
+ return []
769
+ ## OK, no intersection but it is possible that one profile
770
+ ## could be inside the other. Do fast bounding box checks
771
+ ## before doing intersection-based checking.
772
+ if isinsidebbox(bbox1,bbox2[0]) and isinsidebbox(bbox1,bbox2[1]) \
773
+ and isinsideXY(g1,sample(g2,0.0)): # g2 is inside g1
774
+ ## g2 is inside g1
775
+ if operation == 'union':
776
+ return g1
777
+ elif operation == 'intersection':
778
+ return g2
779
+ else: #difference, g2 is a hole in g1
780
+ return [g1,g2]
781
+ elif isinsidebbox(bbox2,bbox1[0]) and isinsidebbox(bbox2,bbox1[1]) \
782
+ and isinsideXY(g2,sample(g1,0.0)): # g1 is inside g2
783
+ ## g1 is indside g2
784
+ if operation == 'union':
785
+ return g2
786
+ elif operation == 'intersection':
787
+ return g1
788
+ else: #difference, g2 has eaten g1
789
+ return []
790
+ else: # g1 and g2 are disjoint
791
+ if operation == 'union':
792
+ return [g1,g2]
793
+ elif operation == 'difference':
794
+ return g1
795
+ else: #intersection
796
+ return []
797
+ if len(inter[0]) == 1 and len(inter[1]) == 1:
798
+ ## single point of intersection:
799
+ if operation == 'union':
800
+ return [g1, g2]
801
+ elif operation == 'difference':
802
+ return g1
803
+ else: #intersection
804
+ return []
805
+ ## There are two or more points of intersection.
806
+ inter = rsort(inter)
807
+ #print("rsort: ",vstr(inter))
808
+
809
+ if len(inter[0]) %2 != 0:
810
+ print("WARNING: odd number of intersections (",len(inter[0]),", unpredictable behavior may result")
811
+ r = []
812
+ for i in range(1,len(inter[0])+1):
813
+ r += cmbin(g1,g2,[[inter[0][i-1],
814
+ inter[0][i%len(inter[0])]],
815
+ [inter[1][i-1],
816
+ inter[1][i%len(inter[1])]]])
817
+ return r