Pressure Pipe Runs & Add Fittings

Thanks to Camber package we are now able to create pressure pipe runs for multiple selected polylines, but I am a little greedy :sweat_smile:, and asking for any way in Dynamo to simulate the lengthen of multiple pipe run alignments to add automatically branch fittings, as demonstrated below.

Animation

Please share with me your ideas, as it will help us a lot and save great time and effort rather than doing it manually for each pipe branching.

May this help you
If not you find note for your idea

Appreciate your reply, but unfortunately I am not pro enough to know and apply what you shared. It would be great if you can elaborate more. Thank you in advance.

Unfortunately
This needs a civil version 2023 ŲŒ I only have the 2022 version If you attach an example file with the dynamo can someone help you

Iā€™ll take a look at this when I find time. Sorry for the delay.

1 Like

So just to bump and keep this thread alive Iā€™ll post some progress. I found a few hours to mess around with this last night. This is actually by far the most complicated python script Iā€™ve attempted. I didnā€™t expect it to be so difficult but you canā€™t simply pair up pipes and say add branch fitting. You have to:

1.) Break the pipe runs at the point of intersections. I was able to do do this by getting the parent alignment of the pipe runs and checking if the parameter at the intersection points is not 0 or 1. If it isnā€™t zero or 1, then I know the point in the loop is mid pipe run and the pipe run should be ā€œbreakedā€ at the given point. Here is the working code:

*EDIT: First section of code that breaks the pipes bugfixed 06/29/2022.

# Load the Python Standard and DesignScript Libraries
import sys
import clr

# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')

# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *

# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *

# The inputs to this node will be stored as a list in the IN variables.
pipeNetworkDynamo = IN[0]
inputPoints = IN[1]

points2d = [Point2d(inputPoint.X, inputPoint.Y) for inputPoint in inputPoints]

points3d = [Point3d(inputPoint.X, inputPoint.Y, inputPoint.Z) for inputPoint in inputPoints]

adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

with adoc.LockDocument():
    with adoc.Database as db:

        with db.TransactionManager.StartTransaction() as t:
            cdoc = CivilDocument.GetCivilDocument(db)
            ppn = pipeNetworkDynamo.InternalDBObject
            ppn.UpgradeOpen()
            
            
            params = []
            
            i=0
            
            for point2d, point3d in zip(points2d, points3d):
            
            	pipeRuns = [pipeRun for pipeRun in ppn.PipeRuns]
            	
            	for pipeRun in pipeRuns:
            		#partIds = pipeRun.GetPartIds()
            		#pipes = []
            		alignmentId = pipeRun.AlignmentId
            		alignment = t.GetObject(alignmentId, OpenMode.ForWrite)
            		startPoint = alignment.StartPoint
            		endPoint = alignment.EndPoint
            		closestPointOnAlignment = alignment.GetClosestPointTo(point3d, 0)
            		
            		
            		#if closest point on the alignment is very close to the point in the loop, continue.
            		if abs(closestPointOnAlignment.X - point3d.X) < 1 and abs(closestPointOnAlignment.Y - point3d.Y) < 1:
            			param = alignment.GetParameterAtPoint(closestPointOnAlignment)
            			params.append(param)
            			
            			if param != 0 and param != 1:
            				i = i + 1
            				pipeName = "NewPipe" + str(i)
            				pipeRun.Break(point2d, pipeName)
            		

            		

                    


            t.Commit()
            pass

# Assign your output to the OUT variable.
OUT = i, pipeRuns, params, pipeName

2.) Add a fitting to the pipe intersection points. (These will not come in with a desired rotation and will not automatically connect to any pipes)
This worked for me pipe network wide:

# Load the Python Standard and DesignScript Libraries
import sys
import clr

# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')

# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *

# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *

# The inputs to this node will be stored as a list in the IN variables.
pressurePipeNetworkDynamo = IN[0]
inputPoints = IN[1]
pressurePartsListDynamo = IN[2]

points = [Point3d(inputPoint.X, inputPoint.Y, inputPoint.Z) for inputPoint in inputPoints]

adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

with adoc.LockDocument():
    with adoc.Database as db:

        with db.TransactionManager.StartTransaction() as t:
            cdoc = CivilDocument.GetCivilDocument(db)
            ppn = pressurePipeNetworkDynamo.InternalDBObject
            ppn.UpgradeOpen()
            
            ppl = pressurePartsListDynamo.InternalDBObject
            ppl.UpgradeOpen()
            
            parts = ppl.GetParts(PressurePartDomainType.Fitting)
            
            for part in parts:
                if "tee" in part.Description:
                    partSize = part
            
            for point in points:
                fitting = ppn.AddFitting(point, partSize)
            
            
            
            t.Commit()
            pass

# Assign your output to the OUT variable.
OUT = points, partSize

3.) We have to connect the new fittings to the respective adjacent pipes. I was able to get some test code to connect one port of a single selected fitting to one port of a single selected pipe. This feels really complicated and difficult for me because you have to use ā€œport indexesā€ of the pipes and fittings. I also donā€™t see a property for pressure pipes that allow you input a point and get back an index of a pipe (whether it is the start or end). Maybe Iā€™ll have to get the parent alignment or geometry of a pipe / pipe run and use the parameter at point property to check if the index of the connection should be set to the start or end of the pipe, for each pipe adjacent to each fitting. A tad confusing! Here is the test code for connecting a single port to a single pipe:

# Load the Python Standard and DesignScript Libraries
import sys
import clr

# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')

# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *

# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *

# The inputs to this node will be stored as a list in the IN variables.
pressureFittingDynamo = IN[0]
pipe1Dynamo = IN[1]


adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

with adoc.LockDocument():
    with adoc.Database as db:

        with db.TransactionManager.StartTransaction() as t:
            cdoc = CivilDocument.GetCivilDocument(db)
            pFitting = pressureFittingDynamo.InternalDBObject
            pFitting.UpgradeOpen()
            
            p1 = pipe1Dynamo.InternalDBObject
            p1.UpgradeOpen()
            
            p1Id = pipe1Dynamo.InternalObjectId
            
            pFitting.ConnectToPipe(2, p1Id, 0)
            
            
            t.Commit()
            pass

# Assign your output to the OUT variable.
OUT = pFitting.ConnectToPipe.__doc__, p1Id, type(p1Id)

@mzjensen Hey Zachri, just wondering if you have any advice on the port connection step. Would you go about it as I have described?

2 Likes

Okay. I fixed a ton of bugs in the code that connects the TEEs to the adjacent pipes. It ended up being pretty darn complicated. I also was able to group the pipes and fittings into their respective groups to be able to loop this code across multiple TEEs and pipes.
Now I just need to get the ā€œbreak pipesā€, ā€œadd teesā€ and ā€œconnect teesā€ steps strung together properly but the code should be complete and I should be able to post the .dyn soon.

# Load the Python Standard and DesignScript Libraries
import sys
import clr
import itertools

# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')

# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *

# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *

# The inputs to this node will be stored as a list in the IN variables.
inputPoints = IN[0]
pipeNetworkDynamo = IN[1]
allPoints = []
for point in inputPoints:
    convertedPoint = Point2d(round(point.X, 1), round(point.Y, 1))
    allPoints.append(convertedPoint)


adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

with adoc.LockDocument():
    with adoc.Database as db:

        with db.TransactionManager.StartTransaction() as t:
            cdoc = CivilDocument.GetCivilDocument(db)
            
            try:
                pipeNetwork = pipeNetworkDynamo.InternalDBObject
                allFittingIds = pipeNetwork.GetFittingIds()
                allPipeIds = pipeNetwork.GetPipeIds()
                
                allFittings = []
                allPipes = []
                allFittingLocations = []
                allPipeStartPoints = []
                allPipeEndPoints = []
                
                for fittingId in allFittingIds:
                    fitting = t.GetObject(fittingId, OpenMode.ForWrite)
                    allFittings.append(fitting)
                    
                for fitting in allFittings:
                    fittingLocation = fitting.Location
                    allFittingLocations.append(fittingLocation)
                
                for pipeId in allPipeIds:
                    pipe = t.GetObject(pipeId, OpenMode.ForWrite)
                    allPipes.append(pipe)
            
                for pipe in allPipes:
                    pipeStartPoint = pipe.StartPoint
                    pipeEndPoint = pipe.EndPoint
                    allPipeStartPoints.append(pipeStartPoint)
                    allPipeEndPoints.append(pipeEndPoint)
                    
                
                
                for point in allPoints:
                    pipes = []
                    pipeIds = []
                    
                    for fitting, fittingLocation in zip(allFittings, allFittingLocations):
                        if abs(fittingLocation.X - point.X) < 2 and abs(fittingLocation.Y - point.Y) < 2:
                            pFitting = fitting
                    
                    for pipe, pipeId, startPoint, endPoint in zip(allPipes, allPipeIds, allPipeStartPoints, allPipeEndPoints):
                        if (abs(startPoint.X - point.X) < 2 and abs(startPoint.Y - point.Y) < 2) or (abs(endPoint.X - point.X) < 2 and abs(endPoint.Y - point.Y) < 2):
                            pipes.append(pipe)
                            pipeIds.append(pipeId)
                            
                    bearings = []
                    pipePortIndexes = []
                            
                    p1 = pipes[0]
                    p1Id = pipeIds[0]
                    
                    p1StartPoint = p1.StartPoint
                    p1StartPoint = Point2d(round(p1StartPoint.X, 1), round(p1StartPoint.Y, 1))
                    p1EndPoint = p1.EndPoint
                    p1EndPoint = Point2d(round(p1EndPoint.X, 1), round(p1EndPoint.Y, 1))
                    p1Bearing = p1.Bearing
                    bearings.append(p1Bearing)
                    p1Curve = p1.BaseCurve
                    if abs(p1EndPoint.X - point.X) < 2 and abs(p1EndPoint.Y - point.Y) < 2:
                        p1PortIndex = 1
                    else:
                        p1PortIndex = 0
                    pipePortIndexes.append(p1PortIndex) 
                    
                    
                    p2 = pipes[1]
                    p2Id = pipeIds[1]
                    p2StartPoint = p2.StartPoint
                    p2StartPoint = Point2d(round(p2StartPoint.X, 1), round(p2StartPoint.Y, 1))
                    p2EndPoint = p2.EndPoint
                    p2EndPoint = Point2d(round(p2EndPoint.X, 1), round(p2EndPoint.Y, 1))
                    p2Bearing = p2.Bearing
                    bearings.append(p2Bearing)
                    p2Curve = p2.BaseCurve
                    if abs(p2EndPoint.X - point.X) < 2 and abs(p2EndPoint.Y - point.Y) < 2:
                        p2PortIndex = 1
                    else:
                        p2PortIndex = 0
                    pipePortIndexes.append(p2PortIndex)
                    
                    
                    p3 = pipes[2]
                    p3Id = pipeIds[2]
                    p3StartPoint = p3.StartPoint
                    p3StartPoint = Point2d(round(p3StartPoint.X, 1), round(p3StartPoint.Y, 1))
                    p3EndPoint = p3.EndPoint
                    p3EndPoint = Point2d(round(p3EndPoint.X, 1), round(p3EndPoint.Y, 1))
                    p3Bearing = p3.Bearing
                    bearings.append(p3Bearing)
                    p3Curve = p3.BaseCurve
            
            
                    if abs(p3EndPoint.X - point.X) < 2 and abs(p3EndPoint.Y - point.Y) < 2:
                        p3PortIndex = 1
                    else:
                        p3PortIndex = 0
                    pipePortIndexes.append(p3PortIndex)

                            
                    




                    deltas = []
                    bearingCounts = []
                    #for bearing in bearings:
            
                    bearing0differences = [bearings[0] - bearings[1], bearings[0] - bearings[2]]
                    bearing0count = 0
                    for bearingDifference in bearing0differences:
                        if abs(bearingDifference) > 1:
                            bearing0count = bearing0count + 1
                    bearingCounts.append(bearing0count)
            
                    bearing1differences = [bearings[1] - bearings[0], bearings[1] - bearings[2]]
                    bearing1count = 0
                    for bearingDifference in bearing1differences:
                        if abs(bearingDifference) > 1:
                            bearing1count = bearing1count + 1
                    bearingCounts.append(bearing1count)
            
            
                    bearing2differences = [bearings[2] - bearings[0], bearings[2] - bearings[1]]
                    bearing2count = 0
                    for bearingDifference in bearing2differences:
                        if abs(bearingDifference) > 1:
                            bearing2count = bearing2count + 1
                    bearingCounts.append(bearing2count)
            
                    middlePipeIndex = bearingCounts.index(max(bearingCounts))
            

            
                    bearingsDegrees = [bearing * 57.2958 for bearing in bearings]
            
                    #get vectors
            
                    pipeVectors = []
            
                    #get p1 Vector
                    if abs(p1EndPoint.X - point.X) < 2 and abs(p1EndPoint.Y - point.Y) < 2: #if p1 endpoint is == point, then should be startpoint of vector
                        p1VectorStartPoint = p1EndPoint
                        p1VectorEndPoint = p1StartPoint
                    else:
                        p1VectorStartPoint = p1StartPoint
                        p1VectorEndPoint = p1EndPoint
                
                    p1Vector = Vector3d(p1VectorEndPoint.X - p1VectorStartPoint.X, p1VectorEndPoint.Y - p1VectorStartPoint.Y, 0)
            
                    pipeVectors.append(p1Vector)
            
                    #get p2 Vector
                    if abs(p2EndPoint.X - point.X) < 2 and abs(p2EndPoint.Y - point.Y) < 2: #if p2 endpoint is == point, then should be startpoint of vector
                        p2VectorStartPoint = p2EndPoint
                        p2VectorEndPoint = p2StartPoint
                    else:
                        p2VectorStartPoint = p2StartPoint
                        p2VectorEndPoint = p2EndPoint
                
                    p2Vector = Vector3d(p2VectorEndPoint.X - p2VectorStartPoint.X, p2VectorEndPoint.Y - p2VectorStartPoint.Y, 0)
            
                    pipeVectors.append(p2Vector)
            
                    #get p3 Vector
                    if abs(p3EndPoint.X - point.X) < 2 and abs(p3EndPoint.Y - point.Y) < 2: #if p3 endpoint is == point, then should be startpoint of vector
                        p3VectorStartPoint = p3EndPoint
                        p3VectorEndPoint = p3StartPoint
                    else:
                        p3VectorStartPoint = p3StartPoint
                        p3VectorEndPoint = p3EndPoint
                
                    p3Vector = Vector3d(p3VectorEndPoint.X - p3VectorStartPoint.X, p3VectorEndPoint.Y - p3VectorStartPoint.Y, 0)
            
                    pipeVectors.append(p3Vector)
            
                    normalVector = Vector3d(0, 0, 1)
            
                    pipeIndexes = [0, 1, 2]
                    pipeIndexes.remove(middlePipeIndex)
            
                    angle1PipeIndex = pipeIndexes[0]
                    angle1 = pipeVectors[angle1PipeIndex].GetAngleTo(pipeVectors[middlePipeIndex], normalVector)
            
                    angle2PipeIndex = pipeIndexes[1]
                    angle2 = pipeVectors[angle2PipeIndex].GetAngleTo(pipeVectors[middlePipeIndex], normalVector)
            
                    #Connect middle port to middle pipe
            
                    pFitting.ConnectToPipe(2, pipeIds[middlePipeIndex], pipePortIndexes[middlePipeIndex])
            
            
            
                    if angle1 > angle2:
                        pFitting.ConnectToPipe(0, pipeIds[angle1PipeIndex], pipePortIndexes[angle1PipeIndex])
                        pFitting.ConnectToPipe(1, pipeIds[angle2PipeIndex], pipePortIndexes[angle2PipeIndex])
                    else:
                        pFitting.ConnectToPipe(1, pipeIds[angle1PipeIndex], pipePortIndexes[angle1PipeIndex])
                        pFitting.ConnectToPipe(0, pipeIds[angle2PipeIndex], pipePortIndexes[angle2PipeIndex])
                    
                
            
            
            except:
                pass
                
            
            
            

            t.Commit()
            pass

# Assign your output to the OUT variable.
OUT = "DONE"

1 Like

Keep it up Shaun! :+1:

I was going to add this to Camber a long time ago but ended up scrapping it since nobody had actually asked for it yet. Itā€™s still a pretty new area of the API so I was going to wait for it to be more complete. But it looks like there might be some renewed interest now. Do you want to contribute your work to the package?

2 Likes

Sure! I donā€™t know how but Iā€™d love to try to contribute. Especially if it all works out and ends up being stable at scale. This is definitely a first for me in terms of complexity. imposter syndrome alert.

Just keep going in Python for now!

1 Like

Will do! Iā€™m going to be learning C# soon too. Anton is mailing me a copy of his book and Iā€™m excited to work through it.

1 Like

@Ahmed.Kamal.Moussa

Here is your solution. Make sure the only alignments being fed into python are the pressure pipe network alignments.


AddTees.dyn (75.9 KB)
AddTees-postScript.dwg (1017.8 KB)
AddTees-preScript.dwg (1008.0 KB)

6 Likes

First of all, thanks a lot for your effort and great contribution in finding a solution. Unfortunately, when I am trying to run the script, the ā€œBreak Pipesā€ python node keeps failing giving me the warning as shown below. I am not a python professional, so I was not able to troubleshoot the issue by myself.

Can you share a dwg? I can look into it when i get c3d access again.

I managed to troubleshoot the warning (the script was choosing part list rather than the one referenced to the created pressure network). However, is there a way to:

  1. Add the tees on the alignments intersection points without breaking up the pipe runs, as this will introduce n+1 alignments (n= number of drawn alignments before running the script), which will affect greatly the project file size and navigating;

  2. ā€œAdd teesā€ python node is looking for part size with description ā€œteeā€ in it. Is it possible to automatically add tees based on the connected pipes part size dimensions (equal or reduced tees), and without the description constraint;

  3. Created tees have zero fitting insertion point elevation. Is it possible to get the correct elevation from the connected pipes center line elevation.

If I remember correctly the pipe runs have to be broken up. The pipe run entity is what has the break method available to it. I donā€™t believe there is a method to break a pipe entity. And the pipe has to end at the intersection points to tell the TEE what port to connect to.

It may be possible but I think it will be pretty difficult. May have to check the part type of the pipes (Help) that get grouped into the sets of 3s for the TEEs and use a sort of dictionary to return the correct corresponding tee. Could you show an example of what you mean in images? The different scenarios that would need to be taken into account?

Looks like the .AddFitting Method takes a point3d (Help), so it should be possible to use the z elevation of one of the pipes at the intersection. I believe Iā€™m just using the point 3d of the alignments, so I thought that would work. Iā€™ll see if I can overwrite the z values of the points based on the pipe elevations at the intersection.

I wonā€™t have C3D access until the 11th and have a laundry list of stuff I want to address but Iā€™ll definitely look into this. If you have an example dwg it may help me test.

2 Likes

Book :open_mouth:
Is there a book about c# or python and Dynamo?
/P

@Anton_Huizinga wrote a book for C# :slight_smile:

Start Programming in .NET for AutoCAD https://a.co/d/daFCAta

3 Likes

@WrightEngineering Hi I am working on pressure pipe network, Im trying to automate pipe fitting process but .AddFitting() is not working in my 2022 version and asking for only one parameterā€¦can you suggest any alternative.

If itā€™s a path-based pressure network, we are still hoping for a dynamic method to add all the branch fitting tees between the crossing pipe runs. If itā€™s a part-based network, then you can use Camber package to add fittings by location.