]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java
Add a wall thickness property to pipe runs.
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / utils / ComponentUtils.java
index fdd5b5bbe8d89c3c4c8a3f4204ae6c618a36186d..6bbabe1e251c9feaf3f10e1767a56ce827ec91f0 100644 (file)
@@ -1,17 +1,26 @@
 package org.simantics.plant3d.utils;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.vecmath.Vector3d;
 
 import org.simantics.Simantics;
 import org.simantics.db.ReadGraph;
+import org.simantics.db.RequestProcessor;
 import org.simantics.db.Resource;
 import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.utils.NameUtils;
 import org.simantics.db.exception.DatabaseException;
+import org.simantics.g3d.math.MathTools;
 import org.simantics.g3d.scenegraph.GeometryProvider;
+import org.simantics.g3d.scenegraph.ParametricGeometryProvider;
 import org.simantics.layer0.Layer0;
+import org.simantics.plant3d.geometry.ParameterRead;
 import org.simantics.plant3d.ontology.Plant3D;
 import org.simantics.plant3d.scenegraph.EndComponent;
 import org.simantics.plant3d.scenegraph.Equipment;
@@ -21,30 +30,44 @@ import org.simantics.plant3d.scenegraph.P3DRootNode;
 import org.simantics.plant3d.scenegraph.PipeRun;
 import org.simantics.plant3d.scenegraph.PipelineComponent;
 import org.simantics.plant3d.scenegraph.TurnComponent;
+import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint;
+import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction;
+import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.simantics.plant3d.scenegraph.controlpoint.PipingRules;
 
 public class ComponentUtils {
 
+       private final static Logger LOGGER = LoggerFactory.getLogger(ComponentUtils.class);
        
        private static Map<String,Class<? extends PipelineComponent>> clazzes = new HashMap<String, Class<? extends PipelineComponent>>();
        private static Map<String,GeometryProvider> providers = new HashMap<String,GeometryProvider>();
+       private static Map<String,String> names = new HashMap<String,String>();
        
-       public static void preloadCache() {
-               Simantics.getSession().asyncRequest(new ReadRequest() {
-                       
-                       @Override
-                       public void run(ReadGraph graph) throws DatabaseException {
-                               List<String> types = new ArrayList<String>();
-                               types.add(Plant3D.URIs.Builtin_Straight);
-                               types.add(Plant3D.URIs.Builtin_Elbow);
-                               types.add(Plant3D.URIs.Builtin_ConcentricReducer);
-                               types.add(Plant3D.URIs.Builtin_BranchSplitComponent);
-                               types.add(Plant3D.URIs.Builtin_EccentricReducer);
+       public static void preloadCache(RequestProcessor session) {
+               try {
+                       session.syncRequest(new ReadRequest() {
                                
-                               for (String typeURI : types) {
-                                       load(graph, typeURI);
+                               @Override
+                               public void run(ReadGraph graph) throws DatabaseException {
+                                       List<String> types = new ArrayList<String>();
+                                       types.add(Plant3D.URIs.Builtin_Straight);
+                                       types.add(Plant3D.URIs.Builtin_Elbow);
+                                       types.add(Plant3D.URIs.Builtin_ConcentricReducer);
+                                       types.add(Plant3D.URIs.Builtin_BranchSplitComponent);
+                                       types.add(Plant3D.URIs.Builtin_EccentricReducer);
+                                       types.add(Plant3D.URIs.Builtin_Elbow45);
+                                       types.add(Plant3D.URIs.Builtin_Elbow90);
+                                       
+                                       for (String typeURI : types) {
+                                               load(graph, typeURI);
+                                       }
                                }
-                       }
-               });
+                       });
+               } catch (DatabaseException e) {
+                       LOGGER.error("ComponentUtils.preloadCache() failed unexpectedly", e);
+               }
        }
        
        private static GeometryProvider getProvider(ReadGraph graph, Resource type) throws DatabaseException {
@@ -62,6 +85,11 @@ public class ComponentUtils {
                }
                if (geom != null) {
                        GeometryProvider provider = graph.adapt(geom, GeometryProvider.class);
+                       if (provider instanceof ParametricGeometryProvider) {
+                       Map<String,Object> params = graph.syncRequest(new ParameterRead(type));
+                       if (params.size() > 0)
+                           ((ParametricGeometryProvider)provider).setProperties(params);
+                       }
                        return provider;
                }
                return null;
@@ -87,7 +115,9 @@ public class ComponentUtils {
                GeometryProvider provider = getProvider(graph, type);
                if (provider != null || graph.hasStatement(type,p3d.NonVisibleComponent)) {
                        providers.put(typeURI, provider);
-                       clazzes.put(typeURI,getClazz(graph, type));
+                       if (graph.isInheritedFrom(type, p3d.PipelineComponent))
+                               clazzes.put(typeURI,getClazz(graph, type));
+                       names.put(typeURI, NameUtils.getSafeName(graph, type));
                        return;
                }
                throw new DatabaseException("Cannot find component for " + typeURI);
@@ -103,6 +133,15 @@ public class ComponentUtils {
                });
        }
        
+       /**
+        * Creates a component
+        * 
+        * Does not set the name or add the component to a piperun.
+        * @param root
+        * @param typeURI
+        * @return
+        * @throws Exception
+        */
        public static PipelineComponent createComponent(P3DRootNode root, String typeURI) throws Exception {
                Class<? extends PipelineComponent> type = clazzes.get(typeURI);
                GeometryProvider provider = providers.get(typeURI);
@@ -127,6 +166,49 @@ public class ComponentUtils {
                return component;
        }
        
+       /**
+        * Creates a equipment
+        * 
+        * Does not set the name
+        * 
+        * @param root
+        * @param typeURI
+        * @return
+        * @throws Exception
+        */
+       
+       public static Equipment createEquipment(P3DRootNode root, String typeURI) throws Exception {
+               GeometryProvider provider = providers.get(typeURI);
+               if (provider == null) {
+                       load(typeURI);
+                       provider = providers.get(typeURI);
+               }
+               Equipment equipment = root.createEquipment();
+               equipment.setType(typeURI);
+               equipment.setGeometry(provider);
+               root.addChild(equipment);
+               return equipment;
+       }
+       
+       public static Equipment createEquipmentWithNozzles(P3DRootNode root, String typeURI, String nozzleTypeUri) throws Exception {
+        GeometryProvider provider = providers.get(typeURI);
+        if (provider == null) {
+            load(typeURI);
+            provider = providers.get(typeURI);
+        }
+        Equipment equipment = root.createEquipment();
+        equipment.setType(typeURI);
+        equipment.setGeometry(provider);
+        root.addChild(equipment);
+        
+        for (int i = 0; i < equipment.numberOfFixedNozzles(); i++) {
+            createNozzle(root, equipment, new Item(nozzleTypeUri, "Nozzle"));
+            
+        }
+        
+        return equipment;
+    }
+       
        public static InlineComponent createStraight(P3DRootNode root) throws Exception{
                InlineComponent component = root.createInline();
                component.setType(Plant3D.URIs.Builtin_Straight);
@@ -155,31 +237,503 @@ public class ComponentUtils {
        }
        
        public static Equipment createEquipment(P3DRootNode root, Item equipmentType) throws Exception {
-           Equipment equipment = root.createEquipment();
-        equipment.setType(equipmentType.getUri());
+               Equipment equipment = createEquipment(root, equipmentType.getUri());
+               String n = root.getUniqueName(equipmentType.getName());
+               equipment.setName(n);
+               return equipment;
+       }
+       
+       public static Equipment createEquipmentWithNozzles(P3DRootNode root, Item equipmentType, Item nozzleType) throws Exception {
+        Equipment equipment = createEquipmentWithNozzles(root, equipmentType.getUri(), nozzleType.getUri());
         String n = root.getUniqueName(equipmentType.getName());
         equipment.setName(n);
-        root.addChild(equipment);
         return equipment;
-       }
+    }
        
        public static Nozzle createDefaultNozzle(P3DRootNode root, Equipment equipment) throws Exception {
-           return createNozzle(root, equipment, new Item(Plant3D.URIs.Builtin_Nozzle, "Nozzle"));
+               return createNozzle(root, equipment, new Item(Plant3D.URIs.Builtin_Nozzle, "Nozzle"));
        }
        
        public static Nozzle createNozzle(P3DRootNode root, Equipment equipment, Item nozzleType) throws Exception {
-           Nozzle nozzle = root.createNozzle();
-        nozzle.setType(nozzleType.getUri());
-        String n = root.getUniqueName(nozzleType.getName());
-        nozzle.setName(n);
-        PipeRun pipeRun = new PipeRun();
-        n = root.getUniqueName("PipeRun");
-        pipeRun.setName(n);
-        nozzle.setPipeRun(pipeRun);
-        
-        equipment.addChild(nozzle);
-        root.addChild(pipeRun);
-        // root.getNodeMap().commit("Add nozzle " + n);
-        return nozzle;
+               Nozzle nozzle = root.createNozzle();
+               nozzle.setType(nozzleType.getUri());
+               String n = root.getUniqueName(nozzleType.getName());
+               nozzle.setName(n);
+               PipeRun pipeRun = new PipeRun();
+               n = root.getUniqueName("PipeRun");
+               pipeRun.setName(n);
+               nozzle.setPipeRun(pipeRun);
+               
+               equipment.addChild(nozzle);
+               root.addChild(pipeRun);
+               // root.getNodeMap().commit("Add nozzle " + n);
+               return nozzle;
+       }
+       
+       public static class InsertInstruction {
+               public String typeUri;
+               
+               public PositionType position = PositionType.NEXT;
+               public PositionType insertPosition = PositionType.NEXT;
+
+               // Component name
+               public String name;
+               
+               // Reducer requires pipe specs
+               public Double diameter;
+               public Double thickness;
+               public Double turnRadius;
+               
+               // Variable length 
+               public Double length;
+               
+               // Variable angle
+               public Double angle;
+               
+               // Rotation angle used with turns and rotated inline.
+               public Double rotationAngle;
+
+               public String getTypeUri() {
+                       return typeUri;
+               }
+
+               public void setTypeUri(String typeUri) {
+                       this.typeUri = typeUri;
+               }
+
+               public PositionType getPosition() {
+                       return position;
+               }
+
+               public void setPosition(PositionType position) {
+                       this.position = position;
+               }
+
+               public PositionType getInsertPosition() {
+                       return insertPosition;
+               }
+
+               public void setInsertPosition(PositionType insertPosition) {
+                       this.insertPosition = insertPosition;
+               }
+
+               public String getName() {
+                       return name;
+               }
+
+               public void setName(String name) {
+                       this.name = name;
+               }
+
+               public Double getDiameter() {
+                       return diameter;
+               }
+
+               public void setDiameter(Double diameter) {
+                       this.diameter = diameter;
+               }
+               
+               public double getThickness() {
+                       return thickness;
+               }
+               
+               public void setThickness(double thickness) {
+                       this.thickness = thickness;
+               }
+
+               public Double getTurnRadius() {
+                       return turnRadius;
+               }
+
+               public void setTurnRadius(Double turnRadius) {
+                       this.turnRadius = turnRadius;
+               }
+
+               public Double getLength() {
+                       return length;
+               }
+
+               public void setLength(Double length) {
+                       this.length = length;
+               }
+
+               public Double getAngle() {
+                       return angle;
+               }
+
+               public void setAngle(Double angle) {
+                       this.angle = angle;
+               }
+               
+               public Double getRotationAngle() {
+            return rotationAngle;
+        }
+               
+               public void setRotationAngle(Double rotationAngle) {
+            this.rotationAngle = rotationAngle;
+        }
+
+       }
+       
+       public static PipelineComponent addComponent(P3DRootNode root, PipelineComponent component,  InsertInstruction inst) throws Exception {
+               
+               PipelineComponent newComponent = ComponentUtils.createComponent(root, inst.typeUri);
+               if (inst.name != null)
+                       newComponent.setName(inst.name);
+               
+               PipeControlPoint newPcp = newComponent.getControlPoint();
+               
+               PipeControlPoint toPcp = component.getControlPoint();
+               PipeRun pipeRun = toPcp.getPipeRun();
+               
+               String typeName = names.get(inst.typeUri);
+               if (typeName == null)
+                       typeName = "Component";
+               
+               Vector3d dir = null;
+               Vector3d pos = null;
+       
+               PositionType position = inst.position;
+               PositionType insertPosition = inst.insertPosition;
+               boolean lengthAdjustable = false;
+               if (newComponent instanceof InlineComponent) {
+                       lengthAdjustable = ((InlineComponent)newComponent).isVariableLength() || ((InlineComponent)newComponent).isModifialble(); 
+               }
+               boolean insertAdjustable = false;
+               if (component instanceof InlineComponent) {
+                       insertAdjustable = ((InlineComponent)component).isVariableLength();
+               }
+               boolean sizeChange = false;
+               if (newComponent instanceof InlineComponent) {
+                       sizeChange = ((InlineComponent)newComponent).isSizeChange();
+               }
+               
+               if (toPcp.isInline()) {
+                       switch (position) {
+                       case NEXT: 
+                               if (toPcp.isDualInline()) {
+                                       toPcp = toPcp.getDualSub();
+                                       pipeRun = toPcp.getPipeRun();
+                               }
+                               
+                               break;
+                       case PREVIOUS:
+                               if (toPcp.isDualSub()) {
+                                       toPcp = toPcp.parent;
+                                       pipeRun = toPcp.getPipeRun();
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       Vector3d start = new Vector3d();
+                       Vector3d end = new Vector3d();
+                       dir = new Vector3d();
+                       toPcp.getInlineControlPointEnds(start, end, dir);
+                       dir.normalize();
+                       switch (position) {
+                       case NEXT:
+                               pos = new Vector3d(end);
+                               break;
+                       case PREVIOUS:
+                               pos = new Vector3d(start);
+                               break;
+                       case SPLIT:
+                               pos = new Vector3d(toPcp.getWorldPosition());
+                               break;
+                       default:
+                               break;
+                       }
+
+               } else if (toPcp.isDirected()) {
+                       dir = new Vector3d(toPcp.getDirection(Direction.NEXT));
+                       pos = new Vector3d(toPcp.getWorldPosition());
+               } else if (toPcp.isTurn() && toPcp.asFixedAngle()) {
+                       dir = new Vector3d(toPcp.getDirection(position == PositionType.NEXT ? Direction.NEXT : Direction.PREVIOUS));
+                       pos = new Vector3d(toPcp.getWorldPosition());
+                       if (!lengthAdjustable) {
+                               Vector3d v = new Vector3d(dir);
+                               v.scale(toPcp.getInlineLength());
+                               pos.add(v);
+                       } else {
+                               if (insertPosition == PositionType.NEXT) {
+                                       Vector3d v = new Vector3d(dir);
+                                       v.scale(toPcp.getInlineLength());
+                                       pos.add(v);
+                               } else if (insertPosition == PositionType.SPLIT) {
+                                       // scale 0.5*length so that we don't remove the length twice from the new component
+                                       Vector3d v = new Vector3d(dir);
+                                       v.scale(toPcp.getInlineLength()*0.5);  
+                                       pos.add(v);
+                               }
+                       }
+               }
+               
+               String name = component.getPipeRun().getUniqueName(typeName);
+               newComponent.setName(name);
+
+               pipeRun.addChild(newComponent);
+               if (newPcp.isSizeChange())
+                       newComponent.setAlternativePipeRun(pipeRun);
+
+               if (newComponent instanceof InlineComponent) {
+                   InlineComponent inlineComponent = (InlineComponent)newComponent;
+                   if (inlineComponent.isVariableLength()|| inlineComponent.isModifialble()) {
+                       newPcp.setLength(inst.length);
+                       newComponent.setParameter("length", inst.length);
+                   }
+                   if (inst.rotationAngle != null)
+                       ((InlineComponent) newComponent).setRotationAngle(inst.rotationAngle);
+               } else if (newComponent instanceof TurnComponent) {
+                   TurnComponent turnComponent = (TurnComponent)newComponent;
+                   if  (turnComponent.isVariableAngle()) {
+                               newPcp.setTurnAngle(inst.angle);
+                               newComponent.setParameter("turnAngle", inst.angle);
+                   }
+                   if (inst.rotationAngle != null)
+                ((TurnComponent) newComponent).setRotationAngle(inst.rotationAngle);
+               }
+               
+               
+               newComponent.updateParameters();
+               
+               Vector3d v = new Vector3d(dir);
+               if (insertAdjustable) {
+                       if (insertPosition == PositionType.NEXT)
+                               v.scale(newComponent.getControlPoint().getInlineLength());
+                       else if (insertPosition == PositionType.SPLIT)
+                               v.set(0, 0, 0);
+                       else if (insertPosition == PositionType.PREVIOUS)
+                               v.scale(-newComponent.getControlPoint().getInlineLength());
+               } else {
+                       v.scale(newComponent.getControlPoint().getInlineLength());
+               }
+               switch (position) {
+               case NEXT:
+                       pos.add(v);
+                       break;
+               case PREVIOUS:
+                       pos.sub(v);
+                       break;
+               case SPLIT:
+                       break;
+               default:
+                       break;
+               }
+               
+               switch (position) {
+               case NEXT: 
+                       if (toPcp.isDualInline())
+                               toPcp = toPcp.getDualSub();
+                       newPcp.insert(toPcp, Direction.NEXT);
+                       newPcp.setWorldPosition(pos);
+                       break;
+               case PREVIOUS:
+                       if (toPcp.isDualSub())
+                               toPcp = toPcp.parent;
+                       newPcp.insert(toPcp, Direction.PREVIOUS);
+                       newPcp.setWorldPosition(pos);
+                       break;
+               case SPLIT:
+                       PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, true);
+               default:
+                       break;
+               }
+               
+               // Move the size change and the rest of the components in the pipe run to a new pipe run
+               if (sizeChange) {
+                       PipeRun other = new PipeRun();
+                       String n = root.getUniqueName("PipeRun");
+                       other.setName(n);
+                       other.setPipeDiameter(inst.diameter);
+                       other.setPipeThickness(inst.thickness);
+                       other.setTurnRadius(inst.turnRadius);
+                       root.addChild(other);
+                       
+                       other.addChild(newComponent.getControlPoint().getDualSub());
+                       newComponent.setAlternativePipeRun(other);
+                       
+                       boolean forward = position != PositionType.PREVIOUS;
+                       PipelineComponent comp = forward ? newComponent.getNext() : newComponent.getPrevious();
+                       while (comp != null && comp.getPipeRun() == pipeRun) {
+                               if (comp.getParent() == pipeRun) {
+                                       comp.deattach();
+                                       other.addChild(comp);
+                               } else {
+                                       comp.setPipeRun(other);
+                               }
+                               
+                               // Reset parameters to match new pipe run
+                               comp.updateParameters();
+                               
+                               comp = forward ? comp.getNext() : comp.getPrevious();
+                       }
+                       
+                       newComponent.updateParameters();
+               }
+               
+               return newComponent;
+       }
+       
+       public static boolean connect(PipelineComponent current, PipelineComponent endTo) throws Exception {
+               return connect(current, endTo, null, null);
+       }
+       
+       /**
+        * Connects component to another component
+        * @param current
+        * @param endTo
+        * @param endType
+        * @param position
+        * @return
+        * @throws Exception
+        */
+       public static boolean connect(PipelineComponent current, PipelineComponent endTo, PositionType endType, Vector3d position) throws Exception{
+               PipeControlPoint endCP = endTo.getControlPoint();
+               boolean reversed;
+               if (current.getNext() == null)
+                       reversed = false;
+               else if (current.getPrevious() == null)
+                       reversed = true;
+               else
+                       return false;
+               
+               PipeRun pipeRun = current.getPipeRun();
+               P3DRootNode root = (P3DRootNode)current.getRootNode();
+               PipeControlPoint currentCP = current.getControlPoint();
+               
+               if (endType == null || endType == PositionType.NEXT || endType == PositionType.PREVIOUS) {
+                       
+                       
+                       
+                       boolean requiresReverse = false;
+                       if (!reversed && endCP.getPrevious() != null) {
+                               if (endCP.getNext() != null)
+                                       return false;
+                               requiresReverse = true;
+                       } else if (reversed && endCP.getNext() != null) {
+                               if (endCP.getPrevious() != null)
+                                       return false;
+                               requiresReverse = true;
+                       }
+                       PipeRun other = endCP.getPipeRun();
+                       boolean mergeRuns = other == null ? true : pipeRun.canMerge(other);
+                       
+                       if (requiresReverse) {
+                               // Pipe line must be traversible with next/previous relations without direction change.
+                               // Now the component, where we are connecting the created pipeline is defined in different order.
+                               PipingRules.reverse(other);
+                               
+                       }
+
+                       if (mergeRuns) {
+                               // Runs have compatible specs and must be merged
+                               if (other != null && pipeRun != other)
+                                       pipeRun.merge(other);
+                               else if (other == null) {
+                                       if (!(endTo instanceof Nozzle)) {
+                                               pipeRun.addChild(endTo);
+                                       } else {
+                                               endTo.setPipeRun(pipeRun);
+                                       }
+                               }
+                               if (!reversed) {
+                                       currentCP.setNext(endCP);
+                                       endCP.setPrevious(currentCP);
+                               } else {
+                                       currentCP.setPrevious(endCP);
+                                       endCP.setNext(currentCP);
+                               }
+                       } else {
+                               // Runs do not have compatible specs, and a reducer must be attached in between.
+                               InlineComponent reducer = ComponentUtils.createReducer(root);
+                               PipeControlPoint pcp = reducer.getControlPoint();
+                               
+                               Vector3d endPos = endCP.getWorldPosition();
+                               Vector3d currentPos = currentCP.getWorldPosition();
+                               Vector3d v = new Vector3d(endPos);
+                               v.sub(currentPos);
+                               v.scale(0.5);
+                               v.add(currentPos);
+                               
+                               PipingRules.addSizeChange(reversed, pipeRun, other, reducer, currentCP, endCP);
+                               
+                               pcp.setWorldPosition(v);
+                               reducer.updateParameters();
+                       }
+                       PipingRules.positionUpdate(endCP);
+                       return true;
+                       
+               } else if (endType == PositionType.SPLIT) {
+                       InlineComponent branchSplit = createBranchSplit((InlineComponent)endTo, position);
+                       if (branchSplit == null)
+                               return false;
+                       PipeControlPoint branchSplitCP = branchSplit.getControlPoint();
+                       PipeControlPoint pcp = new PipeControlPoint(branchSplit,pipeRun);
+                       branchSplitCP.children.add(pcp);
+                       pcp.parent = branchSplitCP;
+                       pcp.setWorldOrientation(branchSplitCP.getWorldOrientation());
+                       pcp.setWorldPosition(branchSplitCP.getWorldPosition());
+                                               
+                       
+                       if(!reversed) {
+                               pcp.setPrevious(currentCP);
+                               currentCP.setNext(pcp);
+                       } else {
+                               pcp.setNext(currentCP);
+                               currentCP.setPrevious(pcp);
+                       }
+                       PipingRules.positionUpdate(endCP);
+                       return true;
+               }
+               return false;
+       }
+       
+       public static InlineComponent createBranchSplit(InlineComponent component, Vector3d pos) throws Exception{
+               if (!component.isVariableLength())
+                       return null;
+               PipeRun pipeRun = component.getPipeRun();
+               Vector3d sStart = new Vector3d();
+               Vector3d sEnd = new Vector3d();
+               component.getControlPoint().getInlineControlPointEnds(sStart, sEnd);
+               
+               if (MathTools.distance(sStart, sEnd) < (pipeRun.getPipeDiameter()*0.5))
+                       return null;
+               
+               
+               Vector3d p = MathTools.closestPointOnEdge(new Vector3d(pos), sStart, sEnd);
+               if (p == sStart) {
+                       Vector3d v = new Vector3d(sEnd);
+                       v.sub(sStart);
+                       v.normalize();
+                       v.scale(component.getPipeRun().getPipeDiameter()*0.5);
+                       p.add(v);
+               } else if (p == sEnd) {
+                       Vector3d v = new Vector3d(sStart);
+                       v.sub(sEnd);
+                       v.normalize();
+                       v.scale(component.getPipeRun().getPipeDiameter()*0.5);
+                       p.add(v);
+               }
+               
+               P3DRootNode root = (P3DRootNode)component.getRootNode();
+               InlineComponent branchSplit = ComponentUtils.createBranchSplit(root);
+               String branchName = component.getPipeRun().getUniqueName("Branch");
+               branchSplit.setName(branchName);
+               component.getPipeRun().addChild(branchSplit);
+               PipeControlPoint branchSplitCP = branchSplit.getControlPoint();
+               branchSplitCP.setWorldPosition(p);
+               PipingRules.splitVariableLengthComponent(branchSplit, component, false);
+               return branchSplit;
+       }
+
+       public static Collection<String> getPipelineComponentNames(P3DRootNode root) {
+               Collection<String> usedNames = root.getChild().stream()
+                               .filter(n -> n instanceof PipeRun)
+                               .flatMap(n -> ((PipeRun)n).getChild().stream())
+                               .filter(n -> n instanceof PipelineComponent)
+                               .map(n -> ((PipelineComponent)n).getName())
+                               .collect(Collectors.toSet());
+               return usedNames;
        }
 }