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

[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 base, the nose, the bore and the two flank circles, placed roughly (rotated 90 degrees)
Figure 1: The circles, their centers marked (rotated 90 degrees)

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)
Every dimension and tie of the sketch
Figure 2: The fully constrained sketch

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 outline, the trimmed pieces dashed (rotated 90 degrees)
Figure 3: The trimmed outline (rotated 90 degrees)

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, its centroid marked
Figure 4: The cam

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