28
28
29
29
import spatialmath .base as smb
30
30
from spatialmath .base .types import *
31
+ from spatialmath .base .vectors import orthogonalize
31
32
from spatialmath .baseposematrix import BasePoseMatrix
32
33
from spatialmath .pose2d import SE2
33
34
@@ -337,7 +338,7 @@ def eulervec(self) -> R3:
337
338
"""
338
339
theta , v = smb .tr2angvec (self .R )
339
340
return theta * v
340
-
341
+
341
342
# ------------------------------------------------------------------------ #
342
343
343
344
@staticmethod
@@ -651,8 +652,10 @@ def TwoVectors(
651
652
axes in terms of the old axes. Axes are denoted by strings ``"x"``,
652
653
``"y"``, ``"z"``, ``"-x"``, ``"-y"``, ``"-z"``.
653
654
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.
656
659
657
660
To create a rotation where the new frame has its x-axis in -z-direction
658
661
of the previous frame, and its z-axis in the x-direction of the previous
@@ -679,25 +682,41 @@ def vval(v):
679
682
else :
680
683
return smb .unitvec (smb .getvector (v , 3 ))
681
684
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 :
683
691
# z = x x y
684
692
x = vval (x )
685
693
y = vval (y )
694
+ # Orthogonalizes y w.r.t. x
695
+ y = orthogonalize (y , x , normalize = True )
686
696
z = np .cross (x , y )
687
697
688
698
elif x is None and y is not None and z is not None :
689
699
# x = y x z
690
700
y = vval (y )
691
701
z = vval (z )
702
+ # Orthogonalizes z w.r.t. y
703
+ z = orthogonalize (z , y , normalize = True )
692
704
x = np .cross (y , z )
693
705
694
706
elif x is not None and y is None and z is not None :
695
707
# y = z x x
696
708
z = vval (z )
697
709
x = vval (x )
710
+ # Orthogonalizes z w.r.t. x
711
+ z = orthogonalize (z , x , normalize = True )
698
712
y = np .cross (z , x )
699
713
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 )
701
720
702
721
@classmethod
703
722
def AngleAxis (cls , theta : float , v : ArrayLike3 , * , unit : str = "rad" ) -> Self :
@@ -1190,11 +1209,11 @@ def yaw_SE2(self, order: str = "zyx") -> SE2:
1190
1209
"""
1191
1210
if len (self ) == 1 :
1192
1211
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 ])
1194
1213
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 ])
1196
1215
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 ])
1198
1217
else :
1199
1218
return SE2 ([e .yaw_SE2 () for e in self ])
1200
1219
@@ -1938,11 +1957,7 @@ def Rt(
1938
1957
return cls (smb .rt2tr (R , t , check = check ), check = check )
1939
1958
1940
1959
@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 :
1946
1961
"""
1947
1962
Create an SE(3) from a 4x4 numpy array that is passed by value.
1948
1963
0 commit comments