--- jupyter: description: 'A planex2d constraint sketch as a kinematic solver: a four-bar linkage driven through a full turn, reference dimensions as outputs, positions of any point, checked against the closed form.' --- # Mechanisms: the sketch as a solver A linkage can be modeled as a sketch. The links are lines of fixed length, the joints are shared points, and the input is an angle. `Update` turns the input and the solver finds the rest, moving in steps so that the linkage stays in the assembly it was drawn in. `Location(ref)` reads any solved point, `Geometry(id)` reads an element, and a reference dimension measures an output. ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## A four-bar linkage The example is a four-bar linkage: a crank of 10 turns about a fixed pivot, and a coupler of 35 joins it to a rocker of 30 on a second pivot 40 away. The crank angle `phi` is the input. `span`, the distance from the coupler's far end to the crank's pivot, is a reference dimension that measures and drives nothing. Driven through a full turn in steps of 10°, the middle of the coupler traces the coupler curve. ```python sk = px.Sketch() sk.Move((0, 0), px.Fix).Line(10, 3, id="crank").Line(32, 10, id="coupler") sk.Line(-2, -13, id="rocker") sk.Move("crank.start", px.Coincident).Line(40, 0, id="ground", construction=True) sk.Constrain("ground.end", px.Fix) sk.Constrain("rocker.end", "ground.end", px.Coincident) sk.Constrain("crank", px.Distance, 10, id="a") sk.Constrain("coupler", px.Distance, 35, id="b") sk.Constrain("rocker", px.Distance, 30, id="c") sk.Constrain("ground", "crank", px.Angle, 20, id="phi") sk.Constrain("coupler.end", "crank.start", px.Distance, id="span", reference=True) print(sk.status, sk.dof, round(sk.condition, 2)) path, spans = [], [] for phi in range(20, 380, 10): sk.Update({"phi": phi}) path.append(sk.Location("coupler.mid")) spans.append(sk["span"]) ``` For the check, the crank's end B lies at angle φ and the rocker's pivot D at (40, 0). The joint C lies on the circles of radius 35 about B and 30 about D, on the side the linkage was drawn in, so the span |C| has a closed form. The solver agrees with it at every angle. ```python def joint(phi, a=10.0, b=35.0, c=30.0, d=40.0): bx, by = a * math.cos(math.radians(phi)), a * math.sin(math.radians(phi)) e = math.hypot(d - bx, -by) along = (b**2 - c**2 + e**2) / (2 * e) up = math.sqrt(b**2 - along**2) ux, uy = (d - bx) / e, -by / e return bx + along * ux - up * uy, by + along * uy + up * ux angles = range(20, 380, 10) worst = max(abs(s - math.hypot(*joint(phi))) for phi, s in zip(angles, spans)) print("largest difference to the closed form over a full turn:", worst) ``` ```python nbsphinx="hide-input" sk.Update({"phi": 20}) curve = px.FromPolyline([p[:2] for p in path], closed=True) Row((sk, "The four-bar at phi 20", {"constraints": True}), (curve, "The coupler curve of the middle", {"fill": False, "dashed": [px.Line((0, 0), (40, 0))], "marks": [(0, 0), (40, 0)]}), caption="A four-bar linkage as a constraint sketch") ``` ## Bigger linkages The same approach works for linkages of any size. [Jansen's leg](../../examples/jansen-linkage.md) is one sketch turned by `Update`, and the [excavator arm](../../examples/excavator-arm.md) and the [hood hinge](../../examples/hood-hinge.md) solve real mechanisms with reference dimensions as their outputs.