/[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 2948 - (show annotations)
Thu Feb 25 04:54:30 2010 UTC (9 years, 5 months ago) by gross
File MIME type: text/x-python
File size: 67877 byte(s)
a new almost completed version of the cookbook
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 getCoordinatesAsList(self):
415 """
416 Returns the coodinates of the point as a ``list`` object.
417 """
418 return [self._x[0], self._x[1], self._x[2] ]
419
420 def setCoordinates(self,x):
421 """
422 Sets the coodinates of the point from a ``numpy.ndarray`` object ``x``.
423 """
424 if not isinstance(x, numpy.ndarray):
425 self._x=numpy.array(x,_TYPE)
426 else:
427 self._x=x
428
429 def collectPrimitiveBases(self):
430 """
431 Returns primitives used to construct the primitive.
432 """
433 return [self]
434
435 def isColocated(self,primitive):
436 """
437 Returns True if the `Point` ``primitive`` is colocated (has the same
438 coordinates) with self. That is, if
439 *|self - primitive| <= tol * max(\|self\|,|primitive|)*.
440 """
441 if isinstance(primitive,Point):
442 primitive=primitive.getCoordinates()
443 c=self.getCoordinates()
444 d=c-primitive
445 if numpyImported:
446 return numpy.dot(d,d)<=getToleranceForColocation()**2*max(numpy.dot(c,c),numpy.dot(primitive,primitive))
447 else:
448 return numpy.dot(d,d)<=getToleranceForColocation()**2*max(numpy.dot(c,c),numpy.dot(primitive,primitive))
449 else:
450 return False
451
452 def substitute(self,sub_dict):
453 """
454 Returns a copy of self with substitutes for the primitives used to
455 construct it given by the dictionary ``sub_dict``. If a substitute for
456 the object is given by ``sub_dict`` the value is returned, otherwise a
457 new instance with substituted arguments is returned.
458 """
459 if not sub_dict.has_key(self):
460 c=self.getCoordinates()
461 sub_dict[self]=Point(c[0],c[1],c[2],local_scale=self.getLocalScale())
462 return sub_dict[self]
463
464 def modifyBy(self,transformation):
465 """
466 Modifies the coordinates by applying the given transformation.
467 """
468 self.setCoordinates(transformation(self.getCoordinates()))
469
470 def __neg__(self):
471 """
472 Returns a view of the object with reverse orientation. As a point has
473 no direction the object itself is returned.
474 """
475 return self
476
477 class Manifold1D(PrimitiveBase):
478 """
479 General one-dimensional manifold in 1D defined by a start and end point.
480 """
481 def __init__(self):
482 """
483 Initializes the one-dimensional manifold.
484 """
485 PrimitiveBase.__init__(self)
486 self.__apply_elements=False
487
488 def getStartPoint(self):
489 """
490 Returns the start point.
491 """
492 raise NotImplementedError()
493
494 def getEndPoint(self):
495 """
496 Returns the end point.
497 """
498 raise NotImplementedError()
499
500 def getBoundary(self):
501 """
502 Returns a list of the zero-dimensional manifolds forming the boundary
503 of the curve.
504 """
505 return [ self.getStartPoint(), self.getEndPoint()]
506
507
508 def setElementDistribution(self,n,progression=1,createBump=False):
509 """
510 Defines the number of elements on the line. If set it overwrites the local length setting which would be applied.
511 The progression factor ``progression`` defines the change of element size between naighboured elements. If ``createBump`` is set
512 progression is applied towards the center of the line.
513
514 :param n: number of elements on the line
515 :type n: ``int``
516 :param progression: a positive progression factor
517 :type progression: positive ``float``
518 :param createBump: of elements on the line
519 :type createBump: ``bool``
520 """
521 if isinstance(self, ReversePrimitive):
522 self.getUnderlyingPrimitive().setElementDistribution(n,progression,createBump)
523 else:
524 if n<1:
525 raise ValueError,"number of elements must be positive."
526 if progression<=0:
527 raise ValueError,"progression factor must be positive."
528 self.__apply_elements=True
529 self.__n=n
530 self.__progression_factor=progression
531 self.__createBump=createBump
532
533 def resetElementDistribution(self):
534 """
535 removes the a previously set element distribution from the line.
536 """
537 if isinstance(self, ReversePrimitive):
538 self.getUnderlyingPrimitive().resetElementDistribution()
539 else:
540 self.__apply_elements=False
541
542 def getElementDistribution(self):
543 """
544 Returns the element distribution.
545
546 :return: the tuple of the number of elements, the progression factor and the bump flag. If no element distribution is set ``None`` is returned
547 :rtype: ``tuple``
548 """
549 if isinstance(self, ReversePrimitive):
550 return self.getUnderlyingPrimitive().getElementDistribution()
551 else:
552 if self.__apply_elements:
553 return (self.__n, self.__progression_factor, self.__createBump)
554 else:
555 return None
556
557 class CurveBase(Manifold1D):
558 """
559 Base class for curves. A Curve is defined by a set of control points.
560 """
561 def __init__(self):
562 """
563 Initializes the curve.
564 """
565 Manifold1D.__init__(self)
566
567 def __len__(self):
568 """
569 Returns the number of control points.
570 """
571 return len(self.getControlPoints())
572
573 def getStartPoint(self):
574 """
575 Returns the start point.
576 """
577 return self.getControlPoints()[0]
578
579 def getEndPoint(self):
580 """
581 Returns the end point.
582 """
583 return self.getControlPoints()[-1]
584
585 def getControlPoints(self):
586 """
587 Returns a list of the points.
588 """
589 raise NotImplementedError()
590
591 class Curve(CurveBase, Primitive):
592 """
593 A curve defined through a list of control points.
594 """
595 def __init__(self,*points):
596 """
597 Defines a curve from control points given by ``points``.
598 """
599 if len(points)==1:
600 points=points[0]
601 if not hasattr(points,'__iter__'): raise ValueError("Curve needs at least two points")
602 if len(points)<2:
603 raise ValueError("Curve needs at least two points")
604 i=0
605 for p in points:
606 i+=1
607 if not isinstance(p,Point): raise TypeError("%s-th argument is not a Point object."%i)
608 self.__points=points
609 CurveBase.__init__(self)
610 Primitive.__init__(self)
611
612 def getControlPoints(self):
613 """
614 Returns a list of the points.
615 """
616 return self.__points
617
618 def __neg__(self):
619 """
620 Returns a view onto the curve with reversed ordering.
621 """
622 return ReverseCurve(self)
623
624 def substitute(self,sub_dict):
625 """
626 Returns a copy of self with substitutes for the primitives used to
627 construct it given by the dictionary ``sub_dict``. If a substitute for
628 the object is given by ``sub_dict`` the value is returned, otherwise a
629 new instance with substituted arguments is returned.
630 """
631 if not sub_dict.has_key(self):
632 new_p=[]
633 for p in self.getControlPoints(): new_p.append(p.substitute(sub_dict))
634 sub_dict[self]=self.__class__(*tuple(new_p))
635 return sub_dict[self]
636
637 def collectPrimitiveBases(self):
638 """
639 Returns the primitives used to construct the curve.
640 """
641 out=[self]
642 for p in self.getControlPoints(): out+=p.collectPrimitiveBases()
643 return out
644
645 def isColocated(self,primitive):
646 """
647 Returns True if curves are at the same position.
648 """
649 if hasattr(primitive,"getUnderlyingPrimitive"):
650 if isinstance(primitive.getUnderlyingPrimitive(),self.__class__):
651 if len(primitive) == len(self):
652 cp0=self.getControlPoints()
653 cp1=primitive.getControlPoints()
654 match=True
655 for i in range(len(cp0)):
656 if not cp0[i].isColocated(cp1[i]):
657 match=False
658 break
659 if not match:
660 for i in range(len(cp0)):
661 if not cp0[i].isColocated(cp1[len(cp0)-1-i]):
662 return False
663 return True
664 return False
665
666 class ReverseCurve(CurveBase, ReversePrimitive):
667 """
668 A curve defined through a list of control points.
669 """
670 def __init__(self,curve):
671 """
672 Defines a curve from control points.
673 """
674 if not isinstance(curve, Curve):
675 raise TypeError("ReverseCurve needs to be an instance of Curve")
676 CurveBase.__init__(self)
677 ReversePrimitive.__init__(self,curve)
678
679 def getControlPoints(self):
680 """
681 Returns a list of the points.
682 """
683 out=[p for p in self.getUnderlyingPrimitive().getControlPoints()]
684 out.reverse()
685 return tuple(out)
686
687 class Spline(Curve):
688 """
689 A spline curve defined through a list of control points.
690 """
691 pass
692
693 class BezierCurve(Curve):
694 """
695 A Bezier curve.
696 """
697 pass
698
699 class BSpline(Curve):
700 """
701 A BSpline curve. Control points may be repeated.
702 """
703 pass
704
705 class Line(Curve):
706 """
707 A line is defined by two points.
708 """
709 def __init__(self,*points):
710 """
711 Defines a line with start and end point.
712 """
713 if len(points)!=2:
714 raise TypeError("Line needs two points")
715 Curve.__init__(self,*points)
716
717 class ArcBase(Manifold1D):
718 """
719 Base class for arcs.
720 """
721 def __init__(self):
722 """
723 Initializes the arc.
724 """
725 Manifold1D.__init__(self)
726
727 def collectPrimitiveBases(self):
728 """
729 Returns the primitives used to construct the Arc.
730 """
731 out=[self]
732 out+=self.getStartPoint().collectPrimitiveBases()
733 out+=self.getEndPoint().collectPrimitiveBases()
734 out+=self.getCenterPoint().collectPrimitiveBases()
735 return out
736
737 def getCenterPoint(self):
738 """
739 Returns the center.
740 """
741 raise NotImplementedError()
742
743 class Arc(ArcBase, Primitive):
744 """
745 Defines an arc which is strictly smaller than pi.
746 """
747 def __init__(self,center,start,end):
748 """
749 Creates an arc defined by the start point, end point and center.
750 """
751 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
752 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
753 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
754 if center.isColocated(end): raise TypeError("center and start point are colocated.")
755 if center.isColocated(start): raise TypeError("center end end point are colocated.")
756 if start.isColocated(end): raise TypeError("start and end are colocated.")
757 # TODO: check length of circle.
758 ArcBase.__init__(self)
759 Primitive.__init__(self)
760 self.__center=center
761 self.__start=start
762 self.__end=end
763
764 def __neg__(self):
765 """
766 Returns a view onto the curve with reversed ordering.
767 """
768 return ReverseArc(self)
769
770 def getStartPoint(self):
771 """
772 Returns the start point.
773 """
774 return self.__start
775
776 def getEndPoint(self):
777 """
778 Returns the end point.
779 """
780 return self.__end
781
782 def getCenterPoint(self):
783 """
784 Returns the center point.
785 """
786 return self.__center
787
788 def substitute(self,sub_dict):
789 """
790 Returns a copy of self with substitutes for the primitives used to
791 construct it given by the dictionary ``sub_dict``. If a substitute for
792 the object is given by ``sub_dict`` the value is returned, otherwise a
793 new instance with substituted arguments is returned.
794 """
795 if not sub_dict.has_key(self):
796 sub_dict[self]=Arc(self.getCenterPoint().substitute(sub_dict),self.getStartPoint().substitute(sub_dict),self.getEndPoint().substitute(sub_dict))
797 return sub_dict[self]
798
799 def isColocated(self,primitive):
800 """
801 Returns True if curves are at the same position.
802 """
803 if hasattr(primitive,"getUnderlyingPrimitive"):
804 if isinstance(primitive.getUnderlyingPrimitive(),Arc):
805 return (self.getCenterPoint().isColocated(primitive.getCenterPoint())) and ( \
806 (self.getEndPoint().isColocated(primitive.getEndPoint()) and self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
807 or (self.getEndPoint().isColocated(primitive.getStartPoint()) and self.getStartPoint().isColocated(primitive.getEndPoint()) ) )
808 return False
809
810 class ReverseArc(ArcBase, ReversePrimitive):
811 """
812 Defines an arc which is strictly smaller than pi.
813 """
814 def __init__(self,arc):
815 """
816 Creates an arc defined by the start point, end point and center.
817 """
818 if not isinstance(arc, Arc):
819 raise TypeError("ReverseCurve needs to be an instance of Arc")
820 ArcBase.__init__(self)
821 ReversePrimitive.__init__(self,arc)
822
823 def getStartPoint(self):
824 """
825 Returns the start point.
826 """
827 return self.getUnderlyingPrimitive().getEndPoint()
828
829 def getEndPoint(self):
830 """
831 Returns the end point.
832 """
833 return self.getUnderlyingPrimitive().getStartPoint()
834
835 def getCenterPoint(self):
836 """
837 Returns the center point.
838 """
839 return self.getUnderlyingPrimitive().getCenterPoint()
840
841 class EllipseBase(Manifold1D):
842 """
843 Base class for ellipses.
844 """
845 def __init__(self):
846 """
847 Initializes the ellipse.
848 """
849 Manifold1D.__init__(self)
850
851 def collectPrimitiveBases(self):
852 """
853 Returns the primitives used to construct the ellipse.
854 """
855 out=[self]
856 out+=self.getStartPoint().collectPrimitiveBases()
857 out+=self.getEndPoint().collectPrimitiveBases()
858 out+=self.getCenterPoint().collectPrimitiveBases()
859 out+=self.getPointOnMainAxis().collectPrimitiveBases()
860 return out
861
862 class Ellipse(EllipseBase, Primitive):
863 """
864 Defines an ellipse which is strictly smaller than pi.
865 """
866 def __init__(self,center,point_on_main_axis,start,end):
867 """
868 Creates an ellipse defined by the start point, end point, the center
869 and a point on the main axis.
870 """
871 if not isinstance(center,Point): raise TypeError("center needs to be a Point object.")
872 if not isinstance(end,Point): raise TypeError("end needs to be a Point object.")
873 if not isinstance(start,Point): raise TypeError("start needs to be a Point object.")
874 if not isinstance(point_on_main_axis,Point): raise TypeError("point on main axis needs to be a Point object.")
875 if center.isColocated(end): raise TypeError("center and start point are colocated.")
876 if center.isColocated(start): raise TypeError("center end end point are colocated.")
877 if center.isColocated(point_on_main_axis): raise TypeError("center and point on main axis are colocated.")
878 if start.isColocated(end): raise TypeError("start and end point are colocated.")
879 # TODO: check length of circle.
880 EllipseBase.__init__(self)
881 Primitive.__init__(self)
882 self.__center=center
883 self.__start=start
884 self.__end=end
885 self.__point_on_main_axis=point_on_main_axis
886
887 def __neg__(self):
888 """
889 Returns a view onto the curve with reversed ordering.
890 """
891 return ReverseEllipse(self)
892
893 def getStartPoint(self):
894 """
895 Returns the start point.
896 """
897 return self.__start
898
899 def getEndPoint(self):
900 """
901 Returns the end point.
902 """
903 return self.__end
904
905 def getCenterPoint(self):
906 """
907 Returns the center.
908 """
909 return self.__center
910
911 def getPointOnMainAxis(self):
912 """
913 Returns a point on the main axis.
914 """
915 return self.__point_on_main_axis
916
917 def substitute(self,sub_dict):
918 """
919 Returns a copy of self with substitutes for the primitives used to
920 construct it given by the dictionary ``sub_dict``. If a substitute for
921 the object is given by ``sub_dict`` the value is returned, otherwise a
922 new instance with substituted arguments is returned.
923 """
924 if not sub_dict.has_key(self):
925 sub_dict[self]=Ellipse(self.getCenterPoint().substitute(sub_dict),
926 self.getPointOnMainAxis().substitute(sub_dict),
927 self.getStartPoint().substitute(sub_dict),
928 self.getEndPoint().substitute(sub_dict))
929 return sub_dict[self]
930
931
932 def isColocated(self,primitive):
933 """
934 Returns True if curves are at the same position.
935 """
936 if hasattr(primitive,"getUnderlyingPrimitive"):
937 if isinstance(primitive.getUnderlyingPrimitive(),Ellipse):
938 self_c=self.getCenterPoint().getCoordinates()
939 p=self.getPointOnMainAxis().getCoordinates()-self_c
940 q=primitive.getPointOnMainAxis().getCoordinates()-self_c
941 # are p and q orthogonal or collinear?
942 len_p=math.sqrt(p[0]**2+p[1]**2+p[2]**2)
943 len_q=math.sqrt(q[0]**2+q[1]**2+q[2]**2)
944 p_q= abs(p[0]*q[0]+p[1]*q[1]+p[2]*q[2])
945 return ((p_q <= getToleranceForColocation() * len_q * p_q) or \
946 (abs(p_q - len_q * p_q) <= getToleranceForColocation())) and \
947 self.getCenterPoint().isColocated(primitive.getCenterPoint()) and \
948 ( \
949 (self.getEndPoint().isColocated(primitive.getEndPoint()) and \
950 self.getStartPoint().isColocated(primitive.getStartPoint()) ) \
951 or \
952 (self.getEndPoint().isColocated(primitive.getStartPoint()) and \
953 self.getStartPoint().isColocated(primitive.getEndPoint())) \
954 )
955 return False
956
957 class ReverseEllipse(EllipseBase, ReversePrimitive):
958 """
959 Defines an ellipse which is strictly smaller than pi.
960 """
961 def __init__(self,arc):
962 """
963 Creates an instance of a reverse view to an ellipse.
964 """
965 if not isinstance(arc, Ellipse):
966 raise TypeError("ReverseCurve needs to be an instance of Ellipse")
967 EllipseBase.__init__(self)
968 ReversePrimitive.__init__(self,arc)
969
970 def getStartPoint(self):
971 """
972 Returns the start point.
973 """
974 return self.getUnderlyingPrimitive().getEndPoint()
975
976 def getEndPoint(self):
977 """
978 Returns the end point.
979 """
980 return self.getUnderlyingPrimitive().getStartPoint()
981
982 def getCenterPoint(self):
983 """
984 Returns the center point.
985 """
986 return self.getUnderlyingPrimitive().getCenterPoint()
987
988 def getPointOnMainAxis(self):
989 """
990 Returns a point on the main axis.
991 """
992 return self.getUnderlyingPrimitive().getPointOnMainAxis()
993
994
995 class CurveLoop(Primitive, PrimitiveBase):
996 """
997 An oriented loop of one-dimensional manifolds (= curves and arcs).
998
999 The loop must be closed and the `Manifold1D` s should be oriented
1000 consistently.
1001 """
1002 def __init__(self,*curves):
1003 """
1004 Creates a polygon from a list of line curves. The curves must form a
1005 closed loop.
1006 """
1007 if len(curves)==1:
1008 curves=curves[0]
1009 if not hasattr(curves,'__iter__'): raise ValueError("CurveLoop needs at least two points")
1010 if len(curves)<2:
1011 raise ValueError("At least two curves have to be given.")
1012 for i in range(len(curves)):
1013 if not isinstance(curves[i],Manifold1D):
1014 raise TypeError("%s-th argument is not a Manifold1D object."%i)
1015 # for the curves a loop:
1016 #used=[ False for i in curves]
1017 self.__curves=[]
1018 for c in curves:
1019 if not c in self.__curves: self.__curves.append(c)
1020 Primitive.__init__(self)
1021 PrimitiveBase.__init__(self)
1022
1023
1024 def getCurves(self):
1025 """
1026 Returns the curves defining the CurveLoop.
1027 """
1028 return self.__curves
1029
1030 def __neg__(self):
1031 """
1032 Returns a view onto the curve with reversed ordering.
1033 """
1034 return ReverseCurveLoop(self)
1035
1036 def __len__(self):
1037 """
1038 Returns the number of curves in the CurveLoop.
1039 """
1040 return len(self.getCurves())
1041
1042 def collectPrimitiveBases(self):
1043 """
1044 Returns primitives used to construct the CurveLoop.
1045 """
1046 out=[self]
1047 for c in self.getCurves(): out+=c.collectPrimitiveBases()
1048 return out
1049
1050 def substitute(self,sub_dict):
1051 """
1052 Returns a copy of self with substitutes for the primitives used to
1053 construct it given by the dictionary ``sub_dict``. If a substitute for
1054 the object is given by ``sub_dict`` the value is returned, otherwise a
1055 new instance with substituted arguments is returned.
1056 """
1057 if not sub_dict.has_key(self):
1058 new_c=[]
1059 for c in self.getCurves(): new_c.append(c.substitute(sub_dict))
1060 sub_dict[self]=CurveLoop(*tuple(new_c))
1061 return sub_dict[self]
1062
1063 def isColocated(self,primitive):
1064 """
1065 Returns True if each curve is colocated with a curve in ``primitive``.
1066 """
1067 if hasattr(primitive,"getUnderlyingPrimitive"):
1068 if isinstance(primitive.getUnderlyingPrimitive(),CurveLoop):
1069 if len(primitive) == len(self):
1070 cp0=self.getCurves()
1071 cp1=primitive.getCurves()
1072 for c0 in cp0:
1073 colocated = False
1074 for c1 in cp1:
1075 colocated = colocated or c0.isColocated(c1)
1076 if not colocated: return False
1077 return True
1078 return False
1079
1080 def getPolygon(self):
1081 """
1082 Returns a list of start/end points of the 1D mainfold form the loop. If not closed and exception is thrown.
1083 """
1084 curves=self.getCurves()
1085 s=[curves[0].getStartPoint(), curves[0].getEndPoint()]
1086 found= [ curves[0], ]
1087 restart=True
1088 while restart:
1089 restart=False
1090 for k in curves:
1091 if not k in found:
1092 if k.getStartPoint() == s[-1]:
1093 found.append(k)
1094 if hasattr(k,"getControlPoints"): s+=k.getControlPoints()[1:-1]
1095 if k.getEndPoint() == s[0]:
1096 if len(found) == len(curves):
1097 return s
1098 else:
1099 raise ValueError,"loop %s is not closed."%self.getID()
1100 s.append(k.getEndPoint())
1101 restart=True
1102 break
1103 if not restart:
1104 raise ValueError,"loop %s is not closed."%self.getID()
1105
1106 class ReverseCurveLoop(ReversePrimitive, PrimitiveBase):
1107 """
1108 An oriented loop of one-dimensional manifolds (= curves and arcs).
1109
1110 The loop must be closed and the one-dimensional manifolds should be
1111 oriented consistently.
1112 """
1113 def __init__(self,curve_loop):
1114 """
1115 Creates a polygon from a list of line curves. The curves must form a
1116 closed loop.
1117 """
1118 if not isinstance(curve_loop, CurveLoop):
1119 raise TypeError("arguments need to be an instance of CurveLoop.")
1120 ReversePrimitive.__init__(self, curve_loop)
1121 PrimitiveBase.__init__(self)
1122
1123 def getCurves(self):
1124 """
1125 Returns the curves defining the CurveLoop.
1126 """
1127 return [ -c for c in self.getUnderlyingPrimitive().getCurves() ]
1128
1129 def __len__(self):
1130 return len(self.getUnderlyingPrimitive())
1131 #=
1132 class Manifold2D(PrimitiveBase):
1133 """
1134 General two-dimensional manifold.
1135
1136 :ivar LEFT: left element orientation when meshing with transifinite meshing
1137 :ivar RIGHT: right element orientation when meshing with transifinite meshing
1138 :ivar ALTERNATE: alternate element orientation when meshing with transifinite meshing
1139 """
1140 LEFT="Left"
1141 RIGHT="Right"
1142 ALTERNATE="Alternate"
1143 def __init__(self):
1144 """
1145 Creates a two-dimensional manifold.
1146 """
1147 PrimitiveBase.__init__(self)
1148 self.__transfinitemeshing=False
1149 self.__recombination_angle=None
1150
1151 def getBoundary(self):
1152 """
1153 Returns a list of the one-dimensional manifolds forming the boundary
1154 of the surface (including holes).
1155 """
1156 raise NotImplementedError()
1157
1158 def hasHole(self):
1159 """
1160 Returns True if a hole is present.
1161 """
1162 raise NotImplementedError()
1163
1164 def setElementDistribution(self,n,progression=1,createBump=False):
1165 """
1166 Defines the number of elements on the lines
1167
1168 :param n: number of elements on the line
1169 :type n: ``int``
1170 :param progression: a positive progression factor
1171 :type progression: positive ``float``
1172 :param createBump: of elements on the line
1173 :type createBump: ``bool``
1174 """
1175 for i in self.getBoundary(): i.setElementDistribution(n,progression,createBump)
1176
1177 def getPoints(self):
1178 """
1179 returns a list of points used to define the boundary
1180
1181 :return: list of points used to define the boundary
1182 :rtype: ``list`` of `Point` s
1183 """
1184 out=[]
1185 boundary=self.getBoundary()
1186 for l in boundary:
1187 for p in l.getBoundary():
1188 if not p in out: out.append(p)
1189 return out
1190
1191 def setRecombination(self, max_deviation=45*DEG):
1192 """
1193 Recombines triangular meshes on the surface into mixed triangular/quadrangular meshes.
1194 ``max_deviation`` specifies the maximum derivation of the largest angle in the quadrangle
1195 from the right angle. Use ``max_deviation``==``None`` to switch off recombination.
1196
1197 :param max_deviation: maximum derivation of the largest angle in the quadrangle from the right angle.
1198 :type max_deviation: ``float`` or ``None``.
1199 """
1200 if isinstance(self, ReversePrimitive):
1201 self.getUnderlyingPrimitive().setRecombination(max_deviation)
1202 else:
1203 if not max_deviation==None:
1204 if max_deviation<=0:
1205 raise ValueError, "max_deviation must be positive."
1206 if max_deviation/DEG>=90:
1207 raise ValueError, "max_deviation must be smaller than 90 DEG"
1208 self.__recombination_angle=max_deviation
1209
1210 def getRecombination(self):
1211 """
1212 returns max deviation from right angle in the recombination algorithm
1213
1214 :return: max deviation from right angle in the recombination algorithm. If recombination is switched off, ``None`` is returned.
1215 :rtype: ``float`` or ``None``
1216 """
1217 if isinstance(self, ReversePrimitive):
1218 return self.getUnderlyingPrimitive().getRecombination()
1219 else:
1220 return self.__recombination_angle
1221
1222 def setTransfiniteMeshing(self,orientation="Left"):
1223 """
1224 applies 2D transfinite meshing to the surface.
1225
1226 :param orientation: sets the orientation of the triangles. It is only relevant if recombination is not used.
1227 :type orientation: `Manifold2D.LEFT`, `Manifold2D.RIGHT`, `Manifold2D.ALTERNATE`
1228 :note: Transfinite meshing can not be applied if holes are present.
1229 """
1230 if isinstance(self, ReversePrimitive):
1231 return self.getUnderlyingPrimitive().setTransfiniteMeshing(orientation)
1232 else:
1233 if not orientation in [ Manifold2D.LEFT, Manifold2D.RIGHT, Manifold2D.ALTERNATE]:
1234 raise ValueError,"invalid orientation %s."%orientation
1235 if self.hasHole():
1236 raise ValueError,"transfinite meshing cannot be appled to surfaces with a hole."
1237 b=self.getBoundary()
1238 if len(b)>4 or len(b)<3:
1239 raise ValueError,"transfinite meshing permits 3 or 4 boundary lines only."
1240 for l in b:
1241 if l.getElementDistribution() == None: raise ValueError,"transfinite meshing requires element distribution on all boundary lines."
1242 start=b[0]
1243 opposite=None
1244 top=None
1245 bottom=None
1246 for l in b[1:]:
1247 if l.getEndPoint() == start.getStartPoint():
1248 bottom=l
1249 elif l.getStartPoint() == start.getEndPoint():
1250 top=l
1251 else:
1252 opposite=l
1253 if top==None or bottom == None:
1254 raise ValueError,"transfinite meshing cannot be applied to boundary is not closed. Most likely the orientation of some boundray segments is wrong."
1255 if opposite == None: # three sides only
1256 if not top.getElementDistribution()[0] == bottom.getElementDistribution()[0]: start, top, bottom= bottom, start, top
1257 if not top.getElementDistribution() == bottom.getElementDistribution():
1258 raise ValueError,"transfinite meshing requires opposite faces to be have the same element distribution."
1259 if not opposite == None:
1260 if not start.getElementDistribution()[0] == opposite.getElementDistribution()[0]:
1261 raise ValueError,"transfinite meshing requires oposite faces to be have the same element distribution."
1262 if opposite == None:
1263 if bottom.getEndPoint == top.getStartPoint():
1264 raise ValueError,"cannot identify corner proints for transfinite meshing."
1265 else:
1266 points=[ bottom.getStartPoint(), bottom.getEndPoint(), top.getStartPoint() ]
1267 else:
1268 points=[ bottom.getStartPoint(), bottom.getEndPoint(), top.getStartPoint(), top.getEndPoint() ]
1269 self.__points=points
1270 self.__orientation=orientation
1271 self.__transfinitemeshing=True
1272
1273 def resetTransfiniteMeshing(self):
1274 """
1275 removes the transfinite meshing from the surface
1276 """
1277 if isinstance(self, ReversePrimitive):
1278 self.getUnderlyingPrimitive().resetTransfiniteMeshing()
1279 else:
1280 self.__transfinitemeshing=False
1281
1282 def getTransfiniteMeshing(self):
1283 """
1284 returns the transfinite meshing setings. If transfinite meshing is not set, ``None`` is returned.
1285
1286 :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.
1287 :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`
1288 """
1289 if isinstance(self, ReversePrimitive):
1290 return self.getUnderlyingPrimitive().getTransfiniteMeshing()
1291 else:
1292 if self.__transfinitemeshing:
1293 return (self.__points, self.__orientation)
1294 else:
1295 return None
1296 class RuledSurface(Primitive, Manifold2D):
1297 """
1298 A ruled surface, i.e. a surface that can be interpolated using transfinite
1299 interpolation.
1300 """
1301 def __init__(self,loop):
1302 """
1303 Creates a ruled surface with boundary ``loop``.
1304
1305 :param loop: `CurveLoop` defining the boundary of the surface.
1306 """
1307 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1308 raise TypeError("argument loop needs to be a CurveLoop object.")
1309 if len(loop)<2:
1310 raise ValueError("the loop must contain at least two Curves.")
1311 if len(loop)>4:
1312 raise ValueError("the loop must contain at most four Curves.")
1313 Primitive.__init__(self)
1314 Manifold2D.__init__(self)
1315 self.__loop=loop
1316
1317 def hasHole(self):
1318 """
1319 Returns True if a hole is present.
1320 """
1321 return False
1322
1323 def __neg__(self):
1324 """
1325 Returns a view onto the suface with reversed ordering.
1326 """
1327 return ReverseRuledSurface(self)
1328
1329 def getBoundaryLoop(self):
1330 """
1331 Returns the loop defining the outer boundary.
1332 """
1333 return self.__loop
1334
1335 def getBoundary(self):
1336 """
1337 Returns a list of the one-dimensional manifolds forming the boundary
1338 of the Surface (including holes).
1339 """
1340 return self.getBoundaryLoop().getCurves()
1341
1342 def substitute(self,sub_dict):
1343 """
1344 Returns a copy of self with substitutes for the primitives used to
1345 construct it given by the dictionary ``sub_dict``. If a substitute for
1346 the object is given by ``sub_dict`` the value is returned, otherwise a
1347 new instance with substituted arguments is returned.
1348 """
1349 if not sub_dict.has_key(self):
1350 sub_dict[self]=RuledSurface(self.getBoundaryLoop().substitute(sub_dict))
1351 return sub_dict[self]
1352
1353 def isColocated(self,primitive):
1354 """
1355 Returns True if each curve is colocated with a curve in ``primitive``.
1356 """
1357 if hasattr(primitive,"getUnderlyingPrimitive"):
1358 if isinstance(primitive.getUnderlyingPrimitive(),RuledSurface):
1359 return self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop())
1360 return False
1361
1362 def collectPrimitiveBases(self):
1363 """
1364 Returns primitives used to construct the Surface.
1365 """
1366 return [self] + self.getBoundaryLoop().collectPrimitiveBases()
1367
1368 def createRuledSurface(*curves):
1369 """
1370 An easier way to create a `RuledSurface` from given curves.
1371 """
1372 return RuledSurface(CurveLoop(*curves))
1373
1374
1375 class ReverseRuledSurface(ReversePrimitive, Manifold2D):
1376 """
1377 Creates a view onto a `RuledSurface` but with reverse orientation.
1378 """
1379 def __init__(self,surface):
1380 """
1381 Creates a polygon from a list of line curves. The curves must form a
1382 closed loop.
1383 """
1384 if not isinstance(surface, RuledSurface):
1385 raise TypeError("arguments need to be an instance of CurveLoop.")
1386 ReversePrimitive.__init__(self, surface)
1387 Manifold2D.__init__(self)
1388
1389 def getBoundaryLoop(self):
1390 """
1391 Returns the CurveLoop defining the ReverseRuledSurface.
1392 """
1393 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1394
1395 def getBoundary(self):
1396 """
1397 Returns a list of the one-dimensional manifolds forming the boundary
1398 of the Surface (including holes).
1399 """
1400 return self.getBoundaryLoop().getCurves()
1401
1402 def hasHole(self):
1403 """
1404 Returns True if a hole is present.
1405 """
1406 return False
1407
1408 #==============================
1409 class PlaneSurface(Primitive, Manifold2D):
1410 """
1411 A plane surface with holes.
1412 """
1413 def __init__(self,loop,holes=[]):
1414 """
1415 Creates a plane surface with holes.
1416
1417 :param loop: `CurveLoop` defining the boundary of the surface
1418 :param holes: list of `CurveLoop` s defining holes in the surface
1419 :note: A CurveLoop defining a hole should not have any lines in common
1420 with the exterior CurveLoop.
1421 :note: A CurveLoop defining a hole should not have any lines in common
1422 with another CurveLoop defining a hole in the same surface.
1423 """
1424 if not isinstance(loop.getUnderlyingPrimitive(),CurveLoop):
1425 raise TypeError("argument loop needs to be a CurveLoop object.")
1426 for i in range(len(holes)):
1427 if not isinstance(holes[i].getUnderlyingPrimitive(), CurveLoop):
1428 raise TypeError("%i-th hole needs to be a CurveLoop object.")
1429 #TODO: check if lines and holes are in a plane
1430 #TODO: are holes really holes?
1431 Primitive.__init__(self)
1432 Manifold2D.__init__(self)
1433 self.__loop=loop
1434 self.__holes=holes
1435
1436 def hasHole(self):
1437 """
1438 Returns True if a hole is present.
1439 """
1440 return len(self.getHoles())>0
1441
1442 def getHoles(self):
1443 """
1444 Returns the holes.
1445 """
1446 return self.__holes
1447
1448 def getBoundaryLoop(self):
1449 """
1450 Returns the loop defining the boundary.
1451 """
1452 return self.__loop
1453
1454 def substitute(self,sub_dict):
1455 """
1456 Returns a copy of self with substitutes for the primitives used to
1457 construct it given by the dictionary ``sub_dict``. If a substitute for
1458 the object is given by ``sub_dict`` the value is returned, otherwise a
1459 new instance with substituted arguments is returned.
1460 """
1461 if not sub_dict.has_key(self):
1462 sub_dict[self]=PlaneSurface(self.getBoundaryLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1463 return sub_dict[self]
1464
1465 def isColocated(self,primitive):
1466 """
1467 Returns True if each curve is colocated with a curve in ``primitive``.
1468 """
1469 if hasattr(primitive,"getUnderlyingPrimitive"):
1470 if isinstance(primitive.getUnderlyingPrimitive(),PlaneSurface):
1471 if self.getBoundaryLoop().isColocated(primitive.getBoundaryLoop()):
1472 hs0=self.getHoles()
1473 hs1=primitive.getHoles()
1474 if len(hs0) == len(hs1):
1475 for h0 in hs0:
1476 colocated = False
1477 for h1 in hs1:
1478 colocated = colocated or h0.isColocated(h1)
1479 if not colocated: return False
1480 return True
1481 return False
1482
1483 def collectPrimitiveBases(self):
1484 """
1485 Returns primitives used to construct the Surface.
1486 """
1487 out=[self] + self.getBoundaryLoop().collectPrimitiveBases()
1488 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1489 return out
1490
1491 def __neg__(self):
1492 """
1493 Returns a view onto the curve with reversed ordering.
1494 """
1495 return ReversePlaneSurface(self)
1496
1497 def getBoundary(self):
1498 """
1499 Returns a list of the one-dimensional manifolds forming the boundary
1500 of the Surface (including holes).
1501 """
1502 out = []+ self.getBoundaryLoop().getCurves()
1503 for h in self.getHoles(): out+=h.getCurves()
1504 return out
1505
1506 class ReversePlaneSurface(ReversePrimitive, Manifold2D):
1507 """
1508 Creates a view onto a `PlaneSurface` but with reverse orientation.
1509 """
1510 def __init__(self,surface):
1511 """
1512 Creates a polygon from a `PlaneSurface`.
1513 """
1514 if not isinstance(surface, PlaneSurface):
1515 raise TypeError("arguments need to be an instance of PlaneSurface.")
1516 ReversePrimitive.__init__(self, surface)
1517 Manifold2D.__init__(self)
1518
1519 def getBoundaryLoop(self):
1520 """
1521 Returns the CurveLoop defining the ReversePlaneSurface.
1522 """
1523 return -self.getUnderlyingPrimitive().getBoundaryLoop()
1524
1525 def getHoles(self):
1526 """
1527 Returns the holes.
1528 """
1529 return [ -h for h in self.getUnderlyingPrimitive().getHoles() ]
1530
1531 def getBoundary(self):
1532 """
1533 Returns a list of the one-dimensional manifolds forming the boundary
1534 of the Surface (including holes).
1535 """
1536 out = [] + self.getBoundaryLoop().getCurves()
1537 for h in self.getHoles(): out+=h.getCurves()
1538 return out
1539
1540 def hasHole(self):
1541 """
1542 Returns True if a hole is present.
1543 """
1544 return len(self.getHoles())>0
1545
1546 #=========================================================================
1547 class SurfaceLoop(Primitive, PrimitiveBase):
1548 """
1549 A loop of 2D primitives which defines the shell of a volume.
1550
1551 The loop must represent a closed shell, and the primitives should be
1552 oriented consistently.
1553 """
1554 def __init__(self,*surfaces):
1555 """
1556 Creates a surface loop.
1557 """
1558 if len(surfaces)==1:
1559 surfaces=surfaces[0]
1560 if not hasattr(surfaces,'__iter__'): raise ValueError("SurfaceLoop needs at least two points")
1561 if len(surfaces)<2:
1562 raise ValueError("at least two surfaces have to be given.")
1563 for i in range(len(surfaces)):
1564 if not isinstance(surfaces[i].getUnderlyingPrimitive(),Manifold2D):
1565 raise TypeError("%s-th argument is not a Manifold2D object."%i)
1566 self.__surfaces=list(surfaces)
1567 Primitive.__init__(self)
1568 PrimitiveBase.__init__(self)
1569
1570 def __len__(self):
1571 """
1572 Returns the number of curves in the SurfaceLoop.
1573 """
1574 return len(self.__surfaces)
1575
1576 def __neg__(self):
1577 """
1578 Returns a view onto the curve with reversed ordering.
1579 """
1580 return ReverseSurfaceLoop(self)
1581
1582 def getSurfaces(self):
1583 """
1584 Returns the surfaces defining the SurfaceLoop.
1585 """
1586 return self.__surfaces
1587
1588 def collectPrimitiveBases(self):
1589 """
1590 Returns primitives used to construct the SurfaceLoop.
1591 """
1592 out=[self]
1593 for c in self.getSurfaces(): out+=c.collectPrimitiveBases()
1594 return out
1595
1596 def substitute(self,sub_dict):
1597 """
1598 Returns a copy of self with substitutes for the primitives used to
1599 construct it given by the dictionary ``sub_dict``. If a substitute for
1600 the object is given by ``sub_dict`` the value is returned, otherwise a
1601 new instance with substituted arguments is returned.
1602 """
1603 if not sub_dict.has_key(self):
1604 new_s=[]
1605 for s in self.getSurfaces(): new_s.append(s.substitute(sub_dict))
1606 sub_dict[self]=SurfaceLoop(*tuple(new_s))
1607 return sub_dict[self]
1608
1609 def isColocated(self,primitive):
1610 """
1611 Returns True if each surface is colocated with a curve in ``primitive``
1612 and vice versa.
1613 """
1614 if hasattr(primitive,"getUnderlyingPrimitive"):
1615 if isinstance(primitive.getUnderlyingPrimitive(),SurfaceLoop):
1616 if len(primitive) == len(self):
1617 sp0=self.getSurfaces()
1618 sp1=primitive.getSurfaces()
1619 for s0 in sp0:
1620 colocated = False
1621 for s1 in sp1:
1622 colocated = colocated or s0.isColocated(s1)
1623 if not colocated: return False
1624 return True
1625 return False
1626
1627 class ReverseSurfaceLoop(ReversePrimitive, PrimitiveBase):
1628 """
1629 A view of a SurfaceLoop with reverse orientation.
1630
1631 The loop must represent a closed shell and the primitives should be
1632 oriented consistently.
1633 """
1634 def __init__(self,surface_loop):
1635 """
1636 Creates a polygon from a list of line surfaces. The curves must form
1637 a closed loop.
1638 """
1639 if not isinstance(surface_loop, SurfaceLoop):
1640 raise TypeError("arguments need to be an instance of SurfaceLoop.")
1641 ReversePrimitive.__init__(self, surface_loop)
1642 PrimitiveBase.__init__(self)
1643
1644 def getSurfaces(self):
1645 """
1646 Returns the surfaces defining the SurfaceLoop.
1647 """
1648 return [ -s for s in self.getUnderlyingPrimitive().getSurfaces() ]
1649
1650 def __len__(self):
1651 return len(self.getUnderlyingPrimitive())
1652
1653 #==============================
1654 class Manifold3D(PrimitiveBase):
1655 """
1656 General three-dimensional manifold.
1657 """
1658 def __init__(self):
1659 """
1660 Creates a three-dimensional manifold.
1661 """
1662 PrimitiveBase.__init__(self)
1663 self.__transfinitemeshing=False
1664
1665 def getBoundary(self):
1666 """
1667 Returns a list of the 2-dimensional manifolds forming the boundary
1668 of the volume (including holes).
1669 """
1670 raise NotImplementedError()
1671
1672 def setElementDistribution(self,n,progression=1,createBump=False):
1673 """
1674 Defines the number of elements on the lines and surfaces
1675
1676 :param n: number of elements on the line
1677 :type n: ``int``
1678 :param progression: a positive progression factor
1679 :type progression: positive ``float``
1680 :param createBump: of elements on the line
1681 :type createBump: ``bool``
1682 """
1683 for i in self.getBoundary(): i.setElementDistribution(n,progression,createBump)
1684
1685 def setRecombination(self, max_deviation=45*DEG):
1686 """
1687 Recombines triangular meshes on all surface into mixed triangular/quadrangular meshes. These meshes
1688 are then used to generate the volume mesh if possible. Recombination requires 3D transfinite meshing.
1689
1690 ``max_deviation`` specifies the maximum derivation of the largest angle in the quadrangle
1691 from the right angle. Use ``max_deviation``==``None`` to switch off recombination.
1692
1693 :param max_deviation: maximum derivation of the largest angle in the quadrangle from the right angle.
1694 :type max_deviation: ``float`` or ``None``.
1695 """
1696 if not max_deviation==None:
1697 if max_deviation<=0:
1698 raise ValueError, "max_deviation must be positive."
1699 if max_deviation/DEG>=90:
1700 raise ValueError, "max_deviation must be smaller than 90 DEG"
1701 for i in self.getBoundary(): i.setRecombination(max_deviation)
1702 self.setTransfiniteMeshing()
1703
1704 def setTransfiniteMeshing(self,orientation="Left"):
1705 """
1706 applies 3D transfinite meshing to the volume and all surface. It requires transfinite meshing on all faces which will be enforced (except
1707 if ``orientation`` is equal to ``None``.
1708
1709 :param orientation: sets the orientation of the triangles on the surfaces. It is only relevant if recombination is not used.
1710 If orientation is equal to ``None``, the transinite meshing is not applied to the surfaces but must be set by the user.
1711 :type orientation: `Manifold2D.LEFT`, `Manifold2D.RIGHT`, `Manifold2D.ALTERNATE`
1712 :note: Transfinite meshing can not be applied if holes are present.
1713 :note: only five or six surfaces may be used.
1714 :warning: The functionality of transfinite meshing without recombination is not entirely clear in `gmsh`. So please apply this method with care.
1715 """
1716 if isinstance(self, ReversePrimitive):
1717 return self.getUnderlyingPrimitive().setTransfiniteMeshing(orientation)
1718 else:
1719 if not orientation == None:
1720 if not orientation in [ Manifold2D.LEFT, Manifold2D.RIGHT, Manifold2D.ALTERNATE]:
1721 raise ValueError,"invalid orientation %s."%orientation
1722
1723 if self.hasHole():
1724 raise ValueError,"transfinite meshing cannot be appled to surfaces with a hole."
1725 b=self.getBoundary()
1726 # find a face with 3/4 Points:
1727 if len(b) == 6 :
1728 des_len=4
1729 elif len(b) == 5:
1730 des_len=3
1731 else:
1732 raise ValueError,"transfinite meshing permits 5 or 6 surface only."
1733 # start_b=None
1734 # for l in b:
1735 # if len(l.getPolygon()) == des_len:
1736 # start_b = l
1737 # break
1738 # if start_b == None:
1739 # raise ValueError,"Expect face with %s points."%des_len
1740 # start_poly=start_b.getPolygon()
1741 # now we need to find the opposite face:
1742 # opposite = None
1743 # for l in b:
1744 # if all( [ not k in start_poly for k in l.getPolygon() ]):
1745 # opposite = l
1746 # break
1747 # if opposite == None:
1748 # raise ValueError,"Unable to find face for transfinite interpolation."
1749 # opposite_poly=opposite.getPolygon()
1750 # if not len(opposite_poly) == des_len:
1751 # raise ValueError,"Unable to find face for transfinite interpolation."
1752 # this needs more work to find the points!!!!
1753 points = []
1754 self.__points=points
1755 if not orientation == None:
1756 for i in b: i.setTransfiniteMeshing(orientation)
1757 self.__transfinitemeshing=True
1758
1759 def resetTransfiniteMeshing(self):
1760 """
1761 removes the transfinite meshing from the volume but not from the surfaces
1762 """
1763 if isinstance(self, ReversePrimitive):
1764 self.getUnderlyingPrimitive().resetTransfiniteMeshing()
1765 else:
1766 self.__transfinitemeshing=False
1767
1768 def getTransfiniteMeshing(self):
1769 """
1770 returns the transfinite meshing setings. If transfinite meshing is not set, ``None`` is returned.
1771
1772 :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.
1773 :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`
1774 """
1775 if isinstance(self, ReversePrimitive):
1776 return self.getUnderlyingPrimitive().getTransfiniteMeshing()
1777 else:
1778 if self.__transfinitemeshing:
1779 return self.__points
1780 else:
1781 return None
1782
1783 class Volume(Manifold3D, Primitive):
1784 """
1785 A volume with holes.
1786 """
1787 def __init__(self,loop,holes=[]):
1788 """
1789 Creates a volume with holes.
1790
1791 :param loop: `SurfaceLoop` defining the boundary of the surface
1792 :param holes: list of `SurfaceLoop` defining holes in the surface
1793 :note: A SurfaceLoop defining a hole should not have any surfaces in
1794 common with the exterior SurfaceLoop.
1795 :note: A SurfaceLoop defining a hole should not have any surfaces in
1796 common with another SurfaceLoop defining a hole in the same
1797 volume.
1798 """
1799 if not isinstance(loop.getUnderlyingPrimitive(), SurfaceLoop):
1800 raise TypeError("argument loop needs to be a SurfaceLoop object.")
1801 for i in range(len(holes)):
1802 if not isinstance(holes[i].getUnderlyingPrimitive(), SurfaceLoop):
1803 raise TypeError("%i th hole needs to be a SurfaceLoop object.")
1804 Primitive.__init__(self)
1805 Manifold3D.__init__(self)
1806 self.__loop=loop
1807 self.__holes=holes
1808 self.__transfinitemeshing=False
1809
1810 def getHoles(self):
1811 """
1812 Returns the holes in the volume.
1813 """
1814 return self.__holes
1815
1816 def getSurfaceLoop(self):
1817 """
1818 Returns the loop forming the surface.
1819 """
1820 return self.__loop
1821
1822 def substitute(self,sub_dict):
1823 """
1824 Returns a copy of self with substitutes for the primitives used to
1825 construct it given by the dictionary ``sub_dict``. If a substitute for
1826 the object is given by ``sub_dict`` the value is returned, otherwise a
1827 new instance with substituted arguments is returned.
1828 """
1829 if not sub_dict.has_key(self):
1830 sub_dict[self]=Volume(self.getSurfaceLoop().substitute(sub_dict),[ h.substitute(sub_dict) for h in self.getHoles()])
1831 return sub_dict[self]
1832
1833 def isColocated(self,primitive):
1834 """
1835 Returns True if each curve is colocated with a curve in ``primitive``.
1836 """
1837 if hasattr(primitive,"getUnderlyingPrimitive"):
1838 if isinstance(primitive.getUnderlyingPrimitive(),Volume):
1839 if self.getSurfaceLoop().isColocated(primitive.getSurfaceLoop()):
1840 hs0=self.getHoles()
1841 hs1=primitive.getHoles()
1842 if len(hs0) == len(hs1):
1843 for h0 in hs0:
1844 colocated = False
1845 for h1 in hs1:
1846 colocated = colocated or h0.isColocated(h1)
1847 if not colocated: return False
1848 return True
1849 return False
1850
1851 def collectPrimitiveBases(self):
1852 """
1853 Returns primitives used to construct the surface.
1854 """
1855 out=[self] + self.getSurfaceLoop().collectPrimitiveBases()
1856 for i in self.getHoles(): out+=i.collectPrimitiveBases()
1857 return out
1858
1859 def getBoundary(self):
1860 """
1861 Returns a list of the 2-dimensional manifolds forming the surface of the Volume (including holes).
1862 """
1863 out = []+ self.getSurfaceLoop().getSurfaces()
1864 for h in self.getHoles(): out+=h.getSurfaces()
1865 return out
1866
1867 def hasHole(self):
1868 """
1869 Returns True if a hole is present.
1870 """
1871 return len(self.getHoles())>0
1872 class PropertySet(Primitive, PrimitiveBase):
1873 """
1874 Defines a group of `Primitive` s which can be accessed through a name.
1875 """
1876 def __init__(self,name,*items):
1877 Primitive.__init__(self)
1878 self.__dim=None
1879 self.clearItems()
1880 self.addItem(*items)
1881 self.setName(name)
1882
1883 def getDim(self):
1884 """
1885 Returns the dimensionality of the items.
1886 """
1887 if self.__dim == None:
1888 items=self.getItems()
1889 if len(items)>0:
1890 if isinstance(items[0] ,Manifold1D):
1891 self.__dim=1
1892 elif isinstance(items[0] ,Manifold2D):
1893 self.__dim=2
1894 elif isinstance(items[0] ,Manifold3D):
1895 self.__dim=3
1896 else:
1897 self.__dim=0
1898 return self.__dim
1899
1900 def __repr__(self):
1901 """
1902 Returns a string representation.
1903 """
1904 return "%s(%s)"%(self.getName(),self.getID())
1905
1906 def getManifoldClass(self):
1907 """
1908 Returns the manifold class expected from items.
1909 """
1910 d=self.getDim()
1911 if d == None:
1912 raise ValueError("undefined spatial diemnsion.")
1913 else:
1914 if d==0:
1915 return Point
1916 elif d==1:
1917 return Manifold1D
1918 elif d==2:
1919 return Manifold2D
1920 else:
1921 return Manifold3D
1922
1923 def getName(self):
1924 """
1925 Returns the name of the set.
1926 """
1927 return self.__name
1928
1929 def setName(self,name):
1930 """
1931 Sets the name.
1932 """
1933 self.__name=str(name)
1934
1935 def addItems(self,*items):
1936 """
1937 Adds items. An item my be any `Primitive` but no `PropertySet`.
1938 """
1939 self.addItem(*items)
1940
1941 def addItem(self,*items):
1942 """
1943 Adds items. An item my be any `Primitive` but no `PropertySet`.
1944 """
1945 for i in items:
1946 if not (isinstance(i, Manifold1D) or isinstance(i, Manifold2D) or isinstance(i, Manifold3D) ):
1947 raise TypeError, "Illegal argument type %s added to PropertySet."%(i.__class__)
1948 for i in items:
1949 if not i in self.__items:
1950 if len(self.__items)>0:
1951 m=self.getManifoldClass()
1952 if not isinstance(i, m):
1953 raise TypeError("argument %s is not a %s class object."%(i, m.__name__))
1954 self.__items.append(i)
1955
1956 def getNumItems(self):
1957 """
1958 Returns the number of items in the property set.
1959 """
1960 return len(self.__items)
1961
1962 def getItems(self):
1963 """
1964 Returns the list of items.
1965 """
1966 return self.__items
1967
1968 def clearItems(self):
1969 """
1970 Clears the list of items.
1971 """
1972 self.__items=[]
1973
1974 def collectPrimitiveBases(self):
1975 """
1976 Returns primitives used to construct the PropertySet.
1977 """
1978 out=[self]
1979 for i in self.getItems(): out+=i.collectPrimitiveBases()
1980 return out
1981
1982 def getTag(self):
1983 """
1984 Returns the tag used for this property set.
1985 """
1986 return self.getID()
1987

  ViewVC Help
Powered by ViewVC 1.1.26