]> gerrit.simantics Code Review - simantics/3d.git/commitdiff
Preliminary example of creating pipes with elbow coordinates 65/3165/1
authorMarko Luukkainen <marko.luukkainen@semantum.fi>
Wed, 28 Aug 2019 14:55:06 +0000 (17:55 +0300)
committerMarko Luukkainen <marko.luukkainen@semantum.fi>
Wed, 28 Aug 2019 14:55:06 +0000 (17:55 +0300)
* ComponentUtil.connect now works, if connected Component is not
attached to a PipeRun
* Modified Piping rules to use path leg ends for turn calculation

gitlab #28

Change-Id: If16e6e63e44a697d5c07c0f3ec0d1a0c0fba3b9d

org.simantics.plant3d/scl/Plant3d/Test/Test2.scl [new file with mode: 0644]
org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java

diff --git a/org.simantics.plant3d/scl/Plant3d/Test/Test2.scl b/org.simantics.plant3d/scl/Plant3d/Test/Test2.scl
new file mode 100644 (file)
index 0000000..354a49f
--- /dev/null
@@ -0,0 +1,100 @@
+import "Plant3d/Utils/P3DUtil"
+import "Simantics/DB"
+import "Plant3d/Utils/Loader"
+import "G3D/SCLUtil"
+import "JavaBuiltin"
+import "G3D/Math/Tuple3d" as T3D
+import "G3D/Math/Vector3d" as V3D
+import "G3D/Scenegraph/G3DNode" as G3D
+import "Plant3d/Scenegraph/P3DRootNode" as P3R
+import "Plant3d/Scenegraph/P3DNode" as P3N
+import "Plant3d/Scenegraph/Equipment" as E
+import "Plant3d/Scenegraph/PipelineComponent" as PC
+import "Plant3d/Scenegraph/EndComponent" as EC
+import "Plant3d/Scenegraph/InlineComponent" as IC
+import "Plant3d/Scenegraph/TurnComponent" as TC
+import "Plant3d/Utils/P3DScriptNodeMap" as P3S
+import "Plant3d/Utils/P3DUtil" as P3DUtil
+import "Plant3d/Utils/ComponentUtils" as CU
+import "http://www.simantics.org/Layer0-1.1" as L0
+
+doTest :: <Proc> ()
+doTest = do
+
+    myModel = syncWrite(\_ -> do
+          myModel = P3DUtil.createModel "Test2"
+          claim (resource "http://Projects/Development%20Project") L0.ConsistsOf myModel
+          myModel)
+    p3dmap = load myModel
+    rootMaybe = javaSafeCoerce (P3S.getRootNode p3dmap) :: Maybe P3R.P3DRootNode
+    root = fromJust rootMaybe
+    pump = CU.createEquipmentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Pump"
+    P3N.setName pump "My Pump"
+    P3S.commit p3dmap "Created a pump"
+    P3S.update p3dmap
+    n1 = CU.createDefaultNozzle root pump
+    P3S.commit p3dmap "Created a nozzle"
+    P3S.update p3dmap
+    pipe = CU.addComponent root (unsafeCoerce n1) (CU.createVariableLength "http://www.simantics.org/Plant3D-0.1/Builtin/Straight" PC.NEXT PC.NEXT 1.0)
+    P3S.commit p3dmap "Created a pipe"
+    P3S.update p3dmap
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (-1.6) 0.15 0.0)
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (-1.6) 2.6 0.0)
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (-1.6) 2.6 (-1.4))
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (2.9) 2.6 (-1.4))
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (2.9) 2.6 (3.0))
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    
+    elbow = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"
+    G3D.setPosition elbow (V3D.createVector3d (2.9) 0.15 (3.0))
+    CU.connect pipe elbow
+    
+    pipe = CU.createComponentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"
+    CU.connect elbow pipe
+    G3D.setPosition pipe (V3D.createVector3d (1.8) 0.15 3.0)
+    P3S.commit p3dmap "Created a pipe"
+    P3S.update p3dmap
+    
+    tank = CU.createEquipmentWithURI root "http://www.simantics.org/Plant3D-0.1/Builtin/HorizontalTank"
+    P3N.setName tank "My Tank"
+    G3D.setPosition tank (V3D.createVector3d 0.0 0.0 3.0)
+    P3S.commit p3dmap "Created a tank"
+    P3S.update p3dmap
+    n2 = CU.createDefaultNozzle root tank
+    G3D.setPosition n2 (V3D.createVector3d 0.6 0.3 0.0)
+    P3S.commit p3dmap "Created a nozzle"
+    P3S.update p3dmap
+    
+    CU.connect pipe (unsafeCoerce n2)
+    P3S.commit p3dmap "Connected a pipe to a nozzle"
+    P3S.update p3dmap
+    ()
\ No newline at end of file
index 386cc56a211d3245d6bb07137550d538d31a50f6..2fef57fbeb6af4f92db459c8c87062349e053323 100644 (file)
@@ -296,41 +296,27 @@ public class PipingRules {
        }
 
        private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
-               PipeControlPoint end = start.findNextEnd(list);
-               // this is for inline cp that is also path leg end
-               if (lengthChange == PathLegUpdateType.NONE) {
-                       if (start.equals(updated))
-                               lengthChange = PathLegUpdateType.NEXT;
-                       else if (end.equals(updated))
-                               lengthChange = PathLegUpdateType.PREV;
-               }
-               updatePathLegNext(start, list, end, updated, lengthChange);
-       }
 
-       private static void updatePathLegNext(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               updatePathLeg(start, list, end, false, 0, new ArrayList<ExpandIterInfo>(), updated, lengthChange);
+           UpdateStruct2 us = createUS(start, Direction.NEXT, 0, new ArrayList<ExpandIterInfo>(), updated);
+        if (lengthChange == PathLegUpdateType.NONE) {
+            if (start.equals(updated))
+                lengthChange = PathLegUpdateType.NEXT;
+            else if (us.end.equals(updated))
+                lengthChange = PathLegUpdateType.PREV;
+        }
+               updatePathLeg(us, lengthChange);
        }
        
        private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
-               PipeControlPoint end = start.findPreviousEnd(list);
                // TODO: this method is not symmetric with updatePathLegNext, which may alter lengthChange parameter?
-               updatePathLegPrev(start, list, end, updated, lengthChange);
-       }
-
-       private static void updatePathLegPrev(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               // reverses the list
-               ArrayList<PipeControlPoint> nextList = new ArrayList<PipeControlPoint>();
-               for (PipeControlPoint icp : list) {
-                       if (icp.isDualSub()) {
-                               nextList.add(0, icp.getParentPoint());
-                       } else {
-                               nextList.add(0, icp);
-                       }
-               }
-               updatePathLeg(end, nextList, start, true, 0, new ArrayList<ExpandIterInfo>(), updated, lengthChange);
-
+           UpdateStruct2 us = createUS(start, Direction.PREVIOUS, 0, new ArrayList<ExpandIterInfo>(), updated);
+//        if (lengthChange == PathLegUpdateType.NONE) {
+//            if (start.equals(updated))
+//                lengthChange = PathLegUpdateType.NEXT;
+//            else if (us.end.equals(updated))
+//                lengthChange = PathLegUpdateType.PREV;
+//        }
+        updatePathLeg(us, lengthChange);
        }
 
        private static class UpdateStruct2 {
@@ -425,44 +411,38 @@ public class PipingRules {
                        hasOffsets = true;
                }
                
-//             for (PipeControlPoint icp : list) {
-//                     if (icp.isOffset()) {
-//                             icp.setOffset(((InlineComponent)icp.getPipelineComponent()).getOffset());
-//                             hasOffsets = true;
-//                             Vector3d v = icp.getSizeChangeOffsetVector(dir);
-//                             offset.add(v);
-//                     } else if (icp.isDualSub())
-//                             ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
-//             }
                if (DEBUG && hasOffsets)
                        System.out.println("calcOffset s:"+ startPoint + " e:" + endPoint + " d:" + dir + " o:"+offset) ;
                return hasOffsets;
        }
-
-       /**
-        * @param start
-        *            starting point of the pipe run
-        * @param list
-        *            list of inline control points in the pipe run
-        * @param end
-        *            ending point of the pipe run
-        * @param reversed
-        *            boolean flag indicating wether start or end control point was
-        *            modified (if true then end point was modified)
-        * @throws TransactionException
-        */
-       private static void updatePathLeg(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, boolean reversed, int iter, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               if (start == end)
-                       return;
-               // FIXME: direction is calculated wrong way!
-               boolean hasOffsets = false;
-               Vector3d offset = new Vector3d();
-               Vector3d startPoint = start.getWorldPosition();
-               Vector3d endPoint = end.getWorldPosition();
-               Vector3d dir = new Vector3d();
-               hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
-               updatePathLeg(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, reversed, toRemove, updated), lengthChange);
-
+       
+       private static UpdateStruct2 createUS(PipeControlPoint start, Direction direction, int iter, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) {
+           ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
+        PipeControlPoint end = null;
+        if (direction == Direction.NEXT) {
+            end = start.findNextEnd(list);
+        } else {
+            ArrayList<PipeControlPoint> prevList = new ArrayList<PipeControlPoint>();
+            PipeControlPoint tend = start.findPreviousEnd(prevList);
+            for (PipeControlPoint icp : prevList) {
+                if (icp.isDualSub()) {
+                    list.add(0, icp.getParentPoint());
+                } else {
+                    list.add(0, icp);
+                }
+            }  
+            end = start;
+            start = tend;
+        }
+        if (start == end)
+            return null;
+        boolean hasOffsets = false;
+        Vector3d offset = new Vector3d();
+        Vector3d startPoint = start.getWorldPosition();
+        Vector3d endPoint = end.getWorldPosition();
+        Vector3d dir = new Vector3d();
+        hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
+        return new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, direction == Direction.PREVIOUS, toRemove, updated);
        }
        
        private static boolean asDirected(PipeControlPoint pcp, Direction direction) {
@@ -853,7 +833,8 @@ public class PipingRules {
 
                Vector3d directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
                if (directedDirection == null) {
-                   updateTurnControlPointTurn(dcp, dcp.getPrevious(), dcp.getNext());
+                   //updateTurnControlPointTurn(dcp, dcp.getPrevious(), dcp.getNext());
+                   updateTurnControlPointTurn(dcp, null, null);
                    directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
                    if (directedDirection == null) {
                        return;
@@ -1148,15 +1129,7 @@ public class PipingRules {
                        // this won't work properly if inline control points are not updated
                        PipeControlPoint startPrev = u.start.getPrevious();
                        if (startPrev != null) {
-                               double a;
-                               if (!u.hasOffsets) {
-                                       a = updateTurnControlPointTurn(u.start, startPrev, u.end);
-                               } else {
-                                       Vector3d ep = new Vector3d(u.endPoint);
-                                       ep.sub(u.offset);
-                                       a = updateTurnControlPointTurn(u.start, u.startPoint, startPrev.getPosition(), ep);
-
-                               }
+                               double a = updateTurnControlPointTurn(u.start, null, u.dir);
                                if (a < MIN_TURN_ANGLE && u.start.isDeletable())
                                        startRemoved = true;
                                else if (lengthChange == PathLegUpdateType.PREV || lengthChange == PathLegUpdateType.PREV_S) {
@@ -1173,14 +1146,8 @@ public class PipingRules {
 
                        PipeControlPoint endNext = u.end.getNext();
                        if (endNext != null) {
-                               double a;
-                               if (!u.hasOffsets) {
-                                       a = updateTurnControlPointTurn(u.end, u.start, endNext);
-                               } else {
-                                       Vector3d sp = new Vector3d(u.startPoint);
-                                       sp.add(u.offset);
-                                       a = updateTurnControlPointTurn(u.end, u.endPoint, sp, endNext.getPosition());
-                               }
+                           // TODO: u.end, u.dir, null
+                               double a = updateTurnControlPointTurn(u.end, null, null);
                                if (a < MIN_TURN_ANGLE && u.end.isDeletable())
                                        endRemoved = true;
                                else if (lengthChange == PathLegUpdateType.NEXT || lengthChange == PathLegUpdateType.NEXT_S) {
@@ -1347,7 +1314,8 @@ public class PipingRules {
                
                if (updateEnds) {
                        if (u.start.isTurn()) {
-                               updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext());
+                               //updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext());
+                           updateTurnControlPointTurn(u.start, null, null);
 //                             updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE);
                        } else if (u.start.isEnd()) {
                                updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
@@ -1355,7 +1323,8 @@ public class PipingRules {
                                updateControlPointOrientation(u.start);
                        }
                        if (u.end.isTurn()) {
-                               updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext());
+                               //updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext());
+                           updateTurnControlPointTurn(u.end, null, null);
 //                             updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE);
                        } else if (u.end.isEnd()) {
                                updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
@@ -1568,58 +1537,75 @@ public class PipingRules {
                }
        }
 
-       /**
-        * Recalculates turn control point's internal data (turn angle and offset)
-        * 
-        * @param tcp
-        * @param prev
-        * @param next
-        */
-       private static double updateTurnControlPointTurn(PipeControlPoint tcp, PipeControlPoint prev, PipeControlPoint next) {
-               if (DEBUG)
-                       System.out.println("PipingTools.updateTurnControlPointTurn()" + tcp);
-               
-               if (!tcp.isFixed()) {
-                   if (next == null || prev == null)
-                   return tcp.getTurnAngle();
-               Vector3d middlePoint = tcp.getWorldPosition();
-               Vector3d nextPoint = next.getWorldPosition();
-               Vector3d prevPoint = prev.getWorldPosition();
-               return updateTurnControlPointTurn(tcp, middlePoint, prevPoint, nextPoint);
-               } else {
-                   // Verify that fixed turn is properly defined.
-                   // TODO : when reversed flag is changed, rotation angle should be recalculated, otherwise the orientation will change.
-                   // TODO : should reverse flag toggle to be done already when we are removing defining side?
-                   if (prev != null && next != null) {
-                       // Nothing to do
-                   } else if (prev == null) {
-                       if (!tcp._getReversed())
-                           tcp.setReversed(true);
-                   } else if (next == null) {
-                       if (tcp._getReversed())
-                           tcp.setReversed(false);
-                   }
-                   
-                   Vector3d dir;
-                   if (!tcp._getReversed()) {
-                   if (prev == null)
-                   return Math.PI; // FIXME : argh
-                   
-                   Vector3d middlePoint = tcp.getWorldPosition();
-                   Vector3d prevPoint = prev.getWorldPosition();
-                   dir = new Vector3d();
-                   dir.sub(middlePoint, prevPoint);        
-                  
-                   } else {
-                       if (next == null)
-                    return Math.PI; // FIXME : argh
-                
-                Vector3d middlePoint = tcp.getWorldPosition();
-                Vector3d nextPoint = next.getWorldPosition();
-                dir = new Vector3d();
-                dir.sub(middlePoint, nextPoint);
-                   }
-                   dir.normalize();
+       private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d prev, Vector3d next) {
+           if (next == null) {
+            UpdateStruct2 us = createUS(tcp, Direction.NEXT, 0, new ArrayList<PipingRules.ExpandIterInfo>(), tcp);
+            if (us != null)
+                next = us.dir;
+        }
+        if (prev == null) {
+            UpdateStruct2 us = createUS(tcp, Direction.PREVIOUS, 0, new ArrayList<PipingRules.ExpandIterInfo>(), tcp);
+            if (us != null) {
+                prev = us.dir;
+            }
+        }
+        
+           if (!tcp.isFixed()) {
+               
+               
+               if (next == null || prev == null) {
+                if (tcp.getTurnAngle() != null)
+                    return tcp.getTurnAngle();
+                return Math.PI; // FIXME : argh
+            }
+               double turnAngle = prev.angle(next);
+    
+               double angle = Math.PI - turnAngle;
+    
+               Vector3d turnAxis = new Vector3d();
+               turnAxis.cross(prev, next);
+               if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) {
+                       double elbowRadius = tcp.getPipelineComponent().getPipeRun().getTurnRadius();
+                       double R = elbowRadius / Math.tan(angle * 0.5);
+                       
+                       turnAxis.normalize();
+                       tcp.setTurnAngle(turnAngle);
+                       tcp.setLength(R);// setComponentOffsetValue(R);
+                       tcp.setTurnAxis(turnAxis);
+    //                 tcp.setPosition(tcp.getPosition());
+               } else {
+                       turnAngle = 0.0;
+                       tcp.setTurnAngle(0.0);
+                       tcp.setLength(0.0);
+                       tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
+               }
+               
+               updateControlPointOrientation(tcp);
+               
+               if (DEBUG)
+                System.out.println("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis);
+               return turnAngle;
+           } else {
+               
+           if (prev != null && next != null) {
+                // Nothing to do
+            } else if (prev == null) {
+                if (!tcp._getReversed())
+                    tcp.setReversed(true);
+            } else if (next == null) {
+                if (tcp._getReversed())
+                    tcp.setReversed(false);
+            }
+            
+            Vector3d dir = null;
+            if (!tcp._getReversed()) {
+                dir = prev;
+            } else {
+                dir = next;
+            }
+            if (dir == null) {
+                return Math.PI; // FIXME : argh
+            }
             
             Quat4d q = PipeControlPoint.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0);
             Vector3d v = new Vector3d();
@@ -1627,54 +1613,9 @@ public class PipingRules {
             tcp.setTurnAxis(v);
             tcp.setWorldOrientation(q);
             return tcp.getTurnAngle();
-               }
-       }
-
-       /**
-        * Recalculates turn control point's internal data (turn angle and offset)
-        * 
-        * @param tcp
-        * @param middlePoint
-        * @param nextPoint
-        * @param prevPoint
-        */
-       private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d middlePoint, Vector3d prevPoint, Vector3d nextPoint) {
-
-               Vector3d dir1 = new Vector3d(middlePoint);
-               dir1.sub(prevPoint);
-               Vector3d dir2 = new Vector3d(nextPoint);
-               dir2.sub(middlePoint);
-               if (DEBUG)
-                       System.out.println("PipingTools.updateTurnControlPointTurn " + tcp + " " + prevPoint + " " + middlePoint + " " + nextPoint);
-               return updateTurnControlPointTurn(tcp, dir1, dir2);
-       }
-
-       private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d dir1, Vector3d dir2) {
-               double turnAngle = dir1.angle(dir2);
-
-               double angle = Math.PI - turnAngle;
-
-               Vector3d turnAxis = new Vector3d();
-               turnAxis.cross(dir1, dir2);
-               if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) {
-                       double elbowRadius = tcp.getPipelineComponent().getPipeRun().getTurnRadius();
-                       double R = elbowRadius / Math.tan(angle * 0.5);
-                       
-                       turnAxis.normalize();
-                       tcp.setTurnAngle(turnAngle);
-                       tcp.setLength(R);// setComponentOffsetValue(R);
-                       tcp.setTurnAxis(turnAxis);
-//                     tcp.setPosition(tcp.getPosition());
-               } else {
-                       turnAngle = 0.0;
-                       tcp.setTurnAngle(0.0);
-                       tcp.setLength(0.0);
-                       tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
-               }
-               updateControlPointOrientation(tcp);
-               if (DEBUG)
-                       System.out.println("PipingTools.updateTurnControlPointTurn " + dir1 + " " + dir2 + " " + turnAngle + " " + turnAxis);
-               return turnAngle;
+           }
+               
+               
        }
        
        public static List<PipeControlPoint> getControlPoints(PipeRun pipeRun) {
index cc19260b41bf719ddda3df07a7460f3c4b62f8ee..15ee7918ca1709501eab1bc076054a1459df479f 100644 (file)
@@ -516,7 +516,7 @@ public class ComponentUtils {
                 requiresReverse = true;
             }
             PipeRun other = endCP.getPipeRun();
-            boolean mergeRuns = pipeRun.equalSpecs(other);
+            boolean mergeRuns = other == null ? true : pipeRun.equalSpecs(other);
             
             if (requiresReverse) {
                 // Pipe line must be traversible with next/previous relations without direction change.
@@ -526,8 +526,15 @@ public class ComponentUtils {
             }
             if (mergeRuns) {
                 // Runs have compatible specs and must be merged
-                if (pipeRun != other) // FIXME: temporary workaround.
+                if (other != null && pipeRun != other)
                     PipingRules.merge(pipeRun, other);
+                else if (other == null) {
+                    if (!(endTo instanceof Nozzle)) {
+                        pipeRun.addChild(endTo);
+                    } else {
+                        endTo.setPipeRun(pipeRun);
+                    }
+                }
                 if (!reversed) {
                     currentCP.setNext(endCP);
                     endCP.setPrevious(currentCP);