/[escript]/trunk/pycad/py_src/primitives.py
ViewVC logotype

Contents of /trunk/pycad/py_src/primitives.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 929 - (show annotations)
Wed Jan 17 07:41:13 2007 UTC (13 years, 8 months ago) by gross
File MIME type: text/x-python
File size: 40874 byte(s)
reverse orientation added but does not work for 2D yet.
1 # $Id:$
2
3 """
4 Geometrical Primitives
5
6 the concept is inspired by gmsh and very much focused on the fact that
7 the classes are used to wrk with gmsh.
8
9 @var __author__: name of author
10 @var __copyright__: copyrights
11 @var __license__: licence agreement
12 @var __url__: url entry point on documentation
13 @var __version__: version
14 @var __date__: date of the version
15 """
16
17
18 __author__="Lutz Gross, l.gross@uq.edu.au"
19 __copyright__=""" Copyright (c) 2006 by ACcESS MNRF
20 http://www.access.edu.au
21 Primary Business: Queensland, Australia"""
22 __license__="""Licensed under the Open Software License version 3.0
23 http://www.opensource.org/licenses/osl-3.0.php"""
24 __url__="http://www.iservo.edu.au/esys/escript"
25 __version__="$Revision:$"
26 __date__="$Date:$"
27
28 import numarray
29 from transformations import _TYPE, Translation, Dilation, Transformation
30
31
32 def resetGlobalPrimitiveIdCounter():
33 """
34 initializes the global primitive ID counter
35 """
36 global global_primitive_id_counter
37 global_primitive_id_counter=1
38
39 def setToleranceForColocation(tol=1.e-11):
40 """
41 set the global tolerance for colocation checks to tol
42 """
43 global global_tolerance_for_colocation
44 global_tolerance_for_colocation=tol
45
46 def getToleranceForColocation():
47 """
48 returns the global tolerance for colocation checks
49 """
50 return global_tolerance_for_colocation
51
52 resetGlobalPrimitiveIdCounter()
53 setToleranceForColocation()
54
55
56 class PrimitiveBase(object):
57 """
58 template for a set of primitives
59 """
60 def __init__(self):
61 """
62 initializes PrimitiveBase instance object with id
63 """
64 pass
65
66 def __cmp__(self,other):
67 """
68 compares object with other by comparing the absolute value of the ID
69 """
70 if isinstance(other, PrimitiveBase):
71 return cmp(self.getID(),other.getID())
72 else:
73 return False
74 def getConstructionPoints(self):
75 """
76 returns the points used to construct the primitive
77 """
78 out=set()
79 for i in self.getPrimitives():
80 if isinstance(i,Point): out.add(i)
81 return list(out)
82
83 def getPrimitives(self):
84 """
85 returns a list of primitives used to construct the primitive with no double entries
86 """
87 out=set()
88 return list(set([p for p in self.collectPrimitiveBases()]))
89
90 def copy(self):
91 """
92 returns a deep copy of the object
93 """
94 return self.substitute({})
95
96 def modifyBy(self,transformation):
97 """
98 modifies the coordinates by applying a transformation
99 """
100 for p in self.getConstructionPoints(): p.modifyBy(transformation)
101
102 def __add__(self,other):
103 """
104 returns a new object shifted by other
105 """
106 return self.apply(Translation(numarray.array(other,_TYPE)))
107
108 def __sub__(self,other):
109 """
110 returns a new object shifted by other
111 """
112 return self.apply(Translation(-numarray.array(other,_TYPE)))
113
114 def __iadd__(self,other):
115 """
116 shifts the point by other
117 """
118 self.modifyBy(Translation(numarray.array(other,_TYPE)))
119 return self
120
121 def __isub__(self,other):
122 """
123 shifts the point by -other
124 """
125 self.modifyBy(Translation(-numarray.array(other,_TYPE)))
126 return self
127
128 def __imul__(self,other):
129 """
130 modifies object by applying L{Transformation} other. If other is not a L{Transformation} it will try convert it.
131 """
132 if isinstance(other,int) or isinstance(other,float):
133 trafo=Dilation(other)
134 elif isinstance(other,numarray.NumArray):
135 trafo=Translation(other)
136 elif isinstance(other,Transformation):
137 trafo=other
138 else:
139 raise TypeError, "cannot convert argument to Trnsformation class object."
140 self.modifyBy(trafo)
141 return self
142
143 def __rmul__(self,other):
144 """
145 applies L{Transformation} other to object. If other is not a L{Transformation} it will try convert it.
146 """
147 if isinstance(other,int) or isinstance(other,float):
148 trafo=Dilation(other)
149 elif isinstance(other,numarray.NumArray):
150 trafo=Translation(other)
151 elif isinstance(other,Transformation):
152 trafo=other
153 else:
154 raise TypeError, "cannot convert argument to Transformation class object."
155 return self.apply(trafo)
156
157
158 def setLocalScale(self,factor=1.):
159 """
160 sets the local refinement factor
161 """
162 for p in self.getConstructionPoints(): p.setLocalScale(factor)
163
164 def apply(self,transformation):
165 """
166 returns a new object by applying the transformation
167 """
168 out=self.copy()
169 out.modifyBy(transformation)
170 return out
171
172
173
174 class Primitive(object):
175 """
176 A general primitive
177 """
178 def __init__(self):
179 """
180 initializes PrimitiveBase instance object with id
181 """
182 global global_primitive_id_counter
183 self.__ID=global_primitive_id_counter
184 global_primitive_id_counter+=1
185
186 def getID(self):
187 """
188 returns the primitive ID
189 """
190 return self.__ID
191
192 def getDirectedID(self):
193 """
194 returns the primitive ID where a negative signs means that the reversed ordring is used.
195 """
196 return self.getID()
197
198 def __repr__(self):
199 return "%s(%s)"%(self.__class__.__name__,self.getID())
200
201 def getUnderlyingPrimitive(self):
202 """
203 returns the underlying primitive
204 """
205 return self
206
207 def __neg__(self):
208 """
209 returns a view onto the curve with reversed ordering
210
211 @note: this class is overwritten by subclass
212 """
213 raise NotImplementedError("__neg__ is not implemented.")
214
215 def getGmshCommand(self, local_scaling_factor=1.):
216 """
217 returns the Gmsh command(s) to create the primitive
218
219 @note: this class is overwritten by subclass
220 """
221 raise NotImplementedError("getGmshCommand is not implemented.")
222
223 def substitute(self,sub_dict):
224 """
225 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
226 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
227 with substituted arguments is returned.
228
229 @note: this class is overwritten by subclass
230 """
231 raise NotImplementedError("substitute is not implemented.")
232
233 def collectPrimitiveBases(self):
234 """
235 returns a list of primitives used to construct the primitive. It may contain primitives twice
236
237 @note: this class is overwritten by subclass
238 """
239 raise NotImplementedError("collectPrimitiveBases is not implemented.")
240
241 def isColocated(self,primitive):
242 """
243 returns True is the two primitives are located at the smae position
244
245 @note: this class is overwritten by subclass
246 """
247 raise NotImplementedError("isColocated is not implemented.")
248
249
250 class ReversePrimitive(object):
251 """
252 A view onto a primitive creating an reverse orientation
253 """
254 def __init__(self,primitive):
255 """
256 instantiate a view onto primitve
257 """
258 if not isinstance(primitive, Primitive):
259 raise ValueError("argument needs to be a Primitive class object.")
260 self.__primitive=primitive
261
262 def getID(self):
263 """
264 returns the primitive ID
265 """
266 return self.__primitive.getID()
267
268 def getUnderlyingPrimitive(self):
269 """
270 returns the underlying primitive
271 """
272 return self.__primitive
273
274 def __repr__(self):
275 return "-%s(%s)"%(self.__primitive.__class__.__name__,self.getID())
276
277 def getDirectedID(self):
278 """
279 returns the primitive ID where a negative signs means that the reversed ordring is used.
280 """
281 return -self.__primitive.getID()
282
283 def substitute(self,sub_dict):
284 """
285 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
286 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
287 with substituted arguments is returned.
288 """
289 if not sub_dict.has_key(self):
290 sub_dict[self]=-self.getUnderlyingPrimitive().substitute(sub_dict)
291 return sub_dict[self]
292
293 def __neg__(self):
294 """
295 returns a view onto the curve with reversed ordering
296 """
297 return self.__primitive
298
299 def getGmshCommand(self, local_scaling_factor=1.):
300 """
301 returns the Gmsh command(s) to create the primitive
302 """
303 return self.__primitive.getGmshCommand(local_scaling_factor)
304
305 def collectPrimitiveBases(self):
306 """
307 returns a list of primitives used to construct the primitive. It may contain primitives twice
308 """
309 return self.__primitive.collectPrimitiveBases()
310
311 def isColocated(self,primitive):
312 """
313 returns True is the two primitives are located at the smae position
314
315 @note: this class is overwritten by subclass
316 """
317 return self.__primitive.isColocated(primitive)
318
319 class Point(Primitive, PrimitiveBase):
320 """
321 a three dimensional point
322 """
323 def __init__(self,x=0.,y=0.,z=0.,local_scale=1.):
324 """
325 creates a point with coorinates x,y,z with the local refinement factor local_scale
326 """
327 PrimitiveBase.__init__(self)
328 Primitive.__init__(self)
329 self.setCoordinates(numarray.array([x,y,z],_TYPE))
330 self.setLocalScale(local_scale)
331
332 def setLocalScale(self,factor=1.):
333 """
334 sets the local refinement factor
335 """
336 if factor<=0.:
337 raise ValueError("scaling factor must be positive.")
338 self.__local_scale=factor
339
340 def getLocalScale(self):
341 """
342 returns the local refinement factor
343 """
344 return self.__local_scale
345 def getCoordinates(self):
346 """
347 returns the coodinates of the point as L{numarray.NumArray} object
348 """
349 return self._x
350 def setCoordinates(self,x):
351 """
352 returns the coodinates of the point as L{numarray.NumArray} object
353 """
354 if not isinstance(x, numarray.NumArray):
355 self._x=numarray.array(x,_TYPE)
356 else:
357 self._x=x
358
359 def collectPrimitiveBases(self):
360 """
361 returns primitives used to construct the primitive
362 """
363 return [self]
364
365 def isColocated(self,primitive):
366 """
367 returns True if L{Point} primitive is colocation (same coordinates)
368 that means if |self-primitive| <= tol * max(|self|,|primitive|)
369 """
370 if isinstance(primitive,Point):
371 primitive=primitive.getCoordinates()
372 c=self.getCoordinates()
373 d=c-primitive
374 return numarray.dot(d,d)<=getToleranceForColocation()**2*max(numarray.dot(c,c),numarray.dot(primitive,primitive))
375 else:
376 return False
377
378 def substitute(self,sub_dict):
379 """
380 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
381 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
382 with substituted arguments is returned.
383 """
384 if not sub_dict.has_key(self):
385 c=self.getCoordinates()
386 sub_dict[self]=Point(c[0],c[1],c[2],local_scale=self.getLocalScale())
387 return sub_dict[self]
388
389 def modifyBy(self,transformation):
390 """
391 modifies the coordinates by applying a transformation
392 """
393 self.setCoordinates(transformation(self.getCoordinates()))
394
395
396 def getGmshCommand(self, local_scaling_factor=1.):
397 """
398 returns the Gmsh command(s) to create the primitive
399 """
400 c=self.getCoordinates()
401 return "Point(%s) = {%s , %s, %s , %s };"%(self.getID(),c[0],c[1],c[2], self.getLocalScale()*local_scaling_factor)
402
403 def __neg__(self):
404 """
405 returns a view of the object with reverse orientiention. As a point has no direction the object itself is returned.
406 """
407 return self
408
409 class Manifold1D(PrimitiveBase):
410 """
411 general one-dimensional minifold in 3D defined by a start and end point.
412 """
413 def __init__(self):
414 """
415 create a one-dimensional manifold
416 """
417 PrimitiveBase.__init__(self)
418
419 def getStartPoint(self):
420 """
421 returns start point
422 """
423 raise NotImplementedError()
424
425 def getEndPoint(self):
426 """
427 returns end point
428 """
429 raise NotImplementedError()
430
431 class CurveBase(Manifold1D):
432 """
433 A Curve is defined by a set of control points
434 """
435 def __init__(self):
436 """
437 create curve
438 """
439 Manifold1D.__init__(self)
440
441 def __len__(self):
442 """
443 returns the number of control points
444 """
445 return len(self.getControlPoints())
446
447 def getStartPoint(self):
448 """
449 returns start point
450 """
451 return self.getControlPoints()[0]
452
453 def getEndPoint(self):
454 """
455 returns end point
456 """
457 return self.getControlPoints()[-1]
458
459 def getControlPoints(self):
460 """
461 returns a list of the points
462 """
463 raise NotImplementedError()
464
465 class Curve(CurveBase, Primitive):
466 """
467 a curve defined through a list of control points.
468 """
469 def __init__(self,*points):
470 """
471 defines a curve form control points
472 """
473 if len(points)<2:
474 raise TypeError("Curve needs at least two points")
475 i=0
476 for p in points:
477 i+=1
478 if not isinstance(p,Point): raise TypeError("%s-th argument is not a Point object."%i)
479 self.__points=points
480 CurveBase.__init__(self)
481 Primitive.__init__(self)
482
483 def getControlPoints(self):
484 """
485 returns a list of the points
486 """
487 return self.__points
488
489 def __neg__(self):
490 """
491 returns a view onto the curve with reversed ordering
492 """
493 return ReverseCurve(self)
494
495 def substitute(self,sub_dict):
496 """
497 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
498 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
499 with substituted arguments is returned.
500 """
501 if not sub_dict.has_key(self):
502 new_p=[]
503 for p in self.getControlPoints(): new_p.append(p.substitute(sub_dict))
504 sub_dict[self]=self.__class__(*tuple(new_p))
505 return sub_dict[self]
506
507 def collectPrimitiveBases(self):
508 """
509 returns primitives used to construct the Curve
510 """
511 out=[self]
512 for p in self.getControlPoints(): out+=p.collectPrimitiveBases()
513 return out
514
515 def isColocated(self,primitive):
516 """
517 returns True curves are on the same position
518 """
519 if isinstance(primitive.getUnderlyingPrimitive(),self.__class__):
520 if len(primitive) == len(self):
521 cp0=self.getControlPoints()
522 cp1=primitive.getControlPoints()
523 match=True
524 for i in range(len(cp0)):
525 if not cp0[i].isColocated(cp1[i]):
526 match=False
527 break
528 if not match:
529 for i in range(len(cp0)):
530 if not cp0[i].isColocated(cp1[len(cp0)-1-i]):
531 return False
532 return True
533 else:
534 return False
535 else:
536 return False
537
538 class ReverseCurve(CurveBase, ReversePrimitive):
539 """
540 a curve defined through a list of control points.
541 """
542 def __init__(self,curve):
543 """
544 defines a curve form control points
545 """
546 if not isinstance(curve, Curve):
547 raise TypeError("ReverseCurve needs to be an instance of Curve")
548 CurveBase.__init__(self)
549 ReversePrimitive.__init__(self,curve)
550
551 def getControlPoints(self):
552 """
553 returns a list of the points
554 """
555 out=[p for p in self.getUnderlyingPrimitive().getControlPoints()]
556 out.reverse()
557 return out
558
559 class Spline(Curve):
560 """
561 a spline curve defined through a list of control points.
562 """
563 def getGmshCommand(self,scaling_factor=1.):
564 """
565 returns the Gmsh command(s) to create the Curve
566 """
567 out=""
568 for i in self.getControlPoints():
569 if len(out)>0:
570 out+=", %s"%i.getDirectedID()
571 else:
572 out="%s"%i.getDirectedID()
573 return "Spline(%s) = {%s};"%(self.getID(),out)
574
575
576 class BezierCurve(Curve):
577 """
578 a Bezier curve
579 """
580 def getGmshCommand(self,scaling_factor=1.):
581 """
582 returns the Gmsh command(s) to create the Curve
583 """
584 out=""
585 for i in self.getControlPoints():
586 if len(out)>0:
587 out+=", %s"%i.getDirectedID()
588 else:
589 out="%s"%i.getDirectedID()
590 return "Bezier(%s) = {%s};"%(self.getID(),out)
591
592 class BSpline(Curve):
593 """
594 a BSpline curve. Control points may be repeated.
595 """
596 def getGmshCommand(self,scaling_factor=1.):
597 """
598 returns the Gmsh command(s) to create the Curve
599 """
600 out=""
601 for i in self.getControlPoints():
602 if len(out)>0:
603 out+=", %s"%i.getDirectedID()
604 else:
605 out="%s"%i.getDirectedID()
606 return "BSpline(%s) = {%s};"%(self.getID(),out)
607
608 class Line(Curve):
609 """
610 a line is defined by two pointDirecteds
611 """
612 def __init__(self,*points):
613 """
614 defines a line with start and end point
615 """
616 if len(points)!=2:
617 raise TypeError("Line needs two points")
618 Curve.__init__(self,*points)
619 def getGmshCommand(self,scaling_factor=1.):
620 """
621 returns the Gmsh command(s) to create the Curve
622 """
623 return "Line(%s) = {%s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getEndPoint().getDirectedID())
624
625
626 class ArcBase(Manifold1D):
627 def __init__(self):
628 """
629 create curve
630 """
631 Manifold1D.__init__(self)
632 def collectPrimitiveBases(self):
633 """
634 returns the primitives used to construct the Curve
635 """
636 out=[self]
637 out+=self.getStartPoint().collectPrimitiveBases()
638 out+=self.getEndPoint().collectPrimitiveBases()
639 out+=self.getCenterPoint().collectPrimitiveBases()
640 return out
641
642
643 def getCenterPoint(self):
644 """
645 returns center
646 """
647 raise NotImplementedError()
648
649 class Arc(ArcBase, Primitive):
650 """
651 defines an arc which is strictly, smaller than Pi
652 """
653 def __init__(self,center,start,end):
654 """
655 creates an arc by the start point, end point and center
656 """
657 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
658 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
659 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
660 # TODO: check length of circle.
661 ArcBase.__init__(self)
662 Primitive.__init__(self)
663 self.__center=center
664 self.__start=start
665 self.__end=end
666 def __neg__(self):
667 """
668 returns a view onto the curve with reversed ordering
669 """
670 return ReverseArc(self)
671
672 def getStartPoint(self):
673 """
674 returns start point
675 """
676 return self.__start
677
678 def getEndPoint(self):
679 """
680 returns end point
681 """
682 return self.__end
683
684 def getCenterPoint(self):
685 """
686 returns center
687 """
688 return self.__center
689
690 def substitute(self,sub_dict):
691 """
692 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
693 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
694 with substituted arguments is returned.
695 """
696 if not sub_dict.has_key(self):
697 sub_dict[self]=Arc(self.getCenterPoint().substitute(sub_dict),self.getStartPoint().substitute(sub_dict),self.getEndPoint().substitute(sub_dict))
698 return sub_dict[self]
699
700 def getGmshCommand(self,scaling_factor=1.):
701 """
702 returns the Gmsh command(s) to create the primitive
703 """
704 return "Circle(%s) = {%s, %s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getCenterPoint().getDirectedID(),self.getEndPoint().getDirectedID())
705
706 def isColocated(self,primitive):
707 """
708 returns True curves are on the same position
709 """
710 if isinstance(primitive.getUnderlyingPrimitive(),Arc):
711 return (self.getCenterPoint().isColocated(primitive.getCenterPoint())) and ( \
712 (self.getEndPoint().isColocated(primitive.getEndPoint()) and self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
713 or (self.getEndPoint().isColocated(primitive.getStartPoint()) and self.getStartPoint().isColocated(primitive.getEndPoint()) ) )
714 else:
715 return False
716
717 class ReverseArc(ArcBase, ReversePrimitive):
718 """
719 defines an arc which is strictly, smaller than Pi
720 """
721 def __init__(self,arc):
722 """
723 creates an arc by the start point, end point and center
724 """
725 if not isinstance(arc, Arc):
726 raise TypeError("ReverseCurve needs to be an instance of Arc")
727 ArcBase.__init__(self)
728 ReversePrimitive.__init__(self,arc)
729
730 def getStartPoint(self):
731 """
732 returns start point
733 """
734 return self.getUnderlyingPrimitive().getEndPoint()
735
736 def getEndPoint(self):
737 """
738 returns end point
739 """
740 return self.getUnderlyingPrimitive().getStartPoint()
741
742 def getCenterPoint(self):
743 """
744 returns center
745 """
746 return self.getUnderlyingPrimitive().getCenterPoint()
747
748 class CurveLoop(Primitive, PrimitiveBase):
749 """
750 An oriented loop of 1D primitives (= curves and arcs)
751
752 The loop must be closed and the L{Manifold1D}s should be oriented consistently.
753 """
754 def __init__(self,*curves):
755 """
756 creates a polygon from a list of line curves. The curves must form a closed loop.
757 """
758 Primitive.__init__(self)
759 PrimitiveBase.__init__(self)
760 if len(curves)<2:
761 raise TypeError("at least two curves have to be given.")
762 for i in range(len(curves)):
763 if not isinstance(curves[i],Manifold1D):
764 raise TypeError("%s-th argument is not a Manifold1D object."%i)
765 # for the curves a loop:
766 used=[ False for i in curves]
767 self.__curves=[curves[0]]
768 used[0]=True
769 while not min(used):
770 found=False
771 for i in xrange(len(curves)):
772 if not used[i]:
773 if self.__curves[-1].getEndPoint() == curves[i].getStartPoint():
774 self.__curves.append(curves[i])
775 used[i]=True
776 found=True
777 break
778 if not found:
779 raise ValueError("loop is not closed.")
780 if not self.__curves[0].getStartPoint() == self.__curves[-1].getEndPoint():
781 raise ValueError("loop is not closed.")
782
783 def getCurves(self):
784 """
785 returns the curves defining the CurveLoop
786 """
787 return self.__curves
788
789 def __neg__(self):
790 """
791 returns a view onto the curve with reversed ordering
792 """
793 return ReverseCurveLoop(self)
794
795 def __len__(self):
796 """
797 return the number of curves in the CurveLoop
798 """
799 return len(self.getCurves())
800
801
802 def collectPrimitiveBases(self):
803 """
804 returns primitives used to construct the CurveLoop
805 """
806 out=[self]
807 for c in self.getCurves(): out+=c.collectPrimitiveBases()
808 return out
809
810 def substitute(self,sub_dict):
811 """
812 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
813 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
814 with substituted arguments is returned.
815 """
816 if not sub_dict.has_key(self):
817 new_c=[]
818 for c in self.getCurves(): new_c.append(c.substitute(sub_dict))
819 sub_dict[self]=CurveLoop(*tuple(new_c))
820 return sub_dict[self]
821
822 def isColocated(self,primitive):
823 """
824 returns True if each curve is collocted with a curve in primitive
825 """
826 if isinstance(primitive,CurveLoop):
827 if len(primitive) == len(self):
828 cp0=self.getCurves()
829 cp1=primitive.getCurves()
830 for c0 in cp0:
831 collocated = False
832 for c1 in cp1:
833 collocated = collocated or c0.isColocated(c1)
834 if not collocated: return False
835 return True
836 else:
837 return False
838 else:
839 return False
840
841 def getGmshCommand(self,scaling_factor=1.):
842 """
843 returns the Gmsh command(s) to create the primitive
844 """
845 out=""
846 for i in self.getCurves():
847 if len(out)>0:
848 out+=", %s"%i.getDirectedID()
849 else:
850 out="%s"%i.getDirectedID()
851 return "Line Loop(%s) = {%s};"%(self.getID(),out)
852
853 class ReverseCurveLoop(ReversePrimitive, PrimitiveBase):
854 """
855 An oriented loop of 1D primitives (= curves and arcs)
856
857 The loop must be closed and the L{Manifold1D}s should be oriented consistently.
858 """
859 def __init__(self,curve_loop):
860 """
861 creates a polygon from a list of line curves. The curves must form a closed loop.
862 """
863 if not isinstance(curve_loop, CurveLoop):
864 raise ValueError("arguments need to be an instance of CurveLoop.")
865 ReversePrimitive.__init__(self, curve_loop)
866 PrimitiveBase.__init__(self)
867
868 def getCurves(self):
869 """
870 returns the curves defining the CurveLoop
871 """
872 return [ -c for c in self.getUnderlyingPrimitive().getCurves() ]
873
874 def __len__(self):
875 return len(self.getUnderlyingPrimitive())
876
877 class PrimitiveBase2D(PrimitiveBase):
878 """
879 general two-dimensional primitive
880 """
881 def __init__(self):
882 """
883 create a two-dimensional primitive
884 """
885 super(PrimitiveBase2D, self).__init__()
886
887 def getBoundary(self):
888 """
889 returns a list of the 1D primitives forming the boundary of the Surface (including holes)
890 """
891 out=[]
892 for i in self.getPrimitives():
893 if isinstance(i, Manifold1D): out.append(i)
894 return out
895
896 def getBoundary(self):
897 """
898 returns a list of the 1D primitives forming the boundary of the Surface (including holes)
899 """
900 out=[]
901 for i in self.getPrimitives():
902 if isinstance(i, Manifold1D): out.append(i)
903 return out
904
905 def getBoundaryLoop(self):
906 """
907 returns the loop defining the outer boundary
908 """
909 raise NotImplementedError("getBoundaryLoop is not implemented for this class %s."%self.__class__.__name__)
910
911 def getHoles(self):
912 """
913 returns the holes
914 """
915 raise NotImplementedError("getHoles is not implemented for this class %s."%self.__class__.__name__)
916
917 def collectPrimitiveBases(self):
918 """
919 returns primitives used to construct the Surface
920 """
921 out=[self] + self.getBoundaryLoop().collectPrimitiveBases()
922 for i in self.getHoles(): out+=i.collectPrimitiveBases()
923 return out
924
925 class RuledSurface(PrimitiveBase2D):
926 """
927 A ruled surface, i.e., a surface that can be interpolated using transfinite interpolation
928 """
929 def __init__(self,loop):
930 """
931 creates a ruled surface with boundary loop
932
933 @param loop: L{CurveLoop} defining the boundary of the surface.
934 """
935 super(RuledSurface, self).__init__()
936 if not isinstance(loop,CurveLoop):
937 raise TypeError("argument loop needs to be a CurveLoop object.")
938 if len(loop)<2:
939 raise TypeError("the loop must contain at least two Curves.")
940 if len(loop)>4:
941 raise TypeError("the loop must contain at least three Curves.")
942
943 self.__loop=loop
944
945 def getBoundaryLoop(self):
946 """
947 returns the loop defining the outer boundary
948 """
949 return self.__loop
950
951 def getHoles(self):
952 """
953 returns the holes
954 """
955 return []
956
957 def getGmshCommand(self,scaling_factor=1.):
958 """
959 returns the Gmsh command(s) to create the primitive
960 """
961 return "Ruled Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
962
963 def substitute(self,sub_dict):
964 """
965 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
966 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
967 with substituted arguments is returned.
968 """
969 if not sub_dict.has_key(self):
970 sub_dict[self]=RuledSurface(self.getBoundaryLoop().substitute(sub_dict))
971 return sub_dict[self]
972
973 def isColocated(self,primitive):
974 """
975 returns True if each curve is collocted with a curve in primitive
976 """
977 if isinstance(primitive,RuledSurface):
978 return self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop())
979 else:
980 return False
981
982 def createRuledSurface(*curves):
983 """
984 an easier way to create a L{RuledSurface} from given curves.
985 """
986 return RuledSurface(CurveLoop(*curves))
987
988 class PlaneSurface(PrimitiveBase2D):
989 """
990 a plane surface with holes
991 """
992 def __init__(self,loop,holes=[]):
993 """
994 creates a plane surface with a hole
995
996 @param loop: L{CurveLoop} defining the boundary of the surface
997 @param holes: list of L{CurveLoop} defining holes in the surface.
998 @note: A CurveLoop defining a hole should not have any lines in common with the exterior CurveLoop.
999 A CurveLoop defining a hole should not have any lines in common with another CurveLoop defining a hole in the same surface.
1000 """
1001 super(PlaneSurface, self).__init__()
1002 if not isinstance(loop,CurveLoop):
1003 raise TypeError("argument loop needs to be a CurveLoop object.")
1004 for l in loop.getCurves():
1005 if not isinstance(l,Line):
1006 raise TypeError("loop may be formed by Lines only.")
1007 for i in range(len(holes)):
1008 if not isinstance(holes[i], CurveLoop):
1009 raise TypeError("%i-th hole needs to be a CurveLoop object.")
1010 for l in holes[i].getCurves():
1011 if not isinstance(l,Line):
1012 raise TypeError("holes may be formed by Lines only.")
1013 #TODO: check if lines and holes are in a plane
1014 #TODO: are holes really holes?
1015 self.__loop=loop
1016 self.__holes=holes
1017 def getHoles(self):
1018 """
1019 returns the holes
1020 """
1021 return self.__holes
1022 def getBoundaryLoop(self):
1023 """
1024 returns the loop defining the boundary
1025 """
1026 return self.__loop
1027
1028 def getGmshCommand(self,scaling_factor=1.):
1029 """
1030 returns the Gmsh command(s) to create the primitive
1031 """
1032 out=""
1033 for i in self.getHoles():
1034 if len(out)>0:
1035 out+=", %s"%i.getDirectedID()
1036 else:
1037 out="%s"%i.getDirectedID()
1038 if len(out)>0:
1039 return "Plane Surface(%s) = {%s, %s};"%(self.getID(),self.getBoundaryLoop().getDirectedID(), out)
1040 else:
1041 return "Plane Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
1042
1043 def substitute(self,sub_dict):
1044 """
1045 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1046 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1047 with substituted arguments is returned.
1048 """
1049 if not sub_dict.has_key(self):
1050 sub_dict[self]=PlaneSurface(self.getBoundaryLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1051 return sub_dict[self]
1052
1053 def isColocated(self,primitive):
1054 """
1055 returns True if each curve is collocted with a curve in primitive
1056 """
1057 if isinstance(primitive,PlaneSurface):
1058 if self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop()):
1059 hs0=self.getHoles()
1060 hs1=primitive.getHoles()
1061 if len(hs0) == len(hs1):
1062 for h0 in hs0:
1063 collocated = False
1064 for h1 in hs1:
1065 collocated = collocated or h0.isColocated(h1)
1066 if not collocated: return False
1067 return True
1068 return False
1069 else:
1070 return False
1071 else:
1072 return False
1073
1074 class SurfaceLoop(PrimitiveBase):
1075 """
1076 a loop of 2D primitives. It defines the shell of a volume.
1077
1078 The loop must represent a closed shell, and the primitives should be oriented consistently.
1079 """
1080 def __init__(self,*surfaces):
1081 """
1082 creates a surface loop
1083 """
1084 super(SurfaceLoop, self).__init__()
1085 if len(surfaces)<2:
1086 raise TypeError("at least two surfaces have to be given.")
1087 for i in range(len(surfaces)):
1088 if not isinstance(surfaces[i],PrimitiveBase2D):
1089 raise TypeError("%s-th argument is not a PrimitiveBase2D object."%i)
1090 # for the curves a loop:
1091 used=[ True for s in surfaces]
1092 self.__surfaces=[surfaces[0]]
1093 edges=[ e in surfaces[0].getBoundary() ]
1094 used_edges=[ False in surfaces[0].getBoundary() ]
1095 while min(used):
1096 found=False
1097 for i in xrange(len(surfaces)):
1098 if not used[i]:
1099 i_boundary=surfaces[i].getBoundary()
1100 for ib in xrange(i_boundary):
1101 if i_boundary[ib] in edges:
1102 if used_edges[edges.index(i_boundary[ib])]:
1103 raise TypeError("boundary segment %s is shared by more than one surface."%str(i_boundary[ib]))
1104 used_edges[edges.index(i_boundary[ib])]=True
1105 self.__surfaces.append(surfaces[i])
1106 for b in i_boundary:
1107 if not b in edges:
1108 edges.append(b)
1109 used_edges.append(False)
1110 found=True
1111 used[i]=True
1112 break
1113 if found: break
1114 if not found:
1115 raise ValueError("loop is not closed.")
1116 if min(used_edges):
1117 raise ValueError("loop is not closed. Surface is missing.")
1118 def __len__(self):
1119 """
1120 return the number of curves in the SurfaceLoop
1121 """
1122 return len(self.__surfaces)
1123
1124 def getSurfaces(self):
1125 """
1126 returns the surfaces defining the SurfaceLoop
1127 """
1128 return self.__curves
1129
1130 def collectPrimitiveBases(self):
1131 """
1132 returns primitives used to construct the SurfaceLoop
1133 """
1134 out=[self]
1135 for c in self.getSurfaces(): out+=c.collectPrimitiveBases()
1136 return out
1137 def getGmshCommand(self,scaling_factor=1.):
1138 """
1139 returns the Gmsh command(s) to create the primitive
1140 """
1141 out=""
1142 for i in self.getSurfaces():
1143 if len(out)>0:
1144 out+=", %s"%i.getDirectedID()
1145 else:
1146 out="%s"%i.getDirectedID()
1147 return "Surface Loop(%s) = {%s};"%(self.getID(),out)
1148 def substitute(self,sub_dict):
1149 """
1150 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1151 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1152 with substituted arguments is returned.
1153 """
1154 if not sub_dict.has_key(self):
1155 new_s=[]
1156 for s in self.getCurves(): new_s.append(s.substitute(sub_dict))
1157 sub_dict[self]=SurfaceLoop(*tuple(new_s))
1158 return sub_dict[self]
1159
1160 def isColocated(self,primitive):
1161 """
1162 returns True if each surface is collocted with a curve in primitive and vice versa.
1163 """
1164 if isinstance(primitive,SurfaceLoop):
1165 if len(primitive) == len(self):
1166 sp0=self.getSurfaces()
1167 sp1=primitive.getCurves()
1168 for s0 in sp0:
1169 collocated = False
1170 for s1 in sp1:
1171 collocated = collocated or s0.isColocated(s1)
1172 if not collocated: return False
1173 return True
1174 else:
1175 return False
1176 else:
1177 return False
1178
1179 #==========================
1180 class Volume(PrimitiveBase):
1181 """
1182 a volume with holes.
1183 """
1184 def __init__(self,loop,holes=[]):
1185 """
1186 creates a volume
1187
1188 @param loop: L{SurfaceLoop} defining the boundary of the surface
1189 @param holes: list of L{SurfaceLoop} defining holes in the surface.
1190 @note: A SurfaceLoop defining a hole should not have any surfaces in common with the exterior SurfaceLoop.
1191 A SurfaceLoop defining a hole should not have any surfaces in common with another SurfaceLoop defining a hole in the same volume.
1192 """
1193 super(Volume, self).__init__()
1194 if not loop.isSurfaceLoop():
1195 raise TypeError("argument loop needs to be a SurfaceLoop object.")
1196 for i in range(len(holes)):
1197 if not holes[i].isSurfaceLoop():
1198 raise TypeError("%i th hole needs to be a SurfaceLoop object.")
1199 self.__loop=loop
1200 self.__holes=holes
1201 def getHoles(self):
1202 return self.__holes
1203 def getSurfaceLoop(self):
1204 return self.__loop
1205 def __add__(self,other):
1206 return Volume(self.getSurfaceLoop()+other, holes=[h+other for h in self.getHoles()])
1207 def collectPrimitiveBases(self):
1208 out=[self] + self.getSurfaceLoop().collectPrimitiveBases()
1209 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1210 return out
1211 def getConstructionPoints(self):
1212 out=self.getSurfaceLoop().getConstructionPoints()
1213 for i in self.getHoles(): out|=i.Points()
1214 return out
1215 def getGmshCommand(self,scaling_factor=1.):
1216 """
1217 returns the Gmsh command(s) to create the primitive
1218 """
1219 out=""
1220 for i in self.getHoles():
1221 if len(out)>0:
1222 out+=", %s"%i.getDirectedID()
1223 else:
1224 out="%s"%i.getDirectedID()
1225 if len(out)>0:
1226 return "Volume(%s) = {%s, %s};"%(self.getID(),self.getSurfaceLoop().getDirectedID(), out)
1227 else:
1228 return "Volume(%s) = {%s};"%(self.getID(),self.getSurfaceLoop().getDirectedID())
1229
1230 class PropertySet(PrimitiveBase):
1231 """
1232 defines a group L{PrimitiveBase} objects.
1233 """
1234 def __init__(self,tag=None,*items):
1235 super(PropertySet, self).__init__()
1236 self.__items=items
1237 self.__tag=tag
1238 def collectPrimitiveBases(self):
1239 out=[self]+self.getBoundaryLoop().collectPrimitiveBases()
1240 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1241 return out
1242
1243 class PrimitiveBaseStack(object):
1244 def __init__(self,*items):
1245 self.__prims=set()
1246 for i in items:
1247 self.__prims|=i.getPrimitives()
1248 self.__prims=list(self.__prims)
1249 self.__prims.sort()
1250
1251 def getGmshCommands(self,scaling_factor=1.):
1252 out=""
1253 for i in self.__prims:
1254 out+=i.getGmshCommand(scaling_factor)+"\n"
1255 return out

  ViewVC Help
Powered by ViewVC 1.1.26