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.

[1]:
import math
import planex as px
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 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).
[3]:
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");
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):
[5]:
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")))
status full | degrees of freedom 0
upper flank center (0.6, -29.994)
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).
[7]:
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"]);
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):
[9]:
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)
area 1445.74 mm²
centroid 8.48 toward the nose, off the axis by 0.0
lift 18.0 exact 18
The cam as DXF in millimeters, for another CAD system or a laser cutter:
[11]:
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