Is it possible to use/call the various tools that are present in WaterGEMS through WaterObjects.NET? I'm mainly interested in the following tools:
- Reverse pipe
- Batch Morph
- Batch Pipe Split
I've already tried to reverse pipes in WaterObjects by interchanging the start and stop nodes of a pipe, and reversing the GeometryPoint variable of the pipe (vertices). This seems to work, however when I open the model, the check-valve icon of the pipe is still facing the wrong direction, this can be manualy fixed by updating the drawing though. Is it possible to call the update of the drawing through WaterObjects.NET? I could also separately program each step of the Batch Morph tool, but using the tool directly in WaterObjects.NET would be much easier...
The reverse pipe tool is mainly used to make sure the check-valve in the pipe is working in the correct direction. The batch morph tool will mainly be used to convert taps to junctions after which the batch pipe split tool is used to split the pipes.
Joeri Legierse said:Is it possible to use/call the various tools that are present in WaterGEMS through WaterObjects.NET? I'm mainly interested in the following tools:
- Reverse pipe
- Batch Morph
- Batch Pipe Split
I checked with one of our developers who indicated that this could get a bit complicated. Could you tell us a bit more about why you need to perform such functions using a Waterobjects.NET tool? What version of WaterObjects/WaterGEMS are you using?
Joeri Legierse said:This seems to work, however when I open the model, the check-valve icon of the pipe is still facing the wrong direction, this can be manualy fixed by updating the drawing though. Is it possible to call the update of the drawing through WaterObjects.NET?
The appearance of the drawing pane (including the pipes and nodes and "decorations" like the check valve symbol) is stored in the model's .DWH file. Deleting this file would force a re-sync when re-opening the model. Here is some additional feedback from one of our developers:
If they are opening just the SQLite database using WO.Net, then they may need to check this:
This is would be done when they use ConnectionProperty.Filename. This flag will make sure that internal counters are incremented when geometry is changed (like he does with reverse pipe). If the counter is correctly incremented, this should also trigger a drawing sync on opening in the UI.
Jesse DringoliTechnical Support Manager, OpenFlows ProductsBentley Communities Site AdministratorBentley Systems, Inc.
All these tools will be used in a more elaborate workflow, I'd be happy to explain it in more detail in a non-public conference call.
I'm opening the model using the following code:
I believe it is part of Haestad.Framework.Application
Hi Joeri, one of our developers will reply here shortly. Thanks for your patience.
I apologize for the delayed response.
Unfortunately, these tools can be complicated to use with WaterObjects.NET as they are tightly integrated into the UI (so it makes it difficult not to use them without the UI). I'll provide some information here to hopefully get you going with them. If you need clarification on anything, please ask.
For reverse pipe you will need:
ParentFormUIModel (for LayoutController), IDomainProject (for DomainDataSet), DomainManager (can get from LayoutController)
// DomainDataSet is assembed to be from an open IDomainProject.
// DomainManager is also assumed to be valid
// This sample code will reverse the nodes of all pipes in a WaterGEMS model.
// You also need a valid IProject as well. Since you indicate you are opening the model with ProjectManager, you should have it.
IDomainElementManager pipeManager = DomainDataSet.DomainElementManager((int)DomainElementType.IdahoPipeElementManager);
int linkElementType = (int)DomainElementType.IdahoPipeElementManager;
// Using a "using" block here is important so that the IHmIDDelayedCollection that is created is correctly disposed. Event
// though ElementIDs() returns an HmIDCollection it is really, internally, an IHmIDDelayedCollection.
using (HmIDCollection pipeIDs = pipeManager.ElementIDs())
IField upstreamNodeIdField = pipeManager.DomainElementField(StandardFieldName.HmiTopologyStartNodeID, StandardAlternativeName.HmiTopology);
IField downstreamNodeIdField = pipeManager.DomainElementField(StandardFieldName.HmiTopologyStopNodeID, StandardAlternativeName.HmiTopology);
IHmIDToObjectDictionary upstreamNodeIDs = (IHmIDToObjectDictionary)upstreamNodeIdField.GetValues();
IHmIDToObjectDictionary downstreamNodeIDs = (IHmIDToObjectDictionary)downstreamNodeIdField.GetValues();
// At this point, a simple loop can be used to batch reverse the pipes.
for (int i = 0; i < pipeIDs.Count; ++i)
int pipeID = pipeIDs[i];
object objUpstreamNodeID = upstreamNodeIDs[pipeID];
object objDownstreamNodeID = downstreamNodeIDs[pipeID];
if (objUpstreamNodeID is int upstreamNodeID &&
objDownstreamNodeID is int downstreamNodeID)
// This check will make sure both the start and stop
// node IDs are set. If either are undefined (dictionary will have value of null for pipe id)
// it will skip the pipe from being reversed.
int upstreamNodeType = DomainDataSet.DomainElementTypeID(upstreamNodeID);
int downstreamNodeType = DomainDataSet.DomainElementTypeID(downstreamNodeID);
ElementIdentifier startNodeIdentifier = new ElementIdentifier(upstreamNodeType, upstreamNodeID);
ElementIdentifier stopNodeIdentifier = new ElementIdentifier(downstreamNodeType, downstreamNodeID);
ElementIdentifier linkIdentifier = new ElementIdentifier(linkElementType, pipeID);
// This call does the reversal. It take care of the geometry as well.
// Also keep in mind that this call will create an undoable action. It uses the UndoManager from the provided Project
DomainManager.ReverseStartStopNodes(linkIdentifier, startNodeIdentifier, stopNodeIdentifier, (ILabeledElementDomainProject)project);
// Because you mentioned that the drawing didn't seem to update, you can use this call to do it. I am using a NullProgressIndicator here
// but you can use a ProgressIndicatorForm instead to show visual status. Up to you.
// This call requires the LayoutController. This can be retrieed from the ParentFormUIModel which I assume you are using as well.
ParentFormUIModel.LayoutController.SynchronizeWithDatabase(IGraphicalProject)project, new NullProgressIndicator());
Batch morph is nearly as easy as reverse. Again, it will use the DomainManager to do the bulk of the work. You will need the same required items listed for reverse. FeatureManager is also required but that can be retrieved from casting the ApplicationModel to IMappingApplicationModel.
// For this code, you need to provide a list of node ids to morph into a target element type.
// Any ids that are already of the target element type will be ignored.
// You should EXCLUDE ids of the types ISO valves, spot elevations, SCADA elements. These
// types cannot be morphed. The UI code automatically excludes these but this code will
// not check for them. I am assuming you will NOT include these types in the list of IDs
// to morph.
// This method will also require the IFeatureManager. You can get this by casting the
// ApplicationModel to IMappingApplicationModel and using the property on that interface
public void PerformBatchMorph(int targetElementType, HmIDCollection ids)
if (DomainManager is IBatchDomainManager batchDomainManager)
// Create a composite undoable action which will be provided to the morph method, HOWEVER,
// for this code, it will NOT be added to the project's undo manager (unless you modify the code to do that).
CompositeUndoableAction action = m_project.UndoManager.NewCompositeUndoableAction(TextManager.Current["undoActionBatchMorph"]);
for (int i = 0; i < ids.Count; ++i)
int nodeID = ids[i];
int typeID = DomainDataSet.DomainElementTypeID(nodeID);
if (typeID != targetElementType)
// If the node is NOT the target element, then morph
IGeometryPointField geometryField = (IGeometryPointField)DomainDataSet.FieldManager.DomainElementField(
StandardFieldName.HmiGeometry, (int)AlternativeType.HmiDataSetGeometryAlternative, typeID);
GeometryPoint point = geometryField.GetPoint(nodeID);
if (((IGraphicalProject)m_project).Drawing != null)
point = ((IGraphicalProject)m_project).Drawing.ConvertPhysicalToWorldCoordinate(point);
DomainManager.MorphNode(targetElementType, new ElementIdentifier(typeID, nodeID), point,
null, DomainManager.CurrentProject, action, true);
bool needsRefresh = !action.IsEmpty;
// For now, NOT adding the composite action to the undo manager.
// You can optionally uncomment this code.
if (DomainManager is IBatchDomainManager batchDomainManager)
Batch Pipe Split
This is the most complicated of these three tools. I need to review the code in more detail to extract out what you need as it is much more tightly integrated with the UI than these two tools. For now, see if you can get batch reverse and morph working in your code and hopefully by the time you are done with that, I will have something for batch pipe split.
Again, if you have ANY questions, just ask.
Kris CulinSenior Software EngineerCivil EngineeringBentley Systems, Inc.
Answer Verified By: Joeri Legierse
Thanks a lot! I will give this a try, having quickly read the code you propose, I can understand what is being done in sequence. It contains the puzzle pieces I was missing!
I ran into some problems implementing your code and think I've also come up with some solutions for them but I'm not sure they are correct:
- In the code for pipe reversal you wrote, I cannot seem to use an HmIDCollection in the using block because it doesn't implement IDisposable. My solution is to cast it to an IHmIDDelayedCollection instead, that does implement IDisposable.
- In the code for the batch morph, you declare the CompositeUndoableAction in the try block. This makes it unavailable in the finally block. My solution is to declare the action right before (outside) the try block.
- In the code for batch morph, the variable batchDomainManager is declared twice. My solution is to declare it using a different variablename on it's second declaration.
Sorry about that. I wrote the code without the benefit of Visual Studio and IntelliSense (straight in the communities editor).
For the using block of HmIDCollection, use IHmIDDelayedCollection instead. Change pipeIDs to ids. Then the first line of the using block should be:
HmIDCollection pipeIDs = new HmIDCollection((HmIDCollection)ids);
That should get you going there.
Yes, on the composite undoable action. My bad.
Your way for IBatchDomainManager should work. You could also try checking to see if it is not null for the second location and then call EndBatch() on it. That might work.
I'm still working on getting some usable code for batch pipe split for you to use.
Do you have any update on the batch pipe split code?
I sincerely apologize for the delay.
I will get something to you later this morning that should get you going.