--- jupyter: description: 'A circular-arc cam as a 2D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from circles tangent from the inside and trimmed to the outline, and the area, centroid and lift to check a model against.' --- # Circular-arc cam A cam turns with its shaft and moves a follower once per turn. Over the base circle the follower rests, the flanks lift it and let it down again, and over the nose it passes its highest point. A circular-arc cam makes all of these arcs, each tangent to the next. Model the cam from the drawing in planex2d or in any CAD system, then check its area, centroid and lift against the values at the end of the page. A solution in planex2d follows. ![The circular-arc cam exercise: a dimensioned drawing of a cam with a base circle, a nose and two flanks of large radius](../_static/gallery/circular-arc-cam.png){.result} ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## The circles The cam is drawn as four circles, the base circle around the origin, the nose to the right of it, and two flank circles of radius 50 that enclose both. The `tangent=` helper places a circle tangent to two others from the outside, as the flanks of the [chain link plate](chain-plate.md) are. A cam's flank touches the base and the nose from the inside, so the flank circles are placed roughly, one below and one above the cam, and the dimensions in the next section make them tangent. The bore goes around the base circle's center with `concentric=`, which `auto_constraint=True` keeps as a constraint (Figure 1). ```python cam = px.Sketch(auto_constraint=True) cam.Circle(20, id="base") cam.Circle(8, center=(30, 0), id="nose") cam.Circle(50, center=(0, -30), id="upper_flank") # its center below the cam, its arc over the top cam.Circle(50, center=(0, 30), id="lower_flank") cam.Circle(20 / 2, concentric="base", id="bore"); ``` ```python nbsphinx="hide-input" circles = ("base", "nose", "upper_flank", "lower_flank") Row((cam, "The base, the nose, the bore and the two flank circles, placed roughly", {"fill": False, "marks": [cam.Location(c + ".center") for c in circles], "rotate": 90}), caption="The circles, their centers marked") ``` ## The dimensions The base circle's center is the origin, and the nose's center lies on the horizontal axis, 30 away. Both flanks are tangent to the base and to the nose. From where the rough circles lie, the solver finds the tangency from the inside. The second flank is equal to the first, so the sketch carries the flank radius once. With the radii and the bore, nothing in the sketch is left free (Figure 2): ```python cam.Constrain("base.center", "origin", px.Coincident) cam.Constrain("nose.center", "H", px.Coincident) cam.Constrain("base.center", "nose.center", px.Distance, 30, id="nose_at") cam.Constrain("base", px.Radius, 20, id="base_r") cam.Constrain("nose", px.Radius, 8, id="nose_r") cam.Constrain("upper_flank", px.Radius, 50, id="flank_r") cam.Constrain("lower_flank", "upper_flank", px.Equal) cam.Constrain("upper_flank", "base", px.Tangent) cam.Constrain("upper_flank", "nose", px.Tangent) cam.Constrain("lower_flank", "base", px.Tangent) cam.Constrain("lower_flank", "nose", px.Tangent) cam.Constrain("bore", px.Diameter, 20, id="bore_d") print("status", cam.status, "| degrees of freedom", cam.dof) print("upper flank center", tuple(round(v, 3) for v in cam.Location("upper_flank.center"))) ``` ```python nbsphinx="hide-input" Row((cam, "Every dimension and tie of the sketch", {"constraints": True}), caption="The fully constrained sketch", height=60) ``` ## Trimming Each circle is cut where it touches the others, and `Trim` turns one piece of it into construction. These are the base's piece toward the nose, the nose's piece toward the base, and the long piece of each flank. The two flank circles also cross each other beyond the nose. The second argument of `Trim` names what cuts, so a flank cut only where it meets the base and the nose has one long piece, which goes in one trim (Figure 3). ```python cam.Trim(("base", (20, 0))) # the piece of the base nearest to (20, 0), toward the nose, becomes construction cam.Trim(("nose", (22, 0))) cam.Trim(("upper_flank", (0, -80)), ["base", "nose"]) # cut only where it meets the base and the nose cam.Trim(("lower_flank", (0, 80)), ["base", "nose"]); ``` ```python nbsphinx="hide-input" Row((cam, "The outline, the trimmed pieces dashed", {"fill": False, "rotate": 90}), caption="The trimmed outline") ``` ## The checks These are the area, the centroid and the lift, with the base circle's center at the origin, to compare with your own model. The lift, the follower's stroke, is the cam's largest distance from the center, 30 + 8, less the base circle's radius, 20. The cam is symmetric about the horizontal axis, so its centroid lies on it (Figure 4): ```python face = cam.Face() c = face.center far = max(math.hypot(p[0], p[1]) for line in face.Polylines(1e-5) for p in line) print("area", round(face.mass, 2), "mm²") print("centroid", round(c[0], 2), "toward the nose, off the axis by", round(abs(c[1]), 6)) print("lift", round(far - 20, 4), "exact", 30 + 8 - 20) ``` ```python nbsphinx="hide-input" Row((face, "The cam, its centroid marked", {"marks": [c[:2]]}), caption="The cam") ``` The cam as DXF in millimeters, for another CAD system or a laser cutter: ```python cam.WriteDXF("circular_arc_cam.dxf", unit=px.mm, scale=1) # scale=1: the numbers as they are ``` The cam as DXF: [circular_arc_cam.dxf](circular_arc_cam.dxf) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import SheetFigure, View from gallery import save_figure view = View([(cam, {"nose_at": -28, "base_r": (225, "inside"), "nose_r": (45, "outside"), "flank_r": (60, "inside", 2), "bore_d": 135}, {"bore": "lines", "nose": "cross"})], axes=[((-25, 0), (43, 0), (0, 0))]) drawing = SheetFigure([view], margin=15, notes=("planex2d.org", "2D exercise 07")) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "circular-arc-cam", dpi=200) plt.close(drawing) ```