roboticstoolbox-python 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roboticstoolbox/__init__.py +107 -0
- roboticstoolbox/backends/Connector.py +113 -0
- roboticstoolbox/backends/Dynamixel/README.md +9 -0
- roboticstoolbox/backends/Dynamixel/dynamixel.json +581 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_io.py +450 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/LICENSE +201 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/README.md +28 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/ReleaseNote.md +181 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/__init__.py +27 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/group_bulk_read.py +163 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/group_bulk_write.py +109 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/group_sync_read.py +166 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/group_sync_write.py +99 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/packet_handler.py +33 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/port_handler.py +155 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/protocol1_packet_handler.py +548 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/protocol2_packet_handler.py +1080 -0
- roboticstoolbox/backends/Dynamixel/dynamixel_sdk/robotis_def.py +75 -0
- roboticstoolbox/backends/Dynamixel/dyndata.py +121 -0
- roboticstoolbox/backends/PyPlot/EllipsePlot.py +256 -0
- roboticstoolbox/backends/PyPlot/PyPlot.py +778 -0
- roboticstoolbox/backends/PyPlot/PyPlot2.py +537 -0
- roboticstoolbox/backends/PyPlot/README.md +67 -0
- roboticstoolbox/backends/PyPlot/RobotPlot.py +247 -0
- roboticstoolbox/backends/PyPlot/RobotPlot2.py +123 -0
- roboticstoolbox/backends/PyPlot/__init__.py +4 -0
- roboticstoolbox/backends/ROS/ROS.py +129 -0
- roboticstoolbox/backends/ROS/__init__.py +3 -0
- roboticstoolbox/backends/__init__.py +39 -0
- roboticstoolbox/backends/swift/__init__.py +165 -0
- roboticstoolbox/bin/__init__.py +0 -0
- roboticstoolbox/bin/_bintools.py +61 -0
- roboticstoolbox/bin/rtbtool.py +404 -0
- roboticstoolbox/blocks/Icons/250x250/armplot.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/bicycle.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/camera.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/circlepath.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/coriolis.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/ctraj.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/delta2tr.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/diffsteer.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/fdyn.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/fdynx.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/fkine.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/gravload.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/idyn.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/idynx.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/ikine.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/inertia.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/jacobian.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/jtraj.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/lspb.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/multirotor.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/multirotormixer.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/multirotorplot.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/point2tr.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/tr2delta.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/tr2t.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/unicycle.png +0 -0
- roboticstoolbox/blocks/Icons/250x250/vehicleplot.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/armplot.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/bicycle.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/camera.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/circlepath.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/coriolis.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/delta2tr.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/diffsteer.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/fdyn.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/fdynx.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/fkine.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/gravload.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/idyn.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/idynx.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/ikine.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/inertia.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/jacobian.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/jtraj.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/lspb.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/multirotor.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/multirotormixer.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/multirotorplot.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/point2tr.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/tr2delta.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/tr2t.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/unicycle.png +0 -0
- roboticstoolbox/blocks/Icons/50x50/vehicleplot.png +0 -0
- roboticstoolbox/blocks/Icons/armplot.png +0 -0
- roboticstoolbox/blocks/Icons/bicycle.png +0 -0
- roboticstoolbox/blocks/Icons/camera.png +0 -0
- roboticstoolbox/blocks/Icons/circlepath.png +0 -0
- roboticstoolbox/blocks/Icons/coriolis.png +0 -0
- roboticstoolbox/blocks/Icons/ctraj.png +0 -0
- roboticstoolbox/blocks/Icons/delta2tr.png +0 -0
- roboticstoolbox/blocks/Icons/diffsteer.png +0 -0
- roboticstoolbox/blocks/Icons/fdyn.png +0 -0
- roboticstoolbox/blocks/Icons/fdynx.png +0 -0
- roboticstoolbox/blocks/Icons/fkine.png +0 -0
- roboticstoolbox/blocks/Icons/gravload.png +0 -0
- roboticstoolbox/blocks/Icons/idyn.png +0 -0
- roboticstoolbox/blocks/Icons/idynx.png +0 -0
- roboticstoolbox/blocks/Icons/ikine.png +0 -0
- roboticstoolbox/blocks/Icons/inertia.png +0 -0
- roboticstoolbox/blocks/Icons/jacobian.png +0 -0
- roboticstoolbox/blocks/Icons/jtraj.png +0 -0
- roboticstoolbox/blocks/Icons/lspb.png +0 -0
- roboticstoolbox/blocks/Icons/multirotor.png +0 -0
- roboticstoolbox/blocks/Icons/multirotormixer.png +0 -0
- roboticstoolbox/blocks/Icons/multirotorplot.png +0 -0
- roboticstoolbox/blocks/Icons/point2tr.png +0 -0
- roboticstoolbox/blocks/Icons/tr2delta.png +0 -0
- roboticstoolbox/blocks/Icons/tr2t.png +0 -0
- roboticstoolbox/blocks/Icons/unicycle.png +0 -0
- roboticstoolbox/blocks/Icons/vehicleplot.png +0 -0
- roboticstoolbox/blocks/README.md +43 -0
- roboticstoolbox/blocks/__init__.py +6 -0
- roboticstoolbox/blocks/arm.py +1591 -0
- roboticstoolbox/blocks/mobile.py +522 -0
- roboticstoolbox/blocks/quad_model.py +132 -0
- roboticstoolbox/blocks/spatial.py +245 -0
- roboticstoolbox/blocks/uav.py +949 -0
- roboticstoolbox/demo/__init__.py +0 -0
- roboticstoolbox/demo/eigdemo.py +112 -0
- roboticstoolbox/demo/tripleangledemo.py +239 -0
- roboticstoolbox/demo/twistdemo.py +175 -0
- roboticstoolbox/ets/ET.py +439 -0
- roboticstoolbox/ets/ET2.py +179 -0
- roboticstoolbox/ets/ETS.py +2013 -0
- roboticstoolbox/ets/ETS2.py +449 -0
- roboticstoolbox/ets/_ET.py +668 -0
- roboticstoolbox/ets/_ETS.py +781 -0
- roboticstoolbox/ets/__init__.py +11 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Cholesky +45 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/CholmodSupport +48 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Core +384 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Dense +7 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Eigen +2 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Eigenvalues +60 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Geometry +59 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Householder +29 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/IterativeLinearSolvers +48 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Jacobi +32 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/KLUSupport +41 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/LU +47 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/MetisSupport +35 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/OrderingMethods +70 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/PaStiXSupport +49 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/PardisoSupport +35 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/QR +50 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/QtAlignedMalloc +39 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SPQRSupport +34 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SVD +50 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/Sparse +34 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SparseCholesky +37 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SparseCore +69 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SparseLU +50 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SparseQR +36 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/StdDeque +27 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/StdList +26 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/StdVector +27 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/SuperLUSupport +64 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/UmfPackSupport +40 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Cholesky/LDLT.h +688 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Cholesky/LLT.h +558 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Cholesky/LLT_LAPACKE.h +99 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/CholmodSupport/CholmodSupport.h +682 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ArithmeticSequence.h +413 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Array.h +417 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ArrayBase.h +226 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ArrayWrapper.h +209 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Assign.h +90 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/AssignEvaluator.h +1010 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Assign_MKL.h +178 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/BandMatrix.h +353 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Block.h +448 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/BooleanRedux.h +162 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CommaInitializer.h +164 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ConditionEstimator.h +175 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CoreEvaluators.h +1741 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CoreIterators.h +132 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CwiseBinaryOp.h +183 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CwiseNullaryOp.h +1001 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CwiseTernaryOp.h +197 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CwiseUnaryOp.h +103 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/CwiseUnaryView.h +132 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/DenseBase.h +701 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/DenseCoeffsBase.h +685 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/DenseStorage.h +652 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Diagonal.h +258 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/DiagonalMatrix.h +391 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/DiagonalProduct.h +28 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Dot.h +318 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/EigenBase.h +160 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ForceAlignedAccess.h +150 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Fuzzy.h +155 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/GeneralProduct.h +465 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/GenericPacketMath.h +1040 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/GlobalFunctions.h +194 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/IO.h +258 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/IndexedView.h +237 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Inverse.h +117 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Map.h +171 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/MapBase.h +310 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/MathFunctions.h +2057 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/MathFunctionsImpl.h +200 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Matrix.h +565 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/MatrixBase.h +547 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/NestByValue.h +85 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/NoAlias.h +109 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/NumTraits.h +335 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/PartialReduxEvaluator.h +232 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/PermutationMatrix.h +605 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/PlainObjectBase.h +1128 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Product.h +191 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ProductEvaluators.h +1179 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Random.h +218 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Redux.h +515 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Ref.h +381 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Replicate.h +142 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Reshaped.h +454 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/ReturnByValue.h +119 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Reverse.h +217 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Select.h +164 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/SelfAdjointView.h +365 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/SelfCwiseBinaryOp.h +47 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Solve.h +188 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/SolveTriangular.h +235 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/SolverBase.h +168 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/StableNorm.h +251 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/StlIterators.h +463 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Stride.h +116 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Swap.h +68 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Transpose.h +464 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Transpositions.h +386 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/TriangularMatrix.h +1001 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/VectorBlock.h +96 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/VectorwiseOp.h +784 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/Visitor.h +381 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX/Complex.h +372 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX/MathFunctions.h +228 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX/PacketMath.h +1574 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX/TypeCasting.h +115 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX512/Complex.h +422 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX512/MathFunctions.h +362 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX512/PacketMath.h +2303 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AVX512/TypeCasting.h +89 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/Complex.h +417 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/MathFunctions.h +90 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/MatrixProduct.h +2937 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/MatrixProductCommon.h +221 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/MatrixProductMMA.h +629 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/AltiVec/PacketMath.h +2711 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/CUDA/Complex.h +258 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/BFloat16.h +700 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/ConjHelper.h +117 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/GenericPacketMathFunctions.h +1649 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/GenericPacketMathFunctionsFwd.h +110 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/Half.h +942 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/Settings.h +49 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/Default/TypeCasting.h +120 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/GPU/MathFunctions.h +103 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/GPU/PacketMath.h +1685 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/GPU/TypeCasting.h +80 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/HIP/hcc/math_constants.h +23 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/MSA/Complex.h +648 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/MSA/MathFunctions.h +387 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/MSA/PacketMath.h +1233 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/NEON/Complex.h +584 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/NEON/GeneralBlockPanelKernel.h +183 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/NEON/MathFunctions.h +75 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/NEON/PacketMath.h +4587 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/NEON/TypeCasting.h +1419 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SSE/Complex.h +351 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SSE/MathFunctions.h +199 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SSE/PacketMath.h +1505 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SSE/TypeCasting.h +142 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SVE/MathFunctions.h +44 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SVE/PacketMath.h +752 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SVE/TypeCasting.h +49 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SYCL/InteropHeaders.h +232 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SYCL/MathFunctions.h +301 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SYCL/PacketMath.h +670 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SYCL/SyclMemoryModel.h +694 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/SYCL/TypeCasting.h +85 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/ZVector/Complex.h +426 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/ZVector/MathFunctions.h +233 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/arch/ZVector/PacketMath.h +1060 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/AssignmentFunctors.h +177 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/BinaryFunctors.h +541 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/NullaryFunctors.h +189 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/StlFunctors.h +166 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/TernaryFunctors.h +25 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/functors/UnaryFunctors.h +1131 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralBlockPanelKernel.h +2645 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixMatrix.h +517 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixMatrixTriangular.h +317 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixMatrixTriangular_BLAS.h +145 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixMatrix_BLAS.h +124 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixVector.h +518 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/GeneralMatrixVector_BLAS.h +136 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/Parallelizer.h +180 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointMatrixMatrix.h +544 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointMatrixMatrix_BLAS.h +295 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointMatrixVector.h +262 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointMatrixVector_BLAS.h +118 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointProduct.h +133 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/SelfadjointRank2Update.h +94 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularMatrixMatrix.h +472 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularMatrixMatrix_BLAS.h +317 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularMatrixVector.h +350 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularMatrixVector_BLAS.h +255 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularSolverMatrix.h +337 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularSolverMatrix_BLAS.h +167 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/products/TriangularSolverVector.h +148 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/BlasUtil.h +583 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/ConfigureVectorization.h +512 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/Constants.h +563 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/DisableStupidWarnings.h +106 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/ForwardDeclarations.h +322 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/IndexedViewHelper.h +186 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/IntegralConstant.h +272 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/MKL_support.h +137 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/Macros.h +1464 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/Memory.h +1163 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/Meta.h +812 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/NonMPL2.h +3 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/ReenableStupidWarnings.h +31 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/ReshapedHelper.h +51 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/StaticAssert.h +221 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/SymbolicIndex.h +293 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Core/util/XprHelper.h +856 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/ComplexEigenSolver.h +346 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/ComplexSchur.h +462 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/ComplexSchur_LAPACKE.h +91 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/EigenSolver.h +622 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/GeneralizedEigenSolver.h +418 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/GeneralizedSelfAdjointEigenSolver.h +226 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/HessenbergDecomposition.h +374 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h +158 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/RealQZ.h +657 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/RealSchur.h +558 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/RealSchur_LAPACKE.h +77 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h +904 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/SelfAdjointEigenSolver_LAPACKE.h +87 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Eigenvalues/Tridiagonalization.h +561 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/AlignedBox.h +486 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/AngleAxis.h +247 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/EulerAngles.h +114 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Homogeneous.h +501 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Hyperplane.h +282 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/OrthoMethods.h +235 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/ParametrizedLine.h +232 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Quaternion.h +870 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Rotation2D.h +199 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/RotationBase.h +206 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Scaling.h +188 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Transform.h +1563 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Translation.h +202 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/Umeyama.h +166 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Geometry/arch/Geometry_SIMD.h +168 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Householder/BlockHouseholder.h +110 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Householder/Householder.h +176 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Householder/HouseholderSequence.h +545 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/BasicPreconditioners.h +226 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/BiCGSTAB.h +212 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/ConjugateGradient.h +229 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/IncompleteCholesky.h +394 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/IncompleteLUT.h +453 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/IterativeSolverBase.h +444 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/LeastSquareConjugateGradient.h +198 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/IterativeLinearSolvers/SolveWithGuess.h +117 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/Jacobi/Jacobi.h +483 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/KLUSupport/KLUSupport.h +358 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/Determinant.h +117 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/FullPivLU.h +877 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/InverseImpl.h +432 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/PartialPivLU.h +624 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/PartialPivLU_LAPACKE.h +83 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/LU/arch/InverseSize4.h +351 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/MetisSupport/MetisSupport.h +137 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/OrderingMethods/Amd.h +435 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/OrderingMethods/Eigen_Colamd.h +1863 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/OrderingMethods/Ordering.h +153 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/PaStiXSupport/PaStiXSupport.h +678 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/PardisoSupport/PardisoSupport.h +545 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/ColPivHouseholderQR.h +674 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/ColPivHouseholderQR_LAPACKE.h +97 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/CompleteOrthogonalDecomposition.h +635 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/FullPivHouseholderQR.h +713 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/HouseholderQR.h +434 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/QR/HouseholderQR_LAPACKE.h +68 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SPQRSupport/SuiteSparseQRSupport.h +335 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SVD/BDCSVD.h +1366 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SVD/JacobiSVD.h +812 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SVD/JacobiSVD_LAPACKE.h +91 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SVD/SVDBase.h +376 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SVD/UpperBidiagonalization.h +414 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCholesky/SimplicialCholesky.h +697 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h +174 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/AmbiVector.h +378 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/CompressedStorage.h +274 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/ConservativeSparseSparseProduct.h +352 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/MappedSparseMatrix.h +67 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseAssign.h +270 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseBlock.h +571 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseColEtree.h +206 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseCompressedBase.h +370 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseCwiseBinaryOp.h +722 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseCwiseUnaryOp.h +150 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseDenseProduct.h +342 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseDiagonalProduct.h +138 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseDot.h +98 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseFuzzy.h +29 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseMap.h +305 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseMatrix.h +1518 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseMatrixBase.h +398 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparsePermutation.h +178 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseProduct.h +181 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseRedux.h +49 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseRef.h +397 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseSelfAdjointView.h +659 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseSolverBase.h +124 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseSparseProductWithPruning.h +198 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseTranspose.h +92 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseTriangularView.h +189 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseUtil.h +186 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseVector.h +478 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/SparseView.h +254 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseCore/TriangularSolver.h +315 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU.h +923 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLUImpl.h +66 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_Memory.h +226 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_Structs.h +110 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h +375 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_Utils.h +80 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_column_bmod.h +181 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_column_dfs.h +179 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_copy_to_ucol.h +107 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_gemm_kernel.h +280 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h +126 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_kernel_bmod.h +130 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_panel_bmod.h +223 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_panel_dfs.h +258 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_pivotL.h +137 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_pruneL.h +136 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseLU/SparseLU_relax_snode.h +83 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SparseQR/SparseQR.h +758 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/StlSupport/StdDeque.h +116 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/StlSupport/StdList.h +106 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/StlSupport/StdVector.h +131 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/StlSupport/details.h +84 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/SuperLUSupport/SuperLUSupport.h +1025 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/UmfPackSupport/UmfPackSupport.h +642 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/Image.h +82 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/Kernel.h +79 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/RealSvd2x2.h +55 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/blas.h +440 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/lapack.h +152 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/lapacke.h +16292 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/misc/lapacke_mangling.h +17 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/ArrayCwiseBinaryOps.h +358 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/ArrayCwiseUnaryOps.h +696 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/BlockMethods.h +1442 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/CommonCwiseBinaryOps.h +115 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/CommonCwiseUnaryOps.h +177 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/IndexedViewMethods.h +262 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/MatrixCwiseBinaryOps.h +152 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/MatrixCwiseUnaryOps.h +95 -0
- roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/ReshapedMethods.h +149 -0
- roboticstoolbox/ets/cpp-extensions/README.md +73 -0
- roboticstoolbox/ets/cpp-extensions/fknm_nb.cpp +534 -0
- roboticstoolbox/ets/cpp-extensions/ik.cpp +301 -0
- roboticstoolbox/ets/cpp-extensions/ik.h +59 -0
- roboticstoolbox/ets/cpp-extensions/linalg.cpp +316 -0
- roboticstoolbox/ets/cpp-extensions/linalg.h +64 -0
- roboticstoolbox/ets/cpp-extensions/methods.cpp +372 -0
- roboticstoolbox/ets/cpp-extensions/methods.h +32 -0
- roboticstoolbox/ets/cpp-extensions/structs.cpp +24 -0
- roboticstoolbox/ets/cpp-extensions/structs.h +62 -0
- roboticstoolbox/ets/fknm.py +366 -0
- roboticstoolbox/mobile/Animations.py +485 -0
- roboticstoolbox/mobile/Bug2.py +455 -0
- roboticstoolbox/mobile/CurvaturePolyPlanner.py +179 -0
- roboticstoolbox/mobile/DistanceTransformPlanner.py +395 -0
- roboticstoolbox/mobile/DstarPlanner.py +591 -0
- roboticstoolbox/mobile/DubinsPlanner.py +474 -0
- roboticstoolbox/mobile/EKF.py +1617 -0
- roboticstoolbox/mobile/LatticePlanner.py +419 -0
- roboticstoolbox/mobile/OccGrid.py +613 -0
- roboticstoolbox/mobile/PRMPlanner.py +348 -0
- roboticstoolbox/mobile/ParticleFilter.py +706 -0
- roboticstoolbox/mobile/PlannerBase.py +1009 -0
- roboticstoolbox/mobile/PoseGraph.py +544 -0
- roboticstoolbox/mobile/QuinticPolyPlanner.py +349 -0
- roboticstoolbox/mobile/RRTPlanner.py +359 -0
- roboticstoolbox/mobile/ReedsSheppPlanner.py +545 -0
- roboticstoolbox/mobile/Vehicle.py +1909 -0
- roboticstoolbox/mobile/__init__.py +194 -0
- roboticstoolbox/mobile/drivers.py +397 -0
- roboticstoolbox/mobile/landmarkmap.py +181 -0
- roboticstoolbox/mobile/sensors.py +789 -0
- roboticstoolbox/models/DH/AL5D.py +121 -0
- roboticstoolbox/models/DH/Ball.py +87 -0
- roboticstoolbox/models/DH/Baxter.py +91 -0
- roboticstoolbox/models/DH/Cobra600.py +63 -0
- roboticstoolbox/models/DH/Coil.py +80 -0
- roboticstoolbox/models/DH/Hyper.py +81 -0
- roboticstoolbox/models/DH/Hyper3d.py +83 -0
- roboticstoolbox/models/DH/IRB140.py +159 -0
- roboticstoolbox/models/DH/Jaco.py +102 -0
- roboticstoolbox/models/DH/KR5.py +112 -0
- roboticstoolbox/models/DH/LWR4.py +80 -0
- roboticstoolbox/models/DH/Mico.py +102 -0
- roboticstoolbox/models/DH/Orion5.py +91 -0
- roboticstoolbox/models/DH/P8.py +80 -0
- roboticstoolbox/models/DH/Panda.py +213 -0
- roboticstoolbox/models/DH/Planar2.py +69 -0
- roboticstoolbox/models/DH/Planar3.py +51 -0
- roboticstoolbox/models/DH/Puma560.py +326 -0
- roboticstoolbox/models/DH/README.md +216 -0
- roboticstoolbox/models/DH/Sawyer.py +85 -0
- roboticstoolbox/models/DH/Stanford.py +147 -0
- roboticstoolbox/models/DH/TwoLink.py +153 -0
- roboticstoolbox/models/DH/UR10.py +124 -0
- roboticstoolbox/models/DH/UR3.py +98 -0
- roboticstoolbox/models/DH/UR5.py +98 -0
- roboticstoolbox/models/DH/Uprighttl.py +24 -0
- roboticstoolbox/models/DH/__init__.py +52 -0
- roboticstoolbox/models/ETS/Frankie.py +90 -0
- roboticstoolbox/models/ETS/GenericSeven.py +54 -0
- roboticstoolbox/models/ETS/Omni.py +74 -0
- roboticstoolbox/models/ETS/Panda.py +69 -0
- roboticstoolbox/models/ETS/Planar2.py +49 -0
- roboticstoolbox/models/ETS/Planar_Y.py +65 -0
- roboticstoolbox/models/ETS/Puma560.py +70 -0
- roboticstoolbox/models/ETS/XYPanda.py +84 -0
- roboticstoolbox/models/ETS/__init__.py +20 -0
- roboticstoolbox/models/README.md +9 -0
- roboticstoolbox/models/URDF/AL5D.py +45 -0
- roboticstoolbox/models/URDF/Fetch.py +96 -0
- roboticstoolbox/models/URDF/Frankie.py +67 -0
- roboticstoolbox/models/URDF/FrankieOmni.py +94 -0
- roboticstoolbox/models/URDF/Jaco.py +49 -0
- roboticstoolbox/models/URDF/KinovaGen3.py +63 -0
- roboticstoolbox/models/URDF/LBR.py +57 -0
- roboticstoolbox/models/URDF/PR2.py +66 -0
- roboticstoolbox/models/URDF/Panda.py +60 -0
- roboticstoolbox/models/URDF/Puma560.py +90 -0
- roboticstoolbox/models/URDF/UR10.py +43 -0
- roboticstoolbox/models/URDF/UR3.py +43 -0
- roboticstoolbox/models/URDF/UR5.py +64 -0
- roboticstoolbox/models/URDF/URDFRobot.py +402 -0
- roboticstoolbox/models/URDF/Valkyrie.py +110 -0
- roboticstoolbox/models/URDF/YuMi.py +107 -0
- roboticstoolbox/models/URDF/__init__.py +51 -0
- roboticstoolbox/models/URDF/px100.py +48 -0
- roboticstoolbox/models/URDF/px150.py +48 -0
- roboticstoolbox/models/URDF/rx150.py +48 -0
- roboticstoolbox/models/URDF/rx200.py +48 -0
- roboticstoolbox/models/URDF/vx300.py +48 -0
- roboticstoolbox/models/URDF/vx300s.py +48 -0
- roboticstoolbox/models/URDF/wx200.py +48 -0
- roboticstoolbox/models/URDF/wx250.py +48 -0
- roboticstoolbox/models/URDF/wx250s.py +48 -0
- roboticstoolbox/models/__init__.py +7 -0
- roboticstoolbox/models/catalog.py +226 -0
- roboticstoolbox/robot/BaseRobot.py +2589 -0
- roboticstoolbox/robot/DHFactor.py +522 -0
- roboticstoolbox/robot/DHLink.py +949 -0
- roboticstoolbox/robot/DHRobot.py +2591 -0
- roboticstoolbox/robot/Dynamics.py +1537 -0
- roboticstoolbox/robot/ELink.py +23 -0
- roboticstoolbox/robot/ERobot.py +25 -0
- roboticstoolbox/robot/Gripper.py +242 -0
- roboticstoolbox/robot/IK.py +1378 -0
- roboticstoolbox/robot/Link.py +1557 -0
- roboticstoolbox/robot/PoERobot.py +352 -0
- roboticstoolbox/robot/Robot.py +2157 -0
- roboticstoolbox/robot/RobotKinematics.py +1505 -0
- roboticstoolbox/robot/RobotPlottingMPL.py +355 -0
- roboticstoolbox/robot/RobotProto.py +156 -0
- roboticstoolbox/robot/__init__.py +57 -0
- roboticstoolbox/robot/cpp-extensions/README.md +29 -0
- roboticstoolbox/robot/cpp-extensions/frne.h +96 -0
- roboticstoolbox/robot/cpp-extensions/frne_nb.cpp +237 -0
- roboticstoolbox/robot/cpp-extensions/ne.c +495 -0
- roboticstoolbox/robot/cpp-extensions/vmath.c +163 -0
- roboticstoolbox/robot/cpp-extensions/vmath.h +32 -0
- roboticstoolbox/robot/frne.py +51 -0
- roboticstoolbox/tools/DHFactor.py +375 -0
- roboticstoolbox/tools/Ticker.py +53 -0
- roboticstoolbox/tools/__init__.py +54 -0
- roboticstoolbox/tools/data.py +187 -0
- roboticstoolbox/tools/jsingu.py +51 -0
- roboticstoolbox/tools/null.py +48 -0
- roboticstoolbox/tools/numerical.py +96 -0
- roboticstoolbox/tools/p_servo.py +108 -0
- roboticstoolbox/tools/params.py +11 -0
- roboticstoolbox/tools/plot.py +109 -0
- roboticstoolbox/tools/trajectory.py +1152 -0
- roboticstoolbox/tools/types.py +13 -0
- roboticstoolbox/tools/urdf/__init__.py +45 -0
- roboticstoolbox/tools/urdf/tests/data/ur5.urdf +341 -0
- roboticstoolbox/tools/urdf/tests/test_urdf.py +116 -0
- roboticstoolbox/tools/urdf/urdf.py +1930 -0
- roboticstoolbox/tools/urdf/utils.py +50 -0
- roboticstoolbox_python-1.4.0.dist-info/METADATA +664 -0
- roboticstoolbox_python-1.4.0.dist-info/RECORD +610 -0
- roboticstoolbox_python-1.4.0.dist-info/WHEEL +5 -0
- roboticstoolbox_python-1.4.0.dist-info/entry_points.txt +6 -0
- roboticstoolbox_python-1.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,2589 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
@author: Jesse Haviland
|
|
5
|
+
@author: Peter Corke
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
# import sys
|
|
10
|
+
import io
|
|
11
|
+
from abc import ABC
|
|
12
|
+
from copy import deepcopy
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from typing import (
|
|
15
|
+
IO,
|
|
16
|
+
TYPE_CHECKING,
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
Generic,
|
|
20
|
+
TypeVar,
|
|
21
|
+
cast,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from typing_extensions import Literal as L
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from spatialmath import SE3
|
|
29
|
+
from spatialmath.base.argcheck import (
|
|
30
|
+
getvector,
|
|
31
|
+
getmatrix,
|
|
32
|
+
getunit,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from ansitable import ANSITable, Column
|
|
36
|
+
from spatialgeometry import SceneNode
|
|
37
|
+
from roboticstoolbox.backends.Connector import Connector
|
|
38
|
+
|
|
39
|
+
from roboticstoolbox.ets.fknm import Robot_link_T
|
|
40
|
+
import roboticstoolbox as rtb
|
|
41
|
+
from roboticstoolbox.robot.Gripper import Gripper
|
|
42
|
+
from roboticstoolbox.robot.Link import BaseLink, Link
|
|
43
|
+
from roboticstoolbox.ets.ETS import ETS
|
|
44
|
+
from roboticstoolbox.ets.ET import ET
|
|
45
|
+
from roboticstoolbox.robot.Dynamics import DynamicsMixin
|
|
46
|
+
from roboticstoolbox.tools.types import ArrayLike, NDArray
|
|
47
|
+
from roboticstoolbox.tools.params import rtb_get_param
|
|
48
|
+
from roboticstoolbox.robot.RobotPlottingMPL import RobotPlottingMPLMixin
|
|
49
|
+
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
from matplotlib.cm import Color # pragma nocover
|
|
52
|
+
else:
|
|
53
|
+
Color = None
|
|
54
|
+
|
|
55
|
+
# _default_backend = None
|
|
56
|
+
|
|
57
|
+
# A generic type variable representing any subclass of BaseLink
|
|
58
|
+
LinkType = TypeVar("LinkType", bound=BaseLink)
|
|
59
|
+
|
|
60
|
+
# Link attributes scanned for symbolic (e.g. SymPy) content at build time --
|
|
61
|
+
# DH kinematic parameters plus dynamics parameters; getattr(..., None) below
|
|
62
|
+
# skips whichever of these don't exist on a given Link subclass (e.g. a
|
|
63
|
+
# non-DH Link has no a/alpha/theta/d).
|
|
64
|
+
_SYMBOLIC_LINK_ATTRS = (
|
|
65
|
+
"a", "alpha", "theta", "d", "offset",
|
|
66
|
+
"m", "r", "I", "Jm", "G", "B", "Tc",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _is_symbolic(value: Any) -> bool:
|
|
71
|
+
"""True if value holds non-numeric (e.g. SymPy) content -- same
|
|
72
|
+
object-dtype test used by roboticstoolbox.ets.fknm._is_symbolic."""
|
|
73
|
+
if value is None:
|
|
74
|
+
return False
|
|
75
|
+
return np.asarray(value).dtype == object
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BaseRobot(SceneNode, DynamicsMixin, RobotPlottingMPLMixin, ABC, Generic[LinkType]):
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
links: list[LinkType],
|
|
82
|
+
gripper_links: LinkType | list[LinkType] | None = None,
|
|
83
|
+
name: str = "",
|
|
84
|
+
manufacturer: str = "",
|
|
85
|
+
comment: str = "",
|
|
86
|
+
base: NDArray | SE3 | None = None,
|
|
87
|
+
tool: NDArray | SE3 | None = None,
|
|
88
|
+
gravity: ArrayLike = [0, 0, -9.81],
|
|
89
|
+
keywords: list[str] | tuple[str, ...] = [],
|
|
90
|
+
symbolic: bool = False,
|
|
91
|
+
configs: dict[str, NDArray] | None = None,
|
|
92
|
+
check_jindex: bool = True,
|
|
93
|
+
):
|
|
94
|
+
# Initialise the scene node
|
|
95
|
+
SceneNode.__init__(self)
|
|
96
|
+
|
|
97
|
+
# Lets sort out links now
|
|
98
|
+
self._linkdict: dict[str, LinkType] = {}
|
|
99
|
+
|
|
100
|
+
# Sort links and set self.link, self.n, self.base_link,
|
|
101
|
+
# self.ee_links
|
|
102
|
+
self._sort_links(links, gripper_links, check_jindex)
|
|
103
|
+
|
|
104
|
+
# Fix number of links for gripper links
|
|
105
|
+
self._nlinks = len(links)
|
|
106
|
+
|
|
107
|
+
for gripper in self.grippers:
|
|
108
|
+
self._nlinks += len(gripper.links)
|
|
109
|
+
|
|
110
|
+
# Set the pose of the robot in the world frame
|
|
111
|
+
# in the scenenode object to a numpy array
|
|
112
|
+
if isinstance(base, SE3):
|
|
113
|
+
if len(base) != 1:
|
|
114
|
+
raise ValueError("base must be a single SE3, not a batch")
|
|
115
|
+
self._T = cast(NDArray, base.A) # SE3.A typed list|ndarray; single pose always ndarray
|
|
116
|
+
elif isinstance(base, np.ndarray):
|
|
117
|
+
self._T = base
|
|
118
|
+
|
|
119
|
+
# Set the robot tool transform
|
|
120
|
+
if isinstance(tool, SE3):
|
|
121
|
+
if len(tool) != 1:
|
|
122
|
+
raise ValueError("tool must be a single SE3, not a batch")
|
|
123
|
+
self._tool = cast(NDArray, tool.A) # SE3.A typed list|ndarray; single pose always ndarray
|
|
124
|
+
elif isinstance(tool, np.ndarray):
|
|
125
|
+
self._tool = tool
|
|
126
|
+
else:
|
|
127
|
+
self._tool = np.eye(4)
|
|
128
|
+
|
|
129
|
+
# Set the keywords
|
|
130
|
+
if keywords is not None and not isinstance(keywords, (tuple, list)):
|
|
131
|
+
raise TypeError("keywords must be a list or tuple")
|
|
132
|
+
else:
|
|
133
|
+
self._keywords = list(keywords)
|
|
134
|
+
|
|
135
|
+
# Gravity is in the negative-z direction.
|
|
136
|
+
self.gravity = np.array(gravity)
|
|
137
|
+
|
|
138
|
+
# Basic arguments
|
|
139
|
+
self.name = name
|
|
140
|
+
self.manufacturer = manufacturer
|
|
141
|
+
self.comment = comment
|
|
142
|
+
self._symbolic = symbolic
|
|
143
|
+
self._reach = None
|
|
144
|
+
self._hasdynamics = False
|
|
145
|
+
self._hasgeometry = False
|
|
146
|
+
self._hascollision = False
|
|
147
|
+
self._urdf_string = ""
|
|
148
|
+
self._urdf_filepath = ""
|
|
149
|
+
|
|
150
|
+
# Time to checkout the links for geometry information
|
|
151
|
+
auto_symbolic = False
|
|
152
|
+
for link in self.links:
|
|
153
|
+
# Add link back to robot object
|
|
154
|
+
link._robot = self
|
|
155
|
+
|
|
156
|
+
if link.hasdynamics:
|
|
157
|
+
self._hasdynamics = True
|
|
158
|
+
if link.geometry:
|
|
159
|
+
self._hasgeometry = []
|
|
160
|
+
if link.collision:
|
|
161
|
+
self._hascollision = True
|
|
162
|
+
|
|
163
|
+
if isinstance(link, Link):
|
|
164
|
+
if len(link.geometry) > 0:
|
|
165
|
+
self._hasgeometry = True
|
|
166
|
+
|
|
167
|
+
# Detect symbolic (e.g. SymPy) model parameters regardless of
|
|
168
|
+
# whether the caller remembered to pass symbolic=True -- see
|
|
169
|
+
# rne.md issue 3/4: forgetting the flag previously left
|
|
170
|
+
# self.symbolic False even with genuinely symbolic link
|
|
171
|
+
# parameters, which broke rne_python()'s own float64
|
|
172
|
+
# allocation, not just the C dispatch. symbolic= is kept as an
|
|
173
|
+
# override (OR'd in below), not the source of truth.
|
|
174
|
+
if not auto_symbolic:
|
|
175
|
+
for attr in _SYMBOLIC_LINK_ATTRS:
|
|
176
|
+
if _is_symbolic(getattr(link, attr, None)):
|
|
177
|
+
auto_symbolic = True
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
self._symbolic = self._symbolic or auto_symbolic
|
|
181
|
+
|
|
182
|
+
# Current joint configuraiton, velocity, acceleration
|
|
183
|
+
self.q = np.zeros(self.n)
|
|
184
|
+
self.qd = np.zeros(self.n)
|
|
185
|
+
self.qdd = np.zeros(self.n)
|
|
186
|
+
|
|
187
|
+
# The current control mode of the robot
|
|
188
|
+
self.control_mode = "v"
|
|
189
|
+
|
|
190
|
+
# Set up named configuration property
|
|
191
|
+
if configs is None:
|
|
192
|
+
configs = dict()
|
|
193
|
+
self._configs = configs
|
|
194
|
+
|
|
195
|
+
# A flag for watching dynamics properties
|
|
196
|
+
self._frne_stale = False
|
|
197
|
+
|
|
198
|
+
# Set up qlim
|
|
199
|
+
qlim = np.zeros((2, self.n))
|
|
200
|
+
j = 0
|
|
201
|
+
|
|
202
|
+
for i in range(len(self.links)):
|
|
203
|
+
if self.links[i].isjoint:
|
|
204
|
+
qlim[:, j] = self.links[i].qlim
|
|
205
|
+
j += 1
|
|
206
|
+
self._qlim = qlim
|
|
207
|
+
|
|
208
|
+
self._valid_qlim = False
|
|
209
|
+
for i in range(self.n):
|
|
210
|
+
if any(qlim[:, i] != 0) and not any(np.isnan(qlim[:, i])):
|
|
211
|
+
self._valid_qlim = True
|
|
212
|
+
|
|
213
|
+
# SceneNode, set a reference to the first link
|
|
214
|
+
self.scene_children = [self.links[0]] # type: ignore
|
|
215
|
+
|
|
216
|
+
self._default_backend = None
|
|
217
|
+
self._active_plot_env = None
|
|
218
|
+
|
|
219
|
+
# --------------------------------------------------------------------- #
|
|
220
|
+
# --------- Private Methods ------------------------------------------- #
|
|
221
|
+
# --------------------------------------------------------------------- #
|
|
222
|
+
|
|
223
|
+
def _sort_links(
|
|
224
|
+
self,
|
|
225
|
+
links: list[LinkType],
|
|
226
|
+
gripper_links: LinkType | list[LinkType] | None,
|
|
227
|
+
check_jindex: bool,
|
|
228
|
+
):
|
|
229
|
+
"""
|
|
230
|
+
This method does several things for setting up the links of a robot
|
|
231
|
+
|
|
232
|
+
- Gives each link a unique name if it doesn't have one
|
|
233
|
+
- Assigns each link a parent if it doesn't have one
|
|
234
|
+
- Finds and sets the base link
|
|
235
|
+
- Finds and sets the ee links
|
|
236
|
+
- sets the jindices
|
|
237
|
+
- sets n
|
|
238
|
+
- sets links
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
# The ordered links
|
|
243
|
+
orlinks: list[LinkType] = []
|
|
244
|
+
|
|
245
|
+
# The end-effector links
|
|
246
|
+
self._ee_links: list[LinkType] = []
|
|
247
|
+
|
|
248
|
+
# Check all the incoming Link objects
|
|
249
|
+
n: int = 0
|
|
250
|
+
|
|
251
|
+
# Make sure each link has a name
|
|
252
|
+
# ------------------------------
|
|
253
|
+
for k, link in enumerate(links):
|
|
254
|
+
if not isinstance(link, BaseLink):
|
|
255
|
+
raise TypeError("links should all be Link subclass")
|
|
256
|
+
|
|
257
|
+
# If link has no name, give it one
|
|
258
|
+
if link.name is None or link.name == "":
|
|
259
|
+
link.name = f"link-{k}"
|
|
260
|
+
|
|
261
|
+
link.number = k + 1
|
|
262
|
+
|
|
263
|
+
# Put it in the link dictionary, check for duplicates
|
|
264
|
+
if link.name in self._linkdict:
|
|
265
|
+
raise ValueError(f"link name {link.name} is not unique")
|
|
266
|
+
|
|
267
|
+
self._linkdict[link.name] = link
|
|
268
|
+
|
|
269
|
+
if link.isjoint:
|
|
270
|
+
n += 1
|
|
271
|
+
|
|
272
|
+
# Resolve parents given by name, within the context of
|
|
273
|
+
# this set of links
|
|
274
|
+
# ----------------------------------------------------
|
|
275
|
+
for link in links:
|
|
276
|
+
if link.parent is None and link.parent_name is not None:
|
|
277
|
+
link.parent = self._linkdict[link.parent_name]
|
|
278
|
+
|
|
279
|
+
if all([link.parent is None for link in links]):
|
|
280
|
+
# No parent links were given, assume they are sequential
|
|
281
|
+
for i in range(len(links) - 1):
|
|
282
|
+
# li = links[i]
|
|
283
|
+
links[i + 1].parent = links[i]
|
|
284
|
+
|
|
285
|
+
# Set the base link
|
|
286
|
+
# -----------------
|
|
287
|
+
for link in links:
|
|
288
|
+
# Is this a base link?
|
|
289
|
+
|
|
290
|
+
if isinstance(link.parent, BaseLink):
|
|
291
|
+
# Update children of this link's parent
|
|
292
|
+
link.parent._children.append(link)
|
|
293
|
+
else:
|
|
294
|
+
try:
|
|
295
|
+
if self._base_link is not None:
|
|
296
|
+
raise ValueError("Multiple base links")
|
|
297
|
+
except AttributeError:
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
self._base_link = link
|
|
301
|
+
|
|
302
|
+
if not hasattr(self, "_base_link"):
|
|
303
|
+
raise ValueError(
|
|
304
|
+
"Invalid link configuration provided, must have a base link"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Scene node, set links between the links
|
|
308
|
+
# ---------------------------------------
|
|
309
|
+
for link in links:
|
|
310
|
+
if isinstance(link.parent, BaseLink):
|
|
311
|
+
link.scene_parent = link.parent
|
|
312
|
+
|
|
313
|
+
# Set up the gripper, make a list containing the root of all
|
|
314
|
+
# grippers
|
|
315
|
+
# ----------------------------------------------------------
|
|
316
|
+
if gripper_links is None:
|
|
317
|
+
gripper_links = []
|
|
318
|
+
|
|
319
|
+
if not isinstance(gripper_links, list):
|
|
320
|
+
gripper_links = [gripper_links]
|
|
321
|
+
|
|
322
|
+
# An empty list to hold all grippers
|
|
323
|
+
self._grippers = []
|
|
324
|
+
|
|
325
|
+
# Make a gripper object for each gripper
|
|
326
|
+
for link in gripper_links:
|
|
327
|
+
g_links = self.dfs_links(link)
|
|
328
|
+
|
|
329
|
+
# Remove gripper links from the robot
|
|
330
|
+
for g_link in g_links:
|
|
331
|
+
# print(g_link)
|
|
332
|
+
links.remove(g_link)
|
|
333
|
+
|
|
334
|
+
# Save the gripper object
|
|
335
|
+
self._grippers.append(Gripper(g_links, name=link.name))
|
|
336
|
+
|
|
337
|
+
# Subtract the n of the grippers from the n of the robot
|
|
338
|
+
for gripper in self._grippers:
|
|
339
|
+
n -= gripper.n
|
|
340
|
+
|
|
341
|
+
# Set the ee links
|
|
342
|
+
# ----------------
|
|
343
|
+
ee_links: list[LinkType] = []
|
|
344
|
+
|
|
345
|
+
if len(gripper_links) == 0:
|
|
346
|
+
for link in links:
|
|
347
|
+
# Is this a leaf node? and do we not have any grippers
|
|
348
|
+
if link.children is None or len(link.children) == 0:
|
|
349
|
+
# No children, must be an end-effector
|
|
350
|
+
ee_links.append(link)
|
|
351
|
+
else:
|
|
352
|
+
for link in gripper_links:
|
|
353
|
+
# Use the passed in value
|
|
354
|
+
if link.parent is not None:
|
|
355
|
+
ee_links.append(link.parent)
|
|
356
|
+
|
|
357
|
+
self._ee_links = ee_links
|
|
358
|
+
|
|
359
|
+
# Assign the joint indices and sort the links
|
|
360
|
+
# -------------------------------------------
|
|
361
|
+
if all([link.jindex is None or link.ets._auto_jindex for link in links]):
|
|
362
|
+
# No joints have an index
|
|
363
|
+
jindex = [0] # "mutable integer" hack
|
|
364
|
+
|
|
365
|
+
def visit_link(link, jindex):
|
|
366
|
+
# if it's a joint, assign it a jindex and increment it
|
|
367
|
+
if link.isjoint and link in links:
|
|
368
|
+
link.jindex = jindex[0]
|
|
369
|
+
jindex[0] += 1
|
|
370
|
+
|
|
371
|
+
if link in links:
|
|
372
|
+
orlinks.append(link)
|
|
373
|
+
|
|
374
|
+
# visit all links in DFS order
|
|
375
|
+
self.dfs_links(self.base_link, lambda link: visit_link(link, jindex))
|
|
376
|
+
|
|
377
|
+
elif all(
|
|
378
|
+
[
|
|
379
|
+
link.jindex is not None and not link.ets._auto_jindex
|
|
380
|
+
for link in links
|
|
381
|
+
if link.isjoint
|
|
382
|
+
]
|
|
383
|
+
):
|
|
384
|
+
# Jindex set on all, check they are unique and contiguous
|
|
385
|
+
if check_jindex:
|
|
386
|
+
jset = set(range(n))
|
|
387
|
+
for link in links:
|
|
388
|
+
if link.isjoint and link.jindex not in jset:
|
|
389
|
+
raise ValueError(
|
|
390
|
+
f"joint index {link.jindex} was repeated or out of range"
|
|
391
|
+
)
|
|
392
|
+
jset -= set([link.jindex])
|
|
393
|
+
if len(jset) > 0: # pragma nocover # is impossible
|
|
394
|
+
raise ValueError(f"joints {jset} were not assigned")
|
|
395
|
+
orlinks = links
|
|
396
|
+
else:
|
|
397
|
+
# must be a mixture of Links with/without jindex
|
|
398
|
+
raise ValueError("all links must have a jindex, or none have a jindex")
|
|
399
|
+
|
|
400
|
+
# Set n
|
|
401
|
+
# -----
|
|
402
|
+
self._n = n
|
|
403
|
+
|
|
404
|
+
# Set links
|
|
405
|
+
# ---------
|
|
406
|
+
self._links = orlinks
|
|
407
|
+
|
|
408
|
+
def dynchanged(self, what: str | None = None):
|
|
409
|
+
"""
|
|
410
|
+
Dynamic parameters have changed
|
|
411
|
+
|
|
412
|
+
Called from a property setter to inform the robot that the cache of
|
|
413
|
+
dynamic parameters is invalid.
|
|
414
|
+
|
|
415
|
+
See Also
|
|
416
|
+
--------
|
|
417
|
+
:func:`roboticstoolbox.Link._dirties_frne`
|
|
418
|
+
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
self._frne_stale = True
|
|
422
|
+
if what != "gravity":
|
|
423
|
+
self._hasdynamics = True
|
|
424
|
+
|
|
425
|
+
# --------------------------------------------------------------------- #
|
|
426
|
+
# --------- Magic Methods --------------------------------------------- #
|
|
427
|
+
# --------------------------------------------------------------------- #
|
|
428
|
+
|
|
429
|
+
def __iter__(self):
|
|
430
|
+
self._iter = 0
|
|
431
|
+
return self
|
|
432
|
+
|
|
433
|
+
def __next__(self) -> LinkType:
|
|
434
|
+
if self._iter < len(self.links):
|
|
435
|
+
link = self[self._iter]
|
|
436
|
+
self._iter += 1
|
|
437
|
+
return link
|
|
438
|
+
else:
|
|
439
|
+
raise StopIteration
|
|
440
|
+
|
|
441
|
+
def __getitem__(self, i: int | str) -> LinkType:
|
|
442
|
+
"""
|
|
443
|
+
Get link
|
|
444
|
+
|
|
445
|
+
This also supports iterating over each link in the robot object,
|
|
446
|
+
from the base to the tool.
|
|
447
|
+
|
|
448
|
+
:param i: link number or name
|
|
449
|
+
:returns: i'th link or named link
|
|
450
|
+
|
|
451
|
+
Examples
|
|
452
|
+
--------
|
|
453
|
+
|
|
454
|
+
.. runblock:: pycon
|
|
455
|
+
|
|
456
|
+
>>> import roboticstoolbox as rtb
|
|
457
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
458
|
+
>>> print(robot[1]) # print the 2nd link
|
|
459
|
+
>>> print([link.a for link in robot]) # print all the a_j values
|
|
460
|
+
|
|
461
|
+
.. rubric:: Notes
|
|
462
|
+
|
|
463
|
+
``Robot`` supports link lookup by name,
|
|
464
|
+
eg. ``robot['link1']``
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
if isinstance(i, int):
|
|
469
|
+
return self._links[i]
|
|
470
|
+
else:
|
|
471
|
+
return self._linkdict[i]
|
|
472
|
+
|
|
473
|
+
def __str__(self) -> str:
|
|
474
|
+
"""
|
|
475
|
+
Pretty prints the ETS Model of the robot.
|
|
476
|
+
|
|
477
|
+
:returns: Pretty print of the robot model
|
|
478
|
+
|
|
479
|
+
.. rubric:: Notes
|
|
480
|
+
|
|
481
|
+
- Constant links are shown in blue.
|
|
482
|
+
- End-effector links are prefixed with an @
|
|
483
|
+
- Angles in degrees
|
|
484
|
+
- The robot base frame is denoted as ``BASE`` and is equal to the
|
|
485
|
+
robot's ``base`` attribute.
|
|
486
|
+
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
unicode = rtb_get_param("unicode")
|
|
490
|
+
border = "thin" if unicode else "ascii"
|
|
491
|
+
|
|
492
|
+
table = ANSITable(
|
|
493
|
+
Column("link", headalign="^", colalign=">"),
|
|
494
|
+
Column("link", headalign="^", colalign="<"),
|
|
495
|
+
Column("joint", headalign="^", colalign=">"),
|
|
496
|
+
Column("parent", headalign="^", colalign="<"),
|
|
497
|
+
Column("ETS: parent to link", headalign="^", colalign="<"),
|
|
498
|
+
border=border,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
for k, link in enumerate(self.links):
|
|
502
|
+
color = "" if link.isjoint else "<<blue>>"
|
|
503
|
+
ee = "@" if link in self.ee_links else ""
|
|
504
|
+
ets = link.ets
|
|
505
|
+
if link.parent is None:
|
|
506
|
+
parent_name = "BASE"
|
|
507
|
+
else:
|
|
508
|
+
parent_name = link.parent.name
|
|
509
|
+
s = ets.__str__(f"q{link.jindex}")
|
|
510
|
+
# if len(s) > 0:
|
|
511
|
+
# op = " \u2295 " if unicode else " * " # \oplus
|
|
512
|
+
# s = op + s
|
|
513
|
+
|
|
514
|
+
if link.isjoint:
|
|
515
|
+
jname = link.jindex
|
|
516
|
+
else:
|
|
517
|
+
jname = ""
|
|
518
|
+
table.row(
|
|
519
|
+
# link.jindex,
|
|
520
|
+
k,
|
|
521
|
+
color + ee + link.name,
|
|
522
|
+
jname,
|
|
523
|
+
parent_name,
|
|
524
|
+
f"{s}",
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
classname = "ERobot"
|
|
528
|
+
|
|
529
|
+
s = f"{classname}: {self.name}"
|
|
530
|
+
if self.manufacturer is not None and len(self.manufacturer) > 0:
|
|
531
|
+
s += f" (by {self.manufacturer})"
|
|
532
|
+
s += f", {self.n} joints ({self.structure})"
|
|
533
|
+
if len(self.grippers) > 0:
|
|
534
|
+
s += (
|
|
535
|
+
f", {len(self.grippers)} gripper{'s' if len(self.grippers) > 1 else ''}"
|
|
536
|
+
)
|
|
537
|
+
if self.nbranches > 1:
|
|
538
|
+
s += f", {self.nbranches} branches"
|
|
539
|
+
if self._hasdynamics:
|
|
540
|
+
s += ", dynamics"
|
|
541
|
+
if any([len(link.geometry) > 0 for link in self.links]):
|
|
542
|
+
s += ", geometry"
|
|
543
|
+
if any([len(link.collision) > 0 for link in self.links]):
|
|
544
|
+
s += ", collision"
|
|
545
|
+
s += "\n"
|
|
546
|
+
|
|
547
|
+
s += str(table)
|
|
548
|
+
s += self.configurations_str(border=border)
|
|
549
|
+
|
|
550
|
+
return s
|
|
551
|
+
|
|
552
|
+
def __repr__(self) -> str:
|
|
553
|
+
return str(self)
|
|
554
|
+
|
|
555
|
+
# --------------------------------------------------------------------- #
|
|
556
|
+
# --------- Properties ------------------------------------------------ #
|
|
557
|
+
# --------------------------------------------------------------------- #
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def links(self) -> list[LinkType]:
|
|
561
|
+
"""
|
|
562
|
+
Robot links
|
|
563
|
+
|
|
564
|
+
:returns: A list of link objects
|
|
565
|
+
|
|
566
|
+
.. rubric:: Notes
|
|
567
|
+
|
|
568
|
+
It is probably more concise to index the robot object rather
|
|
569
|
+
than the list of links, ie. the following are equivalent:
|
|
570
|
+
- ``robot.links[i]``
|
|
571
|
+
- ``robot[i]``
|
|
572
|
+
|
|
573
|
+
"""
|
|
574
|
+
|
|
575
|
+
return self._links
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def link_dict(self) -> dict[str, LinkType]:
|
|
579
|
+
return self._linkdict
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def grippers(self) -> list[Gripper]:
|
|
583
|
+
"""
|
|
584
|
+
Grippers attached to the robot
|
|
585
|
+
|
|
586
|
+
:returns: A list of grippers
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
return self._grippers
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def base_link(self) -> LinkType:
|
|
593
|
+
"""
|
|
594
|
+
Get the robot base link
|
|
595
|
+
|
|
596
|
+
- ``robot.base_link`` is the robot base link
|
|
597
|
+
|
|
598
|
+
:returns: the first link in the robot tree
|
|
599
|
+
"""
|
|
600
|
+
|
|
601
|
+
return self._base_link
|
|
602
|
+
|
|
603
|
+
@property
|
|
604
|
+
def ee_links(self) -> list[LinkType]:
|
|
605
|
+
return self._ee_links
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def n(self) -> int:
|
|
609
|
+
"""
|
|
610
|
+
Number of joints
|
|
611
|
+
|
|
612
|
+
:returns: Number of joints
|
|
613
|
+
|
|
614
|
+
Examples
|
|
615
|
+
--------
|
|
616
|
+
|
|
617
|
+
.. runblock:: pycon
|
|
618
|
+
|
|
619
|
+
>>> import roboticstoolbox as rtb
|
|
620
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
621
|
+
>>> robot.n
|
|
622
|
+
|
|
623
|
+
See Also
|
|
624
|
+
--------
|
|
625
|
+
:func:`nlinks`
|
|
626
|
+
:func:`nbranches`
|
|
627
|
+
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
return self._n
|
|
631
|
+
|
|
632
|
+
@property
|
|
633
|
+
def nlinks(self):
|
|
634
|
+
"""
|
|
635
|
+
Number of links
|
|
636
|
+
|
|
637
|
+
The returned number is the total of both variable joints and
|
|
638
|
+
static links
|
|
639
|
+
|
|
640
|
+
:returns: Number of links
|
|
641
|
+
|
|
642
|
+
Examples
|
|
643
|
+
--------
|
|
644
|
+
|
|
645
|
+
.. runblock:: pycon
|
|
646
|
+
|
|
647
|
+
>>> import roboticstoolbox as rtb
|
|
648
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
649
|
+
>>> robot.nlinks
|
|
650
|
+
|
|
651
|
+
See Also
|
|
652
|
+
--------
|
|
653
|
+
:func:`n`
|
|
654
|
+
:func:`nbranches`
|
|
655
|
+
|
|
656
|
+
"""
|
|
657
|
+
|
|
658
|
+
return self._nlinks
|
|
659
|
+
|
|
660
|
+
@property
|
|
661
|
+
def nbranches(self) -> int:
|
|
662
|
+
"""
|
|
663
|
+
Number of branches
|
|
664
|
+
|
|
665
|
+
Number of branches in this robot. Computed as the number of links with
|
|
666
|
+
zero children
|
|
667
|
+
|
|
668
|
+
:returns: number of branches in the robot's kinematic tree
|
|
669
|
+
|
|
670
|
+
Examples
|
|
671
|
+
--------
|
|
672
|
+
|
|
673
|
+
.. runblock:: pycon
|
|
674
|
+
|
|
675
|
+
>>> import roboticstoolbox as rtb
|
|
676
|
+
>>> robot = rtb.models.ETS.Panda()
|
|
677
|
+
>>> robot.nbranches
|
|
678
|
+
|
|
679
|
+
See Also
|
|
680
|
+
--------
|
|
681
|
+
:func:`n`
|
|
682
|
+
:func:`nlinks`
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
return sum([link.nchildren == 0 for link in self.links]) + len(self.grippers)
|
|
686
|
+
|
|
687
|
+
# --------------------------------------------------------------------- #
|
|
688
|
+
|
|
689
|
+
@property
|
|
690
|
+
def name(self) -> str:
|
|
691
|
+
"""
|
|
692
|
+
Get/set robot name
|
|
693
|
+
|
|
694
|
+
- ``robot.name`` is the robot name
|
|
695
|
+
- ``robot.name = ...`` checks and sets the robot name
|
|
696
|
+
|
|
697
|
+
:param name: the new robot name
|
|
698
|
+
:returns: the current robot name
|
|
699
|
+
"""
|
|
700
|
+
return self._name
|
|
701
|
+
|
|
702
|
+
@name.setter
|
|
703
|
+
def name(self, name_new: str):
|
|
704
|
+
self._name = name_new
|
|
705
|
+
|
|
706
|
+
@property
|
|
707
|
+
def comment(self) -> str:
|
|
708
|
+
"""
|
|
709
|
+
Get/set robot comment
|
|
710
|
+
|
|
711
|
+
- ``robot.comment`` is the robot comment
|
|
712
|
+
- ``robot.comment = ...`` checks and sets the robot comment
|
|
713
|
+
|
|
714
|
+
:param name: the new robot comment
|
|
715
|
+
:returns: robot comment
|
|
716
|
+
"""
|
|
717
|
+
return self._comment
|
|
718
|
+
|
|
719
|
+
@comment.setter
|
|
720
|
+
def comment(self, comment_new: str):
|
|
721
|
+
self._comment = comment_new
|
|
722
|
+
|
|
723
|
+
@property
|
|
724
|
+
def manufacturer(self):
|
|
725
|
+
"""
|
|
726
|
+
Get/set robot manufacturer's name
|
|
727
|
+
|
|
728
|
+
- ``robot.manufacturer`` is the robot manufacturer's name
|
|
729
|
+
- ``robot.manufacturer = ...`` checks and sets the manufacturer's name
|
|
730
|
+
|
|
731
|
+
:returns: robot manufacturer's name
|
|
732
|
+
"""
|
|
733
|
+
return self._manufacturer
|
|
734
|
+
|
|
735
|
+
@manufacturer.setter
|
|
736
|
+
def manufacturer(self, manufacturer_new):
|
|
737
|
+
self._manufacturer = manufacturer_new
|
|
738
|
+
|
|
739
|
+
@property
|
|
740
|
+
def configs(self) -> dict[str, NDArray]:
|
|
741
|
+
return self._configs
|
|
742
|
+
|
|
743
|
+
@property
|
|
744
|
+
def keywords(self) -> list[str]:
|
|
745
|
+
return self._keywords
|
|
746
|
+
|
|
747
|
+
@property
|
|
748
|
+
def symbolic(self) -> bool:
|
|
749
|
+
return self._symbolic
|
|
750
|
+
|
|
751
|
+
@property
|
|
752
|
+
def hasdynamics(self):
|
|
753
|
+
"""
|
|
754
|
+
Robot has dynamic parameters
|
|
755
|
+
|
|
756
|
+
:returns: Robot has dynamic parameters
|
|
757
|
+
:returns: At least one link has associated dynamic parameters.
|
|
758
|
+
|
|
759
|
+
Examples
|
|
760
|
+
--------
|
|
761
|
+
|
|
762
|
+
.. runblock:: pycon
|
|
763
|
+
|
|
764
|
+
>>> import roboticstoolbox as rtb
|
|
765
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
766
|
+
>>> robot.hasdynamics
|
|
767
|
+
|
|
768
|
+
"""
|
|
769
|
+
|
|
770
|
+
return self._hasdynamics
|
|
771
|
+
|
|
772
|
+
@property
|
|
773
|
+
def hasgeometry(self):
|
|
774
|
+
"""
|
|
775
|
+
Robot has geometry model
|
|
776
|
+
|
|
777
|
+
At least one link has associated mesh to describe its shape.
|
|
778
|
+
|
|
779
|
+
:returns: Robot has geometry model
|
|
780
|
+
|
|
781
|
+
Examples
|
|
782
|
+
--------
|
|
783
|
+
|
|
784
|
+
.. runblock:: pycon
|
|
785
|
+
|
|
786
|
+
>>> import roboticstoolbox as rtb
|
|
787
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
788
|
+
>>> robot.hasgeometry
|
|
789
|
+
|
|
790
|
+
See Also
|
|
791
|
+
--------
|
|
792
|
+
:func:`hascollision`
|
|
793
|
+
|
|
794
|
+
"""
|
|
795
|
+
|
|
796
|
+
return self._hasgeometry
|
|
797
|
+
|
|
798
|
+
@property
|
|
799
|
+
def hascollision(self):
|
|
800
|
+
"""
|
|
801
|
+
Robot has collision model
|
|
802
|
+
|
|
803
|
+
:returns: Robot has collision model
|
|
804
|
+
:returns: At least one link has associated collision model.
|
|
805
|
+
|
|
806
|
+
Examples
|
|
807
|
+
--------
|
|
808
|
+
|
|
809
|
+
.. runblock:: pycon
|
|
810
|
+
|
|
811
|
+
>>> import roboticstoolbox as rtb
|
|
812
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
813
|
+
>>> robot.hascollision
|
|
814
|
+
|
|
815
|
+
See Also
|
|
816
|
+
--------
|
|
817
|
+
:func:`hasgeometry`
|
|
818
|
+
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
return self._hascollision
|
|
822
|
+
|
|
823
|
+
@property
|
|
824
|
+
def default_backend(self):
|
|
825
|
+
"""
|
|
826
|
+
Get default graphical backend
|
|
827
|
+
|
|
828
|
+
- ``robot.default_backend`` Get the default graphical backend, used when
|
|
829
|
+
no explicit backend is passed to ``Robot.plot``.
|
|
830
|
+
- ``robot.default_backend = ...`` Set the default graphical backend, used when
|
|
831
|
+
no explicit backend is passed to ``Robot.plot``. The default set here will
|
|
832
|
+
be overridden if the particular ``Robot`` subclass cannot support it.
|
|
833
|
+
|
|
834
|
+
:returns: backend name
|
|
835
|
+
"""
|
|
836
|
+
return self._default_backend
|
|
837
|
+
|
|
838
|
+
@default_backend.setter
|
|
839
|
+
def default_backend(self, be):
|
|
840
|
+
self._default_backend = be
|
|
841
|
+
|
|
842
|
+
@property
|
|
843
|
+
def gravity(self) -> NDArray:
|
|
844
|
+
"""
|
|
845
|
+
Get/set default gravitational acceleration (Robot superclass)
|
|
846
|
+
|
|
847
|
+
- ``robot.gravity`` is the default gravitational acceleration
|
|
848
|
+
- ``robot.gravity = ...`` checks and sets default gravitational
|
|
849
|
+
acceleration
|
|
850
|
+
|
|
851
|
+
:param gravity: gravitational acceleration in the world frame,
|
|
852
|
+
downwards gravitational force is equivalent to robot base
|
|
853
|
+
acceleration upwards (positive)
|
|
854
|
+
:returns: gravitational acceleration
|
|
855
|
+
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
return self._gravity
|
|
859
|
+
|
|
860
|
+
@gravity.setter
|
|
861
|
+
def gravity(self, gravity_new: ArrayLike):
|
|
862
|
+
self._gravity = np.array(getvector(gravity_new, 3))
|
|
863
|
+
self.dynchanged()
|
|
864
|
+
|
|
865
|
+
# --------------------------------------------------------------------- #
|
|
866
|
+
|
|
867
|
+
@property
|
|
868
|
+
def q(self) -> NDArray:
|
|
869
|
+
"""
|
|
870
|
+
Get/set robot joint configuration
|
|
871
|
+
|
|
872
|
+
- ``robot.q`` is the robot joint configuration
|
|
873
|
+
- ``robot.q = ...`` checks and sets the joint configuration
|
|
874
|
+
|
|
875
|
+
:param q: the new robot joint configuration
|
|
876
|
+
:returns: robot joint configuration
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
return self._q
|
|
880
|
+
|
|
881
|
+
@q.setter
|
|
882
|
+
def q(self, q_new: ArrayLike):
|
|
883
|
+
self._q = np.array(getvector(q_new, self.n))
|
|
884
|
+
|
|
885
|
+
@property
|
|
886
|
+
def qd(self) -> NDArray:
|
|
887
|
+
"""
|
|
888
|
+
Get/set robot joint velocity
|
|
889
|
+
|
|
890
|
+
- ``robot.qd`` is the robot joint velocity
|
|
891
|
+
- ``robot.qd = ...`` checks and sets the joint velocity
|
|
892
|
+
|
|
893
|
+
:returns: robot joint velocity
|
|
894
|
+
"""
|
|
895
|
+
|
|
896
|
+
return self._qd
|
|
897
|
+
|
|
898
|
+
@qd.setter
|
|
899
|
+
def qd(self, qd_new: ArrayLike):
|
|
900
|
+
self._qd = np.array(getvector(qd_new, self.n))
|
|
901
|
+
|
|
902
|
+
@property
|
|
903
|
+
def qdd(self) -> NDArray:
|
|
904
|
+
"""
|
|
905
|
+
Get/set robot joint acceleration
|
|
906
|
+
|
|
907
|
+
- ``robot.qdd`` is the robot joint acceleration
|
|
908
|
+
- ``robot.qdd = ...`` checks and sets the robot joint acceleration
|
|
909
|
+
|
|
910
|
+
:returns: robot joint acceleration
|
|
911
|
+
"""
|
|
912
|
+
return self._qdd
|
|
913
|
+
|
|
914
|
+
@qdd.setter
|
|
915
|
+
def qdd(self, qdd_new: ArrayLike):
|
|
916
|
+
self._qdd = np.array(getvector(qdd_new, self.n))
|
|
917
|
+
|
|
918
|
+
@property
|
|
919
|
+
def qlim(self) -> NDArray:
|
|
920
|
+
r"""
|
|
921
|
+
Joint limits
|
|
922
|
+
|
|
923
|
+
Limits are extracted from the link objects. If joints limits are
|
|
924
|
+
not set for:
|
|
925
|
+
|
|
926
|
+
- a revolute joint [-𝜋. 𝜋] is returned
|
|
927
|
+
- a prismatic joint an exception is raised
|
|
928
|
+
|
|
929
|
+
:param qlim: An array of joints limits (2, n)
|
|
930
|
+
:raises ValueError: unset limits for a prismatic joint
|
|
931
|
+
:returns: Array of joint limit values
|
|
932
|
+
|
|
933
|
+
Examples
|
|
934
|
+
--------
|
|
935
|
+
|
|
936
|
+
.. runblock:: pycon
|
|
937
|
+
|
|
938
|
+
>>> import roboticstoolbox as rtb
|
|
939
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
940
|
+
>>> robot.qlim
|
|
941
|
+
|
|
942
|
+
"""
|
|
943
|
+
|
|
944
|
+
limits = np.zeros((2, self.n))
|
|
945
|
+
j = 0
|
|
946
|
+
|
|
947
|
+
for link in self.links:
|
|
948
|
+
if link.isrevolute:
|
|
949
|
+
if (
|
|
950
|
+
link.qlim is None
|
|
951
|
+
or link.qlim[0] is None
|
|
952
|
+
or np.any(np.isnan(link.qlim))
|
|
953
|
+
):
|
|
954
|
+
v = [-np.pi, np.pi]
|
|
955
|
+
else:
|
|
956
|
+
v = link.qlim
|
|
957
|
+
elif link.isprismatic:
|
|
958
|
+
if link.qlim is None:
|
|
959
|
+
raise ValueError("Undefined prismatic joint limit")
|
|
960
|
+
else:
|
|
961
|
+
v = link.qlim
|
|
962
|
+
else:
|
|
963
|
+
# Fixed link
|
|
964
|
+
continue # pragma nocover
|
|
965
|
+
|
|
966
|
+
limits[:, j] = v
|
|
967
|
+
j += 1
|
|
968
|
+
|
|
969
|
+
return limits
|
|
970
|
+
|
|
971
|
+
@qlim.setter
|
|
972
|
+
def qlim(self, new_qlim: ArrayLike):
|
|
973
|
+
new_qlim = np.array(new_qlim)
|
|
974
|
+
|
|
975
|
+
if new_qlim.shape != (2, self.n):
|
|
976
|
+
raise ValueError("new_qlim must be of shape (2, n)")
|
|
977
|
+
|
|
978
|
+
j = 0
|
|
979
|
+
for link in self.links:
|
|
980
|
+
if link.isjoint:
|
|
981
|
+
link.qlim = new_qlim[:, j]
|
|
982
|
+
j += 1
|
|
983
|
+
|
|
984
|
+
@property
|
|
985
|
+
def structure(self) -> str:
|
|
986
|
+
"""
|
|
987
|
+
Return the joint structure string
|
|
988
|
+
|
|
989
|
+
A string with one letter per joint: ``R`` for a revolute
|
|
990
|
+
joint, and ``P`` for a prismatic joint.
|
|
991
|
+
|
|
992
|
+
:returns: joint configuration string
|
|
993
|
+
|
|
994
|
+
Examples
|
|
995
|
+
--------
|
|
996
|
+
|
|
997
|
+
.. runblock:: pycon
|
|
998
|
+
|
|
999
|
+
>>> import roboticstoolbox as rtb
|
|
1000
|
+
>>> puma = rtb.models.DH.Puma560()
|
|
1001
|
+
>>> puma.structure
|
|
1002
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1003
|
+
>>> stanford.structure
|
|
1004
|
+
|
|
1005
|
+
.. rubric:: Notes
|
|
1006
|
+
|
|
1007
|
+
Fixed joints, that maintain a constant link relative pose,
|
|
1008
|
+
are not included.
|
|
1009
|
+
``len(self.structure) == self.n``.
|
|
1010
|
+
|
|
1011
|
+
"""
|
|
1012
|
+
|
|
1013
|
+
structure = []
|
|
1014
|
+
|
|
1015
|
+
for link in self.links:
|
|
1016
|
+
if link.isrevolute:
|
|
1017
|
+
structure.append("R")
|
|
1018
|
+
elif link.isprismatic:
|
|
1019
|
+
structure.append("P")
|
|
1020
|
+
|
|
1021
|
+
return "".join(structure)
|
|
1022
|
+
|
|
1023
|
+
@property
|
|
1024
|
+
def prismaticjoints(self) -> list[bool]:
|
|
1025
|
+
"""
|
|
1026
|
+
Revolute joints as bool array
|
|
1027
|
+
|
|
1028
|
+
:returns: array of joint type, True if prismatic
|
|
1029
|
+
|
|
1030
|
+
Examples
|
|
1031
|
+
--------
|
|
1032
|
+
|
|
1033
|
+
.. runblock:: pycon
|
|
1034
|
+
|
|
1035
|
+
>>> import roboticstoolbox as rtb
|
|
1036
|
+
>>> puma = rtb.models.DH.Puma560()
|
|
1037
|
+
>>> puma.prismaticjoints
|
|
1038
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1039
|
+
>>> stanford.prismaticjoints
|
|
1040
|
+
|
|
1041
|
+
.. rubric:: Notes
|
|
1042
|
+
|
|
1043
|
+
Fixed joints, that maintain a constant link relative pose,
|
|
1044
|
+
are not included.
|
|
1045
|
+
|
|
1046
|
+
See Also
|
|
1047
|
+
--------
|
|
1048
|
+
:func:`Link.isprismatic`
|
|
1049
|
+
:func:`revolutejoints`
|
|
1050
|
+
|
|
1051
|
+
"""
|
|
1052
|
+
|
|
1053
|
+
return [link.isprismatic for link in self.links if link.isjoint]
|
|
1054
|
+
|
|
1055
|
+
@property
|
|
1056
|
+
def revolutejoints(self) -> list[bool]:
|
|
1057
|
+
"""
|
|
1058
|
+
Revolute joints as bool array
|
|
1059
|
+
|
|
1060
|
+
:returns: array of joint type, True if revolute
|
|
1061
|
+
|
|
1062
|
+
Examples
|
|
1063
|
+
--------
|
|
1064
|
+
|
|
1065
|
+
.. runblock:: pycon
|
|
1066
|
+
|
|
1067
|
+
>>> import roboticstoolbox as rtb
|
|
1068
|
+
>>> puma = rtb.models.DH.Puma560()
|
|
1069
|
+
>>> puma.revolutejoints
|
|
1070
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1071
|
+
>>> stanford.revolutejoints
|
|
1072
|
+
|
|
1073
|
+
.. rubric:: Notes
|
|
1074
|
+
|
|
1075
|
+
Fixed joints, that maintain a constant link relative pose,
|
|
1076
|
+
are not included.
|
|
1077
|
+
|
|
1078
|
+
See Also
|
|
1079
|
+
--------
|
|
1080
|
+
:func:`Link.isrevolute`
|
|
1081
|
+
:func:`prismaticjoints`
|
|
1082
|
+
|
|
1083
|
+
"""
|
|
1084
|
+
|
|
1085
|
+
return [link.isrevolute for link in self.links if link.isjoint]
|
|
1086
|
+
|
|
1087
|
+
@property
|
|
1088
|
+
def control_mode(self) -> str:
|
|
1089
|
+
"""
|
|
1090
|
+
Get/set robot control mode
|
|
1091
|
+
|
|
1092
|
+
- ``robot.control_type`` is the robot control mode
|
|
1093
|
+
- ``robot.control_type = ...`` checks and sets the robot control mode
|
|
1094
|
+
|
|
1095
|
+
:param control_mode: the new robot control mode
|
|
1096
|
+
:returns: the current robot control mode
|
|
1097
|
+
"""
|
|
1098
|
+
|
|
1099
|
+
return self._control_mode
|
|
1100
|
+
|
|
1101
|
+
@control_mode.setter
|
|
1102
|
+
def control_mode(self, cn: str):
|
|
1103
|
+
if cn == "p" or cn == "v" or cn == "a":
|
|
1104
|
+
self._control_mode = cn
|
|
1105
|
+
else:
|
|
1106
|
+
raise ValueError("Control type must be one of 'p', 'v', or 'a'")
|
|
1107
|
+
|
|
1108
|
+
# --------------------------------------------------------------------- #
|
|
1109
|
+
|
|
1110
|
+
@property
|
|
1111
|
+
def urdf_string(self) -> str:
|
|
1112
|
+
return self._urdf_string
|
|
1113
|
+
|
|
1114
|
+
@property
|
|
1115
|
+
def urdf_filepath(self) -> str:
|
|
1116
|
+
return self._urdf_filepath
|
|
1117
|
+
|
|
1118
|
+
# --------------------------------------------------------------------- #
|
|
1119
|
+
|
|
1120
|
+
@property
|
|
1121
|
+
def tool(self) -> SE3:
|
|
1122
|
+
"""
|
|
1123
|
+
Get/set robot tool transform
|
|
1124
|
+
|
|
1125
|
+
- ``robot.tool`` is the robot tool transform as an SE3 object
|
|
1126
|
+
- ``robot._tool`` is the robot tool transform as a numpy array
|
|
1127
|
+
- ``robot.tool = ...`` checks and sets the robot tool transform
|
|
1128
|
+
|
|
1129
|
+
:param tool: the new robot tool transform (as an SE(3))
|
|
1130
|
+
:returns: robot tool transform
|
|
1131
|
+
"""
|
|
1132
|
+
return SE3(self._tool, check=False)
|
|
1133
|
+
|
|
1134
|
+
@tool.setter
|
|
1135
|
+
def tool(self, T: SE3 | NDArray):
|
|
1136
|
+
if isinstance(T, SE3):
|
|
1137
|
+
if len(T) != 1:
|
|
1138
|
+
raise ValueError("tool must be a single SE3, not a batch")
|
|
1139
|
+
self._tool = cast(NDArray, T.A) # SE3.A typed list|ndarray; single pose always ndarray
|
|
1140
|
+
else:
|
|
1141
|
+
self._tool = T
|
|
1142
|
+
|
|
1143
|
+
@property
|
|
1144
|
+
def base(self) -> SE3:
|
|
1145
|
+
"""
|
|
1146
|
+
Get/set robot base transform
|
|
1147
|
+
|
|
1148
|
+
- ``robot.base`` is the robot base transform
|
|
1149
|
+
- ``robot.base = ...`` checks and sets the robot base transform
|
|
1150
|
+
|
|
1151
|
+
:param base: the new robot base transform
|
|
1152
|
+
:returns: the current robot base transform
|
|
1153
|
+
"""
|
|
1154
|
+
|
|
1155
|
+
# return a copy, otherwise somebody with
|
|
1156
|
+
# reference to the base can change it
|
|
1157
|
+
|
|
1158
|
+
# This now returns the Scene Node transform
|
|
1159
|
+
# self._T is a copy of SceneNode.__T
|
|
1160
|
+
return SE3(self._T, check=False)
|
|
1161
|
+
|
|
1162
|
+
@base.setter
|
|
1163
|
+
def base(self, T: NDArray | SE3):
|
|
1164
|
+
if isinstance(self, rtb.Robot):
|
|
1165
|
+
# All 3D robots
|
|
1166
|
+
# Set the SceneNode T
|
|
1167
|
+
if isinstance(T, SE3):
|
|
1168
|
+
if len(T) != 1:
|
|
1169
|
+
raise ValueError("base must be a single SE3, not a batch")
|
|
1170
|
+
self._T = cast(NDArray, T.A) # SE3.A typed list|ndarray; single pose always ndarray
|
|
1171
|
+
else:
|
|
1172
|
+
self._T = T
|
|
1173
|
+
|
|
1174
|
+
# --------------------------------------------------------------------- #
|
|
1175
|
+
|
|
1176
|
+
@lru_cache(maxsize=32)
|
|
1177
|
+
def get_path(
|
|
1178
|
+
self,
|
|
1179
|
+
end: Gripper | LinkType | str | None = None,
|
|
1180
|
+
start: Gripper | LinkType | str | None = None,
|
|
1181
|
+
) -> tuple[list[LinkType], int, SE3]:
|
|
1182
|
+
"""
|
|
1183
|
+
Find a path from start to end
|
|
1184
|
+
|
|
1185
|
+
:param end: end-effector or gripper to compute forward kinematics to
|
|
1186
|
+
:param start: name or reference to a base link, defaults to None
|
|
1187
|
+
:raises ValueError: link not known or ambiguous
|
|
1188
|
+
:returns: the path from start to end
|
|
1189
|
+
:returns: the number of joints in the path
|
|
1190
|
+
:returns: the tool transform present after end
|
|
1191
|
+
"""
|
|
1192
|
+
|
|
1193
|
+
def search(
|
|
1194
|
+
start,
|
|
1195
|
+
end,
|
|
1196
|
+
explored: set[LinkType | Link],
|
|
1197
|
+
path: list[LinkType | Link],
|
|
1198
|
+
) -> list[LinkType | Link] | None:
|
|
1199
|
+
link = self._getlink(start, self.base_link)
|
|
1200
|
+
end = self._getlink(end, self.ee_links[0])
|
|
1201
|
+
|
|
1202
|
+
toplevel = len(path) == 0
|
|
1203
|
+
explored.add(link)
|
|
1204
|
+
|
|
1205
|
+
if link == end:
|
|
1206
|
+
return path
|
|
1207
|
+
|
|
1208
|
+
# unlike regular DFS, the neighbours of the node are its children
|
|
1209
|
+
# and its parent.
|
|
1210
|
+
|
|
1211
|
+
# visit child nodes below start
|
|
1212
|
+
if toplevel:
|
|
1213
|
+
path = [link]
|
|
1214
|
+
|
|
1215
|
+
if link.children is not None:
|
|
1216
|
+
for child in link.children:
|
|
1217
|
+
if child not in explored:
|
|
1218
|
+
path.append(child)
|
|
1219
|
+
p = search(child, end, explored, path)
|
|
1220
|
+
if p is not None:
|
|
1221
|
+
return p
|
|
1222
|
+
|
|
1223
|
+
# We didn't find the node below, keep going up a level, and recursing
|
|
1224
|
+
# down again
|
|
1225
|
+
if toplevel:
|
|
1226
|
+
path = []
|
|
1227
|
+
|
|
1228
|
+
if link.parent is not None:
|
|
1229
|
+
parent = link.parent # go up one level toward the root
|
|
1230
|
+
if parent not in explored:
|
|
1231
|
+
if len(path) == 0:
|
|
1232
|
+
p = search(parent, end, explored, [link])
|
|
1233
|
+
else:
|
|
1234
|
+
path.append(link)
|
|
1235
|
+
p = search(parent, end, explored, path)
|
|
1236
|
+
|
|
1237
|
+
if p is not None and len(p) > 0:
|
|
1238
|
+
return p
|
|
1239
|
+
|
|
1240
|
+
end, start, tool = self._get_limit_links(end=end, start=start)
|
|
1241
|
+
|
|
1242
|
+
path = search(start, end, set(), [])
|
|
1243
|
+
|
|
1244
|
+
if path is None or len(path) == 0:
|
|
1245
|
+
raise ValueError("No path found") # pragma nocover
|
|
1246
|
+
elif path[-1] != end:
|
|
1247
|
+
path.append(end)
|
|
1248
|
+
|
|
1249
|
+
if tool is None:
|
|
1250
|
+
tool = SE3()
|
|
1251
|
+
|
|
1252
|
+
return path, len(path), tool # type: ignore
|
|
1253
|
+
|
|
1254
|
+
@lru_cache(maxsize=32)
|
|
1255
|
+
def _getlink(
|
|
1256
|
+
self,
|
|
1257
|
+
link: LinkType | Gripper | str | None,
|
|
1258
|
+
default: LinkType | Gripper | str | None = None,
|
|
1259
|
+
) -> LinkType | Link:
|
|
1260
|
+
"""
|
|
1261
|
+
Validate reference to Link
|
|
1262
|
+
|
|
1263
|
+
``robot._getlink(link)`` is a validated reference to a Link within
|
|
1264
|
+
the ERobot ``robot``. If ``link`` is:
|
|
1265
|
+
|
|
1266
|
+
- an ``Link`` reference it is validated as belonging to
|
|
1267
|
+
``robot``.
|
|
1268
|
+
- a string, then it looked up in the robot's link name dictionary, and
|
|
1269
|
+
a Link reference returned.
|
|
1270
|
+
|
|
1271
|
+
:param link: link
|
|
1272
|
+
:raises ValueError: link does not belong to this ERobot
|
|
1273
|
+
:raises TypeError: bad argument
|
|
1274
|
+
:returns: link reference
|
|
1275
|
+
"""
|
|
1276
|
+
|
|
1277
|
+
if link is None:
|
|
1278
|
+
link = default
|
|
1279
|
+
|
|
1280
|
+
if isinstance(link, str):
|
|
1281
|
+
if link in self.link_dict:
|
|
1282
|
+
return self.link_dict[link]
|
|
1283
|
+
|
|
1284
|
+
raise ValueError(f"no link named {link}")
|
|
1285
|
+
|
|
1286
|
+
elif isinstance(link, BaseLink):
|
|
1287
|
+
if link in self.links:
|
|
1288
|
+
return link
|
|
1289
|
+
else:
|
|
1290
|
+
for gripper in self.grippers:
|
|
1291
|
+
if link in gripper.links:
|
|
1292
|
+
return link
|
|
1293
|
+
|
|
1294
|
+
raise ValueError("link not in robot links")
|
|
1295
|
+
elif isinstance(link, Gripper):
|
|
1296
|
+
for gripper in self.grippers:
|
|
1297
|
+
if link is gripper:
|
|
1298
|
+
return gripper.links[0]
|
|
1299
|
+
|
|
1300
|
+
raise ValueError("Gripper not in robot")
|
|
1301
|
+
else:
|
|
1302
|
+
raise TypeError("unknown argument")
|
|
1303
|
+
|
|
1304
|
+
def _find_ets(self, start, end, explored, path) -> ETS | None:
|
|
1305
|
+
"""
|
|
1306
|
+
Privade method which will recursively find the ETS of a path
|
|
1307
|
+
see ets()
|
|
1308
|
+
"""
|
|
1309
|
+
|
|
1310
|
+
link = self._getlink(start, self.base_link)
|
|
1311
|
+
end = self._getlink(end, self.ee_links[0])
|
|
1312
|
+
|
|
1313
|
+
toplevel = path is None
|
|
1314
|
+
explored.add(link)
|
|
1315
|
+
|
|
1316
|
+
if link == end:
|
|
1317
|
+
return path
|
|
1318
|
+
|
|
1319
|
+
# unlike regular DFS, the neighbours of the node are its children
|
|
1320
|
+
# and its parent.
|
|
1321
|
+
|
|
1322
|
+
# visit child nodes below start
|
|
1323
|
+
if toplevel:
|
|
1324
|
+
path = link.ets
|
|
1325
|
+
|
|
1326
|
+
if link.children is not None:
|
|
1327
|
+
for child in link.children:
|
|
1328
|
+
if child not in explored:
|
|
1329
|
+
p = self._find_ets(child, end, explored, path * child.ets)
|
|
1330
|
+
if p is not None:
|
|
1331
|
+
return p
|
|
1332
|
+
|
|
1333
|
+
# we didn't find the node below, keep going up a level, and recursing
|
|
1334
|
+
# down again
|
|
1335
|
+
if toplevel:
|
|
1336
|
+
path = None
|
|
1337
|
+
if link.parent is not None:
|
|
1338
|
+
parent = link.parent # go up one level toward the root
|
|
1339
|
+
if parent not in explored:
|
|
1340
|
+
if path is None:
|
|
1341
|
+
p = self._find_ets(parent, end, explored, link.ets.inv())
|
|
1342
|
+
else:
|
|
1343
|
+
p = self._find_ets(parent, end, explored, path * link.ets.inv())
|
|
1344
|
+
if p is not None:
|
|
1345
|
+
return p
|
|
1346
|
+
|
|
1347
|
+
def _gripper_ets(self, gripper: Gripper) -> ETS:
|
|
1348
|
+
"""
|
|
1349
|
+
Privade method which will find the ETS of a gripper
|
|
1350
|
+
|
|
1351
|
+
"""
|
|
1352
|
+
|
|
1353
|
+
return ETS(ET.SE3(gripper.tool))
|
|
1354
|
+
|
|
1355
|
+
@lru_cache(maxsize=32)
|
|
1356
|
+
def _get_limit_links(
|
|
1357
|
+
self,
|
|
1358
|
+
end: Gripper | LinkType | str | None = None,
|
|
1359
|
+
start: Gripper | LinkType | str | None = None,
|
|
1360
|
+
) -> tuple[LinkType, LinkType, SE3 | None]:
|
|
1361
|
+
"""
|
|
1362
|
+
Get and validate an end-effector, and a base link
|
|
1363
|
+
|
|
1364
|
+
Helper method to find or validate an end-effector and base link
|
|
1365
|
+
|
|
1366
|
+
end
|
|
1367
|
+
end-effector or gripper to compute forward kinematics to
|
|
1368
|
+
start
|
|
1369
|
+
name or reference to a base link, defaults to None
|
|
1370
|
+
|
|
1371
|
+
ValueError
|
|
1372
|
+
link not known or ambiguous
|
|
1373
|
+
ValueError
|
|
1374
|
+
[description]
|
|
1375
|
+
TypeError
|
|
1376
|
+
unknown type provided
|
|
1377
|
+
|
|
1378
|
+
:returns: end-effector link
|
|
1379
|
+
:returns: base link
|
|
1380
|
+
:returns: tool transform of gripper if applicable
|
|
1381
|
+
"""
|
|
1382
|
+
|
|
1383
|
+
tool = None
|
|
1384
|
+
if end is None:
|
|
1385
|
+
if len(self.grippers) > 1:
|
|
1386
|
+
end_ret = self.grippers[0].links[0]
|
|
1387
|
+
tool = self.grippers[0].tool
|
|
1388
|
+
if len(self.grippers) > 1:
|
|
1389
|
+
# Warn user: more than one gripper
|
|
1390
|
+
print("More than one gripper present, using robot.grippers[0]")
|
|
1391
|
+
elif len(self.grippers) == 1:
|
|
1392
|
+
end_ret = self.grippers[0].links[0]
|
|
1393
|
+
tool = self.grippers[0].tool
|
|
1394
|
+
|
|
1395
|
+
# no grippers, use ee link if just one
|
|
1396
|
+
elif len(self.ee_links) > 1:
|
|
1397
|
+
end_ret = self.ee_links[0]
|
|
1398
|
+
if len(self.ee_links) > 1:
|
|
1399
|
+
# Warn user: more than one EE
|
|
1400
|
+
print("More than one end-effector present, using robot.ee_links[0]")
|
|
1401
|
+
else:
|
|
1402
|
+
end_ret = self.ee_links[0]
|
|
1403
|
+
|
|
1404
|
+
else:
|
|
1405
|
+
# Check if end corresponds to gripper
|
|
1406
|
+
for gripper in self.grippers:
|
|
1407
|
+
if end == gripper or end == gripper.name:
|
|
1408
|
+
tool = gripper.tool
|
|
1409
|
+
# end_ret = gripper.links[0]
|
|
1410
|
+
|
|
1411
|
+
# otherwise check for end in the links
|
|
1412
|
+
end_ret = self._getlink(end)
|
|
1413
|
+
|
|
1414
|
+
if start is None:
|
|
1415
|
+
start_ret = self.base_link
|
|
1416
|
+
|
|
1417
|
+
# Cache result
|
|
1418
|
+
self._cache_start = start
|
|
1419
|
+
else:
|
|
1420
|
+
# start effector is specified
|
|
1421
|
+
start_ret = self._getlink(start)
|
|
1422
|
+
|
|
1423
|
+
# because Gripper returns Link not LinkType
|
|
1424
|
+
return end_ret, start_ret, tool # type: ignore
|
|
1425
|
+
|
|
1426
|
+
@lru_cache(maxsize=32)
|
|
1427
|
+
def ets(
|
|
1428
|
+
self,
|
|
1429
|
+
start: LinkType | Gripper | str | None = None,
|
|
1430
|
+
end: LinkType | Gripper | str | None = None,
|
|
1431
|
+
) -> ETS:
|
|
1432
|
+
"""
|
|
1433
|
+
Robot to ETS
|
|
1434
|
+
|
|
1435
|
+
``robot.ets()`` is an ETS representing the kinematics from base to
|
|
1436
|
+
end-effector.
|
|
1437
|
+
|
|
1438
|
+
``robot.ets(end=link)`` is an ETS representing the kinematics from
|
|
1439
|
+
base to the link ``link`` specified as a Link reference or a name.
|
|
1440
|
+
|
|
1441
|
+
``robot.ets(start=l1, end=l2)`` is an ETS representing the kinematics
|
|
1442
|
+
from link ``l1`` to link ``l2``.
|
|
1443
|
+
|
|
1444
|
+
:param :param start: start of path, defaults to ``base_link``:
|
|
1445
|
+
:param :param end: end of path, defaults to end-effector:
|
|
1446
|
+
:raises ValueError: a link does not belong to this ERobot
|
|
1447
|
+
:raises TypeError: a bad link argument
|
|
1448
|
+
:returns: elementary transform sequence
|
|
1449
|
+
|
|
1450
|
+
Examples
|
|
1451
|
+
--------
|
|
1452
|
+
|
|
1453
|
+
.. runblock:: pycon
|
|
1454
|
+
|
|
1455
|
+
>>> import roboticstoolbox as rtb
|
|
1456
|
+
>>> panda = rtb.models.ETS.Panda()
|
|
1457
|
+
>>> panda.ets()
|
|
1458
|
+
|
|
1459
|
+
"""
|
|
1460
|
+
|
|
1461
|
+
# ets to stand and end incase of grippers
|
|
1462
|
+
ets_init = None
|
|
1463
|
+
ets_end = None
|
|
1464
|
+
|
|
1465
|
+
if isinstance(start, Gripper):
|
|
1466
|
+
ets_init = self._gripper_ets(start).inv()
|
|
1467
|
+
link = start.links[0].parent
|
|
1468
|
+
if link is None: # pragma nocover
|
|
1469
|
+
raise ValueError("Invalid robot link configuration")
|
|
1470
|
+
else:
|
|
1471
|
+
link = self._getlink(start, self.base_link)
|
|
1472
|
+
|
|
1473
|
+
if end is None:
|
|
1474
|
+
if len(self.grippers) > 1:
|
|
1475
|
+
end_link = self.grippers[0].links[0]
|
|
1476
|
+
ets_end = self._gripper_ets(self.grippers[0])
|
|
1477
|
+
print("multiple grippers present, ambiguous, using self.grippers[0]")
|
|
1478
|
+
elif len(self.grippers) == 1:
|
|
1479
|
+
end_link = self.grippers[0].links[0]
|
|
1480
|
+
ets_end = self._gripper_ets(self.grippers[0])
|
|
1481
|
+
elif len(self.ee_links) > 1:
|
|
1482
|
+
end_link = self._getlink(end, self.ee_links[0])
|
|
1483
|
+
print(
|
|
1484
|
+
"multiple end-effectors present, ambiguous, using self.ee_links[0]"
|
|
1485
|
+
)
|
|
1486
|
+
else:
|
|
1487
|
+
end_link = self._getlink(end, self.ee_links[0])
|
|
1488
|
+
else:
|
|
1489
|
+
if isinstance(end, Gripper):
|
|
1490
|
+
ets_end = self._gripper_ets(end)
|
|
1491
|
+
end_link = end.links[0].parent # type: ignore
|
|
1492
|
+
if end_link is None: # pragma nocover
|
|
1493
|
+
raise ValueError("Invalid robot link configuration")
|
|
1494
|
+
else:
|
|
1495
|
+
end_link = self._getlink(end, self.ee_links[0])
|
|
1496
|
+
|
|
1497
|
+
explored = set()
|
|
1498
|
+
|
|
1499
|
+
if link is end_link:
|
|
1500
|
+
ets = link.ets
|
|
1501
|
+
else:
|
|
1502
|
+
ets = self._find_ets(link, end_link, explored, path=None)
|
|
1503
|
+
|
|
1504
|
+
if ets is None:
|
|
1505
|
+
raise ValueError(
|
|
1506
|
+
"Could not find the requested ETS in this robot"
|
|
1507
|
+
) # pragma nocover
|
|
1508
|
+
|
|
1509
|
+
if ets_init is not None:
|
|
1510
|
+
ets = ets_init * ets
|
|
1511
|
+
|
|
1512
|
+
if ets_end is not None:
|
|
1513
|
+
ets = ets * ets_end
|
|
1514
|
+
|
|
1515
|
+
return ets
|
|
1516
|
+
|
|
1517
|
+
def copy(self):
|
|
1518
|
+
return deepcopy(self)
|
|
1519
|
+
|
|
1520
|
+
def __deepcopy__(self, memo):
|
|
1521
|
+
links = []
|
|
1522
|
+
|
|
1523
|
+
# if isinstance(self, rtb.DHRobot):
|
|
1524
|
+
# cls = rtb.DHRobot
|
|
1525
|
+
if isinstance(self, rtb.Robot2):
|
|
1526
|
+
cls = rtb.Robot2
|
|
1527
|
+
else:
|
|
1528
|
+
cls = rtb.Robot
|
|
1529
|
+
|
|
1530
|
+
for link in self.links:
|
|
1531
|
+
links.append(deepcopy(link))
|
|
1532
|
+
|
|
1533
|
+
name = deepcopy(self.name)
|
|
1534
|
+
manufacturer = deepcopy(self.manufacturer)
|
|
1535
|
+
comment = deepcopy(self.comment)
|
|
1536
|
+
base = deepcopy(self.base)
|
|
1537
|
+
tool = deepcopy(self.tool)
|
|
1538
|
+
gravity = deepcopy(self.gravity)
|
|
1539
|
+
keywords = deepcopy(self.keywords)
|
|
1540
|
+
symbolic = deepcopy(self.symbolic)
|
|
1541
|
+
configs = deepcopy(self.configs)
|
|
1542
|
+
|
|
1543
|
+
result = cls(
|
|
1544
|
+
links,
|
|
1545
|
+
name=name,
|
|
1546
|
+
manufacturer=manufacturer,
|
|
1547
|
+
comment=comment,
|
|
1548
|
+
base=base, # type: ignore
|
|
1549
|
+
tool=tool,
|
|
1550
|
+
gravity=gravity,
|
|
1551
|
+
keywords=keywords,
|
|
1552
|
+
symbolic=symbolic,
|
|
1553
|
+
configs=configs,
|
|
1554
|
+
)
|
|
1555
|
+
|
|
1556
|
+
# if a configuration was an attribute of original robot, make it an
|
|
1557
|
+
# attribute of the deep copy
|
|
1558
|
+
for config in configs:
|
|
1559
|
+
if hasattr(self, config):
|
|
1560
|
+
setattr(result, config, configs[config])
|
|
1561
|
+
|
|
1562
|
+
memo[id(self)] = result
|
|
1563
|
+
return result
|
|
1564
|
+
|
|
1565
|
+
# --------------------------------------------------------------------- #
|
|
1566
|
+
|
|
1567
|
+
def todegrees(self, q) -> NDArray:
|
|
1568
|
+
"""
|
|
1569
|
+
Convert joint angles to degrees
|
|
1570
|
+
|
|
1571
|
+
:param q: The joint configuration of the robot
|
|
1572
|
+
:returns: a vector of joint coordinates in degrees and metres
|
|
1573
|
+
:returns: ``robot.todegrees(q)`` converts joint coordinates ``q`` to degrees
|
|
1574
|
+
:returns: taking into account whether elements of ``q`` correspond to revolute
|
|
1575
|
+
:returns: or prismatic joints, ie. prismatic joint values are not converted.
|
|
1576
|
+
:returns: If ``q`` is a matrix, with one column per joint, the conversion is
|
|
1577
|
+
:returns: performed columnwise.
|
|
1578
|
+
|
|
1579
|
+
Examples
|
|
1580
|
+
--------
|
|
1581
|
+
|
|
1582
|
+
.. runblock:: pycon
|
|
1583
|
+
|
|
1584
|
+
>>> import roboticstoolbox as rtb
|
|
1585
|
+
>>> from math import pi
|
|
1586
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1587
|
+
>>> stanford.todegrees([pi/4, pi/8, 2, -pi/4, pi/6, pi/3])
|
|
1588
|
+
|
|
1589
|
+
"""
|
|
1590
|
+
|
|
1591
|
+
q = getmatrix(q, (None, self.n))
|
|
1592
|
+
|
|
1593
|
+
for j, revolute in enumerate(self.revolutejoints):
|
|
1594
|
+
if revolute:
|
|
1595
|
+
q[:, j] *= 180.0 / np.pi
|
|
1596
|
+
|
|
1597
|
+
if q.shape[0] == 1:
|
|
1598
|
+
return q[0]
|
|
1599
|
+
else:
|
|
1600
|
+
return q
|
|
1601
|
+
|
|
1602
|
+
def toradians(self, q) -> NDArray:
|
|
1603
|
+
"""
|
|
1604
|
+
Convert joint angles to radians
|
|
1605
|
+
|
|
1606
|
+
``robot.toradians(q)`` converts joint coordinates ``q`` to radians
|
|
1607
|
+
taking into account whether elements of ``q`` correspond to revolute
|
|
1608
|
+
or prismatic joints, ie. prismatic joint values are not converted.
|
|
1609
|
+
|
|
1610
|
+
If ``q`` is a matrix, with one column per joint, the conversion is
|
|
1611
|
+
performed columnwise.
|
|
1612
|
+
|
|
1613
|
+
:param q: The joint configuration of the robot
|
|
1614
|
+
:returns: a vector of joint coordinates in radians and metres
|
|
1615
|
+
|
|
1616
|
+
Examples
|
|
1617
|
+
--------
|
|
1618
|
+
|
|
1619
|
+
.. runblock:: pycon
|
|
1620
|
+
|
|
1621
|
+
>>> import roboticstoolbox as rtb
|
|
1622
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1623
|
+
>>> stanford.toradians([10, 20, 2, 30, 40, 50])
|
|
1624
|
+
|
|
1625
|
+
"""
|
|
1626
|
+
|
|
1627
|
+
q = getmatrix(q, (None, self.n))
|
|
1628
|
+
|
|
1629
|
+
for j, revolute in enumerate(self.revolutejoints):
|
|
1630
|
+
if revolute:
|
|
1631
|
+
q[:, j] *= np.pi / 180.0
|
|
1632
|
+
|
|
1633
|
+
if q.shape[0] == 1:
|
|
1634
|
+
return q[0]
|
|
1635
|
+
else:
|
|
1636
|
+
return q
|
|
1637
|
+
|
|
1638
|
+
def isrevolute(self, j) -> bool:
|
|
1639
|
+
"""
|
|
1640
|
+
Check if joint is revolute
|
|
1641
|
+
|
|
1642
|
+
:returns: True if revolute
|
|
1643
|
+
|
|
1644
|
+
Examples
|
|
1645
|
+
--------
|
|
1646
|
+
|
|
1647
|
+
.. runblock:: pycon
|
|
1648
|
+
|
|
1649
|
+
>>> import roboticstoolbox as rtb
|
|
1650
|
+
>>> puma = rtb.models.DH.Puma560()
|
|
1651
|
+
>>> puma.revolutejoints
|
|
1652
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1653
|
+
>>> stanford.isrevolute(1)
|
|
1654
|
+
|
|
1655
|
+
See Also
|
|
1656
|
+
--------
|
|
1657
|
+
:func:`Link.isrevolute`
|
|
1658
|
+
:func:`revolutejoints`
|
|
1659
|
+
|
|
1660
|
+
"""
|
|
1661
|
+
return self.revolutejoints[j]
|
|
1662
|
+
|
|
1663
|
+
def isprismatic(self, j) -> bool:
|
|
1664
|
+
"""
|
|
1665
|
+
Check if joint is prismatic
|
|
1666
|
+
|
|
1667
|
+
:returns: True if prismatic
|
|
1668
|
+
|
|
1669
|
+
Examples
|
|
1670
|
+
--------
|
|
1671
|
+
|
|
1672
|
+
.. runblock:: pycon
|
|
1673
|
+
|
|
1674
|
+
>>> import roboticstoolbox as rtb
|
|
1675
|
+
>>> puma = rtb.models.DH.Puma560()
|
|
1676
|
+
>>> puma.prismaticjoints
|
|
1677
|
+
>>> stanford = rtb.models.DH.Stanford()
|
|
1678
|
+
>>> stanford.isprismatic(1)
|
|
1679
|
+
|
|
1680
|
+
See Also
|
|
1681
|
+
--------
|
|
1682
|
+
:func:`Link.isprismatic`
|
|
1683
|
+
:func:`prismaticjoints`
|
|
1684
|
+
|
|
1685
|
+
"""
|
|
1686
|
+
|
|
1687
|
+
return self.prismaticjoints[j]
|
|
1688
|
+
|
|
1689
|
+
# --------------------------------------------------------------------- #
|
|
1690
|
+
|
|
1691
|
+
def dfs_links(
|
|
1692
|
+
self,
|
|
1693
|
+
start: LinkType,
|
|
1694
|
+
func: Callable[[LinkType | None, Any]] = None,
|
|
1695
|
+
) -> list[LinkType]:
|
|
1696
|
+
"""
|
|
1697
|
+
A link search method
|
|
1698
|
+
|
|
1699
|
+
Visit all links from start in depth-first order and will apply
|
|
1700
|
+
func to each visited link
|
|
1701
|
+
|
|
1702
|
+
:param start: The link to start at
|
|
1703
|
+
:param func: An optional function to apply to each link as it is found
|
|
1704
|
+
:returns: A list of links
|
|
1705
|
+
"""
|
|
1706
|
+
|
|
1707
|
+
visited = []
|
|
1708
|
+
|
|
1709
|
+
def vis_children(link):
|
|
1710
|
+
visited.append(link)
|
|
1711
|
+
if func is not None:
|
|
1712
|
+
func(link)
|
|
1713
|
+
|
|
1714
|
+
for li in link.children:
|
|
1715
|
+
if li not in visited:
|
|
1716
|
+
vis_children(li)
|
|
1717
|
+
|
|
1718
|
+
vis_children(start)
|
|
1719
|
+
|
|
1720
|
+
return visited
|
|
1721
|
+
|
|
1722
|
+
def addconfiguration_attr(self, name: str, q: ArrayLike, unit: str = "rad"):
|
|
1723
|
+
"""
|
|
1724
|
+
Add a named joint configuration as an attribute
|
|
1725
|
+
|
|
1726
|
+
:param name: Name of the joint configuration
|
|
1727
|
+
:param q: Joint configuration
|
|
1728
|
+
|
|
1729
|
+
Examples
|
|
1730
|
+
--------
|
|
1731
|
+
|
|
1732
|
+
.. runblock:: pycon
|
|
1733
|
+
|
|
1734
|
+
>>> import roboticstoolbox as rtb
|
|
1735
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
1736
|
+
>>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
|
|
1737
|
+
>>> robot.mypos
|
|
1738
|
+
>>> robot.configs["mypos"]
|
|
1739
|
+
|
|
1740
|
+
.. rubric:: Notes
|
|
1741
|
+
|
|
1742
|
+
- Used in robot model init method to store the ``qr`` configuration
|
|
1743
|
+
- Dynamically adding attributes to objects can cause issues with
|
|
1744
|
+
Python type checking.
|
|
1745
|
+
- Configuration is also added to the robot instance's dictionary of
|
|
1746
|
+
named configurations.
|
|
1747
|
+
|
|
1748
|
+
See Also
|
|
1749
|
+
--------
|
|
1750
|
+
:meth:`addconfiguration`
|
|
1751
|
+
|
|
1752
|
+
"""
|
|
1753
|
+
|
|
1754
|
+
v = getunit(q, unit, dim=self.n)
|
|
1755
|
+
self._configs[name] = v
|
|
1756
|
+
setattr(self, name, v)
|
|
1757
|
+
|
|
1758
|
+
def addconfiguration(self, name: str, q: ArrayLike):
|
|
1759
|
+
"""
|
|
1760
|
+
Add a named joint configuration
|
|
1761
|
+
|
|
1762
|
+
Add a named configuration to the robot instance's dictionary of named
|
|
1763
|
+
configurations.
|
|
1764
|
+
|
|
1765
|
+
:param name: Name of the joint configuration
|
|
1766
|
+
:param q: Joint configuration
|
|
1767
|
+
|
|
1768
|
+
Examples
|
|
1769
|
+
--------
|
|
1770
|
+
|
|
1771
|
+
.. runblock:: pycon
|
|
1772
|
+
|
|
1773
|
+
>>> import roboticstoolbox as rtb
|
|
1774
|
+
>>> robot = rtb.models.DH.Puma560()
|
|
1775
|
+
>>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
|
|
1776
|
+
>>> robot.configs["mypos"]
|
|
1777
|
+
|
|
1778
|
+
See Also
|
|
1779
|
+
--------
|
|
1780
|
+
:meth:`addconfiguration`
|
|
1781
|
+
|
|
1782
|
+
"""
|
|
1783
|
+
|
|
1784
|
+
self._configs[name] = np.array(getvector(q, self.n))
|
|
1785
|
+
|
|
1786
|
+
def configurations_str(self, border="thin"):
|
|
1787
|
+
deg = 180 / np.pi
|
|
1788
|
+
|
|
1789
|
+
# TODO: factor this out of DHRobot
|
|
1790
|
+
def angle(theta, fmt=None):
|
|
1791
|
+
if fmt is not None:
|
|
1792
|
+
try:
|
|
1793
|
+
return fmt.format(theta * deg) + "\u00b0"
|
|
1794
|
+
except TypeError: # pragma nocover
|
|
1795
|
+
pass
|
|
1796
|
+
|
|
1797
|
+
return str(theta * deg) + "\u00b0" # pragma nocover
|
|
1798
|
+
|
|
1799
|
+
# show named configurations
|
|
1800
|
+
if len(self._configs) > 0:
|
|
1801
|
+
table = ANSITable(
|
|
1802
|
+
Column("name", colalign=">"),
|
|
1803
|
+
*[
|
|
1804
|
+
Column(f"q{j:d}", colalign="<", headalign="<")
|
|
1805
|
+
for j in range(self.n)
|
|
1806
|
+
],
|
|
1807
|
+
border=border,
|
|
1808
|
+
)
|
|
1809
|
+
|
|
1810
|
+
for name, q in self._configs.items():
|
|
1811
|
+
qlist = []
|
|
1812
|
+
for j, c in enumerate(self.structure):
|
|
1813
|
+
if c == "P":
|
|
1814
|
+
qlist.append(f"{q[j]: .3g}")
|
|
1815
|
+
else:
|
|
1816
|
+
qlist.append(angle(q[j], "{: .3g}"))
|
|
1817
|
+
table.row(name, *qlist)
|
|
1818
|
+
|
|
1819
|
+
return "\n" + str(table)
|
|
1820
|
+
else: # pragma nocover
|
|
1821
|
+
return ""
|
|
1822
|
+
|
|
1823
|
+
def random_q(self):
|
|
1824
|
+
"""
|
|
1825
|
+
Return a random joint configuration
|
|
1826
|
+
|
|
1827
|
+
The value for each joint is uniform randomly distributed between the
|
|
1828
|
+
limits set for the robot.
|
|
1829
|
+
|
|
1830
|
+
.. rubric:: Notes
|
|
1831
|
+
|
|
1832
|
+
The joint limit for all joints must be set.
|
|
1833
|
+
|
|
1834
|
+
:returns: Random joint configuration :rtype: ndarray(n)
|
|
1835
|
+
|
|
1836
|
+
See Also
|
|
1837
|
+
--------
|
|
1838
|
+
:func:`Robot.qlim`
|
|
1839
|
+
:func:`Link.qlim`
|
|
1840
|
+
|
|
1841
|
+
"""
|
|
1842
|
+
|
|
1843
|
+
qlim = self.qlim
|
|
1844
|
+
if np.any(np.isnan(qlim)):
|
|
1845
|
+
raise ValueError("some joint limits not defined") # pragma nocover
|
|
1846
|
+
return np.random.uniform(low=qlim[0, :], high=qlim[1, :], size=(self.n,))
|
|
1847
|
+
|
|
1848
|
+
def hierarchy(self) -> None:
|
|
1849
|
+
"""
|
|
1850
|
+
Pretty print the robot link hierachy
|
|
1851
|
+
|
|
1852
|
+
:returns: Pretty print of the robot model
|
|
1853
|
+
|
|
1854
|
+
Examples
|
|
1855
|
+
--------
|
|
1856
|
+
|
|
1857
|
+
Makes a robot and prints the heirachy
|
|
1858
|
+
|
|
1859
|
+
.. runblock:: pycon
|
|
1860
|
+
>>> import roboticstoolbox as rtb
|
|
1861
|
+
>>> robot = rtb.models.URDF.Panda()
|
|
1862
|
+
>>> robot.hierarchy()
|
|
1863
|
+
|
|
1864
|
+
"""
|
|
1865
|
+
|
|
1866
|
+
def recurse(link, indent=0):
|
|
1867
|
+
print(" " * indent * 2, link.name)
|
|
1868
|
+
if link.children is not None:
|
|
1869
|
+
for child in link.children:
|
|
1870
|
+
recurse(child, indent + 1)
|
|
1871
|
+
|
|
1872
|
+
recurse(self.base_link)
|
|
1873
|
+
|
|
1874
|
+
def segments(self) -> list[list[LinkType | None]]:
|
|
1875
|
+
"""
|
|
1876
|
+
Segments of branched robot
|
|
1877
|
+
|
|
1878
|
+
For a single-chain robot with structure::
|
|
1879
|
+
|
|
1880
|
+
L1 - L2 - L3
|
|
1881
|
+
|
|
1882
|
+
the return is ``[[None, L1, L2, L3]]``
|
|
1883
|
+
|
|
1884
|
+
For a robot with structure::
|
|
1885
|
+
|
|
1886
|
+
L1 - L2 +- L3 - L4
|
|
1887
|
+
+- L5 - L6
|
|
1888
|
+
|
|
1889
|
+
the return is ``[[None, L1, L2], [L2, L3, L4], [L2, L5, L6]]``
|
|
1890
|
+
|
|
1891
|
+
:returns: Segment list
|
|
1892
|
+
|
|
1893
|
+
.. rubric:: Notes
|
|
1894
|
+
|
|
1895
|
+
- the length of the list is the number of segments in the robot
|
|
1896
|
+
- the first segment always starts with ``None`` which represents
|
|
1897
|
+
the base transform (since there is no base link)
|
|
1898
|
+
- the last link of one segment is also the first link of subsequent
|
|
1899
|
+
segments
|
|
1900
|
+
"""
|
|
1901
|
+
|
|
1902
|
+
def recurse(link: Link):
|
|
1903
|
+
segs = [link.parent]
|
|
1904
|
+
while True:
|
|
1905
|
+
segs.append(link)
|
|
1906
|
+
if link.nchildren == 0:
|
|
1907
|
+
return [segs]
|
|
1908
|
+
elif link.nchildren == 1:
|
|
1909
|
+
link = link.children[0] # type: ignore
|
|
1910
|
+
continue
|
|
1911
|
+
elif link.nchildren > 1:
|
|
1912
|
+
segs = [segs]
|
|
1913
|
+
|
|
1914
|
+
for child in link.children: # type: ignore
|
|
1915
|
+
segs.extend(recurse(child))
|
|
1916
|
+
|
|
1917
|
+
return segs
|
|
1918
|
+
|
|
1919
|
+
return recurse(self.links[0]) # type: ignore
|
|
1920
|
+
|
|
1921
|
+
# --------------------------------------------------------------------- #
|
|
1922
|
+
# Scene Graph section
|
|
1923
|
+
# --------------------------------------------------------------------- #
|
|
1924
|
+
|
|
1925
|
+
def _update_link_tf(self, q: ArrayLike | None = None):
|
|
1926
|
+
"""
|
|
1927
|
+
This private method updates the local transform of each link within
|
|
1928
|
+
this robot according to q (or self.q if q is none)
|
|
1929
|
+
"""
|
|
1930
|
+
|
|
1931
|
+
@lru_cache(maxsize=2)
|
|
1932
|
+
def get_link_ets():
|
|
1933
|
+
return [link.ets._fknm for link in self.links]
|
|
1934
|
+
|
|
1935
|
+
@lru_cache(maxsize=2)
|
|
1936
|
+
def get_link_scene_node():
|
|
1937
|
+
return [link._T_reference for link in self.links]
|
|
1938
|
+
|
|
1939
|
+
Robot_link_T(get_link_ets(), get_link_scene_node(), self._q, q)
|
|
1940
|
+
|
|
1941
|
+
[gripper._update_link_tf() for gripper in self.grippers]
|
|
1942
|
+
|
|
1943
|
+
# --------------------------------------------------------------------- #
|
|
1944
|
+
# --------- PyPlot Methods -------------------------------------------- #
|
|
1945
|
+
# --------------------------------------------------------------------- #
|
|
1946
|
+
|
|
1947
|
+
def _get_graphical_backend(
|
|
1948
|
+
self,
|
|
1949
|
+
backend: L["swift", "pyplot", "pyplot2"] | None = None,
|
|
1950
|
+
) -> Connector:
|
|
1951
|
+
import sys
|
|
1952
|
+
from roboticstoolbox.backends import load_backend
|
|
1953
|
+
|
|
1954
|
+
# Resolve which backend name to use
|
|
1955
|
+
if backend is not None:
|
|
1956
|
+
using_backend = backend.lower()
|
|
1957
|
+
else:
|
|
1958
|
+
# In JupyterLite/Pyodide, only pyplot/pyplot2 are available
|
|
1959
|
+
if sys.platform == "emscripten":
|
|
1960
|
+
if isinstance(self, rtb.Robot2):
|
|
1961
|
+
using_backend = "pyplot2"
|
|
1962
|
+
else:
|
|
1963
|
+
using_backend = "pyplot"
|
|
1964
|
+
else:
|
|
1965
|
+
# Infer from robot type and robot-level default override
|
|
1966
|
+
using_backend = self.default_backend
|
|
1967
|
+
if using_backend is None:
|
|
1968
|
+
if isinstance(self, rtb.DHRobot):
|
|
1969
|
+
using_backend = "pyplot"
|
|
1970
|
+
elif isinstance(self, rtb.Robot2):
|
|
1971
|
+
using_backend = "pyplot2"
|
|
1972
|
+
elif isinstance(self, rtb.Robot):
|
|
1973
|
+
using_backend = "swift" if self.hasgeometry else "pyplot"
|
|
1974
|
+
else:
|
|
1975
|
+
using_backend = "pyplot"
|
|
1976
|
+
|
|
1977
|
+
# swift is optional; fall back to pyplot with a helpful message
|
|
1978
|
+
if using_backend == "swift": # pragma nocover
|
|
1979
|
+
if isinstance(self, rtb.DHRobot):
|
|
1980
|
+
raise NotImplementedError(
|
|
1981
|
+
"Plotting in Swift is not implemented for DHRobots yet"
|
|
1982
|
+
)
|
|
1983
|
+
try:
|
|
1984
|
+
return load_backend("swift")
|
|
1985
|
+
except ModuleNotFoundError:
|
|
1986
|
+
if backend is not None:
|
|
1987
|
+
# user explicitly asked for swift
|
|
1988
|
+
print(
|
|
1989
|
+
"Swift is not installed. "
|
|
1990
|
+
"Install it with: pip install swift-sim"
|
|
1991
|
+
)
|
|
1992
|
+
using_backend = "pyplot"
|
|
1993
|
+
|
|
1994
|
+
return load_backend(using_backend)
|
|
1995
|
+
|
|
1996
|
+
def plot(
|
|
1997
|
+
self,
|
|
1998
|
+
q: ArrayLike,
|
|
1999
|
+
backend: L["swift", "pyplot", "pyplot2"] | None = None,
|
|
2000
|
+
block: bool = False,
|
|
2001
|
+
dt: float = 0.050,
|
|
2002
|
+
limits: ArrayLike | None = None,
|
|
2003
|
+
vellipse: bool = False,
|
|
2004
|
+
fellipse: bool = False,
|
|
2005
|
+
fig: str | None = None,
|
|
2006
|
+
movie: str | None = None,
|
|
2007
|
+
loop: bool = False,
|
|
2008
|
+
**kwargs,
|
|
2009
|
+
) -> Connector:
|
|
2010
|
+
"""
|
|
2011
|
+
Graphical display and animation
|
|
2012
|
+
|
|
2013
|
+
``robot.plot(q, 'pyplot')`` displays a graphical view of a robot
|
|
2014
|
+
based on the kinematic model and the joint configuration ``q``.
|
|
2015
|
+
This is a stick figure polyline which joins the origins of the
|
|
2016
|
+
link coordinate frames. The plot will autoscale with an aspect
|
|
2017
|
+
ratio of 1.
|
|
2018
|
+
|
|
2019
|
+
If ``q`` (m,n) representing a joint-space trajectory it will create an
|
|
2020
|
+
animation with a pause of ``dt`` seconds between each frame.
|
|
2021
|
+
|
|
2022
|
+
:param q: The joint configuration of the robot.
|
|
2023
|
+
:param backend: The graphical backend to use, currently 'swift' and 'pyplot' are implemented. Defaults to 'swift' of a ``Robot`` and 'pyplot` for a ``DHRobot``
|
|
2024
|
+
:param block: Block operation of the code and keep the figure open
|
|
2025
|
+
:param dt: if q is a trajectory, this describes the delay in seconds between frames
|
|
2026
|
+
:param limits: Custom view limits for the plot. If not supplied will autoscale, [x1, x2, y1, y2, z1, z2] (this option is for 'pyplot' only)
|
|
2027
|
+
:param vellipse: (Plot Option) Plot the velocity ellipse at the end-effector (this option is for 'pyplot' only)
|
|
2028
|
+
:param fellipse: (Plot Option) Plot the force ellipse at the end-effector (this option is for 'pyplot' only)
|
|
2029
|
+
:param fig: (Plot Option) The figure label to plot in (this option is for 'pyplot' only)
|
|
2030
|
+
:param movie: (Plot Option) The filename to save the movie to (this option is for 'pyplot' only)
|
|
2031
|
+
:param loop: (Plot Option) Loop the movie (this option is for 'pyplot' only)
|
|
2032
|
+
:param jointaxes: (Plot Option) Plot an arrow indicating the axes in which the joint revolves around(revolute joint) or translates along (prosmatic joint) (this option is for 'pyplot' only)
|
|
2033
|
+
:param eeframe: (Plot Option) Plot the end-effector coordinate frame at the location of the end-effector. Uses three arrows, red, green and blue to indicate the x, y, and z-axes. (this option is for 'pyplot' only)
|
|
2034
|
+
:param shadow: (Plot Option) Plot a shadow of the robot in the x-y plane. (this option is for 'pyplot' only)
|
|
2035
|
+
:param name: (Plot Option) Plot the name of the robot near its base (this option is for 'pyplot' only)
|
|
2036
|
+
:param render_mode: (Plot Option) Rendering mode for matplotlib backends: ``'window'``, ``'notebook-widget'``, or ``'notebook-inline'``. If omitted, an environment-appropriate mode is selected.
|
|
2037
|
+
:param inline_every_n: (Plot Option) In notebook-inline mode, push one rendered frame every N simulation steps. Larger N reduces output load.
|
|
2038
|
+
:param inline_format: (Plot Option) In notebook-inline mode, frame format: ``'svg'`` (default) or ``'png'``.
|
|
2039
|
+
:param inline_dpi: (Plot Option) DPI for PNG inline frames only; ignored when ``inline_format='svg'``.
|
|
2040
|
+
:returns: A reference to the environment object which controls the figure
|
|
2041
|
+
|
|
2042
|
+
.. rubric:: Notes
|
|
2043
|
+
|
|
2044
|
+
- By default this method will block until the figure is dismissed.
|
|
2045
|
+
To avoid this set ``block=False``.
|
|
2046
|
+
- For PyPlot, the polyline joins the origins of the link frames,
|
|
2047
|
+
but for some Denavit-Hartenberg models those frames may not
|
|
2048
|
+
actually be on the robot, ie. the lines to not neccessarily
|
|
2049
|
+
represent the links of the robot.
|
|
2050
|
+
|
|
2051
|
+
See Also
|
|
2052
|
+
--------
|
|
2053
|
+
:func:`teach`
|
|
2054
|
+
|
|
2055
|
+
"""
|
|
2056
|
+
|
|
2057
|
+
env = None
|
|
2058
|
+
|
|
2059
|
+
env = self._get_graphical_backend(backend)
|
|
2060
|
+
|
|
2061
|
+
launch_kwargs = {}
|
|
2062
|
+
for key in ("render_mode", "inline_every_n", "inline_format", "inline_dpi"):
|
|
2063
|
+
if key in kwargs:
|
|
2064
|
+
launch_kwargs[key] = kwargs.pop(key)
|
|
2065
|
+
|
|
2066
|
+
q = np.array(getmatrix(q, (None, self.n)))
|
|
2067
|
+
self.q = q[0, :]
|
|
2068
|
+
|
|
2069
|
+
# Add the self to the figure in readonly mode
|
|
2070
|
+
if q.shape[0] == 1:
|
|
2071
|
+
env.launch(
|
|
2072
|
+
name=self.name + " Plot", limits=limits, fig=fig, **launch_kwargs
|
|
2073
|
+
)
|
|
2074
|
+
else:
|
|
2075
|
+
env.launch(
|
|
2076
|
+
name=self.name + " Trajectory Plot",
|
|
2077
|
+
limits=limits,
|
|
2078
|
+
fig=fig,
|
|
2079
|
+
**launch_kwargs,
|
|
2080
|
+
)
|
|
2081
|
+
|
|
2082
|
+
env.add(self, readonly=True, **kwargs)
|
|
2083
|
+
self._active_plot_env = env
|
|
2084
|
+
|
|
2085
|
+
if vellipse:
|
|
2086
|
+
vell = self.vellipse(q[0], centre="ee", add=False)
|
|
2087
|
+
env.add(vell)
|
|
2088
|
+
else:
|
|
2089
|
+
vell = None
|
|
2090
|
+
|
|
2091
|
+
if fellipse:
|
|
2092
|
+
fell = self.fellipse(q[0], centre="ee", add=False)
|
|
2093
|
+
env.add(fell)
|
|
2094
|
+
else:
|
|
2095
|
+
fell = None
|
|
2096
|
+
|
|
2097
|
+
# List of images saved from each plot
|
|
2098
|
+
images = []
|
|
2099
|
+
|
|
2100
|
+
if movie is not None: # pragma: nocover
|
|
2101
|
+
loop = False
|
|
2102
|
+
|
|
2103
|
+
while True:
|
|
2104
|
+
for qk in q:
|
|
2105
|
+
self.q = qk
|
|
2106
|
+
if vell is not None:
|
|
2107
|
+
vell.q = qk
|
|
2108
|
+
if fell is not None:
|
|
2109
|
+
fell.q = qk
|
|
2110
|
+
env.step(dt)
|
|
2111
|
+
|
|
2112
|
+
if movie is not None and isinstance(env, PyPlot): # pragma nocover
|
|
2113
|
+
images.append(env.getframe())
|
|
2114
|
+
|
|
2115
|
+
if movie is not None: # pragma nocover
|
|
2116
|
+
# save it as an animated GIF
|
|
2117
|
+
images[0].save(
|
|
2118
|
+
movie,
|
|
2119
|
+
save_all=True,
|
|
2120
|
+
append_images=images[1:],
|
|
2121
|
+
optimize=False,
|
|
2122
|
+
duration=dt,
|
|
2123
|
+
loop=0,
|
|
2124
|
+
)
|
|
2125
|
+
if not loop:
|
|
2126
|
+
break
|
|
2127
|
+
|
|
2128
|
+
# Keep the plot open
|
|
2129
|
+
if block: # pragma nocover
|
|
2130
|
+
env.hold()
|
|
2131
|
+
|
|
2132
|
+
return env
|
|
2133
|
+
|
|
2134
|
+
def teach(
|
|
2135
|
+
self,
|
|
2136
|
+
q: ArrayLike | None,
|
|
2137
|
+
block: bool = True,
|
|
2138
|
+
limits: ArrayLike | None = None,
|
|
2139
|
+
vellipse: bool = False,
|
|
2140
|
+
fellipse: bool = False,
|
|
2141
|
+
backend: L["swift", "pyplot", "pyplot2"] | None = None,
|
|
2142
|
+
) -> Connector:
|
|
2143
|
+
"""
|
|
2144
|
+
Graphical teach pendant
|
|
2145
|
+
|
|
2146
|
+
``robot.teach(q)`` opens a graphical view (PyPlot or Swift --
|
|
2147
|
+
see ``backend``) which allows the user to "drive" a graphical
|
|
2148
|
+
robot using a graphical slider panel. The robot's inital joint
|
|
2149
|
+
configuration is ``q``.
|
|
2150
|
+
|
|
2151
|
+
``robot.teach()`` as above except the robot's stored value of ``q``
|
|
2152
|
+
is used.
|
|
2153
|
+
|
|
2154
|
+
:param q: The joint configuration of the robot (Optional,
|
|
2155
|
+
if not supplied will use the stored q values).
|
|
2156
|
+
:param block: Block operation of the code and keep the figure open
|
|
2157
|
+
:param limits: Custom view limits for the plot. If not supplied will
|
|
2158
|
+
autoscale, [x1, x2, y1, y2, z1, z2] (this option is for
|
|
2159
|
+
'pyplot'/'pyplot2' only)
|
|
2160
|
+
:param vellipse: (Plot Option) Plot the velocity ellipse at the
|
|
2161
|
+
end-effector (this option is for 'pyplot' only)
|
|
2162
|
+
:param fellipse: (Plot Option) Plot the force ellipse at the
|
|
2163
|
+
end-effector (this option is for 'pyplot' only)
|
|
2164
|
+
:param backend: The graphical backend to use -- 'swift', 'pyplot',
|
|
2165
|
+
or 'pyplot2'. Defaults to whatever :meth:`plot` would pick for
|
|
2166
|
+
this robot (see its own ``backend`` for the resolution rule).
|
|
2167
|
+
|
|
2168
|
+
:returns: A reference to the environment object which controls
|
|
2169
|
+
the figure/view
|
|
2170
|
+
|
|
2171
|
+
.. rubric:: Notes
|
|
2172
|
+
|
|
2173
|
+
- Program execution is blocked until the teach window is
|
|
2174
|
+
dismissed. If ``block=False`` the method is non-blocking but
|
|
2175
|
+
you need to poll the window manager (PyPlot) or keep calling
|
|
2176
|
+
``env.step()`` yourself (Swift) to ensure the window/panel
|
|
2177
|
+
remains responsive.
|
|
2178
|
+
- Once ``teach()`` returns (``block=True``), ``robot.q`` holds
|
|
2179
|
+
the final pose the sliders were left at -- true for every
|
|
2180
|
+
backend, even though Swift's own live joint state during the
|
|
2181
|
+
session lives on the returned environment's handle
|
|
2182
|
+
(``env.swift_objects[0].q``), not ``robot.q`` itself, to keep
|
|
2183
|
+
the robot model plain and shareable while teaching is
|
|
2184
|
+
in progress (see swift's ``AssemblyHandle``).
|
|
2185
|
+
- The slider limits are derived from the joint limit properties.
|
|
2186
|
+
If not set then:
|
|
2187
|
+
|
|
2188
|
+
- For revolute joints they are assumed to be [-pi, +pi]
|
|
2189
|
+
- For prismatic joint they are assumed unknown and an error
|
|
2190
|
+
occurs.
|
|
2191
|
+
|
|
2192
|
+
"""
|
|
2193
|
+
|
|
2194
|
+
if q is None:
|
|
2195
|
+
q = np.zeros((self.n,))
|
|
2196
|
+
else:
|
|
2197
|
+
q = getvector(q, self.n)
|
|
2198
|
+
|
|
2199
|
+
# Make an empty 3D figure
|
|
2200
|
+
env = self._get_graphical_backend(backend)
|
|
2201
|
+
|
|
2202
|
+
if not getattr(env, "supports_teach", True): # pragma: nocover
|
|
2203
|
+
raise TypeError(
|
|
2204
|
+
f"teach() is not supported by the {type(env).__name__} backend"
|
|
2205
|
+
)
|
|
2206
|
+
|
|
2207
|
+
# Add the self to the figure in readonly mode
|
|
2208
|
+
env.launch(name="Teach " + self.name, limits=limits)
|
|
2209
|
+
handle = env.add(
|
|
2210
|
+
self,
|
|
2211
|
+
readonly=True,
|
|
2212
|
+
# jointaxes=jointaxes,
|
|
2213
|
+
# jointlabels=jointlabels,
|
|
2214
|
+
# eeframe=eeframe,
|
|
2215
|
+
# shadow=shadow,
|
|
2216
|
+
# name=name,
|
|
2217
|
+
)
|
|
2218
|
+
self._active_plot_env = env
|
|
2219
|
+
|
|
2220
|
+
# True if _add_teach_panel already fully handled block=True itself
|
|
2221
|
+
# (Swift's own env.run() loop -- see its docstring) -- PyPlot/
|
|
2222
|
+
# PyPlot2 never do (their own env.hold() below is what actually
|
|
2223
|
+
# enters matplotlib's blocking GUI mainloop), so their
|
|
2224
|
+
# _add_teach_panel implicitly returns None here, same as always.
|
|
2225
|
+
already_blocked = env._add_teach_panel(self, q, handle, block)
|
|
2226
|
+
|
|
2227
|
+
if vellipse:
|
|
2228
|
+
vell = self.vellipse(q, centre="ee", scale=0.5, add=False)
|
|
2229
|
+
env.add(vell)
|
|
2230
|
+
|
|
2231
|
+
if fellipse:
|
|
2232
|
+
fell = self.fellipse(q, centre="ee", add=False)
|
|
2233
|
+
env.add(fell)
|
|
2234
|
+
|
|
2235
|
+
# Keep the plot open -- skipped if the backend already blocked
|
|
2236
|
+
# itself above: calling this a second time isn't just wasteful,
|
|
2237
|
+
# it can hang outright for Swift (env.hold()'s disconnect-poll
|
|
2238
|
+
# never expires in headless mode, and even non-headless there's
|
|
2239
|
+
# a race where close() doesn't synchronously guarantee the
|
|
2240
|
+
# websocket's been dropped from socket.USERS before returning).
|
|
2241
|
+
if block and not already_blocked: # pragma nocover
|
|
2242
|
+
env.hold()
|
|
2243
|
+
|
|
2244
|
+
return env
|
|
2245
|
+
|
|
2246
|
+
# --------------------------------------------------------------------- #
|
|
2247
|
+
|
|
2248
|
+
# --------------------------------------------------------------------- #
|
|
2249
|
+
# --------- Utility Methods ------------------------------------------- #
|
|
2250
|
+
# --------------------------------------------------------------------- #
|
|
2251
|
+
|
|
2252
|
+
def showgraph(self, display_graph: bool = True, **kwargs) -> str | None:
|
|
2253
|
+
"""
|
|
2254
|
+
Display a link transform graph in browser
|
|
2255
|
+
|
|
2256
|
+
``robot.showgraph()`` displays a graph of the robot's link frames
|
|
2257
|
+
and the ETS between them. It uses GraphViz dot.
|
|
2258
|
+
|
|
2259
|
+
The nodes are:
|
|
2260
|
+
- Base is shown as a grey square. This is the world frame origin,
|
|
2261
|
+
but can be changed using the ``base`` attribute of the robot.
|
|
2262
|
+
- Link frames are indicated by circles
|
|
2263
|
+
- ETS transforms are indicated by rounded boxes
|
|
2264
|
+
|
|
2265
|
+
The edges are:
|
|
2266
|
+
- an arrow if `jtype` is False or the joint is fixed
|
|
2267
|
+
- an arrow with a round head if `jtype` is True and the joint is
|
|
2268
|
+
revolute
|
|
2269
|
+
- an arrow with a box head if `jtype` is True and the joint is
|
|
2270
|
+
prismatic
|
|
2271
|
+
|
|
2272
|
+
Edge labels or nodes in blue have a fixed transformation to the
|
|
2273
|
+
preceding link.
|
|
2274
|
+
|
|
2275
|
+
:param display_graph: Open the graph in a browser if True. Otherwise will return the file path
|
|
2276
|
+
:param etsbox: Put the link ETS in a box, otherwise an edge label
|
|
2277
|
+
:param jtype: Arrowhead to node indicates revolute or prismatic type
|
|
2278
|
+
:param static: Show static joints in blue and bold
|
|
2279
|
+
|
|
2280
|
+
Examples
|
|
2281
|
+
--------
|
|
2282
|
+
|
|
2283
|
+
>>> import roboticstoolbox as rtb
|
|
2284
|
+
>>> panda = rtb.models.URDF.Panda()
|
|
2285
|
+
>>> panda.showgraph()
|
|
2286
|
+
|
|
2287
|
+
.. image:: ../figs/panda-graph.svg
|
|
2288
|
+
:width: 600
|
|
2289
|
+
|
|
2290
|
+
See Also
|
|
2291
|
+
--------
|
|
2292
|
+
:func:`dotfile` :func:`graph`
|
|
2293
|
+
|
|
2294
|
+
"""
|
|
2295
|
+
|
|
2296
|
+
# Lazy import
|
|
2297
|
+
import tempfile
|
|
2298
|
+
import subprocess
|
|
2299
|
+
import webbrowser
|
|
2300
|
+
|
|
2301
|
+
# create the temporary dotfile
|
|
2302
|
+
dotfile = tempfile.TemporaryFile(mode="w")
|
|
2303
|
+
self.dotfile(dotfile, **kwargs)
|
|
2304
|
+
|
|
2305
|
+
# rewind the dot file, create PDF file in the filesystem, run dot
|
|
2306
|
+
dotfile.seek(0)
|
|
2307
|
+
pdffile = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
|
|
2308
|
+
subprocess.run("dot -Tpdf", shell=True, stdin=dotfile, stdout=pdffile)
|
|
2309
|
+
|
|
2310
|
+
# open the PDF file in browser (hopefully portable), then cleanup
|
|
2311
|
+
if display_graph: # pragma nocover
|
|
2312
|
+
webbrowser.open(f"file://{pdffile.name}")
|
|
2313
|
+
else:
|
|
2314
|
+
return pdffile.name
|
|
2315
|
+
|
|
2316
|
+
def dotfile(
|
|
2317
|
+
self,
|
|
2318
|
+
filename: str | IO[str],
|
|
2319
|
+
etsbox: bool = False,
|
|
2320
|
+
ets: L["full", "brief"] = "full",
|
|
2321
|
+
jtype: bool = False,
|
|
2322
|
+
static: bool = True,
|
|
2323
|
+
):
|
|
2324
|
+
"""
|
|
2325
|
+
Write a link transform graph as a GraphViz dot file
|
|
2326
|
+
|
|
2327
|
+
The file can be processed using dot::
|
|
2328
|
+
|
|
2329
|
+
% dot -Tpng -o out.png dotfile.dot
|
|
2330
|
+
|
|
2331
|
+
The nodes are:
|
|
2332
|
+
|
|
2333
|
+
- Base is shown as a grey square. This is the world frame origin,
|
|
2334
|
+
but can be changed using the ``base`` attribute of the robot.
|
|
2335
|
+
- Link frames are indicated by circles
|
|
2336
|
+
- ETS transforms are indicated by rounded boxes
|
|
2337
|
+
|
|
2338
|
+
The edges are:
|
|
2339
|
+
|
|
2340
|
+
- an arrow if `jtype` is False or the joint is fixed
|
|
2341
|
+
- an arrow with a round head if `jtype` is True and the joint is
|
|
2342
|
+
revolute
|
|
2343
|
+
- an arrow with a box head if `jtype` is True and the joint is
|
|
2344
|
+
prismatic
|
|
2345
|
+
|
|
2346
|
+
Edge labels or nodes in blue have a fixed transformation to the
|
|
2347
|
+
preceding link.
|
|
2348
|
+
|
|
2349
|
+
.. rubric:: Notes
|
|
2350
|
+
|
|
2351
|
+
If ``filename`` is a file object then the file will *not*
|
|
2352
|
+
be closed after the GraphViz model is written.
|
|
2353
|
+
|
|
2354
|
+
:param file: Name of file to write to
|
|
2355
|
+
:param etsbox: Put the link ETS in a box, otherwise an edge label
|
|
2356
|
+
:param ets: Display the full ets with "full" or a brief version with "brief"
|
|
2357
|
+
:param jtype: Arrowhead to node indicates revolute or prismatic type
|
|
2358
|
+
:param static: Show static joints in blue and bold
|
|
2359
|
+
|
|
2360
|
+
See Also
|
|
2361
|
+
--------
|
|
2362
|
+
:func:`showgraph` :func:`graph`
|
|
2363
|
+
|
|
2364
|
+
"""
|
|
2365
|
+
|
|
2366
|
+
if isinstance(filename, str):
|
|
2367
|
+
file = open(filename, "w", encoding="utf-8")
|
|
2368
|
+
else:
|
|
2369
|
+
file = filename
|
|
2370
|
+
|
|
2371
|
+
header = r"""digraph G {
|
|
2372
|
+
graph [rankdir=LR];
|
|
2373
|
+
"""
|
|
2374
|
+
|
|
2375
|
+
def draw_edge(link, etsbox, jtype, static):
|
|
2376
|
+
# draw the edge
|
|
2377
|
+
if jtype:
|
|
2378
|
+
if link.isprismatic:
|
|
2379
|
+
edge_options = 'arrowhead="box", arrowtail="inv", dir="both"'
|
|
2380
|
+
elif link.isrevolute:
|
|
2381
|
+
edge_options = 'arrowhead="dot", arrowtail="inv", dir="both"'
|
|
2382
|
+
else:
|
|
2383
|
+
edge_options = 'arrowhead="normal"'
|
|
2384
|
+
else:
|
|
2385
|
+
edge_options = 'arrowhead="normal"'
|
|
2386
|
+
|
|
2387
|
+
if link.parent is None:
|
|
2388
|
+
parent = "BASE"
|
|
2389
|
+
else:
|
|
2390
|
+
parent = link.parent.name
|
|
2391
|
+
|
|
2392
|
+
if etsbox:
|
|
2393
|
+
# put the ets fragment in a box
|
|
2394
|
+
if not link.isjoint and static:
|
|
2395
|
+
node_options = ', fontcolor="blue"'
|
|
2396
|
+
else:
|
|
2397
|
+
node_options = ""
|
|
2398
|
+
|
|
2399
|
+
try:
|
|
2400
|
+
file.write(
|
|
2401
|
+
' {}_ets [shape=box, style=rounded, label="{}"{}];\n'.format(
|
|
2402
|
+
link.name,
|
|
2403
|
+
link.ets.__str__(q=f"q{link.jindex}"),
|
|
2404
|
+
node_options,
|
|
2405
|
+
)
|
|
2406
|
+
)
|
|
2407
|
+
except UnicodeEncodeError: # pragma nocover
|
|
2408
|
+
file.write(
|
|
2409
|
+
' {}_ets [shape=box, style=rounded, label="{}"{}];\n'.format(
|
|
2410
|
+
link.name,
|
|
2411
|
+
link.ets.__str__(q=f"q{link.jindex}")
|
|
2412
|
+
.encode("ascii", "ignore")
|
|
2413
|
+
.decode("ascii"),
|
|
2414
|
+
node_options,
|
|
2415
|
+
)
|
|
2416
|
+
)
|
|
2417
|
+
|
|
2418
|
+
file.write(" {} -> {}_ets;\n".format(parent, link.name))
|
|
2419
|
+
file.write(
|
|
2420
|
+
" {}_ets -> {} [{}];\n".format(link.name, link.name, edge_options)
|
|
2421
|
+
)
|
|
2422
|
+
else:
|
|
2423
|
+
# put the ets fragment as an edge label
|
|
2424
|
+
if not link.isjoint and static:
|
|
2425
|
+
edge_options += ', fontcolor="blue"'
|
|
2426
|
+
if ets == "full":
|
|
2427
|
+
estr = link.ets.__str__(q=f"q{link.jindex}")
|
|
2428
|
+
elif ets == "brief":
|
|
2429
|
+
if link.jindex is None:
|
|
2430
|
+
estr = ""
|
|
2431
|
+
else:
|
|
2432
|
+
estr = f"...q{link.jindex}"
|
|
2433
|
+
else:
|
|
2434
|
+
return
|
|
2435
|
+
try:
|
|
2436
|
+
file.write(
|
|
2437
|
+
' {} -> {} [label="{}", {}];\n'.format(
|
|
2438
|
+
parent,
|
|
2439
|
+
link.name,
|
|
2440
|
+
estr,
|
|
2441
|
+
edge_options,
|
|
2442
|
+
)
|
|
2443
|
+
)
|
|
2444
|
+
except UnicodeEncodeError: # pragma nocover
|
|
2445
|
+
file.write(
|
|
2446
|
+
' {} -> {} [label="{}", {}];\n'.format(
|
|
2447
|
+
parent,
|
|
2448
|
+
link.name,
|
|
2449
|
+
estr.encode("ascii", "ignore").decode("ascii"),
|
|
2450
|
+
edge_options,
|
|
2451
|
+
)
|
|
2452
|
+
)
|
|
2453
|
+
|
|
2454
|
+
file.write(header)
|
|
2455
|
+
|
|
2456
|
+
# add the base link
|
|
2457
|
+
file.write(" BASE [shape=square, style=filled, fillcolor=gray]\n")
|
|
2458
|
+
|
|
2459
|
+
# add the links
|
|
2460
|
+
for link in self:
|
|
2461
|
+
# draw the link frame node (circle) or ee node (doublecircle)
|
|
2462
|
+
if link in self.ee_links:
|
|
2463
|
+
# end-effector
|
|
2464
|
+
node_options = 'shape="doublecircle", color="blue", fontcolor="blue"'
|
|
2465
|
+
else:
|
|
2466
|
+
node_options = 'shape="circle"'
|
|
2467
|
+
|
|
2468
|
+
file.write(" {} [{}];\n".format(link.name, node_options))
|
|
2469
|
+
|
|
2470
|
+
draw_edge(link, etsbox, jtype, static)
|
|
2471
|
+
|
|
2472
|
+
for gripper in self.grippers:
|
|
2473
|
+
for link in gripper.links:
|
|
2474
|
+
file.write(" {} [shape=cds];\n".format(link.name))
|
|
2475
|
+
draw_edge(link, etsbox, jtype, static)
|
|
2476
|
+
|
|
2477
|
+
file.write("}\n")
|
|
2478
|
+
|
|
2479
|
+
if isinstance(filename, str):
|
|
2480
|
+
file.close()
|
|
2481
|
+
|
|
2482
|
+
def graph(
|
|
2483
|
+
self,
|
|
2484
|
+
format: L["dot", "mermaid", "mermaid_fenced"] = "dot",
|
|
2485
|
+
filename: str | IO[str] | None = None,
|
|
2486
|
+
**kwargs,
|
|
2487
|
+
) -> str:
|
|
2488
|
+
"""
|
|
2489
|
+
Render a link transform graph as text
|
|
2490
|
+
|
|
2491
|
+
:param format: graph output format: ``"dot"``, ``"mermaid"`` or
|
|
2492
|
+
``"mermaid_fenced"``, defaults to ``"dot"``
|
|
2493
|
+
:param filename: destination path or open text stream, defaults to
|
|
2494
|
+
None
|
|
2495
|
+
:param kwargs: for ``format="dot"``, forwarded to :meth:`dotfile`
|
|
2496
|
+
(``etsbox``, ``ets``, ``jtype``, ``static``); ignored otherwise
|
|
2497
|
+
:return: rendered graph text
|
|
2498
|
+
|
|
2499
|
+
Creates graph text representing the robot's link transform graph --
|
|
2500
|
+
the same structure :meth:`dotfile`/:meth:`showgraph` render, in a
|
|
2501
|
+
choice of formats. If ``filename`` is provided, the rendered text
|
|
2502
|
+
is also written to it.
|
|
2503
|
+
|
|
2504
|
+
``"dot"`` produces the same GraphViz output as :meth:`dotfile`,
|
|
2505
|
+
just returned as a string rather than requiring a real file --
|
|
2506
|
+
``etsbox``/``ets``/``jtype``/``static`` behave exactly as they do
|
|
2507
|
+
there.
|
|
2508
|
+
|
|
2509
|
+
``"mermaid"``/``"mermaid_fenced"`` render a simplified `Mermaid
|
|
2510
|
+
<https://mermaid.js.org>`_ flowchart: one node per link (a double
|
|
2511
|
+
circle for end-effectors, a hexagon for gripper links), edges
|
|
2512
|
+
labelled with the joint type (``R``/``P``) and joint index for
|
|
2513
|
+
actuated joints. Mermaid has no equivalent of DOT's per-edge
|
|
2514
|
+
arrowhead/colour styling, so ``jtype``/``static``/``etsbox`` only
|
|
2515
|
+
affect ``"dot"`` output. ``"mermaid_fenced"`` wraps the same text
|
|
2516
|
+
in a fenced ``mermaid`` code block, ready to paste into Markdown
|
|
2517
|
+
or render directly in a Jupyter cell::
|
|
2518
|
+
|
|
2519
|
+
from IPython.display import Markdown, display
|
|
2520
|
+
display(Markdown(robot.graph(format="mermaid_fenced")))
|
|
2521
|
+
|
|
2522
|
+
:seealso: :meth:`dotfile` :meth:`showgraph`
|
|
2523
|
+
"""
|
|
2524
|
+
graph_format = format.lower().replace("-", "_")
|
|
2525
|
+
|
|
2526
|
+
if graph_format == "dot":
|
|
2527
|
+
stream = io.StringIO()
|
|
2528
|
+
self.dotfile(stream, **kwargs)
|
|
2529
|
+
text = stream.getvalue()
|
|
2530
|
+
|
|
2531
|
+
elif graph_format in ("mermaid", "mermaid_fenced"):
|
|
2532
|
+
lines = ["flowchart LR"]
|
|
2533
|
+
node_ids: dict[Any, str] = {}
|
|
2534
|
+
|
|
2535
|
+
def node_id(link) -> str:
|
|
2536
|
+
return node_ids.setdefault(link, f"n{len(node_ids)}")
|
|
2537
|
+
|
|
2538
|
+
lines.append(' BASE["BASE"]')
|
|
2539
|
+
|
|
2540
|
+
for link in self:
|
|
2541
|
+
nid = node_id(link)
|
|
2542
|
+
if link in self.ee_links:
|
|
2543
|
+
lines.append(f' {nid}(("{link.name}"))')
|
|
2544
|
+
else:
|
|
2545
|
+
lines.append(f' {nid}["{link.name}"]')
|
|
2546
|
+
|
|
2547
|
+
for gripper in self.grippers:
|
|
2548
|
+
for link in gripper.links:
|
|
2549
|
+
lines.append(f' {node_id(link)}{{{{"{link.name}"}}}}')
|
|
2550
|
+
|
|
2551
|
+
def edge_label(link) -> str:
|
|
2552
|
+
if not link.isjoint:
|
|
2553
|
+
return ""
|
|
2554
|
+
kind = "P" if link.isprismatic else "R"
|
|
2555
|
+
return f"|{kind}{link.jindex}|"
|
|
2556
|
+
|
|
2557
|
+
for link in self:
|
|
2558
|
+
parent = "BASE" if link.parent is None else node_id(link.parent)
|
|
2559
|
+
lines.append(f" {parent} -->{edge_label(link)} {node_id(link)}")
|
|
2560
|
+
|
|
2561
|
+
for gripper in self.grippers:
|
|
2562
|
+
for link in gripper.links:
|
|
2563
|
+
parent = (
|
|
2564
|
+
"BASE" if link.parent is None else node_id(link.parent)
|
|
2565
|
+
)
|
|
2566
|
+
lines.append(
|
|
2567
|
+
f" {parent} -->{edge_label(link)} {node_id(link)}"
|
|
2568
|
+
)
|
|
2569
|
+
|
|
2570
|
+
mermaid_text = "\n".join(lines) + "\n"
|
|
2571
|
+
if graph_format == "mermaid_fenced":
|
|
2572
|
+
text = f"```mermaid\n{mermaid_text}```\n"
|
|
2573
|
+
else:
|
|
2574
|
+
text = mermaid_text
|
|
2575
|
+
|
|
2576
|
+
else:
|
|
2577
|
+
raise ValueError(
|
|
2578
|
+
f"unsupported graph format {format!r}, expected 'dot', "
|
|
2579
|
+
"'mermaid' or 'mermaid_fenced'"
|
|
2580
|
+
)
|
|
2581
|
+
|
|
2582
|
+
if filename is not None:
|
|
2583
|
+
if isinstance(filename, str):
|
|
2584
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
2585
|
+
f.write(text)
|
|
2586
|
+
else:
|
|
2587
|
+
filename.write(text)
|
|
2588
|
+
|
|
2589
|
+
return text
|