In one of my regular Civil 3D API training the following discussion came up: how can we automate the creation of a pipe network? As you might already know, automate a process requires a very well defined rule for it, otherwise we cannot program it.
So, in this case, they just need to create a Pipe Network from one high point to another lower point. After some discussion, we agreed that a interesting first attempt could be along the water drop, which will give us a path with minimum cuts on the terrain.
This is not a ultimate solution, it still requires adjustments. For instance, the water drop path can circle around and give us a very long path, or reach a big slope, or many other issues… so please feel free to comment and suggest!
Sure we can do a similar with regular Water Drop features, creating an Alignment from the pline and using it to the network. But here we can define a distance between structs that may not follow the original path, align them and, of course, automate the whole process.
This sample requires a tin surface, a empty network already created (just to simplify) and a starting point. This first part of the sample will perform the operation.
Private Sub CreateNetworkAlongWaterDropPath( _
networkName As String, _
surfaceId As ObjectId, _
initialPoint As Point3d)
Dim db As Database = Application. _
DocumentManager.MdiActiveDocument.Database
Using trans As Transaction = db. _
TransactionManager.StartTransaction
Try
' abrir a superficie
Dim surface As TinSurface = trans.GetObject( _
surfaceId, OpenMode.ForRead)
' get the plines from the initial point
Dim idPlines As ObjectIdCollection = surface. _
Analysis.CreateWaterdrop( _
initialPoint.Convert2d(New Plane()), _
Autodesk.Civil.WaterdropObjectType.Polyline3D)
' open the pline that will be the path
' here we'll simplify and get the first
' pline of the collection, but keep in mind
' that a water drop can return multiple
Dim idPline As ObjectId = idPlines.Item(0)
Dim pline As Polyline3d = trans.GetObject( _
idPline, OpenMode.ForRead)
' get the network by name
Dim rede As Network = Nothing
Dim civilDoc As CivilDocument = _
CivilApplication.ActiveDocument
For Each idRede As ObjectId In _
civilDoc.GetPipeNetworkIds()
rede = trans.GetObject(idRede, _
OpenMode.ForRead)
If (rede.Name.Equals(networkName)) _
Then Exit For
Next
' and upgrade to write
rede.UpgradeOpen()
' get the part family and size for
' pipes and structs
Dim idStructFamily As ObjectId = _
SelectPartFamily( _
rede.PartsListId, _
DomainType.Structure)
Dim idStructSize As ObjectId = _
SelectPartSize(idStructFamily)
Dim idPipeFamily As ObjectId = _
SelectPartFamily( _
rede.PartsListId, _
DomainType.Pipe)
Dim idPipeSize As ObjectId = _
SelectPartSize(idPipeFamily)
' iterate through the pline
Dim previousPoint As Point3d = pline.GetPointAtDist(0)
Dim previousStructPoint As ObjectId = ObjectId.Null
Dim distanceBetweenStruct As Double = 100
For distanceAtPline As Double = 0 To pline.Length _
Step distanceBetweenStruct
' point at the current distance
Dim currentPoint As Point3d = _
pline.GetPointAtDist(distanceAtPline)
' let's align the struct with the pline
Dim angle As Double = _
pline.GetFirstDerivative(currentPoint). _
AngleOnPlane(New Plane())
Dim idStruct As ObjectId
rede.AddStructure(idStructFamily, _
idStructSize, _
currentPoint,
angle,
idStruct, True)
If (distanceAtPline > 1) Then ' skipt the first
' create a line segment
Dim pipeLineSegment As New LineSegment3d( _
previousPoint, currentPoint)
' create the pipe using the line segment
Dim pipeId As ObjectId
rede.AddLinePipe(idPipeFamily, _
idPipeSize, pipeLineSegment, _
pipeId, True)
' open the pipe to connect to the structs
Dim pipe As Pipe = trans.GetObject(pipeId, _
OpenMode.ForWrite)
' start struct
pipe.ConnectToStructure(ConnectorPositionType.Start, _
previousStructPoint, True)
' end struct
pipe.ConnectToStructure(ConnectorPositionType.End, _
idStruct, True)
End If
previousStructPoint = idStruct
previousPoint = currentPoint
Next
' erase the plines...
For Each id As ObjectId In idPlines
trans.GetObject(id, OpenMode.ForWrite).Erase()
Next
' commit changes
trans.Commit()
Catch
' something went wrong...
trans.Abort()
End Try
End Using
End Sub
And the result is a network as shown below, with the structs aligned.
Now we need a code responsible for the user interface: selections. The following code sample do it.
<CommandMethod("createNetworkAlongWaterDropPath")> _
Public Sub CmdCreateNetworkAlongWaterDropPath()
Dim ed As Editor = Application. _
DocumentManager.MdiActiveDocument.Editor
' ask the user for the network
Dim networkName As String = SelectNetwork()
If (String.IsNullOrWhiteSpace(networkName)) _
Then Exit Sub
' select the surface
Dim opSelSurface As New _
PromptEntityOptions("Select a TinSurface: ")
opSelSurface.SetRejectMessage _
("Only TinSurface")
opSelSurface.AddAllowedClass _
(GetType(TinSurface), True)
Dim resSurface As PromptEntityResult = _
ed.GetEntity(opSelSurface)
If (resSurface.Status <> PromptStatus.OK) _
Then Exit Sub
' select the initial point
Dim resInitialPoint As PromptPointResult = _
ed.GetPoint("Select initial point: ")
If (resInitialPoint.Status <> PromptStatus.OK) _
Then Exit Sub
CreateNetworkAlongWaterDropPath( _
networkName, _
resSurface.ObjectId, _
resInitialPoint.Value)
End Sub
This code requires some ‘utility’ functions for selections, which basically use a form to ask for information using a Combo Box list.
Private Function SelectNetwork() As String
Dim frm As New FormSelect
Dim db As Database = Application.DocumentManager. _
MdiActiveDocument.Database
Using trans As Transaction = db.TransactionManager.StartTransaction
Dim civilDoc As CivilDocument = CivilApplication.ActiveDocument
For Each idRede As ObjectId In civilDoc.GetPipeNetworkIds()
Dim rede As Network = trans.GetObject(idRede, OpenMode.ForRead)
frm.cboOptions.Items.Add(rede.Name)
Next
End Using
If (frm.cboOptions.Items.Count = 0) Then
Application.DocumentManager.MdiActiveDocument. _
Editor.WriteMessage("No networks available")
Return String.Empty
End If
frm.cboOptions.SelectedIndex = 0
If (Application.ShowModalDialog(frm) _
<> Windows.Forms.DialogResult.OK) _
Then
Return String.Empty
End If
Return frm.cboOptions.SelectedItem
End Function
Private Function SelectPartFamily(idPartList As ObjectId, _
domainType As DomainType) As ObjectId
Dim db As Database = Application.DocumentManager. _
MdiActiveDocument.Database
Using trans As Transaction = db.TransactionManager.StartTransaction
Dim partList As PartsList = trans.GetObject(idPartList, _
OpenMode.ForRead)
Dim idsPartFamily As ObjectIdCollection = _
partList.GetPartFamilyIdsByDomain(domainType)
Dim frm As New FormSelect
For Each partFamilyId As ObjectId In idsPartFamily
Dim family As PartFamily = trans.GetObject(partFamilyId, _
OpenMode.ForRead)
frm.cboOptions.Items.Add(family.Name)
Next
frm.cboOptions.SelectedIndex = 0
Application.ShowModalDialog(frm)
Dim partFamName As String = frm.cboOptions.SelectedItem
For Each idFamilia As ObjectId In idsPartFamily
Dim pFamily As PartFamily = trans.GetObject(idFamilia, _
OpenMode.ForRead)
If (partFamName.Equals(pFamily.Name)) Then Return idFamilia
Next
End Using
End Function
Private Function SelectPartSize(idFamily As ObjectId) As ObjectId
Dim db As Database = Application.DocumentManager. _
MdiActiveDocument.Database
Using trans As Transaction = db.TransactionManager.StartTransaction
Dim frm As New FormSelect
Dim partFam As PartFamily = trans.GetObject(idFamily, _
OpenMode.ForRead)
For i As Integer = 0 To partFam.PartSizeCount - 1
Dim size As PartSize = trans.GetObject(partFam.Item(i), _
OpenMode.ForRead)
frm.cboOptions.Items.Add(size.Name)
Next
frm.cboOptions.SelectedIndex = 0
Application.ShowModalDialog(frm)
Dim selectedSize As String = frm.cboOptions.SelectedItem
For i As Integer = 0 To partFam.PartSizeCount - 1
Dim size As PartSize = trans.GetObject(partFam.Item(i), _
OpenMode.ForRead)
If (size.Name.Equals(selectedSize)) Then Return size.ObjectId
Next
End Using
End Function