]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
Use unique names at the model scope instead of pipe run
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipingRules.java
index 57ee4ba86a382e9ac49ed7253ccdaf03f0a248e4..3d38107990b4c25e53a4389fb42803ba529039bd 100644 (file)
@@ -18,9 +18,12 @@ 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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class PipingRules {
-       private static final boolean DEBUG = false;
+       private static final Logger LOGGER = LoggerFactory.getLogger(PipingRules.class);
+       
        private static final boolean DUMMY = false;
 
        private static double MIN_TURN_ANGLE = 0.001;    // Threshold for removing turn components.
@@ -57,7 +60,7 @@ public class PipingRules {
        public static void requestUpdate(PipeControlPoint pcp) {
            if (!PipingRules.enabled)
                return;
-               if (DEBUG) System.out.println("PipingRules request " + pcp);
+               if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules request " + pcp);
                synchronized (updateMutex) {
                        if (!requestUpdates.contains(pcp))
                                requestUpdates.add(pcp);
@@ -68,11 +71,12 @@ public class PipingRules {
            if (!PipingRules.enabled)
                return false;
            
-               if (requestUpdates.size() == 0)
-                       return false;
-               
-               List<PipeControlPoint> temp = new ArrayList<PipeControlPoint>(requestUpdates.size());
-               synchronized(updateMutex) {
+           List<PipeControlPoint> temp;
+           synchronized(updateMutex) {
+                       if (requestUpdates.size() == 0)
+                               return false;
+                       
+                       temp = new ArrayList<PipeControlPoint>(requestUpdates.size());
                        temp.addAll(requestUpdates);
                        requestUpdates.clear();
                }
@@ -112,7 +116,7 @@ public class PipingRules {
                if (pcp.getPipeRun() == null)
                        return false;
                try {
-                       if (DEBUG) System.out.println("PipingRules " + pcp);
+                       if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules " + pcp);
                        updating = true;
                        allowInsertRemove = allowIR;
                        triedIR = false;
@@ -132,14 +136,17 @@ public class PipingRules {
                        return true;
                } finally {
                        updating = false;
-//                     System.out.println("PipingRules done " + pcp);
+//                     LOGGER.trace("PipingRules done " + pcp);
                }
        }
        
        public static void setEnabled(boolean enabled) {
                PipingRules.enabled = enabled;
-               if(!enabled)
-                       currentUpdates.clear();
+               if(!enabled) {
+                       synchronized (ruleMutex) {
+                               currentUpdates.clear();
+                       }
+               }
        }
        
        public static boolean isEnabled() {
@@ -197,8 +204,8 @@ public class PipingRules {
        }
 
        private static void updatePathLegEndControlPoint(PipeControlPoint pcp) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updatePathLegEndControlPoint() " + pcp);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updatePathLegEndControlPoint() " + pcp);
                if (pcp.getNext() != null) {
                        updatePathLegNext(pcp, pcp, PathLegUpdateType.NEXT_S);
                }
@@ -209,15 +216,26 @@ public class PipingRules {
        }
 
        private static void updateInlineControlPoint(PipeControlPoint pcp) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateInlineControlPoint() " + pcp);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("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{
-               if (DEBUG)
-                       System.out.println("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos);
                if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
                        
                } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) {
@@ -249,8 +267,8 @@ public class PipingRules {
        }
        
        private static PipeControlPoint insertStraight(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos, double length) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos);
                if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
                        
                } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) {
@@ -277,14 +295,17 @@ public class PipingRules {
                scp.insert(pcp1, pcp2);
 
                scp.setWorldPosition(pos);
+               Vector3d dir = new Vector3d();
+               dir.sub(pcp2.getWorldPosition(), pcp1.getWorldPosition());
+               scp.orientToDirection(dir);
                scp.setLength(length);
                validate(scp.getPipeRun());
                return scp;
        }
        
        private static PipeControlPoint insertStraight(PipeControlPoint pcp, Direction direction , Vector3d pos, double length) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.insertStraight() " + pcp + " " + direction + " " + pos);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.insertStraight() " + pcp + " " + direction + " " + pos);
                
                InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode());
                PipeControlPoint scp = component.getControlPoint();
@@ -305,7 +326,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 (us == null) {
-                   System.out.println("Null update struct " + start);
+                   LOGGER.trace("Null update struct " + start);
                    return; 
                }
                updatePathLeg(us, lengthChange);
@@ -314,7 +335,7 @@ public class PipingRules {
        private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
                UpdateStruct2 us = createUS(start, Direction.PREVIOUS, 0, new ArrayList<ExpandIterInfo>(), updated);
                if (us == null) {
-            System.out.println("Null update struct " + start);
+            LOGGER.trace("Null update struct " + start);
             return; 
         }
                updatePathLeg(us, lengthChange);
@@ -363,37 +384,66 @@ public class PipingRules {
 
        }
 
-       @SuppressWarnings("unused")
-       private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d dir, Vector3d offset) {
-               boolean hasOffsets = false;
-               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!"));
-               }
+       /**
+        * Calculate offset based on a given fixed component direction.
+        * 
+        * The desired component direction is provided as an input to this method,
+        * unlike the direction vector that is calculated by calculateOffset.
+        * 
+        * The returned offset vector is always perpendicular to the given direction
+        * vector.
+        * 
+        * @param startPoint Start point of leg
+        * @param endPoint   End point of leg
+        * @param start      Starting component of leg
+        * @param list       Inline components between start and end
+        * @param end        Ending component of leg
+        * @param dir        Direction at which the offset is calculated
+        * @param offset     A vector object to receive the offset vector values
+        * @return True if offsets are present
+        */
+       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);
+       }
+
+       /**
+        * Calculate offset and direction vectors for a path leg so that the given chain
+        * of components starts and ends at the given coordinates
+        * 
+        * The returned direction and offset vectors are perpendicular to each other.
+        * 
+        * @param startPoint Start point of the leg
+        * @param endPoint   End point of the leg
+        * @param start      Starting component of the leg
+        * @param list       Inline components between start and end
+        * @param end        Ending component of the leg
+        * @param dir        A vector object to receive the component direction vector
+        * @param offset     A vector object to receive the offset vector
+        * @return True if offsets are present
+        */
+       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);
                        Point3d ep = new Point3d(endPoint);
-                       dir.set(ep);
-                       dir.sub(sp);
+                       if (!directed) {
+                               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) {
+                       while (true) {
                                iter--;
                                offset.set(0.0, 0.0, 0.0);
                                
@@ -401,11 +451,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);
@@ -413,14 +468,37 @@ public class PipingRules {
                                if (l > MathTools.NEAR_ZERO)
                                        dir.scale(1.0/Math.sqrt(l));
                        }
-                       hasOffsets = true;
+                       
+                       if (LOGGER.isTraceEnabled())
+                               LOGGER.trace("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;
@@ -450,6 +528,21 @@ public class PipingRules {
                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;
@@ -503,16 +596,16 @@ public class PipingRules {
        }
 
        private static void updateFreePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
                checkExpandPathLeg(u, lengthChange);
                if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle()|| u.end.asFixedAngle())
                        processPathLeg(u, true, false);
        }
 
        private static void updateInlineControlPoints(UpdateStruct2 u, boolean checkSizes) throws Exception{
-               if (DEBUG)
-                       System.out.println("PipingRules.updateInlineControlPoints() " + u);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateInlineControlPoints() " + u);
 
                Vector3d start = new Vector3d(u.startPoint);
                Vector3d end = new Vector3d(u.endPoint);
@@ -637,7 +730,7 @@ public class PipingRules {
                 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());
+//            LOGGER.trace(u.start.getPipelineComponent().toString() + " " + pathLegLength + " " + availableLength + " " + u.end.getPipelineComponent().toString() + " " + u.start.getInlineLength() + " " + u.end.getInlineLength());
                }
        }
        
@@ -706,8 +799,8 @@ public class PipingRules {
                        continue;
                    double curr = gapObj.d;
                    int d = 1;
-                   while (curr < -MIN_INLINE_LENGTH) {
-                       GapObj next = i+d >= 0 ? gaps.get(i+d) : null;
+                   while (d < gaps.size() && curr < -MIN_INLINE_LENGTH) {
+                       GapObj next = i+d < gaps.size() ? 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);
@@ -725,12 +818,12 @@ public class PipingRules {
                                pcp.first.setWorldPosition(p);
                            }
                        }
-                       if (curr < -MIN_INLINE_LENGTH && prev != null && prev.gap == Gap.SPACE) {
+                       else if (prev != null && prev.gap == Gap.SPACE) {
                            double move = Math.min(-curr, prev.d);
                         curr+= move;
-                        next.d -= move;
-                        if (next.d < MIN_INLINE_LENGTH)
-                            next.gap = Gap.ATTACHED;
+                        prev.d -= move;
+                        if (prev.d < MIN_INLINE_LENGTH)
+                            prev.gap = Gap.ATTACHED;
                         Vector3d mv = new Vector3d(dir);
                         mv.normalize();
                         mv.scale(-move);
@@ -741,6 +834,9 @@ public class PipingRules {
                             pcp.first.setWorldPosition(p);
                         }
                        }
+                       else {
+                           d++;
+                       }
                    }
                }
            } else {
@@ -842,8 +938,8 @@ public class PipingRules {
                                triedIR = true;
                                return false;
                            }
-                               if (DEBUG)
-                                       System.out.println("PipingRules.updateVariableLength removing " + icp);
+                               if (LOGGER.isTraceEnabled())
+                                       LOGGER.trace("PipingRules.updateVariableLength removing " + icp);
                                icp._remove();
                                return true;
                        } else {
@@ -887,7 +983,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;
@@ -913,25 +1013,30 @@ public class PipingRules {
                        double l = beginPos.distance(endPos);
                        
                        if (Double.isNaN(l))
-                               System.out.println("Length for " + icp + " is NaN");
+                               LOGGER.debug("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);
+                       if (LOGGER.isTraceEnabled())
+                               LOGGER.trace("PipingRules.updateInlineControlPoints() setting variable length to " + l);
                        icp.setLength(l);
        
                        icp.setWorldPosition(new Vector3d(beginPos));
                }
        }
 
-       private static void ppNoOffset(UpdateStruct2 u) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.ppNoOffset() " + u);
+       /**
+        * 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 (LOGGER.isTraceEnabled())
+                       LOGGER.trace("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));
@@ -940,17 +1045,17 @@ 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 {
-               if (DEBUG)
-                       System.out.println("PipingRules.ppNoDir() " + start + " " + end + " " + iter + " " + toRemove.size());
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.ppNoDir() " + start + " " + end + " " + iter + " " + toRemove.size());
                // FIXME : extra loop (dir should be calculated here)
                Vector3d dir = new Vector3d();
                Vector3d offset = new Vector3d();
                hasOffsets = calculateOffset(startPoint, endPoint, start, list, end, dir, offset);
-               ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated));
+               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 {
@@ -958,8 +1063,8 @@ public class PipingRules {
        }
        
        private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean updateEnds) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.checkExpandPathLeg() " + u + " " + lengthChange);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("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
@@ -976,8 +1081,8 @@ public class PipingRules {
        }
 
        private static void updateDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange);
                PipeControlPoint dcp;
                PipeControlPoint other;
                boolean canMoveOther = false;
@@ -1011,22 +1116,31 @@ public class PipingRules {
                                return;
                        }
                }
-               Point3d directedEndPoint = new Point3d(u.endPoint);
-               if (u.hasOffsets)
-                       directedEndPoint.sub(u.offset);
+               
+               Point3d otherPosition = new Point3d(dcpStart ? u.endPoint : u.startPoint);
+               if (u.hasOffsets) {
+                       Vector3d dir = dcp.getDirection(dcpStart ? Direction.NEXT : Direction.PREVIOUS);
+                       if (!dcpStart)
+                               dir.negate();
+                       
+                       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.sub(offset);
+                       else
+                               otherPosition.add(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);
@@ -1041,10 +1155,9 @@ public class PipingRules {
                    canMoveOther = true;
                }
                if (aligned) {
-                       if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle() || u.end.asFixedAngle())
-                               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);
@@ -1075,15 +1188,28 @@ public class PipingRules {
                                        }
 
                                        if (canMoveOther) {
-                                               if (DEBUG)
-                                                       System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest);
+                                               if (LOGGER.isTraceEnabled())
+                                                       LOGGER.trace("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);
                                                }
@@ -1091,7 +1217,6 @@ public class PipingRules {
                                                // TODO : calculate needed space from next run end.
                                                if (allowInsertRemove)
                                                        insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
-                                                       
                                                else
                                                        triedIR = true;
                                        }
@@ -1116,8 +1241,8 @@ public class PipingRules {
                                                throw new UnsupportedOperationException("not implemented");
                                        }
                                        if (canMoveOther) {
-                                               if (DEBUG)
-                                                       System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect);
+                                               if (LOGGER.isTraceEnabled())
+                                                       LOGGER.trace("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect);
                                                // is required branch position is in possible range
                                                bcp.setWorldPosition(bintersect);
                                                if (dcpStart) {
@@ -1143,26 +1268,29 @@ public class PipingRules {
                                }
                        }
                }
-               
-               
        }
 
        private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange);
                
                PipeControlPoint dcp1 = u.start;
                PipeControlPoint dcp2 = u.end;
                Point3d position1 = new Point3d(u.startPoint);
                Point3d position2 = new Point3d(u.endPoint);
+               
+               Vector3d dir = u.start.getDirection(Direction.NEXT), 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));
 
@@ -1202,9 +1330,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)
@@ -1215,8 +1343,8 @@ public class PipingRules {
                                PipeControlPoint tcp1 = insertElbow(dcp, next, p1);
                                PipeControlPoint tcp2 = insertElbow(tcp1, next, p2);
 
-                               if (DEBUG)
-                                       System.out.println("PipingRules.updateDualDirectedPipeRun() created two turns " + tcp1 + " " + tcp2);
+                               if (LOGGER.isTraceEnabled())
+                                       LOGGER.trace("PipingRules.updateDualDirectedPipeRun() created two turns " + tcp1 + " " + tcp2);
 
                                if (!u.reversed) {
                                        Vector3d dd = new Vector3d(p2);
@@ -1264,34 +1392,51 @@ public class PipingRules {
            if (other == null)
                return tr; // space for 90 deg
            Vector3d dir = dcp.getDirectedControlPointDirection();
-           Vector3d dp = dcp.getWorldPosition();
-           Vector3d op = other.getWorldPosition();
-           double u[] = new double[1];
-           Vector3d closest = MathTools.closestPointOnStraight(op, dp, dir,u);
-           if (MathTools.distanceSquared(closest, op) <= MIN_INLINE_LENGTH) {
-               if (u[0] > -MIN_INLINE_LENGTH)
-                   return 0.0; // point following turn is directly in the front of the nozzle.
-               else
-                   return tr*2.0; // point following turn is directly behind the nozzle, in theory, we should return Double.Inf...
+           Vector3d dir2;
+           if (other == ne) {
+               dir2 = pathLegDirection(tcp);
+           } else {
+               dir2 = pathLegDirection(pe);
+               dir2.negate();
            }
-           double curr = tr*0.1; 
+
+           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 v1 = new Vector3d();
-           Vector3d v2 = new Vector3d();
-           while (iter > 0) {
-               Vector3d tp = new Vector3d(dp);
-               MathTools.mad(tp, dir, curr);
-               v1.sub(tp, dp); // Vector from nozzle to turn 
-               v2.sub(op,tp);  // Vector from turn to other 
-               double a = v1.angle(v2);
-               double t = Math.tan((Math.PI - a) * 0.5);
-               double R = 0.0;
-               if (t > MathTools.NEAR_ZERO)
-                   R = tr / t;
-               if (R <= curr)
-                   break;
-               curr = R*1.001;
-               iter--;
+           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;
        }
@@ -1303,29 +1448,22 @@ 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);
                }
-               double d = MathTools.distance(position, closest);
-               double s = spaceForTurn(tcp,dcp);
-               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);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateDirectedPipeRun() inserted " + tcp);
 
                if (dcpStart) {
                        // update pipe run from new turn to other end
@@ -1344,8 +1482,8 @@ public class PipingRules {
         * Checks if turns can be removed (turn angle near zero)
         */
        private static int checkTurns(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.checkTurns() " + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.checkTurns() " + u.start + " " + u.end);
                boolean startRemoved = false;
                boolean endRemoved = false;
                if (u.start.isVariableAngle()) {
@@ -1383,8 +1521,8 @@ public class PipingRules {
                                }
                        }
                }
-               if (DEBUG)
-                       System.out.println("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved);
                if (!startRemoved && !endRemoved)
                        return REMOVE_NONE;
                if (startRemoved && endRemoved)
@@ -1399,8 +1537,8 @@ public class PipingRules {
         * 
         */
        private static void expandPathLeg(UpdateStruct2 u, int type) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.expandPipeline " + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.expandPipeline " + u.start + " " + u.end);
                ArrayList<PipeControlPoint> newList = new ArrayList<PipeControlPoint>();
                switch (type) {
                case REMOVE_NONE:
@@ -1445,8 +1583,8 @@ public class PipingRules {
                                        ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
                        }
                }
-               if (DEBUG)
-                       System.out.println("PipingRules.expandPipeline expanded " + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.expandPipeline expanded " + u.start + " " + u.end);
                u.iter++;
                updatePathLeg(u, PathLegUpdateType.NONE);
        }
@@ -1456,8 +1594,8 @@ public class PipingRules {
         */
        private static void backIter(UpdateStruct2 u) throws Exception {
 
-               if (DEBUG)
-                       System.out.println("PipingRules.backIter" + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.backIter" + u.start + " " + u.end);
                if (u.iter == 0)
                        throw new RuntimeException("Error in piping rules");
                ExpandIterInfo info = u.toRemove.get(u.toRemove.size() - 1);
@@ -1504,25 +1642,25 @@ public class PipingRules {
        // toRemove) throws TransactionException {
 
        private static void processPathLeg(UpdateStruct2 u) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.processPathLeg " + u.start + " " + u.end);
                processPathLeg(u, true, true);
        }
 
        private static void processPathLeg(UpdateStruct2 u, boolean updateEnds, boolean updateInline) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.processPathLeg " + (updateEnds ? "ends " : "") + (updateInline ? "inline " : "") + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("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());
+                                       if (LOGGER.isTraceEnabled())
+                                               LOGGER.trace("PipingRules.processPathLeg removing start " + info.getStart());
                                        info.getStart()._remove();
                                }
                                if (info.getEnd() != null) {
-                                       if (DEBUG)
-                                               System.out.println("PipingRules.processPathLeg removing end " + info.getEnd());
+                                       if (LOGGER.isTraceEnabled())
+                                               LOGGER.trace("PipingRules.processPathLeg removing end " + info.getEnd());
                                        info.getEnd()._remove();
                                }
                        }
@@ -1543,7 +1681,7 @@ public class PipingRules {
                        } else if (u.start.isEnd()) {
                                updateEndComponentControlPoint(u.start, u.dir);
                        } else if (u.start.isInline()) {
-                               updateControlPointOrientation(u.start, u.dir);
+                               u.start.orientToDirection(u.dir);
                        }
                        if (u.end.isTurn()) {
                                //updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext());
@@ -1552,7 +1690,7 @@ public class PipingRules {
                        } else if (u.end.isEnd()) {
                                updateEndComponentControlPoint(u.end, u.dir);
                        } else if (u.end.isInline()) {
-                               updateControlPointOrientation(u.end, u.dir);
+                               u.end.orientToDirection(u.dir);
                        }
 
                } else {
@@ -1578,8 +1716,8 @@ public class PipingRules {
        // {
        @SuppressWarnings("unused")
        private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.processPathLeg " + u.start + " " + u.end);
                Vector3d offset = new Vector3d();
                if (u.hasOffsets) {
                        u.dir.normalize();
@@ -1611,8 +1749,8 @@ public class PipingRules {
         * @param prevPoint
         */
        private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d prev, Vector3d next,  Vector3d dir) {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateInlineControlPoint() " + icp);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateInlineControlPoint() " + icp);
 
                Vector3d inlinePoint = icp.getWorldPosition();
                Vector3d prevPoint = new Vector3d(prev);
@@ -1627,7 +1765,7 @@ public class PipingRules {
                        }
                }
                boolean canCalc = MathTools.distance(prevPoint, nextPoint) > ALLOWED_OFFSET;
-               if (DEBUG)
+               if (LOGGER.isTraceEnabled())
                        System.out.print("InlineControlPoint update " + icp + " " + inlinePoint + " " + prevPoint + " " + nextPoint);
                Vector3d newInlinePoint = null;
                if (canCalc) {
@@ -1663,8 +1801,8 @@ public class PipingRules {
                                        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]);
+                                       if (LOGGER.isTraceEnabled())
+                                               LOGGER.trace(Double.toString(mu[0]));
                                        // FIXME : reserve space
                                        if (mu[0] < 0.0) {
                                                newInlinePoint = new Vector3d(prevPoint);
@@ -1677,11 +1815,11 @@ public class PipingRules {
                        // prevPoint == nextPoint
                        newInlinePoint = new Vector3d(prevPoint);
                }
-               if (DEBUG)
-                       System.out.println(" " + newInlinePoint);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace(" " + newInlinePoint);
 
                icp.setWorldPosition(newInlinePoint);
-               updateControlPointOrientation(icp, dir);
+               icp.orientToDirection(dir);
        }
 
        /**
@@ -1694,11 +1832,11 @@ public class PipingRules {
         * @param prevPoint
         */
        private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d dir) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateEndComponentControlPoint() " + ecp);
                
                if (!ecp.isFixed()) // prevent overriding nozzle orientations..
-                  updateControlPointOrientation(ecp, dir);
+                       ecp.orientToDirection(dir);
 
                for (PipeControlPoint pcp : ecp.getChildPoints()) {
                        // TODO update position
@@ -1706,35 +1844,20 @@ public class PipingRules {
                }
        }
 
-       private static void updateControlPointOrientation(PipeControlPoint pcp, Vector3d dir) {
-               Double angleO = pcp.getRotationAngle();
-               double angle = 0.0;
-               if (angleO != null)
-                       angle = angleO;
-               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);
-       }
-
        /**
         * Updates all branches when branch's position has been changed
         * 
         * @param bcp
         */
        private static void updateBranchControlPointBranches(PipeControlPoint bcp) throws Exception {
-               if (DEBUG)
-                       System.out.println("PipingRules.updateBranchControlPointBranches() " + bcp);
+               if (LOGGER.isTraceEnabled())
+                       LOGGER.trace("PipingRules.updateBranchControlPointBranches() " + bcp);
                if (bcp.isDualInline())
                        return;
                Collection<PipeControlPoint> branches = bcp.getChildPoints();
                if (branches.size() == 0) {
-                       if (DEBUG)
-                               System.out.println("No Branches found");
+                       if (LOGGER.isTraceEnabled())
+                               LOGGER.trace("No Branches found");
                        return;
                }
                
@@ -1764,15 +1887,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);
@@ -1786,10 +1909,10 @@ public class PipingRules {
                                tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
                        }
                        
-                       updateControlPointOrientation(tcp,prev);
+                       tcp.orientToDirection(prev);
                        
-                       if (DEBUG)
-                               System.out.println("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis);
+                       if (LOGGER.isTraceEnabled())
+                               LOGGER.trace("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis);
                        return turnAngle;
                } else {
                        
@@ -1814,7 +1937,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);
@@ -1981,50 +2104,52 @@ public class PipingRules {
        public static void validate(PipeRun pipeRun) {
                if (pipeRun == null)
                        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 " + pipeRun.getName() + " contains unconnected control points, found " + runPcps.size() + " connected, " + pcps.size() + " total.");
+               synchronized (ruleMutex) {
+                       Collection<PipeControlPoint> pcps = pipeRun.getControlPoints();
+                       int count = 0;
+                       //LOGGER.trace("Validate " + pipeRun.getName());
                        for (PipeControlPoint pcp : pcps) {
-                           if (!runPcps.contains(pcp)) {
-                               System.out.println("Unconnected " + pcp + " " + pcp.getPipelineComponent());
-                           }
+                               if (pcp.getParentPoint() == null || pcp.getParentPoint().getPipeRun() != pipeRun)
+                                       count++;
                        }
-               }
-               for (PipeControlPoint pcp : pcps) {
-                   if (pcp.getPipeRun() == null) {
-                       System.out.println("PipeRun ref missing " + pcp + " " + pcp.getPipelineComponent());
-                   }
-                       if (!pcp.isDirected() && pcp.getNext() == null && pcp.getPrevious() == null)
-                               System.out.println("Orphan undirected " + pcp + " " + pcp.getPipelineComponent());
-               }
-               for (PipeControlPoint pcp : pcps) {
-                       if (pcp.getParentPoint() == null) {
-                               PipeControlPoint sub = null;
-                               if (pcp.isDualInline())
-                                       sub = pcp.getDualSub();
-                               PipeControlPoint next = pcp.getNext();
-                               PipeControlPoint prev = pcp.getPrevious();
-                               if (next != null) {
-                                       if (!(next.getPrevious() == pcp || next.getPrevious() == sub)) {
-                                               System.out.println("Inconsistency between " + pcp + " -> " +next );
-                                       }
+                       List<PipeControlPoint> runPcps = getControlPoints(pipeRun);
+                       if (runPcps.size() != count) {
+                               LOGGER.debug("Run " + pipeRun.getName() + " contains unconnected control points, found " + runPcps.size() + " connected, " + pcps.size() + " total.");
+                               for (PipeControlPoint pcp : pcps) {
+                                   if (!runPcps.contains(pcp)) {
+                                       LOGGER.debug("Unconnected " + pcp + " " + pcp.getPipelineComponent());
+                                   }
                                }
-                               if (prev != null) {
-                                       PipeControlPoint prevParent = null;
-                                       if (prev.isDualSub()) {
-                                               prevParent = prev.getParentPoint();
-                                       } else if (prev.isDualInline()) {
-                                               System.out.println("Inconsistency between " + pcp + " <-- " +prev );
+                       }
+                       for (PipeControlPoint pcp : pcps) {
+                           if (pcp.getPipeRun() == null) {
+                               LOGGER.debug("PipeRun ref missing " + pcp + " " + pcp.getPipelineComponent());
+                           }
+                               if (!pcp.isDirected() && pcp.getNext() == null && pcp.getPrevious() == null)
+                                       LOGGER.debug("Orphan undirected " + pcp + " " + pcp.getPipelineComponent());
+                       }
+                       for (PipeControlPoint pcp : pcps) {
+                               if (pcp.getParentPoint() == null) {
+                                       PipeControlPoint sub = null;
+                                       if (pcp.isDualInline())
+                                               sub = pcp.getDualSub();
+                                       PipeControlPoint next = pcp.getNext();
+                                       PipeControlPoint prev = pcp.getPrevious();
+                                       if (next != null) {
+                                               if (!(next.getPrevious() == pcp || next.getPrevious() == sub)) {
+                                                       LOGGER.debug("Inconsistency between " + pcp + " -> " +next );
+                                               }
                                        }
-                                       if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) {
-                                               System.out.println("Inconsistency between " + pcp + " <-- " +prev );
+                                       if (prev != null) {
+                                               PipeControlPoint prevParent = null;
+                                               if (prev.isDualSub()) {
+                                                       prevParent = prev.getParentPoint();
+                                               } else if (prev.isDualInline()) {
+                                                       LOGGER.debug("Inconsistency between " + pcp + " <-- " +prev );
+                                               }
+                                               if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) {
+                                                       LOGGER.debug("Inconsistency between " + pcp + " <-- " +prev );
+                                               }
                                        }
                                }
                        }