Invalid Duct/Pipe Connections

I’m using a slightly modified version of @T_Pover’s Element.ConnectInto node to connect existing duct and fittings. The only thing I added was an exception to allow rectangular duct as well as round.

	try:
		return openconn.CoordinateSystem.BasisZ,openconn.Origin,openconn.Radius
	except:
		return openconn.CoordinateSystem.BasisZ,openconn.Origin,openconn.Width

The code works. I can connect adjacent duct and duct fittings, but every so often I get the annoying “modified to be in the opposite direction” error.
image

I’m wondering if anyone has found a way around this. The code checks for the closest connector so it should be grabbing the correct ends. The small test case that I’m using even has one end of each element already connected so there’s only one set of connectors available.

Any suggestions would be welcome.

Hi Nick,
I think that node is the biggest node I made (in terms of number of lines of code), so adjusting it to suit your needs is quite a lot of work I’m afraid.
Hard to tell what’s going wrong, but I would think that you need to create an extra function for creating rectangular ducts and for every variable ‘radius1’ and ‘radius2’ in the code you will also need to include variables for the width and the height. Then use those variables for the creation of the rectangular ducts with said new function.

If I had to adjust the code it would take me a few hours I think, so I can only imagine it would take even longer for someone who didn’t write the code himself.

1 Like

I thought so too, but the “size” variable (radius or width) seems to be defined and used in only two of the definitions. So that’s where I made changes. The weird thing is that it works. Just inconsistently.

Lets say I have a run of elements (1, 2, 3, and 4) and I want to connect them consecutively (1 to 2, 2 to 3, 3 to 4.) I can connect each pair separately with no issues. But if I connect 1 and 2, then try to connect 2 and 3 it gives the error. However this doesn’t happen for every element/every pair. If I connect 2 and 3, then try to connect 3 and 4 it works. I’ve also noticed that the order seems to make a difference. Sometimes 1 to 2 then 2 to 3 fails, but 3 to 2 then 2 to 1 works. I just can’t seem to find the problem.

It is indeed weird that it runs :slight_smile: Could you share some more of what you have thus far? What exactly is your test setup in Revit and what does the Dynamo graph look like.

Absolutely.
I’m covering for 4 other people in our office right now so it’ll probably have to wait until tomorrow though. :slight_smile:

1 Like

So here’s the backstory on what’s happening and why we’re looking into this (it’s currently only a proof of concept.)
We have a government project (of course :roll_eyes:) that basically wants us to schedule demoed duct. (No need to go into why this is a bad idea or not going to work, we’ve already tried.) So this duct represents an existing system that has since been demoed. Once a system is demoed it loses all system information. The duct and fittings are no longer connected but are still immediately adjacent to each other. Our hope was that we would be able to reconnect the whole “system” and connected it back into the equipment for (brief) scheduling purposes.

Test setup: A simple AHU with some Undefined duct - nothing is connected.

Graph: The majority of my graph (not shown) is just filtering and sorting elements to get what I want. The codeblock is my list of elements (duct and fittings) already paired and ordered. List[0] is Connection 0 (blue) which is between Elements 0 and 1 (red), List[1] is Connection 1 which is between Elements 1 and 2, and so on.

I can connect each pair of elements individually (connect then reset), but connecting them consecutively (without disconnecting) gives the error for certain combinations.

I’ll paste my modified code here in its entirety (just in case anyone wants to look through it), but I’m pretty sure the only changes I made were here:
image
image

import clr

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)

if isinstance(IN[0], list):
	pipe1 = UnwrapElement(IN[0])
else:
	pipe1 = [UnwrapElement(IN[0])]
	
if isinstance(IN[1], list):
	pipe2 = UnwrapElement(IN[1])
else:
	pipe2 = [UnwrapElement(IN[1])]
	
offset = IN[2]
	
collector = FilteredElementCollector(doc)
pipetypes = collector.OfClass(Plumbing.PipeType).ToElements()
pipetypeIds = [x.Id for x in pipetypes]

collector2 = FilteredElementCollector(doc)
ducttypes = collector2.OfClass(Mechanical.DuctType).ToElements()
ducttypeIds = [x.Id for x in ducttypes]
	
def createPipe(point1,point2,systype,pipetype,level,radius):
	pipe = Plumbing.Pipe.Create(doc,systype,pipetype,level,point1,point2)
		
	param = pipe.get_Parameter(BuiltInParameter.RBS_PIPE_DIAMETER_PARAM)
	diameter = radius * 2
	param.Set(diameter)
	return pipe
	
def createDuctRound(point1,point2,systype,pipetype,level,radius):
	duct = Mechanical.Duct.Create(doc,systype,pipetype,level,point1,point2)
		
	param = duct.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM)
	diameter = radius * 2
	param.Set(diameter)
	return duct

def closest_point(point1,pipe):
	p2line = pipe.Location.Curve
	start = p2line.GetEndPoint(0)
	end = p2line.GetEndPoint(1)
	if point1.DistanceTo(start) < point1.DistanceTo(end):
		return end
	else:
		return start

def closest_connectors(pipe1, pipe2):
	try:
		conn1 = pipe1.ConnectorManager.UnusedConnectors
	except:
		conn1 = pipe1.MEPModel.ConnectorManager.UnusedConnectors
	try:
		conn2 = pipe2.ConnectorManager.UnusedConnectors
	except:
		conn2 = pipe2.MEPModel.ConnectorManager.UnusedConnectors
	
	dist = 100000000
	connset = None
	for c in conn1:
		for d in conn2:			
			conndist = c.Origin.DistanceTo(d.Origin)
			if conndist < dist:
				dist = conndist
				connset = [c,d]
	return connset
	
def closest_connectors_base(pipe1, pipe2):
	try:
		conn1 = pipe1.ConnectorManager.UnusedConnectors
	except:
		conn1 = pipe1.MEPModel.ConnectorManager.UnusedConnectors
	try:
		conn2 = pipe2.ConnectorManager.UnusedConnectors
	except:
		conn2 = pipe2.MEPModel.ConnectorManager.UnusedConnectors
	
	dist = 100000000
	connset = None
	for c in conn1:
		try:
			sys1 = c.PipeSystemType
		except:
			sys1 = c.DuctSystemType
		for d in conn2:			
			try:
				sys2 = d.PipeSystemType
			except:
				sys2 = d.DuctSystemType
			if sys1 != sys2:
				continue
			conndist = c.Origin.DistanceTo(d.Origin)
			if conndist < dist:
				dist = conndist
				connset = [c,d]
	return connset


		
def getBasis(element,element2):
	try:
		connMan = element.ConnectorManager
	except:
		connMan = element.MEPModel.ConnectorManager
	if connMan.UnusedConnectors.Size > 1:
		connset = closest_connectors_base(element,element2)
		try:
			return connset[0].CoordinateSystem.BasisZ,connset[0].Origin,connset[0].Radius
		except:
			return connset[0].CoordinateSystem.BasisZ,connset[0].Origin,connset[0].Width
	else:
		connectors = connMan.Connectors
		for conn in connectors:
			if conn.IsConnected == False:
				openconn = conn
				break
		try:
			return openconn.CoordinateSystem.BasisZ,openconn.Origin,openconn.Radius
		except:
			return openconn.CoordinateSystem.BasisZ,openconn.Origin,openconn.Width
	
listout = []
TransactionManager.Instance.EnsureInTransaction(doc)
for p1,p2 in zip(pipe1,pipe2):
	if p1.GetType() == Plumbing.Pipe or p1.GetType() == Mechanical.Duct:
		ptemp = p1
		p1 = p2
		p2 = ptemp
	
	basis1 = getBasis(p1,p2)
	basis2 = getBasis(p2,p1)
	
	radius1 = basis1[2]
	radius2 = basis2[2]
	
	#create points for duct/pipe creation
	line1 = Line.CreateUnbound(basis1[1],basis1[0])
	if abs(basis1[0].Z) == 1 and abs(basis2[0].Z) == 0:
		point1 = XYZ(basis1[1].X,basis1[1].Y,basis2[1].Z)
	else:
		if offset == 0:
			point1 = line1.Evaluate(radius1*5,False)
		else:
			point1 = line1.Evaluate(offset/304.8,False)
	
	line2 = Line.CreateUnbound(basis2[1],basis2[0])
	
	point3 = None
	projection = line2.Project(basis1[1])
	if basis1[0].Z == 0 and basis2[0].Z >= 0:
		point2 = XYZ(point1.X,point1.Y,basis2[1].Z)
		project2 = line2.Project(point2)
		if project2.Distance > 1/304.8:
			point3 = project2.XYZPoint
	else:
		point2 = projection.XYZPoint
	
	#determine pipe or duct as input and get pipe and system type
	level = p2.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM)
	if level == None:
		level = p1.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM).AsElementId()
	else:
		level = level.AsElementId()
		
	if p1.GetType() == Plumbing.Pipe or p2.GetType() == Plumbing.Pipe:
		try:
			pipetype = p2.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsElementId()
			if pipetype not in pipetypeIds:
				pipetype = p1.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsElementId()

			systype = p2.get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM)
			if systype == None or systype.AsValueString() == "Undefined":
				systype = p1.get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM).AsElementId()
			else:
				systype = systype.AsElementId()
				
			#p2 can connect to p1 directly	
			if projection.Distance < 1/304.8:
				endpoint = closest_point(basis1[1],p2)
				p2.Location.Curve = Line.CreateBound(endpoint,basis1[1])
				if radius1 != radius2:
					conns = closest_connectors(p2,p1)
					doc.Create.NewUnionFitting(conns[0],conns[1])
				else:
					conns = closest_connectors(p2,p1)
					conns[0].ConnectTo(conns[1])
			elif basis1[0].Z == 1:
				point1 = XYZ(basis1[1].X,basis1[1].Y,basis2[1].Z)
				pipe1 = createPipe(basis1[1],point1,systype,pipetype,level,radius1)
				if line2.Project(point1).Distance < 1/304.8:
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					conns = closest_connectors(pipe1,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				else:
					point2 = line2.Project(point1).XYZPoint
					pipe2 = createPipe(point1,point2,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
					conns = closest_connectors(pipe2,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
			elif basis1[0].Z == 0 and basis2[0].Z >= 0:
				if point3 == None:
					pipe1 = createPipe(basis1[1],point1,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					
					pipe2 = createPipe(point1,point2,systype,pipetype,level,radius1)
					
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
					conns = closest_connectors(pipe2,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				else:
					pipe1 = createPipe(basis1[1],point1,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					
					pipe2 = createPipe(point1,point2,systype,pipetype,level,radius1)
					
					pipe3 = createPipe(point2, point3, systype, pipetype, level, radius1)
					
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
					conns = closest_connectors(pipe2,pipe3)
					doc.Create.NewElbowFitting(conns[0],conns[1])
					
					conns = closest_connectors(pipe3,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
			#create pipes and elbows and then connect to p2 with an elbow
			else:
				pipe1 = createPipe(basis1[1],point1,systype,pipetype,level,radius1)
				conns = closest_connectors(pipe1,p1)
				conns[0].ConnectTo(conns[1])
				
				pipe2 = createPipe(point1,point2,systype,pipetype,level,radius1)
	
				conns = closest_connectors(pipe1,pipe2)
				doc.Create.NewElbowFitting(conns[0],conns[1])
				
				conns = closest_connectors(pipe2,p2)
				doc.Create.NewElbowFitting(conns[0],conns[1])
			listout.append("succes")
		except:
			listout.append("failed")		
	else:
		try:
			pipetype = p2.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsElementId()
			if pipetype not in ducttypeIds:
				pipetype = p1.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsElementId()
				
			systype = p2.get_Parameter(BuiltInParameter.RBS_DUCT_SYSTEM_TYPE_PARAM)
			if systype == None or systype.AsValueString() == "Undefined":
				systype = p1.get_Parameter(BuiltInParameter.RBS_DUCT_SYSTEM_TYPE_PARAM).AsElementId()
			else:
				systype = systype.AsElementId()
				
			#p2 can connect to p1 directly	
			if projection.Distance < 1/304.8:
				endpoint = closest_point(basis1[1],p2)
				p2.Location.Curve = Line.CreateBound(endpoint,basis1[1])
				if radius1 != radius2:
					conns = closest_connectors(p2,p1)
					doc.Create.NewUnionFitting(conns[0],conns[1])
				else:
					conns = closest_connectors(p2,p1)
					conns[0].ConnectTo(conns[1])
			elif basis1[0].Z == 1:
				point1 = XYZ(basis1[1].X,basis1[1].Y,basis2[1].Z)
				pipe1 = createDuctRound(basis1[1],point1,systype,pipetype,level,radius1)
				if line2.Project(point1).Distance < 1/304.8:
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					conns = closest_connectors(pipe1,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				else:
					point2 = line2.Project(point1).XYZPoint
					pipe2 = createDuctRound(point1,point2,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
					conns = closest_connectors(pipe2,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
			elif basis1[0].Z == 0 and basis2[0].Z >= 0:
				if point3 == None:
					pipe1 = createDuctRound(basis1[1],point1,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					
					pipe2 = createDuctRound(point1,point2,systype,pipetype,level,radius1)
					
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
					conns = closest_connectors(pipe2,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				else:
					pipe1 = createDuctRound(basis1[1],point1,systype,pipetype,level,radius1)
					conns = closest_connectors(pipe1,p1)
					conns[0].ConnectTo(conns[1])
					
					pipe2 = createDuctRound(point1,point2,systype,pipetype,level,radius1)
					
					pipe3 = createDuctRound(point2, point3, systype, pipetype, level, radius1)
					
					conns = closest_connectors(pipe1,pipe2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
					conns = closest_connectors(pipe2,pipe3)
					doc.Create.NewElbowFitting(conns[0],conns[1])
					
					conns = closest_connectors(pipe3,p2)
					doc.Create.NewElbowFitting(conns[0],conns[1])
				
			#create pipes and elbows and then connect to p2 with an elbow
			else:
				pipe1 = createDuctRound(basis1[1],point1,systype,pipetype,level,radius1)
				conns = closest_connectors(pipe1,p1)
				conns[0].ConnectTo(conns[1])
				
				pipe2 = createDuctRound(point1,point2,systype,pipetype,level,radius1)
	
				conns = closest_connectors(pipe1,pipe2)
				doc.Create.NewElbowFitting(conns[0],conns[1])
				
				conns = closest_connectors(pipe2,p2)
				doc.Create.NewElbowFitting(conns[0],conns[1])
			listout.append("succes")
		except:
			listout.append("failed")
TransactionManager.Instance.TransactionTaskDone()

OUT = listout

Interesting issue you’ve got there :slight_smile: I think the code could be a lot simpler as all you need is to reconnect. Wouldn’t it suffice to find all connectors with the same point and then connect those? I am gonna play around with this a bit.

Absolutely!

Like you said earlier, this is a pretty big chunk of code. I was able to (mostly) get it to do what I wanted without changing much so I was hoping I wouldn’t have to comb through everything. :sweat_smile:
This is partially why I took the route of sorting all my elements beforehand based on connection location.

I appreciate all your help, Taco.
And the MEPover package!

1 Like

I altered the code of another existing node a bit (Elbow.ByMEPCurves) which will take in a bunch of ducts or fittings and searches the closest connector for every connector from the list and then connects them.
I take it that you first undo the demolish action to allow the reconnection of the network? I guess Dynamo could handle this for you as well…

Anyway, here’s the code:

import clr

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)


pipes = UnwrapElement(IN[0])
margin = 0

connectors = {}
connlist = []

for pipe in pipes:
	try:
		conns = pipe.ConnectorManager.Connectors
	except:
		conns = pipe.MEPModel.ConnectorManager.Connectors
	for conn in conns:
		if conn.IsConnected:
			continue
		connectors[conn] = None
		connlist.append(conn)

for k in connectors.keys():
	mindist = 1000000
	closest = None
	for conn in connlist:
		if conn.Owner.Id.Equals(k.Owner.Id):
			continue
		dist = k.Origin.DistanceTo(conn.Origin)
		if dist < mindist:
			mindist = dist
			closest = conn
	if mindist > margin:
		continue
	connectors[k] = closest
	connlist.remove(closest)
	try:
		del connectors[closest]
	except:
		pass


for k,v in connectors.items():
	TransactionManager.Instance.EnsureInTransaction(doc)		
	try:
		k.ConnectTo(v)
	except:
		pass
	TransactionManager.Instance.TransactionTaskDone()

OUT = "Done"

5 Likes

Perfect! This looks like exactly what I was trying to accomplish!

Yes. The proposed workflow is to do just as you did; set Phase Demolished back to none, select the unconnected elements, then run Dynamo. It looks like you even got it to connect to the equipment as well, even better.

I’ll try it out and see if I can break anything. :wink:

Thanks again, Taco!

Great, I hope it works :slight_smile:

So far so good.

Now I’m going to see if I can get a whole run of duct from just a single element selection. The connecting works but it still requires a manual process of selecting all elements in the unconnected system which isn’t very convenient when you have a whole project’s worth of duct or piping systems to filter through.

I guess filtering for elements that are assigned to an undefined system classification should work? The node might get a little slow on big input lists though as there is a nested for loop in it (cross lacing).

That was my thought too. I’m going to see if there’s any difference between letting the code run on all Undefined elements or trying to filter by connector points first.

@T_Pover, thanks a lot for the code. This is perfect. :+1::+1::+1:

1 Like

Thanks Taco,
Your MEP Connector nodes came to the rescue again today. Just re-connected several thousand duct accessories that had become disconnected after switching families.

1 Like

Awesome, great to hear Joseph. Nice to get such positive feedback :slight_smile:

Thank you so much for this code! Is there a way to edit this code to allow the pipe/duct that was demoed to be reconnected to a never demoed chain. This would allow the pipes/ducts to regain their system type selections. Thanks!

@T_Pover I would like to make this work for fabrication .ITM’s in Revit. I would think it would be an easy thing to edit the python script to make it work, but I just don’t have enough knowledge of Python. What I am trying is not working. Think you could give me a hand?