/[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 4286 - (show annotations)
Thu Mar 7 04:28:11 2013 UTC (6 years, 5 months ago) by caltinay
File MIME type: text/x-python
File size: 68437 byte(s)
Assorted spelling fixes.

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

  ViewVC Help
Powered by ViewVC 1.1.26