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.
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.
idx = body.AddAccumulator() # once before loop
body.EmptyAccumulator(idx) # every step
body.AccumulateTorque(idx, torque_vec, local) # torque
body.AccumulateForce(idx, force_vec, appl_point, local) # force
idx is always the first argument. Never hardcode 0; store the return value from AddAccumulator().
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)
.z or (0, 0, T) unless the physical axis is actually ZBefore reading angular rate or building a torque vector, identify the physical rotation axis:
local=TrueThen keep all of these aligned to the same axis:
axis_hat directlyWhen local=False, express the load in world coordinates:
axis_hat = chrono.ChVector3d(ax, ay, az) # normalized physical axis
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) # normalized guide axis
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)
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.
# Example: torque about body-local Y
torque_local = chrono.ChVector3d(0, torque_mag, 0)
body.AccumulateTorque(idx, torque_local, True)
Do not mix a world-axis interpretation with local=True.
If damping is about a hinge axis, compute the relative rate on that same hinge axis. Examples:
# Common planar case: hinge about Z
euler_a = body_a.GetRot().GetCardanAnglesXYZ()
euler_b = body_b.GetRot().GetCardanAnglesXYZ()
rel_rate = body_a.GetAngVelParent().z - body_b.GetAngVelParent().z
# Generic case: hinge about axis_hat in world frame
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.
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:
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.
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)
# WRONG
body.AccumulateTorque(torque_vec, False, idx)
body.AccumulateForce(force_vec, point, False, idx)
# CORRECT
body.AccumulateTorque(idx, torque_vec, False)
body.AccumulateForce(idx, force_vec, point, False)
# WRONG: world-axis vector passed as local load
body.AccumulateTorque(idx, chrono.ChVector3d(0, 0, torque_mag), True)
# WRONG: assumes Z-axis without checking the hinge axis
rel_rate = body_a.GetAngVelParent().z - body_b.GetAngVelParent().z
torque_vec = chrono.ChVector3d(0, 0, torque_mag)
# BETTER: derive both from the same axis_hat
Impose prescribed motion or force between two bodies using ChLinkMotor* classes.
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.
chrono.ChFunctionConst(value)
chrono.ChFunctionSine(amplitude, frequency)
chrono.ChFunctionSine(amplitude, frequency, phase)
Rheonomic motors such as ChLinkMotorRotationSpeed and ChLinkMotorRotationAngle enforce motion geometrically. Prefer torque/force motors when the system must respond dynamically to load.