yapCAD 0.3.0__py2.py3-none-any.whl → 0.5.0__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 CHANGED
@@ -1,392 +1,98 @@
1
1
  ## yapCAD boolen operation support
2
2
 
3
3
  from yapcad.geom import *
4
- from yapcad.poly import *
5
-
6
- combineDebugGL=[]
7
-
8
- class Boolean(IntersectGeometry):
4
+ from yapcad.geom_util import *
5
+ from yapcad.geometry import *
6
+ from yapcad.poly import cullZeroLength
7
+ class Boolean(Geometry):
9
8
  """Boolean operations on Polygons"""
10
9
 
11
10
  types = ('union','intersection','difference')
12
-
13
- def __init__(self, type='union', polys=None):
14
- super().__init__()
15
- if type not in self.types:
16
- raise ValueError('invalid type passed to Boolean(): {}'.format(type))
17
11
 
18
- if polys is None:
19
- polys = []
12
+ def __repr__(self):
13
+ return f"Boolean({self.type},{self.elem})"
20
14
 
15
+ def __init__(self,type='union',polys=[]):
16
+ super().__init__()
17
+ self._setClosed(True)
18
+ self._setSampleable(True)
19
+ if not type in self.types:
20
+ raise ValueError('invalid type passed to Boolean(): {}'.format(type))
21
21
  for p in polys:
22
- if not (isinstance(p, Polygon) or isinstance(p, Boolean)):
23
- raise ValueError('non-poly or non-boolean passed to Boolean(): {}'.format(p))
24
-
25
- self._elem = list(polys)
26
- self._type = type
27
- self._update = True
28
- self._outline = []
29
-
30
-
31
- def combine_geom(self,g1,g2):
32
- bbox1 = g1.bbox()
33
- bbox2 = g2.bbox()
34
- try :
35
- inter = intersectXY(g1.geom(),g2.geom(),params=True)
36
- except ValueError:
37
- print("had a problem intersecting following geometries:")
38
- print("g1.geom(): ",g1.geom())
39
- print("g2.geom(): ",g2.geom())
40
- raise
41
-
42
- if inter != False and (inter[0] == False or inter[1] == False):
43
- raise ValueError('bad intersection list: ',inter)
44
- # Given parameter values that define two potential sub-arcs of
45
- # a figure, determine which sub-arc is valid by looking for
46
- # intersections between these parameter values. In the
47
- # condition where u2 is smaller than u1, it's possible that
48
- # this represents an arc in the counter-clockwise direction
49
- # between u2 and u1. It also could represent a clockwise arc
50
- # that "wraps around" from u1 to (u2+1). We will check for
51
- # values x1, x2 in ilist that are u2 < x1 < u1 and u1 < x2 <
52
- # (u2+1.0). The existance of x1 or x2 rules out the
53
- # corresponding arc. If neither x1 nor x2 exists, we bias
54
- # towards the counter-clockwise arc.
55
-
56
- def between(u1,u2,ilist):
57
- if len(ilist) < 2:
58
- raise ValueError('bad ilist')
22
+ if not ( isinstance(p,Geometry) and p.isclosed()):
23
+ raise ValueError('not closed Geometry instance: {}'.format(p))
24
+ self.elem.append(deepcopy(p))
25
+
26
+ self.__type=type
27
+ self._setUpdate(True)
28
+ self.__outline=[]
29
+
30
+ @property
31
+ def type(self):
32
+ return self.__type
33
+
34
+ def _combine_geom(self,g1,g2):
35
+ gl1 = g1.geom
36
+ gl2 = g2.geom
37
+
38
+ return combineglist(gl1,gl2,self.type)
39
+
40
+ def _calcCenter(self):
41
+ l = len(self.__outline)
42
+ if l == 0:
43
+ return None
44
+ elif l == 1:
45
+ return center(self.__outline[0]) # center is sole point
46
+ else:
59
47
 
60
- if u1 > u2 :
61
- if len(ilist) == 2:
62
- return u1,u2+1.0,False
63
- x1s = list(filter(lambda x: x > u2 and x < u1,ilist))
64
- x2s = list(filter(lambda x: x > u1 or x < u2,ilist))
65
- l1 = len(x1s)
66
- l2 = len(x2s)
67
- #print("u1: ",u1," u2: ",u2," ilist: ",ilist," x1s: ",x1s," x2s: ",x2s)
68
- if l1 > 0 and l2 > 0:
69
- print('WARNING: intersections on both sides')
70
- elif l1 > l2:
71
- print("AA")
72
- if True or self._type == 'union':
73
- return u1,u2+1.0,False
74
- else:
75
- return u2,u1,True
76
- else:
77
- print("A-")
78
- return u2,u1,True
79
-
80
- else:
81
- if len(ilist) == 2:
82
- return u1,u2,False
83
- x1s = list(filter(lambda x: x > u1 and x < u2,ilist))
84
- x2s = list(filter(lambda x: x > u2 or x < u1,ilist))
85
- l1 = len(x1s)
86
- l2 = len(x2s)
87
- #print("u1: ",u1," u2: ",u2," ilist: ",ilist," x1s: ",x1s," x2s: ",x2s)
88
- if l1 > 0 and l2 > 0:
89
- print('WARNING: intersections on both sides')
90
- elif l1 > l2:
91
- print("BB")
92
-
93
- if True or self._type == 'union':
94
- return u2,u1+1,False
95
- else:
96
- return u1,u2,False
97
- else:
98
- print("B-")
99
- return u1,u2,False
48
+ if dist(center(self.__outline[0]),
49
+ center(self.__outline[-1])) < epsilon:
50
+ l -= 1
100
51
 
101
-
102
- ## utility to perform combination on one "segment"
103
- def cmbin(g1,g2,itr):
104
- g1s = itr[0][0]
105
- g1e = itr[0][1]
106
- g2s = itr[1][0]
107
- g2e = itr[1][1]
108
-
109
- seg = []
110
- ZLEN1=close(g1s,g1e)
111
- ZLEN2=close(g2s,g2e)
112
-
113
- g1reverse=False
114
- g2reverse=False
52
+ p = center(self.__outline[0])
53
+ for i in range(1,l):
54
+ p = add(center(self.__outline[i]),p)
55
+
56
+ return scale3(p,1/l)
57
+
58
+ def translate(self,delta):
59
+ self._setUpdate(True)
60
+ for p in self.elem:
61
+ p.translate(delta)
62
+
63
+ def scale(self,sx,sy=False,sz=False,cent=point(0,0)):
64
+ self._setUpdate(True)
65
+ for p in self.elem:
66
+ p.scale(sx,sy,sz,cent)
67
+
68
+ def rotate(self,angle,cent=point(0,0,0),axis=point(0,0,1)):
69
+ self._setUpdate(True)
70
+ for p in self.elem:
71
+ p.rotate(angle,cent,axis)
72
+
73
+ def mirror(self,plane):
74
+ self._setUpdate(True)
75
+ for p in self.elem:
76
+ p.mirror(plane)
77
+
78
+ def transform(self,m):
79
+ self._setUpdate(True)
80
+ for p in self.elem:
81
+ p.transform(m)
115
82
 
116
- if True or self._type == 'difference':
117
- if g1e < g1s:
118
- g1e+=1.0
119
- #g1s,g1e,g1reverse = between(g1s,g1e,inter[0])
120
- g2s,g2e,g2reverse = between(g2s,g2e,inter[1])
121
- g2reverse = False
122
- else:
123
- if g1e < g1s:
124
- g1e+=1.0
125
- if g2e < g2s:
126
- g2e += 1.0
127
-
128
- p1=g1.sample(((g1s+g1e)/2)%1.0)
129
-
130
- p1inside=0
131
- for i in range(5):
132
- u = (i+1)/6.0
133
- p = g1.sample((u*g1e+(1.0-u)*g1s)%1.0)
134
- if g2.isinside(p):
135
- p1inside=p1inside+1
136
-
137
- p2inside = 0
138
- for i in range(5):
139
- u = (i+1)/6.0
140
- p = g2.sample((u*g2e+(1.0-u)*g2s)%1.0)
141
- if g1.isinside(p):
142
- p2inside=p2inside+1
143
-
144
- if p1inside > 0 and p2inside > 0:
145
- print("warning: inside test succeeded for both p1s and p2s: ",
146
- p1inside," ",p2inside)
147
-
148
- if p1inside == 0 and p2inside == 0:
149
- print("warning: inside test failed for both p1s and p2s")
150
-
151
- p2=g2.sample(((g2s+g2e)/2)%1.0)
152
-
153
- if ZLEN1 and ZLEN2:
154
- print ('both segments zero length')
155
- return []
156
- elif ZLEN2 and not ZLEN1:
157
- print ('zero length segment 2')
158
- if self._type=='union':
159
- return g1.segment(g1s,g1e)
160
- elif self._type=='difference':
161
- return
162
- else: #intersection
163
- return []
164
- elif ZLEN1 and not ZLEN2:
165
- print ('zero length segment 1')
166
- if self._type=='union':
167
- if g2e < g2s:
168
- g2e += 1.0
169
- return g2.segment(g2s,g2e)
170
- else: # difference or intersection
171
- return []
172
-
173
- if self._type == 'union':
174
- #if g2.isinside(p1):
175
- if p1inside > p2inside:
176
- # if g2e < g2s:
177
- # g2e += 1.0
178
- seg += g2.segment(g2s,g2e)
179
- else:
180
- seg += g1.segment(g1s,g1e)
181
- elif self._type == 'intersection':
182
- #if g2.isinside(p1):
183
- if p1inside > p2inside:
184
- seg += g1.segment(g1s,g1e)
185
- else:
186
- # if g2e < g2s:
187
- # g2e += 1.0
188
- #seg += g2.segment(g2s,g2e,reverse=g2reverse)
189
- seg += g2.segment(g2s,g2e)
190
- elif self._type == 'difference':
191
- s = []
192
- #if g2.isinside(p1):
193
- if p1inside > p2inside:
194
- pass
195
- else:
196
- # print("rsort: ",vstr(inter))
197
- seg += g1.segment(g1s,g1e)
198
- # print("g2s: ",g2s," g2e: ",g2e," g2reverse: ",g2reverse)
199
- s = g2.segment(g2s,g2e,reverse=g2reverse)
200
- s = reverseGeomList(s)
201
- seg += s
202
- if len(inter[0]) > 2:
203
- combineDebugGL.append(s)
204
- # print("seg: ",vstr(seg))
205
-
206
- return seg
207
-
208
- ## utility function to sort intersections into non-decreasing
209
- ## order
210
- def rsort(il):
211
- nl = []
212
- rl = []
213
- rr = []
214
- for i in range(len(il[0])):
215
- nl.append([il[0][i],il[1][i]])
216
- nl.sort(key=lambda x: x[0])
217
- for i in range(len(nl)):
218
- rl.append(nl[i][0])
219
- rr.append(nl[i][1])
220
- return [rl,rr]
221
-
222
- if inter == False: # disjoint, but bounding boxes might be
223
- # null or one could be inside the other
224
- if not bbox1 and bbox2: # g1 is empty, but g2 contains geometry
225
- if self._type=='union':
226
- return g2.geom()
227
- else:
228
- return []
229
- if bbox1 and not bbox2: # g2 is empty, but g1 isn't
230
- if self._type=='union' or self._type=='difference':
231
- return g1.geom()
232
- if not bbox1 and not bbox2: # no geometry at all
233
- return []
234
- ## OK, no intersection but it is possible that one profile
235
- ## could be inside the other. Do fast bounding box checks
236
- ## before doing intersection-based checking.
237
- if isinsidebbox(bbox1,bbox2[0]) and isinsidebbox(bbox1,bbox2[1]) \
238
- and g1.isinside(g2.sample(0.0)): # g2 is inside g1
239
- ## g2 is inside g1
240
- if self._type == 'union':
241
- return g1.geom()
242
- elif self._type == 'intersection':
243
- return g2.geom()
244
- else: #difference, g2 is a hole in g1
245
- return [g1.geom(),g2.geom()]
246
- elif isinsidebbox(bbox2,bbox1[0]) and isinsidebbox(bbox2,bbox1[1]) \
247
- and g2.isinside(g1.sample(0.0)): # g1 is inside g2
248
- ## g1 is indside g2
249
- if self._type == 'union':
250
- return g2.geom()
251
- elif self._type == 'intersection':
252
- return g1.geom()
253
- else: #difference, g2 has eaten g1
254
- return []
255
- else: # g1 and g2 are disjoint
256
- if self._type == 'union':
257
- return [g1.geom(),g2.geom()]
258
- elif self._type == 'difference':
259
- return g1.geom()
260
- else: #intersection
261
- return []
262
- if len(inter[0]) == 1 and len(inter[1]) == 1:
263
- ## single point of intersection:
264
- if self._type == 'union':
265
- return [g1.geom(), g2.geom()]
266
- elif self._type == 'difference':
267
- return g1.geom()
268
- else: #intersection
269
- return []
270
- ## There are two or more points of intersection.
271
- inter = rsort(inter)
272
- #print("rsort: ",vstr(inter))
273
-
274
- if len(inter[0]) %2 != 0:
275
- print("WARNING: odd number of intersections (",len(inter[0]),", unpredictable behavior may result")
276
- r = []
277
- for i in range(1,len(inter[0])+1):
278
- r += cmbin(g1,g2,[[inter[0][i-1],
279
- inter[0][i%len(inter[0])]],
280
- [inter[1][i-1],
281
- inter[1][i%len(inter[1])]]])
282
- return r
283
-
284
- def bbox(self):
285
- return bbox(self.geom())
286
-
287
- def getCenter(self):
288
- gl = self.geom()
289
- if gl == []:
290
- raise ValueError('empty Boolean, no center')
291
- return center(gl)
292
-
293
- def getLength(self):
294
- gl = self.geom()
295
- if gl == []:
296
- raise ValueError('empty Boolean, no length')
297
- return length(gl)
298
-
299
- def segment(self,u1,u2,reverse=False):
300
- gl = self.geom()
301
- if gl == []:
302
- raise ValueError('empty Boolean, segment not defined')
303
- return segmentgeomlist(gl,u1,u2,closed=True,reverse=reverse)
304
-
305
- def mirror(self,plane,poly=False):
306
- b = deepcopy(self)
307
- b._elem = []
308
- b._update=True
309
- for p in self._elem:
310
- p2 = p.mirror(plane,poly=True)
311
- b._elem.append(p2)
312
- if poly:
313
- return b
314
- return b.geom()
315
-
316
- def rotate(self,angle,cent=point(0,0,0),axis=point(0,0,1),poly=False):
317
- b = deepcopy(self)
318
- b._elem = []
319
- b._update=True
320
- for p in self._elem:
321
- p2 = p.rotate(angle,cent,axis,poly=True)
322
- b._elem.append(p2)
323
- if poly:
324
- return b
325
- return b.geom()
326
-
327
- def scale(self,sx,sy=False,sz=False,cent=point(0,0),poly=False):
328
- b = deepcopy(self)
329
- b._elem = []
330
- b._update=True
331
- for p in self._elem:
332
- p2 = p.scale(sx,sy,sz,cent,poly=True)
333
- b._elem.append(p2)
334
- if poly:
335
- return b
336
- return b.geom()
337
-
338
- def translate(self,delta,poly=False):
339
- b = deepcopy(self)
340
- b._elem = []
341
- b._update=True
342
- for p in self._elem:
343
- p2 = p.translate(delta,poly=True)
344
- b._elem.append(p2)
345
- if poly:
346
- return b
347
- return b.geom()
348
-
349
- def sample(self,u):
350
- gl = self.geom()
351
- if gl == []:
352
- raise ValueError('empty Boolean, sample not defined')
353
- return sample(gl,u)
354
-
355
- def isinside(self,p):
356
- gm = self.geom()
357
- if gm == []:
358
- raise ValueError('empty Boolean, inside not defined')
359
- bb = bbox(gm)
360
- p2 = add([1,1,0,1],bb[1])
361
- l = line(p,p2)
362
-
363
- pp = intersectGeomListXY(l,gm)
364
- if pp == False:
365
- return False
366
- return len(pp) % 2 == 1
367
-
368
- def grow(self,r):
369
- if close(r,0.0):
370
- return
371
- elif r < 0:
372
- raise ValueError('negative growth not allowed')
373
-
374
- if self._type in ['union','intersection']:
375
- for p in self._elem:
376
- p.grow(r)
377
- self._update = True
378
- else:
379
- raise NotImplementedError("Don't have grow support for {} yet".format(self._type))
380
-
381
-
83
+ @property
382
84
  def geom(self):
383
- if self._update:
384
- if len(self._elem)==2:
385
- self._outline = self.combine_geom(self._elem[0],self._elem[1])
386
- self._outline = cullZeroLength(self._outline)
387
- self._update = False
85
+ if self.update:
86
+ if len(self.elem)==2:
87
+ self.__outline = self._combine_geom(self.elem[0],self.elem[1])
88
+ self.__outline = cullZeroLength(self.__outline)
89
+ self._setUpdate(False)
90
+ self._setBbox(bbox(self.__outline))
91
+ self._setLength(length(self.__outline))
92
+ self._setCenter(self._calcCenter())
388
93
  else:
389
- raise NotImplementedError("don't know how to do {} yet for {} polygons".format(self._type,len(self._elem)))
390
- return deepcopy(self._outline)
94
+ raise NotImplementedError(
95
+ f"don't know how to do {self.type} yet for {len(self.elem)} polygons")
96
+ return deepcopy(self.__outline)
391
97
 
392
98
 
yapcad/drawable.py CHANGED
@@ -5,6 +5,7 @@
5
5
  ## See licensing terms here: https://github.com/rdevaul/yapCAD/blob/master/LICENSE
6
6
 
7
7
  from yapcad.geom import *
8
+ from yapcad.geometry import *
8
9
 
9
10
  ## Generic drawing functions -- assumed to use current coordinate
10
11
  ## transform and drawing pen (color, line weight, etc.)
@@ -56,6 +57,40 @@ class Drawable:
56
57
  if 'o' in self.__pointstyle:
57
58
  self.draw_circle(p,self.__pointsize)
58
59
 
60
+ ## utility function to draw a 2D or 3D bounding box
61
+ def draw_bbox(self,box,dim3=False):
62
+ length = box[1][0]-box[0][0]
63
+ width = box[1][1]-box[0][1]
64
+ height = box[1][2]-box[0][2]
65
+
66
+ p0=box[0]
67
+ p1=add(p0,point(length,0,0))
68
+ p2=add(p1,point(0,width,0))
69
+ p3=add(p0,point(0,width,0))
70
+
71
+ self.draw_line(p0,p1)
72
+ self.draw_line(p1,p2)
73
+ self.draw_line(p2,p3)
74
+ self.draw_line(p3,p0)
75
+
76
+ if not dim3:
77
+ return
78
+
79
+ p4=add(p0,point(0,0,height))
80
+ p5=add(p4,point(length,0,0))
81
+ p6=box[1]
82
+ p7=add(p4,point(0,width,0))
83
+
84
+ self.draw_line(p4,p5)
85
+ self.draw_line(p5,p6)
86
+ self.draw_line(p6,p7)
87
+ self.draw_line(p7,p4)
88
+
89
+ self.draw_line(p0,p4)
90
+ self.draw_line(p1,p5)
91
+ self.draw_line(p2,p6)
92
+ self.draw_line(p3,p7)
93
+
59
94
  def __init__(self):
60
95
  self.__pointstyle = 'xo'
61
96
  self.__pointsize = 0.1
@@ -108,7 +143,7 @@ class Drawable:
108
143
 
109
144
  @polystyle.setter
110
145
  def polystyle(self,pst=False):
111
- if pst in [False, 'points','lines','both']:
146
+ if pst in ['points','lines','both']:
112
147
  self._set_polystyle(pst)
113
148
  else:
114
149
  raise ValueError('bad polystyle')
@@ -251,11 +286,17 @@ class Drawable:
251
286
  else:
252
287
  raise ValueError("bad value for polystyle: {}".format(self.polystyle))
253
288
 
254
- elif isgeomlist(x):
289
+ elif isinstance(x,Geometry):
290
+ gl = x.geom
291
+ self.draw(gl)
292
+ elif isinstance(x,list): # could be a geometry list, or a list
293
+ # that mixes yapcad.geom elements and yapcad.geometry
294
+ # Geeometry instances. If the list contains an
295
+ # inappropriate element it will be caught in the "else" case below.
255
296
  for e in x:
256
297
  self.draw(e)
257
298
  else:
258
- raise ValueError('bad argument to Drawable.draw(): '.format(x))
299
+ raise ValueError(f'bad argument to Drawable.draw(): {x}')
259
300
 
260
301
  ## cause drawing page to be rendered -- pure virtual in base class
261
302
  def display(self):
yapcad/ezdxf_drawable.py CHANGED
@@ -123,7 +123,7 @@ class ezdxfDraw(drawable.Drawable):
123
123
  'linetype': linetype})
124
124
 
125
125
  def draw_text(self,text,location,
126
- align='LEFT',
126
+ align=TextEntityAlignment.LEFT,
127
127
  attr={'style': 'LiberationMono',
128
128
  'height': .75}):
129
129
  layer=self.layer