--- jupyter: description: 'A square-to-round transition as a 3D CAD exercise: a dimensioned drawing to model in planex2d or any CAD system, a solution from a loft between a square and a circle drawn as four quarter arcs, hollowed by a second loft, with a flange at each end, and the volume, mass and center of mass to check a model against.' --- # Square-to-round transition A square-to-round transition joins a square duct to a round one, in ventilation and in the ducts of machines. Its wall runs from a square at one end to a circle at the other, a shape that neither padding nor revolving makes. It is a loft, a solid through two sections, the multi-section solid of other CAD systems. A loft joins the sections edge by edge, so the circle is drawn as four quarter arcs, one for each side of the square, and the arcs start where the square's first corner points. The inside is a second loft, 3 smaller all around, and each end has a flange with four bolt holes. Model the transition 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 square-to-round transition exercise: a dimensioned drawing of a duct transition from a square flange at the bottom to a round flange at the top, in a full section, a view from above and an isometric](../_static/gallery/square-to-round.png){.result} ```python import math import netgen.occ as occ import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row, Views ``` ## The sections The transition stands on the xy plane. The square of 100 lies at its bottom, 6 above the base, on top of the square flange, and the circle of 80 at its top, 114 above the base, under the round flange. `RectangleC` draws the square around the origin, its sides counterclockwise from its lower left corner. The circle's four quarter arcs run counterclockwise from the point at 225°, so that each arc lies across from one side of the square. `square` and `circle` sketch the sections on the xy plane lifted to their heights (Figure 1): ```python def square(a, z): return px.QuickSketch(px.Plane(px.XY, z)).RectangleC(a, a) # its first corner the lower left one def circle(d, z): corner = math.radians(225) # across from the square's first corner sk = px.QuickSketch(px.Plane(px.XY, z)).Move((d / 2 * math.cos(corner), d / 2 * math.sin(corner))) for _ in range(4): sk.Arc(center=(0, 0), sweep=90) # a quarter of the circle, around the axis return sk sections = [square(100, 6), circle(80, 114)] ``` ```python nbsphinx="hide-input" Views(("The square and the circle, the circle's arcs one for each side of the square", [(s.ToOCCWire(unify=False), "ink") for s in sections]), angles=(-60, 0, 30), caption="The sections") ``` ## The lofts `occ.ThruSections` lofts a solid through wires. A sketch's `ToOCCWire` merges edges that lie on one carrier, so the four quarter arcs would become one circle. `unify=False` keeps every drawn edge, and the loft joins the square's sides to the circle's arcs one by one. The inside is the same transition 3 smaller all around, from a square of 94 to a circle of 74, and `-` hollows the outside by it (Figure 2): ```python def loft(a, d): return occ.ThruSections([square(a, 6).ToOCCWire(unify=False), circle(d, 114).ToOCCWire(unify=False)], solid=True) outside = loft(100, 80) inside = loft(94, 74) duct = outside - inside ``` ```python nbsphinx="hide-input" Row((outside, "The loft from the square to the circle", {"views": [(outside, "shade"), (outside, "ink")], "angles": (-60, 0, 30)}), (duct, "Hollowed by the inside's loft", {"views": [(duct, "shade"), (duct, "ink")], "angles": (-60, 0, 30)}), caption="The lofts") ``` ## The flanges The square flange is 140 by 140 and 6 thick, under the duct, with the duct's inside as its opening and four holes of 9, 120 apart in both directions. The round flange is 120 across and 6 thick, on top of the duct, with its opening of 74 and four holes of 9 on a bolt circle of 100, at 45° to the square's sides. Both are padded from their sketches, the square one from the base and the round one from the duct's top (Figure 3): ```python bottom = px.QuickSketch().RectangleC(140, 140).RectangleC(94, 94) # the opening a hole: nested in the flange bottom.Circle(9 / 2, center=(60, 60), id="hole") bottom.CircularPattern("hole", "origin", 4) top = px.QuickSketch(px.Plane(px.XY, 114)).Circle(120 / 2).Circle(74 / 2) top.Circle(9 / 2, center=(50 * math.cos(math.pi / 4), 50 * math.sin(math.pi / 4)), id="hole") top.CircularPattern("hole", "origin", 4) transition = duct + px.Pad(bottom, 6) + px.Pad(top, 6) ``` ```python nbsphinx="hide-input" Row((bottom, "The square flange", {}), (top, "The round flange", {}), (transition, "The transition", {"views": [(transition, "shade"), (transition, "ink")], "angles": (-60, 0, 30)}), caption="The flanges") ``` ## The checks These are the volume, the mass in steel S235 (7.85 g/cm³) and the center of mass, with the origin in the middle of the bottom, to compare with your own model. A loft through two sections joins them linearly, so its cross-section's area is a quadratic function of the height. Simpson's rule then gives its volume exactly from the areas at its ends and in its middle, $\tfrac{h}{6}\,(A_\mathrm{bottom} + 4 A_\mathrm{middle} + A_\mathrm{top})$, over its height of 108. `px.Intersect3d` cuts the two lofts at mid height. The flanges add their faces' areas times 6: ```python def simpson(solid, bottom_area, top_area): middle = px.Intersect3d(px.Plane(px.XY, 60), solid).mass # the section at mid height return 108 / 6 * (bottom_area + 4 * middle + top_area) exact = (simpson(outside, 100**2, math.pi * 40**2) - simpson(inside, 94**2, math.pi * 37**2) + 6 * bottom.Face().mass + 6 * top.Face().mass) # each flange's face: its holes nested in it c = transition.center print("volume", round(transition.mass, 1), "mm³, exact", round(exact, 1)) print("mass", round(transition.mass * 7.85e-6, 3), "kg") print("center of mass", round(c.z, 2), "above the bottom, off the axis by", round(math.hypot(c.x, c.y), 6)) ``` The transition as STEP, for another CAD system: ```python transition.WriteStep("square_to_round.step") ``` The transition as STEP: [square_to_round.step](square_to_round.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 plane y = 0 in front, the view from above below it front = px.XZ # the front view: x to the right, z up half = transition * occ.Box((-80, 0, -1), (80, 80, 130)) # 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([], solid=half, plane=front, hidden_edges=False, hatch=cut, axes=[((0, -6), (0, 128))], measures=[((-50, 6), (50, 6), -12, 100), ((-40, 114), (40, 114), 132, "⌀80"), ((-70, 0), (70, 0), -24, 140), ((-60, 120), (60, 120), 148, "⌀120"), ((70, 0), (70, 120), 84, 120), ((-70, 0), (-70, 6), -78, 6), ((-60, 114), (-60, 120), -78, 6)]) plan = View([], solid=transition, plane=px.XY, at=(0, -140), hidden_edges=True, circles=[((0, 0), 50, "pitch")], axes=[((-76, 0), (76, 0)), ((0, -76), (0, 76))] + [((-66, y), (66, y)) for y in (-60, 60)] + [((x, -66), (x, 66)) for x in (-60, 60)], measures=[((-60, -60), (60, -60), -80, 120), ((60, -60), (60, 60), 80, 120)], diameters=[((60, 60), 4.5, (45, "leader", 4)), ((50 * math.cos(math.radians(135)), 50 * math.sin(math.radians(135))), 4.5, (135, "leader", 4)), ((0, 0), 50, (300, "leader")), ((0, 0), 37, (240, "leader"))]) data = Table([("Wall", "3 all around")], at=(105, -95), widths=(40, 60), height=14) pictorial = Pictorial(transition, at=(150, -40), width=90, angles=(-60, 0, 30)) drawing = SheetFigure([section, plan, pictorial, data], margin=30, notes=("planex2d.org", "3D exercise 16"), first_angle=True) print("scale", drawing.planex_scale, "| paper", [round(v * 25.4) for v in drawing.get_size_inches()], "mm") save_figure(drawing, "square-to-round", dpi=200) plt.close(drawing) ```