--- jupyter: description: 'A stepped shaft as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from a half-section profile with chamfers and a ring groove revolved about its axis and a keyseat cut from a plane offset to the shaft''s top, and the volume, mass and center of mass to check a model against.' --- # Stepped shaft A stepped shaft carries its parts on seats of different diameters, two bearing journals at its ends, a collar that locates one bearing, and a body with a keyseat for the key of a hub. A ring groove on the right journal takes a retaining ring, and both ends are chamfered. The turned shape is a profile revolved about the axis. The keyseat is cut afterwards, from the plane tangent to the body's top. Model the shaft 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 stepped shaft exercise: a dimensioned drawing of a shaft with two journals, a collar, a body with a keyseat and a ring groove, in a front view, a top view and an isometric](../_static/gallery/stepped-shaft.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The profile The profile is the half section above the axis, sketched in the front plane, the plane with its normal toward the viewer, $-y$. Its $x$ runs along the axis and $z$ out from it, with the origin on the axis at the left end. The outline runs along the axis to the right end, up it, back along the right journal with the groove, and down the steps of the body and the collar to the left journal. Every line is horizontal or vertical and is drawn so. With `auto_constraint={"horizontal": True, "vertical": True}`, each line stays level or plumb as it is drawn, without a flag of its own. The shaft is 120 long. The left journal is 20 across and 25 long, the collar 30 across and 10 long, the body 25 across and 60 long, and the right journal 20 across. The groove, 1.3 wide and 19 across at its bottom, begins 8.7 from the right end (Figure 1): ```python front = px.XZ shaft = px.Sketch(front, auto_constraint={"horizontal": True, "vertical": True}) shaft.Move((0, 0)).Line(120, id="axis") shaft.Turn(90).Line(10, id="right_end") # Turn(90) turns the pen left, Turn(-90) right shaft.Turn(90).Line(8.7, id="groove_start") shaft.Turn(90).Line(0.5, id="groove_right") shaft.Turn(-90).Line(1.3, id="groove") shaft.Turn(-90).Line(0.5, id="groove_left") shaft.Turn(90).Line(15, id="right_journal") shaft.Turn(-90).Line(2.5, id="body_right") shaft.Turn(90).Line(60, id="body") shaft.Turn(-90).Line(2.5, id="collar_right") shaft.Turn(90).Line(10, id="collar") shaft.Turn(90).Line(5, id="collar_left") shaft.Turn(-90).Line(25, id="left_journal") shaft.Close(id="left_end") shaft.Constrain("axis.start", "origin", px.Coincident) shaft.Constrain("axis", px.Distance, 120, id="length") for line, r in (("groove_start", 10), ("right_journal", 10), ("body", 12.5), ("collar", 15), ("left_journal", 10)): shaft.Constrain(line, "H", px.Distance, r, id=line + "_r") # the radius: half the diameter shaft.Constrain("groove", "H", px.Distance, 19 / 2, id="groove_r") shaft.Constrain("groove_start", px.Distance, 8.7, id="groove_at") shaft.Constrain("groove", px.Distance, 1.3, id="groove_w") shaft.Constrain("body", px.Distance, 60, id="body_l") shaft.Constrain("collar", px.Distance, 10, id="collar_l") shaft.Constrain("left_journal", px.Distance, 25, id="journal_l") print("status", shaft.status, "| degrees of freedom", shaft.dof) ``` ```python nbsphinx="hide-input" Row((shaft, "Every dimension and tie of the half section", {"constraints": True}), caption="The profile") ``` ## The chamfers Both ends are chamfered 1 by 45°, at the corners where the end faces meet the journals. `Chamfer` cuts the two corners and keeps their dimensions (Figure 2): ```python shaft.Chamfer(["right_end.end", "left_end.start"], 1, id=["chamfer_right", "chamfer_left"]) print("status", shaft.status, "| degrees of freedom", shaft.dof) ``` ```python nbsphinx="hide-input" Row((shaft, "The profile with its chamfers", {"constraints": True}), caption="The chamfers") ``` ## The shaft The profile's face, taken to OpenCASCADE with `ToOCCFace`, is revolved a full turn about the $x$ axis with netgen's `Revolve`. By Pappus's theorem, the volume is the profile's area times the length of the circle that its centroid runs on (Figure 3): ```python profile = shaft.Face() solid = profile.ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.X), 360) print("volume", round(solid.mass, 3), "| Pappus", round(2 * math.pi * profile.center[2] * profile.mass, 3)) ``` ```python nbsphinx="hide-input" Views(("The profile revolved about the axis", [(solid, "shade"), (solid, "ink")]), angles=(-50, 0, 30), caption="The shaft") ``` ## The keyseat The keyseat is a slot 8 wide and 40 long with round ends, 4 deep, its left end 10 from the collar. It is sketched on the plane tangent to the body's top, the xy plane lifted by the body's radius of 12.5, which is `px.Plane(px.XY, 12.5)`. On it, $x$ runs along the axis and $y$ across, with the axis below the origin. `Slot` draws the slot from its spine, 32 between the centers of its ends, and its width is the slot's own dimension `keyseat.w`. The slot is pocketed 4 deep, against the plane's normal, down into the body (Figure 4): ```python keyseat = px.Sketch(px.Plane(px.XY, 12.5)) keyseat.Move((49, 0), dir=(1, 0)).Slot(32, 8, id="keyseat") # 32 between the centers of the ends, 8 wide keyseat.Constrain("keyseat.spine", px.Horizontal) keyseat.Constrain("keyseat.spine.start", "H", px.Coincident) keyseat.Constrain("keyseat.spine.start", "V", px.Distance, 35 + 10 + 4, id="keyseat_at") # its end 10 from the collar keyseat.Constrain("keyseat.spine", px.Distance, 32, id="keyseat_spine") print("status", keyseat.status, "| degrees of freedom", keyseat.dof) solid = px.Pocket(solid, keyseat, 4) ``` ```python nbsphinx="hide-input" Row((keyseat, "The keyseat on the plane over the body", {"plot": {"show_parent": solid, "show_constraints": True}}), (solid, "The keyseat in the body", {"views": [(solid, "shade"), (solid, "ink")], "angles": (-50, 0, 30)}), caption="The keyseat") ``` ## The checks These are the volume, the mass in steel C45 (7.85 g/cm³) and the center of mass, with the origin on the axis at the left end, to compare with your own model. The revolved part follows from Pappus's theorem. The keyseat's floor lies 4 below the top, at 8.5 from the axis, and the slot takes from the body what lies above its floor and inside the circle of radius 12.5. That is a straight part 32 long and the two half discs at its ends. The straight part has a closed form, and the half discs are integrated numerically across the slot's width, so the reference volume is as exact as that integral. The keyseat takes material above the axis, so the center of mass lies just below it: ```python def strip(y): # the body's height above the keyseat's floor at y across the slot return math.sqrt(12.5**2 - y * y) - 8.5 def segment(y): # the area under the circle of radius 12.5 from 0 to y return y / 2 * math.sqrt(12.5**2 - y * y) + 12.5**2 / 2 * math.asin(y / 12.5) straight = 32 * (2 * segment(4) - 8.5 * 8) n = 2000 ends = sum(2 * (4 * math.cos(t)) * strip(4 * math.sin(t)) * 4 * math.cos(t) * math.pi / n for t in (-math.pi / 2 + math.pi * (k + 0.5) / n for k in range(n))) # y = 4 sin t: smooth in t reference = 2 * math.pi * profile.center[2] * profile.mass - straight - ends c = solid.center print("volume", round(solid.mass, 1), "mm³, reference", round(reference, 1)) print("mass", round(solid.mass * 7.85e-6, 3), "kg") print("center of mass", round(c.x, 2), "along the axis,", round(-c.z, 3), "below it, off the plane y = 0 by", round(abs(c.y), 6)) ``` The shaft as STEP, for another CAD system: ```python solid.WriteStep("stepped_shaft.step") ``` The shaft as STEP: [stepped_shaft.step](stepped_shaft.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import Pictorial, SheetFigure, View from gallery import save_figure # first angle: the front view, the top view below it; the diameters as lengths with ⌀, the keyseat's depth as the # remaining 21 from the bottom, its place from the collar front_view = View([(shaft, {"length": -27, "journal_l": 22, "collar_l": 22, "body_l": 22, "groove_at": 22, "groove_w": 30})], solid=solid, plane=front, axes=[((-4, 0), (124, 0))], measures=[((12.5, -10), (12.5, 10), 12.5, "⌀20"), ((30, -15), (30, 15), 30, "⌀30"), ((40, -12.5), (40, 12.5), 40, "⌀25"), ((102, -10), (102, 10), 102, "⌀20"), ((110.65, -9.5), (110.65, 9.5), 110.65, "⌀19"), ((65, -12.5), (65, 8.5), 65, 21), ((0, -10), (1, -10), -17, "2× 1 × 45°")]) top_view = View([], solid=solid, plane=px.XY, at=(0, -45), hidden_edges=False, axes=[((-4, 0), (124, 0))], measures=[((35, 0), (45, 0), 8, 10), ((45, 0), (85, 0), 8, 40), ((81, -4), (81, 4), 92, 8)]) pictorial = Pictorial(solid, at=(95, -85), width=50, angles=(-50, 0, 30)) drawing = SheetFigure([front_view, top_view, pictorial], margin=30, notes=("planex2d.org", "3D exercise 13"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "stepped-shaft", dpi=200) plt.close(drawing) ```