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

  ViewVC Help
Powered by ViewVC 1.1.26