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:
| Name | Description | Units | Default value |
|---|---|---|---|
rw | Wheel radius | m | 0.3 |
wheelbase | Distance between the two wheel axles | m | 1.4 |
post_dx | Horizontal offset of the pivot from the rear wheel | m | 0.7 |
post_h | Height of the pivot above the wheel axles | m | 4.7 |
L_long | Length of the long (throwing) arm from the fulcrum to the sling attachment | m | 3.0 |
L_short | Length of the short arm from the fulcrum to the counterweight hinge | m | 1.0 |
L_hang | Length the counterweight hangs below its hinge | m | 0.8 |
l_sling | Total unstretched length of the sling | m | 2.0 |
sling_dir | Hanging direction of the straight sling, resolved in the arm-tip frame | – | [0, -1] |
d_sling | Viscous damping of each sling joint (slight, for a more realistic sling) | N.m.s/rad | 1 |
d_bearing | Viscous damping of the rear wheel bearing | N.m.s/rad | 5 |
color | RGBA color of the trebuchet bodies in animations | – | MultibodyCo...ody_color() |
z_position | z-position of the whole trebuchet in animations (lifts the planar geometry out of the plane) | – | 0 |
c_sling | Rotational stiffness of each sling joint (slight, for a more realistic sling) | N.m/rad | 0.2 |
release_angle | Pivot angle at which the mount releases the projectile | rad | 0.8 |
m_chassis | Mass of the chassis beam | kg | 80 |
m_arm | Mass of the throwing arm | kg | 20 |
m_cw | Mass of the counterweight | kg | 300 |
m_sling | Total mass of the sling | kg | 2 |
m_proj | Mass of the projectile | kg | 15 |
I_cw | Moment of inertia of the counterweight about its center of mass | kg.m2 | 5 |
I_proj | Moment of inertia of the projectile about its center of mass | kg.m2 | 0.5 |
phi0 | Initial pivot angle: long arm down, counterweight raised | rad | -1.0 |
Variables
| Name | Description | Units |
|---|---|---|
released | – |
Behavior
Source
"""
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": {}}}
endFlattened Source
"""
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": {}}}
endTest Cases
No test cases defined.
Related
Examples
Experiments
Analyses