--- jupyter: description: 'A control knob as a 3D CAD exercise: a dimensioned drawing with a full section and a table of the profile''s points to model in planex2d or any CAD system, a solution from a half section with a spline side revolved, grip flutes from a circular pattern and a D-bore for the shaft, and the volume, mass and center of mass to check a model against.' --- # Control knob A control knob sits on the D-shaped shaft of a potentiometer or a switch. The flat of the shaft turns it, and the grip flutes around its side give the fingers a hold. Its side is a free curve, given in the drawing as a table of points that a spline runs through, and the knob is that half section revolved about its axis. The flutes are a circular pattern of circles pocketed down the side, and they run out where the side draws in toward the top. Model the knob from the drawing in planex2d or in any CAD system, then check its volume, mass and center of mass against the values at the end of the page. A solution in planex2d follows. ![The control knob exercise: a dimensioned drawing of a knob with a curved side, grip flutes and a D-bore, in a full section, a view from below, a table of the profile's points and an isometric](../_static/gallery/control-knob.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The half section The half section is sketched in the front plane, the plane with its normal toward the viewer, $-y$, with $x$ out from the axis, $z$ along it and the origin on the axis at the bottom. The knob is 36 across at its skirt, 4 high, and its side is a spline from the skirt's top through the points P1 to P3 of the drawing's table, the last one the edge of the top. The top is a dome, a circular arc around a center on the axis from P3 to the axis, 18 above the bottom. The points are given, so the half section is a `QuickSketch` through them (Figure 1): ```python front = px.XZ zc = (15**2 + 16**2 - 18**2) / (2 * (16 - 18)) # the dome's center on the axis: as far from P3 (15, 16) as from (0, 18) half = px.QuickSketch(front).Line((18, 0)).Line((18, 4)) half.Spline([(17.6, 8), (16.6, 12), (15, 16)], id="side") # from the pen through P1, P2, P3 half.Arc(center=(0, zc), sweep=90 - math.degrees(math.atan2(16 - zc, 15)), id="dome") # from P3 around its center to the axis half.Close() print("dome radius", 18 - zc) ``` ```python nbsphinx="hide-input" Row((half, "The half section, the spline through P1 to P3", {"marks": [(17.6, 8), (16.6, 12), (15, 16)]}), caption="The half section") ``` The half section's face, taken to OpenCASCADE with `ToOCCFace`, is revolved a full turn about the $z$ axis. By Pappus's theorem, the volume is the face's area times the length of the circle that its centroid runs on (Figure 2): ```python face = half.Face() knob = face.ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.Z), 360) print("volume", round(knob.mass, 1), "| Pappus", round(2 * math.pi * face.center[0] * face.mass, 1)) ``` ```python nbsphinx="hide-input" Views(("The half section revolved", [(knob, "shade"), (knob, "ink")]), angles=(-60, 0, 30), caption="The knob") ``` ## The flutes The flutes are circles of radius 2.5 whose centers lie on a circle of 37, 0.5 outside the skirt, one of them on the $x$ axis. `CircularPattern` repeats it around the axis, 12 in all. They are sketched on the xy plane lifted to 20, above the knob, and pocketed 22 down, through the whole knob. Each flute cuts 2 into the skirt and less where the side draws in, until it runs out above the side's radius of 16 (Figure 3): ```python flutes = px.QuickSketch(px.Plane(px.XY, 20)).Circle(2.5, center=(18.5, 0), id="flute") flutes.CircularPattern("flute", "origin", 12) knob = px.Pocket(knob, flutes, 22) ``` ```python nbsphinx="hide-input" Views(("Twelve flutes down the side", [(knob, "shade"), (knob, "ink")]), angles=(-60, 0, 30), caption="The flutes") ``` ## The D-bore The bore takes a shaft of 6 with a flat, 4.5 across from the flat to the far side of the shaft. Its face is a disc of 6 around the axis cut by a rectangle to below the flat, their intersection `*`. It lies on the xy plane, the plane of the knob's bottom, and is pocketed 12 up into the knob. A pocket runs against the plane's normal, so `reverse=True` turns it up (Figure 4): ```python d_bore = px.Circle((0, 0), 6 / 2).Face() * px.QuickSketch((-4, -4)).Rectangle(8, 4 + 1.5).Face() # the flat at y = 1.5 knob = px.Pocket(knob, d_bore, 12, reverse=True) print("D-bore", round(d_bore.mass, 4), "mm²") ``` ```python nbsphinx="hide-input" Row((d_bore, "The D-bore's face", {}), (knob, "The D-bore from below", {"views": [(knob, "shade"), (knob, "ink")], "angles": (-60, 0, -40)}), caption="The D-bore") ``` ## The checks These are the volume, the mass in aluminum EN AW-6082 (2.70 g/cm³) and the center of mass, with the origin on the axis at the bottom, to compare with your own model. The flutes keep the knob symmetric about its axis, but the bore's flat does not, so the center of mass lies just off the axis, on the side of the flat: ```python c = knob.center print("volume", round(knob.mass, 1), "mm³") print("mass", round(knob.mass * 2.70e-6 * 1000, 2), "g") print("center of mass", round(c.z, 3), "above the bottom,", round(c.y, 4), "toward the flat") ``` The knob as STEP, for another CAD system: ```python knob.WriteStep("control_knob.step") ``` The knob as STEP: [control_knob.step](control_knob.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import LANDSCAPE, Pictorial, SheetFigure, Table, View from gallery import save_figure # first angle: the full section through the plane y = 0 in front, the part behind it seen from the front, its cut faces # hatched; the view from below above it, with the D-bore; the profile's points in a table front = px.XZ # x to the right, z up behind = knob * occ.Box((-25, 0, -1), (25, 25, 25)) # the cut faces: flat in y (within the tolerance of edges cut from a revolved spline's surface) cut = [f for f in behind.faces if abs(f.center.y) < 1e-4 and f.bounding_box[1].y - f.bounding_box[0].y < 1e-4] section = View([], solid=behind, plane=front, hidden_edges=False, hatch=cut, axes=[((0, -3), (0, 21))], measures=[((-18, 0), (18, 0), -12, "⌀36"), ((18, 0), (18, 4), 24, 4), ((-18, 0), (-18, 18), -26, 18), ((-3, 0), (3, 0), -5, "⌀6"), ((3, 0), (3, 12), 9, 12)]) from_below = px.Plane(normal=(0, 0, -1)) # the view from below: x to the right, -y up below = View([], solid=knob, plane=from_below, at=(0, 52), hidden_edges=False, circles=[((0, 0), 18.5, "pitch")], axes=[((-23, 0), (23, 0)), ((0, -23), (0, 23))], diameters=[((18.5, 0), 2.5, (330, "leader", 12)), ((0, 0), 18.5, (135, "leader"))], measures=[((-6, -1.5), (-6, 3), -9, 4.5)]) points = Table([("Point", "r", "z"), ("P1", 17.6, 8), ("P2", 16.6, 12), ("P3", 15, 16), ("Dome", "R57,25", "")], at=(48, 70), widths=(18, 18, 12)) pictorial = Pictorial(knob, at=(68, 8), width=34, angles=(-60, 0, 30)) drawing = SheetFigure([section, below, points, pictorial], margin=14, notes=("planex2d.org", "3D exercise 20"), first_angle=True, sheet=LANDSCAPE) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "control-knob", dpi=200) plt.close(drawing) ```