/[escript]/branches/stage3.1/pycad/py_src/primitives.py
ViewVC logotype

Contents of /branches/stage3.1/pycad/py_src/primitives.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2945 - (show annotations)
Wed Feb 24 00:17:46 2010 UTC (8 years, 11 months ago) by jfenwick
File MIME type: text/x-python
File size: 67631 byte(s)
Bringing release stage up to trunk version 2944

1 # -*- coding: utf-8 -*-
2
3 ########################################################
4 #
5 # Copyright (c) 2003-2010 by University of Queensland
6 # Earth Systems Science Computational Center (ESSCC)
7 # http://www.uq.edu.au/esscc
8 #
9 # Primary Business: Queensland, Australia
10 # Licensed under the Open Software License version 3.0
11 # http://www.opensource.org/licenses/osl-3.0.php
12 #
13 ########################################################
14
15 __copyright__="""Copyright (c) 2003-2010 by University of Queensland
16 Earth Systems Science Computational Center (ESSCC)
17 http://www.uq.edu.au/esscc
18 Primary Business: Queensland, Australia"""
19 __license__="""Licensed under the Open Software License version 3.0
20 http://www.opensource.org/licenses/osl-3.0.php"""
21 __url__="https://launchpad.net/escript-finley"
22
23 """
24 Geometrical Primitives
25
26 the concept is inspired by gmsh and very much focused on the fact that
27 the classes are used to wrk with gmsh.
28
29 :var __author__: name of author
30 :var __copyright__: copyrights
31 :var __license__: licence agreement
32 :var __url__: url entry point on documentation
33 :var __version__: version
34 :var __date__: date of the version
35 """
36
37 __author__="Lutz Gross, l.gross@uq.edu.au"
38
39 try:
40 import numpy
41 numpyImported=True
42 except:
43 numpyImported=False
44
45 import numpy
46 from transformations import _TYPE, Translation, Dilation, Transformation, DEG
47 import math
48
49
50 def resetGlobalPrimitiveIdCounter():
51 """
52 Initializes the global primitive ID counter.
53 """
54 global global_primitive_id_counter
55 global_primitive_id_counter=1
56
57 def setToleranceForColocation(tol=1.e-11):
58 """
59 Sets the global tolerance for colocation checks to ``tol``.
60 """
61 global global_tolerance_for_colocation
62 global_tolerance_for_colocation=tol
63
64 def getToleranceForColocation():
65 """
66 Returns the global tolerance for colocation checks.
67 """
68 return global_tolerance_for_colocation
69
70 resetGlobalPrimitiveIdCounter()
71 setToleranceForColocation()
72
73
74 class PrimitiveBase(object):
75 """
76 Template for a set of primitives.
77 """
78 def __init__(self):
79 """
80 Initializes the PrimitiveBase instance object.
81 """
82 pass
83
84 def __cmp__(self,other):
85 """
86 Compares object with other by comparing the absolute value of the ID.
87 """
88 if isinstance(other, PrimitiveBase):
89 return cmp(self.getID(),other.getID())
90 else:
91 return -1
92
93 def getConstructionPoints(self):
94 """
95 Returns the points used to construct the primitive.
96 """
97 out=[]
98 for i in self.getPrimitives():
99 if isinstance(i,Point): out.append(i)
100 return out
101
102 def getPrimitives(self):
103 """
104 Returns a list of primitives used to construct the primitive with no
105 double entries.
106 """
107 out=[]
108 for p in self.collectPrimitiveBases():
109 if not p in out: out.append(p)
110 return out
111
112 def copy(self):
113 """
114 Returns a deep copy of the object.
115 """
116 return self.substitute({})
117
118 def modifyBy(self,transformation):
119 """
120 Modifies the coordinates by applying a transformation.
121 """
122 for p in self.getConstructionPoints(): p.modifyBy(transformation)
123
124 def __add__(self,other):
125 """
126 Returns a new object shifted by ``other``.
127 """
128 return self.apply(Translation(numpy.array(other,_TYPE)))
129
130 def __sub__(self,other):
131 """
132 Returns a new object shifted by ``-other``.
133 """
134 return self.apply(Translation(-numpy.array(other,_TYPE)))
135
136 def __iadd__(self,other):
137 """
138 Shifts the point inplace by ``other``.
139 """
140 self.modifyBy(Translation(numpy.array(other,_TYPE)))
141 return self
142
143 def __isub__(self,other):
144 """
145 Shifts the point inplace by ``-other``.
146 """
147 self.modifyBy(Translation(-numpy.array(other,_TYPE)))
148 return self
149
150 def __imul__(self,other):
151 """
152 Modifies object by applying `Transformation` ``other``. If ``other``
153 is not a `Transformation` it is first tried to be converted.
154 """
155 if isinstance(other,int) or isinstance(other,float):
156 trafo=Dilation(other)
157 elif isinstance(other,numpy.ndarray):
158 trafo=Translation(other)
159 elif isinstance(other,Transformation):
160 trafo=other
161 else:
162 raise TypeError, "cannot convert argument to a Transformation class object."
163 self.modifyBy(trafo)
164 return self
165
166 def __rmul__(self,other):
167 """
168 Applies `Transformation` ``other`` to object. If ``other`` is not a
169 `Transformation` it is first tried to be converted.
170 """
171 if isinstance(other,int) or isinstance(other,float):
172 trafo=Dilation(other)
173 elif isinstance(other,numpy.ndarray):
174 trafo=Translation(other)
175 elif isinstance(other,Transformation):
176 trafo=other
177 else:
178 raise TypeError, "cannot convert argument to Transformation class object."
179 return self.apply(trafo)
180
181
182 def setLocalScale(self,factor=1.):
183 """
184 Sets the local refinement factor.
185 """
186 for p in self.getConstructionPoints(): p.setLocalScale(factor)
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
197 class Primitive(object):
198 """
199 Class that represents a general primitive.
200 """
201 def __init__(self):
202 """
203 Initializes the Primitive instance object with a unique ID.
204 """
205 global global_primitive_id_counter
206 self.__ID=global_primitive_id_counter
207 global_primitive_id_counter+=1
208
209 def getID(self):
210 """
211 Returns the primitive ID.
212 """
213 return self.__ID
214
215 def getDirectedID(self):
216 """
217 Returns the primitive ID where a negative sign means that reversed
218 ordering is used.
219 """
220 return self.getID()
221
222 def __repr__(self):
223 return "%s(%s)"%(self.__class__.__name__,self.getID())
224
225 def getUnderlyingPrimitive(self):
226 """
227 Returns the underlying primitive.
228 """
229 return self
230
231 def hasSameOrientation(self,other):
232 """
233 Returns True if ``other`` is the same primitive and has the same
234 orientation, False otherwise.
235 """
236 return self == other and isinstance(other,Primitive)
237
238 def __neg__(self):
239 """
240 Returns a view onto the curve with reversed ordering.
241
242 :note: This method is overwritten by subclasses.
243 """
244 raise NotImplementedError("__neg__ is not implemented.")
245
246 def substitute(self,sub_dict):
247 """
248 Returns a copy of self with substitutes for the primitives used to
249 construct it given by the dictionary ``sub_dict``. If a substitute for
250 the object is given by ``sub_dict`` the value is returned, otherwise a
251 new instance with substituted arguments is returned.
252
253 :note: This method is overwritten by subclasses.
254 """
255 raise NotImplementedError("substitute is not implemented.")
256
257 def collectPrimitiveBases(self):
258 """
259 Returns a list of primitives used to construct the primitive. It may
260 contain primitives twice.
261
262 :note: This method is overwritten by subclasses.
263 """
264 raise NotImplementedError("collectPrimitiveBases is not implemented.")
265
266 def isColocated(self,primitive):
267 """
268 Rreturns True if the two primitives are located at the same position.
269
270 :note: This method is overwritten by subclasses.
271 """
272 raise NotImplementedError("isColocated is not implemented.")
273 def isReversed(self):
274 """
275 returns True is the primitive is a reversed primitive.
276 """
277 return False
278
279
280 class ReversePrimitive(object):
281 """
282 A view onto a primitive creating a reverse orientation.
283 """
284 def __init__(self,primitive):
285 """
286 Instantiates a view onto ``primitive``.
287 """
288 if not isinstance(primitive, Primitive):
289 raise ValueError("argument needs to be a Primitive class object.")
290 self.__primitive=primitive
291
292 def getID(self):
293 """
294 Returns the primitive ID.
295 """
296 return self.__primitive.getID()
297
298 def getUnderlyingPrimitive(self):
299 """
300 Returns the underlying primitive.
301 """
302 return self.__primitive
303
304 def hasSameOrientation(self,other):
305 """
306 Returns True if ``other`` is the same primitive and has the same
307 orientation as self.
308 """
309 return self == other and isinstance(other, ReversePrimitive)
310
311 def __repr__(self):
312 return "-%s(%s)"%(self.__primitive.__class__.__name__,self.getID())
313
314 def getDirectedID(self):
315 """
316 Returns the primitive ID where a negative signs means that reversed
317 ordering is used.
318 """
319 return -self.__primitive.getID()
320
321 def substitute(self,sub_dict):
322 """
323 Returns a copy of self with substitutes for the primitives used to
324 construct it given by the dictionary ``sub_dict``. If a substitute for
325 the object is given by ``sub_dict`` the value is returned, otherwise a
326 new instance with substituted arguments is returned.
327 """
328 if not sub_dict.has_key(self):
329 sub_dict[self]=-self.getUnderlyingPrimitive().substitute(sub_dict)
330 return sub_dict[self]
331
332 def __neg__(self):
333 """
334 Returns a view onto the curve with reversed ordering.
335 """
336 return self.__primitive
337
338 def collectPrimitiveBases(self):
339 """
340 Returns a list of primitives used to construct the primitive. It may
341 contain primitives twice.
342 """
343 return self.__primitive.collectPrimitiveBases()
344
345 def isColocated(self,primitive):
346 """
347 Returns True if the two primitives are located at the same position.
348
349 :note: This method is overwritten by subclasses.
350 """
351 return self.__primitive.isColocated(primitive)
352
353 def isReversed(self):
354 """
355 returns True is the primitive is a reversed primitive.
356 """
357 return True
358
359 class Point(Primitive, PrimitiveBase):
360 """
361 A three-dimensional point.
362 """
363 def __init__(self,x=0.,y=0.,z=0.,local_scale=1.):
364 """
365 Creates a point with coordinates ``x``, ``y``, ``z`` with the local
366 refinement factor ``local_scale``. If ``x`` is a list or similar it needs to have
367 length less or equal 3. In this case ``y`` and ``z`` are overwritten by
368 ``x[1]`` and ``x[2]``.
369 """
370 PrimitiveBase.__init__(self)
371 Primitive.__init__(self)
372 try:
373 l=len(x)
374 if l>3:
375 raise ValueError,"x has a lanegth bigger than 3."
376 if l>1:
377 y=x[1]
378 else:
379 y=0.
380 if l>2:
381 z=x[2]
382 else:
383 z=0.
384 if l>0:
385 x=x[0]
386 else:
387 x=0.
388 except TypeError:
389 pass
390 a=numpy.array([x,y,z], _TYPE)
391 self.setCoordinates(a)
392 self.setLocalScale(local_scale)
393
394 def setLocalScale(self,factor=1.):
395 """
396 Sets the local refinement factor.
397 """
398 if factor<=0.:
399 raise ValueError("scaling factor must be positive.")
400 self.__local_scale=factor
401
402 def getLocalScale(self):
403 """
404 Returns the local refinement factor.
405 """
406 return self.__local_scale
407
408 def getCoordinates(self):
409 """
410 Returns the coodinates of the point as a ``numpy.ndarray`` object.
411 """
412 return self._x
413
414 def setCoordinates(self,x):
415 """
416 Sets the coodinates of the point from a ``numpy.ndarray`` object ``x``.
417 """
418 if not isinstance(x, numpy.ndarray):
419 self._x=numpy.array(x,_TYPE)
420 else:
421 self._x=x
422
423 def collectPrimitiveBases(self):
424 """
425 Returns primitives used to construct the primitive.
426 """
427 return [self]
428
429 def isColocated(self,primitive):
430 """
431 Returns True if the `Point` ``primitive`` is colocated (has the same
432 coordinates) with self. That is, if
433 *|self - primitive| <= tol * max(\|self\|,|primitive|)*.
434 """
435 if isinstance(primitive,Point):
436 primitive=primitive.getCoordinates()
437 c=self.getCoordinates()
438 d=c-primitive
439 if numpyImported:
440 return numpy.dot(d,d)<=getToleranceForColocation()**2*max(numpy.dot(c,c),numpy.dot(primitive,primitive))
441 else:
442 return numpy.dot(d,d)<=getToleranceForColocation()**2*max(numpy.dot(c,c),numpy.dot(primitive,primitive))
443 else:
444 return False
445
446 def substitute(self,sub_dict):
447 """
448 Returns a copy of self with substitutes for the primitives used to
449 construct it given by the dictionary ``sub_dict``. If a substitute for
450 the object is given by ``sub_dict`` the value is returned, otherwise a
451 new instance with substituted arguments is returned.
452 """
453 if not sub_dict.has_key(self):
454 c=self.getCoordinates()
455 sub_dict[self]=Point(c[0],c[1],c[2],local_scale=self.getLocalScale())
456 return sub_dict[self]
457
458 def modifyBy(self,transformation):
459 """
460 Modifies the coordinates by applying the given transformation.
461 """
462 self.setCoordinates(transformation(self.getCoordinates()))
463
464 def __neg__(self):
465 """
466 Returns a view of the object with reverse orientation. As a point has
467 no direction the object itself is returned.
468 """
469 return self
470
471 class Manifold1D(PrimitiveBase):
472 """
473 General one-dimensional manifold in 1D defined by a start and end point.
474 """
475 def __init__(self):
476 """
477 Initializes the one-dimensional manifold.
478 """
479 PrimitiveBase.__init__(self)
480 self.__apply_elements=False
481
482 def getStartPoint(self):
483 """
484 Returns the start point.
485 """
486 raise NotImplementedError()
487
488 def getEndPoint(self):
489 """
490 Returns the end point.
491 """
492 raise NotImplementedError()
493
494 def getBoundary(self):
495 """
496 Returns a list of the zero-dimensional manifolds forming the boundary
497 of the curve.
498 """
499 return [ self.getStartPoint(), self.getEndPoint()]
500
501
502 def setElementDistribution(self,n,progression=1,createBump=False):
503 """
504 Defines the number of elements on the line. If set it overwrites the local length setting which would be applied.
505 The progression factor ``progression`` defines the change of element size between naighboured elements. If ``createBump`` is set
506 progression is applied towards the center of the line.
507
508 :param n: number of elements on the line
509 :type n: ``int``
510 :param progression: a positive progression factor
511 :type progression: positive ``float``
512 :param createBump: of elements on the line
513 :type createBump: ``bool``
514 """
515 if isinstance(self, ReversePrimitive):
516 self.getUnderlyingPrimitive().setElementDistribution(n,progression,createBump)
517 else:
518 if n<1:
519 raise ValueError,"number of elements must be positive."
520 if progression<=0:
521 raise ValueError,"progression factor must be positive."
522 self.__apply_elements=True
523 self.__n=n
524 self.__progression_factor=progression
525 self.__createBump=createBump
526
527 def resetElementDistribution(self):
528 """
529 removes the a previously set element distribution from the line.
530 """
531 if isinstance(self, ReversePrimitive):
532 self.getUnderlyingPrimitive().resetElementDistribution()
533 else:
534 self.__apply_elements=False
535
536 def getElementDistribution(self):
537 """
538 Returns the element distribution.
539
540 :return: the tuple of the number of elements, the progression factor and the bump flag. If no element distribution is set ``None`` is returned
541 :rtype: ``tuple``
542 """
543 if isinstance(self, ReversePrimitive):
544 return self.getUnderlyingPrimitive().getElementDistribution()
545 else:
546 if self.__apply_elements:
547 return (self.__n, self.__progression_factor, self.__createBump)
548 else:
549 return None
550
551 class CurveBase(Manifold1D):
552 """
553 Base class for curves. A Curve is defined by a set of control points.
554 """
555 def __init__(self):
556 """
557 Initializes the curve.
558 """
559 Manifold1D.__init__(self)
560
561 def __len__(self):
562 """
563 Returns the number of control points.
564 """
565 return len(self.getControlPoints())
566
567 def getStartPoint(self):
568 """
569 Returns the start point.
570 """
571 return self.getControlPoints()[0]
572
573 def getEndPoint(self):
574 """
575 Returns the end point.
576 """
577 return self.getControlPoints()[-1]
578
579 def getControlPoints(self):
580 """
581 Returns a list of the points.
582 """
583 raise NotImplementedError()
584
585 class Curve(CurveBase, Primitive):
586 """
587 A curve defined through a list of control points.
588 """
589 def __init__(self,*points):
590 """
591 Defines a curve from control points given by ``points``.
592 """
593 if len(points)==1:
594 points=points[0]
595 if not hasattr(points,'__iter__'): raise ValueError("Curve needs at least two points")
596 if len(points)<2:
597 raise ValueError("Curve needs at least two points")
598 i=0
599 for p in points:
600 i+=1
601 if not isinstance(p,Point): raise TypeError("%s-th argument is not a Point object."%i)
602 self.__points=points
603 CurveBase.__init__(self)
604 Primitive.__init__(self)
605
606 def getControlPoints(self):
607 """
608 Returns a list of the points.
609 """
610 return self.__points
611
612 def __neg__(self):
613 """
614 Returns a view onto the curve with reversed ordering.
615 """
616 return ReverseCurve(self)
617
618 def substitute(self,sub_dict):
619 """
620 Returns a copy of self with substitutes for the primitives used to
621 construct it given by the dictionary ``sub_dict``. If a substitute for
622 the object is given by ``sub_dict`` the value is returned, otherwise a
623 new instance with substituted arguments is returned.
624 """
625 if not sub_dict.has_key(self):
626 new_p=[]
627 for p in self.getControlPoints(): new_p.append(p.substitute(sub_dict))
628 sub_dict[self]=self.__class__(*tuple(new_p))
629 return sub_dict[self]
630
631 def collectPrimitiveBases(self):
632 """
633 Returns the primitives used to construct the curve.
634 """
635 out=[self]
636 for p in self.getControlPoints(): out+=p.collectPrimitiveBases()
637 return out
638
639 def isColocated(self,primitive):
640 """
641 Returns True if curves are at the same position.
642 """
643 if hasattr(primitive,"getUnderlyingPrimitive"):
644 if isinstance(primitive.getUnderlyingPrimitive(),self.__class__):
645 if len(primitive) == len(self):
646 cp0=self.getControlPoints()
647 cp1=primitive.getControlPoints()
648 match=True
649 for i in range(len(cp0)):
650 if not cp0[i].isColocated(cp1[i]):
651 match=False
652 break
653 if not match:
654 for i in range(len(cp0)):
655 if not cp0[i].isColocated(cp1[len(cp0)-1-i]):
656 return False
657 return True
658 return False
659
660 class ReverseCurve(CurveBase, ReversePrimitive):
661 """
662 A curve defined through a list of control points.
663 """
664 def __init__(self,curve):
665 """
666 Defines a curve from control points.
667 """
668 if not isinstance(curve, Curve):
669 raise TypeError("ReverseCurve needs to be an instance of Curve")
670 CurveBase.__init__(self)
671 ReversePrimitive.__init__(self,curve)
672
673 def getControlPoints(self):
674 """
675 Returns a list of the points.
676 """
677 out=[p for p in self.getUnderlyingPrimitive().getControlPoints()]
678 out.reverse()
679 return tuple(out)
680
681 class Spline(Curve):
682 """
683 A spline curve defined through a list of control points.
684 """
685 pass
686
687 class BezierCurve(Curve):
688 """
689 A Bezier curve.
690 """
691 pass
692
693 class BSpline(Curve):
694 """
695 A BSpline curve. Control points may be repeated.
696 """
697 pass
698
699 class Line(Curve):
700 """
701 A line is defined by two points.
702 """
703 def __init__(self,*points):
704 """
705 Defines a line with start and end point.
706 """
707 if len(points)!=2:
708 raise TypeError("Line needs two points")
709 Curve.__init__(self,*points)
710
711 class ArcBase(Manifold1D):
712 """
713 Base class for arcs.
714 """
715 def __init__(self):
716 """
717 Initializes the arc.
718 """
719 Manifold1D.__init__(self)
720
721 def collectPrimitiveBases(self):
722 """
723 Returns the primitives used to construct the Arc.
724 """
725 out=[self]
726 out+=self.getStartPoint().collectPrimitiveBases()
727 out+=self.getEndPoint().collectPrimitiveBases()
728 out+=self.getCenterPoint().collectPrimitiveBases()
729 return out
730
731 def getCenterPoint(self):
732 """
733 Returns the center.
734 """
735 raise NotImplementedError()
736
737 class Arc(ArcBase, Primitive):
738 """
739 Defines an arc which is strictly smaller than pi.
740 """
741 def __init__(self,center,start,end):
742 """
743 Creates an arc defined by the start point, end point and center.
744 """
745 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
746 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
747 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
748 if center.isColocated(end): raise TypeError("center and start point are colocated.")
749 if center.isColocated(start): raise TypeError("center end end point are colocated.")
750 if start.isColocated(end): raise TypeError("start and end are colocated.")
751 # TODO: check length of circle.
752 ArcBase.__init__(self)
753 Primitive.__init__(self)
754 self.__center=center
755 self.__start=start
756 self.__end=end
757
758 def __neg__(self):
759 """
760 Returns a view onto the curve with reversed ordering.
761 """
762 return ReverseArc(self)
763
764 def getStartPoint(self):
765 """
766 Returns the start point.
767 """
768 return self.__start
769
770 def getEndPoint(self):
771 """
772 Returns the end point.
773 """
774 return self.__end
775
776 def getCenterPoint(self):
777 """
778 Returns the center point.
779 """
780 return self.__center
781
782 def substitute(self,sub_dict):
783 """
784 Returns a copy of self with substitutes for the primitives used to
785 construct it given by the dictionary ``sub_dict``. If a substitute for
786 the object is given by ``sub_dict`` the value is returned, otherwise a
787 new instance with substituted arguments is returned.
788 """
789 if not sub_dict.has_key(self):
790 sub_dict[self]=Arc(self.getCenterPoint().substitute(sub_dict),self.getStartPoint().substitute(sub_dict),self.getEndPoint().substitute(sub_dict))
791 return sub_dict[self]
792
793 def isColocated(self,primitive):
794 """
795 Returns True if curves are at the same position.
796 """
797 if hasattr(primitive,"getUnderlyingPrimitive"):
798 if isinstance(primitive.getUnderlyingPrimitive(),Arc):
799 return (self.getCenterPoint().isColocated(primitive.getCenterPoint())) and ( \
800 (self.getEndPoint().isColocated(primitive.getEndPoint()) and self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
801 or (self.getEndPoint().isColocated(primitive.getStartPoint()) and self.getStartPoint().isColocated(primitive.getEndPoint()) ) )
802 return False
803
804 class ReverseArc(ArcBase, ReversePrimitive):
805 """
806 Defines an arc which is strictly smaller than pi.
807 """
808 def __init__(self,arc):
809 """
810 Creates an arc defined by the start point, end point and center.
811 """
812 if not isinstance(arc, Arc):
813 raise TypeError("ReverseCurve needs to be an instance of Arc")
814 ArcBase.__init__(self)
815 ReversePrimitive.__init__(self,arc)
816
817 def getStartPoint(self):
818 """
819 Returns the start point.
820 """
821 return self.getUnderlyingPrimitive().getEndPoint()
822
823 def getEndPoint(self):
824 """
825 Returns the end point.
826 """
827 return self.getUnderlyingPrimitive().getStartPoint()
828
829 def getCenterPoint(self):
830 """
831 Returns the center point.
832 """
833 return self.getUnderlyingPrimitive().getCenterPoint()
834
835 class EllipseBase(Manifold1D):
836 """
837 Base class for ellipses.
838 """
839 def __init__(self):
840 """
841 Initializes the ellipse.
842 """
843 Manifold1D.__init__(self)
844
845 def collectPrimitiveBases(self):
846 """
847 Returns the primitives used to construct the ellipse.
848 """
849 out=[self]
850 out+=self.getStartPoint().collectPrimitiveBases()
851 out+=self.getEndPoint().collectPrimitiveBases()
852 out+=self.getCenterPoint().collectPrimitiveBases()
853 out+=self.getPointOnMainAxis().collectPrimitiveBases()
854 return out
855
856 class Ellipse(EllipseBase, Primitive):
857 """
858 Defines an ellipse which is strictly smaller than pi.
859 """
860 def __init__(self,center,point_on_main_axis,start,end):
861 """
862 Creates an ellipse defined by the start point, end point, the center
863 and a point on the main axis.
864 """
865 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
866 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
867 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
868 if not isinstance(point_on_main_axis,Point): raise TypeError("point on main axis needs to be a Point object.")
869 if center.isColocated(end): raise TypeError("center and start point are colocated.")
870 if center.isColocated(start): raise TypeError("center end end point are colocated.")
871 if center.isColocated(point_on_main_axis): raise TypeError("center and point on main axis are colocated.")
872 if start.isColocated(end): raise TypeError("start and end point are colocated.")
873 # TODO: check length of circle.
874 EllipseBase.__init__(self)
875 Primitive.__init__(self)
876 self.__center=center
877 self.__start=start
878 self.__end=end
879 self.__point_on_main_axis=point_on_main_axis
880
881 def __neg__(self):
882 """
883 Returns a view onto the curve with reversed ordering.
884 """
885 return ReverseEllipse(self)
886
887 def getStartPoint(self):
888 """
889 Returns the start point.
890 """
891 return self.__start
892
893 def getEndPoint(self):
894 """
895 Returns the end point.
896 """
897 return self.__end
898
899 def getCenterPoint(self):
900 """
901 Returns the center.
902 """
903 return self.__center
904
905 def getPointOnMainAxis(self):
906 """
907 Returns a point on the main axis.
908 """
909 return self.__point_on_main_axis
910
911 def substitute(self,sub_dict):
912 """
913 Returns a copy of self with substitutes for the primitives used to
914 construct it given by the dictionary ``sub_dict``. If a substitute for
915 the object is given by ``sub_dict`` the value is returned, otherwise a
916 new instance with substituted arguments is returned.
917 """
918 if not sub_dict.has_key(self):
919 sub_dict[self]=Ellipse(self.getCenterPoint().substitute(sub_dict),
920 self.getPointOnMainAxis().substitute(sub_dict),
921 self.getStartPoint().substitute(sub_dict),
922 self.getEndPoint().substitute(sub_dict))
923 return sub_dict[self]
924
925
926 def isColocated(self,primitive):
927 """
928 Returns True if curves are at the same position.
929 """
930 if hasattr(primitive,"getUnderlyingPrimitive"):
931 if isinstance(primitive.getUnderlyingPrimitive(),Ellipse):
932 self_c=self.getCenterPoint().getCoordinates()
933 p=self.getPointOnMainAxis().getCoordinates()-self_c
934 q=primitive.getPointOnMainAxis().getCoordinates()-self_c
935 # are p and q orthogonal or collinear?
936 len_p=math.sqrt(p[0]**2+p[1]**2+p[2]**2)
937 len_q=math.sqrt(q[0]**2+q[1]**2+q[2]**2)
938 p_q= abs(p[0]*q[0]+p[1]*q[1]+p[2]*q[2])
939 return ((p_q <= getToleranceForColocation() * len_q * p_q) or \
940 (abs(p_q - len_q * p_q) <= getToleranceForColocation())) and \
941 self.getCenterPoint().isColocated(primitive.getCenterPoint()) and \
942 ( \
943 (self.getEndPoint().isColocated(primitive.getEndPoint()) and \
944 self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
945 or \
946 (self.getEndPoint().isColocated(primitive.getStartPoint()) and \
947 self.getStartPoint().isColocated(primitive.getEndPoint())) \
948 )
949 return False
950
951 class ReverseEllipse(EllipseBase, ReversePrimitive):
952 """
953 Defines an ellipse which is strictly smaller than pi.
954 """
955 def __init__(self,arc):
956 """
957 Creates an instance of a reverse view to an ellipse.
958 """
959 if not isinstance(arc, Ellipse):
960 raise TypeError("ReverseCurve needs to be an instance of Ellipse")
961 EllipseBase.__init__(self)
962 ReversePrimitive.__init__(self,arc)
963
964 def getStartPoint(self):
965 """
966 Returns the start point.
967 """
968 return self.getUnderlyingPrimitive().getEndPoint()
969
970 def getEndPoint(self):
971 """
972 Returns the end point.
973 """
974 return self.getUnderlyingPrimitive().getStartPoint()
975
976 def getCenterPoint(self):
977 """
978 Returns the center point.
979 """
980 return self.getUnderlyingPrimitive().getCenterPoint()
981
982 def getPointOnMainAxis(self):
983 """
984 Returns a point on the main axis.
985 """
986 return self.getUnderlyingPrimitive().getPointOnMainAxis()
987
988
989 class CurveLoop(Primitive, PrimitiveBase):
990 """
991 An oriented loop of one-dimensional manifolds (= curves and arcs).
992
993 The loop must be closed and the `Manifold1D` s should be oriented
994 consistently.
995 """
996 def __init__(self,*curves):
997 """
998 Creates a polygon from a list of line curves. The curves must form a
999 closed loop.
1000 """
1001 if len(curves)==1:
1002 curves=curves[0]
1003 if not hasattr(curves,'__iter__'): raise ValueError("CurveLoop needs at least two points")
1004 if len(curves)<2:
1005 raise ValueError("At least two curves have to be given.")
1006 for i in range(len(curves)):
1007 if not isinstance(curves[i],Manifold1D):
1008 raise TypeError("%s-th argument is not a Manifold1D object."%i)
1009 # for the curves a loop:
1010 #used=[ False for i in curves]
1011 self.__curves=[]
1012 for c in curves:
1013 if not c in self.__curves: self.__curves.append(c)
1014 Primitive.__init__(self)
1015 PrimitiveBase.__init__(self)
1016
1017
1018 def getCurves(self):
1019 """
1020 Returns the curves defining the CurveLoop.
1021 """
1022 return self.__curves
1023
1024 def __neg__(self):
1025 """
1026 Returns a view onto the curve with reversed ordering.
1027 """
1028 return ReverseCurveLoop(self)
1029
1030 def __len__(self):
1031 """
1032 Returns the number of curves in the CurveLoop.
1033 """
1034 return len(self.getCurves())
1035
1036 def collectPrimitiveBases(self):
1037 """
1038 Returns primitives used to construct the CurveLoop.
1039 """
1040 out=[self]
1041 for c in self.getCurves(): out+=c.collectPrimitiveBases()
1042 return out
1043
1044 def substitute(self,sub_dict):
1045 """
1046 Returns a copy of self with substitutes for the primitives used to
1047 construct it given by the dictionary ``sub_dict``. If a substitute for
1048 the object is given by ``sub_dict`` the value is returned, otherwise a
1049 new instance with substituted arguments is returned.
1050 """
1051 if not sub_dict.has_key(self):
1052 new_c=[]
1053 for c in self.getCurves(): new_c.append(c.substitute(sub_dict))
1054 sub_dict[self]=CurveLoop(*tuple(new_c))
1055 return sub_dict[self]
1056
1057 def isColocated(self,primitive):
1058 """
1059 Returns True if each curve is colocated with a curve in ``primitive``.
1060 """
1061 if hasattr(primitive,"getUnderlyingPrimitive"):
1062 if isinstance(primitive.getUnderlyingPrimitive(),CurveLoop):
1063 if len(primitive) == len(self):
1064 cp0=self.getCurves()
1065 cp1=primitive.getCurves()
1066 for c0 in cp0:
1067 colocated = False
1068 for c1 in cp1:
1069 colocated = colocated or c0.isColocated(c1)
1070 if not colocated: return False
1071 return True
1072 return False
1073
1074
1075
1076
1077 class ReverseCurveLoop(ReversePrimitive, PrimitiveBase):
1078 """
1079 An oriented loop of one-dimensional manifolds (= curves and arcs).
1080
1081 The loop must be closed and the one-dimensional manifolds should be
1082 oriented consistently.
1083 """
1084 def __init__(self,curve_loop):
1085 """
1086 Creates a polygon from a list of line curves. The curves must form a
1087 closed loop.
1088 """
1089 if not isinstance(curve_loop, CurveLoop):
1090 raise TypeError("arguments need to be an instance of CurveLoop.")
1091 ReversePrimitive.__init__(self, curve_loop)
1092 PrimitiveBase.__init__(self)
1093
1094 def getCurves(self):
1095 """
1096 Returns the curves defining the CurveLoop.
1097 """
1098 return [ -c for c in self.getUnderlyingPrimitive().getCurves() ]
1099
1100 def __len__(self):
1101 return len(self.getUnderlyingPrimitive())
1102 #=
1103 class Manifold2D(PrimitiveBase):
1104 """
1105 General two-dimensional manifold.
1106
1107 :ivar LEFT: left element orientation when meshing with transifinite meshing
1108 :ivar RIGHT: right element orientation when meshing with transifinite meshing
1109 :ivar ALTERNATE: alternate element orientation when meshing with transifinite meshing
1110 """
1111 LEFT="Left"
1112 RIGHT="Right"
1113 ALTERNATE="Alternate"
1114 def __init__(self):
1115 """
1116 Creates a two-dimensional manifold.
1117 """
1118 PrimitiveBase.__init__(self)
1119 self.__transfinitemeshing=False
1120 self.__recombination_angle=None
1121
1122 def getBoundary(self):
1123 """
1124 Returns a list of the one-dimensional manifolds forming the boundary
1125 of the surface (including holes).
1126 """
1127 raise NotImplementedError()
1128
1129 def hasHole(self):
1130 """
1131 Returns True if a hole is present.
1132 """
1133 raise NotImplementedError()
1134
1135 def setElementDistribution(self,n,progression=1,createBump=False):
1136 """
1137 Defines the number of elements on the lines
1138
1139 :param n: number of elements on the line
1140 :type n: ``int``
1141 :param progression: a positive progression factor
1142 :type progression: positive ``float``
1143 :param createBump: of elements on the line
1144 :type createBump: ``bool``
1145 """
1146 for i in self.getBoundary(): i.setElementDistribution(n,progression,createBump)
1147
1148 def getPoints(self):
1149 """
1150 returns a list of points used to define the boundary
1151
1152 :return: list of points used to define the boundary
1153 :rtype: ``list`` of `Point` s
1154 """
1155 out=[]
1156 boundary=self.getBoundary()
1157 for l in boundary:
1158 for p in l.getBoundary():
1159 if not p in out: out.append(p)
1160 return out
1161
1162 def setRecombination(self, max_deviation=45*DEG):
1163 """
1164 Recombines triangular meshes on the surface into mixed triangular/quadrangular meshes.
1165 ``max_deviation`` specifies the maximum derivation of the largest angle in the quadrangle
1166 from the right angle. Use ``max_deviation``==``None`` to switch off recombination.
1167
1168 :param max_deviation: maximum derivation of the largest angle in the quadrangle from the right angle.
1169 :type max_deviation: ``float`` or ``None``.
1170 """
1171 if isinstance(self, ReversePrimitive):
1172 self.getUnderlyingPrimitive().setRecombination(max_deviation)
1173 else:
1174 if not max_deviation==None:
1175 if max_deviation<=0:
1176 raise ValueError, "max_deviation must be positive."
1177 if max_deviation/DEG>=90:
1178 raise ValueError, "max_deviation must be smaller than 90 DEG"
1179 self.__recombination_angle=max_deviation
1180
1181 def getRecombination(self):
1182 """
1183 returns max deviation from right angle in the recombination algorithm
1184
1185 :return: max deviation from right angle in the recombination algorithm. If recombination is switched off, ``None`` is returned.
1186 :rtype: ``float`` or ``None``
1187 """
1188 if isinstance(self, ReversePrimitive):
1189 return self.getUnderlyingPrimitive().getRecombination()
1190 else:
1191 return self.__recombination_angle
1192
1193 def setTransfiniteMeshing(self,orientation="Left"):
1194 """
1195 applies 2D transfinite meshing to the surface.
1196
1197 :param orientation: sets the orientation of the triangles. It is only relevant if recombination is not used.
1198 :type orientation: `Manifold2D.LEFT`, `Manifold2D.RIGHT`, `Manifold2D.ALTERNATE`
1199 :note: Transfinite meshing can not be applied if holes are present.
1200 """
1201 if isinstance(self, ReversePrimitive):
1202 return self.getUnderlyingPrimitive().setTransfiniteMeshing(orientation)
1203 else:
1204 if not orientation in [ Manifold2D.LEFT, Manifold2D.RIGHT, Manifold2D.ALTERNATE]:
1205 raise ValueError,"invalid orientation %s."%orientation
1206 if self.hasHole():
1207 raise ValueError,"transfinite meshing cannot be appled to surfaces with a hole."
1208 b=self.getBoundary()
1209 if len(b)>4 or len(b)<3:
1210 raise ValueError,"transfinite meshing permits 3 or 4 boundary lines only."
1211 for l in b:
1212 if l.getElementDistribution() == None: raise ValueError,"transfinite meshing requires element distribution on all boundary lines."
1213 start=b[0]
1214 opposite=None
1215 top=None
1216 bottom=None
1217 for l in b[1:]:
1218 if l.getEndPoint() == start.getStartPoint():
1219 bottom=l
1220 elif l.getStartPoint() == start.getEndPoint():
1221 top=l
1222 else:
1223 opposite=l
1224 if top==None or bottom == None:
1225 raise ValueError,"transfinite meshing cannot be applied to boundary is not closed. Most likely the orientation of some boundray segments is wrong."
1226 if opposite == None: # three sides only
1227 if not top.getElementDistribution()[0] == bottom.getElementDistribution()[0]: start, top, bottom= bottom, start, top
1228 if not top.getElementDistribution() == bottom.getElementDistribution():
1229 raise ValueError,"transfinite meshing requires opposite faces to be have the same element distribution."
1230 if not opposite == None:
1231 if not start.getElementDistribution()[0] == opposite.getElementDistribution()[0]:
1232 raise ValueError,"transfinite meshing requires oposite faces to be have the same element distribution."
1233 if opposite == None:
1234 if bottom.getEndPoint == top.getStartPoint():
1235 raise ValueError,"cannot identify corner proints for transfinite meshing."
1236 else:
1237 points=[ bottom.getStartPoint(), bottom.getEndPoint(), top.getStartPoint() ]
1238 else:
1239 points=[ bottom.getStartPoint(), bottom.getEndPoint(), top.getStartPoint(), top.getEndPoint() ]
1240 self.__points=points
1241 self.__orientation=orientation
1242 self.__transfinitemeshing=True
1243
1244 def resetTransfiniteMeshing(self):
1245 """
1246 removes the transfinite meshing from the surface
1247 """
1248 if isinstance(self, ReversePrimitive):
1249 self.getUnderlyingPrimitive().resetTransfiniteMeshing()
1250 else:
1251 self.__transfinitemeshing=False
1252
1253 def getTransfiniteMeshing(self):
1254 """
1255 returns the transfinite meshing setings. If transfinite meshing is not set, ``None`` is returned.
1256
1257 :return: a tuple of the tuple of points used to define the transfinite meshing and the orientation. If no points are set the points tuple is returned as ``None``. If no transfinite meshing is not set, ``None`` is returned.
1258 :rtype: ``tuple`` of a ``tuple`` of `Point` s (or ``None``) and the orientation which is one of the values `Manifold2D.LEFT` , `Manifold2D.RIGHT` , `Manifold2D.ALTERNATE`
1259 """
1260 if isinstance(self, ReversePrimitive):
1261 return self.getUnderlyingPrimitive().getTransfiniteMeshing()
1262 else:
1263 if self.__transfinitemeshing:
1264 return (self.__points, self.__orientation)
1265 else:
1266 return None
1267 def getPolygon(self):
1268 """
1269 Returns a list of start/end points of the 1D mainfold form the loop. If not closed and exception is thrown.
1270 """
1271 curves=self.getBoundary()
1272 s=[curves[0].getStartPoint(), curves[0].getEndPoint()]
1273 found= [ curves[0] ]
1274 restart=True
1275 while restart:
1276 restart=False
1277 for k in curves:
1278 if not k in found:
1279 if k.getStartPoint() == s[-1]:
1280 found.append(k)
1281 if k.getEndPoint() == s[0]:
1282 if len(found) == len(curves):
1283 return s
1284 else:
1285 raise ValueError,"loop %s is not closed."%self.getID()
1286 s.append(k.getEndPoint())
1287 restart=True
1288 break
1289 if not restart:
1290 raise ValueError,"loop %s is not closed."%self.getID()
1291 class RuledSurface(Primitive, Manifold2D):
1292 """
1293 A ruled surface, i.e. a surface that can be interpolated using transfinite
1294 interpolation.
1295 """
1296 def __init__(self,loop):
1297 """
1298 Creates a ruled surface with boundary ``loop``.
1299
1300 :param loop: `CurveLoop` defining the boundary of the surface.
1301 """
1302 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1303 raise TypeError("argument loop needs to be a CurveLoop object.")
1304 if len(loop)<2:
1305 raise ValueError("the loop must contain at least two Curves.")
1306 if len(loop)>4:
1307 raise ValueError("the loop must contain at most four Curves.")
1308 Primitive.__init__(self)
1309 Manifold2D.__init__(self)
1310 self.__loop=loop
1311
1312 def hasHole(self):
1313 """
1314 Returns True if a hole is present.
1315 """
1316 return False
1317
1318 def __neg__(self):
1319 """
1320 Returns a view onto the suface with reversed ordering.
1321 """
1322 return ReverseRuledSurface(self)
1323
1324 def getBoundaryLoop(self):
1325 """
1326 Returns the loop defining the outer boundary.
1327 """
1328 return self.__loop
1329
1330 def getBoundary(self):
1331 """
1332 Returns a list of the one-dimensional manifolds forming the boundary
1333 of the Surface (including holes).
1334 """
1335 return self.getBoundaryLoop().getCurves()
1336
1337 def substitute(self,sub_dict):
1338 """
1339 Returns a copy of self with substitutes for the primitives used to
1340 construct it given by the dictionary ``sub_dict``. If a substitute for
1341 the object is given by ``sub_dict`` the value is returned, otherwise a
1342 new instance with substituted arguments is returned.
1343 """
1344 if not sub_dict.has_key(self):
1345 sub_dict[self]=RuledSurface(self.getBoundaryLoop().substitute(sub_dict))
1346 return sub_dict[self]
1347
1348 def isColocated(self,primitive):
1349 """
1350 Returns True if each curve is colocated with a curve in ``primitive``.
1351 """
1352 if hasattr(primitive,"getUnderlyingPrimitive"):
1353 if isinstance(primitive.getUnderlyingPrimitive(),RuledSurface):
1354 return self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop())
1355 return False
1356
1357 def collectPrimitiveBases(self):
1358 """
1359 Returns primitives used to construct the Surface.
1360 """
1361 return [self] + self.getBoundaryLoop().collectPrimitiveBases()
1362
1363 def createRuledSurface(*curves):
1364 """
1365 An easier way to create a `RuledSurface` from given curves.
1366 """
1367 return RuledSurface(CurveLoop(*curves))
1368
1369
1370 class ReverseRuledSurface(ReversePrimitive, Manifold2D):
1371 """
1372 Creates a view onto a `RuledSurface` but with reverse orientation.
1373 """
1374 def __init__(self,surface):
1375 """
1376 Creates a polygon from a list of line curves. The curves must form a
1377 closed loop.
1378 """
1379 if not isinstance(surface, RuledSurface):
1380 raise TypeError("arguments need to be an instance of CurveLoop.")
1381 ReversePrimitive.__init__(self, surface)
1382 Manifold2D.__init__(self)
1383
1384 def getBoundaryLoop(self):
1385 """
1386 Returns the CurveLoop defining the ReverseRuledSurface.
1387 """
1388 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1389
1390 def getBoundary(self):
1391 """
1392 Returns a list of the one-dimensional manifolds forming the boundary
1393 of the Surface (including holes).
1394 """
1395 return self.getBoundaryLoop().getCurves()
1396
1397 def hasHole(self):
1398 """
1399 Returns True if a hole is present.
1400 """
1401 return False
1402
1403 #==============================
1404 class PlaneSurface(Primitive, Manifold2D):
1405 """
1406 A plane surface with holes.
1407 """
1408 def __init__(self,loop,holes=[]):
1409 """
1410 Creates a plane surface with holes.
1411
1412 :param loop: `CurveLoop` defining the boundary of the surface
1413 :param holes: list of `CurveLoop` s defining holes in the surface
1414 :note: A CurveLoop defining a hole should not have any lines in common
1415 with the exterior CurveLoop.
1416 :note: A CurveLoop defining a hole should not have any lines in common
1417 with another CurveLoop defining a hole in the same surface.
1418 """
1419 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1420 raise TypeError("argument loop needs to be a CurveLoop object.")
1421 for i in range(len(holes)):
1422 if not isinstance(holes[i].getUnderlyingPrimitive(), CurveLoop):
1423 raise TypeError("%i-th hole needs to be a CurveLoop object.")
1424 #TODO: check if lines and holes are in a plane
1425 #TODO: are holes really holes?
1426 Primitive.__init__(self)
1427 Manifold2D.__init__(self)
1428 self.__loop=loop
1429 self.__holes=holes
1430
1431 def hasHole(self):
1432 """
1433 Returns True if a hole is present.
1434 """
1435 return len(self.getHoles())>0
1436
1437 def getHoles(self):
1438 """
1439 Returns the holes.
1440 """
1441 return self.__holes
1442
1443 def getBoundaryLoop(self):
1444 """
1445 Returns the loop defining the boundary.
1446 """
1447 return self.__loop
1448
1449 def substitute(self,sub_dict):
1450 """
1451 Returns a copy of self with substitutes for the primitives used to
1452 construct it given by the dictionary ``sub_dict``. If a substitute for
1453 the object is given by ``sub_dict`` the value is returned, otherwise a
1454 new instance with substituted arguments is returned.
1455 """
1456 if not sub_dict.has_key(self):
1457 sub_dict[self]=PlaneSurface(self.getBoundaryLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1458 return sub_dict[self]
1459
1460 def isColocated(self,primitive):
1461 """
1462 Returns True if each curve is colocated with a curve in ``primitive``.
1463 """
1464 if hasattr(primitive,"getUnderlyingPrimitive"):
1465 if isinstance(primitive.getUnderlyingPrimitive(),PlaneSurface):
1466 if self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop()):
1467 hs0=self.getHoles()
1468 hs1=primitive.getHoles()
1469 if len(hs0) == len(hs1):
1470 for h0 in hs0:
1471 colocated = False
1472 for h1 in hs1:
1473 colocated = colocated or h0.isColocated(h1)
1474 if not colocated: return False
1475 return True
1476 return False
1477
1478 def collectPrimitiveBases(self):
1479 """
1480 Returns primitives used to construct the Surface.
1481 """
1482 out=[self] + self.getBoundaryLoop().collectPrimitiveBases()
1483 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1484 return out
1485
1486 def __neg__(self):
1487 """
1488 Returns a view onto the curve with reversed ordering.
1489 """
1490 return ReversePlaneSurface(self)
1491
1492 def getBoundary(self):
1493 """
1494 Returns a list of the one-dimensional manifolds forming the boundary
1495 of the Surface (including holes).
1496 """
1497 out = []+ self.getBoundaryLoop().getCurves()
1498 for h in self.getHoles(): out+=h.getCurves()
1499 return out
1500
1501 class ReversePlaneSurface(ReversePrimitive, Manifold2D):
1502 """
1503 Creates a view onto a `PlaneSurface` but with reverse orientation.
1504 """
1505 def __init__(self,surface):
1506 """
1507 Creates a polygon from a `PlaneSurface`.
1508 """
1509 if not isinstance(surface, PlaneSurface):
1510 raise TypeError("arguments need to be an instance of PlaneSurface.")
1511 ReversePrimitive.__init__(self, surface)
1512 Manifold2D.__init__(self)
1513
1514 def getBoundaryLoop(self):
1515 """
1516 Returns the CurveLoop defining the ReversePlaneSurface.
1517 """
1518 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1519
1520 def getHoles(self):
1521 """
1522 Returns the holes.
1523 """
1524 return [ -h for h in self.getUnderlyingPrimitive().getHoles() ]
1525
1526 def getBoundary(self):
1527 """
1528 Returns a list of the one-dimensional manifolds forming the boundary
1529 of the Surface (including holes).
1530 """
1531 out = [] + self.getBoundaryLoop().getCurves()
1532 for h in self.getHoles(): out+=h.getCurves()
1533 return out
1534
1535 def hasHole(self):
1536 """
1537 Returns True if a hole is present.
1538 """
1539 return len(self.getHoles())>0
1540
1541 #=========================================================================
1542 class SurfaceLoop(Primitive, PrimitiveBase):
1543 """
1544 A loop of 2D primitives which defines the shell of a volume.
1545
1546 The loop must represent a closed shell, and the primitives should be
1547 oriented consistently.
1548 """
1549 def __init__(self,*surfaces):
1550 """
1551 Creates a surface loop.
1552 """
1553 if len(surfaces)==1:
1554 surfaces=surfaces[0]
1555 if not hasattr(surfaces,'__iter__'): raise ValueError("SurfaceLoop needs at least two points")
1556 if len(surfaces)<2:
1557 raise ValueError("at least two surfaces have to be given.")
1558 for i in range(len(surfaces)):
1559 if not isinstance(surfaces[i].getUnderlyingPrimitive(),Manifold2D):
1560 raise TypeError("%s-th argument is not a Manifold2D object."%i)
1561 self.__surfaces=list(surfaces)
1562 Primitive.__init__(self)
1563 PrimitiveBase.__init__(self)
1564
1565 def __len__(self):
1566 """
1567 Returns the number of curves in the SurfaceLoop.
1568 """
1569 return len(self.__surfaces)
1570
1571 def __neg__(self):
1572 """
1573 Returns a view onto the curve with reversed ordering.
1574 """
1575 return ReverseSurfaceLoop(self)
1576
1577 def getSurfaces(self):
1578 """
1579 Returns the surfaces defining the SurfaceLoop.
1580 """
1581 return self.__surfaces
1582
1583 def collectPrimitiveBases(self):
1584 """
1585 Returns primitives used to construct the SurfaceLoop.
1586 """
1587 out=[self]
1588 for c in self.getSurfaces(): out+=c.collectPrimitiveBases()
1589 return out
1590
1591 def substitute(self,sub_dict):
1592 """
1593 Returns a copy of self with substitutes for the primitives used to
1594 construct it given by the dictionary ``sub_dict``. If a substitute for
1595 the object is given by ``sub_dict`` the value is returned, otherwise a
1596 new instance with substituted arguments is returned.
1597 """
1598 if not sub_dict.has_key(self):
1599 new_s=[]
1600 for s in self.getSurfaces(): new_s.append(s.substitute(sub_dict))
1601 sub_dict[self]=SurfaceLoop(*tuple(new_s))
1602 return sub_dict[self]
1603
1604 def isColocated(self,primitive):
1605 """
1606 Returns True if each surface is colocated with a curve in ``primitive``
1607 and vice versa.
1608 """
1609 if hasattr(primitive,"getUnderlyingPrimitive"):
1610 if isinstance(primitive.getUnderlyingPrimitive(),SurfaceLoop):
1611 if len(primitive) == len(self):
1612 sp0=self.getSurfaces()
1613 sp1=primitive.getSurfaces()
1614 for s0 in sp0:
1615 colocated = False
1616 for s1 in sp1:
1617 colocated = colocated or s0.isColocated(s1)
1618 if not colocated: return False
1619 return True
1620 return False
1621
1622 class ReverseSurfaceLoop(ReversePrimitive, PrimitiveBase):
1623 """
1624 A view of a SurfaceLoop with reverse orientation.
1625
1626 The loop must represent a closed shell and the primitives should be
1627 oriented consistently.
1628 """
1629 def __init__(self,surface_loop):
1630 """
1631 Creates a polygon from a list of line surfaces. The curves must form
1632 a closed loop.
1633 """
1634 if not isinstance(surface_loop, SurfaceLoop):
1635 raise TypeError("arguments need to be an instance of SurfaceLoop.")
1636 ReversePrimitive.__init__(self, surface_loop)
1637 PrimitiveBase.__init__(self)
1638
1639 def getSurfaces(self):
1640 """
1641 Returns the surfaces defining the SurfaceLoop.
1642 """
1643 return [ -s for s in self.getUnderlyingPrimitive().getSurfaces() ]
1644
1645 def __len__(self):
1646 return len(self.getUnderlyingPrimitive())
1647
1648 #==============================
1649 class Manifold3D(PrimitiveBase):
1650 """
1651 General three-dimensional manifold.
1652 """
1653 def __init__(self):
1654 """
1655 Creates a three-dimensional manifold.
1656 """
1657 PrimitiveBase.__init__(self)
1658 self.__transfinitemeshing=False
1659
1660 def getBoundary(self):
1661 """
1662 Returns a list of the 2-dimensional manifolds forming the boundary
1663 of the volume (including holes).
1664 """
1665 raise NotImplementedError()
1666
1667 def setElementDistribution(self,n,progression=1,createBump=False):
1668 """
1669 Defines the number of elements on the lines and surfaces
1670
1671 :param n: number of elements on the line
1672 :type n: ``int``
1673 :param progression: a positive progression factor
1674 :type progression: positive ``float``
1675 :param createBump: of elements on the line
1676 :type createBump: ``bool``
1677 """
1678 for i in self.getBoundary(): i.setElementDistribution(n,progression,createBump)
1679
1680 def setRecombination(self, max_deviation=45*DEG):
1681 """
1682 Recombines triangular meshes on all surface into mixed triangular/quadrangular meshes. These meshes
1683 are then used to generate the volume mesh if possible. Recombination requires 3D transfinite meshing.
1684
1685 ``max_deviation`` specifies the maximum derivation of the largest angle in the quadrangle
1686 from the right angle. Use ``max_deviation``==``None`` to switch off recombination.
1687
1688 :param max_deviation: maximum derivation of the largest angle in the quadrangle from the right angle.
1689 :type max_deviation: ``float`` or ``None``.
1690 """
1691 if not max_deviation==None:
1692 if max_deviation<=0:
1693 raise ValueError, "max_deviation must be positive."
1694 if max_deviation/DEG>=90:
1695 raise ValueError, "max_deviation must be smaller than 90 DEG"
1696 for i in self.getBoundary(): i.setRecombination(max_deviation)
1697 self.setTransfiniteMeshing()
1698
1699 def setTransfiniteMeshing(self,orientation="Left"):
1700 """
1701 applies 3D transfinite meshing to the volume and all surface. It requires transfinite meshing on all faces which will be enforced (except
1702 if ``orientation`` is equal to ``None``.
1703
1704 :param orientation: sets the orientation of the triangles on the surfaces. It is only relevant if recombination is not used.
1705 If orientation is equal to ``None``, the transinite meshing is not applied to the surfaces but must be set by the user.
1706 :type orientation: `Manifold2D.LEFT`, `Manifold2D.RIGHT`, `Manifold2D.ALTERNATE`
1707 :note: Transfinite meshing can not be applied if holes are present.
1708 :note: only five or six surfaces may be used.
1709 :warning: The functionality of transfinite meshing without recombination is not entirely clear in `gmsh`. So please apply this method with care.
1710 """
1711 if isinstance(self, ReversePrimitive):
1712 return self.getUnderlyingPrimitive().setTransfiniteMeshing(orientation)
1713 else:
1714 if not orientation == None:
1715 if not orientation in [ Manifold2D.LEFT, Manifold2D.RIGHT, Manifold2D.ALTERNATE]:
1716 raise ValueError,"invalid orientation %s."%orientation
1717
1718 if self.hasHole():
1719 raise ValueError,"transfinite meshing cannot be appled to surfaces with a hole."
1720 b=self.getBoundary()
1721 # find a face with 3/4 Points:
1722 if len(b) == 6 :
1723 des_len=4
1724 elif len(b) == 5:
1725 des_len=3
1726 else:
1727 raise ValueError,"transfinite meshing permits 5 or 6 surface only."
1728 # start_b=None
1729 # for l in b:
1730 # if len(l.getPolygon()) == des_len:
1731 # start_b = l
1732 # break
1733 # if start_b == None:
1734 # raise ValueError,"Expect face with %s points."%des_len
1735 # start_poly=start_b.getPolygon()
1736 # now we need to find the opposite face:
1737 # opposite = None
1738 # for l in b:
1739 # if all( [ not k in start_poly for k in l.getPolygon() ]):
1740 # opposite = l
1741 # break
1742 # if opposite == None:
1743 # raise ValueError,"Unable to find face for transfinite interpolation."
1744 # opposite_poly=opposite.getPolygon()
1745 # if not len(opposite_poly) == des_len:
1746 # raise ValueError,"Unable to find face for transfinite interpolation."
1747 # this needs more work to find the points!!!!
1748 points = []
1749 self.__points=points
1750 if not orientation == None:
1751 for i in b: i.setTransfiniteMeshing(orientation)
1752 self.__transfinitemeshing=True
1753
1754 def resetTransfiniteMeshing(self):
1755 """
1756 removes the transfinite meshing from the volume but not from the surfaces
1757 """
1758 if isinstance(self, ReversePrimitive):
1759 self.getUnderlyingPrimitive().resetTransfiniteMeshing()
1760 else:
1761 self.__transfinitemeshing=False
1762
1763 def getTransfiniteMeshing(self):
1764 """
1765 returns the transfinite meshing setings. If transfinite meshing is not set, ``None`` is returned.
1766
1767 :return: a tuple of the tuple of points used to define the transfinite meshing and the orientation. If no points are set the points tuple is returned as ``None``. If no transfinite meshing is not set, ``None`` is returned.
1768 :rtype: ``tuple`` of a ``tuple`` of `Point` s (or ``None``) and the orientation which is one of the values `Manifold2D.LEFT` , `Manifold2D.RIGHT` , `Manifold2D.ALTERNATE`
1769 """
1770 if isinstance(self, ReversePrimitive):
1771 return self.getUnderlyingPrimitive().getTransfiniteMeshing()
1772 else:
1773 if self.__transfinitemeshing:
1774 return self.__points
1775 else:
1776 return None
1777
1778 class Volume(Manifold3D, Primitive):
1779 """
1780 A volume with holes.
1781 """
1782 def __init__(self,loop,holes=[]):
1783 """
1784 Creates a volume with holes.
1785
1786 :param loop: `SurfaceLoop` defining the boundary of the surface
1787 :param holes: list of `SurfaceLoop` defining holes in the surface
1788 :note: A SurfaceLoop defining a hole should not have any surfaces in
1789 common with the exterior SurfaceLoop.
1790 :note: A SurfaceLoop defining a hole should not have any surfaces in
1791 common with another SurfaceLoop defining a hole in the same
1792 volume.
1793 """
1794 if not isinstance(loop.getUnderlyingPrimitive(), SurfaceLoop):
1795 raise TypeError("argument loop needs to be a SurfaceLoop object.")
1796 for i in range(len(holes)):
1797 if not isinstance(holes[i].getUnderlyingPrimitive(), SurfaceLoop):
1798 raise TypeError("%i th hole needs to be a SurfaceLoop object.")
1799 Primitive.__init__(self)
1800 Manifold3D.__init__(self)
1801 self.__loop=loop
1802 self.__holes=holes
1803 self.__transfinitemeshing=False
1804
1805 def getHoles(self):
1806 """
1807 Returns the holes in the volume.
1808 """
1809 return self.__holes
1810
1811 def getSurfaceLoop(self):
1812 """
1813 Returns the loop forming the surface.
1814 """
1815 return self.__loop
1816
1817 def substitute(self,sub_dict):
1818 """
1819 Returns a copy of self with substitutes for the primitives used to
1820 construct it given by the dictionary ``sub_dict``. If a substitute for
1821 the object is given by ``sub_dict`` the value is returned, otherwise a
1822 new instance with substituted arguments is returned.
1823 """
1824 if not sub_dict.has_key(self):
1825 sub_dict[self]=Volume(self.getSurfaceLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1826 return sub_dict[self]
1827
1828 def isColocated(self,primitive):
1829 """
1830 Returns True if each curve is colocated with a curve in ``primitive``.
1831 """
1832 if hasattr(primitive,"getUnderlyingPrimitive"):
1833 if isinstance(primitive.getUnderlyingPrimitive(),Volume):
1834 if self.getSurfaceLoop().isColocated(primitive.getSurfaceLoop()):
1835 hs0=self.getHoles()
1836 hs1=primitive.getHoles()
1837 if len(hs0) == len(hs1):
1838 for h0 in hs0:
1839 colocated = False
1840 for h1 in hs1:
1841 colocated = colocated or h0.isColocated(h1)
1842 if not colocated: return False
1843 return True
1844 return False
1845
1846 def collectPrimitiveBases(self):
1847 """
1848 Returns primitives used to construct the surface.
1849 """
1850 out=[self] + self.getSurfaceLoop().collectPrimitiveBases()
1851 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1852 return out
1853
1854 def getBoundary(self):
1855 """
1856 Returns a list of the 2-dimensional manifolds forming the surface of the Volume (including holes).
1857 """
1858 out = []+ self.getSurfaceLoop().getSurfaces()
1859 for h in self.getHoles(): out+=h.getSurfaces()
1860 return out
1861
1862 def hasHole(self):
1863 """
1864 Returns True if a hole is present.
1865 """
1866 return len(self.getHoles())>0
1867 class PropertySet(Primitive, PrimitiveBase):
1868 """
1869 Defines a group of `Primitive` s which can be accessed through a name.
1870 """
1871 def __init__(self,name,*items):
1872 Primitive.__init__(self)
1873 self.__dim=None
1874 self.clearItems()
1875 self.addItem(*items)
1876 self.setName(name)
1877
1878 def getDim(self):
1879 """
1880 Returns the dimensionality of the items.
1881 """
1882 if self.__dim == None:
1883 items=self.getItems()
1884 if len(items)>0:
1885 if isinstance(items[0] ,Manifold1D):
1886 self.__dim=1
1887 elif isinstance(items[0] ,Manifold2D):
1888 self.__dim=2
1889 elif isinstance(items[0] ,Manifold3D):
1890 self.__dim=3
1891 else:
1892 self.__dim=0
1893 return self.__dim
1894
1895 def __repr__(self):
1896 """
1897 Returns a string representation.
1898 """
1899 return "%s(%s)"%(self.getName(),self.getID())
1900
1901 def getManifoldClass(self):
1902 """
1903 Returns the manifold class expected from items.
1904 """
1905 d=self.getDim()
1906 if d == None:
1907 raise ValueError("undefined spatial diemnsion.")
1908 else:
1909 if d==0:
1910 return Point
1911 elif d==1:
1912 return Manifold1D
1913 elif d==2:
1914 return Manifold2D
1915 else:
1916 return Manifold3D
1917
1918 def getName(self):
1919 """
1920 Returns the name of the set.
1921 """
1922 return self.__name
1923
1924 def setName(self,name):
1925 """
1926 Sets the name.
1927 """
1928 self.__name=str(name)
1929
1930 def addItems(self,*items):
1931 """
1932 Adds items. An item my be any `Primitive` but no `PropertySet`.
1933 """
1934 self.addItem(*items)
1935
1936 def addItem(self,*items):
1937 """
1938 Adds items. An item my be any `Primitive` but no `PropertySet`.
1939 """
1940 for i in items:
1941 if not (isinstance(i, Manifold1D) or isinstance(i, Manifold2D) or isinstance(i, Manifold3D) ):
1942 raise TypeError, "Illegal argument type %s added to PropertySet."%(i.__class__)
1943 for i in items:
1944 if not i in self.__items:
1945 if len(self.__items)>0:
1946 m=self.getManifoldClass()
1947 if not isinstance(i, m):
1948 raise TypeError("argument %s is not a %s class object."%(i, m.__name__))
1949 self.__items.append(i)
1950
1951 def getNumItems(self):
1952 """
1953 Returns the number of items in the property set.
1954 """
1955 return len(self.__items)
1956
1957 def getItems(self):
1958 """
1959 Returns the list of items.
1960 """
1961 return self.__items
1962
1963 def clearItems(self):
1964 """
1965 Clears the list of items.
1966 """
1967 self.__items=[]
1968
1969 def collectPrimitiveBases(self):
1970 """
1971 Returns primitives used to construct the PropertySet.
1972 """
1973 out=[self]
1974 for i in self.getItems(): out+=i.collectPrimitiveBases()
1975 return out
1976
1977 def getTag(self):
1978 """
1979 Returns the tag used for this property set.
1980 """
1981 return self.getID()
1982

  ViewVC Help
Powered by ViewVC 1.1.26