--- jupyter: description: 'Drawing a planex2d constraint sketch: constraint flags in the drawing calls, drawing helpers with and without auto_constraint, and which tangent the drawing selects.' --- # Drawing with constraints A constraint sketch draws with the same calls as a [quick sketch](../sketch/index.md): `Move`, `Turn`, `Line`, `Arc`, `Circle`, `Spline`, the profile tools and `Close`. Three things are added once constraints are involved: - flags such as `px.Horizontal` in a drawing call constrain the element while it is drawn; - the references of the drawing calls, such as `tangent=` or `until=`, can become constraints, with `auto_constraint=True`; - where a constraint has several solutions, the drawing decides which one is meant. ```python import warnings import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## Flags while drawing A flag after the numbers of a drawing call adds that constraint to the new element at once, and the sketch is solved. The example is a rectangle drawn roughly, with a fixed start, horizontal top and bottom and vertical sides (Figure 1). The solver squares it up and moves every side as little as it can. Its width and height are still free, which leaves two degrees of freedom. ```python sk = px.Sketch((0, 0), px.Fix) sk.Line(40, 1, px.Horizontal, id="bottom") sk.Line(1, 20, px.Vertical, id="right") sk.Line(-41, 2, px.Horizontal, id="top") sk.Close(px.Vertical, id="left") print(sk.status, sk.dof, "degrees of freedom") print({i: round(sk.Geometry(i)["angle"], 9) for i in ("bottom", "right", "top", "left")}) ``` ```python nbsphinx="hide-input" Row((sk, "Drawn roughly, squared up by its flags", {"constraints": True})) ``` ## Helpers, with and without auto_constraint The references of the drawing calls, such as `until=`, `tangent=`, `perpendicular=` and `center=`, place an element exactly. By default, that is all they do, and the element is afterwards as free as one drawn by eye. With `auto_constraint=True`, every reference also becomes the matching constraint. The example draws the same sketch both ways: a line dropped perpendicular onto a base, a belt tangent to a hub, and a pin at the end of the base. Then the hub grows from a radius of 1 to 1.8. Without `auto_constraint`, the belt keeps its place and the grown hub swallows it (Figure 2a). With it, the belt stays tangent, so its distance from the hub's center equals the new radius (Figure 2b). ```python def drawing(auto): sk = px.Sketch(auto_constraint=auto) sk.Move((0, 0)).Line(4, 0, px.Fix, id="base") sk.Circle(6, 3, 1, id="hub") sk.Constrain("hub.center", px.Fix) sk.Constrain("hub", px.Radius, 1, id="r") sk.Move((1, 2)).Direction(0, -1).Line("base", perpendicular="base", id="drop") sk.Move((0, 3)).Line(tangent="hub", id="belt") sk.Circle(0.4, center="base.end", id="pin") return sk plain, auto = drawing(False), drawing(True) for kind, entries in auto.constraints.items(): if kind not in ("Fix", "Radius"): print(kind, list(entries.values())) plain["r"] = auto["r"] = 1.8 for name, sk in (("helpers only", plain), ("auto_constraint", auto)): print(name, "belt", sk.Measure("hub.center", to="belt")["distance"], "from the hub's center, radius", sk["r"]) ``` ```python nbsphinx="hide-input" Row((plain, "Helpers only, hub radius 1.8", {"constraints": True}), (auto, "auto_constraint, hub radius 1.8", {"constraints": True}), caption="The same drawing with helpers only and with auto_constraint, after the hub grew") ``` With `auto_constraint`, a constraint that a reference implies but that removes no degree of freedom, because it already follows from the others, is left out with a warning. In the example, `top` is horizontal by its flag, so `parallel="base"` adds nothing. A redundant constraint given on purpose stays. See [diagnosis](diagnosis.md) for how redundant constraints are reported. ```python sk = px.Sketch(auto_constraint=True) sk.Move((0, 0)).Line(3, 0, px.Horizontal, id="base") with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") sk.Move((0, 1)).Line(2, px.Horizontal, parallel="base", id="top") for w in caught: print(w.message) ``` ## Which tangent: the drawing decides A line tangent to two circles has four solutions: above, below, and two that cross between the circles. The constraint takes the side from the drawing when it is created and keeps it. In the example, three lines are drawn roughly (Figure 3a), and each is made tangent to both circles (Figure 3b). When the left circle grows (Figure 3c), all three lines follow on their sides. They can still slide along themselves, so they are not fully constrained and stay black. ```python sk = px.Sketch() sk.Circle(0, 0, 1.0, id="left") sk.Circle(4, 0.5, 0.6, px.Fix, id="right") sk.Constrain("left.center", sk.origin, px.Coincident) sk.Constrain("left", px.Radius, 1.0, id="r") sk.Move((-0.3, 1.4)).Line((4.2, 1.3), id="upper") sk.Move((0.2, -1.3)).Line((4.1, -0.3), id="lower") sk.Move((0.1, 1.2)).Line((3.6, -0.2), id="cross") drawn = sk.Copy() for line in ("upper", "lower", "cross"): sk.Constrain(line, "left", px.Tangent) sk.Constrain(line, "right", px.Tangent) tangent = sk.Copy() sk["r"] = 1.6 for line in ("upper", "lower", "cross"): print(line, "distance from the left circle", sk.Measure(line, to="left")["distance"]) ``` ```python nbsphinx="hide-input" Row((drawn, "Drawn", {"constraints": True}), (tangent, "Tangent to both", {"constraints": True}), (sk, "Left radius 1.6: all three follow", {"constraints": True}), caption="Three tangents keep the side they were drawn on") ``` See [Constraints and dimensions](kinds.md) for every kind of constraint, and [Changing dimensions](update.md) for how the solver follows a change.