| name | forces |
| description | Apply custom forces/torques to rigid bodies via accumulators, and impose prescribed motion or force between two bodies using ChLinkMotor classes. Covers axis-aware force/torque application and paired internal reactions. |
| compatibility | pychrono >= 8.0 |
| metadata | {"domain":"mbs"} |
Skill: MBS Custom Forces and Torques (Accumulators)
Purpose
Apply time-varying or conditional loads directly to rigid bodies without losing track of axis direction, frame choice, or reaction pairs. The central rule is: determine the physical load axis first, then express both the measured state and the applied load in that same axis and frame.
For quaternion/component access, see ../quaternions/SKILL.md. For body-axis conventions in planar examples, see ../body_creation/SKILL.md.
When to Use
- custom torques or forces are computed inside the simulation loop
- damping, friction, control effort, or spring-like loads are not represented by a dedicated Chrono link
- equal-and-opposite internal loads must be applied to multiple bodies
Core Workflow
Rule 1: AddAccumulator -> EmptyAccumulator -> Accumulate*
idx = body.AddAccumulator()
body.EmptyAccumulator(idx)
body.AccumulateTorque(idx, torque_vec, local)
body.AccumulateForce(idx, force_vec, appl_point, local)
idx is always the first argument. Never hardcode 0; store the return value from AddAccumulator().
Rule 2: Each body needs its own accumulator index
idx_a = body_a.AddAccumulator()
idx_b = body_b.AddAccumulator()
body_a.EmptyAccumulator(idx_a)
body_b.EmptyAccumulator(idx_b)
body_a.AccumulateTorque(idx_a, torque_a, False)
body_b.AccumulateTorque(idx_b, torque_b, False)
Part 1: Axis Consistency
Rule 3: Do not hardcode .z or (0, 0, T) unless the physical axis is actually Z
Before reading angular rate or building a torque vector, identify the physical rotation axis:
- revolute hinge axis
- motor axis
- guide axis normal/tangent implied by the mechanism
- body-local axis if
local=True
Then keep all of these aligned to the same axis:
- measured angular velocity component
- relative angular velocity used in damping/control
- torque vector direction
Rule 4: World-frame loads use axis_hat directly
When local=False, express the load in world coordinates:
axis_hat = chrono.ChVector3d(ax, ay, az)
torque_mag = gain * rel_rate
torque_vec = chrono.ChVector3d(
torque_mag * axis_hat.x,
torque_mag * axis_hat.y,
torque_mag * axis_hat.z,
)
body.AccumulateTorque(idx, torque_vec, False)
For translational loads along a guide axis:
guide_hat = chrono.ChVector3d(gx, gy, gz)
force_mag = -damping * relative_speed
force_vec = chrono.ChVector3d(
force_mag * guide_hat.x,
force_mag * guide_hat.y,
force_mag * guide_hat.z,
)
body.AccumulateForce(idx, force_vec, application_point_world, False)
Rule 5: Local-frame loads must be expressed in body-local axes
When local=True, the vector is interpreted in body-local coordinates. That means the chosen axis must already be represented in the body's local basis.
torque_local = chrono.ChVector3d(0, torque_mag, 0)
body.AccumulateTorque(idx, torque_local, True)
Do not mix a world-axis interpretation with local=True.
Rule 6: Read state on the same physical axis you actuate
If damping is about a hinge axis, compute the relative rate on that same hinge axis. Examples:
euler_a = body_a.GetRot().GetCardanAnglesXYZ()
euler_b = body_b.GetRot().GetCardanAnglesXYZ()
rel_rate = body_a.GetAngVelParent().z - body_b.GetAngVelParent().z
w_a = body_a.GetAngVelParent()
w_b = body_b.GetAngVelParent()
rel_rate = (
(w_a.x - w_b.x) * axis_hat.x +
(w_a.y - w_b.y) * axis_hat.y +
(w_a.z - w_b.z) * axis_hat.z
)
The axis comes from the mechanism. Do not default to .z just because many demos are planar.
Part 2: Internal Loads Must Balance
Rule 7: Internal damping/friction/coupling loads act in equal-and-opposite pairs
If a load models the interaction between two bodies, apply both reactions:
torque_mag = damping * rel_rate
torque_vec = chrono.ChVector3d(
torque_mag * axis_hat.x,
torque_mag * axis_hat.y,
torque_mag * axis_hat.z,
)
body_a.AccumulateTorque(idx_a, torque_vec, False)
body_b.AccumulateTorque(
idx_b,
chrono.ChVector3d(-torque_vec.x, -torque_vec.y, -torque_vec.z),
False,
)
This includes:
- rotational damping between two links
- coupling torques between connected bodies
- internal force pairs when no Chrono link is carrying that reaction for you
If the load is truly external, such as gravity compensation or a user-applied actuator on one body, a single-body application may be correct. Decide from the physics, not the code pattern.
Rule 8: Sliding loads must align with the actual guide axis
For slider friction, spring, or control force, apply the load along the guide axis, not along a guessed global axis. X is only a common example.
guide_hat = chrono.ChVector3d(gx, gy, gz)
force_mag = -(friction_mag + spring_mag)
force_vec = chrono.ChVector3d(
force_mag * guide_hat.x,
force_mag * guide_hat.y,
force_mag * guide_hat.z,
)
slider.AccumulateForce(idx_slider, force_vec, slider.GetPos(), False)
Part 3: Common Errors
Exact signatures
body.AccumulateTorque(torque_vec, False, idx)
body.AccumulateForce(force_vec, point, False, idx)
body.AccumulateTorque(idx, torque_vec, False)
body.AccumulateForce(idx, force_vec, point, False)
Frame mismatch
body.AccumulateTorque(idx, chrono.ChVector3d(0, 0, torque_mag), True)
rel_rate = body_a.GetAngVelParent().z - body_b.GetAngVelParent().z
torque_vec = chrono.ChVector3d(0, 0, torque_mag)
Skill: MBS Motors
Purpose
Impose prescribed motion or force between two bodies using ChLinkMotor* classes.
Pattern
motor = chrono.ChLinkMotorRotationTorque()
motor.Initialize(body_slave, body_master, chrono.ChFramed(pos_abs))
motor.SetTorqueFunction(some_ChFunction)
sys.Add(motor)
For linear motors, remember the same frame rule as prismatic joints: the motor frame's local +Z must align with the intended translation axis.
Motion functions
chrono.ChFunctionConst(value)
chrono.ChFunctionSine(amplitude, frequency)
chrono.ChFunctionSine(amplitude, frequency, phase)
Warning
Rheonomic motors such as ChLinkMotorRotationSpeed and ChLinkMotorRotationAngle enforce motion geometrically. Prefer torque/force motors when the system must respond dynamically to load.