Simulation & Cartesian position controller
This commit is contained in:
parent
bb6cc1a42c
commit
c28929dec1
3
.gitignore
vendored
3
.gitignore
vendored
@ -127,3 +127,6 @@ dmypy.json
|
|||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
19
controllers.py
Normal file
19
controllers.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class CartesianPoseController:
|
||||||
|
def __init__(self, model):
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
def set_target(self, pose):
|
||||||
|
self.target = pose.translation
|
||||||
|
|
||||||
|
def update(self, q, dq):
|
||||||
|
t = self.model.pose().translation
|
||||||
|
J = self.model.jacobian(q)
|
||||||
|
J_pinv = np.linalg.pinv(J)
|
||||||
|
|
||||||
|
err = 2.0 * (self.target - t)
|
||||||
|
cmd = np.dot(J_pinv, err)
|
||||||
|
|
||||||
|
return cmd
|
9
launch/panda_visualization.launch
Normal file
9
launch/panda_visualization.launch
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<launch>
|
||||||
|
<param name="robot_description" command="$(find xacro)/xacro --inorder $(find franka_description)/robots/panda_arm.urdf.xacro" />
|
||||||
|
|
||||||
|
<node pkg="tf2_ros" type="static_transform_publisher" name="T_world_robot" args="0.03226862 -0.06137175 0.04107702 0.00099995 0. 0.39898185 0.91695828 panda_link8 camera_depth_optical_frame" />
|
||||||
|
|
||||||
|
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />
|
||||||
|
<node pkg="rviz" type="rviz" output="screen" name="rviz" args="-d /home/michel/catkin_ws/src/active_grasp/launch/panda_visualization.rviz" />
|
||||||
|
</launch>
|
189
launch/panda_visualization.rviz
Normal file
189
launch/panda_visualization.rviz
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
Panels:
|
||||||
|
- Class: rviz/Displays
|
||||||
|
Help Height: 78
|
||||||
|
Name: Displays
|
||||||
|
Property Tree Widget:
|
||||||
|
Expanded:
|
||||||
|
- /Global Options1
|
||||||
|
Splitter Ratio: 0.5
|
||||||
|
Tree Height: 549
|
||||||
|
- Class: rviz/Selection
|
||||||
|
Name: Selection
|
||||||
|
- Class: rviz/Tool Properties
|
||||||
|
Expanded:
|
||||||
|
- /2D Pose Estimate1
|
||||||
|
- /2D Nav Goal1
|
||||||
|
- /Publish Point1
|
||||||
|
Name: Tool Properties
|
||||||
|
Splitter Ratio: 0.5886790156364441
|
||||||
|
- Class: rviz/Views
|
||||||
|
Expanded:
|
||||||
|
- /Current View1
|
||||||
|
Name: Views
|
||||||
|
Splitter Ratio: 0.5
|
||||||
|
- Class: rviz/Time
|
||||||
|
Experimental: false
|
||||||
|
Name: Time
|
||||||
|
SyncMode: 0
|
||||||
|
SyncSource: ""
|
||||||
|
Preferences:
|
||||||
|
PromptSaveOnExit: true
|
||||||
|
Toolbars:
|
||||||
|
toolButtonStyle: 2
|
||||||
|
Visualization Manager:
|
||||||
|
Class: ""
|
||||||
|
Displays:
|
||||||
|
- Alpha: 0.5
|
||||||
|
Cell Size: 1
|
||||||
|
Class: rviz/Grid
|
||||||
|
Color: 160; 160; 164
|
||||||
|
Enabled: true
|
||||||
|
Line Style:
|
||||||
|
Line Width: 0.029999999329447746
|
||||||
|
Value: Lines
|
||||||
|
Name: Grid
|
||||||
|
Normal Cell Count: 0
|
||||||
|
Offset:
|
||||||
|
X: 0
|
||||||
|
Y: 0
|
||||||
|
Z: 0
|
||||||
|
Plane: XY
|
||||||
|
Plane Cell Count: 10
|
||||||
|
Reference Frame: <Fixed Frame>
|
||||||
|
Value: true
|
||||||
|
- Alpha: 1
|
||||||
|
Class: rviz/RobotModel
|
||||||
|
Collision Enabled: false
|
||||||
|
Enabled: true
|
||||||
|
Links:
|
||||||
|
All Links Enabled: true
|
||||||
|
Expand Joint Details: false
|
||||||
|
Expand Link Details: false
|
||||||
|
Expand Tree: false
|
||||||
|
Link Tree Style: Links in Alphabetic Order
|
||||||
|
panda_link0:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link1:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link2:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link3:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link4:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link5:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link6:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link7:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
panda_link8:
|
||||||
|
Alpha: 1
|
||||||
|
Show Axes: false
|
||||||
|
Show Trail: false
|
||||||
|
Value: true
|
||||||
|
Name: RobotModel
|
||||||
|
Robot Description: robot_description
|
||||||
|
TF Prefix: ""
|
||||||
|
Update Interval: 0
|
||||||
|
Value: true
|
||||||
|
Visual Enabled: true
|
||||||
|
- Class: rviz/InteractiveMarkers
|
||||||
|
Enable Transparency: true
|
||||||
|
Enabled: true
|
||||||
|
Name: Target
|
||||||
|
Show Axes: false
|
||||||
|
Show Descriptions: true
|
||||||
|
Show Visual Aids: false
|
||||||
|
Update Topic: /target/update
|
||||||
|
Value: true
|
||||||
|
Enabled: true
|
||||||
|
Global Options:
|
||||||
|
Background Color: 48; 48; 48
|
||||||
|
Default Light: true
|
||||||
|
Fixed Frame: panda_link0
|
||||||
|
Frame Rate: 30
|
||||||
|
Name: root
|
||||||
|
Tools:
|
||||||
|
- Class: rviz/Interact
|
||||||
|
Hide Inactive Objects: true
|
||||||
|
- Class: rviz/MoveCamera
|
||||||
|
- Class: rviz/Select
|
||||||
|
- Class: rviz/FocusCamera
|
||||||
|
- Class: rviz/Measure
|
||||||
|
- Class: rviz/SetInitialPose
|
||||||
|
Theta std deviation: 0.2617993950843811
|
||||||
|
Topic: /initialpose
|
||||||
|
X std deviation: 0.5
|
||||||
|
Y std deviation: 0.5
|
||||||
|
- Class: rviz/SetGoal
|
||||||
|
Topic: /move_base_simple/goal
|
||||||
|
- Class: rviz/PublishPoint
|
||||||
|
Single click: true
|
||||||
|
Topic: /clicked_point
|
||||||
|
Value: true
|
||||||
|
Views:
|
||||||
|
Current:
|
||||||
|
Class: rviz/Orbit
|
||||||
|
Distance: 1.841537594795227
|
||||||
|
Enable Stereo Rendering:
|
||||||
|
Stereo Eye Separation: 0.05999999865889549
|
||||||
|
Stereo Focal Distance: 1
|
||||||
|
Swap Stereo Eyes: false
|
||||||
|
Value: false
|
||||||
|
Field of View: 0.7853981852531433
|
||||||
|
Focal Point:
|
||||||
|
X: 0.06870806962251663
|
||||||
|
Y: 0.16815021634101868
|
||||||
|
Z: 0.3779522478580475
|
||||||
|
Focal Shape Fixed Size: true
|
||||||
|
Focal Shape Size: 0.05000000074505806
|
||||||
|
Invert Z Axis: false
|
||||||
|
Name: Current View
|
||||||
|
Near Clip Distance: 0.009999999776482582
|
||||||
|
Pitch: 0.29539811611175537
|
||||||
|
Target Frame: <Fixed Frame>
|
||||||
|
Yaw: 5.128592014312744
|
||||||
|
Saved: ~
|
||||||
|
Window Geometry:
|
||||||
|
Displays:
|
||||||
|
collapsed: false
|
||||||
|
Height: 846
|
||||||
|
Hide Left Dock: false
|
||||||
|
Hide Right Dock: true
|
||||||
|
QMainWindow State: 000000ff00000000fd000000040000000000000156000002b0fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002b0000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d000002b0000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004b00000003efc0100000002fb0000000800540069006d00650100000000000004b0000002eb00fffffffb0000000800540069006d0065010000000000000450000000000000000000000354000002b000000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
|
||||||
|
Selection:
|
||||||
|
collapsed: false
|
||||||
|
Time:
|
||||||
|
collapsed: false
|
||||||
|
Tool Properties:
|
||||||
|
collapsed: false
|
||||||
|
Views:
|
||||||
|
collapsed: true
|
||||||
|
Width: 1200
|
||||||
|
X: 646
|
||||||
|
Y: 193
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
numpy
|
||||||
|
scipy
|
||||||
|
pybullet
|
27
run_demo.py
Normal file
27
run_demo.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import rospy
|
||||||
|
|
||||||
|
from controllers import *
|
||||||
|
from simulation import *
|
||||||
|
from utils import *
|
||||||
|
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
gui = True
|
||||||
|
dt = 1.0 / 60.0
|
||||||
|
|
||||||
|
rospy.init_node("demo")
|
||||||
|
|
||||||
|
env = SimPandaEnv(gui)
|
||||||
|
model = env.arm
|
||||||
|
controller = CartesianPoseController(model)
|
||||||
|
|
||||||
|
init_ee_pose = env.arm.pose()
|
||||||
|
marker = InteractiveMarkerWrapper("target", "panda_link0", init_ee_pose)
|
||||||
|
|
||||||
|
# run the control loop
|
||||||
|
while True:
|
||||||
|
controller.set_target(marker.get_pose())
|
||||||
|
q, dq = env.arm.get_state()
|
||||||
|
cmd = controller.update(q, dq)
|
||||||
|
env.arm.set_desired_joint_velocities(cmd)
|
||||||
|
env.forward(dt)
|
127
simulation.py
Normal file
127
simulation.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pybullet as p
|
||||||
|
import pybullet_data
|
||||||
|
|
||||||
|
from sensor_msgs.msg import JointState
|
||||||
|
|
||||||
|
from utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class PandaArm(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.num_dof = 7
|
||||||
|
self.ee_link_id = 7
|
||||||
|
self.ee_frame_id = "panda_link7"
|
||||||
|
|
||||||
|
self.named_joint_values = {"ready": [0.0, -0.79, 0.0, -2.356, 0.0, 1.57, 0.79]}
|
||||||
|
|
||||||
|
self.upper_limits = [-7] * self.num_dof
|
||||||
|
self.lower_limits = [7] * self.num_dof
|
||||||
|
self.ranges = [7] * self.num_dof
|
||||||
|
|
||||||
|
def load(self, pose):
|
||||||
|
self.T_world_base = pose
|
||||||
|
self.uid = p.loadURDF(
|
||||||
|
"assets/urdfs/panda/panda_arm_hand.urdf",
|
||||||
|
basePosition=pose.translation,
|
||||||
|
baseOrientation=pose.rotation.as_quat(),
|
||||||
|
useFixedBase=True,
|
||||||
|
)
|
||||||
|
for i in range(self.num_dof):
|
||||||
|
p.resetJointState(self.uid, i, self.named_joint_values["ready"][i])
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
joint_states = p.getJointStates(self.uid, range(p.getNumJoints(self.uid)))[:7]
|
||||||
|
positions = [state[0] for state in joint_states]
|
||||||
|
velocities = [state[1] for state in joint_states]
|
||||||
|
return positions, velocities
|
||||||
|
|
||||||
|
def set_desired_joint_positions(self, positions):
|
||||||
|
for i, position in enumerate(positions):
|
||||||
|
p.setJointMotorControl2(self.uid, i, p.POSITION_CONTROL, position)
|
||||||
|
|
||||||
|
def set_desired_joint_velocities(self, velocities):
|
||||||
|
for i, velocity in enumerate(velocities):
|
||||||
|
p.setJointMotorControl2(
|
||||||
|
self.uid, i, p.VELOCITY_CONTROL, targetVelocity=velocity
|
||||||
|
)
|
||||||
|
|
||||||
|
def pose(self):
|
||||||
|
result = p.getLinkState(self.uid, self.ee_link_id, computeForwardKinematics=1)
|
||||||
|
_, _, _, _, frame_pos, frame_quat = result
|
||||||
|
T_world_ee = Transform(Rotation.from_quat(frame_quat), np.array(frame_pos))
|
||||||
|
return self.T_world_base.inverse() * T_world_ee
|
||||||
|
|
||||||
|
def jacobian(self, q):
|
||||||
|
q = np.r_[q, 0.0, 0.0].tolist()
|
||||||
|
q_dot, q_ddot = np.zeros(9).tolist(), np.zeros(9).tolist()
|
||||||
|
linear, _ = p.calculateJacobian(self.uid, 7, [0.0, 0.0, 0.0], q, q_dot, q_ddot)
|
||||||
|
return np.asarray(linear)[:, :7]
|
||||||
|
|
||||||
|
|
||||||
|
class PandaGripper(object):
|
||||||
|
def move(self, width):
|
||||||
|
for i in [9, 10]:
|
||||||
|
p.setJointMotorControl2(
|
||||||
|
self.uid,
|
||||||
|
i,
|
||||||
|
p.POSITION_CONTROL,
|
||||||
|
0.5 * width,
|
||||||
|
force=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return p.getJointState(self.uid, 9)[0] + p.getJointState(self.uid, 10)[0]
|
||||||
|
|
||||||
|
|
||||||
|
class Camera(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimPandaEnv(object):
|
||||||
|
def __init__(self, gui, dt=1.0 / 240.0, publish_state=True):
|
||||||
|
self.gui = gui
|
||||||
|
self.dt = dt
|
||||||
|
p.connect(p.GUI if gui else p.DIRECT)
|
||||||
|
p.setAdditionalSearchPath(pybullet_data.getDataPath())
|
||||||
|
p.setTimeStep(dt)
|
||||||
|
p.setGravity(0.0, 0.0, -9.8)
|
||||||
|
|
||||||
|
p.loadURDF("plane.urdf")
|
||||||
|
p.loadURDF(
|
||||||
|
"table/table.urdf",
|
||||||
|
baseOrientation=Rotation.from_rotvec(np.array([0, 0, np.pi / 2])).as_quat(),
|
||||||
|
useFixedBase=True,
|
||||||
|
)
|
||||||
|
p.loadURDF("cube_small.urdf", [0.0, 0.0, 0.8])
|
||||||
|
|
||||||
|
self.arm = PandaArm()
|
||||||
|
self.arm.load(Transform(Rotation.identity(), np.r_[-0.8, 0.0, 0.4]))
|
||||||
|
self.gripper = PandaGripper()
|
||||||
|
self.gripper.uid = self.arm.uid
|
||||||
|
|
||||||
|
self.camera = None
|
||||||
|
|
||||||
|
if publish_state:
|
||||||
|
self.state_pub = rospy.Publisher("/joint_states", JointState, queue_size=10)
|
||||||
|
rospy.Timer(rospy.Duration(1.0 / 30), self._publish_state)
|
||||||
|
|
||||||
|
def forward(self, dt):
|
||||||
|
steps = int(dt / self.dt)
|
||||||
|
for _ in range(steps):
|
||||||
|
self.step()
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
p.stepSimulation()
|
||||||
|
time.sleep(self.dt)
|
||||||
|
|
||||||
|
def _publish_state(self, event):
|
||||||
|
positions, velocities = self.arm.get_state()
|
||||||
|
msg = JointState()
|
||||||
|
msg.header.stamp = rospy.Time.now()
|
||||||
|
msg.name = ["panda_joint{}".format(i) for i in range(1, 8)]
|
||||||
|
msg.position = positions
|
||||||
|
msg.velocity = velocities
|
||||||
|
self.state_pub.publish(msg)
|
111
utils.py
Normal file
111
utils.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import numpy as np
|
||||||
|
from scipy.spatial.transform import Rotation
|
||||||
|
|
||||||
|
from interactive_markers.interactive_marker_server import *
|
||||||
|
from interactive_markers.menu_handler import *
|
||||||
|
from visualization_msgs.msg import *
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveMarkerWrapper(object):
|
||||||
|
def __init__(self, name, frame_id, pose):
|
||||||
|
server = InteractiveMarkerServer(topic_ns=name)
|
||||||
|
|
||||||
|
target_marker = InteractiveMarker()
|
||||||
|
target_marker.header.frame_id = frame_id
|
||||||
|
target_marker.name = name
|
||||||
|
target_marker.scale = 0.2
|
||||||
|
target_marker.pose.position.x = pose.translation[0]
|
||||||
|
target_marker.pose.position.y = pose.translation[1]
|
||||||
|
target_marker.pose.position.z = pose.translation[2]
|
||||||
|
|
||||||
|
marker = Marker()
|
||||||
|
marker.type = Marker.SPHERE
|
||||||
|
marker.scale.x = 0.05
|
||||||
|
marker.scale.y = 0.05
|
||||||
|
marker.scale.z = 0.05
|
||||||
|
marker.color.r = 0.0
|
||||||
|
marker.color.g = 0.5
|
||||||
|
marker.color.b = 0.5
|
||||||
|
marker.color.a = 0.6
|
||||||
|
ctrl = InteractiveMarkerControl()
|
||||||
|
ctrl.always_visible = True
|
||||||
|
ctrl.markers.append(marker)
|
||||||
|
target_marker.controls.append(ctrl)
|
||||||
|
|
||||||
|
ctrl = InteractiveMarkerControl()
|
||||||
|
ctrl.name = "move_x"
|
||||||
|
ctrl.orientation.w = 1.0
|
||||||
|
ctrl.orientation.x = 1.0
|
||||||
|
ctrl.interaction_mode = InteractiveMarkerControl.MOVE_AXIS
|
||||||
|
target_marker.controls.append(ctrl)
|
||||||
|
|
||||||
|
ctrl = InteractiveMarkerControl()
|
||||||
|
ctrl.name = "move_y"
|
||||||
|
ctrl.orientation.w = 1.0
|
||||||
|
ctrl.orientation.y = 1.0
|
||||||
|
ctrl.interaction_mode = InteractiveMarkerControl.MOVE_AXIS
|
||||||
|
target_marker.controls.append(ctrl)
|
||||||
|
|
||||||
|
ctrl = InteractiveMarkerControl()
|
||||||
|
ctrl.name = "move_z"
|
||||||
|
ctrl.orientation.w = 1.0
|
||||||
|
ctrl.orientation.z = 1.0
|
||||||
|
ctrl.interaction_mode = InteractiveMarkerControl.MOVE_AXIS
|
||||||
|
target_marker.controls.append(ctrl)
|
||||||
|
|
||||||
|
server.insert(target_marker, self.cb)
|
||||||
|
server.applyChanges()
|
||||||
|
|
||||||
|
self.pose = pose
|
||||||
|
|
||||||
|
def cb(self, feedback):
|
||||||
|
pos = feedback.pose.position
|
||||||
|
self.pose = Transform(Rotation.identity(), np.r_[pos.x, pos.y, pos.z])
|
||||||
|
|
||||||
|
def get_pose(self):
|
||||||
|
return self.pose
|
||||||
|
|
||||||
|
|
||||||
|
class Transform(object):
|
||||||
|
def __init__(self, rotation, translation):
|
||||||
|
assert isinstance(rotation, Rotation)
|
||||||
|
assert isinstance(translation, (np.ndarray, list))
|
||||||
|
|
||||||
|
self.rotation = rotation
|
||||||
|
self.translation = np.asarray(translation, np.double)
|
||||||
|
|
||||||
|
def as_matrix(self):
|
||||||
|
return np.vstack(
|
||||||
|
(np.c_[self.rotation.as_matrix(), self.translation], [0.0, 0.0, 0.0, 1.0])
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_list(self):
|
||||||
|
return np.r_[self.rotation.as_quat(), self.translation]
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
rotation = self.rotation * other.rotation
|
||||||
|
translation = self.rotation.apply(other.translation) + self.translation
|
||||||
|
return self.__class__(rotation, translation)
|
||||||
|
|
||||||
|
def inverse(self):
|
||||||
|
rotation = self.rotation.inv()
|
||||||
|
translation = -rotation.apply(self.translation)
|
||||||
|
return self.__class__(rotation, translation)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def identity(cls):
|
||||||
|
rotation = Rotation.from_quat([0.0, 0.0, 0.0, 1.0])
|
||||||
|
translation = np.array([0.0, 0.0, 0.0])
|
||||||
|
return cls(rotation, translation)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_matrix(cls, m):
|
||||||
|
rotation = Rotation.from_matrix(m[:3, :3])
|
||||||
|
translation = m[:3, 3]
|
||||||
|
return cls(rotation, translation)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, list):
|
||||||
|
rotation = Rotation.from_quat(list[:4])
|
||||||
|
translation = list[4:]
|
||||||
|
return cls(rotation, translation)
|
Loading…
x
Reference in New Issue
Block a user