]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
Simplified free ended variable length component update
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipingRules.java
index d7c53d4a085675b603473ac67d2f6579dfafe467..b096266f7af72901d2718812b9dd77d3570d3689 100644 (file)
@@ -25,18 +25,14 @@ public class PipingRules {
        private static final boolean DEBUG = false;
        private static final boolean DUMMY = false;
 
-       private static final double MIN_TURN_ANGLE = 0.01;
+       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 final int REMOVE_NONE = 0;
        private static final int REMOVE_START = 1;
        private static final int REMOVE_END = 2;
        private static final int REMOVE_BOTH = 3;
        
-//     private P3DRootNode root;
-       
-//     public PipingRules(P3DRootNode root) {
-//             this.root = root;
-//     }
 
        private enum PathLegUpdateType {
                NONE, PREV, NEXT, PREV_S, NEXT_S
@@ -91,7 +87,7 @@ public class PipingRules {
                        triedIR = false;
                        validate(pcp.getPipeRun());
                        if (pcp.isPathLegEnd()) {
-                               updatePathLegEndControlPoint(pcp); // FXIME: Rules won't work properly, if they are not run twice.
+                               updatePathLegEndControlPoint(pcp); // FIXME: Rules won't work properly, if they are not run twice.
                                updatePathLegEndControlPoint(pcp);
                        } else {
                                updateInlineControlPoint(pcp);
@@ -281,16 +277,39 @@ public class PipingRules {
                ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
                PipeControlPoint end = start.findNextEnd(list);
                // this is for inline cp that is also path leg end
-               if (start.equals(updated))
-                       lengthChange = PathLegUpdateType.NEXT;
-               else if (end.equals(updated))
-                       lengthChange = PathLegUpdateType.PREV;
+               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);
        }
+       
+       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);
+
+       }
 
        private static class UpdateStruct2 {
                public PipeControlPoint start;
@@ -337,18 +356,64 @@ public class PipingRules {
 
        private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, ArrayList<PipeControlPoint> list, Vector3d dir, Vector3d offset) {
                boolean hasOffsets = false;
-               dir.set(startPoint);
-               dir.sub(endPoint);
-               if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                       dir.normalize();
-               offset.set(0.0, 0.0, 0.0);
+               List<PipeControlPoint> offsets = new ArrayList<PipeControlPoint>(list.size());
                for (PipeControlPoint icp : list) {
                        if (icp.isOffset()) {
-                               hasOffsets = true;
-                               offset.add(icp.getSizeChangeOffsetVector(dir));
+                               offsets.add(icp);
                        } else if (icp.isDualSub())
                                ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
                }
+               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);
+                       return false;
+               } else {
+                       Vector3d sp = new Vector3d(startPoint);
+                       Point3d ep = new Point3d(endPoint);
+                       dir.set(ep);
+                       dir.sub(sp);
+                       double l = dir.lengthSquared(); 
+                       if (l > MathTools.NEAR_ZERO)
+                               dir.scale(1.0/Math.sqrt(l));
+                       int iter = 100;
+                       while (iter >= 0) {
+                               iter--;
+                               offset.set(0.0, 0.0, 0.0);
+                               
+                               for (PipeControlPoint icp : offsets) {
+                                       Vector3d v = icp.getSizeChangeOffsetVector(dir);
+                                       offset.add(v);
+                               }
+                               Point3d nep = new Point3d(endPoint);
+                               nep.sub(offset);
+                               if (nep.distance(ep) < 0.0000000001) {
+                                       break;
+                               } 
+                               ep = nep;
+                               dir.set(ep);
+                               dir.sub(sp);
+                               l = dir.lengthSquared(); 
+                               if (l > MathTools.NEAR_ZERO)
+                                       dir.scale(1.0/Math.sqrt(l));
+                       }
+                       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;
        }
 
@@ -377,12 +442,28 @@ public class PipingRules {
                updatePathLeg(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, reversed, toRemove, updated), lengthChange);
 
        }
-
+       
+       private static boolean asDirected(PipeControlPoint pcp, Direction direction) {
+           if (pcp.isDirected())
+               return true;
+           if (pcp.isTurn() && pcp.isFixed()) {
+              if (!pcp._getReversed())
+                  return direction == Direction.NEXT;
+              else
+                  return direction == Direction.PREVIOUS;
+           }
+           return false;
+       }
+       
+       private static Vector3d direction(PipeControlPoint pcp, Direction direction) {
+           return pcp.getDirection(direction);
+       }
        private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
                int directed = 0;
-               if (u.start.isDirected())
+               if (asDirected(u.start, Direction.NEXT))
                        directed++;
-               if (u.end.isDirected())
+               if (asDirected(u.end, Direction.PREVIOUS))
                        directed++;
                switch (directed) {
                case 0:
@@ -402,7 +483,7 @@ public class PipingRules {
                if (DEBUG)
                        System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
                checkExpandPathLeg(u, lengthChange);
-               if (u.start.isInline() || u.end.isInline())
+               if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
                        processPathLeg(u, true, false);
        }
 
@@ -410,19 +491,30 @@ public class PipingRules {
                if (DEBUG)
                        System.out.println("PipingRules.updateInlineControlPoints() " + u);
 
+               Vector3d start = new Vector3d(u.startPoint);
+               Vector3d end = new Vector3d(u.endPoint);
+               
+               if (checkSizes) {
+                       // create offsets for leg ends.
+                       MathTools.mad(start, u.dir, u.start.getInlineLength());
+                       MathTools.mad(end, u.dir, -u.end.getInlineLength());
+               }
+               
+               boolean recalcline = false;
                if (!u.hasOffsets) {
-                       // FIXME : cache positions
-                       if (!checkSizes) {
-                               Vector3d start = new Vector3d(u.startPoint);
-                               Vector3d end = new Vector3d(u.endPoint);
-                               // create offsets.
-                               MathTools.mad(start, u.dir, 0.1);
-                               MathTools.mad(end, u.dir, -0.1);
-                               for (PipeControlPoint icp : u.list) {
-                                       updateInlineControlPoint(icp, start, end, u.dir);
+                       
+                       
+                       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);
                                }
-                               return;
                        }
+                       if (!checkSizes)
+                               return;                 
 
                        ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
                        pathLegPoints.add(u.start);
@@ -438,51 +530,13 @@ public class PipingRules {
                        for (int i = 1; i < pathLegPoints.size(); i++) {
                                PipeControlPoint icp = pathLegPoints.get(i);
 
-                               PipeControlPoint prev;
-                               Vector3d prevPos;
-                               prev = pathLegPoints.get(i - 1);
-                               prevPos = prev.getWorldPosition();
-                               Vector3d currentPos = icp.getWorldPosition();
+                               PipeControlPoint prev = pathLegPoints.get(i - 1);
+                               
 
                                if (icp.isVariableLength()) {
                                        if (i != pathLegPoints.size() - 1) {
-                                               PipeControlPoint next;
-                                               Vector3d nextPos;
-                                               next = pathLegPoints.get(i + 1);
-                                               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 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.
-                                                       // 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.add(prevPos);
-                                                       icp.setWorldPosition(dir);
-                                                       icp.setLength(length);
-                                               } else {
-                                                       // components leave no space to the component and it
-                                                       // must be removed
-                                                       if (icp.isDeletable())
-                                                               icp._remove();
-                                               }
+                                               PipeControlPoint next = pathLegPoints.get(i + 1);
+                                               recalcline = recalcline | updateVariableLength(icp,  prev, next);
 
                                        } else {
                                                // this is variable length component at the end of the
@@ -490,41 +544,7 @@ public class PipingRules {
                                                // 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.
-                                               double currentLength = icp.getLength();
-                                               
-                                               Vector3d dir = new Vector3d();
-                                               dir.sub(currentPos, prevPos);
-                                               
-                                               if (currentLength < MathTools.NEAR_ZERO) {
-                                                       currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
-                                               }
-                                               
-                                               if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                                                       dir.normalize();
-                                               Point3d endPos = new Point3d(dir);
-                                               endPos.scale(currentLength * 0.5);
-                                               endPos.add(currentPos); // this is the free end of the
-                                                                                               // component
-
-                                               double offset = prev.getInlineLength();
-                                               Point3d beginPos = new Point3d(dir);
-                                               beginPos.scale(offset);
-                                               beginPos.add(prevPos); // this is the connected end of
-                                                                                               // the component
-
-                                               double l = beginPos.distance(endPos);
-                                               
-                                               if (Double.isNaN(l))
-                                                       System.out.println();
-
-                                               dir.scale(l * 0.5);
-                                               beginPos.add(dir); // center position
-
-                                               if (DEBUG)
-                                                       System.out.println("PipingRules.updateInlineControlPoints() setting variable length to " + l);
-                                               icp.setLength(l);
-
-                                               icp.setWorldPosition(new Vector3d(beginPos));
+                                               updateVariableLengthEnd(icp, prev);
                                        }
 
 
@@ -534,47 +554,201 @@ public class PipingRules {
                                        // space between them.
                                        // I there is, we'll have to create new variable length
                                        // component between them.
-                                       Vector3d dir = new Vector3d(currentPos);
-                                       dir.sub(prevPos);
-                                       double l = dir.lengthSquared();
-                                       double l2prev = prev.getInlineLength();
-                                       double l2next = icp.getInlineLength();
-                                       double l2 = l2prev + l2next;
-                                       double l2s = l2 * l2;
-                                       if (l > l2s) {
-                                               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
-                                                       dir.add(prevPos);
-                                                       PipeControlPoint scp = insertStraight(prev, icp, dir, length);
-                                               } else {
-                                                       triedIR = true;
-                                               }
-                                       }
+                                       recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
                                }
                        }
-               } else {
-                       u.endPoint.sub(u.offset);
-                       // FIXME : straights
+               } 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);
+
                        for (PipeControlPoint icp : u.list) {
-                               updateInlineControlPoint(icp, u.startPoint, u.endPoint, u.dir);
+                               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; 
+                       
+                       sp = new Vector3d(u.startPoint);
+                       ep = new Vector3d(u.endPoint);
+                       ep.sub(u.offset);
+                       
+                       for (int i = 1; i < pathLegPoints.size(); i++) {
+                               PipeControlPoint icp = pathLegPoints.get(i);
+
+                               PipeControlPoint prev = pathLegPoints.get(i - 1);
+                               if (prev.isDualInline())
+                                       prev = prev.getSubPoint().get(0);
                                
+
+                               if (icp.isVariableLength()) {
+                                       if (i != pathLegPoints.size() - 1) {
+                                               PipeControlPoint next;
+                                               next = pathLegPoints.get(i + 1);
+                                               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);
+                                       }
+                               } else if (!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
-                                       u.offset = icp.getSizeChangeOffsetVector(u.dir);
-                                       updateOffsetPoint(icp, u.offset);
-                                       u.startPoint.add(u.offset);
-                                       u.endPoint.add(u.offset);
+                                       Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
+                                       sp.add(offset);
+                                       ep.add(offset);
                                }
                        }
                }
+               if (recalcline) {
+                       u.list.clear();
+                       u.start.findNextEnd(u.list);
+               }
+       }
+       
+       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 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.
+                       // 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.add(prevPos);
+                       icp.setWorldPosition(dir);
+                       icp.setLength(length);
+                       return false;
+               } else {
+                       // components leave no space to the component and it
+                       // must be removed
+                       
+                       if (icp.isDeletable()) {
+                               if (DEBUG)
+                                       System.out.println("PipingRules.updateVariableLength removing " + icp);
+                               icp._remove();
+                               return true;
+                       }
+                       return false;
+               }
+       }
+       
+       private static boolean possibleVaribleLengthInsert(PipeControlPoint icp, PipeControlPoint prev) throws Exception{
+               Vector3d currentPos = icp.getWorldPosition();
+               Vector3d prevPos = prev.getWorldPosition();
+               Vector3d dir = new Vector3d(currentPos);
+               dir.sub(prevPos);
+               double l = dir.lengthSquared();
+               double l2prev = prev.getInlineLength();
+               double l2next = icp.getInlineLength();
+               double l2 = l2prev + l2next;
+               double l2s = l2 * l2;
+               if (l > l2s) {
+                       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
+                               dir.add(prevPos);
+                               PipeControlPoint scp = insertStraight(prev, icp, dir, length);
+                               return true;
+                       } else {
+                               triedIR = true;
+                       }
+               }
+               return false;
+       }
+       
+       private static void updateVariableLengthEnd(PipeControlPoint icp,  PipeControlPoint prev) {
+           Vector3d currentPos = icp.getWorldPosition();
+        Vector3d prevPos = prev.getWorldPosition();
+        
+        Vector3d dir = new Vector3d();
+        dir.sub(currentPos, prevPos);
+        
+           boolean simple = true;
+           if (simple) {
+               double currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
+               icp.setLength(currentLength);
+           } else {
+               double currentLength = icp.getLength();
+               if (currentLength < MathTools.NEAR_ZERO) {
+                       currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
+               }
+               
+               if (dir.lengthSquared() > MathTools.NEAR_ZERO)
+                       dir.normalize();
+               Point3d endPos = new Point3d(dir);
+               endPos.scale(currentLength * 0.5);
+               endPos.add(currentPos); // this is the free end of the component
+    
+               double offset = prev.getInlineLength();
+               Point3d beginPos = new Point3d(dir);
+               beginPos.scale(offset);
+               beginPos.add(prevPos); // this is the connected end of the component
+    
+               double l = beginPos.distance(endPos);
+               
+               if (Double.isNaN(l))
+                       System.out.println("Length for " + icp + " is NaN");
+    
+               dir.scale(l * 0.5);
+               beginPos.add(dir); // center position
+    
+               if (DEBUG)
+                       System.out.println("PipingRules.updateInlineControlPoints() setting variable length to " + l);
+               icp.setLength(l);
+    
+               icp.setWorldPosition(new Vector3d(beginPos));
+           }
        }
 
        private static void ppNoOffset(UpdateStruct2 u) throws Exception {
@@ -605,24 +779,24 @@ public class PipingRules {
        }
 
        private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
-               checkExpandPathLeg(u, lengthChange, false);
+               checkExpandPathLeg(u, lengthChange, u.updated.isInline() && u.updated.isOffset());
        }
        
-       private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean forceUpdate) throws Exception {
+       private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean updateEnds) throws Exception {
                if (DEBUG)
                        System.out.println("PipingRules.checkExpandPathLeg() " + u + " " + lengthChange);
                if (lengthChange != PathLegUpdateType.NONE) {
                        // FIXME : turns cannot be checked before inline cps are updated,
                        // since their position affects calculation of turns
-                       processPathLeg(u, forceUpdate, false);
+                       processPathLeg(u, updateEnds, false);
                        int type = checkTurns(u, lengthChange);
                        if (type == REMOVE_NONE) {
-                               processPathLeg(u, forceUpdate, true);
+                               processPathLeg(u, updateEnds, true);
                        } else {
                                expandPathLeg(u, type);
                        }
                } else {
-                       processPathLeg(u, forceUpdate, true);
+                       processPathLeg(u, updateEnds, true);
                }
        }
 
@@ -635,7 +809,7 @@ public class PipingRules {
                boolean dcpStart = false;
                boolean inlineEnd = false;
                Vector3d position;
-               if (u.start.isDirected()) {
+               if (asDirected(u.start, Direction.NEXT)) {
                        dcp = u.start;
                        other = u.end;
                        position = u.startPoint;
@@ -653,7 +827,7 @@ public class PipingRules {
                        inlineEnd = u.start.isInline();
                }
 
-               Vector3d directedDirection = dcp.getDirection();
+               Vector3d directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
                Point3d directedEndPoint = new Point3d(u.endPoint);
                if (u.hasOffsets)
                        directedEndPoint.add(u.offset);
@@ -671,9 +845,11 @@ public class PipingRules {
                        t.sub(closest, u.startPoint);
                }
 
-               double distance = t.lengthSquared();
-               boolean aligned = (distance < 0.002);
+               double distance = t.length();
+               boolean aligned = (distance < ALLOWED_OFFSET);
                if (aligned) {
+                   if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
+                   processPathLeg(u, true, false);
                        checkExpandPathLeg(u, lengthChange, inlineEnd);
                        
                } else {
@@ -700,7 +876,9 @@ public class PipingRules {
                                                } else {
                                                        closest.set(u.endPoint);
                                                }
-                                               closest.add(directedDirection);
+                                               Vector3d v = new Vector3d(directedDirection);
+                                               v.scale(spaceForTurn(other));
+                                               closest.add(v);
                                        }
 
                                        if (canMoveOther) {
@@ -788,14 +966,14 @@ public class PipingRules {
                position1offset.sub(u.offset);
                Point3d position2offset = new Point3d(position2);
                position2offset.add(u.offset);
-               Vector3d dir1 = dcp1.getDirection();
-               Vector3d dir2 = dcp2.getDirection();
+               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);
                double d1 = position1.distance(new Point3d(p1));
                double d2 = position2.distance(new Point3d(p2));
 
-               boolean aligned = (d1 < 0.01 && d2 < 0.01);
+               boolean aligned = (d1 < ALLOWED_OFFSET && d2 < ALLOWED_OFFSET);
                if (aligned) {
                        processPathLeg(u);
                } else {
@@ -864,16 +1042,42 @@ 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 tcp.getPipeRun().getTurnRadius();
+       }
 
        private static void insertElbowUpdate(UpdateStruct2 u, PipeControlPoint dcp, PipeControlPoint next, boolean dcpStart, Vector3d position, Vector3d directedDirection) throws Exception{
 
-               Vector3d closest = new Vector3d(position);
-               closest.add(directedDirection);
+               
+//             Vector3d closest = new Vector3d(position);
+//             closest.add(directedDirection);
+               
                PipeControlPoint tcp = null;
-               if (dcpStart)
-                       tcp = insertElbow(dcp, next, new Vector3d(closest));
-               else
-                       tcp = insertElbow(next, dcp, new Vector3d(closest));
+               Vector3d closest;
+               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;
+               }
+               
+               
 
                if (DEBUG)
                        System.out.println("PipingRules.updateDirectedPipeRun() inserted " + tcp);
@@ -908,7 +1112,7 @@ public class PipingRules {
                                        a = updateTurnControlPointTurn(u.start, startPrev, u.end);
                                } else {
                                        Vector3d ep = new Vector3d(u.endPoint);
-                                       ep.add(u.offset);
+                                       ep.sub(u.offset);
                                        a = updateTurnControlPointTurn(u.start, u.startPoint, startPrev.getPosition(), ep);
 
                                }
@@ -933,7 +1137,7 @@ public class PipingRules {
                                        a = updateTurnControlPointTurn(u.end, u.start, endNext);
                                } else {
                                        Vector3d sp = new Vector3d(u.startPoint);
-                                       sp.sub(u.offset);
+                                       sp.add(u.offset);
                                        a = updateTurnControlPointTurn(u.end, u.endPoint, sp, endNext.getPosition());
                                }
                                if (a < MIN_TURN_ANGLE && u.end.isDeletable())
@@ -1076,19 +1280,22 @@ public class PipingRules {
 
        private static void processPathLeg(UpdateStruct2 u, boolean updateEnds, boolean updateInline) throws Exception {
                if (DEBUG)
-                       System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
+                       System.out.println("PipingRules.processPathLeg " + (updateEnds ? "ends " : "") + (updateInline ? "inline " : "") + u.start + " " + u.end);
 
                if (u.toRemove.size() > 0) {
                        for (ExpandIterInfo info : u.toRemove) {
                                if (info.getStart() != null) {
+                                       if (DEBUG)
+                                               System.out.println("PipingRules.processPathLeg removing start " + info.getStart());
                                        info.getStart()._remove();
                                }
                                if (info.getEnd() != null) {
+                                       if (DEBUG)
+                                               System.out.println("PipingRules.processPathLeg removing end " + info.getEnd());
                                        info.getEnd()._remove();
                                }
                        }
-                       // ControlPointTools.removeControlPoint may remove mo0re than one
-                       // CP;
+                       // ControlPointTools.removeControlPoint may remove more than one CP;
                        // we must populate inline CP list again.
                        u.list.clear();
                        u.start.findNextEnd( u.list);
@@ -1160,27 +1367,6 @@ public class PipingRules {
                ocp.setWorldPosition(world);
        }
 
-       private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
-               ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
-               PipeControlPoint end = start.findPreviousEnd(list);
-               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);
-
-       }
-
        /**
         * Updates InlineControlPoints position when straight pipe's end(s) have
         * been changed)
@@ -1190,54 +1376,72 @@ public class PipingRules {
         * @param nextPoint
         * @param prevPoint
         */
-       private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d nextPoint, Vector3d prevPoint, Vector3d dir) {
+       private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d prev, Vector3d next,  Vector3d dir) {
                if (DEBUG)
                        System.out.println("PipingRules.updateInlineControlPoint() " + icp);
 
                Vector3d inlinePoint = icp.getWorldPosition();
+               Vector3d prevPoint = new Vector3d(prev);
+               Vector3d nextPoint = new Vector3d(next);
+               if (!icp.isVariableLength()) {
+                       // Reserve space for fixed length components. 
+                       MathTools.mad(prevPoint, dir, icp.getInlineLength());
+                       MathTools.mad(nextPoint, dir, -icp.getInlineLength());
+                       if (MathTools.distance(prevPoint, nextPoint) < ALLOWED_OFFSET) {
+                               prevPoint = prev;
+                               nextPoint = next;
+                       }
+               }
+               boolean canCalc = MathTools.distance(prevPoint, nextPoint) > ALLOWED_OFFSET;
                if (DEBUG)
                        System.out.print("InlineControlPoint update " + icp + " " + inlinePoint + " " + prevPoint + " " + nextPoint);
                Vector3d newInlinePoint = null;
-               boolean branchUpdate = false;
-               PipeControlPoint becp = null;
-               for (PipeControlPoint pcp : icp.getSubPoint())
-                       if (pcp.isNonDirected()) {
-                               branchUpdate = true;
-                               becp = pcp;
-                               break;
-                       }
-
-               if (DUMMY || !branchUpdate) {
-                       newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), new Vector3d(nextPoint), new Vector3d(prevPoint));
-               } else {
-
-                       // FIXME : can only handle one branch
-                       PipeControlPoint p = null;
-                       if (becp.getNext() != null) {
-                               p = becp.findNextEnd();
-                       } else if (becp.getPrevious() != null) {
-                               p = becp.findPreviousEnd();
-                       }
-                       if (p == null) {
-                               newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), new Vector3d(nextPoint), new Vector3d(prevPoint));
+               if (canCalc) {
+                       boolean branchUpdate = false;
+                       PipeControlPoint becp = null;
+                       for (PipeControlPoint pcp : icp.getSubPoint())
+                               if (pcp.isNonDirected()) {
+                                       branchUpdate = true;
+                                       becp = pcp;
+                                       break;
+                               }
+       
+                       if (DUMMY || !branchUpdate) {
+                               newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint);
+                               
                        } else {
-                               Vector3d branchLegEnd = p.getWorldPosition();
-                               Vector3d dir2 = new Vector3d(inlinePoint);
-                               dir2.sub(branchLegEnd);
-                               Vector3d dir1 = new Vector3d(nextPoint);
-                               dir1.sub(prevPoint);
-                               newInlinePoint = new Vector3d();
-                               double mu[] = new double[2];
-                               MathTools.intersectStraightStraight(new Vector3d(prevPoint), dir1, new Vector3d(branchLegEnd), dir2, newInlinePoint, new Vector3d(), mu);
-                               if (DEBUG)
-                                       System.out.println(mu[0]);
-                               // FIXME : reserve space
-                               if (mu[0] < 0.0) {
-                                       newInlinePoint = new Vector3d(prevPoint);
-                               } else if (mu[0] > 1.0) {
-                                       newInlinePoint = new Vector3d(nextPoint);
+       
+                               // FIXME : can only handle one branch
+                               PipeControlPoint p = null;
+                               if (becp.getNext() != null) {
+                                       p = becp.findNextEnd();
+                               } else if (becp.getPrevious() != null) {
+                                       p = becp.findPreviousEnd();
+                               }
+                               if (p == null) {
+                                       newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint);
+                               } else if (canCalc){
+                                       Vector3d branchLegEnd = p.getWorldPosition();
+                                       Vector3d dir2 = new Vector3d(inlinePoint);
+                                       dir2.sub(branchLegEnd);
+                                       Vector3d dir1 = new Vector3d(nextPoint);
+                                       dir1.sub(prevPoint);
+                                       newInlinePoint = new Vector3d();
+                                       double mu[] = new double[2];
+                                       MathTools.intersectStraightStraight(new Vector3d(prevPoint), dir1, new Vector3d(branchLegEnd), dir2, newInlinePoint, new Vector3d(), mu);
+                                       if (DEBUG)
+                                               System.out.println(mu[0]);
+                                       // FIXME : reserve space
+                                       if (mu[0] < 0.0) {
+                                               newInlinePoint = new Vector3d(prevPoint);
+                                       } else if (mu[0] > 1.0) {
+                                               newInlinePoint = new Vector3d(nextPoint);
+                                       }
                                }
                        }
+               } else {
+                       // prevPoint == nextPoint
+                       newInlinePoint = new Vector3d(prevPoint);
                }
                if (DEBUG)
                        System.out.println(" " + newInlinePoint);
@@ -1293,8 +1497,11 @@ public class PipingRules {
                double angle = 0.0;
                if (angleO != null)
                        angle = angleO;
-
-               Quat4d q = pcp.getControlPointOrientationQuat(angle);
+               Boolean reversedO = pcp.getReversed();
+               boolean reversed = false;
+               if (reversedO != null)
+                       reversed = reversedO;
+               Quat4d q = pcp.getControlPointOrientationQuat(angle, reversed);
                pcp.setWorldOrientation(q);
        }
 
@@ -1330,12 +1537,43 @@ public class PipingRules {
        private static double updateTurnControlPointTurn(PipeControlPoint tcp, PipeControlPoint prev, PipeControlPoint next) {
                if (DEBUG)
                        System.out.println("PipingTools.updateTurnControlPointTurn()" + tcp);
-               if (next == null || prev == null)
-                       return Math.PI; // FIXME : argh
-               Vector3d middlePoint = tcp.getWorldPosition();
-               Vector3d nextPoint = next.getWorldPosition();
-               Vector3d prevPoint = prev.getWorldPosition();
-               return updateTurnControlPointTurn(tcp, middlePoint, prevPoint, nextPoint);
+               
+               if (!tcp.isFixed()) {
+                   if (next == null || prev == null)
+                   return Math.PI; // FIXME : argh
+               Vector3d middlePoint = tcp.getWorldPosition();
+               Vector3d nextPoint = next.getWorldPosition();
+               Vector3d prevPoint = prev.getWorldPosition();
+               return updateTurnControlPointTurn(tcp, middlePoint, prevPoint, nextPoint);
+               } else {
+                   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(nextPoint,middlePoint);
+                   }
+                   dir.normalize();
+            
+            Quat4d q = PipeControlPoint.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0);
+            Vector3d v = new Vector3d();
+            MathTools.rotate(q, MathTools.Y_AXIS,v);
+            tcp.setTurnAxis(v);
+            tcp.setWorldOrientation(q);
+            return tcp.getTurnAngle();
+               }
        }
 
        /**
@@ -1377,7 +1615,7 @@ public class PipingRules {
                        turnAngle = 0.0;
                        tcp.setTurnAngle(0.0);
                        tcp.setLength(0.0);
-                       tcp.setTurnAxis(MathTools.Y_AXIS);
+                       tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
                }
                updateControlPointOrientation(tcp);
                if (DEBUG)
@@ -1490,13 +1728,18 @@ public class PipingRules {
                        return;
                Collection<PipeControlPoint> pcps = pipeRun.getControlPoints();
                int count = 0;
+               //System.out.println("Validate " + pipeRun.getName());
                for (PipeControlPoint pcp : pcps) {
                        if (pcp.getParentPoint() == null || pcp.getParentPoint().getPipeRun() != pipeRun)
                                count++;
                }
                List<PipeControlPoint> runPcps = getControlPoints(pipeRun);
                if (runPcps.size() != count) {
-                       System.out.println("Run is not connected");
+                       System.out.println("Run " + pipeRun.getName() + " contains unconnected control points");
+               }
+               for (PipeControlPoint pcp : pcps) {
+                       if (!pcp.isDirected() && pcp.getNext() == null && pcp.getPrevious() == null)
+                               System.out.println("Orphan undirected " + pcp);
                }
                for (PipeControlPoint pcp : pcps) {
                        if (pcp.getParentPoint() == null) {