2626from OCP .TopLoc import TopLoc_Location
2727from OCP .BinTools import BinTools_LocationSet
2828
29+ from multimethod import multidispatch
30+
2931from ..types import Real
3032from ..utils import multimethod
3133
@@ -579,20 +581,17 @@ def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
579581 plane ._setPlaneDir (xDir )
580582 return plane
581583
584+ # Prefer multidispatch over multimethod, as that supports keyword
585+ # arguments. These are in use, since Plane.__init__ has not always
586+ # been a multimethod.
587+ @multidispatch
582588 def __init__ (
583589 self ,
584- origin : Union [Tuple [float , float , float ], Vector ],
585- xDir : Optional [Union [Tuple [float , float , float ], Vector ]] = None ,
586- normal : Union [Tuple [float , float , float ], Vector ] = (0 , 0 , 1 ),
590+ origin : Union [Tuple [Real , Real , Real ], Vector ],
591+ xDir : Optional [Union [Tuple [Real , Real , Real ], Vector ]] = None ,
592+ normal : Union [Tuple [Real , Real , Real ], Vector ] = (0 , 0 , 1 ),
587593 ):
588- """
589- Create a Plane with an arbitrary orientation
590-
591- :param origin: the origin in global coordinates
592- :param xDir: an optional vector representing the xDirection.
593- :param normal: the normal direction for the plane
594- :raises ValueError: if the specified xDir is not orthogonal to the provided normal
595- """
594+ """Create a Plane from origin in global coordinates, vector xDir, and normal direction for the plane."""
596595 zDir = Vector (normal )
597596 if zDir .Length == 0.0 :
598597 raise ValueError ("normal should be non null" )
@@ -610,6 +609,50 @@ def __init__(
610609 self ._setPlaneDir (xDir )
611610 self .origin = Vector (origin )
612611
612+ @__init__ .register
613+ def __init__ (
614+ self , loc : "Location" ,
615+ ):
616+ """Create a Plane from Location loc."""
617+
618+ # Ask location for its information
619+ origin , rotations = loc .toTuple ()
620+
621+ # Origin is easy, but the rotational angles of the location need to be
622+ # turned into xDir and normal vectors.
623+ # This is done by multiplying a standard cooridnate system by the given
624+ # angles.
625+ # Rotation of vectors is done by a transformation matrix.
626+ # The order in which rotational angles are introduced is crucial:
627+ # If u is our vector, Rx is rotation around x axis etc, we want the
628+ # following:
629+ # u' = Rz * Ry * Rx * u = R * u
630+ # That way, all rotational angles refer to a global coordinate system,
631+ # and e.g. Ry does not refer to a rotation direction, which already
632+ # was rotated around Rx.
633+ # This definition in the global system is called extrinsic, and it is
634+ # how the Location class wants it to be done.
635+ # And this is why we introduce the rotations from left to right
636+ # and from Z to X.
637+ transformation = Matrix ()
638+ transformation .rotateZ (rotations [2 ] * pi / 180.0 )
639+ transformation .rotateY (rotations [1 ] * pi / 180.0 )
640+ transformation .rotateX (rotations [0 ] * pi / 180.0 )
641+
642+ # Apply rotation on vectors of the global plane
643+ # These vectors are already unit vectors and require no .normalized()
644+ globaldirs = ((1 , 0 , 0 ), (0 , 0 , 1 ))
645+ localdirs = (Vector (* i ).transform (transformation ) for i in globaldirs )
646+
647+ # Unpack vectors
648+ xDir , normal = localdirs
649+
650+ # Apply attributes as in other constructor.
651+ # Rememeber to set zDir before calling _setPlaneDir.
652+ self .zDir = normal
653+ self ._setPlaneDir (xDir )
654+ self .origin = origin
655+
613656 def _eq_iter (self , other ):
614657 """Iterator to successively test equality"""
615658 cls = type (self )
@@ -1012,15 +1055,22 @@ def __init__(
10121055 ) -> None :
10131056 """Location with translation (x,y,z) and 3 rotation angles."""
10141057
1015- T = gp_Trsf ()
1058+ if any (( x , y , z , rx , ry , rz )):
10161059
1017- q = gp_Quaternion ()
1018- q .SetEulerAngles (gp_Extrinsic_XYZ , radians (rx ), radians (ry ), radians (rz ))
1060+ T = gp_Trsf ()
10191061
1020- T . SetRotation ( q )
1021- T . SetTranslationPart ( Vector ( x , y , z ). wrapped )
1062+ q = gp_Quaternion ( )
1063+ q . SetEulerAngles ( gp_Extrinsic_XYZ , radians ( rx ), radians ( ry ), radians ( rz ) )
10221064
1023- self .wrapped = TopLoc_Location (T )
1065+ T .SetRotation (q )
1066+ T .SetTranslationPart (Vector (x , y , z ).wrapped )
1067+
1068+ loc = TopLoc_Location (T )
1069+ else :
1070+ # in this case location is flagged as identity
1071+ loc = TopLoc_Location ()
1072+
1073+ self .wrapped = loc
10241074
10251075 @__init__ .register
10261076 def __init__ (self , t : Plane ) -> None :
@@ -1104,6 +1154,11 @@ def toTuple(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float
11041154
11051155 return rv_trans , (degrees (rx ), degrees (ry ), degrees (rz ))
11061156
1157+ @property
1158+ def plane (self ) -> "Plane" :
1159+
1160+ return Plane (self )
1161+
11071162 def __getstate__ (self ) -> BytesIO :
11081163
11091164 rv = BytesIO ()
@@ -1121,7 +1176,7 @@ def __setstate__(self, data: BytesIO):
11211176 ls = BinTools_LocationSet ()
11221177 ls .Read (data )
11231178
1124- if ls .NbLocations () == 0 :
1125- self .wrapped = TopLoc_Location ()
1126- else :
1179+ if ls .NbLocations () > 0 :
11271180 self .wrapped = ls .Location (1 )
1181+ else :
1182+ self .wrapped = TopLoc_Location () # identity location
0 commit comments