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

[1]:
import math
import netgen.occ as occ
import planex as px

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):

[3]:
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)]
The square and the circle, the circle's arcs one for each side of the square
Figure 1: 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):

[5]:
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
The loft from the square to the circle
(a) The loft from the square to the circle
Hollowed by the inside's loft
(b) Hollowed by the inside's loft
Figure 2: 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):

[7]:
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)
The square flange
(a) The square flange
The round flange
(b) The round flange
The transition
(c) The transition
Figure 3: 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:

[9]:
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))
volume 205450.7 mm³, exact 205450.7
mass 1.613 kg
center of mass 51.68 above the bottom, off the axis by 0.0

The transition as STEP, for another CAD system:

[10]:
transition.WriteStep("square_to_round.step")

The transition as STEP: square_to_round.step