--- jupyter: description: 'Directions from other edges in a planex2d sketch: parallel copies at a distance, perpendicular lines and angles to a reference. A dovetail slide and a bracket, checked against their exact areas.' --- # Parallel, perpendicular, angle A line can take its direction from another edge instead of from the pen. `parallel=` runs it parallel to the edge, and with `distance=` it becomes a parallel copy at that distance. `perpendicular=` runs it at a right angle to the edge, and `angle=(a, edge)` at the angle a to it. Together with an end on a target, this draws most machined profiles without a single computed point. ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Vertices ``` ## A dovetail slide The example is a slide, a block of 60 × 30 with a dovetail groove that is 24 wide at the surface and 10 deep, with flanks at 60° (Figure 1). The block is drawn in two steps. First the construction lines. The bottom is the reference. The top and the floor of the groove are parallel copies of it, 30 and 20 above. The line of the left flank runs at 60° to the bottom through the groove's left corner at the surface, (−12, 30). ```python s = px.QuickSketch((-30, 0)) s.Line(60, id="bottom") s.Line(-60, parallel="bottom", distance=30, id="top", construction=True) s.Line(-60, parallel="bottom", distance=20, id="floor", construction=True) s.Line(20, angle=(180 + 60, "bottom"), through=(-12, 30), id="left flank line", construction=True); ``` Then the outline, counterclockwise from the end of the bottom. The right flank runs down at 60° until it meets the floor, the groove floor runs back until it meets the left flank's line, and the left flank runs up on that line until it meets the top. ```python s.Turn(90).Line(30, id="right side") s.Turn(90).Line(18, id="right shoulder") s.Line("floor", angle=(-60, "bottom"), id="right flank") s.Line("left flank line", parallel="bottom", id="groove floor") s.Line("top", congruent="left flank line", id="left flank") s.Line((-30, 30), id="left shoulder").Close(id="left side") slide = s.Face() ``` ```python nbsphinx="hide-input" Row((s, "The sketch, its construction lines dashed", {"fill": False}), (slide, "The slide"), caption="A dovetail slide from parallel copies and angles") ``` The area is the block minus the groove, a trapezoid 24 wide at the top and 10 deep: ```python print("area", slide.mass, "exact", 60 * 30 - 10 * (24 + 10 / math.tan(math.radians(60)))) ``` ## A bracket with a constant wall The example is a triangular bracket, 10 long, with a wall 0.8 thick all around (Figure 2). Its outer contour is a ramp at 35° from the corner to the wall, the wall down to the ground, and the ground back. The ground and the wall are construction lines. ```python t = 0.8 b = px.QuickSketch() b.Move((-1, 0)).Line((12, 0), id="ground", construction=True) b.Move((10, -1)).Line((10, 8), id="wall", construction=True) b.Move((0, 0)).Line("wall", angle=35, id="ramp") b.Line("ground", congruent="wall", id="back") b.Close(id="base"); ``` The inner contour is made of parallel copies of the three sides, each at the distance t inward, so the wall thickness is a single number. Two more lines join the copies where they meet. Last, the axis of a drain hole runs perpendicular to the ramp, from its midpoint down to the ground. ```python b.Move((0, 0), dir=(1, 0)).Line(14, parallel="ramp", distance=-t, id="ramp in", construction=True) b.Move((0, 0), dir=(1, 0)).Line(12, parallel="ground", distance=t, id="ground in", construction=True) b.Move((0, 0), dir=(0, 1)).Line(9, parallel="wall", distance=t, id="wall in", construction=True) b.Line("ground in", "wall in", congruent="ramp in") b.Line("ground in", congruent="wall in") b.Close() b.Move("ramp.mid") b.Line("ground", perpendicular="ramp", id="drain", construction=True) bracket = b.Face() ``` ```python nbsphinx="hide-input" Row((b, "The sketch: offsets and the drain axis", {"fill": False}), (bracket, "The bracket"), caption="A bracket whose inner contour is three parallel copies") ``` The inner triangle is the outer one shrunk toward the center of its incircle, so the area follows from the inradius. The drain axis, from the ramp's midpoint to the ground, is a/2 · tan β / cos β long, with a = 10 and β = 35°: ```python beta = math.radians(35) height = 10 * math.tan(beta) outer = 10 * height / 2 inradius = outer / ((10 + height + math.hypot(10, height)) / 2) print("area", bracket.mass, "exact", outer * (1 - ((inradius - t) / inradius) ** 2)) print("drain", b.Edge("drain").mass, "exact", 5 * math.tan(beta) / math.cos(beta)) ``` For a wall of constant thickness around a closed outline, `Offset` does the same in one call. See [Offsets](offsets.md) for details.