/[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 931 - (show annotations)
Fri Jan 19 03:06:33 2007 UTC (12 years, 8 months ago) by gross
File MIME type: text/x-python
File size: 47242 byte(s)
pycad supports volumes now.
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 class Primitive(object):
173 """
174 A general primitive
175 """
176 def __init__(self):
177 """
178 initializes PrimitiveBase instance object with id
179 """
180 global global_primitive_id_counter
181 self.__ID=global_primitive_id_counter
182 global_primitive_id_counter+=1
183
184 def getID(self):
185 """
186 returns the primitive ID
187 """
188 return self.__ID
189
190 def getDirectedID(self):
191 """
192 returns the primitive ID where a negative signs means that the reversed ordring is used.
193 """
194 return self.getID()
195
196 def __repr__(self):
197 return "%s(%s)"%(self.__class__.__name__,self.getID())
198
199 def getUnderlyingPrimitive(self):
200 """
201 returns the underlying primitive
202 """
203 return self
204 def hasSameOrientation(self,other):
205 """
206 returns True if other is the same primitive and has the same orientation
207 """
208 return self == other and isinstance(other,Primitive)
209
210 def __neg__(self):
211 """
212 returns a view onto the curve with reversed ordering
213
214 @note: this class is overwritten by subclass
215 """
216 raise NotImplementedError("__neg__ is not implemented.")
217
218 def getGmshCommand(self, local_scaling_factor=1.):
219 """
220 returns the Gmsh command(s) to create the primitive
221
222 @note: this class is overwritten by subclass
223 """
224 raise NotImplementedError("getGmshCommand is not implemented.")
225
226 def substitute(self,sub_dict):
227 """
228 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
229 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
230 with substituted arguments is returned.
231
232 @note: this class is overwritten by subclass
233 """
234 raise NotImplementedError("substitute is not implemented.")
235
236 def collectPrimitiveBases(self):
237 """
238 returns a list of primitives used to construct the primitive. It may contain primitives twice
239
240 @note: this class is overwritten by subclass
241 """
242 raise NotImplementedError("collectPrimitiveBases is not implemented.")
243
244 def isColocated(self,primitive):
245 """
246 returns True is the two primitives are located at the smae position
247
248 @note: this class is overwritten by subclass
249 """
250 raise NotImplementedError("isColocated is not implemented.")
251
252
253 class ReversePrimitive(object):
254 """
255 A view onto a primitive creating an reverse orientation
256 """
257 def __init__(self,primitive):
258 """
259 instantiate a view onto primitve
260 """
261 if not isinstance(primitive, Primitive):
262 raise ValueError("argument needs to be a Primitive class object.")
263 self.__primitive=primitive
264
265 def getID(self):
266 """
267 returns the primitive ID
268 """
269 return self.__primitive.getID()
270
271 def getUnderlyingPrimitive(self):
272 """
273 returns the underlying primitive
274 """
275 return self.__primitive
276
277 def hasSameOrientation(self,other):
278 """
279 returns True if other is the same primitive and has the same orientation
280 """
281 return self == other and isinstance(other,ReversePrimitive)
282
283 def __repr__(self):
284 return "-%s(%s)"%(self.__primitive.__class__.__name__,self.getID())
285
286 def getDirectedID(self):
287 """
288 returns the primitive ID where a negative signs means that the reversed ordring is used.
289 """
290 return -self.__primitive.getID()
291
292 def substitute(self,sub_dict):
293 """
294 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
295 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
296 with substituted arguments is returned.
297 """
298 if not sub_dict.has_key(self):
299 sub_dict[self]=-self.getUnderlyingPrimitive().substitute(sub_dict)
300 return sub_dict[self]
301
302 def __neg__(self):
303 """
304 returns a view onto the curve with reversed ordering
305 """
306 return self.__primitive
307
308 def getGmshCommand(self, local_scaling_factor=1.):
309 """
310 returns the Gmsh command(s) to create the primitive
311 """
312 return self.__primitive.getGmshCommand(local_scaling_factor)
313
314 def collectPrimitiveBases(self):
315 """
316 returns a list of primitives used to construct the primitive. It may contain primitives twice
317 """
318 return self.__primitive.collectPrimitiveBases()
319
320 def isColocated(self,primitive):
321 """
322 returns True is the two primitives are located at the smae position
323
324 @note: this class is overwritten by subclass
325 """
326 return self.__primitive.isColocated(primitive)
327
328 class Point(Primitive, PrimitiveBase):
329 """
330 a three dimensional point
331 """
332 def __init__(self,x=0.,y=0.,z=0.,local_scale=1.):
333 """
334 creates a point with coorinates x,y,z with the local refinement factor local_scale
335 """
336 PrimitiveBase.__init__(self)
337 Primitive.__init__(self)
338 self.setCoordinates(numarray.array([x,y,z],_TYPE))
339 self.setLocalScale(local_scale)
340
341 def setLocalScale(self,factor=1.):
342 """
343 sets the local refinement factor
344 """
345 if factor<=0.:
346 raise ValueError("scaling factor must be positive.")
347 self.__local_scale=factor
348
349 def getLocalScale(self):
350 """
351 returns the local refinement factor
352 """
353 return self.__local_scale
354 def getCoordinates(self):
355 """
356 returns the coodinates of the point as L{numarray.NumArray} object
357 """
358 return self._x
359 def setCoordinates(self,x):
360 """
361 returns the coodinates of the point as L{numarray.NumArray} object
362 """
363 if not isinstance(x, numarray.NumArray):
364 self._x=numarray.array(x,_TYPE)
365 else:
366 self._x=x
367
368 def collectPrimitiveBases(self):
369 """
370 returns primitives used to construct the primitive
371 """
372 return [self]
373
374 def isColocated(self,primitive):
375 """
376 returns True if L{Point} primitive is colocation (same coordinates)
377 that means if |self-primitive| <= tol * max(|self|,|primitive|)
378 """
379 if isinstance(primitive,Point):
380 primitive=primitive.getCoordinates()
381 c=self.getCoordinates()
382 d=c-primitive
383 return numarray.dot(d,d)<=getToleranceForColocation()**2*max(numarray.dot(c,c),numarray.dot(primitive,primitive))
384 else:
385 return False
386
387 def substitute(self,sub_dict):
388 """
389 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
390 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
391 with substituted arguments is returned.
392 """
393 if not sub_dict.has_key(self):
394 c=self.getCoordinates()
395 sub_dict[self]=Point(c[0],c[1],c[2],local_scale=self.getLocalScale())
396 return sub_dict[self]
397
398 def modifyBy(self,transformation):
399 """
400 modifies the coordinates by applying a transformation
401 """
402 self.setCoordinates(transformation(self.getCoordinates()))
403
404
405 def getGmshCommand(self, local_scaling_factor=1.):
406 """
407 returns the Gmsh command(s) to create the primitive
408 """
409 c=self.getCoordinates()
410 return "Point(%s) = {%s , %s, %s , %s };"%(self.getID(),c[0],c[1],c[2], self.getLocalScale()*local_scaling_factor)
411
412 def __neg__(self):
413 """
414 returns a view of the object with reverse orientiention. As a point has no direction the object itself is returned.
415 """
416 return self
417
418 class Manifold1D(PrimitiveBase):
419 """
420 general one-dimensional minifold in 3D defined by a start and end point.
421 """
422 def __init__(self):
423 """
424 create a one-dimensional manifold
425 """
426 PrimitiveBase.__init__(self)
427
428 def getStartPoint(self):
429 """
430 returns start point
431 """
432 raise NotImplementedError()
433
434 def getEndPoint(self):
435 """
436 returns end point
437 """
438 raise NotImplementedError()
439 def getBoundary(self):
440 """
441 returns a list of the zero-dimensional manifolds forming the boundary of the curve
442 """
443 return [ self.getStartPoint(), self.getEndPoint()]
444
445 class CurveBase(Manifold1D):
446 """
447 A Curve is defined by a set of control points
448 """
449 def __init__(self):
450 """
451 create curve
452 """
453 Manifold1D.__init__(self)
454
455 def __len__(self):
456 """
457 returns the number of control points
458 """
459 return len(self.getControlPoints())
460
461 def getStartPoint(self):
462 """
463 returns start point
464 """
465 return self.getControlPoints()[0]
466
467 def getEndPoint(self):
468 """
469 returns end point
470 """
471 return self.getControlPoints()[-1]
472
473 def getControlPoints(self):
474 """
475 returns a list of the points
476 """
477 raise NotImplementedError()
478
479 class Curve(CurveBase, Primitive):
480 """
481 a curve defined through a list of control points.
482 """
483 def __init__(self,*points):
484 """
485 defines a curve form control points
486 """
487 if len(points)<2:
488 raise ValueError("Curve needs at least two points")
489 i=0
490 for p in points:
491 i+=1
492 if not isinstance(p,Point): raise TypeError("%s-th argument is not a Point object."%i)
493 self.__points=points
494 CurveBase.__init__(self)
495 Primitive.__init__(self)
496
497 def getControlPoints(self):
498 """
499 returns a list of the points
500 """
501 return self.__points
502
503 def __neg__(self):
504 """
505 returns a view onto the curve with reversed ordering
506 """
507 return ReverseCurve(self)
508
509 def substitute(self,sub_dict):
510 """
511 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
512 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
513 with substituted arguments is returned.
514 """
515 if not sub_dict.has_key(self):
516 new_p=[]
517 for p in self.getControlPoints(): new_p.append(p.substitute(sub_dict))
518 sub_dict[self]=self.__class__(*tuple(new_p))
519 return sub_dict[self]
520
521 def collectPrimitiveBases(self):
522 """
523 returns primitives used to construct the Curve
524 """
525 out=[self]
526 for p in self.getControlPoints(): out+=p.collectPrimitiveBases()
527 return out
528
529 def isColocated(self,primitive):
530 """
531 returns True curves are on the same position
532 """
533 if hasattr(primitive,"getUnderlyingPrimitive"):
534 if isinstance(primitive.getUnderlyingPrimitive(),self.__class__):
535 if len(primitive) == len(self):
536 cp0=self.getControlPoints()
537 cp1=primitive.getControlPoints()
538 match=True
539 for i in range(len(cp0)):
540 if not cp0[i].isColocated(cp1[i]):
541 match=False
542 break
543 if not match:
544 for i in range(len(cp0)):
545 if not cp0[i].isColocated(cp1[len(cp0)-1-i]):
546 return False
547 return True
548 return False
549
550 class ReverseCurve(CurveBase, ReversePrimitive):
551 """
552 a curve defined through a list of control points.
553 """
554 def __init__(self,curve):
555 """
556 defines a curve form control points
557 """
558 if not isinstance(curve, Curve):
559 raise TypeError("ReverseCurve needs to be an instance of Curve")
560 CurveBase.__init__(self)
561 ReversePrimitive.__init__(self,curve)
562
563 def getControlPoints(self):
564 """
565 returns a list of the points
566 """
567 out=[p for p in self.getUnderlyingPrimitive().getControlPoints()]
568 out.reverse()
569 return out
570
571 class Spline(Curve):
572 """
573 a spline curve defined through a list of control points.
574 """
575 def getGmshCommand(self,scaling_factor=1.):
576 """
577 returns the Gmsh command(s) to create the Curve
578 """
579 out=""
580 for i in self.getControlPoints():
581 if len(out)>0:
582 out+=", %s"%i.getDirectedID()
583 else:
584 out="%s"%i.getDirectedID()
585 return "Spline(%s) = {%s};"%(self.getID(),out)
586
587
588 class BezierCurve(Curve):
589 """
590 a Bezier curve
591 """
592 def getGmshCommand(self,scaling_factor=1.):
593 """
594 returns the Gmsh command(s) to create the Curve
595 """
596 out=""
597 for i in self.getControlPoints():
598 if len(out)>0:
599 out+=", %s"%i.getDirectedID()
600 else:
601 out="%s"%i.getDirectedID()
602 return "Bezier(%s) = {%s};"%(self.getID(),out)
603
604 class BSpline(Curve):
605 """
606 a BSpline curve. Control points may be repeated.
607 """
608 def getGmshCommand(self,scaling_factor=1.):
609 """
610 returns the Gmsh command(s) to create the Curve
611 """
612 out=""
613 for i in self.getControlPoints():
614 if len(out)>0:
615 out+=", %s"%i.getDirectedID()
616 else:
617 out="%s"%i.getDirectedID()
618 return "BSpline(%s) = {%s};"%(self.getID(),out)
619
620 class Line(Curve):
621 """
622 a line is defined by two pointDirecteds
623 """
624 def __init__(self,*points):
625 """
626 defines a line with start and end point
627 """
628 if len(points)!=2:
629 raise TypeError("Line needs two points")
630 Curve.__init__(self,*points)
631 def getGmshCommand(self,scaling_factor=1.):
632 """
633 returns the Gmsh command(s) to create the Curve
634 """
635 return "Line(%s) = {%s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getEndPoint().getDirectedID())
636
637
638 class ArcBase(Manifold1D):
639 def __init__(self):
640 """
641 create curve
642 """
643 Manifold1D.__init__(self)
644 def collectPrimitiveBases(self):
645 """
646 returns the primitives used to construct the Curve
647 """
648 out=[self]
649 out+=self.getStartPoint().collectPrimitiveBases()
650 out+=self.getEndPoint().collectPrimitiveBases()
651 out+=self.getCenterPoint().collectPrimitiveBases()
652 return out
653
654
655 def getCenterPoint(self):
656 """
657 returns center
658 """
659 raise NotImplementedError()
660
661 class Arc(ArcBase, Primitive):
662 """
663 defines an arc which is strictly, smaller than Pi
664 """
665 def __init__(self,center,start,end):
666 """
667 creates an arc by the start point, end point and center
668 """
669 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
670 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
671 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
672 # TODO: check length of circle.
673 ArcBase.__init__(self)
674 Primitive.__init__(self)
675 self.__center=center
676 self.__start=start
677 self.__end=end
678 def __neg__(self):
679 """
680 returns a view onto the curve with reversed ordering
681 """
682 return ReverseArc(self)
683
684 def getStartPoint(self):
685 """
686 returns start point
687 """
688 return self.__start
689
690 def getEndPoint(self):
691 """
692 returns end point
693 """
694 return self.__end
695
696 def getCenterPoint(self):
697 """
698 returns center
699 """
700 return self.__center
701
702 def substitute(self,sub_dict):
703 """
704 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
705 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
706 with substituted arguments is returned.
707 """
708 if not sub_dict.has_key(self):
709 sub_dict[self]=Arc(self.getCenterPoint().substitute(sub_dict),self.getStartPoint().substitute(sub_dict),self.getEndPoint().substitute(sub_dict))
710 return sub_dict[self]
711
712 def getGmshCommand(self,scaling_factor=1.):
713 """
714 returns the Gmsh command(s) to create the primitive
715 """
716 return "Circle(%s) = {%s, %s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getCenterPoint().getDirectedID(),self.getEndPoint().getDirectedID())
717
718 def isColocated(self,primitive):
719 """
720 returns True curves are on the same position
721 """
722 if hasattr(primitive,"getUnderlyingPrimitive"):
723 if isinstance(primitive.getUnderlyingPrimitive(),Arc):
724 return (self.getCenterPoint().isColocated(primitive.getCenterPoint())) and ( \
725 (self.getEndPoint().isColocated(primitive.getEndPoint()) and self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
726 or (self.getEndPoint().isColocated(primitive.getStartPoint()) and self.getStartPoint().isColocated(primitive.getEndPoint()) ) )
727 return False
728
729 class ReverseArc(ArcBase, ReversePrimitive):
730 """
731 defines an arc which is strictly, smaller than Pi
732 """
733 def __init__(self,arc):
734 """
735 creates an arc by the start point, end point and center
736 """
737 if not isinstance(arc, Arc):
738 raise TypeError("ReverseCurve needs to be an instance of Arc")
739 ArcBase.__init__(self)
740 ReversePrimitive.__init__(self,arc)
741
742 def getStartPoint(self):
743 """
744 returns start point
745 """
746 return self.getUnderlyingPrimitive().getEndPoint()
747
748 def getEndPoint(self):
749 """
750 returns end point
751 """
752 return self.getUnderlyingPrimitive().getStartPoint()
753
754 def getCenterPoint(self):
755 """
756 returns center
757 """
758 return self.getUnderlyingPrimitive().getCenterPoint()
759
760 class CurveLoop(Primitive, PrimitiveBase):
761 """
762 An oriented loop of one-dimensional manifolds (= curves and arcs)
763
764 The loop must be closed and the L{Manifold1D}s should be oriented consistently.
765 """
766 def __init__(self,*curves):
767 """
768 creates a polygon from a list of line curves. The curves must form a closed loop.
769 """
770 if len(curves)<2:
771 raise ValueError("at least two curves have to be given.")
772 for i in range(len(curves)):
773 if not isinstance(curves[i],Manifold1D):
774 raise TypeError("%s-th argument is not a Manifold1D object."%i)
775 # for the curves a loop:
776 used=[ False for i in curves]
777 self.__curves=[curves[0]]
778 used[0]=True
779 while not min(used):
780 found=False
781 for i in xrange(len(curves)):
782 if not used[i]:
783 if self.__curves[-1].getEndPoint() == curves[i].getStartPoint():
784 self.__curves.append(curves[i])
785 used[i]=True
786 found=True
787 break
788 if not found:
789 raise ValueError("loop is not closed.")
790 if not self.__curves[0].getStartPoint() == self.__curves[-1].getEndPoint():
791 raise ValueError("loop is not closed.")
792 Primitive.__init__(self)
793 PrimitiveBase.__init__(self)
794
795 def getCurves(self):
796 """
797 returns the curves defining the CurveLoop
798 """
799 return self.__curves
800
801 def __neg__(self):
802 """
803 returns a view onto the curve with reversed ordering
804 """
805 return ReverseCurveLoop(self)
806
807 def __len__(self):
808 """
809 return the number of curves in the CurveLoop
810 """
811 return len(self.getCurves())
812
813
814 def collectPrimitiveBases(self):
815 """
816 returns primitives used to construct the CurveLoop
817 """
818 out=[self]
819 for c in self.getCurves(): out+=c.collectPrimitiveBases()
820 return out
821
822 def substitute(self,sub_dict):
823 """
824 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
825 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
826 with substituted arguments is returned.
827 """
828 if not sub_dict.has_key(self):
829 new_c=[]
830 for c in self.getCurves(): new_c.append(c.substitute(sub_dict))
831 sub_dict[self]=CurveLoop(*tuple(new_c))
832 return sub_dict[self]
833
834 def isColocated(self,primitive):
835 """
836 returns True if each curve is collocted with a curve in primitive
837 """
838 if hasattr(primitive,"getUnderlyingPrimitive"):
839 if isinstance(primitive.getUnderlyingPrimitive(),CurveLoop):
840 if len(primitive) == len(self):
841 cp0=self.getCurves()
842 cp1=primitive.getCurves()
843 for c0 in cp0:
844 collocated = False
845 for c1 in cp1:
846 collocated = collocated or c0.isColocated(c1)
847 if not collocated: return False
848 return True
849 return False
850
851 def getGmshCommand(self,scaling_factor=1.):
852 """
853 returns the Gmsh command(s) to create the primitive
854 """
855 out=""
856 for i in self.getCurves():
857 if len(out)>0:
858 out+=", %s"%i.getDirectedID()
859 else:
860 out="%s"%i.getDirectedID()
861 return "Line Loop(%s) = {%s};"%(self.getID(),out)
862
863 class ReverseCurveLoop(ReversePrimitive, PrimitiveBase):
864 """
865 An oriented loop of one-dimensional manifolds (= curves and arcs)
866
867 The loop must be closed and the one-dimensional manifolds should be oriented consistently.
868 """
869 def __init__(self,curve_loop):
870 """
871 creates a polygon from a list of line curves. The curves must form a closed loop.
872 """
873 if not isinstance(curve_loop, CurveLoop):
874 raise TypeError("arguments need to be an instance of CurveLoop.")
875 ReversePrimitive.__init__(self, curve_loop)
876 PrimitiveBase.__init__(self)
877
878 def getCurves(self):
879 """
880 returns the curves defining the CurveLoop
881 """
882 return [ -c for c in self.getUnderlyingPrimitive().getCurves() ]
883
884 def __len__(self):
885 return len(self.getUnderlyingPrimitive())
886
887 #=
888 class Manifold2D(PrimitiveBase):
889 """
890 general two-dimensional manifold
891 """
892 def __init__(self):
893 """
894 create a two-dimensional manifold
895 """
896 PrimitiveBase.__init__(self)
897
898 def getBoundary(self):
899 """
900 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
901 """
902 raise NotImplementedError()
903
904 class RuledSurface(Primitive, Manifold2D):
905 """
906 A ruled surface, i.e., a surface that can be interpolated using transfinite interpolation
907 """
908 def __init__(self,loop):
909 """
910 creates a ruled surface with boundary loop
911
912 @param loop: L{CurveLoop} defining the boundary of the surface.
913 """
914 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
915 raise TypeError("argument loop needs to be a CurveLoop object.")
916 if len(loop)<2:
917 raise ValueError("the loop must contain at least two Curves.")
918 if len(loop)>4:
919 raise ValueError("the loop must contain at least three Curves.")
920 Primitive.__init__(self)
921 Manifold2D.__init__(self)
922 self.__loop=loop
923
924 def __neg__(self):
925 """
926 returns a view onto the suface with reversed ordering
927 """
928 return ReverseRuledSurface(self)
929
930 def getBoundaryLoop(self):
931 """
932 returns the loop defining the outer boundary
933 """
934 return self.__loop
935
936 def getBoundary(self):
937 """
938 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
939 """
940 return self.getBoundaryLoop().getCurves()
941
942 def getGmshCommand(self,scaling_factor=1.):
943 """
944 returns the Gmsh command(s) to create the primitive
945 """
946 return "Ruled Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
947
948 def substitute(self,sub_dict):
949 """
950 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
951 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
952 with substituted arguments is returned.
953 """
954 if not sub_dict.has_key(self):
955 sub_dict[self]=RuledSurface(self.getBoundaryLoop().substitute(sub_dict))
956 return sub_dict[self]
957
958 def isColocated(self,primitive):
959 """
960 returns True if each curve is collocted with a curve in primitive
961 """
962 if hasattr(primitive,"getUnderlyingPrimitive"):
963 if isinstance(primitive.getUnderlyingPrimitive(),RuledSurface):
964 return self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop())
965 return False
966
967 def collectPrimitiveBases(self):
968 """
969 returns primitives used to construct the Surface
970 """
971 return [self] + self.getBoundaryLoop().collectPrimitiveBases()
972
973 def createRuledSurface(*curves):
974 """
975 an easier way to create a L{RuledSurface} from given curves.
976 """
977 return RuledSurface(CurveLoop(*curves))
978
979
980 class ReverseRuledSurface(ReversePrimitive, Manifold2D):
981 """
982 creates a view onto a L{RuledSurface} but with the reverse orientation
983 """
984 def __init__(self,surface):
985 """
986 creates a polygon from a list of line curves. The curves must form a closed loop.
987 """
988 if not isinstance(surface, RuledSurface):
989 raise TypeError("arguments need to be an instance of CurveLoop.")
990 ReversePrimitive.__init__(self, surface)
991 Manifold2D.__init__(self)
992
993 def getBoundaryLoop(self):
994 """
995 returns the CurveLoop defining the RuledSurface
996 """
997 return -self.getUnderlyingPrimitive().getBoundaryLoop()
998
999 def getBoundary(self):
1000 """
1001 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1002 """
1003 return self.getBoundaryLoop().getCurves()
1004 #==============================
1005 class PlaneSurface(Primitive, Manifold2D):
1006 """
1007 a plane surface with holes
1008 """
1009 def __init__(self,loop,holes=[]):
1010 """
1011 creates a plane surface with a hole
1012
1013 @param loop: L{CurveLoop} defining the boundary of the surface
1014 @param holes: list of L{CurveLoop} defining holes in the surface.
1015 @note: A CurveLoop defining a hole should not have any lines in common with the exterior CurveLoop.
1016 A CurveLoop defining a hole should not have any lines in common with another CurveLoop defining a hole in the same surface.
1017 """
1018 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1019 raise TypeError("argument loop needs to be a CurveLoop object.")
1020 for l in loop.getCurves():
1021 if not isinstance(l.getUnderlyingPrimitive(),Line):
1022 raise TypeError("loop may be formed by Lines only.")
1023 for i in range(len(holes)):
1024 if not isinstance(holes[i].getUnderlyingPrimitive(), CurveLoop):
1025 raise TypeError("%i-th hole needs to be a CurveLoop object.")
1026 for l in holes[i].getCurves():
1027 if not isinstance(l.getUnderlyingPrimitive(),Line):
1028 raise TypeError("holes may be formed by Lines only.")
1029 #TODO: check if lines and holes are in a plane
1030 #TODO: are holes really holes?
1031 Primitive.__init__(self)
1032 Manifold2D.__init__(self)
1033 self.__loop=loop
1034 self.__holes=holes
1035 def getHoles(self):
1036 """
1037 returns the holes
1038 """
1039 return self.__holes
1040
1041 def getBoundaryLoop(self):
1042 """
1043 returns the loop defining the boundary
1044 """
1045 return self.__loop
1046
1047 def getGmshCommand(self,scaling_factor=1.):
1048 """
1049 returns the Gmsh command(s) to create the primitive
1050 """
1051 out=""
1052 for i in self.getHoles():
1053 if len(out)>0:
1054 out+=", %s"%i.getDirectedID()
1055 else:
1056 out="%s"%i.getDirectedID()
1057 if len(out)>0:
1058 return "Plane Surface(%s) = {%s, %s};"%(self.getID(),self.getBoundaryLoop().getDirectedID(), out)
1059 else:
1060 return "Plane Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
1061
1062 def substitute(self,sub_dict):
1063 """
1064 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1065 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1066 with substituted arguments is returned.
1067 """
1068 if not sub_dict.has_key(self):
1069 sub_dict[self]=PlaneSurface(self.getBoundaryLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1070 return sub_dict[self]
1071
1072 def isColocated(self,primitive):
1073 """
1074 returns True if each curve is collocted with a curve in primitive
1075 """
1076 if hasattr(primitive,"getUnderlyingPrimitive"):
1077 if isinstance(primitive.getUnderlyingPrimitive(),PlaneSurface):
1078 if self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop()):
1079 hs0=self.getHoles()
1080 hs1=primitive.getHoles()
1081 if len(hs0) == len(hs1):
1082 for h0 in hs0:
1083 collocated = False
1084 for h1 in hs1:
1085 collocated = collocated or h0.isColocated(h1)
1086 if not collocated: return False
1087 return True
1088 return False
1089 def collectPrimitiveBases(self):
1090 """
1091 returns primitives used to construct the Surface
1092 """
1093 out=[self] + self.getBoundaryLoop().collectPrimitiveBases()
1094 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1095 return out
1096 def __neg__(self):
1097 """
1098 returns a view onto the curve with reversed ordering
1099 """
1100 return ReversePlaneSurface(self)
1101 def getBoundary(self):
1102 """
1103 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1104 """
1105 out = []+ self.getBoundaryLoop().getCurves()
1106 for h in self.getHoles(): out+=h.getCurves()
1107 return out
1108
1109 class ReversePlaneSurface(ReversePrimitive, Manifold2D):
1110 """
1111 creates a view onto a L{PlaneSurface} but with the reverse orientation
1112 """
1113 def __init__(self,surface):
1114 """
1115 creates a polygon from a list of line curves. The curves must form a closed loop.
1116 """
1117 if not isinstance(surface, PlaneSurface):
1118 raise TypeError("arguments need to be an instance of PlaneSurface.")
1119 ReversePrimitive.__init__(self, surface)
1120 Manifold2D.__init__(self)
1121
1122 def getBoundaryLoop(self):
1123 """
1124 returns the CurveLoop defining the RuledSurface
1125 """
1126 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1127
1128 def getHoles(self):
1129 """
1130 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1131 """
1132 return [ -h for h in self.getUnderlyingPrimitive().getHoles() ]
1133
1134 def getBoundary(self):
1135 """
1136 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1137 """
1138 out = [] + self.getBoundaryLoop().getCurves()
1139 for h in self.getHoles(): out+=h.getCurves()
1140 return out
1141
1142
1143 #=========================================================================
1144 class SurfaceLoop(Primitive, PrimitiveBase):
1145 """
1146 a loop of 2D primitives. It defines the shell of a volume.
1147
1148 The loop must represent a closed shell, and the primitives should be oriented consistently.
1149 """
1150 def __init__(self,*surfaces):
1151 """
1152 creates a surface loop
1153 """
1154 if len(surfaces)<2:
1155 raise ValueError("at least two surfaces have to be given.")
1156 for i in range(len(surfaces)):
1157 if not isinstance(surfaces[i].getUnderlyingPrimitive(),Manifold2D):
1158 raise TypeError("%s-th argument is not a Manifold2D object."%i)
1159 Primitive.__init__(self)
1160 PrimitiveBase.__init__(self)
1161 # for the curves a loop:
1162 used=[ False for s in surfaces]
1163 self.__surfaces=[surfaces[0]]
1164 used[0]= True
1165 edges=[ e for e in surfaces[0].getBoundary() ]
1166 used_edges=[ False for e in surfaces[0].getBoundary() ]
1167 while not min(used):
1168 found=False
1169 for i in xrange(len(surfaces)):
1170 if not used[i]:
1171 i_boundary=surfaces[i].getBoundary()
1172 for ib in xrange(len(i_boundary)):
1173 if i_boundary[ib] in edges:
1174 found=True
1175 break
1176 if found:
1177 used[i]=True
1178 self.__surfaces.append(surfaces[i])
1179 for ib in xrange(len(i_boundary)):
1180 if i_boundary[ib] in edges:
1181 if used_edges[edges.index(i_boundary[ib])]:
1182 raise ValueError("boundary segment %s is shared by more than one surface."%str(i_boundary[ib].getUnderlyingPrimitive()))
1183 used_edges[edges.index(i_boundary[ib])]=True
1184 else:
1185 edges.append(i_boundary[ib])
1186 used_edges.append(False)
1187 break
1188 if not found:
1189 raise ValueError("loop is not closed.")
1190 if not min(used_edges):
1191 raise ValueError("loop is not closed. Surface is missing.")
1192 def __len__(self):
1193 """
1194 return the number of curves in the SurfaceLoop
1195 """
1196 return len(self.__surfaces)
1197
1198 def __neg__(self):
1199 """
1200 returns a view onto the curve with reversed ordering
1201 """
1202 return ReverseSurfaceLoop(self)
1203
1204 def getSurfaces(self):
1205 """
1206 returns the surfaces defining the SurfaceLoop
1207 """
1208 return self.__surfaces
1209
1210 def collectPrimitiveBases(self):
1211 """
1212 returns primitives used to construct the SurfaceLoop
1213 """
1214 out=[self]
1215 for c in self.getSurfaces(): out+=c.collectPrimitiveBases()
1216 return out
1217
1218 def getGmshCommand(self,scaling_factor=1.):
1219 """
1220 returns the Gmsh command(s) to create the primitive
1221 """
1222 out=""
1223 for i in self.getSurfaces():
1224 if len(out)>0:
1225 out+=", %s"%i.getDirectedID()
1226 else:
1227 out="%s"%i.getDirectedID()
1228 return "Surface Loop(%s) = {%s};"%(self.getID(),out)
1229
1230 def substitute(self,sub_dict):
1231 """
1232 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1233 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1234 with substituted arguments is returned.
1235 """
1236 if not sub_dict.has_key(self):
1237 new_s=[]
1238 for s in self.getSurfaces(): new_s.append(s.substitute(sub_dict))
1239 sub_dict[self]=SurfaceLoop(*tuple(new_s))
1240 return sub_dict[self]
1241
1242 def isColocated(self,primitive):
1243 """
1244 returns True if each surface is collocted with a curve in primitive and vice versa.
1245 """
1246 if hasattr(primitive,"getUnderlyingPrimitive"):
1247 if isinstance(primitive.getUnderlyingPrimitive(),SurfaceLoop):
1248 if len(primitive) == len(self):
1249 sp0=self.getSurfaces()
1250 sp1=primitive.getSurfaces()
1251 for s0 in sp0:
1252 collocated = False
1253 for s1 in sp1:
1254 collocated = collocated or s0.isColocated(s1)
1255 if not collocated: return False
1256 return True
1257 return False
1258
1259 class ReverseSurfaceLoop(ReversePrimitive, PrimitiveBase):
1260 """
1261 a view to SurfaceLoop with reverse orientaion
1262
1263 The loop must represent a closed shell, and the primitives should be oriented consistently.
1264 An oriented loop of 2-dimensional manifolds (= RuledSurface, PlaneSurface)
1265
1266 The loop must be closed and the one-dimensional manifolds should be oriented consistently.
1267 """
1268 def __init__(self,surface_loop):
1269 """
1270 creates a polygon from a list of line surfaces. The curves must form a closed loop.
1271 """
1272 if not isinstance(surface_loop, SurfaceLoop):
1273 raise TypeError("arguments need to be an instance of SurfaceLoop.")
1274 ReversePrimitive.__init__(self, surface_loop)
1275 PrimitiveBase.__init__(self)
1276
1277 def getSurfaces(self):
1278 """
1279 returns the surfaces defining the SurfaceLoop
1280 """
1281 return [ -s for s in self.getUnderlyingPrimitive().getSurfaces() ]
1282
1283 def __len__(self):
1284 return len(self.getUnderlyingPrimitive())
1285
1286 #==============================
1287 class Manifold3D(PrimitiveBase):
1288 """
1289 general three-dimensional manifold
1290 """
1291 def __init__(self):
1292 """
1293 create a three-dimensional manifold
1294 """
1295 PrimitiveBase.__init__(self)
1296
1297 def getBoundary(self):
1298 """
1299 returns a list of the one-dimensional manifolds forming the boundary of the volume (including holes)
1300 """
1301 raise NotImplementedError()
1302
1303 class Volume(Manifold3D, Primitive):
1304 """
1305 a volume with holes.
1306 """
1307 def __init__(self,loop,holes=[]):
1308 """
1309 creates a volume
1310
1311 @param loop: L{SurfaceLoop} defining the boundary of the surface
1312 @param holes: list of L{SurfaceLoop} defining holes in the surface.
1313 @note: A SurfaceLoop defining a hole should not have any surfaces in common with the exterior SurfaceLoop.
1314 A SurfaceLoop defining a hole should not have any surfaces in common with another SurfaceLoop defining a hole in the same volume.
1315 """
1316 if not isinstance(loop.getUnderlyingPrimitive(), SurfaceLoop):
1317 raise TypeError("argument loop needs to be a SurfaceLoop object.")
1318 for i in range(len(holes)):
1319 if not isinstance(holes[i].getUnderlyingPrimitive(), SurfaceLoop):
1320 raise TypeError("%i th hole needs to be a SurfaceLoop object.")
1321 Primitive.__init__(self)
1322 Manifold3D.__init__(self)
1323 self.__loop=loop
1324 self.__holes=holes
1325 def getHoles(self):
1326 """
1327 returns the hole in the volume
1328 """
1329 return self.__holes
1330 def getSurfaceLoop(self):
1331 """
1332 returns the loop forming the surface
1333 """
1334 return self.__loop
1335
1336 def getGmshCommand(self,scaling_factor=1.):
1337 """
1338 returns the Gmsh command(s) to create the primitive
1339 """
1340 out=""
1341 for i in self.getHoles():
1342 if len(out)>0:
1343 out+=", %s"%i.getDirectedID()
1344 else:
1345 out="%s"%i.getDirectedID()
1346 if len(out)>0:
1347 return "Volume(%s) = {%s, %s};"%(self.getID(),self.getSurfaceLoop().getDirectedID(), out)
1348 else:
1349 return "Volume(%s) = {%s};"%(self.getID(),self.getSurfaceLoop().getDirectedID())
1350
1351 def substitute(self,sub_dict):
1352 """
1353 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1354 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1355 with substituted arguments is returned.
1356 """
1357 if not sub_dict.has_key(self):
1358 sub_dict[self]=Volume(self.getSurfaceLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1359 return sub_dict[self]
1360
1361 def isColocated(self,primitive):
1362 """
1363 returns True if each curve is collocted with a curve in primitive
1364 """
1365 if hasattr(primitive,"getUnderlyingPrimitive"):
1366 if isinstance(primitive.getUnderlyingPrimitive(),Volume):
1367 if self.getSurfaceLoop().isColocated(primitive.getSurfaceLoop()):
1368 hs0=self.getHoles()
1369 hs1=primitive.getHoles()
1370 if len(hs0) == len(hs1):
1371 for h0 in hs0:
1372 collocated = False
1373 for h1 in hs1:
1374 collocated = collocated or h0.isColocated(h1)
1375 if not collocated: return False
1376 return True
1377 return False
1378 def collectPrimitiveBases(self):
1379 """
1380 returns primitives used to construct the Surface
1381 """
1382 out=[self] + self.getSurfaceLoop().collectPrimitiveBases()
1383 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1384 return out
1385 def getBoundary(self):
1386 """
1387 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1388 """
1389 out = []+ self.getSurfaceLoop().getSurfaces()
1390 for h in self.getHoles(): out+=h.getSurfaces()
1391 return out
1392
1393 class PropertySet(PrimitiveBase):
1394 """
1395 defines a group L{PrimitiveBase} objects.
1396 """
1397 def __init__(self,tag=None,*items):
1398 super(PropertySet, self).__init__()
1399 self.__items=items
1400 self.__tag=tag
1401 def collectPrimitiveBases(self):
1402 out=[self]+self.getBoundaryLoop().collectPrimitiveBases()
1403 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1404 return out

  ViewVC Help
Powered by ViewVC 1.1.26