Skip to main content

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.

Number of downloads: PyPI Downloads

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=±1 selects 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 (-π, π].
  • inertia is 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)

Source distribution for fourbarhinge 0.1.1
File Size Uploaded
fourbarhinge-0.1.1.tar.gz 56.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fourbarhinge 0.1.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page