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
23 changes: 23 additions & 0 deletions CPipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ def GetResources(self):
}


class insertDuctReduction:
def IsActive(self):
if FreeCAD.ActiveDocument is None:
return False
else:
return True

def Activated(self):
import pForms

pForms.insertDuctReductionForm()

def GetResources(self):
return {
"Pixmap": "Quetzal_InsertReduct",
"MenuText": QT_TRANSLATE_NOOP("Quetzal_InsertDuctReduction", "Insert a duct reduction"),
"ToolTip": QT_TRANSLATE_NOOP(
"Quetzal_InsertDuctReduction", Quetzal_tooltips.duct_reduction_tooltip
),
}


class insertCap:
def IsActive(self):
if FreeCAD.ActiveDocument is None:
Expand Down Expand Up @@ -712,6 +734,7 @@ def GetResources(self):
addCommand("Quetzal_InsertPipe", insertPipe())
addCommand("Quetzal_InsertElbow", insertElbow())
addCommand("Quetzal_InsertReduct", insertReduct())
addCommand("Quetzal_InsertDuctReduction", insertDuctReduction())
addCommand("Quetzal_InsertCap", insertCap())
addCommand("Quetzal_InsertTee", insertTee())
addCommand("Quetzal_InsertValve", insertValve())
Expand Down
1 change: 1 addition & 0 deletions InitGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def Initialize(self):
"Quetzal_InsertElbow",
"Quetzal_InsertTee",
"Quetzal_InsertReduct",
"Quetzal_InsertDuctReduction",
"Quetzal_InsertCap",
"Quetzal_InsertCoupling",
"Quetzal_InsertValve",
Expand Down
9 changes: 9 additions & 0 deletions Quetzal_tooltips.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@
"Reverse button:\n"
"Rotates the reducer 180 degrees about its attachment port.\n"
"\n"
)

duct_reduction_tooltip = (
"Tool to insert a rectangular HVAC duct transition/reducer\n"
"_________________________________________________ \n"
"Usage \n"
"Select the rectangular duct reduction family from the rating list, then select the inlet/outlet size and transition length preset.\n"
"Click 'Insert' to create a standalone rectangular duct transition at the origin.\n"
"The generated object stores inlet width/height, outlet width/height, wall thickness, length, and outlet offsets as editable properties.\n"
)

cap_tooltip = (
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Here are some of the planned developments for this Workbench:
- [ ] Icrease valve design detail
- [ ] Add HVAC duct support.
- [ ] Elbow
- [ ] Reduction
- [X] Reduction
- [ ] Branch
- [ ] Round duct
- [ ] Square duct
Expand Down
30 changes: 30 additions & 0 deletions pCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,36 @@ def makeReduct(propList=[], pos=None, Z=None, conc=True, smallerEnd=False, ratin
a.Label = translate("Objects", "Reduct")
return a


def makeDuctReduction(propList=[], pos=None, Z=None, smallerEnd=False, rating="Rectangular"):
"""Adds a rectangular duct transition/reducer object.

propList is one optional list with 9 elements:
PSize, W1, H1, W2, H2, thk, Length, OffsetX, OffsetY
"""
if pos == None:
pos = FreeCAD.Vector(0, 0, 0)
if Z == None:
Z = FreeCAD.Vector(0, 0, 1)
a = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Duct-Reduction")
if len(propList) == 9:
pFeatures.DuctReduction(a, rating, *propList)
else:
pFeatures.DuctReduction(a, rating)
if a.ViewObject:
ViewProvider(a.ViewObject, "Quetzal_InsertReduct")
a.Placement.Base = pos
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), Z)
a.Placement.Rotation = rot.multiply(a.Placement.Rotation)
if smallerEnd:
initPos = a.Placement.Base
rotateTheTubeAx(a, FreeCAD.Vector(0, 1, 0), 180)
finalPos = a.Placement.Base
a.Placement.move(initPos - finalPos)
a.Label = translate("Objects", "Duct Reduction")
return a


def doReduct(rating="SCH-STD", propList=[], pypeline=None, pos=None, Z=None, conc=True, smallerEnd=False):
"""propList[] =
PSize (string): nominal diameter (major end)
Expand Down
117 changes: 116 additions & 1 deletion pFeatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "oddtopus"
__url__ = "github.com/oddtopus/dodo"
__license__ = "LGPL 3"
objs = ["Pipe", "Elbow", "Reduct", "Cap", "Flange", "Tee", "Ubolt", "Valve"]
objs = ["Pipe", "Elbow", "DuctReduction", "Reduct", "Cap", "Flange", "Tee", "Ubolt", "Valve"]
metaObjs = ["PypeLine", "PypeBranch"]

from os.path import abspath, dirname, join
Expand Down Expand Up @@ -1378,6 +1378,121 @@ def execute(self, fp):
fp.PortDirections = [FreeCAD.Vector(0, 0, -1), FreeCAD.Vector(0, 0, 1)] #in either case, ports face +Z and -Z
super(Reduct, self).execute(fp) # perform common operations


class DuctReduction:
"""Class for object PType="DuctReduction".

Rectangular duct transition/reducer driven by inlet/outlet width and
height, wall thickness, transition length, and optional outlet offsets.
"""

def __init__(
self,
obj,
rating="Rectangular",
PSize="600x300-400x200",
W1=600,
H1=300,
W2=400,
H2=200,
thk=1.0,
L=386,
OffsetX=0,
OffsetY=0,
):
obj.Proxy = self
obj.addProperty(
"App::PropertyString",
"PType",
"DuctReduction",
QT_TRANSLATE_NOOP("App::Property", "Type of duct feature"),
).PType = "DuctReduction"
obj.addProperty(
"App::PropertyString",
"PRating",
"DuctReduction",
QT_TRANSLATE_NOOP("App::Property", "Duct fitting family"),
).PRating = rating
obj.addProperty(
"App::PropertyString",
"PSize",
"DuctReduction",
QT_TRANSLATE_NOOP("App::Property", "Nominal duct transition size"),
).PSize = PSize
for prop, value, text in [
("W1", W1, "Inlet width"),
("H1", H1, "Inlet height"),
("W2", W2, "Outlet width"),
("H2", H2, "Outlet height"),
("thk", thk, "Wall thickness"),
("Height", L, "Transition length"),
("OffsetX", OffsetX, "Outlet horizontal offset"),
("OffsetY", OffsetY, "Outlet vertical offset"),
]:
obj.addProperty(
"App::PropertyLength",
prop,
"DuctReduction",
QT_TRANSLATE_NOOP("App::Property", text),
)
setattr(obj, prop, value)
obj.addProperty(
"App::PropertyString",
"Profile",
"DuctReduction",
QT_TRANSLATE_NOOP("App::Property", "Section dim."),
).Profile = str(obj.W1) + "x" + str(obj.H1) + ">" + str(obj.W2) + "x" + str(obj.H2)
obj.addProperty(
"App::PropertyVectorList",
"Ports",
"PBase",
QT_TRANSLATE_NOOP("App::Property", "Ports position relative to the origin of Shape"),
)
obj.addProperty(
"App::PropertyVectorList",
"PortDirections",
"PBase",
QT_TRANSLATE_NOOP("App::Property", "Port directions relative to the origin of Shape"),
)
self.execute(obj)

def onChanged(self, fp, prop):
return None

def execute(self, fp):
w1 = max(float(fp.W1), 1.0)
h1 = max(float(fp.H1), 1.0)
w2 = max(float(fp.W2), 1.0)
h2 = max(float(fp.H2), 1.0)
thk = max(min(float(fp.thk), w1 / 2 - 0.1, h1 / 2 - 0.1, w2 / 2 - 0.1, h2 / 2 - 0.1), 0.1)
length = max(float(fp.Height), 1.0)
offset = FreeCAD.Vector(float(fp.OffsetX), float(fp.OffsetY), length)
fp.Profile = str(fp.W1) + "x" + str(fp.H1) + ">" + str(fp.W2) + "x" + str(fp.H2)

inlet = self._rect_wire(vO, vX, vY, w1, h1)
outlet = self._rect_wire(offset, vX, vY, w2, h2)
inner_inlet = self._rect_wire(vO, vX, vY, w1 - 2 * thk, h1 - 2 * thk)
inner_outlet = self._rect_wire(offset, vX, vY, w2 - 2 * thk, h2 - 2 * thk)

outer = Part.makeLoft([inlet, outlet], True, False, False)
inner = Part.makeLoft([inner_inlet, inner_outlet], True, False, False)
fp.Shape = outer.cut(inner)
fp.Ports = [vO, offset]
fp.PortDirections = [FreeCAD.Vector(0, 0, -1), FreeCAD.Vector(0, 0, 1)]

def _rect_wire(self, center, xdir, ydir, width, height):
x = xdir.normalize().multiply(width / 2)
y = ydir.normalize().multiply(height / 2)
pts = [
center - x - y,
center + x - y,
center + x + y,
center - x + y,
center - x - y,
]
return Part.Wire(Part.makePolygon(pts))


class Cap(pypeType):
"""Class for object PType="Cap"
Cap(obj,[PSize="DN50",OD=60.3,thk=3])
Expand Down
62 changes: 62 additions & 0 deletions pForms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,68 @@ def apply(self):
def changeSize(self, s):
super().changeSize(s)


class insertDuctReductionForm(dodoDialogs.protoPypeForm):
"""Dialog to insert rectangular HVAC duct transitions/reducers."""

def __init__(self):
super(insertDuctReductionForm, self).__init__(
translate("insertDuctReductionForm", "Insert duct reductions"),
"DuctReduction",
"Rectangular",
"reduct.svg",
x,
y,
)
self.btn_insert.setDefault(True)
self.btn_insert.setFocus()
self.show()
self.lastDuctReduction = None

def fillSizes(self):
self.sizeList.clear()
self.pipeDictList = []
fname = "DuctReduction_" + self.PRating + ".csv"
fpath = join(dirname(abspath(__file__)), "tablez", fname)
try:
with open(fpath, "r", encoding="utf-8-sig") as fh:
self.pipeDictList = list(csv.DictReader(fh, delimiter=";"))
except Exception:
return

for row in self.pipeDictList:
label = "{} L{} off {},{}".format(
row["PSize"],
row.get("Length", ""),
row.get("OffsetX", "0"),
row.get("OffsetY", "0"),
)
self.sizeList.addItem(label)

def insert(self):
idx = self.sizeList.currentIndex()
if idx < 0 or idx >= len(self.pipeDictList):
return
row = self.pipeDictList[idx]
propList = [
row["PSize"],
float(pq(row["W1"])),
float(pq(row["H1"])),
float(pq(row["W2"])),
float(pq(row["H2"])),
float(pq(row["thk"])),
float(pq(row["Length"])),
float(pq(row.get("OffsetX", "0"))),
float(pq(row.get("OffsetY", "0"))),
]
FreeCAD.activeDocument().openTransaction(
translate("Transaction", "Insert duct reduction")
)
self.lastDuctReduction = pCmd.makeDuctReduction(propList, rating=self.PRating)
FreeCAD.activeDocument().commitTransaction()
FreeCAD.activeDocument().recompute()


class insertReductForm(dodoDialogs.protoPypeForm):
"""
Dialog to insert concentric reductions.
Expand Down
13 changes: 13 additions & 0 deletions tablez/DuctReduction_Rectangular.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PSize;W1;H1;W2;H2;thk;Length;OffsetX;OffsetY;Source
300x200-200x150;300;200;200;150;0.8;190;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
400x200-300x150;400;200;300;150;0.8;190;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
450x300-300x200;450;300;300;200;0.8;280;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
600x300-400x200;600;300;400;200;1.0;375;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
600x450-450x300;600;450;450;300;1.0;280;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
800x400-600x300;800;400;600;300;1.0;375;0;0;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
300x200-200x150-flat-bottom;300;200;200;150;0.8;190;0;25;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
400x200-300x150-flat-bottom;400;200;300;150;0.8;190;0;25;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
450x300-300x200-flat-bottom;450;300;300;200;0.8;375;0;50;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
600x300-400x200-flat-bottom;600;300;400;200;1.0;375;0;50;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
600x450-450x300-flat-bottom;600;450;450;300;1.0;560;0;75;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf
800x400-600x300-flat-bottom;800;400;600;300;1.0;375;0;50;https://www.northwestern.edu/facilities/docs/construction/design_guidelines/23_hvac/NU_23%203114%20-%20DUCTWORK.pdf