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

  ViewVC Help
Powered by ViewVC 1.1.26