Skip to content
LIBRARY
PlanarMechanics.examples.trebuchet.TrebuchetDetachable.md

PlanarMechanics.examples.trebuchet.TrebuchetDetachable

A planar trebuchet that releases its projectile through a gated compliant mount.

The machine stands on two ideal (no-slip) rolling wheels so the whole frame can recoil horizontally as it fires. A heavy counterweight hangs from a hinge on the short side of the throwing arm; the long side of the arm whips a sling, modeled with the Cable component (an open chain of n rigid links). The projectile is a Body joined to the free end of the sling through a DetachableMount.

Unlike the structural-release variant, the projectile stays connected through the whole simulation. The DetachableMount is a stiff (but compliant) spring-damper whose force and torque are scaled by its attached input. That input is driven symbolically: the mount holds the projectile while the throwing arm is below the release angle, and once the arm first reaches the release angle the mount force drops to zero and the projectile flies free. The release is latched by the monotone released state so it is one-way – the arm keeps swinging and crosses the release angle again after the throw, but the mount never re-engages. No structural change or recompilation is needed; a solver event at pivot.phi = release_angle keeps the integrator from stepping over the discontinuity.

A viscous damper on the rear wheel bearing (d_bearing) controls how much the trebuchet rolls during firing; varying it across a field of trebuchets staggers their releases. color and z_position tint and offset the whole machine so several can share one scene.

Usage

MultibodyComponents.PlanarMechanics.examples.trebuchet.TrebuchetDetachable(rw=0.3, wheelbase=1.4, post_dx=0.7, post_h=4.7, L_long=3.0, L_short=1.0, L_hang=0.8, l_sling=2.0, sling_dir=[0, -1], d_sling=1, d_bearing=5, color=MultibodyComponents.world_default_body_color(), z_position=0, c_sling=0.2, release_angle=0.8, m_chassis=80, m_arm=20, m_cw=300, m_sling=2, m_proj=15, I_cw=5, I_proj=0.5, I_arm=m_arm * L_long ^ 2 / 12, I_chassis=m_chassis * wheelbase ^ 2 / 12, phi0=-1.0)

Parameters:

NameDescriptionUnitsDefault value
rwWheel radiusm0.3
wheelbaseDistance between the two wheel axlesm1.4
post_dxHorizontal offset of the pivot from the rear wheelm0.7
post_hHeight of the pivot above the wheel axlesm4.7
L_longLength of the long (throwing) arm from the fulcrum to the sling attachmentm3.0
L_shortLength of the short arm from the fulcrum to the counterweight hingem1.0
L_hangLength the counterweight hangs below its hingem0.8
l_slingTotal unstretched length of the slingm2.0
sling_dirHanging direction of the straight sling, resolved in the arm-tip frame[0, -1]
d_slingViscous damping of each sling joint (slight, for a more realistic sling)N.m.s/rad1
d_bearingViscous damping of the rear wheel bearingN.m.s/rad5
colorRGBA color of the trebuchet bodies in animationsMultibodyCo...ody_color()
z_positionz-position of the whole trebuchet in animations (lifts the planar geometry out of the plane)0
c_slingRotational stiffness of each sling joint (slight, for a more realistic sling)N.m/rad0.2
release_anglePivot angle at which the mount releases the projectilerad0.8
m_chassisMass of the chassis beamkg80
m_armMass of the throwing armkg20
m_cwMass of the counterweightkg300
m_slingTotal mass of the slingkg2
m_projMass of the projectilekg15
I_cwMoment of inertia of the counterweight about its center of masskg.m25
I_projMoment of inertia of the projectile about its center of masskg.m20.5
phi0Initial pivot angle: long arm down, counterweight raisedrad-1.0

Variables

NameDescriptionUnits
released

Behavior

Source

dyad
"""
A planar trebuchet that releases its projectile through a gated compliant mount.

The machine stands on two ideal (no-slip) rolling wheels so the whole frame can
recoil horizontally as it fires. A heavy counterweight hangs from a hinge on the
short side of the throwing arm; the long side of the arm whips a sling, modeled
with the `Cable` component (an open chain of `n` rigid links). The projectile is
a `Body` joined to the free end of the sling through a `DetachableMount`.

Unlike the structural-release variant, the projectile stays connected through the
whole simulation. The `DetachableMount` is a stiff (but compliant) spring-damper
whose force and torque are scaled by its `attached` input. That input is driven
symbolically: the mount holds the projectile while the throwing arm is below the
release angle, and once the arm first reaches the release angle the mount force
drops to zero and the projectile flies free. The release is latched by the
monotone `released` state so it is one-way -- the arm keeps swinging and crosses
the release angle again after the throw, but the mount never re-engages. No
structural change or recompilation is needed; a solver event at
`pivot.phi = release_angle` keeps the integrator from stepping over the
discontinuity.

A viscous damper on the rear wheel bearing (`d_bearing`) controls how much the
trebuchet rolls during firing; varying it across a field of trebuchets staggers
their releases. `color` and `z_position` tint and offset the whole machine so
several can share one scene.
"""
example component TrebuchetDetachable
  world = MultibodyComponents.PlanarMechanics.World(render_ground = false, nominal_length = 2) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  # --- chassis on two no-slip wheels (wheel -> bearing -> chassis beam) ---
  wheel_rear = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = rw, x0 = 0, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 250, "y1": 680, "x2": 350, "y2": 780, "rot": 0}
      },
      "tags": []
    }
  }
  wheel_front = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = rw, x0 = wheelbase, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 490, "y1": 680, "x2": 590, "y2": 780, "rot": 0}
      },
      "tags": []
    }
  }
  bear_rear = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = 0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 250, "y1": 580, "x2": 350, "y2": 680, "rot": 270}
      },
      "tags": []
    }
  }
  bear_front = MultibodyComponents.PlanarMechanics.Revolute(z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 490, "y1": 580, "x2": 590, "y2": 680, "rot": 270}
      },
      "tags": []
    }
  }
  chassis = MultibodyComponents.PlanarMechanics.BodyShape(r = [wheelbase, 0], m = m_chassis, I = I_chassis, radius = 0.06, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 370, "y1": 510, "x2": 470, "y2": 610, "rot": 0}
      },
      "tags": []
    }
  }
  post = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [post_dx, post_h], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 310, "y1": 380, "x2": 450, "y2": 520, "rot": 270}
      },
      "tags": []
    }
  }
  # --- throwing arm + hinged counterweight, rotating about the fulcrum ---
  pivot = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = phi0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 330, "y1": 270, "x2": 430, "y2": 370, "rot": 270}
      },
      "tags": []
    }
  }
  arm = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_long, 0], m = m_arm, I = I_arm, radius = 0.05, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 350, "y1": 210, "x2": 250, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_ext = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [-L_short, 0], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 430, "y1": 210, "x2": 530, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_hinge = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = -phi0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 540, "y1": 210, "x2": 640, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_rod = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [0, -L_hang], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 640, "y1": 270, "x2": 740, "y2": 370, "rot": 90}
      },
      "tags": []
    }
  }
  cw = MultibodyComponents.PlanarMechanics.Body(m = m_cw, I = I_cw, radius = 0.35, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 590, "y1": 380, "x2": 790, "y2": 580, "rot": 90}
      },
      "tags": []
    }
  }
  # --- sling + projectile ---
  # A free pivot connects the sling to the arm tip. The cable uses
  # `end_joints = false` so its far end is the last link's (massive) tip.
  sling_pivot = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = 0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 210, "x2": 140, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cable = MultibodyComponents.PlanarMechanics.Cable(n = 2, end_joints = false, l = l_sling, m = m_sling, dir = sling_dir, radius = 0.04, hasdamping = true, d_joint = d_sling, hasstiffness = true, c_joint = c_sling, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 100, "y1": 210, "x2": 0, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  # Compliant mount holding the projectile to the sling end; released when its
  # `attached` input drops to zero at the release angle.
  mount = MultibodyComponents.PlanarMechanics.DetachableMount(m_a = m_proj, J_a = I_proj, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 60, "y1": 120, "x2": -40, "y2": 220, "rot": 90}
      },
      "tags": []
    }
  }
  projectile = MultibodyComponents.PlanarMechanics.Body(m = m_proj, I = I_proj, radius = 0.12, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 60, "y1": 20, "x2": -40, "y2": 120, "rot": 90}
      },
      "tags": []
    }
  }
  damper = RotationalComponents.Components.Damper(d = 3) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 160, "y1": 350, "x2": 220, "y2": 410, "rot": 90}
      },
      "tags": []
    }
  }
  fixed = RotationalComponents.Components.Fixed() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 160, "y1": 430, "x2": 220, "y2": 490, "rot": 0}
      },
      "tags": []
    }
  }
  # viscous damping on the rear wheel bearing (the parameter varied across a field of trebuchets)
  bear_damper = RotationalComponents.Components.Damper(d = d_bearing) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 180, "y1": 600, "x2": 240, "y2": 660, "rot": 180}
      },
      "tags": []
    }
  }
  bear_ground = RotationalComponents.Components.Fixed() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 90, "y1": 600, "x2": 150, "y2": 660, "rot": 90}
      },
      "tags": []
    }
  }
  # --- geometry parameters ---
  "Wheel radius"
  parameter rw::Length = 0.3
  "Distance between the two wheel axles"
  parameter wheelbase::Length = 1.4
  "Horizontal offset of the pivot from the rear wheel"
  parameter post_dx::Length = 0.7
  "Height of the pivot above the wheel axles"
  parameter post_h::Length = 4.7
  "Length of the long (throwing) arm from the fulcrum to the sling attachment"
  parameter L_long::Length = 3.0
  "Length of the short arm from the fulcrum to the counterweight hinge"
  parameter L_short::Length = 1.0
  "Length the counterweight hangs below its hinge"
  parameter L_hang::Length = 0.8
  "Total unstretched length of the sling"
  parameter l_sling::Length = 2.0
  "Hanging direction of the straight sling, resolved in the arm-tip frame"
  parameter sling_dir::Real[2] = [0, -1]
  "Viscous damping of each sling joint (slight, for a more realistic sling)"
  parameter d_sling::RotationalDampingConstant = 1
  "Viscous damping of the rear wheel bearing"
  parameter d_bearing::RotationalDampingConstant = 5
  "RGBA color of the trebuchet bodies in animations"
  parameter color::Real[4] = MultibodyComponents.world_default_body_color()
  "z-position of the whole trebuchet in animations (lifts the planar geometry out of the plane)"
  parameter z_position::Real = 0
  "Rotational stiffness of each sling joint (slight, for a more realistic sling)"
  parameter c_sling::RotationalSpringConstant = 0.2
  "Pivot angle at which the mount releases the projectile"
  parameter release_angle::Angle = 0.8
  # --- mass parameters ---
  "Mass of the chassis beam"
  parameter m_chassis::Mass = 80
  "Mass of the throwing arm"
  parameter m_arm::Mass = 20
  "Mass of the counterweight"
  parameter m_cw::Mass = 300
  "Total mass of the sling"
  parameter m_sling::Mass = 2
  "Mass of the projectile"
  parameter m_proj::Mass = 15
  # --- inertia parameters ---
  "Moment of inertia of the counterweight about its center of mass"
  parameter I_cw::MomentOfInertia = 5
  "Moment of inertia of the projectile about its center of mass"
  parameter I_proj::MomentOfInertia = 0.5
  "Moment of inertia of the throwing arm about its center of mass (thin rod)"
  final parameter I_arm::MomentOfInertia = m_arm * L_long ^ 2 / 12
  "Moment of inertia of the chassis beam about its center of mass (thin rod)"
  final parameter I_chassis::MomentOfInertia = m_chassis * wheelbase ^ 2 / 12
  # --- initial (cocked) configuration ---
  "Initial pivot angle: long arm down, counterweight raised"
  parameter phi0::Angle = -1.0
  # Monotone release latch: accumulates only while the arm is past the release
  # angle, so it leaves zero exactly once and never returns. This makes the
  # release one-way -- without it the arm swings back through `release_angle`
  # after the throw and the stiff mount re-engages, yanking the freed projectile.
  variable released::Real
relations
  initial released = 0
  der(released) = max(pivot.phi - release_angle, 0)
  # cocked configuration: arm held down, counterweight hanging vertically, at rest
  # chassis carried by the two rolling wheels through bearings
  connect(wheel_rear.frame_a, bear_rear.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_rear.frame_b, chassis.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 300, "y": 560}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(wheel_front.frame_a, bear_front.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_front.frame_b, chassis.frame_b) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 540, "y": 560}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(chassis.frame_a, post.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [], "E": 2}],
      "junctions": [{"x": 380, "y": 500}],
      "renderStyle": "standard"
    }
  }
  connect(post.frame_b, pivot.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  # rigid lever about the fulcrum (pivot.frame_b)
  connect(pivot.frame_b, arm.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 380, "y": 260}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(pivot.frame_b, cw_ext.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 380, "y": 260}], "E": 2}],
      "junctions": [{"x": 380, "y": 280}],
      "renderStyle": "standard"
    }
  }
  connect(cw_ext.frame_b, cw_hinge.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(cw_hinge.frame_b, cw_rod.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 690, "y": 260}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(cw_rod.frame_b, cw.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  # sling pivots freely at the arm tip; projectile held to the open end by the mount
  connect(arm.frame_b, sling_pivot.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(sling_pivot.frame_b, cable.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(fixed.spline, damper.spline_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(damper.spline_a, sling_pivot.flange_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_damper.spline_a, bear_rear.flange_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_damper.spline_b, bear_ground.spline) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(cable.frame_b, mount.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(mount.frame_b, projectile.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 10.000000000000002, "y": 130}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  # symbolic, latching release: mount engaged until the arm first reaches the
  # release angle, then off for good (the latch never returns to zero)
  mount.attached = ifelse(released > 0, 0, 1)
metadata {"Dyad": {"tests": {}}}
end
Flattened Source
dyad
"""
A planar trebuchet that releases its projectile through a gated compliant mount.

The machine stands on two ideal (no-slip) rolling wheels so the whole frame can
recoil horizontally as it fires. A heavy counterweight hangs from a hinge on the
short side of the throwing arm; the long side of the arm whips a sling, modeled
with the `Cable` component (an open chain of `n` rigid links). The projectile is
a `Body` joined to the free end of the sling through a `DetachableMount`.

Unlike the structural-release variant, the projectile stays connected through the
whole simulation. The `DetachableMount` is a stiff (but compliant) spring-damper
whose force and torque are scaled by its `attached` input. That input is driven
symbolically: the mount holds the projectile while the throwing arm is below the
release angle, and once the arm first reaches the release angle the mount force
drops to zero and the projectile flies free. The release is latched by the
monotone `released` state so it is one-way -- the arm keeps swinging and crosses
the release angle again after the throw, but the mount never re-engages. No
structural change or recompilation is needed; a solver event at
`pivot.phi = release_angle` keeps the integrator from stepping over the
discontinuity.

A viscous damper on the rear wheel bearing (`d_bearing`) controls how much the
trebuchet rolls during firing; varying it across a field of trebuchets staggers
their releases. `color` and `z_position` tint and offset the whole machine so
several can share one scene.
"""
example component TrebuchetDetachable
  world = MultibodyComponents.PlanarMechanics.World(render_ground = false, nominal_length = 2) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  # --- chassis on two no-slip wheels (wheel -> bearing -> chassis beam) ---
  wheel_rear = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = rw, x0 = 0, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 250, "y1": 680, "x2": 350, "y2": 780, "rot": 0}
      },
      "tags": []
    }
  }
  wheel_front = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = rw, x0 = wheelbase, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 490, "y1": 680, "x2": 590, "y2": 780, "rot": 0}
      },
      "tags": []
    }
  }
  bear_rear = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = 0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 250, "y1": 580, "x2": 350, "y2": 680, "rot": 270}
      },
      "tags": []
    }
  }
  bear_front = MultibodyComponents.PlanarMechanics.Revolute(z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 490, "y1": 580, "x2": 590, "y2": 680, "rot": 270}
      },
      "tags": []
    }
  }
  chassis = MultibodyComponents.PlanarMechanics.BodyShape(r = [wheelbase, 0], m = m_chassis, I = I_chassis, radius = 0.06, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 370, "y1": 510, "x2": 470, "y2": 610, "rot": 0}
      },
      "tags": []
    }
  }
  post = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [post_dx, post_h], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 310, "y1": 380, "x2": 450, "y2": 520, "rot": 270}
      },
      "tags": []
    }
  }
  # --- throwing arm + hinged counterweight, rotating about the fulcrum ---
  pivot = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = phi0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 330, "y1": 270, "x2": 430, "y2": 370, "rot": 270}
      },
      "tags": []
    }
  }
  arm = MultibodyComponents.PlanarMechanics.BodyShape(r = [L_long, 0], m = m_arm, I = I_arm, radius = 0.05, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 350, "y1": 210, "x2": 250, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_ext = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [-L_short, 0], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 430, "y1": 210, "x2": 530, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_hinge = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = -phi0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 540, "y1": 210, "x2": 640, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cw_rod = MultibodyComponents.PlanarMechanics.FixedTranslation(r = [0, -L_hang], z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 640, "y1": 270, "x2": 740, "y2": 370, "rot": 90}
      },
      "tags": []
    }
  }
  cw = MultibodyComponents.PlanarMechanics.Body(m = m_cw, I = I_cw, radius = 0.35, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 590, "y1": 380, "x2": 790, "y2": 580, "rot": 90}
      },
      "tags": []
    }
  }
  # --- sling + projectile ---
  # A free pivot connects the sling to the arm tip. The cable uses
  # `end_joints = false` so its far end is the last link's (massive) tip.
  sling_pivot = MultibodyComponents.PlanarMechanics.Revolute(phi(initial = 0), w(initial = 0), z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 240, "y1": 210, "x2": 140, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  cable = MultibodyComponents.PlanarMechanics.Cable(n = 2, end_joints = false, l = l_sling, m = m_sling, dir = sling_dir, radius = 0.04, hasdamping = true, d_joint = d_sling, hasstiffness = true, c_joint = c_sling, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 100, "y1": 210, "x2": 0, "y2": 310, "rot": 0}
      },
      "tags": []
    }
  }
  # Compliant mount holding the projectile to the sling end; released when its
  # `attached` input drops to zero at the release angle.
  mount = MultibodyComponents.PlanarMechanics.DetachableMount(m_a = m_proj, J_a = I_proj, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 60, "y1": 120, "x2": -40, "y2": 220, "rot": 90}
      },
      "tags": []
    }
  }
  projectile = MultibodyComponents.PlanarMechanics.Body(m = m_proj, I = I_proj, radius = 0.12, color = color, z_position = z_position) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 60, "y1": 20, "x2": -40, "y2": 120, "rot": 90}
      },
      "tags": []
    }
  }
  damper = RotationalComponents.Components.Damper(d = 3) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 160, "y1": 350, "x2": 220, "y2": 410, "rot": 90}
      },
      "tags": []
    }
  }
  fixed = RotationalComponents.Components.Fixed() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 160, "y1": 430, "x2": 220, "y2": 490, "rot": 0}
      },
      "tags": []
    }
  }
  # viscous damping on the rear wheel bearing (the parameter varied across a field of trebuchets)
  bear_damper = RotationalComponents.Components.Damper(d = d_bearing) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 180, "y1": 600, "x2": 240, "y2": 660, "rot": 180}
      },
      "tags": []
    }
  }
  bear_ground = RotationalComponents.Components.Fixed() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 90, "y1": 600, "x2": 150, "y2": 660, "rot": 90}
      },
      "tags": []
    }
  }
  # --- geometry parameters ---
  "Wheel radius"
  parameter rw::Length = 0.3
  "Distance between the two wheel axles"
  parameter wheelbase::Length = 1.4
  "Horizontal offset of the pivot from the rear wheel"
  parameter post_dx::Length = 0.7
  "Height of the pivot above the wheel axles"
  parameter post_h::Length = 4.7
  "Length of the long (throwing) arm from the fulcrum to the sling attachment"
  parameter L_long::Length = 3.0
  "Length of the short arm from the fulcrum to the counterweight hinge"
  parameter L_short::Length = 1.0
  "Length the counterweight hangs below its hinge"
  parameter L_hang::Length = 0.8
  "Total unstretched length of the sling"
  parameter l_sling::Length = 2.0
  "Hanging direction of the straight sling, resolved in the arm-tip frame"
  parameter sling_dir::Real[2] = [0, -1]
  "Viscous damping of each sling joint (slight, for a more realistic sling)"
  parameter d_sling::RotationalDampingConstant = 1
  "Viscous damping of the rear wheel bearing"
  parameter d_bearing::RotationalDampingConstant = 5
  "RGBA color of the trebuchet bodies in animations"
  parameter color::Real[4] = MultibodyComponents.world_default_body_color()
  "z-position of the whole trebuchet in animations (lifts the planar geometry out of the plane)"
  parameter z_position::Real = 0
  "Rotational stiffness of each sling joint (slight, for a more realistic sling)"
  parameter c_sling::RotationalSpringConstant = 0.2
  "Pivot angle at which the mount releases the projectile"
  parameter release_angle::Angle = 0.8
  # --- mass parameters ---
  "Mass of the chassis beam"
  parameter m_chassis::Mass = 80
  "Mass of the throwing arm"
  parameter m_arm::Mass = 20
  "Mass of the counterweight"
  parameter m_cw::Mass = 300
  "Total mass of the sling"
  parameter m_sling::Mass = 2
  "Mass of the projectile"
  parameter m_proj::Mass = 15
  # --- inertia parameters ---
  "Moment of inertia of the counterweight about its center of mass"
  parameter I_cw::MomentOfInertia = 5
  "Moment of inertia of the projectile about its center of mass"
  parameter I_proj::MomentOfInertia = 0.5
  "Moment of inertia of the throwing arm about its center of mass (thin rod)"
  final parameter I_arm::MomentOfInertia = m_arm * L_long ^ 2 / 12
  "Moment of inertia of the chassis beam about its center of mass (thin rod)"
  final parameter I_chassis::MomentOfInertia = m_chassis * wheelbase ^ 2 / 12
  # --- initial (cocked) configuration ---
  "Initial pivot angle: long arm down, counterweight raised"
  parameter phi0::Angle = -1.0
  # Monotone release latch: accumulates only while the arm is past the release
  # angle, so it leaves zero exactly once and never returns. This makes the
  # release one-way -- without it the arm swings back through `release_angle`
  # after the throw and the stiff mount re-engages, yanking the freed projectile.
  variable released::Real
relations
  initial released = 0
  der(released) = max(pivot.phi - release_angle, 0)
  # cocked configuration: arm held down, counterweight hanging vertically, at rest
  # chassis carried by the two rolling wheels through bearings
  connect(wheel_rear.frame_a, bear_rear.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_rear.frame_b, chassis.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 300, "y": 560}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(wheel_front.frame_a, bear_front.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_front.frame_b, chassis.frame_b) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 540, "y": 560}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(chassis.frame_a, post.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [], "E": 2}],
      "junctions": [{"x": 380, "y": 500}],
      "renderStyle": "standard"
    }
  }
  connect(post.frame_b, pivot.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  # rigid lever about the fulcrum (pivot.frame_b)
  connect(pivot.frame_b, arm.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 380, "y": 260}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(pivot.frame_b, cw_ext.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [], "E": -1}, {"S": -1, "M": [{"x": 380, "y": 260}], "E": 2}],
      "junctions": [{"x": 380, "y": 280}],
      "renderStyle": "standard"
    }
  }
  connect(cw_ext.frame_b, cw_hinge.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(cw_hinge.frame_b, cw_rod.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 690, "y": 260}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(cw_rod.frame_b, cw.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  # sling pivots freely at the arm tip; projectile held to the open end by the mount
  connect(arm.frame_b, sling_pivot.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(sling_pivot.frame_b, cable.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(fixed.spline, damper.spline_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(damper.spline_a, sling_pivot.flange_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_damper.spline_a, bear_rear.flange_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(bear_damper.spline_b, bear_ground.spline) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(cable.frame_b, mount.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(mount.frame_b, projectile.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 10.000000000000002, "y": 130}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  # symbolic, latching release: mount engaged until the arm first reaches the
  # release angle, then off for good (the latch never returns to zero)
  mount.attached = ifelse(released > 0, 0, 1)
metadata {"Dyad": {"tests": {}}}
end


Test Cases

No test cases defined.

  • Examples

  • Experiments

  • Analyses