LIBRARY
tests.Sensors3DTest
3D Sensors Test
A pendulum that exercises all six 3D multibody sensors. A revolute joint about the world z-axis connects the world to a rigid bar carrying a body. Released from rest in the horizontal position, the pendulum swings under gravity.
The inline CutForce, CutTorque and Power sensors sit in the load path between the bar and the body, while AbsoluteAngles, RelativeAngles and RelativePosition read the body pose without exerting forces. Because the rotation is purely about z, the absolute and relative angle sequences reduce to [0, 0, phi], and the relative position traces the bar tip at unit radius.
Usage
MultibodyComponents.tests.Sensors3DTest()
Behavior
julia
using MultibodyComponents #hide
using ModelingToolkit #hide
@named sys = MultibodyComponents.tests.Sensors3DTest() #hide
let eqs = full_equations(sys); Base.length(eqs) > 25 ? nothing : eqs end #hide<< @example-block not executed in draft mode >>Source
dyad
"""
# 3D Sensors Test
A pendulum that exercises all six 3D multibody sensors. A revolute joint about
the world z-axis connects the world to a rigid bar carrying a body. Released
from rest in the horizontal position, the pendulum swings under gravity.
The inline `CutForce`, `CutTorque` and `Power` sensors sit in the load path
between the bar and the body, while `AbsoluteAngles`, `RelativeAngles` and
`RelativePosition` read the body pose without exerting forces. Because the
rotation is purely about z, the absolute and relative angle sequences reduce to
`[0, 0, phi]`, and the relative position traces the bar tip at unit radius.
"""
example component Sensors3DTest
world = MultibodyComponents.World() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 10, "y1": 190, "x2": 110, "y2": 290, "rot": 0}
},
"tags": []
}
}
revolute = MultibodyComponents.Revolute(n = [0, 0, 1], phi(initial = 0), w(initial = 0)) {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 443,
"x2": 250.5,
"y2": 543,
"rot": 0
}
},
"tags": []
}
}
bar = MultibodyComponents.FixedTranslation(r = [1, 0, 0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 276, "y1": 443, "x2": 376, "y2": 543, "rot": 0}
},
"tags": []
}
}
cut_force = MultibodyComponents.CutForce(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 401, "y1": 443, "x2": 501, "y2": 543, "rot": 0}
},
"tags": []
}
}
cut_torque = MultibodyComponents.CutTorque(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 531, "y1": 443, "x2": 631, "y2": 543, "rot": 0}
},
"tags": []
}
}
power = MultibodyComponents.Power() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 661, "y1": 443, "x2": 761, "y2": 543, "rot": 0}
},
"tags": []
}
}
body = MultibodyComponents.Body(m = 1, r_cm = [0, 0, 0], I_11 = 0.1, I_22 = 0.1, I_33 = 0.1) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 276, "y1": 190, "x2": 376, "y2": 290, "rot": 0}
},
"tags": []
}
}
abs_angles = MultibodyComponents.AbsoluteAngles() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 310, "y1": 10, "x2": 410, "y2": 110, "rot": 0}
},
"tags": []
}
}
rel_angles = MultibodyComponents.RelativeAngles() {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 190,
"x2": 250.5,
"y2": 290,
"rot": 0
}
},
"tags": []
}
}
rel_pos = MultibodyComponents.RelativePosition(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 317,
"x2": 250.5,
"y2": 417,
"rot": 0
}
},
"tags": []
}
}
relations
# Load path: world -> revolute -> bar -> cut_force -> cut_torque -> power -> body
connect(world.frame_b, revolute.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 135, "y": 493}], "E": 2}],
"junctions": [{"x": 135, "y": 240}],
"renderStyle": "standard"
}
}
connect(revolute.frame_b, bar.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(bar.frame_b, cut_force.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(cut_force.frame_b, cut_torque.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(cut_torque.frame_b, power.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(power.frame_b, body.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 776, "y": 493}, {"x": 776, "y": 140}], "E": -1},
{"S": -1, "M": [{"x": 266, "y": 240}], "E": 2}
],
"junctions": [{"x": 266, "y": 140}],
"renderStyle": "standard"
}
}
# Read-only sensors on the body
connect(body.frame_a, abs_angles.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 266, "y": 60}], "E": 2}],
"junctions": [{"x": 266, "y": 240}],
"renderStyle": "standard"
}
}
connect(world.frame_b, rel_angles.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(body.frame_a, rel_angles.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(world.frame_b, rel_pos.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 135, "y": 240}], "E": -1}, {"S": -1, "M": [], "E": 2}],
"junctions": [{"x": 135, "y": 367}],
"renderStyle": "standard"
}
}
connect(body.frame_a, rel_pos.frame_b) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 266, "y": 240}, {"x": 266, "y": 367}], "E": 2}],
"renderStyle": "standard"
}
}
metadata {"Dyad": {"tests": {"case1": {"stop": 2}}}}
endFlattened Source
dyad
"""
# 3D Sensors Test
A pendulum that exercises all six 3D multibody sensors. A revolute joint about
the world z-axis connects the world to a rigid bar carrying a body. Released
from rest in the horizontal position, the pendulum swings under gravity.
The inline `CutForce`, `CutTorque` and `Power` sensors sit in the load path
between the bar and the body, while `AbsoluteAngles`, `RelativeAngles` and
`RelativePosition` read the body pose without exerting forces. Because the
rotation is purely about z, the absolute and relative angle sequences reduce to
`[0, 0, phi]`, and the relative position traces the bar tip at unit radius.
"""
example component Sensors3DTest
world = MultibodyComponents.World() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 10, "y1": 190, "x2": 110, "y2": 290, "rot": 0}
},
"tags": []
}
}
revolute = MultibodyComponents.Revolute(n = [0, 0, 1], phi(initial = 0), w(initial = 0)) {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 443,
"x2": 250.5,
"y2": 543,
"rot": 0
}
},
"tags": []
}
}
bar = MultibodyComponents.FixedTranslation(r = [1, 0, 0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 276, "y1": 443, "x2": 376, "y2": 543, "rot": 0}
},
"tags": []
}
}
cut_force = MultibodyComponents.CutForce(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 401, "y1": 443, "x2": 501, "y2": 543, "rot": 0}
},
"tags": []
}
}
cut_torque = MultibodyComponents.CutTorque(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 531, "y1": 443, "x2": 631, "y2": 543, "rot": 0}
},
"tags": []
}
}
power = MultibodyComponents.Power() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 661, "y1": 443, "x2": 761, "y2": 543, "rot": 0}
},
"tags": []
}
}
body = MultibodyComponents.Body(m = 1, r_cm = [0, 0, 0], I_11 = 0.1, I_22 = 0.1, I_33 = 0.1) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 276, "y1": 190, "x2": 376, "y2": 290, "rot": 0}
},
"tags": []
}
}
abs_angles = MultibodyComponents.AbsoluteAngles() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 310, "y1": 10, "x2": 410, "y2": 110, "rot": 0}
},
"tags": []
}
}
rel_angles = MultibodyComponents.RelativeAngles() {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 190,
"x2": 250.5,
"y2": 290,
"rot": 0
}
},
"tags": []
}
}
rel_pos = MultibodyComponents.RelativePosition(resolve_in_frame = ResolveInFrame.World()) {
"Dyad": {
"placement": {
"diagram": {
"iconName": "default",
"x1": 150.5,
"y1": 317,
"x2": 250.5,
"y2": 417,
"rot": 0
}
},
"tags": []
}
}
relations
# Load path: world -> revolute -> bar -> cut_force -> cut_torque -> power -> body
connect(world.frame_b, revolute.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 135, "y": 493}], "E": 2}],
"junctions": [{"x": 135, "y": 240}],
"renderStyle": "standard"
}
}
connect(revolute.frame_b, bar.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(bar.frame_b, cut_force.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(cut_force.frame_b, cut_torque.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(cut_torque.frame_b, power.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(power.frame_b, body.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 776, "y": 493}, {"x": 776, "y": 140}], "E": -1},
{"S": -1, "M": [{"x": 266, "y": 240}], "E": 2}
],
"junctions": [{"x": 266, "y": 140}],
"renderStyle": "standard"
}
}
# Read-only sensors on the body
connect(body.frame_a, abs_angles.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 266, "y": 60}], "E": 2}],
"junctions": [{"x": 266, "y": 240}],
"renderStyle": "standard"
}
}
connect(world.frame_b, rel_angles.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(body.frame_a, rel_angles.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(world.frame_b, rel_pos.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 135, "y": 240}], "E": -1}, {"S": -1, "M": [], "E": 2}],
"junctions": [{"x": 135, "y": 367}],
"renderStyle": "standard"
}
}
connect(body.frame_a, rel_pos.frame_b) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 266, "y": 240}, {"x": 266, "y": 367}], "E": 2}],
"renderStyle": "standard"
}
}
metadata {"Dyad": {"tests": {"case1": {"stop": 2}}}}
endTest Cases
julia
using MultibodyComponents
using DyadInterface: TransientAnalysis, rebuild_sol, ODEAlg
using ModelingToolkit: toggle_namespacing, get_initial_conditions, @named
using CSV, DataFrames, Plots
snapshotsdir = joinpath(dirname(dirname(pathof(MultibodyComponents))), "test", "snapshots")<< @setup-block not executed in draft mode >>Test Case case1
julia
@named model_case1 = MultibodyComponents.tests.Sensors3DTest()
model_case1 = toggle_namespacing(model_case1, false)
model_case1 = toggle_namespacing(model_case1, true)
result_case1 = TransientAnalysis(; model = model_case1, alg = ODEAlg.Auto(), start = 0e+0, stop = 2e+0, abstol=1e-6, reltol=1e-6)
sol_case1 = rebuild_sol(result_case1)<< @setup-block not executed in draft mode >>Related
Examples
Experiments
Analyses
julia
<< @example-block not executed in draft mode >>