PlanarMechanics.examples.excavator.Excavator
Planar excavator digging through soil with hydraulically actuated boom, stick, and bucket.
The machine stands 5 m tall with the arm extended straight up. Three revolute joints connect the boom, stick, and bucket links. Each joint is driven by a ControlledCylinder — a self-contained actuator bundling a proportional valve, a double-acting cylinder, flange sensors, and a position-velocity-force CascadeServoController — whose flange acts on the linkage through an AnchoredCylinderMount that places the cylinder ends on the adjacent links.
A KinematicPTPBoundedJerk trajectory generator issues per-axis position, velocity, acceleration, and jerk references; one TrajectorySelector per axis picks that axis's references out of the vector-valued trajectory outputs and feeds them to the corresponding actuator. The dig cycle lowers the bucket into the soil ahead of the machine, drags it towards the machine, curls it, and lifts it out. While the bucket tip is below the soil surface, the SoilContact component applies penetration and cutting resistance forces, and the swept soil accumulates as a payload mass whose weight remains on the bucket after it leaves the ground.
The chassis and the bucket are rendered with mesh assets attached to the planar frames through PlanarToMultiBody adapters and ShapefileVisualizer components. The meshes are derived from the "Bulldozer" model by Poly by Google (https://poly.pizza/m/ddxtaegI3HQ), licensed CC-BY 3.0.
Usage
MultibodyComponents.PlanarMechanics.examples.excavator.Excavator(main_color=[0.9, 0.6, 0.1, 1.0], pivot_height=1.2, L_boom=2.0, L_stick=1.2, L_bucket=0.6, m_boom=300, m_stick=150, m_bucket=100, q1_0=deg2rad(30), q2_0=deg2rad(-60), q3_0=deg2rad(-60), p_pump=1.6e7, p_tank=1e5, r_boom_base=[0.3, 0.3], d_boom_rod=1.1, d_stick_base=1.4, d_stick_rod=0.5, d_bucket_base=0.7, d_bucket_rod=0.3, s_off_boom=1.10, s_off_stick=0.80, s_off_bucket=0.34, stroke_boom=0.70, stroke_stick=0.35, stroke_bucket=0.42)
Parameters:
| Name | Description | Units | Default value |
|---|---|---|---|
main_color | – | [0.9, 0.6, 0.1, 1.0] | |
pivot_height | Height of the boom pivot above the ground | m | 1.2 |
L_boom | Length of the boom link | m | 2.0 |
L_stick | Length of the stick link | m | 1.2 |
L_bucket | Length of the bucket link | m | 0.6 |
m_boom | Mass of the boom link | kg | 300 |
m_stick | Mass of the stick link | kg | 150 |
m_bucket | Mass of the bucket | kg | 100 |
q1_0 | Initial boom joint angle | rad | deg2rad(30) |
q2_0 | Initial stick joint angle | rad | deg2rad(-60) |
q3_0 | Initial bucket joint angle | rad | deg2rad(-60) |
p_pump | Supply pressure | – | 1.6e7 |
p_tank | Tank pressure | – | 1e5 |
r_boom_base | Boom cylinder base anchor position on the chassis | m | [0.3, 0.3] |
d_boom_rod | Distance from the boom pivot to the boom cylinder rod anchor | m | 1.1 |
d_stick_base | Distance from the boom pivot to the stick cylinder base anchor | m | 1.4 |
d_stick_rod | Distance from the stick joint to the stick cylinder rod anchor | m | 0.5 |
d_bucket_base | Distance from the stick joint to the bucket cylinder base anchor | m | 0.7 |
d_bucket_rod | Distance from the bucket joint to the bucket cylinder rod anchor | m | 0.3 |
s_off_boom | Boom cylinder anchor distance at zero piston position | m | 1.10 |
s_off_stick | Stick cylinder anchor distance at zero piston position | m | 0.80 |
s_off_bucket | Bucket cylinder anchor distance at zero piston position | m | 0.34 |
stroke_boom | Stroke of the boom cylinder | m | 0.70 |
stroke_stick | Stroke of the stick cylinder | m | 0.35 |
stroke_bucket | Stroke of the bucket cylinder | m | 0.42 |
Behavior
Behavior of this component cannot be rendered because it includes path variables.
Source
"""
Planar excavator digging through soil with hydraulically actuated boom, stick,
and bucket.
The machine stands 5 m tall with the arm extended straight up. Three revolute
joints connect the boom, stick, and bucket links. Each joint is driven by a
`ControlledCylinder` — a self-contained actuator bundling a proportional valve,
a double-acting cylinder, flange sensors, and a position-velocity-force
`CascadeServoController` — whose flange acts on the linkage through an
`AnchoredCylinderMount` that places the cylinder ends on the adjacent links.
A `KinematicPTPBoundedJerk` trajectory generator issues per-axis position,
velocity, acceleration, and jerk references; one `TrajectorySelector` per axis
picks that axis's references out of the vector-valued trajectory outputs and
feeds them to the corresponding actuator. The dig cycle lowers the bucket into
the soil ahead of the machine, drags it towards the machine, curls it, and
lifts it out. While the bucket tip is below the soil surface, the `SoilContact`
component applies penetration and cutting resistance forces, and the swept soil
accumulates as a payload mass whose weight remains on the bucket after it
leaves the ground.
The chassis and the bucket are rendered with mesh assets attached to the
planar frames through `PlanarToMultiBody` adapters and `ShapefileVisualizer`
components. The meshes are derived from the "Bulldozer" model by Poly by
Google (https://poly.pizza/m/ddxtaegI3HQ), licensed CC-BY 3.0.
"""
example component Excavator
world = MultibodyComponents.PlanarMechanics.World(render_ground = false, nominal_length = 2, default_joint_color = main_color, default_body_color = main_color, default_rod_color = main_color) {
"Dyad": {
"placement": {"diagram": {"x1": 1440, "y1": -10, "x2": 1340, "y2": 90, "rot": 270}}
}
}
# --- chassis (rigidly fixed to the world, masses are cosmetic) ---
track_mount = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [-1.1, 0.3], render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1380, "y1": 250, "x2": 1480, "y2": 350}}}
}
track = MultibodyComponents.PlanarMechanics.BodyShape(r = [2.2, 0], m = 2500, I = 1000, radius = 0.3, render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1490, "y1": 190, "x2": 1710, "y2": 410}}}
}
cab = MultibodyComponents.PlanarMechanics.BodyShape(r = [0, 0.9], m = 500, I = 200, render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1490, "y1": 40, "x2": 1710, "y2": 260}}}
}
"Rigid post from the world origin to the boom pivot"
pedestal = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [0, pivot_height], render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1340, "y1": 190, "x2": 1240, "y2": 290}}}
}
# --- mesh visuals for the chassis and the bucket ---
adapter_chassis = MultibodyComponents.PlanarMechanics.PlanarToMultiBody() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1350, "y1": 400, "x2": 1450, "y2": 500, "rot": 0}
},
"tags": []
}
}
vis_chassis_orange = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_orange.obj"), color = [0.9, 0.55, 0.09, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 400, "x2": 1600, "y2": 500, "rot": 0}
},
"tags": []
}
}
vis_chassis_dark = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_dark.obj"), color = [0.15, 0.15, 0.15, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 520, "x2": 1600, "y2": 620, "rot": 0}
},
"tags": []
}
}
vis_chassis_white = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_white.obj"), color = [0.9, 0.9, 0.9, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 640, "x2": 1600, "y2": 740, "rot": 0}
},
"tags": []
}
}
vis_chassis_red = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_red.obj"), color = [0.6, 0.08, 0.08, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 760, "x2": 1600, "y2": 860, "rot": 0}
},
"tags": []
}
}
adapter_bucket = MultibodyComponents.PlanarMechanics.PlanarToMultiBody() {
"Dyad": {"placement": {"diagram": {"x1": 1200, "y1": 940, "x2": 1300, "y2": 1040}}}
}
vis_bucket = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("bucket.obj"), color = [0.45, 0.47, 0.5, 1.0], shape_transform = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) {
"Dyad": {"placement": {"diagram": {"x1": 1300, "y1": 940, "x2": 1400, "y2": 1040}}}
}
# --- boom / stick / bucket linkage ---
j_boom = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q1_0), w(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 1230, "y1": 190, "x2": 1130, "y2": 290}}}
}
boom = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_boom, 0], m = m_boom, I = m_boom * L_boom ^ 2 / 12, radius = 0.09) {
"Dyad": {
"placement": {"diagram": {"x1": 1190, "y1": 350, "x2": 1090, "y2": 450, "rot": 270}}
}
}
j_stick = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q2_0), w(initial = 0)) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 500, "x2": 1190, "y2": 600, "rot": 90}}
}
}
stick = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_stick, 0], m = m_stick, I = m_stick * L_stick ^ 2 / 12, radius = 0.07) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 670, "x2": 1190, "y2": 770, "rot": 90}}
}
}
j_bucket = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q3_0), w(initial = 0)) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 810, "x2": 1190, "y2": 910, "rot": 90}}
}
}
bucket = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_bucket, 0], m = m_bucket, I = m_bucket * L_bucket ^ 2 / 12, render = false) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 980, "x2": 1190, "y2": 1080, "rot": 90}}
}
}
# --- hydraulic supply ---
pump = HydraulicComponents.Sources.BoundaryPressure() {
"Dyad": {"placement": {"diagram": {"x1": 480, "y1": -100, "x2": 580, "y2": 0}}}
}
tank = HydraulicComponents.Sources.BoundaryPressure() {
"Dyad": {"placement": {"diagram": {"x1": 750, "y1": -100, "x2": 650, "y2": 0}}}
}
c_pump = BlockComponents.Sources.Constant(k = p_pump) {
"Dyad": {"placement": {"diagram": {"x1": 330, "y1": -100, "x2": 430, "y2": 0}}}
}
c_tank = BlockComponents.Sources.Constant(k = p_tank) {
"Dyad": {"placement": {"diagram": {"x1": 900, "y1": -100, "x2": 800, "y2": 0}}}
}
# --- boom actuator, mount, and reference selector ---
cc_boom = ControlledCylinder(Kv = 9e-7, stroke = stroke_boom, M = 20, area_a = 6.36e-3, area_b = 4.40e-3, p_a0 = 2.4e6, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 1.875, Ti_vel = 1.5, f_max = 8e4, ka_ff = 5000, k_force = 0.08, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 130, "x2": 560, "y2": 330, "rot": 0}
},
"tags": []
}
}
mnt_boom = AnchoredCylinderMount(r_base = r_boom_base, r_rod = [d_boom_rod, 0], s_offset = s_off_boom, stroke = stroke_boom, barrel_radius = 0.055) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 130, "x2": 980, "y2": 330, "rot": 270}}
}
}
sel_boom = MultibodyComponents.TrajectorySelector(nin = 3, index = 1) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 190, "x2": 220, "y2": 290}}}
}
# --- stick actuator, mount, and reference selector ---
cc_stick = ControlledCylinder(Kv = 5.5e-7, stroke = stroke_stick, M = 12, area_a = 3.85e-3, area_b = 2.59e-3, p_a0 = 2.0e6, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 7.0, Ti_vel = 1.5, f_max = 5e4, ka_ff = 4000, k_force = 0.05, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 450, "x2": 560, "y2": 650, "rot": 0}
},
"tags": []
}
}
mnt_stick = AnchoredCylinderMount(r_base = [d_stick_base, 0], r_rod = [d_stick_rod, 0], s_offset = s_off_stick, stroke = stroke_stick, barrel_radius = 0.045) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 450, "x2": 980, "y2": 650, "rot": 270}}
}
}
sel_stick = MultibodyComponents.TrajectorySelector(nin = 3, index = 2) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 510, "x2": 220, "y2": 610}}}
}
# --- bucket actuator, mount, and reference selector ---
cc_bucket = ControlledCylinder(Kv = 4e-7, stroke = stroke_bucket, M = 8, area_a = 2.83e-3, area_b = 1.86e-3, p_a0 = 4e5, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 1.1428571428571428, Ti_vel = 1.0, f_max = 3.5e4, ka_ff = 150, k_force = 1.05, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 770, "x2": 560, "y2": 970, "rot": 0}
},
"tags": []
}
}
mnt_bucket = AnchoredCylinderMount(r_base = [d_bucket_base, 0], r_rod = [d_bucket_rod, 0], s_offset = s_off_bucket, stroke = stroke_bucket, barrel_radius = 0.04) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 770, "x2": 980, "y2": 970, "rot": 270}}
}
}
sel_bucket = MultibodyComponents.TrajectorySelector(nin = 3, index = 3) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 830, "x2": 220, "y2": 930}}}
}
# --- reference trajectory and soil ---
traj = MultibodyComponents.KinematicPTPBoundedJerk(waypoints = transpose(hcat([0.490, 0.061, 0.147, 0.147, 0.596], [0.154, 0.263, 0.100, 0.100, 0.154], [0.360, 0.298, 0.304, 0.044, 0.044])), nout = 3, nwp = 5, qd_max = fill(0.5, 3), qdd_max = fill(0.5, 3), qddd_max = fill(5.0, 3), phase_synchronization = true) {
"Dyad": {"placement": {"diagram": {"x1": -80, "y1": 400, "x2": 20, "y2": 500}}}
}
soil = SoilContact() {
"Dyad": {"placement": {"diagram": {"x1": 1090, "y1": 1120, "x2": 1190, "y2": 1220}}}
}
parameter main_color::Real[4] = [0.9, 0.6, 0.1, 1.0]
"Height of the boom pivot above the ground"
parameter pivot_height::Length = 1.2
"Length of the boom link"
parameter L_boom::Length = 2.0
"Length of the stick link"
parameter L_stick::Length = 1.2
"Length of the bucket link"
parameter L_bucket::Length = 0.6
"Mass of the boom link"
parameter m_boom::Mass = 300
"Mass of the stick link"
parameter m_stick::Mass = 150
"Mass of the bucket"
parameter m_bucket::Mass = 100
"Initial boom joint angle"
parameter q1_0::Angle = deg2rad(30)
"Initial stick joint angle"
parameter q2_0::Angle = deg2rad(-60)
"Initial bucket joint angle"
parameter q3_0::Angle = deg2rad(-60)
"Supply pressure"
parameter p_pump::Real = 1.6e7
"Tank pressure"
parameter p_tank::Real = 1e5
"Boom cylinder base anchor position on the chassis"
parameter r_boom_base::Length[2] = [0.3, 0.3]
"Distance from the boom pivot to the boom cylinder rod anchor"
parameter d_boom_rod::Length = 1.1
"Distance from the boom pivot to the stick cylinder base anchor"
parameter d_stick_base::Length = 1.4
"Distance from the stick joint to the stick cylinder rod anchor"
parameter d_stick_rod::Length = 0.5
"Distance from the stick joint to the bucket cylinder base anchor"
parameter d_bucket_base::Length = 0.7
"Distance from the bucket joint to the bucket cylinder rod anchor"
parameter d_bucket_rod::Length = 0.3
"Boom cylinder anchor distance at zero piston position"
parameter s_off_boom::Length = 1.10
"Stick cylinder anchor distance at zero piston position"
parameter s_off_stick::Length = 0.80
"Bucket cylinder anchor distance at zero piston position"
parameter s_off_bucket::Length = 0.34
"Stroke of the boom cylinder"
parameter stroke_boom::Length = 0.70
"Stroke of the stick cylinder"
parameter stroke_stick::Length = 0.35
"Stroke of the bucket cylinder"
parameter stroke_bucket::Length = 0.42
"Working fluid of the hydraulic circuits"
path medium::HydraulicComponents.Interfaces.AbstractMedium = HydraulicComponents.IsothermalCompressible(bulk_modulus = 1e9, let_gas = false, density = 870, viscosity = 0.03)
relations
# --- kinematic chain ---
connect(pedestal.frame_a, track_mount.frame_a, world.frame_b, cab.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": 2, "M": [], "E": -1},
{"S": 3, "M": [], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [], "E": -2}
],
"junctions": [{"x": 1390, "y": 240}, {"x": 1390, "y": 150}]
}
}
connect(track_mount.frame_b, track.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(pedestal.frame_b, j_boom.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_boom.frame_b, boom.frame_a, mnt_boom.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 330}]
}
}
connect(boom.frame_b, j_stick.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_stick.frame_b, stick.frame_a, mnt_stick.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 650}]
}
}
connect(stick.frame_b, j_bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_bucket.frame_b, bucket.frame_a, mnt_bucket.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 970}]
}
}
connect(bucket.frame_b, soil.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
# --- mesh visuals ---
connect(adapter_chassis.frame_b, vis_chassis_orange.frame_a, vis_chassis_dark.frame_a, vis_chassis_white.frame_a, vis_chassis_red.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": 2},
{"S": 2, "M": [], "E": 3},
{"S": 3, "M": [], "E": 4},
{"S": 4, "M": [], "E": 5}
],
"renderStyle": "standard"
}
}
connect(adapter_bucket.frame_b, vis_bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
# --- hydraulic supply network ---
connect(c_pump.y, pump.p) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(c_tank.y, tank.p) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
continuity(medium, pump.port.medium)
connect(cc_bucket.port_t, cc_stick.port_t, tank.port, cc_boom.port_t) {
"Dyad": {
"edges": [
{"S": -1, "M": [{"x": 630, "y": 970}], "E": 1},
{"S": 2, "M": [], "E": -1},
{"S": 3, "M": [{"x": 630, "y": -50}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [], "E": -2}
],
"junctions": [{"x": 630, "y": 650}, {"x": 630, "y": 330}]
}
}
connect(cc_bucket.port_p, cc_stick.port_p, pump.port, cc_boom.port_p) {
"Dyad": {
"edges": [
{"S": -1, "M": [{"x": 600, "y": 1020}, {"x": 430, "y": 1020}], "E": 1},
{"S": 2, "M": [{"x": 430, "y": 700}], "E": -1},
{"S": 3, "M": [{"x": 600, "y": -50}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 430, "y": 380}], "E": -2}
],
"junctions": [{"x": 600, "y": 700}, {"x": 600, "y": 380}]
}
}
connect(sel_boom.q, traj.q, sel_bucket.q, sel_stick.q) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 40, "y": 205}], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [{"x": 40, "y": 845}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 80, "y": 525}, {"x": 80, "y": 520}], "E": -2}
],
"junctions": [{"x": 40, "y": 415}, {"x": 40, "y": 520}]
}
}
connect(traj.qd, sel_boom.qd, sel_bucket.qd, sel_stick.qd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 50, "y": 225}], "E": 2},
{"S": 3, "M": [{"x": 50, "y": 865}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 85, "y": 545}, {"x": 85, "y": 540}], "E": -2}
],
"junctions": [{"x": 50, "y": 438}, {"x": 50, "y": 540}]
}
}
connect(traj.qdd, sel_boom.qdd, sel_bucket.qdd, sel_stick.qdd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 60, "y": 252}], "E": 2},
{"S": 3, "M": [{"x": 60, "y": 892}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 90, "y": 572}, {"x": 90, "y": 570}], "E": -2}
],
"junctions": [{"x": 60, "y": 464}, {"x": 60, "y": 570}]
}
}
connect(traj.qddd, sel_boom.qddd, sel_bucket.qddd, sel_stick.qddd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 70.5, "y": 484}, {"x": 70.5, "y": 273}], "E": 2},
{"S": 3, "M": [{"x": 70, "y": 912}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 95, "y": 592}, {"x": 95, "y": 590}], "E": -2}
],
"junctions": [{"x": 70, "y": 484}, {"x": 70, "y": 590}]
}
}
connect(cc_boom.flange, mnt_boom.flange) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 655, "y": 230}, {"x": 655, "y": 230.00000000000003}],
"E": 2
}
]
}
}
connect(cc_stick.flange, mnt_stick.flange) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(cc_bucket.flange, mnt_bucket.flange) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(mnt_stick.frame_rod, mnt_bucket.frame_base) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(mnt_boom.frame_rod, mnt_stick.frame_base) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(world.frame_b, mnt_boom.frame_base) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 1390, "y": 110}, {"x": 880, "y": 110}], "E": 2}]
}
}
connect(adapter_chassis.frame_a, track_mount.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 1340, "y": 450}, {"x": 1340, "y": 300}], "E": 2}]
}
}
connect(adapter_bucket.frame_a, bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(sel_boom.s, cc_boom.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 205}, {"x": 290, "y": 168}], "E": 2}]}
}
connect(sel_boom.v, cc_boom.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 225}, {"x": 290, "y": 230}], "E": 2}]}
}
connect(sel_boom.a, cc_boom.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 245}, {"x": 290, "y": 280}], "E": 2}]}
}
connect(sel_stick.s, cc_stick.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 525}, {"x": 290, "y": 488}], "E": 2}]}
}
connect(sel_stick.v, cc_stick.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 545}, {"x": 290, "y": 550}], "E": 2}]}
}
connect(sel_stick.a, cc_stick.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 565}, {"x": 290, "y": 600}], "E": 2}]}
}
connect(sel_bucket.s, cc_bucket.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 845}, {"x": 290, "y": 808}], "E": 2}]}
}
connect(sel_bucket.v, cc_bucket.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 865}, {"x": 290, "y": 870}], "E": 2}]}
}
connect(sel_bucket.a, cc_bucket.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 885}, {"x": 290, "y": 920}], "E": 2}]}
}
metadata {"Dyad": {"tests": {"case1": {"stop": 13}}}}
endFlattened Source
"""
Planar excavator digging through soil with hydraulically actuated boom, stick,
and bucket.
The machine stands 5 m tall with the arm extended straight up. Three revolute
joints connect the boom, stick, and bucket links. Each joint is driven by a
`ControlledCylinder` — a self-contained actuator bundling a proportional valve,
a double-acting cylinder, flange sensors, and a position-velocity-force
`CascadeServoController` — whose flange acts on the linkage through an
`AnchoredCylinderMount` that places the cylinder ends on the adjacent links.
A `KinematicPTPBoundedJerk` trajectory generator issues per-axis position,
velocity, acceleration, and jerk references; one `TrajectorySelector` per axis
picks that axis's references out of the vector-valued trajectory outputs and
feeds them to the corresponding actuator. The dig cycle lowers the bucket into
the soil ahead of the machine, drags it towards the machine, curls it, and
lifts it out. While the bucket tip is below the soil surface, the `SoilContact`
component applies penetration and cutting resistance forces, and the swept soil
accumulates as a payload mass whose weight remains on the bucket after it
leaves the ground.
The chassis and the bucket are rendered with mesh assets attached to the
planar frames through `PlanarToMultiBody` adapters and `ShapefileVisualizer`
components. The meshes are derived from the "Bulldozer" model by Poly by
Google (https://poly.pizza/m/ddxtaegI3HQ), licensed CC-BY 3.0.
"""
example component Excavator
world = MultibodyComponents.PlanarMechanics.World(render_ground = false, nominal_length = 2, default_joint_color = main_color, default_body_color = main_color, default_rod_color = main_color) {
"Dyad": {
"placement": {"diagram": {"x1": 1440, "y1": -10, "x2": 1340, "y2": 90, "rot": 270}}
}
}
# --- chassis (rigidly fixed to the world, masses are cosmetic) ---
track_mount = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [-1.1, 0.3], render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1380, "y1": 250, "x2": 1480, "y2": 350}}}
}
track = MultibodyComponents.PlanarMechanics.BodyShape(r = [2.2, 0], m = 2500, I = 1000, radius = 0.3, render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1490, "y1": 190, "x2": 1710, "y2": 410}}}
}
cab = MultibodyComponents.PlanarMechanics.BodyShape(r = [0, 0.9], m = 500, I = 200, render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1490, "y1": 40, "x2": 1710, "y2": 260}}}
}
"Rigid post from the world origin to the boom pivot"
pedestal = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [0, pivot_height], render = false) {
"Dyad": {"placement": {"diagram": {"x1": 1340, "y1": 190, "x2": 1240, "y2": 290}}}
}
# --- mesh visuals for the chassis and the bucket ---
adapter_chassis = MultibodyComponents.PlanarMechanics.PlanarToMultiBody() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1350, "y1": 400, "x2": 1450, "y2": 500, "rot": 0}
},
"tags": []
}
}
vis_chassis_orange = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_orange.obj"), color = [0.9, 0.55, 0.09, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 400, "x2": 1600, "y2": 500, "rot": 0}
},
"tags": []
}
}
vis_chassis_dark = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_dark.obj"), color = [0.15, 0.15, 0.15, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 520, "x2": 1600, "y2": 620, "rot": 0}
},
"tags": []
}
}
vis_chassis_white = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_white.obj"), color = [0.9, 0.9, 0.9, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 640, "x2": 1600, "y2": 740, "rot": 0}
},
"tags": []
}
}
vis_chassis_red = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("chassis_red.obj"), color = [0.6, 0.08, 0.08, 1.0]) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 1500, "y1": 760, "x2": 1600, "y2": 860, "rot": 0}
},
"tags": []
}
}
adapter_bucket = MultibodyComponents.PlanarMechanics.PlanarToMultiBody() {
"Dyad": {"placement": {"diagram": {"x1": 1200, "y1": 940, "x2": 1300, "y2": 1040}}}
}
vis_bucket = MultibodyComponents.ShapefileVisualizer(shapefile = excavator_asset("bucket.obj"), color = [0.45, 0.47, 0.5, 1.0], shape_transform = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) {
"Dyad": {"placement": {"diagram": {"x1": 1300, "y1": 940, "x2": 1400, "y2": 1040}}}
}
# --- boom / stick / bucket linkage ---
j_boom = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q1_0), w(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 1230, "y1": 190, "x2": 1130, "y2": 290}}}
}
boom = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_boom, 0], m = m_boom, I = m_boom * L_boom ^ 2 / 12, radius = 0.09) {
"Dyad": {
"placement": {"diagram": {"x1": 1190, "y1": 350, "x2": 1090, "y2": 450, "rot": 270}}
}
}
j_stick = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q2_0), w(initial = 0)) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 500, "x2": 1190, "y2": 600, "rot": 90}}
}
}
stick = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_stick, 0], m = m_stick, I = m_stick * L_stick ^ 2 / 12, radius = 0.07) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 670, "x2": 1190, "y2": 770, "rot": 90}}
}
}
j_bucket = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = q3_0), w(initial = 0)) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 810, "x2": 1190, "y2": 910, "rot": 90}}
}
}
bucket = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_bucket, 0], m = m_bucket, I = m_bucket * L_bucket ^ 2 / 12, render = false) {
"Dyad": {
"placement": {"diagram": {"x1": 1090, "y1": 980, "x2": 1190, "y2": 1080, "rot": 90}}
}
}
# --- hydraulic supply ---
pump = HydraulicComponents.Sources.BoundaryPressure() {
"Dyad": {"placement": {"diagram": {"x1": 480, "y1": -100, "x2": 580, "y2": 0}}}
}
tank = HydraulicComponents.Sources.BoundaryPressure() {
"Dyad": {"placement": {"diagram": {"x1": 750, "y1": -100, "x2": 650, "y2": 0}}}
}
c_pump = BlockComponents.Sources.Constant(k = p_pump) {
"Dyad": {"placement": {"diagram": {"x1": 330, "y1": -100, "x2": 430, "y2": 0}}}
}
c_tank = BlockComponents.Sources.Constant(k = p_tank) {
"Dyad": {"placement": {"diagram": {"x1": 900, "y1": -100, "x2": 800, "y2": 0}}}
}
# --- boom actuator, mount, and reference selector ---
cc_boom = ControlledCylinder(Kv = 9e-7, stroke = stroke_boom, M = 20, area_a = 6.36e-3, area_b = 4.40e-3, p_a0 = 2.4e6, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 1.875, Ti_vel = 1.5, f_max = 8e4, ka_ff = 5000, k_force = 0.08, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 130, "x2": 560, "y2": 330, "rot": 0}
},
"tags": []
}
}
mnt_boom = AnchoredCylinderMount(r_base = r_boom_base, r_rod = [d_boom_rod, 0], s_offset = s_off_boom, stroke = stroke_boom, barrel_radius = 0.055) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 130, "x2": 980, "y2": 330, "rot": 270}}
}
}
sel_boom = MultibodyComponents.TrajectorySelector(nin = 3, index = 1) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 190, "x2": 220, "y2": 290}}}
}
# --- stick actuator, mount, and reference selector ---
cc_stick = ControlledCylinder(Kv = 5.5e-7, stroke = stroke_stick, M = 12, area_a = 3.85e-3, area_b = 2.59e-3, p_a0 = 2.0e6, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 7.0, Ti_vel = 1.5, f_max = 5e4, ka_ff = 4000, k_force = 0.05, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 450, "x2": 560, "y2": 650, "rot": 0}
},
"tags": []
}
}
mnt_stick = AnchoredCylinderMount(r_base = [d_stick_base, 0], r_rod = [d_stick_rod, 0], s_offset = s_off_stick, stroke = stroke_stick, barrel_radius = 0.045) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 450, "x2": 980, "y2": 650, "rot": 270}}
}
}
sel_stick = MultibodyComponents.TrajectorySelector(nin = 3, index = 2) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 510, "x2": 220, "y2": 610}}}
}
# --- bucket actuator, mount, and reference selector ---
cc_bucket = ControlledCylinder(Kv = 4e-7, stroke = stroke_bucket, M = 8, area_a = 2.83e-3, area_b = 1.86e-3, p_a0 = 4e5, p_b0 = 5e5, k_pos = 6, v_max = 0.5, k_vel = 1.1428571428571428, Ti_vel = 1.0, f_max = 3.5e4, ka_ff = 150, k_force = 1.05, Ti_force = 0.02) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 360, "y1": 770, "x2": 560, "y2": 970, "rot": 0}
},
"tags": []
}
}
mnt_bucket = AnchoredCylinderMount(r_base = [d_bucket_base, 0], r_rod = [d_bucket_rod, 0], s_offset = s_off_bucket, stroke = stroke_bucket, barrel_radius = 0.04) {
"Dyad": {
"placement": {"diagram": {"x1": 780, "y1": 770, "x2": 980, "y2": 970, "rot": 270}}
}
}
sel_bucket = MultibodyComponents.TrajectorySelector(nin = 3, index = 3) {
"Dyad": {"placement": {"diagram": {"x1": 120, "y1": 830, "x2": 220, "y2": 930}}}
}
# --- reference trajectory and soil ---
traj = MultibodyComponents.KinematicPTPBoundedJerk(waypoints = transpose(hcat([0.490, 0.061, 0.147, 0.147, 0.596], [0.154, 0.263, 0.100, 0.100, 0.154], [0.360, 0.298, 0.304, 0.044, 0.044])), nout = 3, nwp = 5, qd_max = fill(0.5, 3), qdd_max = fill(0.5, 3), qddd_max = fill(5.0, 3), phase_synchronization = true) {
"Dyad": {"placement": {"diagram": {"x1": -80, "y1": 400, "x2": 20, "y2": 500}}}
}
soil = SoilContact() {
"Dyad": {"placement": {"diagram": {"x1": 1090, "y1": 1120, "x2": 1190, "y2": 1220}}}
}
parameter main_color::Real[4] = [0.9, 0.6, 0.1, 1.0]
"Height of the boom pivot above the ground"
parameter pivot_height::Length = 1.2
"Length of the boom link"
parameter L_boom::Length = 2.0
"Length of the stick link"
parameter L_stick::Length = 1.2
"Length of the bucket link"
parameter L_bucket::Length = 0.6
"Mass of the boom link"
parameter m_boom::Mass = 300
"Mass of the stick link"
parameter m_stick::Mass = 150
"Mass of the bucket"
parameter m_bucket::Mass = 100
"Initial boom joint angle"
parameter q1_0::Angle = deg2rad(30)
"Initial stick joint angle"
parameter q2_0::Angle = deg2rad(-60)
"Initial bucket joint angle"
parameter q3_0::Angle = deg2rad(-60)
"Supply pressure"
parameter p_pump::Real = 1.6e7
"Tank pressure"
parameter p_tank::Real = 1e5
"Boom cylinder base anchor position on the chassis"
parameter r_boom_base::Length[2] = [0.3, 0.3]
"Distance from the boom pivot to the boom cylinder rod anchor"
parameter d_boom_rod::Length = 1.1
"Distance from the boom pivot to the stick cylinder base anchor"
parameter d_stick_base::Length = 1.4
"Distance from the stick joint to the stick cylinder rod anchor"
parameter d_stick_rod::Length = 0.5
"Distance from the stick joint to the bucket cylinder base anchor"
parameter d_bucket_base::Length = 0.7
"Distance from the bucket joint to the bucket cylinder rod anchor"
parameter d_bucket_rod::Length = 0.3
"Boom cylinder anchor distance at zero piston position"
parameter s_off_boom::Length = 1.10
"Stick cylinder anchor distance at zero piston position"
parameter s_off_stick::Length = 0.80
"Bucket cylinder anchor distance at zero piston position"
parameter s_off_bucket::Length = 0.34
"Stroke of the boom cylinder"
parameter stroke_boom::Length = 0.70
"Stroke of the stick cylinder"
parameter stroke_stick::Length = 0.35
"Stroke of the bucket cylinder"
parameter stroke_bucket::Length = 0.42
"Working fluid of the hydraulic circuits"
path medium::HydraulicComponents.Interfaces.AbstractMedium = HydraulicComponents.IsothermalCompressible(bulk_modulus = 1e9, let_gas = false, density = 870, viscosity = 0.03)
relations
# --- kinematic chain ---
connect(pedestal.frame_a, track_mount.frame_a, world.frame_b, cab.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": 2, "M": [], "E": -1},
{"S": 3, "M": [], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [], "E": -2}
],
"junctions": [{"x": 1390, "y": 240}, {"x": 1390, "y": 150}]
}
}
connect(track_mount.frame_b, track.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(pedestal.frame_b, j_boom.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_boom.frame_b, boom.frame_a, mnt_boom.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 330}]
}
}
connect(boom.frame_b, j_stick.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_stick.frame_b, stick.frame_a, mnt_stick.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 650}]
}
}
connect(stick.frame_b, j_bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(j_bucket.frame_b, bucket.frame_a, mnt_bucket.frame_rod) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [], "E": -1}
],
"junctions": [{"x": 1140, "y": 970}]
}
}
connect(bucket.frame_b, soil.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
# --- mesh visuals ---
connect(adapter_chassis.frame_b, vis_chassis_orange.frame_a, vis_chassis_dark.frame_a, vis_chassis_white.frame_a, vis_chassis_red.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": 2},
{"S": 2, "M": [], "E": 3},
{"S": 3, "M": [], "E": 4},
{"S": 4, "M": [], "E": 5}
],
"renderStyle": "standard"
}
}
connect(adapter_bucket.frame_b, vis_bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
# --- hydraulic supply network ---
connect(c_pump.y, pump.p) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(c_tank.y, tank.p) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
continuity(medium, pump.port.medium)
connect(cc_bucket.port_t, cc_stick.port_t, tank.port, cc_boom.port_t) {
"Dyad": {
"edges": [
{"S": -1, "M": [{"x": 630, "y": 970}], "E": 1},
{"S": 2, "M": [], "E": -1},
{"S": 3, "M": [{"x": 630, "y": -50}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [], "E": -2}
],
"junctions": [{"x": 630, "y": 650}, {"x": 630, "y": 330}]
}
}
connect(cc_bucket.port_p, cc_stick.port_p, pump.port, cc_boom.port_p) {
"Dyad": {
"edges": [
{"S": -1, "M": [{"x": 600, "y": 1020}, {"x": 430, "y": 1020}], "E": 1},
{"S": 2, "M": [{"x": 430, "y": 700}], "E": -1},
{"S": 3, "M": [{"x": 600, "y": -50}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 430, "y": 380}], "E": -2}
],
"junctions": [{"x": 600, "y": 700}, {"x": 600, "y": 380}]
}
}
connect(sel_boom.q, traj.q, sel_bucket.q, sel_stick.q) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 40, "y": 205}], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": 3, "M": [{"x": 40, "y": 845}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 80, "y": 525}, {"x": 80, "y": 520}], "E": -2}
],
"junctions": [{"x": 40, "y": 415}, {"x": 40, "y": 520}]
}
}
connect(traj.qd, sel_boom.qd, sel_bucket.qd, sel_stick.qd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 50, "y": 225}], "E": 2},
{"S": 3, "M": [{"x": 50, "y": 865}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 85, "y": 545}, {"x": 85, "y": 540}], "E": -2}
],
"junctions": [{"x": 50, "y": 438}, {"x": 50, "y": 540}]
}
}
connect(traj.qdd, sel_boom.qdd, sel_bucket.qdd, sel_stick.qdd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 60, "y": 252}], "E": 2},
{"S": 3, "M": [{"x": 60, "y": 892}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 90, "y": 572}, {"x": 90, "y": 570}], "E": -2}
],
"junctions": [{"x": 60, "y": 464}, {"x": 60, "y": 570}]
}
}
connect(traj.qddd, sel_boom.qddd, sel_bucket.qddd, sel_stick.qddd) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": 70.5, "y": 484}, {"x": 70.5, "y": 273}], "E": 2},
{"S": 3, "M": [{"x": 70, "y": 912}], "E": -2},
{"S": -2, "M": [], "E": -1},
{"S": 4, "M": [{"x": 95, "y": 592}, {"x": 95, "y": 590}], "E": -2}
],
"junctions": [{"x": 70, "y": 484}, {"x": 70, "y": 590}]
}
}
connect(cc_boom.flange, mnt_boom.flange) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 655, "y": 230}, {"x": 655, "y": 230.00000000000003}],
"E": 2
}
]
}
}
connect(cc_stick.flange, mnt_stick.flange) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(cc_bucket.flange, mnt_bucket.flange) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(mnt_stick.frame_rod, mnt_bucket.frame_base) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(mnt_boom.frame_rod, mnt_stick.frame_base) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(world.frame_b, mnt_boom.frame_base) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 1390, "y": 110}, {"x": 880, "y": 110}], "E": 2}]
}
}
connect(adapter_chassis.frame_a, track_mount.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 1340, "y": 450}, {"x": 1340, "y": 300}], "E": 2}]
}
}
connect(adapter_bucket.frame_a, bucket.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}]}}
connect(sel_boom.s, cc_boom.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 205}, {"x": 290, "y": 168}], "E": 2}]}
}
connect(sel_boom.v, cc_boom.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 225}, {"x": 290, "y": 230}], "E": 2}]}
}
connect(sel_boom.a, cc_boom.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 245}, {"x": 290, "y": 280}], "E": 2}]}
}
connect(sel_stick.s, cc_stick.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 525}, {"x": 290, "y": 488}], "E": 2}]}
}
connect(sel_stick.v, cc_stick.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 545}, {"x": 290, "y": 550}], "E": 2}]}
}
connect(sel_stick.a, cc_stick.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 565}, {"x": 290, "y": 600}], "E": 2}]}
}
connect(sel_bucket.s, cc_bucket.s_ref) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 845}, {"x": 290, "y": 808}], "E": 2}]}
}
connect(sel_bucket.v, cc_bucket.v_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 865}, {"x": 290, "y": 870}], "E": 2}]}
}
connect(sel_bucket.a, cc_bucket.a_ff) {
"Dyad": {"edges": [{"S": 1, "M": [{"x": 290, "y": 885}, {"x": 290, "y": 920}], "E": 2}]}
}
metadata {"Dyad": {"tests": {"case1": {"stop": 13}}}}
endTest Cases
Test Case case1
Related
Examples
Experiments
Analyses