{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Credit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This example is a (slightly modified) contribution by [Aykut Satici](https://github.com/symplectomorphism)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Setup"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll be using `RigidBodyDynamics` and `StaticArrays` for the simulation part."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"using RigidBodyDynamics\n",
"using StaticArrays"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Model definition"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We're going to create a [four-bar linkage](https://en.wikipedia.org/wiki/Four-bar_linkage) that looks like this:\n",
"
\n",
"\n",
"We'll 'cut' the mechanism at joint 4: joints 1, 2, and 3 will be part of the spanning tree of the mechanism, but joint 4 will be a 'loop joint' (see e.g. Featherstone's 'Rigid Body Dynamics Algorithms'), for which the dynamics will be enforced using Lagrange multipliers."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we'll define some relevant constants:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# gravitational acceleration\n",
"g = -9.81\n",
"\n",
"# link lengths\n",
"l_0 = 1.10\n",
"l_1 = 0.5\n",
"l_2 = 1.20\n",
"l_3 = 0.75\n",
"\n",
"# link masses\n",
"m_1 = 0.5\n",
"m_2 = 1.0\n",
"m_3 = 0.75\n",
"\n",
"# link center of mass offsets from the preceding joint axes\n",
"c_1 = 0.25\n",
"c_2 = 0.60\n",
"c_3 = 0.375\n",
"\n",
"# moments of inertia about the center of mass of each link\n",
"I_1 = 0.333\n",
"I_2 = 0.537\n",
"I_3 = 0.4\n",
"\n",
"# scalar type\n",
"T = Float64;\n",
"\n",
"# Rotation axis: negative y-axis\n",
"axis = SVector(zero(T), -one(T), zero(T))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Construct the world rigid body and create a new mechanism."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Spanning tree:\n",
"Vertex: world (root)\n",
"No non-tree joints."
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"world = RigidBody{T}(\"world\")\n",
"fourbar = Mechanism(world; gravity = SVector(0., 0., g))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we'll construct the spanning tree of the mechanism, consisting of bodies 1, 2, and 3 connected by joints 1, 2, and 3."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Spanning tree:\n",
"Vertex: world (root)\n",
" Vertex: inertia1_centroidal, Edge: joint1\n",
" Vertex: inertia2_centroidal, Edge: joint2\n",
" Vertex: inertia3_centroidal, Edge: joint3\n",
"No non-tree joints."
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# link1 and joint1\n",
"joint1 = Joint(\"joint1\", Revolute(axis))\n",
"inertia1 = SpatialInertia(CartesianFrame3D(\"inertia1_centroidal\"), I_1*axis*axis', zeros(SVector{3, T}), m_1)\n",
"link1 = RigidBody(inertia1)\n",
"beforeJoint1ToWorld = eye(Transform3D, frame_before(joint1), default_frame(world))\n",
"c1_to_joint = Transform3D(inertia1.frame, frame_after(joint1), SVector(c_1, 0, 0))\n",
"attach!(fourbar, world, joint1, beforeJoint1ToWorld, link1, c1_to_joint)\n",
"\n",
"# link2 and joint2\n",
"joint2 = Joint(\"joint2\", Revolute(axis))\n",
"inertia2 = SpatialInertia(CartesianFrame3D(\"inertia2_centroidal\"), I_2*axis*axis', zeros(SVector{3, T}), m_2)\n",
"link2 = RigidBody(inertia2)\n",
"beforeJoint2ToAfterJoint1 = Transform3D(frame_before(joint2), frame_after(joint1), SVector(l_1, 0., 0.))\n",
"c2_to_joint = Transform3D(inertia2.frame, frame_after(joint2), SVector(c_2, 0, 0))\n",
"attach!(fourbar, link1, joint2, beforeJoint2ToAfterJoint1, link2, c2_to_joint)\n",
"\n",
"# link3 and joint3\n",
"joint3 = Joint(\"joint3\", Revolute(axis))\n",
"inertia3 = SpatialInertia(CartesianFrame3D(\"inertia3_centroidal\"), I_3*axis*axis', zeros(SVector{3, T}), m_3)\n",
"link3 = RigidBody(inertia3)\n",
"beforeJoint3ToWorld = Transform3D(frame_before(joint3), default_frame(world), SVector(l_0, 0., 0.))\n",
"c3_to_joint = Transform3D(inertia3.frame, frame_after(joint3), SVector(c_3, 0, 0))\n",
"attach!(fourbar, world, joint3, beforeJoint3ToWorld, link3, c3_to_joint)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we'll add joint 4 in almost the same way we did the other joints, with the following exceptions:\n",
"1. both `link2` and `link3` are already part of the `Mechanism`, so the `attach!` function will figure out that `joint4` will be a loop joint.\n",
"2. instead of using the default (identity) for the argument that specifies the transform from the successor of joint 4 (i.e., link 3) to the frame directly after joint 4, we'll specify a transform that incorporates the $l_3$ offset."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"Spanning tree:\n",
"Vertex: world (root)\n",
" Vertex: inertia1_centroidal, Edge: joint1\n",
" Vertex: inertia2_centroidal, Edge: joint2\n",
" Vertex: inertia3_centroidal, Edge: joint3\n",
"Non-tree joints:\n",
"joint4, predecessor: inertia2_centroidal, successor: inertia3_centroidal"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# joint between link2 and link3\n",
"joint4 = Joint(\"joint4\", Revolute(axis))\n",
"beforeJoint4ToJoint2 = Transform3D(frame_before(joint4), frame_after(joint2), SVector(l_2, 0., 0.))\n",
"joint3ToAfterJoint4 = Transform3D(frame_after(joint3), frame_after(joint4), SVector(-l_3, 0., 0.))\n",
"attach!(fourbar, link2, joint4, beforeJoint4ToJoint2, link3, joint3ToAfterJoint4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the additional non-tree joint in the printed `Mechanism` summary."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simulation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As usual, we'll now construct a `MechanismState` and `DynamicsResult` for the four-bar `Mechanism`. We'll set some initial conditions for a simulation, which were solved for a priori using a nonlinear program (not shown here)."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"state = MechanismState(T, fourbar)\n",
"result = DynamicsResult{T, T}(fourbar);"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"configuration(state, joint1)[:] = 1.6707963267948966 # θ\n",
"configuration(state, joint2)[:] = -1.4591054166649482 # γ\n",
"configuration(state, joint3)[:] = 1.5397303602625536 # ϕ\n",
"\n",
"velocity(state, joint1)[:] = 0.5\n",
"velocity(state, joint2)[:] = -0.47295\n",
"velocity(state, joint3)[:] = 0.341\n",
"\n",
"# Invalidate the cache variables\n",
"setdirty!(state)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we'll do a 3-second simulation:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"ts, qs, vs = simulate(state, 3., Δt = 1e-2);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Visualization"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For visualization, we'll use [`RigidBodyTreeInspector`](https://github.com/rdeits/RigidBodyTreeInspector.jl).\n",
"\n",
"(Note: the `#NBSKIP` comments are to skip these cells during `Pkg.test()`)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false,
"scrolled": true
},
"outputs": [
{
"data": {
"text/html": [
"