Skip to content

Commit 9485e4a

Browse files
authored
Add wheel constraints, wheel set and some examples (#119)
* move wheels to own file, add vertical weheel constraint * add test for wheel constraint * add wheel examples * tidy up * rm float param
1 parent b5741d6 commit 9485e4a

File tree

9 files changed

+760
-254
lines changed

9 files changed

+760
-254
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ makedocs(;
3838
"Swing" => "examples/swing.md",
3939
"Bodies in space" => "examples/space.md",
4040
"Gyroscopic effects" => "examples/gyroscopic_effects.md",
41+
"Wheels" => "examples/wheel.md",
4142
"Quadrotor with cable-suspended load" => "examples/quad.md",
4243
],
4344
"Rotations and orientation" => "rotations.md",

docs/src/examples/wheel.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Wheels
2+
3+
## Rolling wheel
4+
```@example WHEEL
5+
using Multibody
6+
using ModelingToolkit
7+
import ModelingToolkitStandardLibrary.Mechanical.Rotational
8+
import ModelingToolkitStandardLibrary.Blocks
9+
using Plots
10+
using OrdinaryDiffEq
11+
using LinearAlgebra
12+
using JuliaSimCompiler
13+
using Test
14+
15+
t = Multibody.t
16+
D = Differential(t)
17+
W(args...; kwargs...) = Multibody.world
18+
19+
@mtkmodel WheelInWorld begin
20+
@components begin
21+
world = W()
22+
wheel = RollingWheel(
23+
radius = 0.3,
24+
m = 2,
25+
I_axis = 0.06,
26+
I_long = 0.12,
27+
x0 = 0.2,
28+
z0 = 0.2,
29+
der_angles = [0, 5, 1],
30+
)
31+
end
32+
end
33+
34+
@named worldwheel = WheelInWorld()
35+
worldwheel = complete(worldwheel)
36+
37+
defs = Dict([
38+
worldwheel.wheel.body.r_0[1] => 0.2;
39+
worldwheel.wheel.body.r_0[2] => 0.3;
40+
worldwheel.wheel.body.r_0[3] => 0.2;
41+
])
42+
43+
ssys = structural_simplify(IRSystem(worldwheel))
44+
prob = ODEProblem(ssys, defs, (0, 4))
45+
sol = solve(prob, Tsit5())
46+
@test SciMLBase.successful_retcode(sol)
47+
```
48+
49+
```@example WHEEL
50+
import GLMakie
51+
Multibody.render(worldwheel, sol; filename = "worldwheel.gif")
52+
nothing # hide
53+
```
54+
55+
![wheel animation](worldwheel.gif)
56+
57+
## Wheel set
58+
```@example WHEEL
59+
@mtkmodel DrivingWheelSet begin
60+
@components begin
61+
sine1 = Blocks.Sine(frequency=1, amplitude=2)
62+
sine2 = Blocks.Sine(frequency=1, amplitude=2, phase=pi/2)
63+
torque1 = Rotational.Torque()
64+
torque2 = Rotational.Torque()
65+
wheels = RollingWheelSet(radius=0.1, m_wheel=0.5, I_axis=0.01, I_long=0.02, track=0.5, state_priority=100)
66+
bar = FixedTranslation(r = [0.2, 0, 0])
67+
body = Body(m=0.01, state_priority=1)
68+
world = W()
69+
end
70+
@equations begin
71+
connect(sine1.output, torque1.tau)
72+
connect(sine2.output, torque2.tau)
73+
connect(torque1.flange, wheels.axis1)
74+
connect(torque2.flange, wheels.axis2)
75+
connect(wheels.frame_middle, bar.frame_a)
76+
connect(bar.frame_b, body.frame_a)
77+
end
78+
end
79+
80+
@named model = DrivingWheelSet()
81+
model = complete(model)
82+
ssys = structural_simplify(IRSystem(model))
83+
# display(unknowns(ssys))
84+
prob = ODEProblem(ssys, [
85+
model.wheels.wheelSetJoint.prismatic1.s => 0.1
86+
model.wheels.wheelSetJoint.prismatic2.s => 0.1
87+
], (0, 3))
88+
sol = solve(prob, Tsit5())
89+
@test SciMLBase.successful_retcode(sol)
90+
```
91+
92+
```@example WHEEL
93+
import GLMakie
94+
Multibody.render(model, sol; filename = "wheelset.gif")
95+
nothing # hide
96+
```
97+
98+
![wheelset animation](wheelset.gif)
99+

ext/Render.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ end
239239
render!(scene, ::Any, args...) = false # Fallback for systems that have no rendering
240240

241241
function render!(scene, ::typeof(Body), sys, sol, t)
242+
sol(sol.t[1], idxs=sys.render)==true || return true # yes, == true
242243
color = get_color(sys, sol, :purple)
243244
r_cm = get_fun(sol, collect(sys.r_cm))
244245
framefun = get_frame_fun(sol, sys.frame_a)
@@ -616,7 +617,12 @@ function render!(scene, ::typeof(Multibody.WorldForce), sys, sol, t)
616617
end
617618

618619
function render!(scene, ::Function, sys, sol, t, args...) # Fallback for systems that have at least two frames
619-
count(ModelingToolkit.isframe, sys.systems) == 2 || return false
620+
frameinds = findall(ModelingToolkit.isframe, collect(sys.systems))
621+
length(frameinds) == 2 || return false
622+
623+
nameof(sys.systems[frameinds[1]]) (:frame_a, :frame_b) || return false
624+
nameof(sys.systems[frameinds[2]]) (:frame_a, :frame_b) || return false
625+
620626
r_0a = get_fun(sol, collect(sys.frame_a.r_0))
621627
r_0b = get_fun(sol, collect(sys.frame_b.r_0))
622628
color = get_color(sys, sol, :green)

src/Multibody.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Find variables that are both array form and scalarized / collected
2+
# foreach(println, sort(unknowns(IRSystem(model)), by=string))
13
module Multibody
24

35
using LinearAlgebra
@@ -10,6 +12,22 @@ export Rotational, Translational
1012

1113
export render, render!
1214

15+
"""
16+
Find parameters that occur both scalarized and not scalarized
17+
"""
18+
function find_arry_problems(model)
19+
# foreach(println, sort(unknowns(IRSystem(model)), by=string))
20+
xs = string.(unknowns(IRSystem(model)))
21+
for x in xs
22+
endswith(x, ']') && continue # Only look at non-array vars
23+
l = ncodeunits(x)
24+
inds = findall(y->startswith(y, x*"["), xs)
25+
isempty(inds) && continue
26+
println(x)
27+
println(xs[inds])
28+
end
29+
end
30+
1331
"""
1432
scene, time = render(model, sol, t::Real; framerate = 30, traces = [])
1533
path = render(model, sol, timevec = range(sol.t[1], sol.t[end], step = 1 / framerate); framerate = 30, timescale=1, display=false, loop=1)
@@ -155,12 +173,15 @@ export World, world, Mounting1D, Fixed, FixedTranslation, FixedRotation, Body, B
155173
include("components.jl")
156174

157175
export Revolute, Prismatic, Planar, Spherical, Universal,
158-
GearConstraint, RollingWheelJoint, RollingWheel, FreeMotion, RevolutePlanarLoopConstraint, Cylindrical
176+
GearConstraint, FreeMotion, RevolutePlanarLoopConstraint, Cylindrical
159177
include("joints.jl")
160178

161179
export SphericalSpherical, UniversalSpherical, JointUSR, JointRRR
162180
include("fancy_joints.jl")
163181

182+
export RollingWheelJoint, RollingWheel, RollingWheelSet, RollingConstraintVerticalWheel
183+
include("wheels.jl")
184+
164185
export Spring, Damper, SpringDamperParallel, Torque, Force, WorldForce, WorldTorque
165186
include("forces.jl")
166187

src/components.jl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,16 @@ function inner_gravity(point_gravity, mu, g, n, r)
7979
end
8080

8181

82-
@component function Fixed(; name, r = [0, 0, 0])
82+
@component function Fixed(; name, r = [0, 0, 0], render = true)
8383
systems = @named begin frame_b = Frame() end
84-
@parameters begin r[1:3] = r,
85-
[
86-
description = "Position vector from world frame to frame_b, resolved in world frame",
87-
] end
84+
@parameters begin
85+
r[1:3] = r, [description = "Position vector from world frame to frame_b, resolved in world frame"]
86+
render = render, [description = "Render the component in animations"]
87+
end
8888
eqs = [collect(frame_b.r_0 .~ r)
8989
ori(frame_b) ~ nullrotation()]
90-
compose(ODESystem(eqs, t; name), systems...)
90+
sys = compose(ODESystem(eqs, t; name=:nothing), systems...)
91+
add_params(sys, [render]; name)
9192
end
9293

9394
@component function Mounting1D(; name, n = [1, 0, 0], phi0 = 0)
@@ -120,7 +121,7 @@ Fixed translation of `frame_b` with respect to `frame_a` with position vector `r
120121
121122
Can be thought of as a massless rod. For a massive rod, see [`BodyShape`](@ref) or [`BodyCylinder`](@ref).
122123
"""
123-
@component function FixedTranslation(; name, r, radius=0.02f0, color = purple)
124+
@component function FixedTranslation(; name, r, radius=0.02f0, color = purple, render = true)
124125
@named frame_a = Frame()
125126
@named frame_b = Frame()
126127
@parameters r[1:3]=r [
@@ -130,6 +131,7 @@ Can be thought of as a massless rod. For a massive rod, see [`BodyShape`](@ref)
130131
@parameters begin
131132
radius = radius, [description = "Radius of the body in animations"]
132133
color[1:4] = color, [description = "Color of the body in animations (RGBA)"]
134+
render = render, [description = "Render the component in animations"]
133135
end
134136
fa = frame_a.f |> collect
135137
fb = frame_b.f |> collect
@@ -139,7 +141,7 @@ Can be thought of as a massless rod. For a massive rod, see [`BodyShape`](@ref)
139141
(ori(frame_b) ~ ori(frame_a))
140142
collect(0 .~ fa + fb)
141143
(0 .~ taua + taub + cross(r, fb))]
142-
pars = [r; radius; color]
144+
pars = [r; radius; color; render]
143145
vars = []
144146
compose(ODESystem(eqs, t, vars, pars; name), frame_a, frame_b)
145147
end
@@ -252,6 +254,7 @@ This component has a single frame, `frame_a`. To represent bodies with more than
252254
air_resistance = 0.0,
253255
color = [1,0,0,1],
254256
state_priority = 2,
257+
render = true,
255258
quat=false,)
256259
if state
257260
# @warn "Make the body have state variables by using isroot=true rather than state=true"
@@ -289,6 +292,7 @@ This component has a single frame, `frame_a`. To represent bodies with more than
289292
]
290293
@parameters color[1:4] = color [description = "Color of the body in animations (RGBA)"]
291294
@parameters length_fraction=length_fraction, [description = "Fraction of the length of the body that is the cylinder from frame to COM in animations"]
295+
@parameters render = render [description = "Render the component in animations"]
292296
# @parameters I[1:3, 1:3]=I [description="inertia tensor"]
293297

294298
@parameters I_11=I_11 [description = "Element (1,1) of inertia tensor"]
@@ -361,7 +365,7 @@ This component has a single frame, `frame_a`. To represent bodies with more than
361365
# pars = [m;r_cm;radius;I_11;I_22;I_33;I_21;I_31;I_32;color]
362366

363367
sys = ODESystem(eqs, t; name=:nothing, metadata = Dict(:isroot => isroot), systems = [frame_a])
364-
add_params(sys, [radius; cylinder_radius; color; length_fraction]; name)
368+
add_params(sys, [radius; cylinder_radius; color; length_fraction; render]; name)
365369
end
366370

367371

src/fancy_joints.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ This joint aggregation can be used in cases where in reality a rod with spherica
278278
eRod_a(t)[1:3], [description="Unit vector in direction of rRod_a, resolved in frame_a (needed for analytic loop handling)"]
279279
rRod_0(t)[1:3] = rRod_ia, [description="Position vector from frame_a to frame_b resolved in world frame"]
280280
rRod_a(t)[1:3] = rRod_ia, [description="Position vector from frame_a to frame_b resolved in frame_a"]
281-
(constraintResidue(t) = 0), [description="Constraint equation of joint in residue form: Either length constraint (= default) or equation to compute rod force (for analytic solution of loops in combination with Internal.RevoluteWithLengthConstraint/PrismaticWithLengthConstraint)"]
281+
(constraintResidue(t) = 0), [description="Constraint equation of joint in residue form: Either length constraint (= default) or equation to compute rod force (for analytic solution of loops in combination with RevoluteWithLengthConstraint)"]
282282
f_b_a(t)[1:3], [description="frame_b.f resolved in frame_a"]
283283
f_ia_a(t)[1:3], [description="frame_ia.f resolved in frame_a"]
284284
t_ia_a(t)[1:3], [description="frame_ia.t resolved in frame_a"]

0 commit comments

Comments
 (0)