6
6
from collections import UserList
7
7
8
8
from spatialmath .base import argcheck as arg
9
- from spatialmath import base as sm
9
+ import spatialmath . base as base
10
10
import matplotlib .pyplot as plt
11
11
from mpl_toolkits .mplot3d import Axes3D
12
12
from spatialmath import SE3
@@ -227,16 +227,14 @@ def __init__(self, v=None, w=None):
227
227
:seealso: Plucker.PQ, Plucker.Planes, Plucker.PointDir
228
228
"""
229
229
super ().__init__ () # enable list powers
230
+
230
231
if w is None :
231
- # single parameter
232
- if isinstance (v , Plucker ):
233
- self .data = [v .A ]
234
- elif arg .isvector (v , 6 ):
235
- pl = arg .getvector (v )
236
- self .data = [pl ]
237
- else :
238
- raise ValueError ('bad argument' )
232
+ # zero or one arguments passed
233
+ if super ().arghandler (v , convertfrom = (SE3 ,)):
234
+ return
235
+
239
236
else :
237
+ # additional arguments
240
238
assert arg .isvector (v , 3 ) and arg .isvector (w , 3 ), 'expecting two 3-vectors'
241
239
self .data = [np .r_ [v , w ]]
242
240
@@ -252,8 +250,8 @@ def _identity():
252
250
return np .zeros ((6 ,))
253
251
254
252
@staticmethod
255
- def isvalid (x ):
256
- return x .shape == self . shape
253
+ def isvalid (x , check = False ):
254
+ return x .shape == ( 6 ,)
257
255
258
256
@staticmethod
259
257
def PQ (P = None , Q = None ):
@@ -396,7 +394,7 @@ def uw(self):
396
394
397
395
``line.uw`` is a unit-vector parallel to the line.
398
396
"""
399
- return sm .unitvec (self .w )
397
+ return base .unitvec (self .w )
400
398
401
399
@property
402
400
def vec (self ):
@@ -430,7 +428,8 @@ def skew(self):
430
428
given by :math:`\vee C M C^T` where :math:`C \in \mathbf{R}^{3 \times 4}` is the camera matrix.
431
429
"""
432
430
433
- v = self .v ; w = self .w ;
431
+ v = self .v
432
+ w = self .w
434
433
435
434
# the following matrix is at odds with H&Z pg. 72
436
435
return np .array ([
@@ -471,7 +470,7 @@ def ppd(self):
471
470
"""
472
471
return math .sqrt (np .dot (self .v , self .v ) / np .dot (self .w , self .w ) )
473
472
474
- def point (L , lam ):
473
+ d
B41A
ef point (self , lam ):
475
474
r"""
476
475
Generate point on line
477
476
@@ -488,7 +487,7 @@ def point(L, lam):
488
487
:seealso: Plucker.pp, Plucker.closest, Plucker.uw
489
488
"""
490
489
lam = arg .getvector (lam , out = 'row' )
491
- return L .pp .reshape ((3 ,1 )) + L .uw .reshape ((3 ,1 )) * lam
490
+ return self .pp .reshape ((3 ,1 )) + self .uw .reshape ((3 ,1 )) * lam
492
491
493
492
# ------------------------------------------------------------------------- #
494
493
# TESTS ON PLUCKER OBJECTS
@@ -520,7 +519,7 @@ def contains(self, x, tol=50*_eps):
520
519
else :
521
520
raise ValueError ('bad argument' )
522
521
523
- def __eq__ (l1 , l2 ):
522
+ def __eq__ (l1 , l2 ): # pylint: disable=no-self-argument
524
523
"""
525
524
Test if two lines are equivalent
526
525
@@ -536,9 +535,9 @@ def __eq__(l1, l2):
536
535
space. Note that because of the over parameterization, lines can be
537
536
equivalent even if their coordinate vectors are different.
538
537
"""
539
- return abs ( 1 - np .dot (sm .unitvec (l1 .vec ), sm .unitvec (l2 .vec ))) < 10 * _eps
538
+ return abs ( 1 - np .dot (base .unitvec (l1 .vec ), base .unitvec (l2 .vec ))) < 10 * _eps
540
539
541
- def __ne__ (l1 , l2 ):
540
+ def __ne__ (l1 , l2 ): # pylint: disable=no-self-argument
542
541
"""
543
542
Test if two lines are not equivalent
544
543
@@ -556,7 +555,7 @@ def __ne__(l1, l2):
556
555
557
556
return not l1 .__eq__ (l2 )
558
557
559
- def isparallel (l1 , l2 , tol = 10 * _eps ):
558
+ def isparallel (l1 , l2 , tol = 10 * _eps ): # pylint: disable=no-self-argument
560
559
"""
561
560
Test if lines are parallel
562
561
@@ -577,7 +576,7 @@ def isparallel(l1, l2, tol=10*_eps):
577
576
return np .linalg .norm (np .cross (l1 .w , l2 .w ) ) < tol
578
577
579
578
580
- def __or__ (l1 , l2 ):
579
+ def __or__ (l1 , l2 ): # pylint: disable=no-self-argument
581
580
"""
582
581
Test if lines are parallel as a binary operator
583
582
@@ -595,7 +594,7 @@ def __or__(l1, l2):
595
594
return l1 .isparallel (l2 )
596
595
597
596
598
- def __xor__ (l1 , l2 ):
597
+ def __xor__ (l1 , l2 ): # pylint: disable=no-self-argument
599
598
600
599
"""
601
600
Test if lines intersect as a binary operator
@@ -623,7 +622,7 @@ def __xor__(l1, l2):
623
622
# ------------------------------------------------------------------------- #
624
623
625
624
626
- def intersects (l1 , l2 ):
625
+ def intersects (l1 , l2 ): # pylint: disable=no-self-argument
627
626
"""
628
627
Intersection point of two lines
629
628
@@ -644,12 +643,12 @@ def intersects(l1, l2):
644
643
# lines do intersect
645
644
return - (np .dot (l1 .v , l2 .w ) * np .eye (3 , 3 ) + \
646
645
l1 .w .reshape ((3 ,1 )) @ l2 .v .reshape ((1 ,3 )) - \
647
- l2 .w .reshape ((3 ,1 )) @ l1 .v .reshape ((1 ,3 ))) * sm .unitvec (np .cross (l1 .w , l2 .w ))
646
+ l2 .w .reshape ((3 ,1 )) @ l1 .v .reshape ((1 ,3 ))) * base .unitvec (np .cross (l1 .w , l2 .w ))
648
647
else :
649
648
# lines don't intersect
650
649
return None
651
650
652
- def distance (l1 , l2 ):
651
+ def distance (l1 , l2 ): # pylint: disable=no-self-argument
653
652
"""
654
653
Minimum distance between lines
655
654
@@ -680,7 +679,7 @@ def distance(l1, l2):
680
679
return l
681
680
682
681
683
- def closest (line , x ):
682
+ def closest (self , x ):
684
683
"""
685
684
Point on line closest to given point
686
685
@@ -709,14 +708,14 @@ def closest(line, x):
709
708
710
709
x = arg .getvector (x , 3 )
711
710
712
- lam = np .dot (x - line .pp , line .uw )
713
- p = line .point (lam ).flatten () # is the closest point on the line
711
+ lam = np .dot (x - self .pp , self .uw )
712
+ p = self .point (lam ).flatten () # is the closest point on the line
714
713
d = np .linalg .norm ( x - p )
715
714
716
715
return namedtuple ('closest' , 'p d lam' )(p , d , lam )
717
716
718
717
719
- def commonperp (l1 , l2 ):
718
+ def commonperp (l1 , l2 ): # pylint: disable=no-self-argument
720
719
"""
721
720
Common perpendicular to two lines
722
721
@@ -740,12 +739,12 @@ def commonperp(l1, l2):
740
739
# lines are skew or intersecting
741
740
w = np .cross (l1 .w , l2 .w )
742
741
v = np .cross (l1 .v , l2 .w ) - np .cross (l2 .v , l1 .w ) + \
743
- (l1 * l2 ) * np .dot (l1 .w , l2 .w ) * sm .unitvec (np .cross (l1 .w , l2 .w ))
742
+ (l1 * l2 ) * np .dot (l1 .w , l2 .w ) * base .unitvec (np .cross (l1 .w , l2 .w ))
744
743
745
744
return Plucker (v , w )
746
745
747
746
748
- def __mul__ (left , right ):
747
+ def __mul__ (left , right ): # pylint: disable=no-self-argument
749
748
r"""
750
749
Reciprocal product
751
750
@@ -771,7 +770,7 @@ def __mul__(left, right):
771
770
else :
772
771
raise ValueError ('bad arguments' )
773
772
774
- def __rmul__ (right , left ):
773
+ def __rmul__ (right , left ): # pylint: disable=no-self-argument
775
774
"""
776
775
Line transformation
777
776
@@ -788,7 +787,7 @@ def __rmul__(right, left):
788
787
:seealso: Plucker.__mul__
789
788
"""
790
789
if isinstance (left , SE3 ):
791
- A = np .r_ [ np .c_ [left .R , sm .skew (- left .t ) @ left .R ],
790
+ A = np .r_ [ np .c_ [left .R , base .skew (- left .t ) @ left .R ],
792
791
np .c_ [np .zeros ((3 ,3 )), left .R ]
793
792
]
794
793
return Plucker ( A @ right .vec ) # premultiply by SE3
@@ -800,7 +799,7 @@ def __rmul__(right, left):
800
799
# ------------------------------------------------------------------------- #
801
800
802
801
803
- def intersect_plane (line , plane ):
802
+ def intersect_plane (line , plane ): # pylint: disable=no-self-argument
804
803
r"""
805
804
Line intersection with a plane
806
805
@@ -851,7 +850,7 @@ def intersect_plane(line, plane):
851
850
else :
852
851
return None
853
852
854
- def intersect_volume (line , bounds ):
853
+ def intersect_volume (self , bounds ):
855
854
"""
856
855
Line intersection with a volume
857
856
@@ -890,6 +889,14 @@ def intersect_volume(line, bounds):
890
889
for face in range (0 , 6 ):
891
890
# for each face of the bounding volume
892
891
# x=xmin, x=xmax, y=ymin, y=ymax, z=zmin, z=zmax
892
+
893
+ # planes are:
894
+ # 0 normal in x direction, xmin
895
+ # 1 normal in x direction, xmax
896
+ # 2 normal in y direction, ymin
897
+ # 3 normal in y direction, ymax
898
+ # 4 normal in z direction, zmin
899
+ # 5 normal in z direction, zmax
893
900
894
901
i = face // 2 # 0, 1, 2
895
902
I = np .eye (3 ,3 )
@@ -899,14 +906,15 @@ def intersect_volume(line, bounds):
899
906
900
907
# find where line pierces the plane
901
908
try :
902
- p , lam = line .intersect_plane (plane )
909
+ p , lam = self .intersect_plane (plane )
903
910
except TypeError :
904
911
continue # no intersection with this plane
905
912
906
- # # print('face %d: n=(%f, %f, %f), p=(%f, %f, %f) ' % (face, plane.n, plane.p ))
907
- # print(' : p=(%f, %f, %f) ' % p )
913
+ # print('face %d: n=(%f, %f, %f)' % (face, plane.n[0] , plane.n[1], plane.n[2] ))
914
+ # print(' : p=(%f, %f, %f) ' % (p[0], p[1], p[2]) )
908
915
909
916
# print('face', face, ' point ', p, ' plane ', plane)
917
+ # print('lamda', lam, self.point(lam))
910
918
# find if intersection point is within the cube face
911
919
# test x,y,z simultaneously
912
920
k = (p >= bounds23 [:,0 ]) & (p <= bounds23 [:,1 ])
@@ -919,8 +927,7 @@ def intersect_volume(line, bounds):
919
927
920
928
# put them in ascending order
921
929
intersections .sort ()
922
-
923
- p = line .point (intersections )
930
+ p = self .point (intersections )
924
931
925
932
return namedtuple ('intersect_volume' , 'p lam' )(p , intersections )
926
933
@@ -929,7 +936,7 @@ def intersect_volume(line, bounds):
929
936
# PLOT AND DISPLAY
930
937
# ------------------------------------------------------------------------- #
931
938
932
- def plot (line , bounds = None , ** kwargs ):
939
+ def plot (self , * pos , bounds = None , axis = None , ** kwargs ):
933
940
"""
934
941
Plot a line
935
942
@@ -954,24 +961,32 @@ def plot(line, bounds=None, **kwargs):
954
961
955
962
:seealso: Plucker.intersect_volume
956
963
"""
957
-
958
- if bounds is None :
964
+ if axis is None :
959
965
ax = plt .gca ()
966
+ else :
967
+ ax = axis
968
+
969
+ if bounds is None :
960
970
bounds = np .r_ [ax .get_xlim (), ax .get_ylim (), ax .get_zlim ()]
961
971
else :
972
+ bounds = base .getvector (bounds , 6 )
962
973
ax .set_xlim (bounds [:2 ])
963
974
ax .set_ylim (bounds [2 :4 ])
964
975
ax .set_zlim (bounds [4 :6 ])
976
+
977
+ # print(bounds)
965
978
966
979
#U = self.Q - self.P;
967
980
#line.p = self.P; line.v = unit(U);
968
981
969
- P , lam = line .intersect_volume (bounds )
970
-
971
- if len (lam ) > 0 :
972
- return ax .plot (P [0 ,:], P [1 ,:], P [2 ,:], ** kwargs )
973
- else :
974
- return None
982
+ lines = []
983
+ for line in self :
984
+ P , lam = line .intersect_volume (bounds )
985
+
986
+ if len (lam ) > 0 :
987
+ l = ax .plot3D (P [0 ,:], P [1 ,:], P [2 ,:], * pos , ** kwargs )
988
+ lines .append (l )
989
+ return lines
975
990
976
991
def __str__ (self ):
977
992
"""
@@ -990,7 +1005,7 @@ def __str__(self):
990
1005
991
1006
"""
992
1007
993
- return '\n ' .join (['{{ {:.5g} {:.5g} {:.5g}; {:.5g} {:.5g} {:.5g}}}' .format (* list (x .vec )) for x in self ])
1008
+ return '\n ' .join (['{{ {:.5g} {:.5g} {:.5g}; {:.5g} {:.5g} {:.5g}}}' .format (* list (base . removesmall ( x .vec ) )) for x in self ])
994
1009
995
1010
def __repr__ (self ):
996
1011
"""
@@ -1016,7 +1031,7 @@ def __repr__(self):
1016
1031
1017
1032
def _repr_pretty_ (self , p , cycle ):
1018
1033
"""
1019
- Pretty string for IPython (superclass method)
1034
+ Pretty string for IPython
1020
1035
1021
1036
:param p: pretty printer handle (ignored)
1022
1037
:param cycle: pretty printer flag (ignored)
@@ -1029,7 +1044,16 @@ def _repr_pretty_(self, p, cycle):
1029
1044
In [1]: x
1030
1045
1031
1046
"""
1032
- print (self .__str__ ())
1047
+ if len (self ) == 1 :
1048
+ p .begin_group (8 , 'Plücker ' )
1049
+ p .text (str (self ))
1050
+ p .end_group (8 , '' )
1051
+ else :
1052
+ p .begin_group (8 , 'Plücker(' )
1053
+ for i , x in enumerate (self ):
1054
+ p .break_ ()
1055
+ p .text (str (x ))
1056
+ p .end_group (8 , ')' )
1033
1057
1034
1058
# function z = side(self1, pl2)
1035
1059
# Plucker.side Plucker side operator
0 commit comments