fourbarhinge
A Python library to analyse and design four-bar hinges: kinematics, dynamics on top of modern-robotics, joint reactions, compression/torsion spring effort curves, free-return simulation, path synthesis of the coupler point and spring placement for a target effort–travel curve. Results can be exported as A4 PDF reports.
Visit the library on GitHub https://github.com/ErnestoAvedillo/FourBarHinge and clone the repository using:
git clone git@github.com:ErnestoAvedillo/FourBarHinge.git
Features
-
Kinematics: closed-form position (both assembly branches), velocities, accelerations and any point of any bar.
-
Dynamics reduced to the single DOF: the open-chain mass matrix from modern-robotics is mapped to four-bar coordinates (
M_fb = Tᵀ·M_MR·T) and reduced with the velocity ratios Γ:M_red·θ̈ + C_red + G_red = Q_springs + Q_external + λ·Q_actuator -
Springs: compression springs between any two bars (push only) and torsion springs in the hinges.
-
Required effort: torque or force (fixed or following the bar) needed to hold or move the mechanism.
-
Joint reactions in A, B, C, D by Newton–Euler, cross-checked against the reduced equation.
-
Free-return simulation (RK4) from a released position to home, with energy balance.
-
Path synthesis: find the link lengths, pivots and coupler point so that the coupler point follows a desired travel.
-
Motion synthesis: best-fit geometry from many synchronised positions of two coupler points (guides the whole coupler, not just one point).
-
Spring synthesis: place and size compression/torsion springs to match a target effort–travel curve, with the force tangent to the path or held at a fixed bolt.
-
A4 PDF reports (matplotlib).
Structure
src/fourbarhinge/
├── models/ Data models (pydantic): bars, geometry, springs, loads
├── kinematics/ FourBarKinematics
├── dynamics/ FourBarDynamics (modern-robotics), FourBarReactions
├── simulation/ ReturnSimulation (free return to home)
├── synthesis/ CouplerPathSynthesis, CouplerMotionSynthesis,
│ EffortTravelCurve, SpringSynthesis
└── plots/ FourBarPlotter, SynthesisPlotter (A4 PDF reports)
examples/ Complete worked examples
tests/ Tests (pytest)
Installation
I recommend using uv to install the library (https://docs.astral.sh/uv/):
uv init
uv add fourbarhinge
or with pip:
pip install fourbarhinge
Conventions
B ●────────────● C
/ coupler \
input output
/ \
A ●────────────────────● D
ground
- Each bar has a local frame: origin at its first joint (A, B or D), x axis towards the other joint. Points on a bar (centre of mass, spring anchors, loads, coupler point P) are given in that frame.
- Angles are absolute with respect to the ground bar (A → D), in radians, counter-clockwise. The output bar is measured from D towards C.
branch=±1selects one of the two assembly solutions.- Units are never converted: use a consistent set. With mm–kg–s, forces
are mN, torques mN·mm, energies µJ and gravity is
(0, -9810).
Usage
Analysis
import numpy as np
from fourbarhinge import (
Bar, Point, FourBarGeometry, BarId, FourBarKinematics, FourBarDynamics,
CompressionSpring, TorsionSpring, Attachment, Position, Actuator,
FourBarReactions,
)
def bar(length, mass):
return Bar(length=length, mass=mass, inertia=mass * length**2 / 12,
center_of_mass=Point(x=length / 2, y=0.0))
geometry = FourBarGeometry(
position_ground=Point(x=0.0, y=0.0),
bar={BarId.GROUND: bar(60, 0.05), BarId.INPUT: bar(25, 0.02),
BarId.COUPLER: bar(70, 0.06), BarId.OUTPUT: bar(55, 0.04)},
)
kinematics = FourBarKinematics(geometry, theta_ground=0.0)
dynamics = FourBarDynamics(
kinematics,
compression_springs=[CompressionSpring(
free_length=60.0, constant=500.0,
a=Attachment(bar=BarId.GROUND, position=Position(length=0, angle=0)),
b=Attachment(bar=BarId.COUPLER, position=Position(length=50, angle=0)),
)],
torsion_springs=[TorsionSpring(free_angle=np.deg2rad(20), constant=5e4,
bar_a=BarId.GROUND, bar_b=BarId.INPUT)],
gravity=(0.0, -9810.0),
)
kinematics.solve_configuration(np.deg2rad(100), branch=-1)
kinematics.solve_theta_dot(0.0)
# Force perpendicular to the coupler, at its end, to hold the position
push = Actuator(bar=BarId.COUPLER, point=Point(x=70, y=0),
direction=(0, 1), local_direction=True)
force = dynamics.solve_required_effort(push, theta_input_ddot=0.0)
reactions = FourBarReactions(dynamics).solve(push).reactions
print(force, reactions["A"], reactions["B"], reactions["C"], reactions["D"])
Free return to home and PDF report
from fourbarhinge import ReturnSimulation, FourBarPlotter
trajectory = ReturnSimulation(dynamics, branch=-1).run(
np.deg2rad(150), np.deg2rad(40), dt=1e-4)
print(trajectory.stop_reason, trajectory.home_velocity)
FourBarPlotter(dynamics, branch=-1, actuator=push).build_report(
"report.pdf", trajectory)
The report contains: summary tables, mechanism geometry, effort curve and joint reactions versus the input angle, angle/velocity/acceleration during the return and the energy balance.
Path synthesis
from fourbarhinge import CouplerPathSynthesis, LinkBounds
targets = np.array([[90, 10], [92, 25], [88, 40], [78, 54],
[62, 64], [42, 70], [22, 70]], float)
result = CouplerPathSynthesis(
targets,
LinkBounds(min_length=15, max_length=120,
min_transmission_angle=np.deg2rad(30)),
ground_pivot_a=Point(x=0, y=0), # optional: fixed pivots
ground_pivot_d=Point(x=40, y=-10),
).solve(n_starts=60)
print(result.summary())
geometry = result.to_geometry(linear_density=1.6e-4, coupler_mass=0.4)
kinematics = result.to_kinematics(geometry)
At least five target points are needed for a well-determined problem; with
fewer points one of the infinite solutions is returned. Fixing the ground
pivots or the input angles (theta_inputs) reduces the unknowns.
Motion synthesis (two coupler tracks)
Give many synchronised positions of two coupler points P and Q; the best-fitting geometry is returned (least squares). The two tracks fix the coupler's position and rotation, so the problem is better posed than with a single point.
from fourbarhinge import CouplerMotionSynthesis
result = CouplerMotionSynthesis(
track_p, track_q, # (n, 2) each, same instants
LinkBounds(min_length=15, max_length=120,
min_transmission_angle=np.deg2rad(20)),
weights=None, # optional (n,): e.g. ends heavier
ground_pivot_a=Point(x=0, y=0), # optional fixed pivots
ground_pivot_d=Point(x=60, y=0),
).solve()
print(result.summary()) # also Q local and |PQ| scatter
geometry = result.to_geometry(linear_density=1.6e-4)
Each side (A–B and D–C) is first fitted separately: a coupler point whose positions lie on a circle gives a bar, with the circle centre as the fixed pivot. Pairs of sides are then refined as a complete mechanism on the P and Q position error. Assembly between poses, monotonic input and the transmission angle are checked. With noisy data and a small coupler rotation, quite different geometries can fit equally well: fix the pivots or narrow the bounds to choose one.
Effort–travel curve and spring synthesis
from fourbarhinge import (
EffortTravelCurve, SpringSynthesis, CompressionSpringDesign,
TorsionSpringDesign,
)
dynamics = FourBarDynamics(kinematics, gravity=(0, -9810))
curve = EffortTravelCurve(dynamics, result.coupler_point,
result.theta_start, result.theta_end,
branch=result.branch)
# Effort with any set of springs (fast, vectorised)
effort = curve.effort(compression_springs=[...], torsion_springs=[...])
# Or let the optimiser place and size them: each parameter is a (low, high)
# range, low == high fixes it.
springs = SpringSynthesis(
curve,
target_travel=[0, curve.travel[-1]],
target_effort=[3000, -2000],
compression_designs=[CompressionSpringDesign(
bar_a=BarId.GROUND, bar_b=BarId.OUTPUT,
a_x=(0, 40), a_y=(-15, 15), b_x=(10, 100), b_y=(-5, 5),
constant=(50, 2000), free_length=(20, 120))],
torsion_designs=[TorsionSpringDesign(
bar_a=BarId.GROUND, bar_b=BarId.INPUT,
constant=(0, 2e5), free_angle=(-np.pi, np.pi))],
min_spring_length=15,
).solve()
print(springs.summary())
The effort is the static force at the coupler point, tangent to its path:
F > 0 means it must be pushed along the travel, F < 0 means the hinge
moves by itself.
If the force is held at a fixed bolt S (a strut, cylinder or rod pinned to
the frame), pass force_origin=Point(...) (global). The force then acts
along the line S → P (F > 0 pushes P away from S) and the travel is the
stroke |SP| − |SP|₀. bar= puts P on another moving bar, and
curve.actuator(i) gives the equivalent Actuator at sample i to check
against FourBarDynamics.solve_required_effort.
curve = EffortTravelCurve(dynamics, Point(x=50, y=20), theta_start,
theta_end, force_origin=Point(x=80, y=-40))
The spring working ranges (length, force, deflection) are reported so the springs can then be designed, for example with springcalc.
Examples
uv run python examples/ejemplo_completo.py # every analysis step
uv run python examples/informe_bisagra.py # dynamic analysis PDF report
uv run python examples/sintesis_bisagra.py # path + spring synthesis
uv run python examples/sintesis_trayectoria.py # path -> geometry (4 cases, ~2 min)
uv run python examples/sintesis_movimiento.py # P and Q tracks -> geometry (4 cases)
uv run python examples/sintesis_muelles.py # effort-travel -> springs (4 cases)
PDF reports are written to output/.
Development
uv sync
make unittest # run the tests
make build # bump patch version and build sdist + wheel
make test # build and upload to TestPyPI
make pypi # build and upload to PyPI
Uploads use the pypi / testpypi repositories of ~/.pypirc.
Modelling notes
- Compression springs only push: beyond their free length their force is 0.
- Torsion spring deflection is wrapped to (-π, π].
inertiais the moment of inertia about the centre of mass.- Dead points (singular Jacobian) raise an error; non-assemblable input
angles are detected with
FourBarKinematics.get_closure_error(). - No friction is modelled.
License
MIT
Metadata
Release files for fourbarhinge 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fourbarhinge-0.1.1.tar.gz | 56.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fourbarhinge-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 107.7 kB
Release files / fourbarhinge-0.1.1.tar.gz
| Download URL | fourbarhinge-0.1.1.tar.gz |
|---|---|
| Size | 56.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25c9c484555a76dfa93edb9b702e53e8dcc7ed452e33d7cb44b4730e8b9fe211
|
|
BLAKE2b-256 checksum How to use checksums |
cacd919434c23c5604fe655e128273e55a855061d3bc6273dc917ea7a59dd09d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / fourbarhinge-0.1.1-py3-none-any.whl
| Download URL | fourbarhinge-0.1.1-py3-none-any.whl |
|---|---|
| Size | 50.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
65ebf7e03b2acfd6760d676a66bb1ee095ae0f2535085eeb97cd42bcf3f852e0
|
|
BLAKE2b-256 checksum How to use checksums |
7efab39c0ce6cf1c006d9baece888d7d413b19c3f53744742b8bebed42e1fb54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log