2828
2929import spatialmath .base as smb
3030from spatialmath .base .types import *
31+ from spatialmath .base .vectors import orthogonalize
3132from spatialmath .baseposematrix import BasePoseMatrix
3233from spatialmath .pose2d import SE2
3334
@@ -337,7 +338,7 @@ def eulervec(self) -> R3:
337338 """
338339 theta , v = smb .tr2angvec (self .R )
339340 return theta * v
340-
341+
341342 # ------------------------------------------------------------------------ #
342343
343344 @staticmethod
@@ -651,8 +652,10 @@ def TwoVectors(
651652 axes in terms of the old axes. Axes are denoted by strings ``"x"``,
652653 ``"y"``, ``"z"``, ``"-x"``, ``"-y"``, ``"-z"``.
653654
654- The directions can also be specified by 3-element vectors, but these
655- must be orthogonal.
655+ The directions can also be specified by 3-element vectors. If the vectors are not orthogonal,
656+ they will orthogonalized w.r.t. the first available dimension. I.e. if x is available, it will be
657+ normalized and the remaining vector will be orthogonalized w.r.t. x, else, y will be normalized
658+ and z will be orthogonalized w.r.t. y.
656659
657660 To create a rotation where the new frame has its x-axis in -z-direction
658661 of the previous frame, and its z-axis in the x-direction of the previous
@@ -679,25 +682,41 @@ def vval(v):
679682 else :
680683 return smb .unitvec (smb .getvector (v , 3 ))
681684
682- if x is not None and y is not None and z is None :
685+ if x is not None and y is not None and z is not None :
686+ raise ValueError (
687+ "Only two vectors should be provided. Please set one to None."
688+ )
689+
690+ elif x is not None and y is not None and z is None :
683691 # z = x x y
684692 x = vval (x )
685693 y = vval (y )
694+ # Orthogonalizes y w.r.t. x
695+ y = orthogonalize (y , x , normalize = True )
686696 z = np .cross (x , y )
687697
688698 elif x is None and y is not None and z is not None :
689699 # x = y x z
690700 y = vval (y )
691701 z = vval (z )
702+ # Orthogonalizes z w.r.t. y
703+ z = orthogonalize (z , y , normalize = True )
692704 x = np .cross (y , z )
693705
694706 elif x is not None and y is None and z is not None :
695707 # y = z x x
696708 z = vval (z )
697709 x = vval (x )
710+ # Orthogonalizes z w.r.t. x
711+ z = orthogonalize (z , x , normalize = True )
698712 y = np .cross (z , x )
699713
700- return cls (np .c_ [x , y , z ], check = False )
714+ else :
715+ raise ValueError (
716+ "Insufficient number of vectors. Please provide exactly two vectors."
717+ )
718+
719+ return cls (np .c_ [x , y , z ], check = True )
701720
702721 @classmethod
703722 def AngleAxis (cls , theta : float , v : ArrayLike3 , * , unit : str = "rad" ) -> Self :
@@ -1190,11 +1209,11 @@ def yaw_SE2(self, order: str = "zyx") -> SE2:
11901209 """
11911210 if len (self ) == 1 :
11921211 if order == "zyx" :
1193- return SE2 (self .x , self .y , self .rpy (order = order )[2 ])
1212+ return SE2 (self .x , self .y , self .rpy (order = order )[2 ])
11941213 elif order == "xyz" :
1195- return SE2 (self .z , self .y , self .rpy (order = order )[2 ])
1214+ return SE2 (self .z , self .y , self .rpy (order = order )[2 ])
11961215 elif order == "yxz" :
1197- return SE2 (self .z , self .x , self .rpy (order = order )[2 ])
1216+ return SE2 (self .z , self .x , self .rpy (order = order )[2 ])
11981217 else :
11991218 return SE2 ([e .yaw_SE2 () for e in self ])
12001219
@@ -1938,11 +1957,7 @@ def Rt(
19381957 return cls (smb .rt2tr (R , t , check = check ), check = check )
19391958
19401959 @classmethod
1941- def CopyFrom (
1942- cls ,
1943- T : SE3Array ,
1944- check : bool = True
1945- ) -> SE3 :
1960+ def CopyFrom (cls , T : SE3Array , check : bool = True ) -> SE3 :
19461961 """
19471962 Create an SE(3) from a 4x4 numpy array that is passed by value.
19481963
0 commit comments