--- jupyter: description: 'Auto constraints in a planex2d constraint sketch: the relations a drawing shows exactly become constraints, such as tangent joints, concentric circles or level lines. Which relations are found, per sketch with auto_constraint= and for all sketches in px.config.' --- # Auto constraints With auto constraining on, a constraint sketch turns the relations that the drawing shows exactly into constraints. A chain that runs on smoothly stays tangent at its joints, and a circle drawn around the center of an arc stays concentric with it, whatever the dimensions do later. Which relations are found is a setting, per sketch with `auto_constraint=` and for all sketches in `px.config`. Auto constraints never add a dimension, so the sizes of the part still come from the dimensions. ```python import os import tempfile import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## The relations Auto constraints know eleven relations and one setting. The first four relations are on by default, since a drawing that shows them almost always means them. The other seven are off, since a line drawn level is not always meant to stay level. The setting `named_helpers` is on. | Key | Finds | Default | |---|---|---| | `coincident` | Points at one place | on | | `tangent` | A joint where a chain runs on smoothly | on | | `concentric` | Arcs and circles with one center | on | | `point_on_curve` | An end that lies on another element | on | | `horizontal`, `vertical` | Lines drawn level or plumb | off | | `perpendicular` | Lines that meet at a right angle | off | | `parallel` | Parallel lines that do not meet | off | | `equal_radius`, `equal_length` | Arcs and circles of one radius, lines of one length | off | | `collinear` | Lines on one straight line | off | | `named_helpers` | The helpers named in a drawing call, such as `parallel=` | on | A relation is found only where it holds to rounding. A line drawn a hundredth of a degree off level is not horizontal, and an arc that nearly touches a line is not tangent to it. Geometry that is close but not exact, such as a drawing read from a file with few decimals, gets its relations from `AutoConstrain` with a tolerance. See [From a drawing to a parametric sketch](from-drawing.md) for details. ## Switching relations `auto_constraint=True` finds the relations that are on, `False` finds none, and a dict switches auto constraining on and changes the relations it names. The example is a mounting plate of 80 × 40 with a rounded corner of radius 10, a slot 30 long and 10 wide, and two holes of diameter 8, drawn at its size. The same drawing is made with three settings: ```python def plate(auto): sk = px.Sketch(auto_constraint=auto) sk.Line(80, id="bottom").Line((80, 40), id="right").Line((10, 40), id="top") sk.Arc((0, 30), center=(10, 30), id="corner").Close(id="left") sk.Move((20, 15), dir=(1, 0)).Line(30, id="slot_lower").Arc(5, 180, id="slot_end") sk.Line(30, id="slot_upper").Arc(5, 180, id="slot_start") sk.Circle((65, 12), 4, id="hole_a").Circle((65, 30), 4, id="hole_b") return sk level = {"horizontal": True, "vertical": True, "equal_radius": True} for auto in (False, True, level): sk = plate(auto) print(auto, "->", sk.dof, "degrees of freedom,", {kind: len(ids) for kind, ids in sk.constraints.items()}) ``` Without auto constraints, every element is free. With `True`, the six joints of the corner and of the slot are tangent. With the dict, the level and plumb sides become horizontal and vertical, and the two holes get one radius (Figure 1). The round ends of the slot already have one radius through their tangents to the two parallel sides, so they get no `Equal` of their own: ```python print(plate(level).constraints["Equal"]) ``` ```python nbsphinx="hide-input" Row((plate(True), "auto_constraint=True", {"constraints": True}), (plate(level), "Horizontal, vertical and equal radius on", {"constraints": True}), caption="One drawing with the default relations and with three more switched on") ``` What is left free are the sizes and positions, which only dimensions give. See [Constraints and dimensions](kinds.md) for them. ## Helpers named in a call A drawing call can take its geometry from a helper, such as `parallel="base"`. The key `named_helpers` decides whether the helper also becomes its constraint. With it on, the default, the rail stays parallel to the base when the base turns. With it off, the helper only places the rail, unless a relation that is switched on finds the same thing anyway: ```python for helpers in (True, False): sk = px.Sketch(auto_constraint={"named_helpers": helpers}) sk.Line(50, id="base").Move((0, 10), dir=(1, 0)).Line(30, parallel="base", id="rail") print("named_helpers", helpers, "->", {kind: list(ids) for kind, ids in sk.constraints.items()}) ``` A key that is not one of the relations raises an error that lists the valid ones: ```python try: px.Sketch(auto_constraint={"tangency": True}) except TypeError as err: print(err) ``` ## The defaults for all sketches Two entries of `px.config` hold the defaults. `px.config.auto_constraint` is the default of the argument, `False` as long as it is not changed. `px.config.auto_constraint_options` holds the relations that `auto_constraint=True` uses, and a dict given to a sketch changes only the relations it names. Assigning a dict to it works the same way and keeps the other relations: ```python print(px.config.auto_constraint) px.config.auto_constraint_options = {"horizontal": True, "vertical": True} print(px.config.auto_constraint_options) ``` From here on, `auto_constraint=True` also finds level and plumb lines. The defaults come back by assigning them again: ```python px.config.auto_constraint_options = {"horizontal": False, "vertical": False} ``` ## Geometry from a file Geometry copied into a sketch gets the same relations as a drawing, whether it comes from a DXF file, from wires or from faces. `px.Sketch(wires, copy=True, auto_constraint=True)` copies the wires and constrains them. The plate, written to a DXF file by planex2d and read back, comes in with its six tangent joints: ```python face = plate(True).Face() with tempfile.TemporaryDirectory() as folder: path = os.path.join(folder, "plate.dxf") px.WriteDXF(face, path) copied = px.Sketch(px.FromDXF(path), copy=True, auto_constraint=True) print(copied.dof, "degrees of freedom,", {kind: len(ids) for kind, ids in copied.constraints.items()}) ``` A drawing from another program often has its coordinates rounded to a few decimals, so its tangents hold only to that rounding and are not found here. See [From a drawing to a parametric sketch](from-drawing.md) for finding them with a tolerance.