Skip to content
LIBRARY
PlanarMechanics.examples.excavator.Excavator.md

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:

NameDescriptionUnitsDefault value
main_color[0.9, 0.6, 0.1, 1.0]
pivot_heightHeight of the boom pivot above the groundm1.2
L_boomLength of the boom linkm2.0
L_stickLength of the stick linkm1.2
L_bucketLength of the bucket linkm0.6
m_boomMass of the boom linkkg300
m_stickMass of the stick linkkg150
m_bucketMass of the bucketkg100
q1_0Initial boom joint angleraddeg2rad(30)
q2_0Initial stick joint angleraddeg2rad(-60)
q3_0Initial bucket joint angleraddeg2rad(-60)
p_pumpSupply pressure1.6e7
p_tankTank pressure1e5
r_boom_baseBoom cylinder base anchor position on the chassism[0.3, 0.3]
d_boom_rodDistance from the boom pivot to the boom cylinder rod anchorm1.1
d_stick_baseDistance from the boom pivot to the stick cylinder base anchorm1.4
d_stick_rodDistance from the stick joint to the stick cylinder rod anchorm0.5
d_bucket_baseDistance from the stick joint to the bucket cylinder base anchorm0.7
d_bucket_rodDistance from the bucket joint to the bucket cylinder rod anchorm0.3
s_off_boomBoom cylinder anchor distance at zero piston positionm1.10
s_off_stickStick cylinder anchor distance at zero piston positionm0.80
s_off_bucketBucket cylinder anchor distance at zero piston positionm0.34
stroke_boomStroke of the boom cylinderm0.70
stroke_stickStroke of the stick cylinderm0.35
stroke_bucketStroke of the bucket cylinderm0.42

Behavior

Behavior of this component cannot be rendered because it includes path variables.

Source

dyad
"""
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}}}}
end
Flattened Source
dyad
"""
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}}}}
end


Test Cases

Test Case case1

  • Examples

  • Experiments

  • Analyses