Skip to content

Commit dd5651c

Browse files
committed
add Two-track example
1 parent df2a14f commit dd5651c

File tree

7 files changed

+350
-22
lines changed

7 files changed

+350
-22
lines changed

docs/src/examples/wheel.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ defs = ModelingToolkit.defaults(model)
258258
prob = ODEProblem(ssys, [
259259
model.inertia.w => 1e-10, # This is important, at zero velocity, the friction is ill-defined
260260
model.revolute.frame_b.phi => 0,
261+
model.body.w => 0,
261262
D(model.revolute.frame_b.phi) => 0,
262263
D(model.prismatic.r0[2]) => 0,
263264
], (0.0, 15.0))
@@ -266,3 +267,120 @@ render(model, sol, show_axis=false, x=0, y=0, z=4, traces=[model.slipBasedWheelJ
266267
```
267268

268269
![slipwheel animation](slipwheel.gif)
270+
271+
272+
## Planar two-track model
273+
A more elaborate example with 4 wheels.
274+
```@example WHEEL
275+
@mtkmodel TwoTrackWithDifferentialGear begin
276+
@components begin
277+
body = Pl.Body(m = 100, I = 1, gy = 0)
278+
body1 = Pl.Body(m = 300, I = 0.1, r = [1, 1], v = [0, 0], phi = 0, w = 0, gy = 0)
279+
body2 = Pl.Body(m = 100, I = 1, gy = 0)
280+
wheelJoint1 = Pl.SlipBasedWheelJoint(
281+
radius = 0.25,
282+
r = [0, 1],
283+
mu_A = 1,
284+
mu_S = 0.7,
285+
N = 1000,
286+
sAdhesion = 0.04,
287+
sSlide = 0.12,
288+
vAdhesion_min = 0.05,
289+
vSlide_min = 0.15,
290+
phi_roll = 0)
291+
wheelJoint2 = Pl.SlipBasedWheelJoint(
292+
radius = 0.25,
293+
r = [0, 1],
294+
mu_A = 1,
295+
mu_S = 0.7,
296+
N = 1500,
297+
sAdhesion = 0.04,
298+
sSlide = 0.12,
299+
vAdhesion_min = 0.05,
300+
vSlide_min = 0.15,
301+
phi_roll = 0)
302+
wheelJoint3 = Pl.SlipBasedWheelJoint(
303+
radius = 0.25,
304+
r = [0, 1],
305+
mu_A = 1,
306+
mu_S = 0.7,
307+
N = 1500,
308+
sAdhesion = 0.04,
309+
sSlide = 0.12,
310+
vAdhesion_min = 0.05,
311+
vSlide_min = 0.15,
312+
phi_roll = 0)
313+
wheelJoint4 = Pl.SlipBasedWheelJoint(
314+
radius = 0.25,
315+
r = [0, 1],
316+
mu_A = 1,
317+
mu_S = 0.7,
318+
N = 1000,
319+
sAdhesion = 0.04,
320+
sSlide = 0.12,
321+
vAdhesion_min = 0.05,
322+
vSlide_min = 0.15,
323+
phi_roll = 0)
324+
differentialGear = Pl.DifferentialGear()
325+
pulse = Blocks.Square(frequency = 1/2, offset = 0, start_time = 1, amplitude = -2)
326+
torque = Rotational.Torque()
327+
constantTorque1 = Rotational.ConstantTorque(tau_constant = 25)
328+
inertia = Rotational.Inertia(J = 1, phi = 0, w = 0)
329+
inertia1 = Rotational.Inertia(J = 1, phi = 0, w = 0)
330+
inertia2 = Rotational.Inertia(J = 1, phi = 0, w = 0)
331+
inertia3 = Rotational.Inertia(J = 1, phi = 0, w = 0)
332+
fixedTranslation1 = Pl.FixedTranslation(r = [0, 2])
333+
fixedTranslation2 = Pl.FixedTranslation(r = [0.75, 0])
334+
fixedTranslation3 = Pl.FixedTranslation(r = [-0.75, 0])
335+
fixedTranslation4 = Pl.FixedTranslation(r = [0.75, 0])
336+
fixedTranslation5 = Pl.FixedTranslation(r = [-0.75, 0])
337+
leftTrail = Pl.FixedTranslation(r = [0, -0.05])
338+
rightTrail = Pl.FixedTranslation(r = [0, -0.05])
339+
revolute = Pl.Revolute(axisflange=true)
340+
revolute2 = Pl.Revolute(axisflange=true, phi = -0.43633231299858, w = 0)
341+
dynamic_load = Blocks.Constant(k=0)
342+
end
343+
344+
345+
@equations begin
346+
connect(wheelJoint2.flange_a, inertia1.flange_b)
347+
connect(inertia.flange_b, wheelJoint1.flange_a)
348+
connect(fixedTranslation2.frame_b, fixedTranslation1.frame_a)
349+
connect(fixedTranslation2.frame_a, wheelJoint2.frame_a)
350+
connect(fixedTranslation3.frame_b, fixedTranslation1.frame_a)
351+
connect(wheelJoint3.frame_a, fixedTranslation3.frame_a)
352+
connect(inertia2.flange_b, wheelJoint3.flange_a)
353+
connect(body1.frame_a, fixedTranslation1.frame_a)
354+
connect(fixedTranslation1.frame_b, fixedTranslation4.frame_b)
355+
connect(fixedTranslation1.frame_b, fixedTranslation5.frame_b)
356+
connect(inertia3.flange_b, wheelJoint4.flange_a)
357+
connect(pulse.output, torque.tau)
358+
connect(differentialGear.flange_right, wheelJoint3.flange_a)
359+
connect(differentialGear.flange_left, wheelJoint2.flange_a)
360+
connect(constantTorque1.flange, differentialGear.flange_b)
361+
connect(body.frame_a, leftTrail.frame_b)
362+
connect(leftTrail.frame_b, wheelJoint1.frame_a)
363+
connect(body2.frame_a, rightTrail.frame_b)
364+
connect(wheelJoint4.frame_a, rightTrail.frame_b)
365+
connect(leftTrail.frame_a, revolute2.frame_a)
366+
connect(revolute2.frame_b, fixedTranslation4.frame_a)
367+
connect(torque.flange, revolute.flange_a, revolute2.flange_a)
368+
connect(revolute.frame_a, rightTrail.frame_a)
369+
connect(revolute.frame_b, fixedTranslation5.frame_a)
370+
connect(dynamic_load.output, wheelJoint1.dynamicLoad, wheelJoint2.dynamicLoad, wheelJoint3.dynamicLoad, wheelJoint4.dynamicLoad)
371+
end
372+
end
373+
374+
@named model = TwoTrackWithDifferentialGear()
375+
model = complete(model)
376+
ssys = structural_simplify(IRSystem(model))
377+
defs = merge(
378+
Dict(unknowns(ssys) .=> 0),
379+
ModelingToolkit.defaults(model),
380+
Dict(model.body.w => 0),
381+
)
382+
prob = ODEProblem(ssys, defs, (0.0, 5.0))
383+
sol = solve(prob, Rodas5P(autodiff=false))
384+
@test SciMLBase.successful_retcode(sol)
385+
Multibody.render(model, sol, show_axis=false, x=0, y=0, z=5, filename="twotrack.gif")
386+
```

ext/Render.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,12 +827,20 @@ function render!(scene, ::Union{typeof(P.SimpleWheel), typeof(P.SlipBasedWheelJo
827827
catch
828828
0.05f0
829829
end |> Float32
830+
z = try
831+
sol(sol.t[1], idxs=sys.z)
832+
catch
833+
0.0
834+
end |> Float32
835+
r = try
836+
sol(sol.t[1], idxs=collect(sys.r))
837+
catch
838+
[1, 0]
839+
end .|> Float32
840+
n_a = perp(normalize(r)) # Rotation axis
830841
thing = @lift begin
831-
# radius = sol($t, idxs=sys.radius)
832-
O = [r_0($t)..., 0]
833-
# T_w_a = framefun($t)
842+
O = [r_0($t)..., z]
834843
R_w_a = rotfun($t)
835-
n_a = [0,1] # Wheel rotates around y axis
836844
n_w = [R_w_a*n_a; 0] # Rotate to the world frame
837845
width = radius/10
838846
p1 = Point3f(O + width*n_w)
@@ -843,4 +851,12 @@ function render!(scene, ::Union{typeof(P.SimpleWheel), typeof(P.SlipBasedWheelJo
843851
true
844852
end
845853

854+
function perp(r)
855+
if r[1] == 0
856+
return [1, 0]
857+
else
858+
return [-r[2], r[1]]
859+
end
860+
end
861+
846862
end

src/PlanarMechanics/PlanarMechanics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export Frame, FrameResolve, PartialTwoFrames, ZeroPosition, ori_2d
1717
include("utils.jl")
1818

1919
export Fixed, Body, BodyShape, FixedTranslation, Spring, Damper, SpringDamper
20-
export SlipBasedWheelJoint, SimpleWheel
20+
export SlipBasedWheelJoint, SimpleWheel, IdealPlanetary, DifferentialGear
2121
include("components.jl")
2222

2323
export Revolute, Prismatic

src/PlanarMechanics/components.jl

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Body component with mass and inertia
5858
# Connectors:
5959
- `frame`: 2-dim. Coordinate system
6060
"""
61-
@component function Body(; name, m, I, r = zeros(2), phi = 0, gy = -9.807, radius=0.1, render=true, color=Multibody.purple)
61+
@component function Body(; name, m, I, r = zeros(2), v=nothing, phi = 0, w=nothing, gy = -9.807, radius=0.1, render=true, color=Multibody.purple, state_priority=2)
6262
@named frame_a = Frame()
6363
pars = @parameters begin
6464
m = m, [description = "Mass of the body"]
@@ -72,11 +72,11 @@ Body component with mass and inertia
7272

7373
vars = @variables begin
7474
f(t)[1:2], [description = "Force"]
75-
(r(t)[1:2] = r), [description = "x,y position"]
76-
v(t)[1:2], [description = "x,y velocity"]
75+
(r(t)[1:2] = r), [state_priority=state_priority, description = "x,y position"]
76+
(v(t)[1:2] = v), [state_priority=state_priority, description = "x,y velocity"]
7777
a(t)[1:2], [description = "x,y acceleration"]
78-
(phi(t) = phi), [description = "Rotation angle"]
79-
w(t), [description = "Angular velocity"]
78+
(phi(t) = phi), [state_priority=state_priority, description = "Rotation angle"]
79+
(w(t) = w), [state_priority=state_priority, description = "Angular velocity"]
8080
α(t), [description = "Angular acceleration"]
8181
end
8282

@@ -177,14 +177,20 @@ A fixed translation between two components (rigid rod)
177177
begin
178178
r = collect(r)
179179
end
180+
@variables begin
181+
phi(t), [state_priority=1, description = "Angle"]
182+
w(t), [state_priority=1, description = "Angular velocity"]
183+
end
180184

181185
begin
182-
R = [cos(frame_a.phi) -sin(frame_a.phi);
183-
sin(frame_a.phi) cos(frame_a.phi)]
186+
R = [cos(phi) -sin(phi);
187+
sin(phi) cos(phi)]
184188
r0 = R * r
185189
end
186190

187191
@equations begin
192+
phi ~ frame_a.phi
193+
w ~ D(phi)
188194
# rigidly connect positions
189195
frame_a.x + r0[1] ~ frame_b.x
190196
frame_a.y + r0[2] ~ frame_b.y
@@ -586,6 +592,7 @@ In addition there is an input `dynamicLoad` for a dynamic component of the norma
586592
diameter = 0.1,
587593
width = diameter * 0.6,
588594
radius = 0.1,
595+
phi_roll = nothing,
589596
w_roll = nothing,
590597
)
591598
systems = @named begin
@@ -615,7 +622,7 @@ In addition there is an input `dynamicLoad` for a dynamic component of the norma
615622

616623
vars = @variables begin
617624
e0(t)[1:2], [description="Unit vector in direction of r resolved w.r.t. inertial frame"]
618-
phi_roll(t), [guess=0, description="wheel angle"] # wheel angle
625+
(phi_roll(t) = phi_roll), [guess=0, description="wheel angle"] # wheel angle
619626
(w_roll(t)=w_roll), [guess=0, description="Roll velocity of wheel"]
620627
v(t)[1:2], [description="velocity"]
621628
v_lat(t), [guess=0, description="Driving in lateral direction"]
@@ -664,3 +671,66 @@ In addition there is an input `dynamicLoad` for a dynamic component of the norma
664671
return ODESystem(equations, t, vars, pars; name, systems)
665672

666673
end
674+
675+
676+
677+
"""
678+
IdealPlanetary(; name, ratio = 2)
679+
680+
The IdealPlanetary gear box is an ideal gear without inertia, elasticity, damping or backlash consisting of an inner sun wheel, an outer ring wheel and a planet wheel located between sun and ring wheel. The bearing of the planet wheel shaft is fixed in the planet carrier. The component can be connected to other elements at the sun, ring and/or carrier flanges. It is not possible to connect to the planet wheel. If inertia shall not be neglected, the sun, ring and carrier inertias can be easily added by attaching inertias (= model Inertia) to the corresponding connectors. The inertias of the planet wheels are always neglected.
681+
682+
The ideal planetary gearbox is uniquely defined by the ratio of the number of ring teeth ``z_r`` with respect to the number of sun teeth ``z_s``. For example, if there are 100 ring teeth and 50 sun teeth then ratio = ``z_r/z_s = 2``. The number of planet teeth ``z_p`` has to fulfill the following relationship:
683+
```math
684+
z_p = (z_r - z_s) / 2
685+
```
686+
Therefore, in the above example ``z_p = 25`` is required.
687+
688+
According to the overall convention, the positive direction of all vectors, especially the absolute angular velocities and cut-torques in the flanges, are along the axis vector displayed in the icon.
689+
690+
# Parameters:
691+
- `ratio`: Number of ring teeth/sun teeth
692+
693+
# Connectors:
694+
- `sun` (Rotational.Flange) Sun wheel
695+
- `carrier` (Rotational.Flange) Planet carrier
696+
- `ring` (Rotational.Flange) Ring wheel
697+
"""
698+
@mtkmodel IdealPlanetary begin
699+
@parameters begin
700+
ratio = 2, [description="Number of ring_teeth/sun_teeth"]
701+
end
702+
@components begin
703+
sun = Rotational.Flange()
704+
carrier = Rotational.Flange()
705+
ring = Rotational.Flange()
706+
end
707+
@equations begin
708+
(1 + ratio)*carrier.phi ~ sun.phi + ratio*ring.phi
709+
ring.tau ~ ratio*sun.tau
710+
carrier.tau ~ -(1 + ratio)*sun.tau
711+
end
712+
end
713+
714+
"""
715+
DifferentialGear(; name)
716+
717+
A 1D-rotational component that is a variant of a planetary gear and can be used to distribute the torque equally among the wheels on one axis.
718+
719+
# Connectors:
720+
- `flange_b` (Rotational.Flange) Flange for the input torque
721+
- `flange_left` (Rotational.Flange) Flange for the left output torque
722+
- `flange_right` (Rotational.Flange) Flange for the right output torque
723+
"""
724+
@mtkmodel DifferentialGear begin
725+
@components begin
726+
ideal_planetary = IdealPlanetary(ratio=-2)
727+
flange_b = Rotational.Flange()
728+
flange_left = Rotational.Flange()
729+
flange_right = Rotational.Flange()
730+
end
731+
@equations begin
732+
connect(flange_b, ideal_planetary.ring)
733+
connect(ideal_planetary.carrier, flange_right)
734+
connect(ideal_planetary.sun, flange_left)
735+
end
736+
end

src/PlanarMechanics/joints.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ https://github.com/dzimmer/PlanarMechanics/blob/743462f58858a808202be93b70839146
2424
"""
2525
@component function Revolute(;
2626
name,
27-
axisflange = false, render = true, radius = 0.1, color = [1.0, 0.0, 0.0, 1.0], phi=0, w=0)
27+
axisflange = false, render = true, radius = 0.1, color = [1.0, 0.0, 0.0, 1.0], phi=0, w=0,
28+
state_priority = 10)
2829
@named partial_frames = PartialTwoFrames()
2930
@unpack frame_a, frame_b = partial_frames
3031
systems = [frame_a, frame_b]
3132

3233
vars = @variables begin
33-
(phi(t) = phi), [state_priority=10]
34-
(w(t) = w), [state_priority=10]
34+
(phi(t) = phi), [state_priority=state_priority]
35+
(w(t) = w), [state_priority=state_priority]
3536
α(t)
3637
tau(t)
3738
end
@@ -59,12 +60,16 @@ https://github.com/dzimmer/PlanarMechanics/blob/743462f58858a808202be93b70839146
5960

6061
if axisflange
6162
@named fixed = Rotational.Fixed()
62-
push!(systems, fixed)
6363
@named flange_a = Rotational.Flange(; phi, tau)
64-
push!(systems, flange_a)
6564
@named support = Rotational.Support()
65+
push!(systems, fixed)
66+
push!(systems, flange_a)
6667
push!(systems, support)
67-
push!(eqs, connect(fixed.flange, support))
68+
append!(eqs, [
69+
connect(fixed.flange, support)
70+
flange_a.phi ~ phi
71+
flange_a.tau ~ tau
72+
])
6873
else
6974
# actutation torque
7075
push!(eqs, tau ~ 0)

src/PlanarMechanics/utils.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@connector function Frame(; name, render=false, length=1.0, radius=0.1)
22
vars = @variables begin
3-
x(t), [description = "x position"]
4-
y(t), [description = "y position"]
5-
phi(t), [state_priority=2, description = "rotation angle (counterclockwise)"]
3+
x(t), [state_priority = -1, description = "x position"]
4+
y(t), [state_priority = -1, description = "y position"]
5+
phi(t), [state_priority = 0, description = "rotation angle (counterclockwise)"]
66
fx(t), [connect = Flow, description = "force in the x direction"]
77
fy(t), [connect = Flow, description = "force in the y direction"]
88
tau(t), [connect = Flow, description = "torque (clockwise)"]

0 commit comments

Comments
 (0)