--- jupyter: description: 'An elbow duct as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from a rounded rectangle swept along a path of a straight, a 90° bend and a straight, hollowed by its offset, with a flange at each end, and the volume, mass and center of mass to check a model against.' --- # Elbow duct An elbow duct turns a flow by 90°, in a ventilation duct or at the intake of a machine. Its section stays the same along it, a rounded rectangle, and its center line is a path of a straight, a bend and a second straight. A section swept along a path is a sweep, `occ.Pipe` in OpenCASCADE. Each point of the section follows the path, and the section stays square to it all the way. The wall is 2 thick, the inside is swept the same way, and each end has a flange. Model the duct 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 elbow duct exercise: a dimensioned drawing of a rectangular duct with a 90° bend and a flange at each end, in a front view, a view onto the end flange and an isometric](../_static/gallery/elbow-duct.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The path and the section The path is the duct's center line, sketched in the front plane, the plane with its normal toward the viewer, $-y$, with $x$ to the right and $z$ up and the origin in the middle of the duct's lower end. It runs 50 up, through a bend of radius 80 to the right, and 40 along $x$. The section lies on the xy plane, square to the path's start. It is 60 by 40 with corners of radius 8, the 60 in the plane of the bend. The inside is the section offset by -2, the wall's thickness, which also takes the corners' radius down to 6 (Figure 1): ```python front = px.XZ path = px.QuickSketch(front).Move((0, 0), dir=(0, 1)).Line(50).Arc(80, -90).Line(40) # up, a bend to the right, along x spine = path.Wire() section = px.QuickSketch().RectangleC(60, 40) section.Fillet([(30, 20), (-30, 20), (-30, -20), (30, -20)], 8) # the corners as points outside = section.Face() inside = outside.Offset(-2, inplace=False) print("path", round(spine.mass, 4), "| section", round(outside.mass, 4), "| inside", round(inside.mass, 4)) ``` ```python nbsphinx="hide-input" Views(("The path in the front plane and the section at its start, with its inside", [(spine.ToOCCWire(), "accent"), (outside.ToOCCFace(), "shade"), (outside.ToOCCFace(), "ink"), (inside.ToOCCFace(), "ink")]), angles=(-60, 0, 30), caption="The path and the section") ``` ## The sweep `occ.Pipe` sweeps a face along a wire. Swept along the path, the section gives the outside of the duct and its inside the passage, and `-` hollows the one by the other (Figure 2): ```python duct = occ.Pipe(spine.ToOCCWire(), outside.ToOCCFace()) - occ.Pipe(spine.ToOCCWire(), inside.ToOCCFace()) ``` ```python nbsphinx="hide-input" Views(("The section swept along the path, hollowed", [(duct, "shade"), (duct, "ink")]), angles=(-60, 0, 30), caption="The sweep") ``` ## The flanges Each flange is 90 by 70 and 6 thick, with the duct's inside as its opening and four holes of 7, 76 by 56 apart. The lower flange is sketched on the xy plane, under the duct's lower end, and padded 6 down with `reverse=True`. The end flange is sketched on the duct's end face, the face highest in $x$, whose axes are $y$ and $z$, with the global origin projected onto it. The flange's middle lies 130 up, at the path's end, and there the 90 runs along $z$, in the plane of the bend (Figure 3): ```python def flange(sketch, center, along_h): """the flange around center on the sketch's plane, 90 along h if along_h else along v, its opening the inside""" (h, v), (a, b) = center, (90, 70) if along_h else (70, 90) sketch.RectangleC(center, a, b) sketch.RectangleC(center, a - 34, b - 34) # the inside's straight sides, 56 by 36 sketch.Fillet([(h + sh * (a - 34) / 2, v + sv * (b - 34) / 2) for sh in (-1, 1) for sv in (-1, 1)], 6) for sh in (-1, 1): for sv in (-1, 1): sketch.Circle(7 / 2, center=(h + sh * (a - 14) / 2, v + sv * (b - 14) / 2)) return sketch lower = flange(px.QuickSketch(), (0, 0), True) upper = flange(px.QuickSketch(duct.faces.Max(occ.X)), (0, 130), False) # on the end face: h along y, v along z elbow = duct + px.Pad(lower, 6, reverse=True) + px.Pad(upper, 6) ``` ```python nbsphinx="hide-input" Row((lower, "The lower flange", {}), (elbow, "The duct with its flanges", {"views": [(elbow, "shade"), (elbow, "ink")], "angles": (-60, 0, 30)}), caption="The flanges") ``` ## The checks These are the volume, the mass in aluminum AlMg3 (2.66 g/cm³) and the center of mass, with the origin in the middle of the lower end, to compare with your own model. A sweep whose section stays square to its path and has its centroid on it has the volume of the section's area times the path's length, by Pappus's second theorem, here for the wall between the outside and the inside. The flanges add their faces' areas times 6. The duct is symmetric about the plane $y = 0$: ```python exact = (outside.mass - inside.mass) * spine.mass + 6 * (lower.Face().mass + upper.Face().mass) c = elbow.center print("volume", round(elbow.mass, 1), "mm³, exact", round(exact, 1)) print("mass", round(elbow.mass * 2.66e-6, 3), "kg") print("center of mass", (round(c.x, 2), round(c.z, 2)), "in the plane of the bend, off it by", round(abs(c.y), 6)) ``` The duct as STEP, for another CAD system: ```python elbow.WriteStep("elbow_duct.step") ``` The duct as STEP: [elbow_duct.step](elbow_duct.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 front view with the duct's center line, the view from the right onto the end flange to its left; # the duct's section in a table (at 1:2, the table's cells twice as large in the drawing's units) t = [k / 60 for k in range(61)] center_line = [(80 - 80 * math.cos(math.pi / 2 * s), 50 + 80 * math.sin(math.pi / 2 * s)) for s in t] front_view = View([], solid=elbow, plane=front, hidden_edges=False, axes=[((0, -10), (0, 50)), center_line, ((80, 130), (130, 130))], measures=[((0, 0), (0, 50), -50, 50), ((80, 130), (120, 130), 175, 40), ((45, -6), (45, 0), 50, 6), ((120, 100), (126, 100), 80, 6), ((-45, -6), (45, -6), -18, 90)], radii=[((80, 50), 80, (135, "inside"))]) right = px.Plane(normal=(1, 0, 0)) # the view from the right: y to the right, z up right_view = View([], solid=elbow, plane=right, at=(-115, 0), hidden_edges=False, axes=[((-45, 130), (45, 130)), ((0, -10), (0, 180))], measures=[((-35, 175), (35, 175), 192, 70), ((-28, 168), (28, 168), 182, 56), ((35, 85), (35, 175), 52, 90), ((-28, 92), (-28, 168), -46, 76)], diameters=[((28, 168), 3.5, (20, "leader", 4))]) data = Table([("Duct outside", "60 × 40"), ("Corner radius outside", 8), ("Wall", 2)], at=(10, -40), widths=(96, 40), height=14) pictorial = Pictorial(elbow, at=(-110, -80), width=70, angles=(-60, 0, 30)) drawing = SheetFigure([front_view, right_view, data, pictorial], margin=26, notes=("planex2d.org", "3D exercise 17"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "elbow-duct", dpi=200) plt.close(drawing) ```