/[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 930 - (show annotations)
Thu Jan 18 08:12:58 2007 UTC (12 years, 8 months ago) by gross
File MIME type: text/x-python
File size: 45456 byte(s)
more tests and some modifications on SurfaceLoop
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
440 class CurveBase(Manifold1D):
441 """
442 A Curve is defined by a set of control points
443 """
444 def __init__(self):
445 """
446 create curve
447 """
448 Manifold1D.__init__(self)
449
450 def __len__(self):
451 """
452 returns the number of control points
453 """
454 return len(self.getControlPoints())
455
456 def getStartPoint(self):
457 """
458 returns start point
459 """
460 return self.getControlPoints()[0]
461
462 def getEndPoint(self):
463 """
464 returns end point
465 """
466 return self.getControlPoints()[-1]
467
468 def getControlPoints(self):
469 """
470 returns a list of the points
471 """
472 raise NotImplementedError()
473
474 class Curve(CurveBase, Primitive):
475 """
476 a curve defined through a list of control points.
477 """
478 def __init__(self,*points):
479 """
480 defines a curve form control points
481 """
482 if len(points)<2:
483 raise TypeError("Curve needs at least two points")
484 i=0
485 for p in points:
486 i+=1
487 if not isinstance(p,Point): raise TypeError("%s-th argument is not a Point object."%i)
488 self.__points=points
489 CurveBase.__init__(self)
490 Primitive.__init__(self)
491
492 def getControlPoints(self):
493 """
494 returns a list of the points
495 """
496 return self.__points
497
498 def __neg__(self):
499 """
500 returns a view onto the curve with reversed ordering
501 """
502 return ReverseCurve(self)
503
504 def substitute(self,sub_dict):
505 """
506 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
507 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
508 with substituted arguments is returned.
509 """
510 if not sub_dict.has_key(self):
511 new_p=[]
512 for p in self.getControlPoints(): new_p.append(p.substitute(sub_dict))
513 sub_dict[self]=self.__class__(*tuple(new_p))
514 return sub_dict[self]
515
516 def collectPrimitiveBases(self):
517 """
518 returns primitives used to construct the Curve
519 """
520 out=[self]
521 for p in self.getControlPoints(): out+=p.collectPrimitiveBases()
522 return out
523
524 def isColocated(self,primitive):
525 """
526 returns True curves are on the same position
527 """
528 if hasattr(primitive,"getUnderlyingPrimitive"):
529 if isinstance(primitive.getUnderlyingPrimitive(),self.__class__):
530 if len(primitive) == len(self):
531 cp0=self.getControlPoints()
532 cp1=primitive.getControlPoints()
533 match=True
534 for i in range(len(cp0)):
535 if not cp0[i].isColocated(cp1[i]):
536 match=False
537 break
538 if not match:
539 for i in range(len(cp0)):
540 if not cp0[i].isColocated(cp1[len(cp0)-1-i]):
541 return False
542 return True
543 return False
544
545 class ReverseCurve(CurveBase, ReversePrimitive):
546 """
547 a curve defined through a list of control points.
548 """
549 def __init__(self,curve):
550 """
551 defines a curve form control points
552 """
553 if not isinstance(curve, Curve):
554 raise TypeError("ReverseCurve needs to be an instance of Curve")
555 CurveBase.__init__(self)
556 ReversePrimitive.__init__(self,curve)
557
558 def getControlPoints(self):
559 """
560 returns a list of the points
561 """
562 out=[p for p in self.getUnderlyingPrimitive().getControlPoints()]
563 out.reverse()
564 return out
565
566 class Spline(Curve):
567 """
568 a spline curve defined through a list of control points.
569 """
570 def getGmshCommand(self,scaling_factor=1.):
571 """
572 returns the Gmsh command(s) to create the Curve
573 """
574 out=""
575 for i in self.getControlPoints():
576 if len(out)>0:
577 out+=", %s"%i.getDirectedID()
578 else:
579 out="%s"%i.getDirectedID()
580 return "Spline(%s) = {%s};"%(self.getID(),out)
581
582
583 class BezierCurve(Curve):
584 """
585 a Bezier curve
586 """
587 def getGmshCommand(self,scaling_factor=1.):
588 """
589 returns the Gmsh command(s) to create the Curve
590 """
591 out=""
592 for i in self.getControlPoints():
593 if len(out)>0:
594 out+=", %s"%i.getDirectedID()
595 else:
596 out="%s"%i.getDirectedID()
597 return "Bezier(%s) = {%s};"%(self.getID(),out)
598
599 class BSpline(Curve):
600 """
601 a BSpline curve. Control points may be repeated.
602 """
603 def getGmshCommand(self,scaling_factor=1.):
604 """
605 returns the Gmsh command(s) to create the Curve
606 """
607 out=""
608 for i in self.getControlPoints():
609 if len(out)>0:
610 out+=", %s"%i.getDirectedID()
611 else:
612 out="%s"%i.getDirectedID()
613 return "BSpline(%s) = {%s};"%(self.getID(),out)
614
615 class Line(Curve):
616 """
617 a line is defined by two pointDirecteds
618 """
619 def __init__(self,*points):
620 """
621 defines a line with start and end point
622 """
623 if len(points)!=2:
624 raise TypeError("Line needs two points")
625 Curve.__init__(self,*points)
626 def getGmshCommand(self,scaling_factor=1.):
627 """
628 returns the Gmsh command(s) to create the Curve
629 """
630 return "Line(%s) = {%s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getEndPoint().getDirectedID())
631
632
633 class ArcBase(Manifold1D):
634 def __init__(self):
635 """
636 create curve
637 """
638 Manifold1D.__init__(self)
639 def collectPrimitiveBases(self):
640 """
641 returns the primitives used to construct the Curve
642 """
643 out=[self]
644 out+=self.getStartPoint().collectPrimitiveBases()
645 out+=self.getEndPoint().collectPrimitiveBases()
646 out+=self.getCenterPoint().collectPrimitiveBases()
647 return out
648
649
650 def getCenterPoint(self):
651 """
652 returns center
653 """
654 raise NotImplementedError()
655
656 class Arc(ArcBase, Primitive):
657 """
658 defines an arc which is strictly, smaller than Pi
659 """
660 def __init__(self,center,start,end):
661 """
662 creates an arc by the start point, end point and center
663 """
664 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
665 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
666 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
667 # TODO: check length of circle.
668 ArcBase.__init__(self)
669 Primitive.__init__(self)
670 self.__center=center
671 self.__start=start
672 self.__end=end
673 def __neg__(self):
674 """
675 returns a view onto the curve with reversed ordering
676 """
677 return ReverseArc(self)
678
679 def getStartPoint(self):
680 """
681 returns start point
682 """
683 return self.__start
684
685 def getEndPoint(self):
686 """
687 returns end point
688 """
689 return self.__end
690
691 def getCenterPoint(self):
692 """
693 returns center
694 """
695 return self.__center
696
697 def substitute(self,sub_dict):
698 """
699 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
700 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
701 with substituted arguments is returned.
702 """
703 if not sub_dict.has_key(self):
704 sub_dict[self]=Arc(self.getCenterPoint().substitute(sub_dict),self.getStartPoint().substitute(sub_dict),self.getEndPoint().substitute(sub_dict))
705 return sub_dict[self]
706
707 def getGmshCommand(self,scaling_factor=1.):
708 """
709 returns the Gmsh command(s) to create the primitive
710 """
711 return "Circle(%s) = {%s, %s, %s};"%(self.getID(),self.getStartPoint().getDirectedID(),self.getCenterPoint().getDirectedID(),self.getEndPoint().getDirectedID())
712
713 def isColocated(self,primitive):
714 """
715 returns True curves are on the same position
716 """
717 if hasattr(primitive,"getUnderlyingPrimitive"):
718 if isinstance(primitive.getUnderlyingPrimitive(),Arc):
719 return (self.getCenterPoint().isColocated(primitive.getCenterPoint())) and ( \
720 (self.getEndPoint().isColocated(primitive.getEndPoint()) and self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
721 or (self.getEndPoint().isColocated(primitive.getStartPoint()) and self.getStartPoint().isColocated(primitive.getEndPoint()) ) )
722 return False
723
724 class ReverseArc(ArcBase, ReversePrimitive):
725 """
726 defines an arc which is strictly, smaller than Pi
727 """
728 def __init__(self,arc):
729 """
730 creates an arc by the start point, end point and center
731 """
732 if not isinstance(arc, Arc):
733 raise TypeError("ReverseCurve needs to be an instance of Arc")
734 ArcBase.__init__(self)
735 ReversePrimitive.__init__(self,arc)
736
737 def getStartPoint(self):
738 """
739 returns start point
740 """
741 return self.getUnderlyingPrimitive().getEndPoint()
742
743 def getEndPoint(self):
744 """
745 returns end point
746 """
747 return self.getUnderlyingPrimitive().getStartPoint()
748
749 def getCenterPoint(self):
750 """
751 returns center
752 """
753 return self.getUnderlyingPrimitive().getCenterPoint()
754
755 class CurveLoop(Primitive, PrimitiveBase):
756 """
757 An oriented loop of one-dimensional manifolds (= curves and arcs)
758
759 The loop must be closed and the L{Manifold1D}s should be oriented consistently.
760 """
761 def __init__(self,*curves):
762 """
763 creates a polygon from a list of line curves. The curves must form a closed loop.
764 """
765 if len(curves)<2:
766 raise TypeError("at least two curves have to be given.")
767 for i in range(len(curves)):
768 if not isinstance(curves[i],Manifold1D):
769 raise TypeError("%s-th argument is not a Manifold1D object."%i)
770 # for the curves a loop:
771 used=[ False for i in curves]
772 self.__curves=[curves[0]]
773 used[0]=True
774 while not min(used):
775 found=False
776 for i in xrange(len(curves)):
777 if not used[i]:
778 if self.__curves[-1].getEndPoint() == curves[i].getStartPoint():
779 self.__curves.append(curves[i])
780 used[i]=True
781 found=True
782 break
783 if not found:
784 raise ValueError("loop is not closed.")
785 if not self.__curves[0].getStartPoint() == self.__curves[-1].getEndPoint():
786 raise ValueError("loop is not closed.")
787 Primitive.__init__(self)
788 PrimitiveBase.__init__(self)
789
790 def getCurves(self):
791 """
792 returns the curves defining the CurveLoop
793 """
794 return self.__curves
795
796 def __neg__(self):
797 """
798 returns a view onto the curve with reversed ordering
799 """
800 return ReverseCurveLoop(self)
801
802 def __len__(self):
803 """
804 return the number of curves in the CurveLoop
805 """
806 return len(self.getCurves())
807
808
809 def collectPrimitiveBases(self):
810 """
811 returns primitives used to construct the CurveLoop
812 """
813 out=[self]
814 for c in self.getCurves(): out+=c.collectPrimitiveBases()
815 return out
816
817 def substitute(self,sub_dict):
818 """
819 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
820 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
821 with substituted arguments is returned.
822 """
823 if not sub_dict.has_key(self):
824 new_c=[]
825 for c in self.getCurves(): new_c.append(c.substitute(sub_dict))
826 sub_dict[self]=CurveLoop(*tuple(new_c))
827 return sub_dict[self]
828
829 def isColocated(self,primitive):
830 """
831 returns True if each curve is collocted with a curve in primitive
832 """
833 if hasattr(primitive,"getUnderlyingPrimitive"):
834 if isinstance(primitive.getUnderlyingPrimitive(),CurveLoop):
835 if len(primitive) == len(self):
836 cp0=self.getCurves()
837 cp1=primitive.getCurves()
838 for c0 in cp0:
839 collocated = False
840 for c1 in cp1:
841 collocated = collocated or c0.isColocated(c1)
842 if not collocated: return False
843 return True
844 return False
845
846 def getGmshCommand(self,scaling_factor=1.):
847 """
848 returns the Gmsh command(s) to create the primitive
849 """
850 out=""
851 for i in self.getCurves():
852 if len(out)>0:
853 out+=", %s"%i.getDirectedID()
854 else:
855 out="%s"%i.getDirectedID()
856 return "Line Loop(%s) = {%s};"%(self.getID(),out)
857
858 class ReverseCurveLoop(ReversePrimitive, PrimitiveBase):
859 """
860 An oriented loop of one-dimensional manifolds (= curves and arcs)
861
862 The loop must be closed and the one-dimensional manifolds should be oriented consistently.
863 """
864 def __init__(self,curve_loop):
865 """
866 creates a polygon from a list of line curves. The curves must form a closed loop.
867 """
868 if not isinstance(curve_loop, CurveLoop):
869 raise ValueError("arguments need to be an instance of CurveLoop.")
870 ReversePrimitive.__init__(self, curve_loop)
871 PrimitiveBase.__init__(self)
872
873 def getCurves(self):
874 """
875 returns the curves defining the CurveLoop
876 """
877 return [ -c for c in self.getUnderlyingPrimitive().getCurves() ]
878
879 def __len__(self):
880 return len(self.getUnderlyingPrimitive())
881
882 #=
883 class Manifold2D(PrimitiveBase):
884 """
885 general two-dimensional manifold
886 """
887 def __init__(self):
888 """
889 create a two-dimensional manifold
890 """
891 PrimitiveBase.__init__(self)
892
893 def getBoundary(self):
894 """
895 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
896 """
897 raise NotImplementedError()
898
899 class RuledSurface(Primitive, Manifold2D):
900 """
901 A ruled surface, i.e., a surface that can be interpolated using transfinite interpolation
902 """
903 def __init__(self,loop):
904 """
905 creates a ruled surface with boundary loop
906
907 @param loop: L{CurveLoop} defining the boundary of the surface.
908 """
909 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
910 raise TypeError("argument loop needs to be a CurveLoop object.")
911 if len(loop)<2:
912 raise TypeError("the loop must contain at least two Curves.")
913 if len(loop)>4:
914 raise TypeError("the loop must contain at least three Curves.")
915 Primitive.__init__(self)
916 Manifold2D.__init__(self)
917 self.__loop=loop
918
919 def __neg__(self):
920 """
921 returns a view onto the suface with reversed ordering
922 """
923 return ReverseRuledSurface(self)
924
925 def getBoundaryLoop(self):
926 """
927 returns the loop defining the outer boundary
928 """
929 return self.__loop
930
931 def getBoundary(self):
932 """
933 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
934 """
935 return self.getBoundaryLoop().getCurves()
936
937 def getGmshCommand(self,scaling_factor=1.):
938 """
939 returns the Gmsh command(s) to create the primitive
940 """
941 return "Ruled Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
942
943 def substitute(self,sub_dict):
944 """
945 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
946 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
947 with substituted arguments is returned.
948 """
949 if not sub_dict.has_key(self):
950 sub_dict[self]=RuledSurface(self.getBoundaryLoop().substitute(sub_dict))
951 return sub_dict[self]
952
953 def isColocated(self,primitive):
954 """
955 returns True if each curve is collocted with a curve in primitive
956 """
957 if hasattr(primitive,"getUnderlyingPrimitive"):
958 if isinstance(primitive.getUnderlyingPrimitive(),RuledSurface):
959 return self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop())
960 return False
961
962 def collectPrimitiveBases(self):
963 """
964 returns primitives used to construct the Surface
965 """
966 return [self] + self.getBoundaryLoop().collectPrimitiveBases()
967
968 def createRuledSurface(*curves):
969 """
970 an easier way to create a L{RuledSurface} from given curves.
971 """
972 return RuledSurface(CurveLoop(*curves))
973
974
975 class ReverseRuledSurface(ReversePrimitive, Manifold2D):
976 """
977 creates a view onto a L{RuledSurface} but with the reverse orientation
978 """
979 def __init__(self,surface):
980 """
981 creates a polygon from a list of line curves. The curves must form a closed loop.
982 """
983 if not isinstance(surface, RuledSurface):
984 raise ValueError("arguments need to be an instance of CurveLoop.")
985 ReversePrimitive.__init__(self, surface)
986 Manifold2D.__init__(self)
987
988 def getBoundaryLoop(self):
989 """
990 returns the CurveLoop defining the RuledSurface
991 """
992 return -self.getUnderlyingPrimitive().getBoundaryLoop()
993
994 def getBoundary(self):
995 """
996 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
997 """
998 return self.getBoundaryLoop().getCurves()
999 #==============================
1000 class PlaneSurface(Primitive, Manifold2D):
1001 """
1002 a plane surface with holes
1003 """
1004 def __init__(self,loop,holes=[]):
1005 """
1006 creates a plane surface with a hole
1007
1008 @param loop: L{CurveLoop} defining the boundary of the surface
1009 @param holes: list of L{CurveLoop} defining holes in the surface.
1010 @note: A CurveLoop defining a hole should not have any lines in common with the exterior CurveLoop.
1011 A CurveLoop defining a hole should not have any lines in common with another CurveLoop defining a hole in the same surface.
1012 """
1013 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1014 raise TypeError("argument loop needs to be a CurveLoop object.")
1015 for l in loop.getCurves():
1016 if not isinstance(l.getUnderlyingPrimitive(),Line):
1017 raise TypeError("loop may be formed by Lines only.")
1018 for i in range(len(holes)):
1019 if not isinstance(holes[i].getUnderlyingPrimitive(), CurveLoop):
1020 raise TypeError("%i-th hole needs to be a CurveLoop object.")
1021 for l in holes[i].getCurves():
1022 if not isinstance(l.getUnderlyingPrimitive(),Line):
1023 raise TypeError("holes may be formed by Lines only.")
1024 #TODO: check if lines and holes are in a plane
1025 #TODO: are holes really holes?
1026 Primitive.__init__(self)
1027 Manifold2D.__init__(self)
1028 self.__loop=loop
1029 self.__holes=holes
1030 def getHoles(self):
1031 """
1032 returns the holes
1033 """
1034 return self.__holes
1035
1036 def getBoundaryLoop(self):
1037 """
1038 returns the loop defining the boundary
1039 """
1040 return self.__loop
1041
1042 def getGmshCommand(self,scaling_factor=1.):
1043 """
1044 returns the Gmsh command(s) to create the primitive
1045 """
1046 out=""
1047 for i in self.getHoles():
1048 if len(out)>0:
1049 out+=", %s"%i.getDirectedID()
1050 else:
1051 out="%s"%i.getDirectedID()
1052 if len(out)>0:
1053 return "Plane Surface(%s) = {%s, %s};"%(self.getID(),self.getBoundaryLoop().getDirectedID(), out)
1054 else:
1055 return "Plane Surface(%s) = {%s};"%(self.getID(),self.getBoundaryLoop().getDirectedID())
1056
1057 def substitute(self,sub_dict):
1058 """
1059 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1060 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1061 with substituted arguments is returned.
1062 """
1063 if not sub_dict.has_key(self):
1064 sub_dict[self]=PlaneSurface(self.getBoundaryLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1065 return sub_dict[self]
1066
1067 def isColocated(self,primitive):
1068 """
1069 returns True if each curve is collocted with a curve in primitive
1070 """
1071 if hasattr(primitive,"getUnderlyingPrimitive"):
1072 if isinstance(primitive.getUnderlyingPrimitive(),PlaneSurface):
1073 if self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop()):
1074 hs0=self.getHoles()
1075 hs1=primitive.getHoles()
1076 if len(hs0) == len(hs1):
1077 for h0 in hs0:
1078 collocated = False
1079 for h1 in hs1:
1080 collocated = collocated or h0.isColocated(h1)
1081 if not collocated: return False
1082 return True
1083 return False
1084 def collectPrimitiveBases(self):
1085 """
1086 returns primitives used to construct the Surface
1087 """
1088 out=[self] + self.getBoundaryLoop().collectPrimitiveBases()
1089 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1090 return out
1091 def __neg__(self):
1092 """
1093 returns a view onto the curve with reversed ordering
1094 """
1095 return ReversePlaneSurface(self)
1096 def getBoundary(self):
1097 """
1098 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1099 """
1100 out = []+ self.getBoundaryLoop().getCurves()
1101 for h in self.getHoles(): out+=h.getCurves()
1102 return out
1103
1104 class ReversePlaneSurface(ReversePrimitive, Manifold2D):
1105 """
1106 creates a view onto a L{PlaneSurface} but with the reverse orientation
1107 """
1108 def __init__(self,surface):
1109 """
1110 creates a polygon from a list of line curves. The curves must form a closed loop.
1111 """
1112 if not isinstance(surface, PlaneSurface):
1113 raise ValueError("arguments need to be an instance of PlaneSurface.")
1114 ReversePrimitive.__init__(self, surface)
1115 Manifold2D.__init__(self)
1116
1117 def getBoundaryLoop(self):
1118 """
1119 returns the CurveLoop defining the RuledSurface
1120 """
1121 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1122
1123 def getHoles(self):
1124 """
1125 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1126 """
1127 return [ -h for h in self.getUnderlyingPrimitive().getHoles() ]
1128
1129 def getBoundary(self):
1130 """
1131 returns a list of the one-dimensional manifolds forming the boundary of the Surface (including holes)
1132 """
1133 out = [] + self.getBoundaryLoop().getCurves()
1134 for h in self.getHoles(): out+=h.getCurves()
1135 return out
1136
1137
1138 #=========================================================================
1139 class SurfaceLoop(Primitive, PrimitiveBase):
1140 """
1141 a loop of 2D primitives. It defines the shell of a volume.
1142
1143 The loop must represent a closed shell, and the primitives should be oriented consistently.
1144 """
1145 def __init__(self,*surfaces):
1146 """
1147 creates a surface loop
1148 """
1149 if len(surfaces)<2:
1150 raise TypeError("at least two surfaces have to be given.")
1151 for i in range(len(surfaces)):
1152 if not isinstance(surfaces[i].getUnderlyingPrimitive(),Manifold2D):
1153 raise TypeError("%s-th argument is not a Manifold2D object."%i)
1154 Primitive.__init__(self)
1155 PrimitiveBase.__init__(self)
1156 # for the curves a loop:
1157 used=[ False for s in surfaces]
1158 self.__surfaces=[surfaces[0]]
1159 used[0]= True
1160 edges=[ e for e in surfaces[0].getBoundary() ]
1161 used_edges=[ False for e in surfaces[0].getBoundary() ]
1162 while not min(used):
1163 found=False
1164 for i in xrange(len(surfaces)):
1165 if not used[i]:
1166 i_boundary=surfaces[i].getBoundary()
1167 print i, i_boundary
1168 for ib in xrange(len(i_boundary)):
1169 print ib, i_boundary[ib], edges
1170 if i_boundary[ib] in edges:
1171 if used_edges[edges.index(i_boundary[ib])]:
1172 raise ValueError("boundary segment %s is shared by more than one surface."%str(i_boundary[ib]))
1173 used_edges[edges.index(i_boundary[ib])]=True
1174 self.__surfaces.append(surfaces[i])
1175 for b in i_boundary:
1176 if not b in edges:
1177 edges.append(b)
1178 used_edges.append(False)
1179 found=True
1180 used[i]=True
1181 break
1182 if found: break
1183 if not found:
1184 raise ValueError("loop is not closed.")
1185 print min(used_edges), used_edges
1186 if min(used_edges):
1187 raise ValueError("loop is not closed. Surface is missing.")
1188 def __len__(self):
1189 """
1190 return the number of curves in the SurfaceLoop
1191 """
1192 return len(self.__surfaces)
1193
1194 def __neg__(self):
1195 """
1196 returns a view onto the curve with reversed ordering
1197 """
1198 return ReverseSurfaceLoop(self)
1199
1200 def getSurfaces(self):
1201 """
1202 returns the surfaces defining the SurfaceLoop
1203 """
1204 return self.__surfaces
1205
1206 def collectPrimitiveBases(self):
1207 """
1208 returns primitives used to construct the SurfaceLoop
1209 """
1210 out=[self]
1211 for c in self.getSurfaces(): out+=c.collectPrimitiveBases()
1212 return out
1213
1214 def getGmshCommand(self,scaling_factor=1.):
1215 """
1216 returns the Gmsh command(s) to create the primitive
1217 """
1218 out=""
1219 for i in self.getSurfaces():
1220 if len(out)>0:
1221 out+=", %s"%i.getDirectedID()
1222 else:
1223 out="%s"%i.getDirectedID()
1224 return "Surface Loop(%s) = {%s};"%(self.getID(),out)
1225 def substitute(self,sub_dict):
1226 """
1227 returns a copy of self with substitutes for the primitives used to construct it given by the dictionary C{sub_dict}.
1228 If a substitute for the object is given by C{sub_dict} the value is returned, otherwise a new instance
1229 with substituted arguments is returned.
1230 """
1231 if not sub_dict.has_key(self):
1232 new_s=[]
1233 for s in self.getCurves(): new_s.append(s.substitute(sub_dict))
1234 sub_dict[self]=SurfaceLoop(*tuple(new_s))
1235 return sub_dict[self]
1236
1237 def isColocated(self,primitive):
1238 """
1239 returns True if each surface is collocted with a curve in primitive and vice versa.
1240 """
1241 if hasattr(primitive,"getUnderlyingPrimitive"):
1242 if isinstance(primitive.getUnderlyingPrimitive(),SurfaceLoop):
1243 if len(primitive) == len(self):
1244 sp0=self.getSurfaces()
1245 sp1=primitive.getCurves()
1246 for s0 in sp0:
1247 collocated = False
1248 for s1 in sp1:
1249 collocated = collocated or s0.isColocated(s1)
1250 if not collocated: return False
1251 return True
1252 return False
1253
1254 class ReverseSurfaceLoop(ReversePrimitive, PrimitiveBase):
1255 """
1256 a view to SurfaceLoop with reverse orientaion
1257
1258 The loop must represent a closed shell, and the primitives should be oriented consistently.
1259 An oriented loop of 2-dimensional manifolds (= RuledSurface, PlaneSurface)
1260
1261 The loop must be closed and the one-dimensional manifolds should be oriented consistently.
1262 """
1263 def __init__(self,surface_loop):
1264 """
1265 creates a polygon from a list of line surfaces. The curves must form a closed loop.
1266 """
1267 if not isinstance(surface_loop, SurfaceLoop):
1268 raise ValueError("arguments need to be an instance of SurfaceLoop.")
1269 ReversePrimitive.__init__(self, surface_loop)
1270 PrimitiveBase.__init__(self)
1271
1272 def getSurfaces(self):
1273 """
1274 returns the surfaces defining the SurfaceLoop
1275 """
1276 return [ -s for s in self.getUnderlyingPrimitive().getSurfaces() ]
1277
1278 def __len__(self):
1279 return len(self.getUnderlyingPrimitive())
1280 #==========================
1281 class Volume(PrimitiveBase):
1282 """
1283 a volume with holes.
1284 """
1285 def __init__(self,loop,holes=[]):
1286 """
1287 creates a volume
1288
1289 @param loop: L{SurfaceLoop} defining the boundary of the surface
1290 @param holes: list of L{SurfaceLoop} defining holes in the surface.
1291 @note: A SurfaceLoop defining a hole should not have any surfaces in common with the exterior SurfaceLoop.
1292 A SurfaceLoop defining a hole should not have any surfaces in common with another SurfaceLoop defining a hole in the same volume.
1293 """
1294 super(Volume, self).__init__()
1295 if not loop.isSurfaceLoop():
1296 raise TypeError("argument loop needs to be a SurfaceLoop object.")
1297 for i in range(len(holes)):
1298 if not holes[i].isSurfaceLoop():
1299 raise TypeError("%i th hole needs to be a SurfaceLoop object.")
1300 self.__loop=loop
1301 self.__holes=holes
1302 def getHoles(self):
1303 return self.__holes
1304 def getSurfaceLoop(self):
1305 return self.__loop
1306 def __add__(self,other):
1307 return Volume(self.getSurfaceLoop()+other, holes=[h+other for h in self.getHoles()])
1308 def collectPrimitiveBases(self):
1309 out=[self] + self.getSurfaceLoop().collectPrimitiveBases()
1310 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1311 return out
1312 def getConstructionPoints(self):
1313 out=self.getSurfaceLoop().getConstructionPoints()
1314 for i in self.getHoles(): out|=i.Points()
1315 return out
1316 def getGmshCommand(self,scaling_factor=1.):
1317 """
1318 returns the Gmsh command(s) to create the primitive
1319 """
1320 out=""
1321 for i in self.getHoles():
1322 if len(out)>0:
1323 out+=", %s"%i.getDirectedID()
1324 else:
1325 out="%s"%i.getDirectedID()
1326 if len(out)>0:
1327 return "Volume(%s) = {%s, %s};"%(self.getID(),self.getSurfaceLoop().getDirectedID(), out)
1328 else:
1329 return "Volume(%s) = {%s};"%(self.getID(),self.getSurfaceLoop().getDirectedID())
1330
1331 class PropertySet(PrimitiveBase):
1332 """
1333 defines a group L{PrimitiveBase} objects.
1334 """
1335 def __init__(self,tag=None,*items):
1336 super(PropertySet, self).__init__()
1337 self.__items=items
1338 self.__tag=tag
1339 def collectPrimitiveBases(self):
1340 out=[self]+self.getBoundaryLoop().collectPrimitiveBases()
1341 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1342 return out
1343
1344 class PrimitiveBaseStack(object):
1345 def __init__(self,*items):
1346 self.__prims=set()
1347 for i in items:
1348 self.__prims|=i.getPrimitives()
1349 self.__prims=list(self.__prims)
1350 self.__prims.sort()
1351
1352 def getGmshCommands(self,scaling_factor=1.):
1353 out=""
1354 for i in self.__prims:
1355 out+=i.getGmshCommand(scaling_factor)+"\n"
1356 return out

  ViewVC Help
Powered by ViewVC 1.1.26