/[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 6939 - (show annotations)
Mon Jan 20 03:37:18 2020 UTC (3 years, 2 months ago) by uqaeller
File MIME type: text/x-python
File size: 68672 byte(s)
Updated the copyright header.


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

  ViewVC Help
Powered by ViewVC 1.1.26