--- jupyter: description: 'A pump impeller as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from a revolved half section with a fillet, six backward-curved blades as offsets of circular arcs cut to a revolved contour, and a keyed bore, with the volume, mass and center of mass to check a model against.' --- # Impeller The impeller of a centrifugal pump throws the liquid outward between its blades. The liquid enters around the hub, at the inlet circle, and leaves at the rim, at the outlet circle. On this semi-open impeller, six blades stand on a back disc. Each blade is a circular arc from the inlet circle to the outlet circle, 3 thick with round ends, and the blades lose height toward the rim, from 15 at the inlet to 8 at the outlet. Seen from the blades' side, the impeller turns clockwise, and the blades curve backward against it. As a solid, the disc and the hub are one revolved half section, and the blades are padded and then cut to a revolved contour. Model the impeller 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 impeller exercise: a dimensioned drawing of a semi-open impeller with six curved blades on a back disc, a hub and a keyed bore, in a full section, a view onto the blades and an isometric](../_static/gallery/impeller.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The disc and the hub The disc and the hub are one half section, revolved about the axis. It is sketched in the front plane, the plane with its normal toward the viewer, $-y$. Its $x$ runs out from the axis and $z$ along it, with the origin on the axis at the back of the disc. The outline runs out along the back, up the rim, in along the disc's face to the hub, up the hub and back to the axis. The disc is 120 across and 5 thick, the hub 32 across and 30 high from the back. The hub meets the disc with a fillet of 5 (Figure 1): ```python front = px.XZ section = px.Sketch(front, auto_constraint={"horizontal": True, "vertical": True}) section.Move((0, 0)).Line(60, id="back") section.Turn(90).Line(5, id="rim") # Turn(90) turns the pen left, Turn(-90) right section.Turn(90).Line(44, id="disc_face") section.Turn(-90).Line(25, id="hub_side") section.Turn(90).Line(16, id="hub_top") section.Close(id="axis") section.Constrain("back.start", "origin", px.Coincident) section.Constrain("back", px.Distance, 60, id="disc_r") section.Constrain("rim", px.Distance, 5, id="disc_t") section.Constrain("hub_side", "V", px.Distance, 16, id="hub_r") section.Constrain("hub_top", "H", px.Distance, 30, id="hub_h") section.Fillet(["disc_face.end"], 5, id=["hub_fillet"]) print("status", section.status, "| degrees of freedom", section.dof) ``` ```python nbsphinx="hide-input" Row((section, "Every dimension and tie of the half section", {"constraints": True}), caption="The half section") ``` The 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 = section.Face() body = face.ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.Z), 360) print("volume", round(body.mass, 3), "| Pappus", round(2 * math.pi * face.center[0] * face.mass, 3)) ``` ```python nbsphinx="hide-input" Views(("The disc and the hub", [(body, "shade"), (body, "ink")]), angles=(-60, 0, 30), caption="The disc and the hub") ``` ## The blades The blades are sketched on the plane of the disc's face, the xy plane lifted by 5. Three construction circles carry them: the inlet circle of 44 and the outlet circle of 116, between which the blades run, and a circle of 60 on which the centers of their arcs lie. The first blade's center line is an arc of radius 50 from the inlet circle, where it starts on the $x$ axis, to the outlet circle, drawn roughly and placed by the constraints (Figure 3): ```python blades = px.Sketch(px.Plane(px.XY, 5)) for ref, d in (("inlet", 44), ("outlet", 116), ("centers", 60)): blades.Circle(d / 2, id=ref, construction=True) blades.Constrain(ref + ".center", "origin", px.Coincident) blades.Constrain(ref, px.Diameter, d, id=ref + "_d") blades.Move((22, 0)).Arc((2, 58), 50, id="blade", construction=True) # from the pen to the point, radius 50 blades.Constrain("blade.start", "inlet", px.Coincident) # a point on a circle blades.Constrain("blade.start", "H", px.Coincident) blades.Constrain("blade.end", "outlet", px.Coincident) blades.Constrain("blade.center", "centers", px.Coincident) blades.Constrain("blade", px.Radius, 50, id="blade_r") print("status", blades.status, "| degrees of freedom", blades.dof) ``` ```python nbsphinx="hide-input" Row((blades, "The first blade's center line between the inlet and outlet circles", {"constraints": True}), caption="The center line") ``` `CircularPattern` repeats the center line around the axis, six in all, 60° apart. `Offset` then turns each center line into a blade, 1.5 to each side with round ends. An offset of a construction line is construction too, so each blade is set to be drawn (Figure 4): ```python blades.CircularPattern("blade", "origin", 6, id="lines") # the copies lines.1.blade to lines.5.blade lines = ["blade"] + [f"lines.{k}.blade" for k in range(1, 6)] for k, line in enumerate(lines): blades.Offset(line, 1.5, id=f"vane_{k}") blades(f"vane_{k}").construction = False print("status", blades.status, "| degrees of freedom", blades.dof) ``` ```python nbsphinx="hide-input" Row((blades, "Six blades, each the offset of a center line", {"fill": False}), caption="The blades") ``` ## The blades' contour The blades' tips follow a contour in the half section. It runs 20 above the back, 15 above the disc's face, out to the inlet circle, and from there in a straight line down to 13 above the back at the outlet circle, 8 above the face. Revolved about the axis, the region under the contour is an envelope. The blades are padded 20 from the disc's face, higher than they will be, and `*` keeps what lies inside the envelope (Figure 5): ```python contour = px.Sketch(front, auto_constraint={"horizontal": True, "vertical": True}) contour.Move((0, 0)).Line(62, id="base") contour.Turn(90).Line(12, id="outer") contour.Line((22, 20), id="tip_line") contour.Line((0, 20), id="inlet_top") contour.Close() contour.Vertex("outlet_tip", (58, 13)) contour.Constrain("base.start", "origin", px.Coincident) contour.Constrain("base", px.Distance, 62) contour.Constrain("inlet_top", "H", px.Distance, 20, id="inlet_h") contour.Constrain("tip_line.end", "V", px.Distance, 22, id="inlet_r") contour.Constrain("outlet_tip", "tip_line", px.Coincident) contour.Constrain("outlet_tip", "V", px.Distance, 58, id="outlet_r") contour.Constrain("outlet_tip", "H", px.Distance, 13, id="outlet_h") print("status", contour.status, "| degrees of freedom", contour.dof) envelope = contour.Face().ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.Z), 360) vanes = px.Pad(blades, 20) * envelope impeller = body + vanes ``` ```python nbsphinx="hide-input" Row((vanes, "The blades cut to the revolved contour", {"views": [(vanes, "shade"), (vanes, "ink")], "angles": (-60, 0, 30)}), (impeller, "On the disc", {"views": [(impeller, "shade"), (impeller, "ink")], "angles": (-60, 0, 30)}), caption="The blades' contour") ``` ## The bore The bore, 16 across, and the keyway, 5 wide and 18.3 deep from the far side of the bore, are pocketed from the hub's top. The bore goes to the last face it crosses, and the keyway 30 deep, through the hub (Figure 6): ```python bore = px.QuickSketch(impeller.faces.Max(occ.Z)).Circle(8) impeller = px.Pocket(impeller, bore, until=px.last) keyway = px.QuickSketch(impeller.faces.Max(occ.Z)).Move((-2.5, 0)).Rectangle(5, 18.3 - 8) # from the axis to its top impeller = px.Pocket(impeller, keyway, 30) ``` ```python nbsphinx="hide-input" Views(("The impeller with its keyed bore", [(impeller, "shade"), (impeller, "ink")]), angles=(-60, 0, 30), caption="The impeller") ``` ## The checks These are the volume, the mass in bronze CuSn10 (8.7 g/cm³) and the center of mass, with the origin on the axis at the back of the disc, to compare with your own model. The length of a blade's center line checks the sketch. The line runs between the angles at its center that the law of cosines gives for the inlet and the outlet circle, in the triangle of the axis, the arc's center 30 from it, and a point of the arc 50 from the center. The keyway takes material on one side, so the center of mass lies just off the axis: ```python def angle(rho): # at the arc's center, between the axis and the arc's point at the radius rho return math.acos((50**2 + 30**2 - rho**2) / (2 * 50 * 30)) c = impeller.center print("blade length", round(blades.Measure("blade")["length"], 6), "| exact", round(50 * (angle(58) - angle(22)), 6)) print("volume", round(impeller.mass, 1), "mm³") print("mass", round(impeller.mass * 8.7e-6, 3), "kg") print("center of mass", round(c.z, 2), "above the back,", round(-c.y, 3), "off the axis, away from the keyway") ``` The impeller as STEP, for another CAD system: ```python impeller.WriteStep("impeller.step") ``` The impeller as STEP: [impeller.step](impeller.step) ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from drawings import Pictorial, SheetFigure, Table, View from gallery import save_figure # first angle: the full section through the axis in front, the view onto the blades below it with the # blades' construction circles as center lines half = impeller * occ.Box((-70, 0, -1), (70, 70, 40)) cut = [f for f in half.faces if abs(f.center.y) < 1e-6 and f.bounding_box[1].y - f.bounding_box[0].y < 1e-6] section_view = View([(section, {"disc_t": 70, "hub_h": -78, "hub_fillet.r": (45, "outside")}), (contour, {"inlet_h": -68, "outlet_h": 82})], solid=half, plane=front, hidden_edges=False, hatch=cut, axes=[((0, -4), (0, 34))], measures=[((-60, 0), (60, 0), -12, "⌀120"), ((-16, 30), (16, 30), 48, "⌀32"), ((-8, 30), (8, 30), 38, "⌀16")]) # the first blade's center line, which R50 dimensions; the blades' number and thickness in a table (at 1:2, its # cells twice as large in the drawing's units) (cx, cy) = blades.Location("blade.center")[:2] a0, a1 = (math.atan2(y - cy, x - cx) for x, y in (blades.Location(f"blade.{ref}")[:2] for ref in ("start", "end"))) center_line = [(cx + 50 * math.cos(a0 + (a1 - a0) * j / 40), cy + 50 * math.sin(a0 + (a1 - a0) * j / 40)) for j in range(41)] top_view = View([(blades, {"inlet_d": (230, "leader"), "outlet_d": (40, "leader"), "centers_d": (150, "leader"), "blade_r": (35, "outside")})], solid=impeller, plane=px.XY, at=(0, -100), hidden_edges=False, circles=[((0, 0), 22, "pitch"), ((0, 0), 30, "pitch"), ((0, 0), 58, "pitch")], axes=[((-66, 0), (66, 0)), ((0, -66), (0, 66)), center_line]) pictorial = Pictorial(impeller, at=(125, 12), width=60, angles=(-60, 0, 30)) data = Table([("Blades", 6), ("Blade thickness", 3)], at=(92, -60), widths=(70, 24), height=14) drawing = SheetFigure([section_view, top_view, pictorial, data], margin=24, notes=("planex2d.org", "3D exercise 15"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "impeller", dpi=200) plt.close(drawing) ```