/[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 6651 - (show annotations)
Wed Feb 7 02:12:08 2018 UTC (19 months, 1 week ago) by jfenwick
File MIME type: text/x-python
File size: 68602 byte(s)
Make everyone sad by touching all the files

Copyright dates update

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

  ViewVC Help
Powered by ViewVC 1.1.26