]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
Fix issue with multiple degenerate legs in front of a nozzle
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipingRules.java
index 727cf1a882e8d513fa1ab583ef2584a692054954..b2376341985d60247dece769a5be4921be26a78b 100644 (file)
@@ -2,9 +2,7 @@ package org.simantics.plant3d.scenegraph.controlpoint;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import javax.vecmath.Point3d;
 import javax.vecmath.Quat4d;
@@ -12,21 +10,22 @@ import javax.vecmath.Vector3d;
 
 import org.simantics.g3d.math.MathTools;
 import org.simantics.plant3d.scenegraph.InlineComponent;
-import org.simantics.plant3d.scenegraph.Nozzle;
 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.Direction;
 import org.simantics.plant3d.utils.ComponentUtils;
+import org.simantics.utils.datastructures.Pair;
 import org.simantics.utils.ui.ErrorLogger;
 
 public class PipingRules {
        private static final boolean DEBUG = false;
        private static final boolean DUMMY = false;
 
-       private static double MIN_TURN_ANGLE = 0.001; // Threshold for removing turn components.
-       private static double ALLOWED_OFFSET = 0.001; // Allowed offset for directed path legs
+       private static double MIN_TURN_ANGLE = 0.001;    // Threshold for removing turn components.
+       private static double ALLOWED_OFFSET = 0.001;    // Allowed offset for directed path legs
+       private static double MIN_INLINE_LENGTH = 0.0005; // Minimum length of inline components, when component removal is not allowed. 
 
        private static final int REMOVE_NONE = 0;
        private static final int REMOVE_START = 1;
@@ -34,11 +33,16 @@ public class PipingRules {
        private static final int REMOVE_BOTH = 3;
        
 
+       // PathLeg iteration indicator. NEXT_S > NEXT > NONE  PREV_S > PREV > NONE 
        private enum PathLegUpdateType {
-               NONE, PREV, NEXT, PREV_S, NEXT_S
+               NONE,   // Only current path leg needs to be updated  (for example, inline comp was moved)
+               PREV,   // Current and previous path leg need to be updated 
+               NEXT,   // Current and next path leg need to be updated
+               PREV_S, // Current and previous two path legs need to be updated (turn was moved, which affect other path leg end turns, and thus following path legs
+               NEXT_S  // Current and next two path legs need to be updated
        };
        
-       private static boolean enabled = true;
+       private static boolean enabled = true;  // 
        private static boolean updating = false;
        private static boolean allowInsertRemove = true;
        private static boolean triedIR = false;
@@ -113,12 +117,14 @@ public class PipingRules {
                        allowInsertRemove = allowIR;
                        triedIR = false;
                        validate(pcp.getPipeRun());
-                       if (pcp.isPathLegEnd()) {
+                       if (pcp.getParentPoint() != null)
+                           pcp = pcp.getParentPoint();
+                       if (pcp.asPathLegEnd()) {
                                updatePathLegEndControlPoint(pcp); // FIXME: Rules won't work properly, if they are not run twice.
-                               updatePathLegEndControlPoint(pcp);
+                               //updatePathLegEndControlPoint(pcp);
                        } else {
                                updateInlineControlPoint(pcp);
-                               updateInlineControlPoint(pcp);
+                               //updateInlineControlPoint(pcp);
                        }
                        validate(pcp.getPipeRun());
                        if (!allowInsertRemove)
@@ -132,17 +138,16 @@ public class PipingRules {
        
        public static void setEnabled(boolean enabled) {
                PipingRules.enabled = enabled;
-               if(!enabled)
-                       currentUpdates.clear();
+               if(!enabled) {
+                       synchronized (ruleMutex) {
+                               currentUpdates.clear();
+                       }
+               }
        }
        
        public static boolean isEnabled() {
                return enabled;
        }
-       
-//     private void commit() {
-//             root.getNodeMap().commit();
-//     }
 
        public static class ExpandIterInfo {
                // these two are turn control points
@@ -211,6 +216,17 @@ public class PipingRules {
                        System.out.println("PipingRules.updateInlineControlPoint() " + pcp);
                PipeControlPoint start = pcp.findPreviousEnd();
                updatePathLegNext(start, pcp, PathLegUpdateType.NONE);
+               
+               if (pcp.isOffset()) {
+                       // Adjusting the rotation angle of an offset component may change variable angle turns
+                       PipeControlPoint end = pcp.findNextEnd();
+                       if (end.isVariableAngle()) {
+                               updatePathLegNext(end, end, PathLegUpdateType.NONE);
+                       }
+                       if (start.isVariableAngle()) {
+                               updatePathLegPrev(start, start, PathLegUpdateType.NONE);
+                       }
+               }
        }
 
        private static PipeControlPoint insertElbow(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos) throws Exception{
@@ -218,15 +234,15 @@ public class PipingRules {
                        System.out.println("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos);
                if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
                        
-               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
-                       pcp1 = pcp1.getSubPoint().get(0);       
+               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) {
+                       pcp1 = pcp1.getDualSub();       
                } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
                        PipeControlPoint t = pcp1;
                        pcp1 = pcp2;
                        pcp2 = t;
-               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
+               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getDualSub() && pcp2.getNext() == pcp1) {
                        PipeControlPoint t = pcp1;
-                       pcp1 = pcp2.getSubPoint().get(0);
+                       pcp1 = pcp2.getDualSub();
                        pcp2 = t;
                } else {
                        throw new RuntimeException();
@@ -234,7 +250,7 @@ public class PipingRules {
                TurnComponent elbow = ComponentUtils.createTurn((P3DRootNode)pcp1.getRootNode());
                PipeControlPoint pcp = elbow.getControlPoint();
                if (pcp1.isDualInline())
-                       pcp1 = pcp1.getSubPoint().get(0);
+                       pcp1 = pcp1.getDualSub();
                String name = pcp1.getPipeRun().getUniqueName("Elbow");
                elbow.setName(name);
                pcp1.getPipeRun().addChild(elbow);
@@ -251,15 +267,15 @@ public class PipingRules {
                        System.out.println("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos);
                if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
                        
-               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
-                       pcp1 = pcp1.getSubPoint().get(0);       
+               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) {
+                       pcp1 = pcp1.getDualSub();       
                } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
                        PipeControlPoint t = pcp1;
                        pcp1 = pcp2;
                        pcp2 = t;
-               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
+               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getDualSub() && pcp2.getNext() == pcp1) {
                        PipeControlPoint t = pcp1;
-                       pcp1 = pcp2.getSubPoint().get(0);
+                       pcp1 = pcp2.getDualSub();
                        pcp2 = t;
                } else {
                        throw new RuntimeException();
@@ -267,7 +283,7 @@ public class PipingRules {
                InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp1.getRootNode());
                PipeControlPoint scp = component.getControlPoint();
                if (pcp1.isDualInline())
-                       pcp1 = pcp1.getSubPoint().get(0);
+                       pcp1 = pcp1.getDualSub();
                String name = pcp1.getPipeRun().getUniqueName("Pipe");
                component.setName(name);
                pcp1.getPipeRun().addChild(component);
@@ -275,6 +291,9 @@ public class PipingRules {
                scp.insert(pcp1, pcp2);
 
                scp.setWorldPosition(pos);
+               Vector3d dir = new Vector3d();
+               dir.sub(pcp2.getWorldPosition(), pcp1.getWorldPosition());
+               updateControlPointOrientation(scp, dir);
                scp.setLength(length);
                validate(scp.getPipeRun());
                return scp;
@@ -287,7 +306,7 @@ public class PipingRules {
                InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode());
                PipeControlPoint scp = component.getControlPoint();
                if (pcp.isDualInline() && direction == Direction.NEXT)
-                       pcp = pcp.getSubPoint().get(0);
+                       pcp = pcp.getDualSub();
                String name = pcp.getPipeRun().getUniqueName("Pipe");
                component.setName(name);
                pcp.getPipeRun().addChild(component);
@@ -301,14 +320,7 @@ public class PipingRules {
        }
 
        private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-
                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;
-               }
                if (us == null) {
                    System.out.println("Null update struct " + start);
                    return; 
@@ -317,14 +329,7 @@ public class PipingRules {
        }
        
        private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               // TODO: this method is not symmetric with updatePathLegNext, which may alter lengthChange parameter?
                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;
-//        }
                if (us == null) {
             System.out.println("Null update struct " + start);
             return; 
@@ -375,22 +380,18 @@ public class PipingRules {
 
        }
 
-       private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, ArrayList<PipeControlPoint> list, Vector3d dir, Vector3d offset) {
-               boolean hasOffsets = false;
-               List<PipeControlPoint> offsets = new ArrayList<PipeControlPoint>(list.size());
-               for (PipeControlPoint icp : list) {
-                       if (icp.isOffset()) {
-                               offsets.add(icp);
-                       } else if (icp.isDualSub())
-                               ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
-               }
+       public static boolean calculateDirectedOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d dir, Vector3d offset) {
+               return calculateOffset(startPoint, endPoint, start, list, end, dir, offset, true);
+       }
+
+       public static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d dir, Vector3d offset) {
+               return calculateOffset(startPoint, endPoint, start, list, end, dir, offset, false);
+       }
+       
+       private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d dir, Vector3d offset, boolean directed) {
+               List<PipeControlPoint> offsets = getOffsetPoints(start, list);
                if (offsets.size() == 0) {
-                       dir.set(endPoint);
-                       dir.sub(startPoint);
-                       double l = dir.lengthSquared(); 
-                       if (l > MathTools.NEAR_ZERO)
-                               dir.scale(1.0/Math.sqrt(l));
-                       offset.set(0.0, 0.0, 0.0);
+                       setZeroOffset(startPoint, endPoint, dir, offset);
                        return false;
                } else {
                        Vector3d sp = new Vector3d(startPoint);
@@ -400,8 +401,9 @@ public class PipingRules {
                        double l = dir.lengthSquared(); 
                        if (l > MathTools.NEAR_ZERO)
                                dir.scale(1.0/Math.sqrt(l));
+                       
                        int iter = 100;
-                       while (iter >= 0) {
+                       while (true) {
                                iter--;
                                offset.set(0.0, 0.0, 0.0);
                                
@@ -409,11 +411,16 @@ public class PipingRules {
                                        Vector3d v = icp.getSizeChangeOffsetVector(dir);
                                        offset.add(v);
                                }
+                               
+                               if (directed)
+                                       break;
+                               
                                Point3d nep = new Point3d(endPoint);
                                nep.sub(offset);
-                               if (nep.distance(ep) < 0.0000000001) {
+                               if (nep.distance(ep) < 0.0000000001 || iter <= 0) {
                                        break;
-                               } 
+                               }
+                               
                                ep = nep;
                                dir.set(ep);
                                dir.sub(sp);
@@ -421,14 +428,37 @@ public class PipingRules {
                                if (l > MathTools.NEAR_ZERO)
                                        dir.scale(1.0/Math.sqrt(l));
                        }
-                       hasOffsets = true;
+                       
+                       if (DEBUG)
+                               System.out.println("calcOffset s:"+ startPoint + " e:" + endPoint + " d:" + dir + " o:"+offset) ;
+                       
+                       return true;
                }
-               
-               if (DEBUG && hasOffsets)
-                       System.out.println("calcOffset s:"+ startPoint + " e:" + endPoint + " d:" + dir + " o:"+offset) ;
-               return hasOffsets;
        }
        
+       public static void setZeroOffset(Vector3d startPoint, Vector3d endPoint, Vector3d dir, Vector3d offset) {
+               dir.set(endPoint);
+               dir.sub(startPoint);
+               double l = dir.lengthSquared(); 
+               if (l > MathTools.NEAR_ZERO)
+                       dir.scale(1.0/Math.sqrt(l));
+               offset.set(0.0, 0.0, 0.0);
+       }
+
+       public static List<PipeControlPoint> getOffsetPoints(PipeControlPoint start, ArrayList<PipeControlPoint> list) {
+               List<PipeControlPoint> offsets = new ArrayList<PipeControlPoint>(list.size());
+               // Only start offset affects the calculation
+               if (start.isOffset())
+                   offsets.add(start);
+               for (PipeControlPoint icp : list) {
+                       if (icp.isOffset()) {
+                               offsets.add(icp);
+                       } else if (icp.isDualSub())
+                               ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+               }
+               return offsets;
+       }
+
        private static UpdateStruct2 createUS(PipeControlPoint start, Direction direction, int iter, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) {
                ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
                PipeControlPoint end = null;
@@ -454,14 +484,29 @@ public class PipingRules {
                Vector3d startPoint = start.getWorldPosition();
                Vector3d endPoint = end.getWorldPosition();
                Vector3d dir = new Vector3d();
-               hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
+               hasOffsets = calculateOffset(startPoint, endPoint, start, list, end, dir, offset);
                return new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, direction == Direction.PREVIOUS, toRemove, updated);
        }
        
+       private static Vector3d pathLegDirection(PipeControlPoint start) {
+               ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
+               PipeControlPoint end = start.findNextEnd(list);
+               if (start == end) {
+                       return start.getDirection(Direction.NEXT);
+               }
+               
+               Vector3d offset = new Vector3d();
+               Vector3d startPoint = start.getWorldPosition();
+               Vector3d endPoint = end.getWorldPosition();
+               Vector3d dir = new Vector3d();
+               calculateOffset(startPoint, endPoint, start, list, end, dir, offset);
+               return dir;
+       }
+       
        private static boolean asDirected(PipeControlPoint pcp, Direction direction) {
                if (pcp.isDirected())
                        return true;
-               if (pcp.isTurn() && pcp.isFixed()) {
+               if (pcp.asFixedAngle()) {
                        if (!pcp._getReversed())
                                return direction == Direction.NEXT;
                        else
@@ -475,11 +520,27 @@ public class PipingRules {
        }
  
        private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+           boolean rs = true;
+           boolean re = true;
+           if (lengthChange == PathLegUpdateType.NONE) {
+               rs = false;
+               re = false;
+           }
+           updatePathLeg(u, lengthChange, rs, re);
+       }
+       
+       private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean rs, boolean re) throws Exception {
                int directed = 0;
                if (asDirected(u.start, Direction.NEXT))
                        directed++;
                if (asDirected(u.end, Direction.PREVIOUS))
                        directed++;
+               if (rs)
+                   setErrorForce(u.start, null);
+               if (re)
+                   setErrorForce(u.end, null);
+        for (PipeControlPoint pcp : u.list)
+            setErrorForce(pcp, null);
                switch (directed) {
                case 0:
                        updateFreePathLeg(u, lengthChange);
@@ -498,7 +559,7 @@ public class PipingRules {
                if (DEBUG)
                        System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
                checkExpandPathLeg(u, lengthChange);
-               if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
+               if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle()|| u.end.asFixedAngle())
                        processPathLeg(u, true, false);
        }
 
@@ -511,183 +572,339 @@ public class PipingRules {
                
                if (checkSizes) {
                        // create offsets for leg ends.
-                       MathTools.mad(start, u.dir, u.start.getInlineLength());
-                       MathTools.mad(end, u.dir, -u.end.getInlineLength());
+                   if (u.start.isTurn())
+                       MathTools.mad(start, u.dir, u.start.getInlineLength());
+                   if (u.end.isTurn())
+                       MathTools.mad(end, u.dir, -u.end.getInlineLength());   
                }
-               
-               boolean recalcline = false;
-               if (!u.hasOffsets) {
-                       
-                       
-                       for (PipeControlPoint icp : u.list) {
-                               updateInlineControlPoint(icp, start, end, u.dir);
-                               
-                               if (icp.isOffset()) {
-                                       // TODO : offset vector is already calculated and should be cached
-                                       Vector3d off = icp.getSizeChangeOffsetVector(u.dir);
-                                       updateOffsetPoint(icp, off);
-                               }
-                       }
-                       if (!checkSizes)
-                               return;                 
-
-                       ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
-                       pathLegPoints.add(u.start);
-                       for (PipeControlPoint icp : u.list) {
-                               // updateInlineControlPoint(icp, u.startPoint,
-                               // u.endPoint,u.dir);
-                               updateBranchControlPointBranches(icp);
-                               pathLegPoints.add(icp);
-                       }
-                       pathLegPoints.add(u.end);
-
-                       // TODO : values can be cached in the loop
-                       for (int i = 0; i < pathLegPoints.size(); i++) {
-                               PipeControlPoint icp = pathLegPoints.get(i);
-
-                               PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null;
-                               PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null;
-
-                               if (icp.isVariableLength()) {
-                                       if (prev != null && next != null) {
-                                               
-                                               recalcline = recalcline | updateVariableLength(icp,  prev, next);
-
-                                       } else {
-                                               // this is variable length component at the end of the
-                                               // piperun.
-                                               // the problem is that we want to keep unconnected end
-                                               // of the component in the same
-                                               // place, but center of the component must be moved.
-                                               updateVariableLengthEnd(icp, prev != null ? prev : next);
-                                       }
 
+               boolean recalcline = false;
 
-                               } else if (prev != null && !prev.isVariableLength()) {
-                                       // If this and previous control point are not variable
-                                       // length pcps, we'll have to check if there is no empty
-                                       // space between them.
-                                       // I there is, we'll have to create new variable length
-                                       // component between them.
-                                       recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
-                               }
+               Vector3d sp = new Vector3d(start);
+               Vector3d ep = new Vector3d(end);
+               ep.sub(u.offset);
+               
+               if (u.start.isOffset()) {
+                   Vector3d offset = u.start.getSizeChangeOffsetVector(u.dir);
+                   updateOffsetPoint(u.start, offset);
+            sp.add(offset);
+            ep.add(offset);
+               }
+                   
+               for (PipeControlPoint icp : u.list) {
+                       updateInlineControlPoint(icp, sp, ep, u.dir);
+                       if (icp.isOffset()) {
+                               // TODO : offset vector is already calculated and should be cached
+                               Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
+                               updateOffsetPoint(icp, offset);
+                               sp.add(offset);
+                               ep.add(offset);
                        }
-               } else { // with offset
-                       Vector3d sp = new Vector3d(start);
-                       Vector3d ep = new Vector3d(end);
-                       ep.sub(u.offset);
-                       
-                       ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
-                       pathLegPoints.add(u.start);
+               }
+               
+               if (!checkSizes)
+                       return; 
+
+               // Collect all path leg points for updating variable length components. This list will also contain leg ends (usually turns)
+        ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<>();
+        // Collect all fixed length components with their offsets. 
+        ArrayList<Pair<PipeControlPoint,Vector3d>> fixedLengthPoints = new ArrayList<>();
+
+        pathLegPoints.add(u.start);
+        fixedLengthPoints.add(new Pair<PipeControlPoint, Vector3d>(u.start, new Vector3d()));
+        Vector3d off = new Vector3d();
+        for (PipeControlPoint icp : u.list) {
+            pathLegPoints.add(icp);
+            updateBranchControlPointBranches(icp);
+            if (icp.isOffset()) {
+                fixedLengthPoints.add(new Pair<PipeControlPoint, Vector3d>(icp, new Vector3d(off)));
+                Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
+                off.add(offset);
+            } else if (!icp.isVariableLength()) {
+                fixedLengthPoints.add(new Pair<PipeControlPoint, Vector3d>(icp, new Vector3d(off)));
+            }
+        }
+        pathLegPoints.add(u.end);
+        fixedLengthPoints.add(new Pair<PipeControlPoint, Vector3d>(u.end, new Vector3d(off)));
+        
+               sp = new Vector3d(start);
+               ep = new Vector3d(end);
+               ep.sub(u.offset);
+               
+               updateFixedLengths(fixedLengthPoints, sp, ep, u.dir);
+               
+               for (int i = 0; i < pathLegPoints.size(); i++) {
+                       PipeControlPoint icp = pathLegPoints.get(i);
 
-                       for (PipeControlPoint icp : u.list) {
-                               updateInlineControlPoint(icp, sp, ep, u.dir);
-                               updateBranchControlPointBranches(icp);
-                               pathLegPoints.add(icp);
-                               if (icp.isOffset()) {
-                                       // TODO : offset vector is already calculated and should be
-                                       // cached
-                                       Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
-                                       updateOffsetPoint(icp, offset);
-                                       sp.add(offset);
-                                       ep.add(offset);
-                               }
-                       }
-                       pathLegPoints.add(u.end);
-                       
-                       if (!checkSizes)
-                               return; 
+                       PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null;
+                       PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null;
                        
-                       sp = new Vector3d(u.startPoint);
-                       ep = new Vector3d(u.endPoint);
-                       ep.sub(u.offset);
-                       
-                       for (int i = 0; i < pathLegPoints.size(); i++) {
-                               PipeControlPoint icp = pathLegPoints.get(i);
-
-                               PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null;
-                               PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null;
-                               
-                               if (prev != null && prev.isDualInline())
-                                       prev = prev.getSubPoint().get(0);
-                               
+                       if (prev != null && prev.isDualInline())
+                               prev = prev.getDualSub();               
 
-                               if (icp.isVariableLength()) {
-                                       if (prev != null && next != null) {
-                                               recalcline = recalcline | updateVariableLength(icp,  prev, next);
+                       if (icp.isVariableLength()) {
+                               if (prev != null && next != null) {
+                                       recalcline = recalcline | updateVariableLength(icp,  prev, next);
 
-                                       } else {
-                                               // this is variable length component at the end of the
-                                               // piperun.
-                                               // the problem is that we want to keep unconnected end
-                                               // of the component in the same
-                                               // place, but center of the component must be moved.
-                                               updateVariableLengthEnd(icp, prev != null ? prev : next);
-                                       }
-                               } else if (prev != null && !prev.isVariableLength()) {
-                                       // If this and previous control point are not variable
-                                       // length pcps, we'll have to check if there is no empty
-                                       // space between them.
-                                       // I there is, we'll have to create new variable length
-                                       // component between them.
-                                       recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
-                               }
-                               if (icp.isOffset()) {
-                                       // TODO : offset vector is already calculated and should be
-                                       // cached
-                                       Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
-                                       sp.add(offset);
-                                       ep.add(offset);
+                               } else {
+                                   // this is variable length component at the end of the piperun.
+                    // the problem is that we want to keep unconnected end of the component in the same
+                    // place, but center of the component must be moved.
+                                       updateVariableLengthEnd(icp, prev != null ? prev : next);
                                }
+                       } else if (prev != null && !prev.isVariableLength()) {
+                           // If this and previous control point are not variable length pcps, 
+                // we'll have to check if there is no empty space between them.
+                // I there is, we'll have to create new variable length component between them.
+                               recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
+                       }
+                       if (icp.isOffset()) {
+                               // TODO : offset vector is already calculated and should be cached
+                               Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
+                               sp.add(offset);
+                               ep.add(offset);
                        }
                }
+               
                if (recalcline) {
                        u.list.clear();
                        u.start.findNextEnd(u.list);
+               } 
+               if (checkSizes) {
+                   sp = new Vector3d(u.startPoint);
+               ep = new Vector3d(u.endPoint);
+               ep.sub(u.offset);
+                   double pathLegLength = MathTools.distance(sp, ep);
+            double availableLength = pathLegLength;
+            if (u.start.isTurn())
+                availableLength -= u.start.getInlineLength();
+            if (u.end.isTurn())
+                availableLength -= u.end.getInlineLength();
+            for (PipeControlPoint pcp : u.list) {
+                if (!pcp.isVariableLength())
+                    availableLength-= pcp.getLength();
+                   }
+            if (availableLength < 0.0) {
+                setError(u.start, "Not enough available space");
+                setError(u.end, "Not enough available space");
+                for (PipeControlPoint pcp : u.list)
+                    setError(pcp, "Not enough available space");
+            }
+//            System.out.println(u.start.getPipelineComponent().toString() + " " + pathLegLength + " " + availableLength + " " + u.end.getPipelineComponent().toString() + " " + u.start.getInlineLength() + " " + u.end.getInlineLength());
                }
        }
        
+       private enum Gap{ATTACHED,OVERLAP,SPACE};
+       
+       private static class GapObj {
+           Gap gap;
+           double d;
+           
+           Pair<PipeControlPoint,Vector3d> pcp1;
+           Pair<PipeControlPoint,Vector3d> pcp2;
+       }
+       
+       private static void updateFixedLengths(List<Pair<PipeControlPoint,Vector3d>> fixedLengthPoints, Vector3d s, Vector3d e, Vector3d dir) {
+           double totalLength = MathTools.distance(s, e);
+           double reservedLength = 0.0;
+           List<Double> distances = new ArrayList<>(fixedLengthPoints.size());
+           distances.add(0.0);
+           for (int i = 1; i < fixedLengthPoints.size()-1; i++) {
+               Pair<PipeControlPoint,Vector3d> pcp = fixedLengthPoints.get(i);
+               reservedLength += pcp.first.getLength();
+               Vector3d p = pcp.first.getWorldPosition();
+               p.sub(pcp.second);
+               double d= MathTools.distance(s, p);
+               distances.add(d);
+           }
+           distances.add(totalLength);
+           
+           if (totalLength >= reservedLength) {
+               // There is enough space for all fixed length components.
+               List<GapObj> gaps = new ArrayList<>(fixedLengthPoints.size()-1);
+               int overlaps = 0;
+               // Analyze gaps between components
+               for (int i = 0; i < fixedLengthPoints.size()-1; i++) {
+                   Pair<PipeControlPoint,Vector3d> pcp1 = fixedLengthPoints.get(i);
+                   Pair<PipeControlPoint,Vector3d> pcp2 = fixedLengthPoints.get(i+1);
+                   double d1 = distances.get(i);
+                   double d2 = distances.get(i+1);
+                   double ld1 = i == 0 ? 0.0 :pcp1.first.getInlineLength();
+                   double ld2 = i == fixedLengthPoints.size()-2 ? 0.0 : pcp2.first.getInlineLength();
+                   
+                   double e1 = d1 + ld1; // End of comp1
+                   double s2 = d2 - ld2; // Start of comp2
+                   double diff =s2 - e1;
+                   GapObj obj = new GapObj();
+                   obj.pcp1 = pcp1;
+                   obj.pcp2 = pcp2;
+                   obj.d = diff;
+                   if (diff < -MIN_INLINE_LENGTH) {
+                       obj.gap = Gap.OVERLAP;
+                       overlaps++;
+                   } else if (diff > MIN_INLINE_LENGTH) {
+                       obj.gap = Gap.SPACE;
+                   } else {
+                       obj.gap = Gap.ATTACHED;
+                   }
+                   gaps.add(obj);
+               }
+               // If there are no overlaps, there is nothing to do.
+               if (overlaps == 0)
+                   return;
+               // Get rid of overlapping components by using closest available free spaces.
+               for (int i = 0; i < gaps.size(); i++) {
+                   GapObj gapObj = gaps.get(i);
+                   if (gapObj.gap != Gap.OVERLAP)
+                       continue;
+                   double curr = gapObj.d;
+                   int d = 1;
+                   while (d < gaps.size() && curr < -MIN_INLINE_LENGTH) {
+                       GapObj next = i+d >= 0 ? gaps.get(i+d) : null;
+                    GapObj prev = i-d >= 0 ? gaps.get(i-d) : null;
+                       if (next != null && next.gap == Gap.SPACE) {
+                           double move = Math.min(-curr, next.d);
+                           curr+= move;
+                           next.d -= move;
+                           if (next.d < MIN_INLINE_LENGTH)
+                               next.gap = Gap.ATTACHED;
+                           Vector3d mv = new Vector3d(dir);
+                           mv.normalize();
+                           mv.scale(move);
+                           for (int j = i ; j < i+d; j++) {
+                               Pair<PipeControlPoint,Vector3d> pcp = gaps.get(j).pcp2;
+                               Vector3d p = new Vector3d(pcp.first.getWorldPosition());
+                               p.add(mv);
+                               pcp.first.setWorldPosition(p);
+                           }
+                       }
+                       else if (prev != null && prev.gap == Gap.SPACE) {
+                           double move = Math.min(-curr, prev.d);
+                        curr+= move;
+                        prev.d -= move;
+                        if (prev.d < MIN_INLINE_LENGTH)
+                            prev.gap = Gap.ATTACHED;
+                        Vector3d mv = new Vector3d(dir);
+                        mv.normalize();
+                        mv.scale(-move);
+                        for (int j = i ; j > i-d; j--) {
+                            Pair<PipeControlPoint,Vector3d> pcp = gaps.get(j).pcp1;
+                            Vector3d p = new Vector3d(pcp.first.getWorldPosition());
+                            p.add(mv);
+                            pcp.first.setWorldPosition(p);
+                        }
+                       }
+                       else {
+                           d++;
+                       }
+                   }
+               }
+           } else {
+            for (int i = 1; i < fixedLengthPoints.size()-1; i++) {
+               Pair<PipeControlPoint,Vector3d> prev = i == 0 ? null : fixedLengthPoints.get(i-1);
+               Pair<PipeControlPoint,Vector3d> curr = fixedLengthPoints.get(i);
+               Pair<PipeControlPoint,Vector3d> next = i == fixedLengthPoints.size() -1 ? null : fixedLengthPoints.get(i+1);
+                updateFixedLength(curr, prev, next, s,e, dir);
+            }
+           }
+       }
+       
+       private static void updateFixedLength(Pair<PipeControlPoint,Vector3d> icp, Pair<PipeControlPoint,Vector3d> prev,  Pair<PipeControlPoint,Vector3d> next, Vector3d s, Vector3d e, Vector3d dir) {
+           if (prev != null) {
+              checkOverlap(prev, icp, dir,true);
+           }
+           if (next != null)
+               checkOverlap(icp, next, dir,true);
+       }
+       
+       private static boolean checkOverlap(Pair<PipeControlPoint,Vector3d> icp, Pair<PipeControlPoint,Vector3d> icp2, Vector3d dir, boolean se) {
+           Vector3d p1 = icp.first.getWorldPosition();
+           Vector3d p2 = icp2.first.getWorldPosition();
+           p1.add(icp.second);
+           p2.add(icp2.second);
+           double u[] = new double[1];
+           MathTools.closestPointOnStraight(p2, p1, dir, u);
+           if (u[0] < 0.0) {
+               p2.set(p1);
+               p2.sub(icp.second);
+               p2.add(icp2.second);
+               MathTools.mad(p2, dir, MIN_INLINE_LENGTH);
+               icp2.first.setWorldPosition(p2);
+           }
+           double d = MathTools.distance(p1, p2);
+        double r = icp.first.getInlineLength() + icp2.first.getInlineLength();
+        
+           if ((d-r) < - MIN_INLINE_LENGTH) {
+            if (se) {
+                setError(icp.first, "Overlapping");
+                setError(icp2.first, "Overlapping");
+            }
+            return true;
+        }
+        return false;
+       }
+       
+       /**
+        * Overrides current error of a component
+        * @param pcp
+        * @param error
+        */
+       private static void setErrorForce(PipeControlPoint pcp, String error) {
+           PipelineComponent comp = pcp.getPipelineComponent();
+        if (comp == null)
+            return;
+        comp.setError(error);
+       }
+       
+       /**
+        * Sets error for a component, if there is no existing error.
+        * @param pcp
+        * @param error
+        */
+       private static void setError(PipeControlPoint pcp, String error) {
+           PipelineComponent comp = pcp.getPipelineComponent();
+           if (comp == null)
+               return;
+           if (comp.getError() != null)
+               return;
+           comp.setError(error);
+       }
+       
        private static boolean updateVariableLength(PipeControlPoint icp, PipeControlPoint prev,  PipeControlPoint next) {
                Vector3d prevPos = prev.getWorldPosition();
                Vector3d nextPos = next.getWorldPosition();
                
                Vector3d dir = new Vector3d(nextPos);
                dir.sub(prevPos);
-               double l = dir.lengthSquared(); // distance between
-                                                                               // control points
-                                                                               // (square)
-               double l2prev = prev.getInlineLength(); // distance
-                                                                                                                                       // taken
-                                                                                                                                       // by
-                                                                                                                                       // components
+               double l = dir.length();                // distance between control points
+               double l2prev = prev.getInlineLength(); // distance taken by components
                double l2next = next.getInlineLength();
                double l2 = l2prev + l2next;
-               double l2s = MathTools.square(l2);
-               if (l2s < l) { // check if there is enough space for
-                                               // variable length component.
+               double length = l - l2;                 // true length of the variable length component
+               if (length >= MIN_INLINE_LENGTH) {      // check if there is enough space for variable length component.
                        // components fit
                        dir.normalize();
-                       double length = Math.sqrt(l) - l2; // true length of
-                                                                                               // the variable
-                                                                                               // length
-                                                                                               // component
-                       dir.scale(length * 0.5 + l2prev); // calculate
-                                                                                               // center
-                                                                                               // position of
-                                                                                               // the component
+                       dir.scale(length * 0.5 + l2prev);   // calculate center position of the component
                        dir.add(prevPos);
                        icp.setWorldPosition(dir);
                        icp.setLength(length);
                        return false;
                } else {
-                       // components leave no space to the component and it
-                       // must be removed
-                       
+                       // components leave no space to the component and it must be removed
                        if (icp.isDeletable()) {
+                           if (!allowInsertRemove) {
+                               icp.setLength(MIN_INLINE_LENGTH);
+                               setError(icp, "Not enough available space");
+                               triedIR = true;
+                               return false;
+                           }
                                if (DEBUG)
                                        System.out.println("PipingRules.updateVariableLength removing " + icp);
                                icp._remove();
                                return true;
+                       } else {
+                           icp.setLength(MIN_INLINE_LENGTH);
+                           icp.getPipelineComponent().setError("Not enough available space");
                        }
                        return false;
                }
@@ -703,17 +920,14 @@ public class PipingRules {
                double l2next = icp.getInlineLength();
                double l2 = l2prev + l2next;
                double l2s = l2 * l2;
-               if (l > l2s) {
+               double diff = l - l2s;
+               if (diff >= MIN_INLINE_LENGTH) {
                        if (allowInsertRemove) {
                                dir.normalize();
-                               double length = Math.sqrt(l) - l2; // true length of the
-                                                                                                       // variable length
-                                                                                                       // component
-                               dir.scale(length * 0.5 + l2prev); // calculate center
-                                                                                                       // position of the
-                                                                                                       // component
+                               double length = Math.sqrt(l) - l2; // true length of the variable length component
+                               dir.scale(length * 0.5 + l2prev); // calculate center position of the component
                                dir.add(prevPos);
-                               PipeControlPoint scp = insertStraight(prev, icp, dir, length);
+                               insertStraight(prev, icp, dir, length);
                                return true;
                        } else {
                                triedIR = true;
@@ -729,7 +943,11 @@ public class PipingRules {
                Vector3d dir = new Vector3d();
                dir.sub(currentPos, prevPos);
                
-               boolean simple = currentUpdates.contains(icp);
+               boolean simple;
+               synchronized (ruleMutex) {
+                       simple = currentUpdates.contains(icp);
+               }
+               
                if (simple) {
                        // Update based on position -> adjust length
                        double currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
@@ -768,12 +986,17 @@ public class PipingRules {
                }
        }
 
-       private static void ppNoOffset(UpdateStruct2 u) throws Exception {
+       /**
+        * Recalculates offset vector based on current direction, and calls checkExpandPathLeg
+        * @param u
+        * @param updateEnds
+        * @throws Exception
+        */
+       private static void ppNoOffset(UpdateStruct2 u, boolean updateEnds) throws Exception {
                if (DEBUG)
                        System.out.println("PipingRules.ppNoOffset() " + u);
                Vector3d offset = new Vector3d();
                if (u.hasOffsets) {
-                       u.dir.normalize();
                        for (PipeControlPoint icp : u.list) {
                                if (icp.isOffset()) {
                                        offset.add(icp.getSizeChangeOffsetVector(u.dir));
@@ -782,7 +1005,7 @@ public class PipingRules {
                        }
                }
                u.offset = offset;
-               checkExpandPathLeg(u, PathLegUpdateType.NONE);
+               checkExpandPathLeg(u, PathLegUpdateType.NONE, updateEnds);
        }
 
        private static void ppNoDir(PipeControlPoint start, Vector3d startPoint, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d endPoint, boolean hasOffsets, int iter, boolean reversed, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) throws Exception {
@@ -791,8 +1014,8 @@ public class PipingRules {
                // FIXME : extra loop (dir should be calculated here)
                Vector3d dir = new Vector3d();
                Vector3d offset = new Vector3d();
-               hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
-               ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated));
+               hasOffsets = calculateOffset(startPoint, endPoint, start, list, end, dir, offset);
+               ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated),true);
        }
 
        private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
@@ -832,7 +1055,7 @@ public class PipingRules {
                        position = u.startPoint;
                        dcpStart = true;
                        if (!u.reversed)
-                               canMoveOther = true;
+                canMoveOther = true;
                        inlineEnd = u.end.isInline();
                                
                } else {
@@ -840,10 +1063,10 @@ public class PipingRules {
                        other = u.start;
                        position = u.endPoint;
                        if (u.reversed)
-                               canMoveOther = true;
+                canMoveOther = true;
                        inlineEnd = u.start.isInline();
                }
-
+               
                Vector3d directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
                if (directedDirection == null) {
                        //updateTurnControlPointTurn(dcp, dcp.getPrevious(), dcp.getNext());
@@ -853,30 +1076,44 @@ public class PipingRules {
                                return;
                        }
                }
-               Point3d directedEndPoint = new Point3d(u.endPoint);
-               if (u.hasOffsets)
-                       directedEndPoint.add(u.offset);
+               
+               Point3d otherPosition = new Point3d(dcpStart ? u.endPoint : u.startPoint);
+               if (u.hasOffsets) {
+                       Vector3d dir = new Vector3d(), offset = new Vector3d();
+                       calculateDirectedOffset(u.startPoint, u.endPoint, u.start, u.list, u.end, dir, offset);
+                       u.dir = dir;
+                       u.offset = offset;
+                       
+                       if (dcpStart)
+                               otherPosition.add(offset);
+                       else
+                               otherPosition.sub(offset);
+               }
 
                double mu[] = new double[2];
 
                Vector3d closest;
                Vector3d t = new Vector3d();
 
-               if (dcpStart) {
-                       closest = MathTools.closestPointOnStraight(directedEndPoint, u.startPoint, directedDirection, mu);
-                       t.sub(closest, directedEndPoint);
-               } else {
-                       closest = MathTools.closestPointOnStraight(u.startPoint, directedEndPoint, directedDirection, mu);
-                       t.sub(closest, u.startPoint);
-               }
+               closest = MathTools.closestPointOnStraight(otherPosition, position, directedDirection, mu);
+               t.sub(closest, otherPosition);
 
                double distance = t.length();
                boolean aligned = (distance < ALLOWED_OFFSET);
+               double requiredSpace  = 0.0;
+               if (other.isVariableAngle()) {
+                   requiredSpace = spaceForTurn(other, dcp);
+               }
+               if (mu[0] < requiredSpace) {
+                   // At the moment, if next component is directly behind the nozzle, we must force moving the other component.
+                   // Trying to solve the situation by adding new turn creates infinite loop...   
+                   aligned = false;
+                   canMoveOther = true;
+               }
                if (aligned) {
-                       if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
-                               processPathLeg(u, true, false);
-                       checkExpandPathLeg(u, lengthChange, inlineEnd);
-                       
+                       //if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle() || u.end.asFixedAngle())
+                   //    processPathLeg(u, true, false);
+                       checkExpandPathLeg(u, lengthChange, inlineEnd || u.start.isInline() || u.end.isInline() || u.start.asFixedAngle() || u.end.asFixedAngle());
                } else {
                        if (u.iter > 0) {
                                backIter(u);
@@ -895,28 +1132,40 @@ public class PipingRules {
                                if (other.isVariableAngle()) {
 
                                        // TODO calculate needed space from next run end.
-                                   double space = spaceForTurn(other);
-                                       if (mu[0] < space) {
+                                   if (mu[0] < requiredSpace) {
                                                if (dcpStart) {
                                                        closest.set(u.startPoint);
                                                } else {
                                                        closest.set(u.endPoint);
                                                }
                                                Vector3d v = new Vector3d(directedDirection);
-                                               v.scale(space);
+                                               v.scale(requiredSpace);
                                                closest.add(v);
                                        }
 
                                        if (canMoveOther) {
                                                if (DEBUG)
                                                        System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest);
+                                               
+                                               // Not aligned - we need to recalculate the offset to reflect new end points.
+                                               Vector3d offset;
+                                               if (u.hasOffsets) {
+                                                       offset = new Vector3d();
+                                                       Vector3d newDir = new Vector3d();
+                                                       calculateDirectedOffset(position, closest, u.start, u.list, u.end, newDir, offset);
+                                                       closest.add(offset);
+                                               } else {
+                                                       offset = new Vector3d();
+                                               }
+                                               
                                                other.setWorldPosition(closest);
+                                               
                                                if (dcpStart) {
-                                                       ppNoOffset(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(closest), directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated));
+                                                       checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(closest), directedDirection, offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), PathLegUpdateType.NONE, true);
                                                        if (u.end.getNext() != null)
                                                                updatePathLegNext(u.end, u.updated, PathLegUpdateType.NEXT);
                                                } else {
-                                                       ppNoOffset(new UpdateStruct2(u.start, new Vector3d(closest), u.list, u.end, u.endPoint, directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated));
+                                                       checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(closest), u.list, u.end, u.endPoint, directedDirection, offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), PathLegUpdateType.NONE, true);
                                                        if (u.start.getPrevious() != null)
                                                                updatePathLegPrev(u.start, u.updated, PathLegUpdateType.PREV);
                                                }
@@ -924,7 +1173,6 @@ public class PipingRules {
                                                // TODO : calculate needed space from next run end.
                                                if (allowInsertRemove)
                                                        insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
-                                                       
                                                else
                                                        triedIR = true;
                                        }
@@ -976,8 +1224,6 @@ public class PipingRules {
                                }
                        }
                }
-               
-               
        }
 
        private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
@@ -988,14 +1234,19 @@ public class PipingRules {
                PipeControlPoint dcp2 = u.end;
                Point3d position1 = new Point3d(u.startPoint);
                Point3d position2 = new Point3d(u.endPoint);
+               
+               Vector3d dir = new Vector3d(), offset = new Vector3d();
+               calculateDirectedOffset(new Vector3d(position1), new Vector3d(position2), u.start, u.list, u.end, dir, offset);
+               
                Point3d position1offset = new Point3d(position1);
-               position1offset.sub(u.offset);
+               position1offset.add(offset);
                Point3d position2offset = new Point3d(position2);
-               position2offset.add(u.offset);
+               position2offset.sub(offset);
                Vector3d dir1 = direction(dcp1, Direction.NEXT);
                Vector3d dir2 = direction(dcp2, Direction.PREVIOUS);
-               Vector3d p1 = MathTools.closestPointOnStraight(position1offset, position2, dir2);
-               Vector3d p2 = MathTools.closestPointOnStraight(position2offset, position1, dir1);
+               
+               Vector3d p1 = MathTools.closestPointOnStraight(position1, position2offset, dir2);
+               Vector3d p2 = MathTools.closestPointOnStraight(position2, position1offset, dir1);
                double d1 = position1.distance(new Point3d(p1));
                double d2 = position2.distance(new Point3d(p2));
 
@@ -1035,9 +1286,9 @@ public class PipingRules {
                                p1.add(v);
 
                                if (!u.reversed)
-                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2, dir2);
+                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2offset, dir2);
                                else
-                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1, dir1);
+                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1offset, dir1);
 
                                // By default, the elbows are placed next to each other, by using 90 deg angles.
                                // If the distance between elbows is not enough, we must move the other elbow (and create more shallow angle elbows)
@@ -1079,11 +1330,71 @@ public class PipingRules {
                
        }
        
-       private static double spaceForTurn(PipeControlPoint tcp) {
-               // TODO : this returns now space for 90 deg turn.
-               // The challenge: position of tcp affects the turn angle, which then affects the required space. Perhaps we need to iterate...
-               // Additionally, if the path legs contain offset, using just positions of opposite path leg ends is not enough,    
-               return ((TurnComponent)tcp.getPipelineComponent()).getTurnRadius();
+       private static double spaceForTurn(PipeControlPoint tcp, PipeControlPoint dcp) {
+               // TODO : if the path legs contain offset, using just positions of opposite path leg ends is not enough.
+           // TODO : current iterative way for calculating required space may return longer length that is required.
+               double tr = ((TurnComponent)tcp.getPipelineComponent()).getTurnRadius();
+           if (dcp == null)
+               return tr; // space for 90 deg
+           PipeControlPoint ne = tcp.findNextEnd();
+           PipeControlPoint pe = tcp.findPreviousEnd();
+           PipeControlPoint other = null;
+           if (dcp == ne)
+               other = pe;
+           else if (dcp == pe)
+               other = ne;
+           else
+               return tr; // space for 90 deg
+           if (other == null)
+               return tr; // space for 90 deg
+           Vector3d dir = dcp.getDirectedControlPointDirection();
+           Vector3d dir2;
+           if (other == ne) {
+               dir2 = pathLegDirection(tcp);
+           } else {
+               dir2 = pathLegDirection(pe);
+               dir2.negate();
+           }
+
+           double d = dir.dot(dir2);
+           if (d > 0.9999)
+               return 0.0; // point following turn is directly in the front of the nozzle.
+           else if (d < -0.9999)
+            return tr*2.0; // point following turn is directly behind the nozzle, in theory, we should return Double.Inf...
+           
+           double curr = 0.0;
+           int iter = 10;
+           Vector3d tp0 = tcp.getPosition();
+           try {
+                   Vector3d dp = dcp.getWorldPosition();
+                   while (iter > 0) {
+                       Vector3d tp = new Vector3d(dir);
+                       tp.scaleAdd(curr, dp);
+                       tcp._setPosition(tp); // no firing of listeners here
+                           if (other == ne) {
+                               dir2 = pathLegDirection(tcp);
+                           } else {
+                               dir2 = pathLegDirection(pe);
+                               dir2.negate();
+                           }
+                           
+                       double a = dir.angle(dir2);
+                       
+                       // other is directly between dcp and tcp, a zero angle turn should do
+                       if (Math.PI - a <= MathTools.NEAR_ZERO)
+                           return 0.0;
+                       
+                       double R = tr * Math.tan(a * 0.5);
+                       if (R <= curr)
+                           break;
+                       curr = R*1.001;
+                       iter--;
+                   }
+           }
+           finally {
+               tcp._setPosition(tp0); // return the original value
+           }
+           return curr;
        }
 
        private static void insertElbowUpdate(UpdateStruct2 u, PipeControlPoint dcp, PipeControlPoint next, boolean dcpStart, Vector3d position, Vector3d directedDirection) throws Exception{
@@ -1093,27 +1404,19 @@ public class PipingRules {
 //             closest.add(directedDirection);
                
                PipeControlPoint tcp = null;
-               Vector3d closest;
+               Vector3d closest = new Vector3d(directedDirection);
+               closest.scaleAdd(dcp.getPipeRun().getTurnRadius(), position);
                if (dcpStart) {
-                       closest = MathTools.closestPointOnStraight(next.getWorldPosition(), position, directedDirection);
                        tcp = insertElbow(dcp, next, closest);
                } else {
-                       closest = MathTools.closestPointOnStraight(dcp.getWorldPosition(), position, directedDirection);
                        tcp = insertElbow(next, dcp, closest);
                }
-               // TODO properly calculate required distance between start and inserted elbow.
-               double d = MathTools.distance(position, closest);
-               double s = spaceForTurn(tcp);
-               if (d < s)  {
-                       d = s - d;
-                       Vector3d p = new Vector3d(directedDirection);
-                       p.scale(d);
-                       p.add(closest);
-                       tcp.setPosition(p);
-                       closest = p;
-               }
-               
                
+               double s = spaceForTurn(tcp,dcp);
+               Vector3d p = new Vector3d(directedDirection);
+               p.scaleAdd(s, position);
+               tcp.setPosition(p);
+               closest = p;
 
                if (DEBUG)
                        System.out.println("PipingRules.updateDirectedPipeRun() inserted " + tcp);
@@ -1332,26 +1635,26 @@ public class PipingRules {
                                updateTurnControlPointTurn(u.start, null, null);
 //                             updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE);
                        } else if (u.start.isEnd()) {
-                               updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
+                               updateEndComponentControlPoint(u.start, u.dir);
                        } else if (u.start.isInline()) {
-                               updateControlPointOrientation(u.start);
+                               updateControlPointOrientation(u.start, u.dir);
                        }
                        if (u.end.isTurn()) {
                                //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);
+                               updateEndComponentControlPoint(u.end, u.dir);
                        } else if (u.end.isInline()) {
-                               updateControlPointOrientation(u.end);
+                               updateControlPointOrientation(u.end, u.dir);
                        }
 
                } else {
                        if (u.start.isEnd()) {
-                               updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
+                               updateEndComponentControlPoint(u.start, u.dir);
                        }
                        if (u.end.isEnd()) {
-                               updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
+                               updateEndComponentControlPoint(u.end, u.dir);
                        }
                }
                if (updateInline)
@@ -1367,6 +1670,7 @@ public class PipingRules {
        // end,Point3d endPoint, Vector3d dir, boolean hasOffsets,int iter, boolean
        // reversed, ArrayList<ExpandIterInfo> toRemove) throws TransactionException
        // {
+       @SuppressWarnings("unused")
        private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception {
                if (DEBUG)
                        System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
@@ -1387,7 +1691,7 @@ public class PipingRules {
        private static void updateOffsetPoint(PipeControlPoint sccp, Vector3d offset) {
                Vector3d world = sccp.getWorldPosition();
                world.add(offset);
-               PipeControlPoint ocp = sccp.getSubPoint().iterator().next();
+               PipeControlPoint ocp = sccp.getDualSub();
                ocp.setWorldPosition(world);
        }
 
@@ -1423,7 +1727,7 @@ public class PipingRules {
                if (canCalc) {
                        boolean branchUpdate = false;
                        PipeControlPoint becp = null;
-                       for (PipeControlPoint pcp : icp.getSubPoint())
+                       for (PipeControlPoint pcp : icp.getChildPoints())
                                if (pcp.isNonDirected()) {
                                        branchUpdate = true;
                                        becp = pcp;
@@ -1471,7 +1775,7 @@ public class PipingRules {
                        System.out.println(" " + newInlinePoint);
 
                icp.setWorldPosition(newInlinePoint);
-               updateControlPointOrientation(icp);
+               updateControlPointOrientation(icp, dir);
        }
 
        /**
@@ -1483,49 +1787,31 @@ public class PipingRules {
         * @param nextPoint
         * @param prevPoint
         */
-       private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d start, Vector3d end) throws Exception {
+       private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d dir) throws Exception {
                if (DEBUG)
                        System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp);
-               // PipeControlPoint next = ecp.getNext();
-               // PipeControlPoint prev = ecp.getPrevious();
-               // if (next != null) {
-               // end = G3DTools.getPoint(next.getLocalPosition());
-               // start = G3DTools.getPoint(ecp.getLocalPosition());
-               // } else if (prev != null) {
-               // end = G3DTools.getPoint(ecp.getLocalPosition());
-               // start = G3DTools.getPoint(prev.getLocalPosition());
-               // } else {
-               // // TODO : warning?
-               // return;
-               // }
-               // Vector3d dir = new Vector3d (end);
-               // dir.sub(start);
-               // dir.normalize();
-               // G3DTools.setTuple(ecp.getDirection(), dir);
-               if (!ecp.isFixed())
-                       updateControlPointOrientation(ecp);
-
-               for (PipeControlPoint pcp : ecp.getSubPoint()) {
+               
+               if (!ecp.isFixed()) // prevent overriding nozzle orientations..
+                  updateControlPointOrientation(ecp, dir);
+
+               for (PipeControlPoint pcp : ecp.getChildPoints()) {
                        // TODO update position
                        updatePathLegEndControlPoint(pcp);
                }
        }
 
-       private static void updateControlPointOrientation(PipeControlPoint pcp) {
-               // FIXME : hack to bypass variable length components orientation
-//             if (pcp.getAtMostOneRelatedObject(ProcessResource.g3dResource.HasWorldOrientation) == null)
-//                     return;
-//             if (pcp.rotationAngle == null)
-//                     return;
+       private static void updateControlPointOrientation(PipeControlPoint pcp, Vector3d dir) {
                Double angleO = pcp.getRotationAngle();
                double angle = 0.0;
                if (angleO != null)
                        angle = angleO;
-               Boolean reversedO = pcp.getReversed();
-               boolean reversed = false;
-               if (reversedO != null)
-                       reversed = reversedO;
-               Quat4d q = pcp.getControlPointOrientationQuat(angle, reversed);
+               boolean reversed = pcp._getReversed();
+               Quat4d q = null;
+               if (dir != null) {
+                   q = pcp.getControlPointOrientationQuat(dir, angle, reversed);
+               } else {
+                   q = pcp.getControlPointOrientationQuat(angle, reversed);
+               }
                pcp.setWorldOrientation(q);
        }
 
@@ -1539,7 +1825,7 @@ public class PipingRules {
                        System.out.println("PipingRules.updateBranchControlPointBranches() " + bcp);
                if (bcp.isDualInline())
                        return;
-               Collection<PipeControlPoint> branches = bcp.getSubPoint();
+               Collection<PipeControlPoint> branches = bcp.getChildPoints();
                if (branches.size() == 0) {
                        if (DEBUG)
                                System.out.println("No Branches found");
@@ -1564,7 +1850,7 @@ public class PipingRules {
                        }
                }
                
-               if (!tcp.isFixed()) {
+               if (!tcp.asFixedAngle()) {
                        
                        
                        if (next == null || prev == null) {
@@ -1572,15 +1858,15 @@ public class PipingRules {
                                        return tcp.getTurnAngle();
                                return Math.PI; // FIXME : argh
                        }
-                       double turnAngle = prev.angle(next);
-       
-                       double angle = Math.PI - turnAngle;
+                       
+                       final boolean isDegenerate = prev.lengthSquared() < MathTools.NEAR_ZERO || next.lengthSquared() < MathTools.NEAR_ZERO;
+                       double turnAngle = isDegenerate ? 0.0 : prev.angle(next);
        
                        Vector3d turnAxis = new Vector3d();
                        turnAxis.cross(prev, next);
                        if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) {
                                double elbowRadius = ((TurnComponent)tcp.getPipelineComponent()).getTurnRadius();
-                               double R = elbowRadius / Math.tan(angle * 0.5);
+                               double R = elbowRadius * Math.tan(turnAngle * 0.5);
                                
                                turnAxis.normalize();
                                tcp.setTurnAngle(turnAngle);
@@ -1594,7 +1880,7 @@ public class PipingRules {
                                tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
                        }
                        
-                       updateControlPointOrientation(tcp);
+                       updateControlPointOrientation(tcp,prev);
                        
                        if (DEBUG)
                                System.out.println("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis);
@@ -1622,7 +1908,7 @@ public class PipingRules {
                                return Math.PI; // FIXME : argh
                        }
                        
-                       Quat4d q = PipeControlPoint.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0);
+                       Quat4d q = tcp.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0);
                        Vector3d v = new Vector3d();
                        MathTools.rotate(q, MathTools.Y_AXIS,v);
                        tcp.setTurnAxis(v);
@@ -1665,8 +1951,11 @@ public class PipingRules {
                while (true) {
                        List<PipeControlPoint> points = getControlPoints(pipeRun);
                        PipeControlPoint pcp = points.get(0);
-                       if (pcp.isSizeChange() && pcp.getSubPoint().size() > 0) {
-                               pipeRun = pcp.getPipeRun();
+                       if (pcp.isSizeChange() && pcp.getChildPoints().size() > 0) {
+                               PipeRun pr = pcp.getPipeRun();
+                               if (pr != pipeRun)
+                                   pipeRun = pr;
+                               else break;
                        } else {
                                break;
                        }
@@ -1678,8 +1967,12 @@ public class PipingRules {
                        List<PipeControlPoint> points = getControlPoints(pipeRun);
                        pcps.add(points);
                        PipeControlPoint pcp = points.get(points.size()-1);
-                       if (pcp.getSubPoint().size() > 0) {
-                               pipeRun = pcp.getSubPoint().get(0).getPipeRun();
+                       if (pcp.getChildPoints().size() > 0) {
+                               PipeRun pipeRun2 = pcp.getChildPoints().get(0).getPipeRun();
+                               if (pipeRun == pipeRun2)
+                                   break;
+                               else
+                                   pipeRun = pipeRun2;
                        } else {
                                break;
                        }
@@ -1696,7 +1989,6 @@ public class PipingRules {
                                List<PipeControlPoint> list2 = pcps.get(i+1);
                                PipeControlPoint prev = list.get(list.size()-1);
                                PipeControlPoint next = list2.get(0);
-                               System.out.println();
                                if (prev == next) {
                                        // Reverse the component on the boundary.
                                        InlineComponent ic = (InlineComponent)prev.getPipelineComponent();
@@ -1727,11 +2019,11 @@ public class PipingRules {
                        PipeControlPoint current = list.get(i);
                        PipeControlPoint currentSub = null;
                        if (current.isDualInline())
-                               currentSub = current.getSubPoint().get(0);
+                               currentSub = current.getDualSub();
                        if (first) {
                                PipeControlPoint next = list.get(i+1);
                                if (next.isDualInline())
-                                       next = next.getSubPoint().get(0);
+                                       next = next.getDualSub();
                                if (current.getNext() == next)
                                        current.setNext(null);
                                current.setPrevious(next);
@@ -1756,7 +2048,7 @@ public class PipingRules {
                                PipeControlPoint prev = list.get(i-1);
                                PipeControlPoint next = list.get(i+1);
                                if (next.isDualInline())
-                                       next = next.getSubPoint().get(0);
+                                       next = next.getDualSub();
                                
                                
                                current.setPrevious(next);
@@ -1768,7 +2060,8 @@ public class PipingRules {
                                }
                                
                        }
-                       if (current.isTurn() && current.isFixed()) {
+                       //if (current.isTurn() && current.isFixed()) {
+                       if (current.asFixedAngle()) {
                                current.setReversed(!current._getReversed());
                        }
                        if (current.isInline() && current.isReverse()) {
@@ -1777,30 +2070,7 @@ public class PipingRules {
                }
        }
        
-       public static void merge(PipeRun run1, PipeRun r2) {
-               Map<PipeControlPoint, Vector3d> positions = new HashMap<PipeControlPoint, Vector3d>();
-               Map<PipeControlPoint, Quat4d> orientations = new HashMap<PipeControlPoint, Quat4d>();
-               for (PipeControlPoint pcp : r2.getControlPoints()) {
-                       positions.put(pcp, pcp.getWorldPosition());
-                       orientations.put(pcp, pcp.getWorldOrientation());
-               }
-               for (PipeControlPoint pcp : r2.getControlPoints()) {
-                       r2.deattachChild(pcp);
-                       run1.addChild(pcp);
-                       PipelineComponent component = pcp.getPipelineComponent();
-                       if (component != null) {
-                               if (!(component instanceof Nozzle)) {
-                                       component.deattach();
-                                       run1.addChild(component);
-                               } else {
-                                       Nozzle n = (Nozzle)component;
-                                       n.setPipeRun(run1);
-                               }
-                       }
-               }
-               r2.remove();
-               
-       }
+       
        
        public static void validate(PipeRun pipeRun) {
                if (pipeRun == null)
@@ -1832,7 +2102,7 @@ public class PipingRules {
                        if (pcp.getParentPoint() == null) {
                                PipeControlPoint sub = null;
                                if (pcp.isDualInline())
-                                       sub = pcp.getSubPoint().get(0);
+                                       sub = pcp.getDualSub();
                                PipeControlPoint next = pcp.getNext();
                                PipeControlPoint prev = pcp.getPrevious();
                                if (next != null) {
@@ -1856,8 +2126,8 @@ public class PipingRules {
        }
        
        public static void splitVariableLengthComponent(PipelineComponent newComponent, InlineComponent splittingComponent, boolean assignPos) throws Exception{
-               assert(!splittingComponent.getControlPoint().isFixed());
-               assert(!(newComponent instanceof  InlineComponent && !newComponent.getControlPoint().isFixed()));
+               assert(!splittingComponent.getControlPoint().isFixedLength());
+               assert(!(newComponent instanceof  InlineComponent && !newComponent.getControlPoint().isFixedLength()));
                PipeControlPoint newCP = newComponent.getControlPoint();
                PipeControlPoint splittingCP = splittingComponent.getControlPoint();
                PipeControlPoint nextCP = splittingCP.getNext();
@@ -1885,7 +2155,6 @@ public class PipingRules {
                        // this should not be possible
                        throw new RuntimeException("VariableLengthComponent " + splittingComponent + " is not connected to anything.");
                }
-               double reservedLength = splittingComponent.getControlPoint().getLength();
                double newLength = newComponent.getControlPoint().getLength();
                
                
@@ -1921,21 +2190,20 @@ public class PipingRules {
                vn.interpolate(next, 0.5);
                
                
-               PipeControlPoint newVariableLengthCP = null;//insertStraight(pcp1, pcp2, pos, length);
                if (nextCP == null) {
                        newCP.insert(splittingCP, Direction.NEXT);
-                       newVariableLengthCP = insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln);
+                       insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln);
                        splittingCP.setWorldPosition(new Vector3d(vp));
 //                     ControlPointTools.setWorldPosition(splittingCP, vp);
 //                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
                } else if (prevCP == null) {
                        newCP.insert(splittingCP, Direction.PREVIOUS);
-                       newVariableLengthCP = insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp);
+                       insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp);
                        splittingCP.setWorldPosition(new Vector3d(vn));
 //                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, ln);
                } else {
                        newCP.insert(splittingCP, nextCP);
-                       newVariableLengthCP = insertStraight(newCP, nextCP, new Vector3d(vn), ln);
+                       insertStraight(newCP, nextCP, new Vector3d(vn), ln);
                        splittingCP.setWorldPosition(new Vector3d(vp));
 //                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
                }
@@ -1945,7 +2213,7 @@ public class PipingRules {
        
        public static void addSizeChange(boolean reversed, PipeRun pipeRun, PipeRun other, InlineComponent reducer, PipeControlPoint previous, PipeControlPoint next) {
                PipeControlPoint pcp = reducer.getControlPoint();
-               PipeControlPoint ocp = pcp.getSubPoint().get(0);
+               PipeControlPoint ocp = pcp.getDualSub();
                if (!reversed) {
                        String name = pipeRun.getUniqueName("Reducer");
                        reducer.setName(name);