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