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/poly.py CHANGED
@@ -1,4 +1,4 @@
1
- # Polyline and Polygon geometry generating classes for yapCAD
1
+ ## Derived Geometry clases for yapCAD
2
2
  from yapcad.geom import *
3
3
  from yapcad.geometry import *
4
4
 
@@ -26,229 +26,136 @@ from yapcad.geometry import *
26
26
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
27
  # SOFTWARE.
28
28
 
29
- ## These geometry-generating classes are somewhat analogous to the
30
- ## polygonal representations available by way of poly() point lists.
31
- ## There are two important diferences that might lead you to use these
32
- ## classes rather than the point-list based representations.
33
-
34
- ## First, because instances of these classes can cache
35
- ## meta-information about the geometry, it means that sampling and
36
- ## intersection operations might be more efficient for class instances
37
- ## than utilizing the functions avalable for the list-based
38
- ## representations.
39
-
40
- ## Seond, and more importantly, these geometry-generating classes,
41
- ## they can support a richer set of representations and operations.
42
- ## For example, the Polygon() class for closed figures allows you to
43
- ## specify points, lines, arcs, or circles as elements of the
44
- ## geometry, and will intelligently interpolate between these to
45
- ## generate the resulting geometry. This means you can use circles to
46
- ## represented rounded corners without having to worry about
47
- ## converting them into just the right arc, since the tangent lines
48
- ## and arc trimming are done automatically.
49
-
50
-
51
- class Polyline(IntersectGeometry):
52
- """Generalized multi-element open figure class"""
53
-
54
- def __init__(self,a=False):
55
- self._elem=[]
56
- self._length=0.0
57
- self._lengths=[]
58
- self._lines=[]
59
- self._update=True
60
- self._closed=False
61
- self._center=point(0,0,0)
62
- self._bbox=line(point(-epsilon,-epsilon),
63
- point(epsilon,epsilon))
64
- if isinstance(a,Polyline):
65
- self._elem = deepcopy(a._elem)
66
- self._updateInternals()
67
- elif isgeomlist(a):
68
- for i in a:
69
- if ispoint(i) or isarc(i) or isline(i):
70
- self._elem.append(deepcopy(i))
71
- elif ispoly(i):
72
- self._elem = self._elem + i
73
- else:
74
- raise ValueError("bad argument to Polyline constructor")
75
- elif a != False:
76
- raise ValueError("bad argument passed to Polyline constructor")
29
+ """
30
+ Derived Geometry Classes
31
+ ========================
77
32
 
78
- def __repr__(self):
79
- return 'Polyline({})'.format(vstr(self._elem))
33
+ These subclasses of Geometry compute a figure based on the contents
34
+ of their elements list, rather than simply providing a wrapper around
35
+ the contents of their elements.
80
36
 
81
- ## add another drawing element
82
- def addPoint(self,element):
83
- if ispoint(element):
84
- self._update=True # flag that we need to recalculate stuff
85
- self._elem.append(element)
86
- else:
87
- raise ValueError('attempt to add a non point to Polyline')
37
+ The Polygon class is an intepolating closed figure class that will
38
+ interpolate lines between each point or open figure element in its
39
+ element list. It will also treat circles as rounded convex conrners
40
+ of the specified radius.
88
41
 
89
- ## set a point to a specified value
90
- def setPoint(self,i,p):
91
- if not ispoint(p):
92
- raise ValueError('bad point passed to Polyline.setPoint(): '.format(p))
93
- if i < len(self._elem):
94
- self._elem[i]=point(p)
95
- elif i == len(self._elem):
96
- self._elem.append(p)
97
- else:
98
- raise ValueError('index out of range in PolylinesetPoint(): '.format(i))
99
- self._update=True
100
-
101
- ## return a copy of the elem list
102
- def getElem(self):
103
- return deepcopy(self._elem)
104
-
105
- def _updateInternals(self):
106
- if self._update:
107
- self._updateCenter()
108
- self._updateLines()
109
- self._update=False
110
-
111
-
112
- ## compute barycentric center of figure by equally weighting the
113
- ## points. If last and first points are the same, ignore the
114
- ## last point.
115
- def _updateCenter(self):
116
- l = len(self._elem)
117
- if l == 0:
118
- return
119
- elif l == 1:
120
- self._center = center(self._elem[0]) # center is sole point
121
- self._bbox = bbox(self._elem[0])
122
- else:
123
-
124
- if dist(center(self._elem[0]),
125
- center(self._elem[-1])) < epsilon:
126
- l -= 1
127
-
128
-
129
- p = center(self._elem[0])
130
- for i in range(1,l):
131
- p = add(center(self._elem[i]),p)
132
-
133
- self._center = scale3(p,1/l)
134
- self._bbox = bbox(self._elem)
135
- return
136
-
137
- ## return the center of the figure. If necessary, recompute that
138
- def getCenter(self):
139
- if self._update:
140
- self._updateInternals()
141
- return self._center
142
-
143
-
144
- def getLength(self):
145
- if self._update:
146
- self._updateInternals()
147
- return self._length
148
-
149
- ## function to take the points in the elem[] list and build a
150
- ## list of the individual lines, line lengths and total length to
151
- ## facilitate sampling
152
-
153
- def _updateLines(self):
154
- for i in range(1,len(self._elem)):
155
- p0= center(self._elem[i-1])
156
- p1= center(self._elem[i])
157
- self._lines.append(line(p0,p1))
158
- l = dist(p0,p1)
159
- self._lengths.append(l)
160
- self._length += l
161
- if len(self._elem) > 2 and dist(center(self._elem[0]),
162
- center(self._elem[-1])) < epsilon:
163
- self._closed = True
164
-
165
- def geom(self):
166
- if self._update:
167
- self._updateInternals()
168
- return deepcopy(self._lines)
169
-
170
- def sample(self,u):
171
- if self._update:
172
- self._updateInternals()
173
- dist = u * self._length;
174
- d = 0
175
- if self._closed:
176
- u = u%1.0
177
- if u < 1.0:
178
- for i in range(len(self._lines)):
179
- l=self._lengths[i]
180
- if dist <= d+l:
181
- uu = 1.0 - (d+l-dist)/l
182
- return sampleline(self._lines[i],uu)
183
- else:
184
- d+=l
185
- else:
186
- uu = (dist-self._length+self._lengths[-1])/self._lengths[-1]
187
- return sampleline(self._lines[-1],uu)
188
-
42
+ By treating circles as rounded corners, polygon instances can be
43
+ "grown" to create new figures that are a fixed distance larger, which
44
+ is very useful for offsetting drill holes from the edge of a boundary,
45
+ etc. """
189
46
 
190
- class Polygon(Polyline):
191
- """Generalized multi-element closed figure geometry generation class"""
47
+ class Polygon(Geometry):
48
+ """Multi-element closed figure derived geometry class"""
192
49
 
193
- def __init__(self,a=False):
50
+ def __init__(self,a=None):
194
51
  super().__init__()
52
+ self._setDerived(True)
53
+ self._setClosed(True)
54
+ self.__lengths=0
55
+ self.__outline=[]
56
+
195
57
  if isinstance(a,Polygon):
196
- self._elem = deepcopy(a._elem)
197
- self._updateInternals()
58
+ self._setElem(deepcopy(a.elem))
59
+ self._setUpdate(True)
198
60
  elif isgeomlist(a):
199
61
  for i in a:
200
62
  if ispoint(i) or isarc(i):
201
- self._elem.append(i)
63
+ self.elem.append(i)
202
64
  elif isline(i):
203
65
  pass
204
66
  elif ispoly(i):
205
- self._elem = self._elem + i
67
+ self._setElem(self.elem + i)
206
68
  else:
207
69
  raise ValueError("bad argument to Polygon constructor")
208
70
 
209
- elif a != False:
71
+ elif a != None:
210
72
  raise ValueError("bad argument to Polygon constructor")
211
73
 
212
- elm = self._elem
74
+ elm = self.elem
213
75
 
214
76
  l = len(elm)
215
77
  if l > 2:
216
78
  # if first and last element are identicial points or circles,
217
79
  # remove them
218
- if (ispoint(elm[0]) and ispoint(elm[-1]) and \
219
- dist(elm[0],elm[-1]) < epsilon) or \
220
- (iscircle(elm[0]) and iscircle(elm[-1]) and \
221
- abs(elm[0][1][0] - elm[-1][1][0]) < epsilon):
222
- self._elem.pop()
80
+ if ((ispoint(elm[0]) and ispoint(elm[-1]) and
81
+ dist(elm[0],elm[-1]) < epsilon) or
82
+ (iscircle(elm[0]) and iscircle(elm[-1]) and
83
+ abs(elm[0][1][0] - elm[-1][1][0]) < epsilon)):
84
+ self.elem.pop()
223
85
 
224
86
  def __repr__(self):
225
- return 'Polygon({})'.format(vstr(self._elem))
87
+ return 'Polygon({})'.format(vstr(self.elem))
226
88
 
89
+ ## compute barycentric center of figure by equally weighting the
90
+ ## points. If last and first points are the same, ignore the
91
+ ## last point.
92
+ def _calcCenter(self):
93
+ l = len(self.elem)
94
+ if l == 0:
95
+ return None
96
+ elif l == 1:
97
+ return center(self.elem[0]) # center is sole point
98
+ else:
99
+
100
+ if dist(center(self.elem[0]),
101
+ center(self.elem[-1])) < epsilon:
102
+ l -= 1
103
+
104
+
105
+ p = center(self.elem[0])
106
+ for i in range(1,l):
107
+ p = add(center(self.elem[i]),p)
108
+
109
+ return scale3(p,1/l)
110
+
111
+
227
112
  def _updateInternals(self):
228
- if self._update:
229
- self._updateCenter()
113
+ if self.update:
114
+ self._setUpdate(False)
230
115
  self._makeoutline()
231
- self._update=False
116
+ self._setCenter(self._calcCenter())
117
+ self._setBbox(bbox(self.__outline))
118
+
119
+ def addPoint(self,element):
120
+ if ispoint(element):
121
+ self._setUpdate(True) # flag that we need to recalculate stuff
122
+ self.elem.append(deepcopy(element))
123
+ else:
124
+ raise ValueError('attempt to add a non point to Polyline')
125
+
126
+ ## set a point to a specified value
127
+ def setPoint(self,i,p):
128
+ if not ispoint(p):
129
+ raise ValueError('bad point passed to Polyline.setPoint(): '.format(p))
130
+ elem = self.elem
131
+ if i < len(elem):
132
+ elem[i]=point(p)
133
+ elif i == len(elem):
134
+ elem.append(p)
135
+ else:
136
+ raise ValueError('index out of range in PolylinesetPoint(): '.format(i))
137
+ self._setElem(elem)
138
+ self._setUpdate(True)
232
139
 
233
140
  ## add another drawing element
234
141
  def addLine(self,element):
235
142
  if isline(element):
236
- self._update=True # flag that we need to recalculate stuff
237
- self._elem.append(deepcopy(element))
143
+ self._setUpdate(True) # flag that we need to recalculate stuff
144
+ self.elem.append(deepcopy(element))
238
145
  else:
239
146
  raise ValueError('attempt to add a non point, line or arc to poly')
240
147
 
241
148
  ## add another drawing element
242
149
  def addArc(self,element):
243
150
  if isarc(element):
244
- self._update=True # flag that we need to recalculate stuff
245
- self._elem.append(deepcopy(element))
151
+ self._setUpdate(True) # flag that we need to recalculate stuff
152
+ self.elem.append(deepcopy(element))
246
153
  else:
247
154
  raise ValueError('attempt to add a non point, line or arc to poly')
248
155
 
249
156
  def remove(self,element):
250
- self._update=True # flag that we need to recalculate stuff
251
- self._elem.remove(element)
157
+ self._setUpdate(True)
158
+ self.elem.remove(element)
252
159
 
253
160
  ## function to take the elements in the elem[] list and
254
161
  ## construct the full outline. For example, consider a list of
@@ -260,23 +167,24 @@ class Polygon(Polyline):
260
167
  ## non-circular arcs are joined to adjacent elements by lines
261
168
 
262
169
  def _makeoutline(self):
170
+ # import pdb; pdb.set_trace()
263
171
  def _calclength():
264
172
  l = 0
265
173
  ll = []
266
174
  length = 0
267
- for o in self._outline:
175
+ for o in self.__outline:
268
176
  if isarc(o):
269
177
  length=arclength(o)
270
178
  elif isline(o): # line
271
179
  length=linelength(o)
272
180
  else:
273
- #print("self._outline: ",vstr(self._outline))
181
+ #print("self.__outline: ",vstr(self.__outline))
274
182
  #print("o: ",vstr(o))
275
183
  raise ValueError("bad element in outline list for _calclength()")
276
184
  l += length
277
185
  ll.append(length)
278
- self._length = l
279
- self._lengths=ll
186
+ self._setLength(l)
187
+ self.__lengths=ll
280
188
 
281
189
  def _handleCircle(e0,e1,e2):
282
190
  ## get the two tangent lines from circle e1 to the circle
@@ -315,33 +223,33 @@ class Polygon(Polyline):
315
223
  else:
316
224
  l = ll[1]
317
225
 
318
- self._outline.append(line(l))
226
+ self.__outline.append(line(l))
319
227
 
320
228
  def _fromPointAdd(e0,p1,e2):
321
229
  if ispoint(e2): #r1 is a point -- simplest case
322
230
  #ll = dist(p1,e2)
323
- self._outline.append(line(p1,e2))
231
+ self.__outline.append(line(p1,e2))
324
232
  elif isline(e2):
325
- self._outline.append(line(p1,e2[0]))
326
- self._outline.append(line(e2))
233
+ self.__outline.append(line(p1,e2[0]))
234
+ self.__outline.append(line(e2))
327
235
  elif isarc(e2) and not iscircle(e2):
328
236
  p = samplearc(e2,1*epsilon)
329
- self._outline.append(line(p1,p))
330
- self._outline.append(arc(e2))
237
+ self.__outline.append(line(p1,p))
238
+ self.__outline.append(arc(e2))
331
239
  elif iscircle(e2):
332
240
  _handleCircle(e0,arc(p1,1*epsilon),e2)
333
- self._outline.append(arc(e2))
241
+ self.__outline.append(arc(e2))
334
242
  else:
335
243
  raise ValueError('bad object in element list')
336
244
 
337
- self._outline=[]
338
- self._length=0
339
- self._lengths=[]
245
+ self.__outline=[]
246
+ self._setLength(0.0)
247
+ self.__lengths=[]
340
248
 
341
- if len(self._elem) < 2:
249
+ if len(self.elem) < 2:
342
250
  ## if one element, the outline is the element
343
- if len(self._elem) == 1:
344
- self._outline=deepcopy(self._elem)
251
+ if len(self.elem) == 1:
252
+ self.__outline=deepcopy(self.elem)
345
253
  _calclength()
346
254
  ## if there are fewer than one elem, there is nothing to do
347
255
  return
@@ -351,16 +259,16 @@ class Polygon(Polyline):
351
259
  ## total length and length list for use in sample() function.
352
260
 
353
261
 
354
- for i in range(len(self._elem)):
262
+ for i in range(len(self.elem)):
355
263
  if i == 0:
356
- e0 = self._elem[-1]
264
+ e0 = self.elem[-1]
357
265
  else:
358
- e0 = self._elem[i-1]
359
- e1 = self._elem[i]
360
- if i == len(self._elem)-1: #last item
361
- e2 = self._elem[0]
266
+ e0 = self.elem[i-1]
267
+ e1 = self.elem[i]
268
+ if i == len(self.elem)-1: #last item
269
+ e2 = self.elem[0]
362
270
  else:
363
- e2 = self._elem[i+1]
271
+ e2 = self.elem[i+1]
364
272
  ## work through element types
365
273
  if ispoint(e1): #e1 is a point
366
274
  _fromPointAdd(e0,e1,e2)
@@ -381,18 +289,18 @@ class Polygon(Polyline):
381
289
  _handleCircle(e0,e1,c2)
382
290
 
383
291
  if not ispoint(e2):
384
- self._outline.append(deepcopy(e2))
292
+ self.__outline.append(deepcopy(e2))
385
293
 
386
294
  ## OK, now go back through and replace full circles with arcs
387
295
  ## and catch any intersecting lines due to non-convex
388
296
  ## curvature
389
- for i in range(1,len(self._outline)):
390
- e0 = self._outline[i-1]
391
- e1 = self._outline[i]
392
- if i == len(self._outline)-1: #last item
393
- e2 = self._outline[0]
297
+ for i in range(1,len(self.__outline)):
298
+ e0 = self.__outline[i-1]
299
+ e1 = self.__outline[i]
300
+ if i == len(self.__outline)-1: #last item
301
+ e2 = self.__outline[0]
394
302
  else:
395
- e2 = self._outline[i+1]
303
+ e2 = self.__outline[i+1]
396
304
  if isline(e1) and isline(e2):
397
305
  pi = lineLineIntersectXY(e1,e2,inside=True)
398
306
  if pi == False:
@@ -403,8 +311,8 @@ class Polygon(Polyline):
403
311
  print("odd parallel? adjacent line condition")
404
312
  continue
405
313
  else:
406
- self._outline[i] = line(e1[0],pi)
407
- self._outline[(i+1)%len(self._outline)] = line(pi,e2[1])
314
+ self.__outline[i] = line(e1[0],pi)
315
+ self.__outline[(i+1)%len(self.__outline)] = line(pi,e2[1])
408
316
  if iscircle(e1):
409
317
  if not isline(e0) or not isline(e2):
410
318
  raise ValueError('circle not bracketed by lines')
@@ -422,95 +330,36 @@ class Polygon(Polyline):
422
330
  start = (atan2(p0[1],p0[0]) % pi2) * 360.0/pi2
423
331
  end = (atan2(p1[1],p1[0]) % pi2) * 360.0/pi2
424
332
  newarc = arc(e1[0],e1[1][0],start,end)
425
- self._outline[i]=newarc
333
+ self.__outline[i]=newarc
426
334
 
427
- self._outline = cullZeroLength(self._outline)
335
+ self.__outline = cullZeroLength(self.__outline)
428
336
 
429
337
  _calclength()
430
- self._bbox = geomlistbbox(self._outline)
431
-
338
+
339
+ # sample function uses cached length and lengths[], otherwise
340
+ # behavior is identical to superclass
432
341
  def sample(self,u):
433
- if self._update:
342
+ if self.update:
434
343
  self._updateInternals()
435
- if len(self._outline) == 0:
344
+ if len(self.__outline) == 0:
436
345
  raise ValueError('no geometry to sample, empty poly')
437
- dist = (u % 1.0) * self._length
346
+ dist = (u % 1.0) * self.length
438
347
  d = 0
439
- for i in range(len(self._outline)):
440
- l=self._lengths[i]
348
+ for i in range(len(self.__outline)):
349
+ l=self.__lengths[i]
441
350
  if dist <= d+l:
442
351
  u = 1.0 - (d+l-dist)/l
443
- e = self._outline[i]
352
+ e = self.__outline[i]
444
353
  return sample(e,u)
445
354
  else:
446
355
  d=d+l
447
-
448
- def segment(self,u1,u2,reverse=False):
449
- return segmentgeomlist(self.geom(),u1,u2,closed=True,reverse=reverse)
450
-
451
- def mirror(self,plane,poly=False):
452
- if poly:
453
- p = Polygon()
454
- p._elem = deepcopy(self._elem)
455
- p._elem.reverse()
456
- p._elem = mirror(p._elem,plane)
457
- p._update=True
458
- return p
459
-
460
- return mirror(self.geom(),plane)
461
-
462
- def rotate(self,angle,cent=point(0,0,0),axis=point(0,0,1),poly=False):
463
- if poly:
464
- p = Polygon(self)
465
- p._elem = rotate(self._elem,angle,cent,axis)
466
- p._update = True
467
- return p
468
-
469
- return rotate(self.geom(),angle,cent,axis)
470
-
471
- def scale(self,sx,sy=False,sz=False,cent=point(0,0),poly=False):
472
- if poly:
473
- p = Polygon(self)
474
- p._elem = scale(self._elem,sx,sy,sz,cent)
475
- p._update = True
476
- return p
477
-
478
- return scale(self.geom(),sx,sy,sz,cent)
479
-
480
- def translate(self,delta,poly=False):
481
- if poly:
482
- p = Polygon(self)
483
- p._elem = translate(self._elem,delta)
484
- p._update = True
485
- return p
486
-
487
- return translate(self.geom(),delta)
488
-
356
+
357
+ @property
489
358
  def geom(self):
490
- if self._update:
359
+ if self.update:
491
360
  self._updateInternals()
492
- return deepcopy(self._outline)
361
+ return deepcopy(self.__outline)
493
362
 
494
- def bbox(self):
495
- if self._update:
496
- self._updateInternals()
497
- return line(self._bbox)
498
-
499
- def isinside(self,p):
500
- if self._update:
501
- self._updateInternals()
502
- bb = self._bbox
503
- if not isinsidebbox(bb,p):
504
- return False
505
- p2 = add([1,1,0,1],bb[1])
506
- if vclose(p2,p): ## did we randomly pick an outside point near the
507
- ## test point?
508
- p2 = sub(bb[0],[1,1,0,1])
509
- l = line(p,p2)
510
- pp = intersectGeomListXY(l,self.geom())
511
- if pp == False:
512
- return False
513
- return len(pp) % 2 == 1
514
363
 
515
364
  def grow(self,r):
516
365
  if close(r,0.0):
@@ -535,10 +384,12 @@ class Polygon(Polyline):
535
384
  elif isgeomlist(e):
536
385
  ne += _growgeom(e)
537
386
  return ne
538
- self._elem = _growgeom(self._elem)
539
- self._update=True
387
+ self._setElem(_growgeom(self.elem))
388
+ self._setUpdate(True)
540
389
 
390
+
541
391
  def shrink(self,r):
392
+ raise NotImplementedError('shrink operation not yet implemented')
542
393
  return False
543
394
 
544
395
 
@@ -565,17 +416,20 @@ def cullZeroLength(geom):
565
416
 
566
417
 
567
418
 
568
- ## make a poly containing a single circle
419
+ ## make a Polygon instance containing a circle as two half-arcs
569
420
 
570
- def makeCircle(center=point(0,0,0),radius=1.0):
571
- poly = Polygon()
572
- poly.addArc(arc(center,radius,0.0,180.0))
573
- poly.addArc(arc(center,radius,180.0,359.99))
574
- return poly
421
+ # def Circle(center=point(0,0,0),radius=1.0):
422
+ # poly = Polygon()
423
+ # poly.addArc(arc(center,radius,0.0,180.0))
424
+ # poly.addArc(arc(center,radius,180.0,360.0))
425
+ # return poly
426
+
427
+ def Circle(center=point(0,0,0),radius=1.0):
428
+ return Geometry(arc(center,radius,0,360))
575
429
 
576
430
  ## make a rectangle with the specified width and height
577
431
 
578
- def makeRect(width,height,center=point(0,0,0)):
432
+ def Rect(width,height,center=point(0,0,0)):
579
433
  w=width/2.0
580
434
  h=height/2.0
581
435
  p0=add(point(-w,h),center)
@@ -592,7 +446,7 @@ def makeRect(width,height,center=point(0,0,0)):
592
446
  ## make rounded rectangle with specified width, height, and chamfer,
593
447
  ## centered at the origin by default
594
448
 
595
- def makeRoundRect(width,height,chamf,center=point(0,0,0)):
449
+ def RoundRect(width,height,chamf,center=point(0,0,0)):
596
450
  cr=chamf/2.0
597
451
  wid=width-chamf
598
452
  hei=height-chamf