--- jupyter: description: 'An impeller with twisted blades as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from blades lofted through two sections that differ in shape, cut to a revolved shroud contour, on a revolved hub, and the volume, mass and center of mass to check a model against.' --- # Impeller with twisted blades The blades of the [impeller](impeller.md) are the same at every height. In a real pump, the liquid meets a blade at a different angle near the hub than near the tip, and the blade twists to follow it. Here each blade is given by two sections. At the hub, 5 above the back, it is an arc of radius 50 from the inlet circle of 44. At the top, 25 higher, it is an arc of radius 40 from a circle of 70. Both end at the same point of the outlet circle, so the blade's outlet edge stays parallel to the axis while its inlet edge leans. A loft through the two sections gives the twisted blade, the multi-section solid of other CAD systems. The blades are cut to the shroud's contour, a revolved arc, and stand on a hub whose contour curves into the back disc. 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 exercise of an impeller with twisted blades: a dimensioned drawing of an impeller with six twisted blades on a hub that curves into the back disc, in a full section, a view onto the blades with their two sections and an isometric](../_static/gallery/twisted-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 hub The back disc and the hub are one half section, revolved about the axis, as on the [impeller](impeller.md). It 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 back. The disc is 120 across and 5 thick, the hub 32 across and 40 high, and the hub's contour curves into the disc's face with a radius of 14 (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(35, 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, 40, id="hub_h") section.Fillet(["disc_face.end"], 14, id=["hub_contour"]) face = section.Face() body = face.ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.Z), 360) print("status", section.status, "| degrees of freedom", section.dof) print("volume", round(body.mass, 3), "| Pappus", round(2 * math.pi * face.center[0] * face.mass, 3)) ``` ```python nbsphinx="hide-input" Row((section, "The half section", {"constraints": True}), (body, "Revolved", {"views": [(body, "shade"), (body, "ink")], "angles": (-60, 0, 30)}), caption="The hub") ``` ## The blade's sections `blade_section` sketches one section on the xy plane lifted to the height $z$. The blade's center line is an arc of the given radius from its inlet circle, where it starts on the $x$ axis, to a given point, and the blade is 3 thick around it, the center line offset by 1.5 to each side with round ends. The hub's section, at 5, has its arc's center on a circle of 60, as on the [impeller](impeller.md). Its end on the outlet circle of 116 is where the top section ends too, and a point fixed there ends the top section's center line, at 30 (Figure 2): ```python def blade_section(z, inlet_d, radius): sk = px.Sketch(px.Plane(px.XY, z)) sk.Circle(inlet_d / 2, id="inlet", construction=True) sk.Constrain("inlet.center", "origin", px.Coincident) sk.Constrain("inlet", px.Diameter, inlet_d, id="inlet_d") sk.Move((inlet_d / 2, 0)).Arc((2, 58), radius, id="line", construction=True) # roughly to the outlet point sk.Constrain("line.start", "inlet", px.Coincident) sk.Constrain("line.start", "H", px.Coincident) sk.Constrain("line", px.Radius, radius, id="line_r") return sk low = blade_section(5, 44, 50) low.Circle(116 / 2, id="outlet", construction=True) low.Circle(60 / 2, id="centers", construction=True) for ref, d in (("outlet", 116), ("centers", 60)): low.Constrain(ref + ".center", "origin", px.Coincident) low.Constrain(ref, px.Diameter, d, id=ref + "_d") low.Constrain("line.end", "outlet", px.Coincident) low.Constrain("line.center", "centers", px.Coincident) outlet = low.Location("line.end")[:2] # the point where both sections end high = blade_section(30, 70, 40) high.Vertex("outlet_point", outlet) high.Constrain("outlet_point", px.Fix) high.Constrain("line.end", "outlet_point", px.Coincident) for sk in (low, high): sk.Offset("line", 1.5, id="blade") sk("blade").construction = False print("status", sk.status, "| degrees of freedom", sk.dof) print("outlet point", tuple(round(v, 4) for v in outlet), "at", round(math.degrees(math.atan2(outlet[1], outlet[0])), 3), "°") ``` ```python nbsphinx="hide-input" Row((low, "The section at the hub, z = 5", {"constraints": True}), (high, "The section at the top, z = 30", {"constraints": True}), caption="The blade's sections") ``` ## The twisted blades `occ.ThruSections` lofts a solid through the two sections' wires. Each has the same four edges, the two sides and the two round ends, and the loft joins them one by one. Six copies of the blade turned about the axis by 60° each make the set (Figure 3): ```python blade = occ.ThruSections([low.ToOCCWire(), high.ToOCCWire()], solid=True) blades = blade for k in range(1, 6): blades = blades + blade.Rotate(occ.Axis((0, 0, 0), occ.Z), 60 * k) # a turned copy ``` ```python nbsphinx="hide-input" Row((blade, "One blade lofted through its two sections", {"views": [(blade, "shade"), (blade, "ink"), (low.ToOCCWire(), "accent"), (high.ToOCCWire(), "accent")], "angles": (-60, 0, 30)}), (blades, "Six blades", {"views": [(blades, "shade"), (blades, "ink")], "angles": (-60, 0, 30)}), caption="The twisted blades") ``` ## The shroud's contour The blades' tips follow the shroud's contour in the half section. It runs 30 above the back out to a radius of 35, the top section's inlet, and then along an arc of radius 30 tangent to it down toward the rim. Revolved about the axis, the region under the contour is an envelope, and `*` cuts the blades to it. On the hub, they make the impeller (Figure 4): ```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.Arc((35, 30), 30, id="shroud") # roughly, to the top section's inlet contour.Line((0, 30), id="top") contour.Close() contour.Constrain("base.start", "origin", px.Coincident) contour.Constrain("base", px.Distance, 62) contour.Constrain("top", "H", px.Distance, 30, id="top_h") contour.Constrain("top.start", "V", px.Distance, 35, id="top_r") contour.Constrain("shroud", px.Radius, 30, id="shroud_r") contour.Constrain("shroud", "top", px.Tangent) print("status", contour.status, "| degrees of freedom", contour.dof) envelope = contour.Face().ToOCCFace().Revolve(occ.Axis((0, 0, 0), occ.Z), 360) impeller = body + blades * envelope ``` ```python nbsphinx="hide-input" Views(("The blades cut to the shroud's contour, on the hub", [(impeller, "shade"), (impeller, "ink")]), angles=(-60, 0, 30), caption="The shroud's contour") ``` ## The bore The bore is 16 across, and the keyway is 5 wide and 18.3 deep from the far side of the bore. Both are pocketed from the hub's top, the keyway 40 deep, through the hub (Figure 5): ```python bore = px.QuickSketch(impeller.faces.Max(occ.Z)).Circle(16 / 2) 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, 40) ``` ```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. One blade's loft, before the cut, checks the twist. A loft through two sections joins them linearly, so its cross-section's area is a quadratic function of the height, and Simpson's rule gives its volume exactly from the sections' areas at its ends and in its middle, $\tfrac{h}{6}\,(A_\mathrm{hub} + 4 A_\mathrm{middle} + A_\mathrm{top})$ over its height of 25: ```python middle = px.Intersect3d(px.Plane(px.XY, 17.5), blade).mass # the loft's section at mid height simpson = 25 / 6 * (low.Face().mass + 4 * middle + high.Face().mass) c = impeller.center print("one blade's loft", round(blade.mass, 3), "mm³, Simpson", round(simpson, 3)) 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("twisted_impeller.step") ``` The impeller as STEP: [twisted_impeller.step](twisted_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 def center_line(sk): # the section's center line, which its radius dimensions, as a dash-dot polyline (cx, cy) = sk.Location("line.center")[:2] a0, a1 = (math.atan2(y - cy, x - cx) for x, y in (sk.Location(f"line.{ref}")[:2] for ref in ("start", "end"))) r = sk.Measure("line")["radius"] return [(cx + r * math.cos(a0 + (a1 - a0) * j / 40), cy + r * math.sin(a0 + (a1 - a0) * j / 40)) for j in range(41)] # first angle: the full section through the axis in front, the view onto the blades below it with the two sections' # center lines and circles; the sections' data in a table (at 1:2, its cells twice as large in the drawing's units) half = impeller * occ.Box((-70, 0, -1), (70, 70, 50)) # the cut faces: flat in y (within the tolerance of edges cut from a loft's surface) cut = [f for f in half.faces if abs(f.center.y) < 1e-4 and f.bounding_box[1].y - f.bounding_box[0].y < 1e-4] section_view = View([(section, {"disc_t": 70, "hub_h": -78, "hub_contour.r": (45, "outside")}), (contour, {"top_h": -68, "top_r": 70, "shroud_r": (20, "inside")})], solid=half, plane=front, hidden_edges=False, hatch=cut, axes=[((0, -4), (0, 44))], measures=[((-60, 0), (60, 0), -12, "⌀120"), ((-16, 40), (16, 40), 58, "⌀32"), ((-8, 40), (8, 40), 48, "⌀16")]) top_view = View([(low, {"inlet_d": (230, "leader"), "outlet_d": (40, "leader"), "centers_d": (150, "leader"), "line_r": (35, "outside")}), (high, {"inlet_d": (200, "leader"), "line_r": (5, "outside")})], solid=impeller, plane=px.XY, at=(0, -110), hidden_edges=False, circles=[((0, 0), 22, "pitch"), ((0, 0), 30, "pitch"), ((0, 0), 35, "pitch"), ((0, 0), 58, "pitch")], axes=[((-66, 0), (66, 0)), ((0, -66), (0, 66)), center_line(low), center_line(high)]) data = Table([("Blades", 6), ("Blade thickness", 3), ("Hub section at", "z = 5"), ("Top section at", "z = 30")], at=(92, -70), widths=(70, 40), height=14) pictorial = Pictorial(impeller, at=(130, 15), width=60, angles=(-60, 0, 30)) drawing = SheetFigure([section_view, top_view, pictorial, data], margin=34, notes=("planex2d.org", "3D exercise 18"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "twisted-impeller", dpi=200) plt.close(drawing) ```