Skip to content

Commit 5db5587

Browse files
committed
add swing tutorial
1 parent 58bc962 commit 5db5587

File tree

3 files changed

+168
-1
lines changed

3 files changed

+168
-1
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ makedocs(;
3131
"Kinematic loops" => "examples/kinematic_loops.md",
3232
"Industrial robot" => "examples/robot.md",
3333
"Ropes, cables and chains" => "examples/ropes_and_cables.md",
34+
"Swing" => "examples/swing.md",
3435
"Bodies in space" => "examples/space.md",
3536
],
3637
"3D rendering" => "rendering.md",

docs/src/examples/swing.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Swing
2+
3+
In this example, we will model a swing consisting of a rigid seat suspended in 4 ropes, mounted symmetrically in a ceiling. Each rope is modeled as a stiff rod with a small point mass at the center of gravity, terminated by a parallel spring-damper to model slight flexibility in the ropes. The ceiling mounting points are modeled as spherical joints, i.e., they do not transmit any torque in any direction. The rim of the seat is modeled as 4 rigid bodies configured in a square, as well as one point mass representing the load, located slightly below the rim assembly.
4+
5+
![animation](swing.gif)
6+
7+
8+
We start by defining a single rope component and attach it to a body in order to verify that it's working.
9+
10+
```@example SWING
11+
using Multibody
12+
using ModelingToolkit
13+
using Plots
14+
using JuliaSimCompiler
15+
using OrdinaryDiffEq
16+
17+
t = Multibody.t
18+
D = Differential(t)
19+
world = Multibody.world
20+
21+
W(args...; kwargs...) = Multibody.world
22+
23+
@mtkmodel SwingRope begin
24+
@components begin
25+
frame_a = Frame()
26+
frame_b = Frame()
27+
joint1 = Spherical(isroot=true, state=true, d=0.001)
28+
rope = BodyShape(r=[0.0,-1,0], m=0.05, radius=0.01)
29+
spring = Spring(c = inv(0.04/60), m=0.01)
30+
damper = Damper(d = 50.0)
31+
end
32+
@equations begin
33+
connect(frame_a, joint1.frame_a)
34+
connect(joint1.frame_b, rope.frame_a)
35+
36+
connect(rope.frame_b, spring.frame_a, damper.frame_a)
37+
connect(spring.frame_b, damper.frame_b, frame_b)
38+
end
39+
end
40+
41+
@mtkmodel SimpleSwing begin
42+
@structural_parameters begin
43+
h = 2
44+
w = 0.4
45+
end
46+
@components begin
47+
world = W()
48+
upper_trans1 = FixedTranslation(r=[-w/2, 0, 0])
49+
rope1 = SwingRope(rope.r=[-w/2, h, -w/2])
50+
body = Body(m=6, isroot=true, I_11=0.1, I_22=0.1, I_33=0.1)
51+
damper = Damper(d=10.0)
52+
end
53+
@equations begin
54+
connect(world.frame_b, upper_trans1.frame_a)
55+
connect(rope1.frame_a, upper_trans1.frame_b)
56+
# connect(world.frame_b, rope1.frame_a)
57+
connect(rope1.frame_b, body.frame_a)
58+
59+
connect(world.frame_b, damper.frame_a)
60+
connect(body.frame_a, damper.frame_b)
61+
end
62+
end
63+
@named model = SimpleSwing()
64+
model = complete(model)
65+
ssys = structural_simplify(IRSystem(model))
66+
prob = ODEProblem(ssys, [
67+
collect(model.body.v_0) .=> 0;
68+
collect(model.body.w_a) .=> 0;
69+
], (0, 4))
70+
sol = solve(prob, ImplicitEuler(autodiff=false), reltol=5e-3)
71+
@assert SciMLBase.successful_retcode(sol)
72+
```
73+
This makes for a rather interesting-looking springy pendulum!
74+
75+
```@example SWING
76+
import CairoMakie
77+
Multibody.render(model, sol; z = -5, filename = "simple_swing.gif") # Use "simple_swing.mp4" for a video file
78+
nothing # hide
79+
```
80+
81+
Next, we create the full swing assembly
82+
83+
```@example SWING
84+
@mtkmodel Swing begin
85+
@structural_parameters begin
86+
h = 2
87+
w = 0.4
88+
end
89+
@components begin
90+
world = W()
91+
upper_trans1 = FixedTranslation(r=[-w/2, 0, 0])
92+
upper_trans2 = FixedTranslation(r=[ w/2, 0, 0])
93+
rope1 = SwingRope(rope.r=[-w/2, -h, -w/2])
94+
rope2 = SwingRope(rope.r=[-w/2, -h, w/2])
95+
rope3 = SwingRope(rope.r=[ w/2, -h, -w/2])
96+
rope4 = SwingRope(rope.r=[ w/2, -h, w/2])
97+
98+
body_back = BodyShape(m=0.1, r = [w, 0, 0])
99+
body_front = BodyShape(m=0.1, r = [w, 0, 0])
100+
body_left = BodyShape(m=0.1, r = [0, 0, w])
101+
body_right = BodyShape(m=0.1, r = [0, 0, -w])
102+
103+
body = Body(m=6, isroot=true, r_cm = [w/2, -w/2, w/2], vel_from_R=true)
104+
105+
damper = Damper(d=0.5)
106+
end
107+
@equations begin
108+
# Rope assembly
109+
connect(world.frame_b, upper_trans1.frame_a, upper_trans2.frame_a)
110+
connect(rope1.frame_a, rope2.frame_a, upper_trans1.frame_b)
111+
connect(rope3.frame_a, rope4.frame_a, upper_trans2.frame_b)
112+
113+
# Body assembly
114+
connect(body_back.frame_a, body_left.frame_a, rope1.frame_b)
115+
connect(body_left.frame_b, body_front.frame_a, rope2.frame_b)
116+
connect(body_front.frame_b, body_right.frame_a, rope4.frame_b)
117+
connect(body_right.frame_b, rope3.frame_b) # Don't close the rigid kinematic loop
118+
connect(body_back.frame_a, body.frame_a)
119+
120+
# World damping (damps swing motion)
121+
connect(world.frame_b, damper.frame_a)
122+
connect(body.frame_a, damper.frame_b)
123+
end
124+
end
125+
126+
@named model = Swing()
127+
model = complete(model)
128+
ssys = structural_simplify(IRSystem(model))
129+
130+
d = 10
131+
dj = 0.01
132+
prob = ODEProblem(ssys, [
133+
collect(model.body_right.body.r_0) .=> [0, -2, 0.5];
134+
# collect(model.body_left.body.r_0) .=> [0, -2, -0.5];
135+
collect(model.body_right.body.v_0) .=> 0;
136+
model.damper.d => 1;
137+
model.rope1.damper.d => d;
138+
model.rope2.damper.d => d;
139+
model.rope3.damper.d => d;
140+
model.rope4.damper.d => d;
141+
142+
model.rope1.joint1.d => dj;
143+
model.rope2.joint1.d => dj;
144+
model.rope3.joint1.d => dj;
145+
model.rope4.joint1.d => dj;
146+
], (0.0, 6))
147+
148+
149+
@time sol = solve(prob, ImplicitEuler(autodiff=false), reltol=1e-2)
150+
@assert SciMLBase.successful_retcode(sol)
151+
152+
Plots.plot(sol, idxs = [collect(model.body.r_0);])
153+
```
154+
155+
```@example SWING
156+
import CairoMakie
157+
Multibody.render(model, sol; y = -1, z = -3, lookat = [0, -1, 0], filename = "swing.gif") # Use "swing.mp4" for a video file
158+
nothing # hide
159+
```
160+
161+
![animation](swing.gif)
162+
163+
There is an initial transient in the beginning where the springs in the ropes are vibrating substantially, but after about a second this transient is damped out (thanks in part to the, in this case desired, numerical damping contributed by the implicit Euler solver).

src/joints.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ Joint with 3 constraints that define that the origin of `frame_a` and the origin
180180
radius = 0.1,
181181
quat = false,
182182
)
183+
184+
dnum = d
183185
@named begin
184186
ptf = PartialTwoFrames()
185187
R_rel = NumRotationMatrix()
@@ -188,6 +190,7 @@ Joint with 3 constraints that define that the origin of `frame_a` and the origin
188190
pars = @parameters begin
189191
radius = radius, [description = "radius of the joint in animations"]
190192
color[1:4] = color, [description = "color of the joint in animations (RGBA)"]
193+
d = d
191194
end
192195
@unpack frame_a, frame_b = ptf
193196
# @parameters begin # Currently not using parameters due to these appearing in if statements
@@ -199,7 +202,7 @@ Joint with 3 constraints that define that the origin of `frame_a` and the origin
199202
] end
200203

201204
# torque balance
202-
if d <= 0
205+
if dnum <= 0
203206
eqs = [zeros(3) .~ collect(frame_a.tau)
204207
zeros(3) .~ collect(frame_b.tau)
205208
collect(frame_b.r_0) .~ collect(frame_a.r_0)]

0 commit comments

Comments
 (0)