Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions designs/round_duct_butterfly_damper/family.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"family": "round_duct_butterfly_damper",
"standard": "Ruskin MDRS25 / CDR94 (round manual balancing / control damper)",
"base_plane": "XY",
"description": "Round HVAC manual balancing (butterfly) damper in the closed position: a rolled sheet-metal frame spool, a circular blade filling the bore on a diametral through-shaft, and a shaft extension for the locking hand quadrant. Axle is round or square.",
"source": "Ruskin MDRS25 (light quadrant) + CDR94 (heavy control) round-damper datasheets; frame depth / blade / shaft ranges from their published size tables",
"contributor": "BenchCAD-org"
}
36 changes: 36 additions & 0 deletions designs/round_duct_butterfly_damper/part.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""round_duct_butterfly_damper — the parametric part.

A round HVAC manual balancing damper, drawn in the CLOSED position: a rolled
sheet-metal spool (annular frame) that slips into round duct, a circular
butterfly blade filling the bore, and a through-shaft on a diameter that carries
the blade and extends out one side for the locking hand quadrant. The shaft is
round, or a square axle (MDRS25 style). Airflow runs along the spool axis (Z).

Interface + examples: docs/DESIGN_SPEC.md
"""

import cadquery as cq


def build(duct_d, wall, frame_depth, blade_t, shaft_d, handle_ext, stub, square_shaft):
outer_r = duct_d / 2.0
bore_r = outer_r - wall

# frame spool: annular sheet-metal tube along Z, centred at z=0
result = cq.Workplane("XY").circle(outer_r).circle(bore_r).extrude(frame_depth / 2.0, both=True)

# butterfly blade: a disc filling the bore, centred at mid-depth (closed)
blade = cq.Workplane("XY").circle(bore_r).extrude(blade_t / 2.0, both=True)
result = result.union(blade)

# control shaft on a diameter (along X): bearing stub one side, handle
# extension the other. Round rod, or a square axle.
length = duct_d + stub + handle_ext
if square_shaft:
shaft = cq.Workplane("YZ").rect(shaft_d, shaft_d).extrude(length)
else:
shaft = cq.Workplane("YZ").circle(shaft_d / 2.0).extrude(length)
shaft = shaft.translate((-(outer_r + stub), 0.0, 0.0))
result = result.union(shaft)

return result
Binary file added designs/round_duct_butterfly_damper/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions designs/round_duct_butterfly_damper/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""round_duct_butterfly_damper — the benchmark generator spec.

A round manual balancing / butterfly volume damper. PARAM_SPEC declares each
build() parameter; check() holds the engineering rules a reviewer audits. No
parameters are coupled, so there is no refine(). Spec: docs/DESIGN_SPEC.md

Sources:
- Ruskin MDRS25 (light manual quadrant damper): 20 ga frame + blade (~0.9 mm),
3/8" (10 mm) square axle, 6" (152 mm) frame, Ø 4"-20" (102-508 mm).
- Ruskin CDR94 (heavy round control damper): frame depth 152/229/305 mm, blade
1/4"-3/8" (6-10 mm), round axle 3/4"-3" (19-76 mm), Ø 102-1829 mm.
Frame depth / blade / shaft step UP with diameter across the size range.
"""


# ── 1. PARAM_SPEC ────────────────────────────────────────────────────────────
PARAM_SPEC = {
"duct_d": dict(
desc="nominal round-duct / spool outer diameter",
unit="mm",
range={"easy": (120.0, 350.0), "medium": (110.0, 650.0), "hard": (110.0, 1000.0)},
source="Ruskin MDRS25/CDR94 round damper sizes (102-1829 mm range)",
askable=True,
),
"wall": dict(
desc="rolled sheet-metal frame wall thickness",
unit="mm",
range={"easy": (0.9, 1.6), "medium": (0.9, 2.2), "hard": (0.9, 3.0)},
source="sheet-metal gauge (20 ga ~0.9 mm up); SMACNA round-duct gauge",
askable=True,
),
"frame_depth": dict(
desc="axial length of the frame spool",
unit="mm",
range={"easy": (150.0, 175.0), "medium": (150.0, 240.0), "hard": (150.0, 320.0)},
source="Ruskin CDR94 frame depth 152 / 229 / 305 mm (steps with diameter)",
askable=True,
),
"blade_t": dict(
desc="butterfly blade (disc) thickness",
unit="mm",
range={"easy": (1.5, 3.0), "medium": (1.5, 6.0), "hard": (1.5, 10.0)},
source="MDRS25 20 ga blade -> CDR94 1/4-3/8 in (1.5-10 mm)",
askable=True,
),
"shaft_d": dict(
desc="control shaft / axle diameter (round rod, or square-bar side)",
unit="mm",
range={"easy": (12.0, 22.0), "medium": (10.0, 45.0), "hard": (10.0, 76.0)},
source="MDRS25 10 mm square axle; CDR94 round axle 19-76 mm",
askable=True,
),
"handle_ext": dict(
desc="shaft extension beyond the frame on the hand-quadrant side",
unit="mm",
range={"easy": (45.0, 65.0), "medium": (40.0, 90.0), "hard": (40.0, 120.0)},
source="MDRS25 hand-quadrant standoff (~2-2.75 in) -> proportion",
askable=True,
),
"stub": dict(
desc="shaft stub beyond the frame on the bearing side",
unit="mm",
range={"easy": (6.0, 14.0), "medium": (5.0, 22.0), "hard": (5.0, 32.0)},
source="proportion (bearing-side journal length)",
askable=True,
),
"square_shaft": dict(
desc="axle is a square bar (1, MDRS25) vs a round rod (0, CDR94)",
unit="",
range={"easy": (0, 0), "medium": (0, 1), "hard": (0, 1)},
source="MDRS25 square axle vs CDR94 round axle (documented variants)",
choices={"easy": [0], "medium": [0, 1], "hard": [0, 1]},
feature=True,
),
}


# ── 2. check ─────────────────────────────────────────────────────────────────
def check(p: dict) -> list[str]:
"""Engineering constraints (empty = valid). Each cites its rule."""
bad = []
d = p["duct_d"]

# frame is rolled sheet, not a thick pipe
if p["wall"] > d / 40.0:
bad.append("wall > D/40: frame is rolled sheet metal, not thick pipe (SMACNA gauge)")

# axle must not choke the bore — CDR94 shaft/diameter ratio stays small
if p["shaft_d"] > 0.20 * d:
bad.append("shaft_d > 0.20*D: axle too thick for the bore (CDR94 axle/diameter ratio)")

# blade must seat within the axial depth of the spool
if p["blade_t"] >= p["frame_depth"]:
bad.append("blade_t >= frame_depth: blade must seat inside the spool depth")

# the bore must stay open around the axle
if p["shaft_d"] >= d - 2.0 * p["wall"]:
bad.append("shaft_d >= bore: axle would span the whole bore, no blade area")

# hand-quadrant side extends past the bearing stub (quadrant standoff)
if p["handle_ext"] <= p["stub"]:
bad.append("handle_ext <= stub: hand-quadrant side must stand off past the bearing stub")

return bad
Loading