MEPFittingByPointAndCurve in a vertical duct/ pipe

Hi,

ive been using the MEPFittingByPointAndCurve node from MEPOver to add fittings into ducts. and it works great.
so the next step was to try and add to it so it adds fire dampers into the vertical ducts where they pass through floors.

with walls i used curve.project node which works fine, but with a floor that wouldnt work.

so i did this which seems to work by giving me the correct intersection point.

i took the X and Y of the vertical duct that intersects with the floor, then replaced the Z value with the floor level. in theory this should work?

but the node fails.

family Instance.ByPoint puts the familiy in the correct place just doesn’t cut it into the duct or change the size.

Has anyone had any success in getting it to work with vertical ducts or pipes?

Thanks,

@T_Pover

Can you show us the Python Script?
You are pointing a Damper_Round to a MEPFitting - isn’t it a Duct Accessory?

The python is the same as the one in the MEPOver node (I’ve not changed anything), says line 183 but i cant figure out if its that thats not going to work with a vertical duct.

Yes it is a duct accessory, but it works fine with the node on horizontal ductwork.

Python Script.....

import clr
import math
clr.AddReference(“RevitServices”)
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference(“RevitAPI”)
from Autodesk.Revit.DB import *

clr.AddReference(“RevitNodes”)
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

from System.Collections.Generic import List

if isinstance(IN[0], list):
ducts = UnwrapElement(IN[0])
else:
ducts = [UnwrapElement(IN[0])]

if isinstance(IN[1], list):
points = IN[1]
else:
points = [IN[1]]

if isinstance(IN[2], list):
famtype = UnwrapElement(IN[2])
else:
famtype = [UnwrapElement(IN[2])]
ftl = len(famtype)

def IsParallel(dir1,dir2):
if dir1.Normalize().IsAlmostEqualTo(dir2.Normalize()):
return True
if dir1.Normalize().Negate().IsAlmostEqualTo(dir2.Normalize()):
return True
return False

def measure(startpoint, point):
return startpoint.DistanceTo(point)

def copyElement(element, oldloc, loc):

elementlist = List[ElementId]()
elementlist.Add(element.Id)
OffsetZ = (oldloc.Z - loc.Z)*-1
OffsetX = (oldloc.X - loc.X)*-1
OffsetY = (oldloc.Y - loc.Y)*-1
direction = XYZ(OffsetX,OffsetY,OffsetZ)
newelementId = ElementTransformUtils.CopyElements(doc,elementlist,direction)
newelement = doc.GetElement(newelementId[0])
return newelement

def GetDirection(faminstance):
for c in faminstance.MEPModel.ConnectorManager.Connectors:
conn = c
break
return conn.CoordinateSystem.BasisZ

#global variables for rotating new families
tempfamtype = None
xAxis = XYZ(1,0,0)
def placeFitting(duct,point,familytype,lineDirection):

toggle = False
isVertical = False


global tempfamtype
if tempfamtype == None:
	tempfamtype = familytype
	toggle = True
elif tempfamtype != familytype:
	toggle = True
	tempfamtype = familytype
level = duct.ReferenceLevel

width = 4
height = 4
radius = 2
round = False
connectors = duct.ConnectorManager.Connectors
for c in connectors:
	if c.ConnectorType != ConnectorType.End:
		continue
	shape = c.Shape
	if shape == ConnectorProfileType.Round:
		radius = c.Radius
		round = True	
		break
	elif shape == ConnectorProfileType.Rectangular or shape == ConnectorProfileType.Oval:
		if abs(lineDirection.Z) == 1:
			isVertical = True
			yDir = c.CoordinateSystem.BasisY
		width = c.Width
		height = c.Height
		break
	
		
point = XYZ(point.X,point.Y,point.Z-level.Elevation)
newfam = doc.Create.NewFamilyInstance(point,familytype,level,Structure.StructuralType.NonStructural)
doc.Regenerate()
transform = newfam.GetTransform()
axis = Line.CreateUnbound(transform.Origin, transform.BasisZ)
global xAxis
if toggle:
	xAxis = GetDirection(newfam)
zAxis = XYZ(0,0,1)

if isVertical:
	angle = xAxis.AngleOnPlaneTo(yDir,zAxis)
else:
	angle = xAxis.AngleOnPlaneTo(lineDirection,zAxis)


ElementTransformUtils.RotateElement(doc,newfam.Id,axis,angle)
doc.Regenerate()

if lineDirection.Z != 0:
	newAxis = GetDirection(newfam)
	yAxis = newAxis.CrossProduct(zAxis)
	angle2 = newAxis.AngleOnPlaneTo(lineDirection,yAxis)
	axis2 = Line.CreateUnbound(transform.Origin, yAxis)
	ElementTransformUtils.RotateElement(doc,newfam.Id,axis2,angle2)

result = {}
connpoints = []
famconns = newfam.MEPModel.ConnectorManager.Connectors

if round:
	for conn in famconns:
		if IsParallel(lineDirection,conn.CoordinateSystem.BasisZ) == False:
			continue
		if conn.Shape != shape:
			continue
		conn.Radius = radius
		connpoints.append(conn.Origin)
else:
	for conn in famconns:
		if IsParallel(lineDirection,conn.CoordinateSystem.BasisZ) == False:
			continue
		if conn.Shape != shape:
			continue
		conn.Width = width
		conn.Height = height
		connpoints.append(conn.Origin)

result[newfam] = connpoints
return result

def ConnectElements(duct, fitting):
ductconns = duct.ConnectorManager.Connectors
fittingconns = fitting.MEPModel.ConnectorManager.Connectors
for conn in fittingconns:
for ductconn in ductconns:
result = ductconn.Origin.IsAlmostEqualTo(conn.Origin)
if result:
ductconn.ConnectTo(conn)
break
return result

def SortedPoints(fittingspoints,ductStartPoint):
sortedpoints = sorted(fittingspoints, key=lambda x: measure(ductStartPoint, x))
return sortedpoints

ductsout =
fittingsout =
combilist =

TransactionManager.Instance.EnsureInTransaction(doc)
for typ in famtype:
if typ.IsActive == False:
typ.Activate()
doc.Regenerate()

for i,duct in enumerate(ducts):
ListOfPoints = [x.ToXyz() for x in points[i]]
familytype = famtype[i%ftl]
#create duct location line
ductline = duct.Location.Curve
ductStartPoint = ductline.GetEndPoint(0)
ductEndPoint = ductline.GetEndPoint(1)
#get end connector to reconnect later
endIsConnected = False
endrefconn = None
for ductconn in duct.ConnectorManager.Connectors:
if ductconn.Origin.DistanceTo(ductEndPoint) < 5/304.8:
if ductconn.IsConnected:
endIsConnected = True
for refconn in ductconn.AllRefs:
if refconn.ConnectorType != ConnectorType.Logical and refconn.Owner.Id.IntegerValue != duct.Id.IntegerValue:
endrefconn = refconn

#sort the points from start of duct to end of duct
pointlist = SortedPoints(ListOfPoints,ductStartPoint)

ductlist = []
newFittings = []
ductlist.append(duct)

tempStartPoint = None
tempEndPoint = None

lineDirection = ductline.Direction

for i,p in enumerate(pointlist):		
	output = placeFitting(duct,p,familytype,lineDirection)
	newfitting = output.keys()[0]
	newFittings.append(newfitting)
	fittingpoints = output.values()[0]
	
	tempPoints = SortedPoints(fittingpoints,ductStartPoint)
	if i == 0:
		tempEndPoint = tempPoints[0]
		tempStartPoint = tempPoints[1]			
		newduct = copyElement(duct,ductStartPoint,tempStartPoint)	
		duct.Location.Curve = Line.CreateBound(ductStartPoint,tempEndPoint)
		ductlist.append(newduct)
		combilist.append([duct,newfitting])
		combilist.append([newduct,newfitting])
	else:
		combilist.append([newduct,newfitting])
		tempEndPoint = tempPoints[0]
		newduct = copyElement(duct,ductStartPoint,tempStartPoint)
		ductlist[-1].Location.Curve = Line.CreateBound(tempStartPoint,tempEndPoint)
		tempStartPoint = tempPoints[1]
		ductlist.append(newduct)
		combilist.append([newduct,newfitting])

newline = Line.CreateBound(tempStartPoint,ductEndPoint)
ductlist[-1].Location.Curve = newline

ductsout.append(ductlist)
fittingsout.append(newFittings)
doc.Regenerate()

if endIsConnected:
	for conn in ductlist[-1].ConnectorManager.Connectors:
		if conn.Origin.DistanceTo(ductEndPoint) < 5/304.8:
			endrefconn.ConnectTo(conn)

TransactionManager.Instance.TransactionTaskDone()

TransactionManager.Instance.EnsureInTransaction(doc)
for combi in combilist:
ConnectElements(combi[0],combi[1])
TransactionManager.Instance.TransactionTaskDone()

OUT = ductsout, fittingsout

I think you have to break up the points into 2 seperate lists as the node expects a List of Points per duct.

2 Likes

Cant believe it was that simple. :weary:

works like a charm!

thanks again @T_Pover :+1::+1:

out of interest how would you have gone about getting the point at the floor intersection?

1 Like

I like using BIMorph’s intersection nodes for that. Works really fast when compared to Dynamo’s geometry intersection nodes.
image

1 Like

Thanks, im using the bimorph nodes already, i tried to do this with geometries and soo much more success with the bimorph nodes.

I meant how would you go about getting the actual points into the MEPFittings node.

with walls i used the project.curve node so if i got this right… it projects the curve with a Z Vector and looks for the point the wall curve intersects with the duct curve. and thats what goes into your MEP node.

That worked fine with a duct going through multiple walls.

with floors i cant use project.curve. i assume its got it it a complex shame and needs to project in X/Y rather than Z.

so i used the elevation of the floor to replace the Z value of the point.

this seems to work ok, but if i have a duct travelling through 2 (or more) floors it only adds one damper.
I end up with 2 Points & 1 Duct.

what would you use instead so as to get the points with multiple floors on 1 duct . or is it possible to use curve.project on a floor?

I’ve attached dumbed down script without all the linked file bits in, incase i’m not making much sense…:crazy_face:

FireDampers.dyn (18.2 KB)

if i could duplicate the ducts to match the levels in the list that could work…:thinking:

I see what you mean. To project the curve onto the floor you would need to query the solid of the floor.
Attached is a small graph that uses the BIMorph node to create a fire damper at every floor intersection. That only works for non sloped floors and 100% vertical ducts though.
DuctFitting in vertical ducts.dyn (13.1 KB)

2 Likes

Thanks @T_Pover, i did try getting the solid of the floor and surface of the floor a few other things but couldn’t get it to work properly.

all seems to be working great! thanks for all your help! :+1::+1:

1 Like