--- jupyter: description: 'A disc cam for a roller follower as a 2D CAD exercise: a dimensioned drawing with its lift table to model in planex2d or any CAD system, a solution from the pitch curve drawn from the lift law and the profile as its offset by the roller''s radius, and the lift, the roller''s contact and the area to check a model against.' --- # Disc cam A disc cam turns with its shaft and lifts a roller follower once per turn. Over the rise the roller climbs, over the dwells it rests, and over the return it comes back down. The lift is given as a law over the cam's angle, and the drawing gives it as a table. The roller's center then runs along the pitch curve, the base circle grown by the roller's radius and the lift, and the cam's profile is the pitch curve offset inward by the roller's radius. Model the cam from the drawing in planex2d or in any CAD system, then check its lift, the roller's contact and its area against the values at the end of the page. A solution in planex2d follows. ![The disc cam exercise: a dimensioned drawing of a disc cam with a bore and a keyway, its pitch curve as a center line, and its lift table](../_static/gallery/disc-cam.png){.result} ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Chart, Row ``` ## The lift The cam lifts the roller by 12. It rises over 150° of its turn, dwells at the top for 30°, returns over 150° and dwells at the bottom for the last 30°. Rise and return follow the cycloidal law, $s = h\left(t - \tfrac{\sin 2\pi t}{2\pi}\right)$ for the share $t$ of the rise, whose speed and acceleration both start and end at zero, so the roller is not jerked at the dwells. The cam angle runs counterclockwise from the keyway. The cam turns clockwise, so the roller meets the angles in that order (Figure 1): ```python def lift(angle): """the follower's lift at the cam angle in degrees""" if angle < 150: t = angle / 150 return 12 * (t - math.sin(2 * math.pi * t) / (2 * math.pi)) if angle < 180: return 12.0 if angle < 330: t = (angle - 180) / 150 return 12 * (1 - t + math.sin(2 * math.pi * t) / (2 * math.pi)) return 0.0 ``` ```python nbsphinx="hide-input" angles = [a / 2 for a in range(721)] Chart("cam angle [°]", "lift [mm]", curves={"lift": (angles, [lift(a) for a in angles])}, title="The lift over a turn of the cam") ``` ## The pitch curve and the profile The base circle, the profile's smallest radius, is 25, and the roller is 10 across. The roller's center lies on the pitch curve, $25 + 5 + s(\theta)$ from the cam's center. The lift law has its pieces, so `Curve` takes the pitch curve as a Python function of the cam angle, over a full turn, and the curve closes on itself as one edge. It stays where the function puts it, held by the `px.Fix` that a curve gets by default. `Offset` draws the profile 5 inside it, the roller's radius, and keeps the offset's distance as a dimension, `profile.d` (Figure 2): ```python def pitch(angle): """the roller's center at the cam angle in degrees: the base circle, the roller's radius and the lift""" rho = 25 + 5 + lift(angle) return (rho * math.cos(math.radians(angle)), rho * math.sin(math.radians(angle))) cam = px.Sketch(auto_constraint={"horizontal": True, "vertical": True}) cam.Curve(pitch, (0, 360), id="pitch", construction=True) # its end meets its start: one closed edge cam.Offset("pitch", -5, id="profile") # negative: inward, by the roller's radius cam("profile").construction = False print("status", cam.status, "| degrees of freedom", cam.dof) ``` ```python nbsphinx="hide-input" Row((cam, "The pitch curve dashed, the profile 5 inside it", {"fill": False, "marks": [(0, 0)]}), caption="The pitch curve and the profile") ``` ## The bore and the keyway The bore is 16 across, and its keyway, 5 wide and 18.3 deep from the far side of the bore, lies on the $x$ axis, at the cam angle 0°, where the rise begins. As on the [keyed hub](keyed-hub.md), the keyway is three lines drawn roughly from the bore, and a vertex on the bore's far side is where its depth is measured from. The sketch was made with `auto_constraint={"horizontal": True, "vertical": True}`, so the keyway's lines, drawn level and plumb, stay so without a constraint of their own. With the dimensions, nothing in the sketch is left free (Figure 3): ```python cam.Circle(16 / 2, center=(0, 0), id="bore") # not at the pen, which is still on the pitch curve cam.Move((4, 2.5), dir=(1, 0)).Line(4, id="keyway_upper") cam.Turn(-90).Line(5, id="keyway_top") # Turn(-90) turns the pen right cam.Turn(-90).Line(4, id="keyway_lower") cam.Vertex("bore_back", (-8, 0)) cam.Constrain("bore.center", "origin", px.Coincident) cam.Constrain("bore", px.Diameter, 16, id="bore_d") cam.Constrain("keyway_upper.start", "bore", px.Coincident) # a point on a circle cam.Constrain("keyway_lower.end", "bore", px.Coincident) cam.Constrain("keyway_top", px.Distance, 5, id="keyway_w") cam.Constrain("keyway_top.mid", "H", px.Coincident) cam.Constrain("bore_back", "H", px.Coincident) cam.Constrain("bore_back", "keyway_top", px.Distance, 18.3, id="keyway_depth") print("status", cam.status, "| degrees of freedom", cam.dof) ``` ```python nbsphinx="hide-input" Row((cam, "Every dimension and tie of the sketch", {"constraints": True}), caption="The fully constrained sketch") ``` `Trim` cuts the bore between the keyway's sides, and the bore and the keyway form one outline (Figure 4): ```python cam.Trim(("bore", (8, 0))); # the piece of the bore nearest to (8, 0), between the sides, becomes construction ``` ```python nbsphinx="hide-input" Row((cam, "The bore trimmed between the keyway's sides, the piece dashed", {"fill": False, "view": ((-12, 16), (-12, 12))}), caption="The trimmed bore") ``` ## The checks These are the lift, the roller's contact, the area and the centroid, with the cam's center at the origin and the keyway along the $x$ axis, to compare with your own model (Figure 5): - the lift is the profile's largest distance from the center less its smallest, the base circle's 25; - a roller of 10 placed at each angle of the lift table, its center on the pitch curve, touches the profile: its center lies 5 from the cam, to within the offset's tolerance. ```python face = cam.Face() radii = [math.hypot(*p) for line in face.outer.Polylines(1e-5) for p in line] # the outline, not the bore print("profile from", round(min(radii), 4), "to", round(max(radii), 4), "| lift", round(max(radii) - min(radii), 4)) worst = 0.0 for angle in range(0, 360, 30): a, rho = math.radians(angle), 25 + 5 + lift(angle) worst = max(worst, abs(face.Distance((rho * math.cos(a), rho * math.sin(a))) - 5)) print("the roller's center from the cam at the table's angles: off 5 by at most", f"{worst:.1e}") c = face.center print("area", round(face.mass, 2), "mm²") print("centroid", tuple(round(v, 2) for v in c[:2])) ``` ```python nbsphinx="hide-input" Row((face, "The disc cam, its centroid marked", {"marks": [c[:2]]}), caption="The disc cam") ``` The cam as DXF in millimeters, for another CAD system or a laser cutter: ```python cam.WriteDXF("disc_cam.dxf", unit=px.mm, scale=1) # scale=1: the numbers as they are ``` The cam as DXF: [disc_cam.dxf](disc_cam.dxf) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import LANDSCAPE, SheetFigure, Table, View from gallery import save_figure # the pitch curve as a center line, the base circle thin; the lift table beside the view, its angles from the keyway pitch = [((30 + lift(a / 2)) * math.cos(math.radians(a / 2)), (30 + lift(a / 2)) * math.sin(math.radians(a / 2))) for a in range(721)] view = View([(cam, {"bore_d": 135, "keyway_w": 32, "keyway_depth": -14}, {"bore": "lines"})], circles=[((0, 0), 25, "pitch")], radii=[((0, 0), 25, (225, "inside"))], axes=[pitch, ((-36, 0), (48, 0), (0, 0)), ((0, -36), (0, 36), (0, 0))]) rows = [("Roller diameter", 10), ("Lift h", 12), ("Rise 0° to 150°", "cycloidal"), ("Return 180° to 330°", "cycloidal"), ("Angle from the keyway", "Lift")] + [(f"{a}°", round(lift(a), 3)) for a in range(0, 360, 30)] data = Table(rows, at=(60, 58), widths=(50, 22)) drawing = SheetFigure([view, data], margin=20, notes=("planex2d.org", "2D exercise 20"), sheet=LANDSCAPE) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "disc-cam", dpi=200) plt.close(drawing) ```