|
1 | 1 | using ModelingToolkit |
2 | 2 | using Multibody |
3 | 3 | import ModelingToolkit: t_nounits as t, D_nounits as D |
| 4 | +import ModelingToolkitStandardLibrary.Mechanical.Rotational |
4 | 5 | using OrdinaryDiffEq |
5 | 6 |
|
6 | | -# A 3D model of a segway using RollingWheelSet |
| 7 | +# A 3D model of a segway using RollingWheelSet (does not allow tilting of body) |
7 | 8 |
|
8 | | -@component function DyadBot3D(; name) |
| 9 | +# @component function DyadBot3D(; name) |
| 10 | +# pars = @parameters begin |
| 11 | +# wheel_radius = 0.04 |
| 12 | +# body_mass = 0.1 |
| 13 | +# body_height = 0.1 |
| 14 | +# body_width = 0.13 |
| 15 | +# body_depth = 0.07 |
| 16 | +# end |
| 17 | + |
| 18 | +# systems = @named begin |
| 19 | +# world = World() |
| 20 | +# wheels = RollingWheelSet( |
| 21 | +# radius = wheel_radius, |
| 22 | +# m_wheel = 0.05, # mass of one wheel |
| 23 | +# I_axis = 5e-5, # moment of inertia of one wheel around the rotation axis |
| 24 | +# I_long = 1e-5, |
| 25 | +# track = body_width, # distance between wheels |
| 26 | +# ) |
| 27 | +# body = BodyShape( |
| 28 | +# m = body_mass, |
| 29 | +# I_22 = 0.01*0.03^2, # Inertia around vertical axis, a very rough approximation |
| 30 | +# I_11 = 0.01*0.05^2, # Total guesses |
| 31 | +# I_33 = 0.01*0.05^2, |
| 32 | +# r = [0.0, body_height, 0.0], # Vector from `frame_a` to `frame_b` (head) resolved in `frame_a` |
| 33 | +# height = body_depth, # we use the word "height" for the length of r |
| 34 | +# width = body_width, |
| 35 | +# ) |
| 36 | +# end |
| 37 | + |
| 38 | +# eqs = [ |
| 39 | +# connect(wheels.frame_middle, body_offset.frame_a) |
| 40 | +# connect(body_offset.frame_b, body.frame_a) |
| 41 | +# ] |
| 42 | + |
| 43 | +# System(eqs, t, [], pars; systems, name) |
| 44 | +# end |
| 45 | + |
| 46 | + |
| 47 | +# @named model = DyadBot3D() |
| 48 | +# model = complete(model) |
| 49 | + |
| 50 | +# ssys = structural_simplify(multibody(model)) |
| 51 | + |
| 52 | +# prob = ODEProblem(ssys, [], (0.0, 5.0)) |
| 53 | + |
| 54 | +# sol = solve(prob, Rodas5P()) |
| 55 | + |
| 56 | +# ## |
| 57 | +# import GLMakie |
| 58 | + |
| 59 | + |
| 60 | +## |
| 61 | + |
| 62 | +# ============================================================================== |
| 63 | +# DyadBot3DWheels - 3D segway using individual RollingWheel components |
| 64 | +# This allows the body to tilt forward/backward (unlike RollingWheelSet which |
| 65 | +# keeps wheels vertical) |
| 66 | +# ============================================================================== |
| 67 | + |
| 68 | +@component function DyadBot3DWheels(; name) |
| 69 | + body_height = 0.1#, [description = "Height of body above wheel axle"] |
| 70 | + track = 0.13#, [description = "Distance between wheels"] |
9 | 71 | pars = @parameters begin |
10 | 72 | wheel_radius = 0.04 |
11 | 73 | body_mass = 0.1 |
12 | | - body_height = 0.1 |
13 | | - body_width = 0.13 |
14 | | - body_depth = 0.07 |
| 74 | + wheel_mass = 0.05 |
| 75 | + wheel_I_axis = 5e-5 |
| 76 | + wheel_I_long = 1e-5 |
| 77 | + d_wheel = 0.1, [description = "Wheel rotational damping coefficient"] |
15 | 78 | end |
16 | 79 |
|
17 | 80 | systems = @named begin |
18 | 81 | world = World() |
19 | | - wheels = RollingWheelSet( |
| 82 | + |
| 83 | + # Two individual rolling wheels (allows tilting) |
| 84 | + wheel_left = SlippingWheel( |
20 | 85 | radius = wheel_radius, |
21 | | - m_wheel = 0.05, # mass of one wheel |
22 | | - I_axis = 5e-5, # moment of inertia of one wheel around the rotation axis |
23 | | - I_long = 1e-5, |
24 | | - track = body_width, # distance between wheels |
| 86 | + m = wheel_mass, |
| 87 | + I_axis = wheel_I_axis, |
| 88 | + I_long = wheel_I_long, |
| 89 | + state = false, |
25 | 90 | ) |
| 91 | + wheel_right = SlippingWheel( |
| 92 | + radius = wheel_radius, |
| 93 | + m = wheel_mass, |
| 94 | + I_axis = wheel_I_axis, |
| 95 | + I_long = wheel_I_long, |
| 96 | + # iscut = true, # Avoid over-constraining |
| 97 | + state = true, |
| 98 | + ) |
| 99 | + |
| 100 | + # Revolute joints for wheel spin |
| 101 | + revolute_left = Revolute(n = [0, 0, 1], axisflange = true, phi0=0, w0=0) |
| 102 | + revolute_right = Revolute(n = [0, 0, 1], axisflange = true, phi0=0, w0=0) |
| 103 | + |
| 104 | + # Axis connecting wheels |
| 105 | + rod_left = FixedTranslation(r = [0, 0, track/2]) |
| 106 | + rod_right = FixedTranslation(r = [0, 0, -track/2]) |
| 107 | + |
| 108 | + # Axis body (small mass at center) |
| 109 | + axis_body = Body(m = 0.01, r_cm = [0, 0, 0]) |
| 110 | + |
| 111 | + # Main body extending upward |
26 | 112 | body = BodyShape( |
27 | 113 | m = body_mass, |
28 | | - I_22 = 0.01*0.03^2, # Inertia around vertical axis, a very rough approximation |
29 | | - I_11 = 0.01*0.05^2, # Total guesses |
30 | | - I_33 = 0.01*0.05^2, |
31 | | - r = [0.0, body_height, 0.0], # Vector from `frame_a` to `frame_b` (head) resolved in `frame_a` |
32 | | - height = body_depth, # we use the word "height" for the length of r |
33 | | - width = body_width, |
34 | | - ) |
| 114 | + I_22 = 0.01*0.03^2, |
| 115 | + I_11 = 0.01*0.05^2, |
| 116 | + I_33 = 0.01*0.05^2, |
| 117 | + r = [0, body_height, 0], |
| 118 | + ) |
| 119 | + |
| 120 | + # Rotational damping for wheel joints |
| 121 | + damper_left = Rotational.Damper(d = d_wheel) |
| 122 | + damper_right = Rotational.Damper(d = d_wheel) |
35 | 123 | end |
36 | 124 |
|
37 | 125 | eqs = [ |
38 | | - connect(wheels.frame_middle, body_offset.frame_a) |
39 | | - connect(body_offset.frame_b, body.frame_a) |
| 126 | + # Connect wheels to revolute joints |
| 127 | + connect(wheel_left.frame_a, revolute_left.frame_b) |
| 128 | + connect(wheel_right.frame_a, revolute_right.frame_b) |
| 129 | + |
| 130 | + # Connect revolute joints to axis via rods |
| 131 | + connect(revolute_left.frame_a, rod_left.frame_b) |
| 132 | + connect(revolute_right.frame_a, rod_right.frame_b) |
| 133 | + |
| 134 | + # Connect rods to axis center |
| 135 | + connect(rod_left.frame_a, axis_body.frame_a) |
| 136 | + connect(rod_right.frame_a, axis_body.frame_a) |
| 137 | + |
| 138 | + # Connect body to axis center |
| 139 | + connect(axis_body.frame_a, body.frame_a) |
| 140 | + |
| 141 | + # Connect dampers to wheel revolute joints |
| 142 | + connect(damper_left.flange_a, revolute_left.axis) |
| 143 | + connect(damper_left.flange_b, revolute_left.support) |
| 144 | + connect(damper_right.flange_a, revolute_right.axis) |
| 145 | + connect(damper_right.flange_b, revolute_right.support) |
40 | 146 | ] |
41 | 147 |
|
42 | 148 | System(eqs, t, [], pars; systems, name) |
43 | 149 | end |
44 | 150 |
|
| 151 | +## |
45 | 152 |
|
46 | | -@named model = DyadBot3D() |
47 | | -model = complete(model) |
| 153 | +@named model_wheels = DyadBot3DWheels() |
| 154 | +model_wheels = complete(model_wheels) |
48 | 155 |
|
49 | | -ssys = structural_simplify(multibody(model)) |
| 156 | +linsys = (; allow_symbolic = false, inline_linear_sccs = true, analytical_linear_scc_limit = 5, reassemble_alg = StructuralTransformations.DefaultReassembleAlgorithm(; inline_linear_sccs = true, analytical_linear_scc_limit = 5)) |
| 157 | +ssys = structural_simplify(multibody(model_wheels); linsys...) |
50 | 158 |
|
51 | | -prob = ODEProblem(ssys, [], (0.0, 5.0)) |
52 | 159 |
|
53 | | -sol = solve(prob, Rodas5P()) |
| 160 | +x0 = [ |
| 161 | + # collect(ssys.axis_body.Q̂) .=> [1, 0, 0, 0]; |
| 162 | +] |
54 | 163 |
|
55 | | -## |
56 | | -import GLMakie |
| 164 | +guesses = unknowns(ssys) .=> 0.0 |
| 165 | + |
| 166 | +prob = ODEProblem(ssys, x0, (0.0, 5.0); guesses) |
57 | 167 |
|
| 168 | +sol_wheels = solve(prob, Rodas5P()) |
58 | 169 |
|
59 | 170 | ## |
60 | 171 |
|
0 commit comments