]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
Merge "Publish Plant3D feature"
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipingRules.java
diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
new file mode 100644 (file)
index 0000000..d7c53d4
--- /dev/null
@@ -0,0 +1,1651 @@
+package org.simantics.plant3d.scenegraph.controlpoint;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.vecmath.Point3d;
+import javax.vecmath.Quat4d;
+import javax.vecmath.Vector3d;
+
+import org.simantics.g3d.math.MathTools;
+import org.simantics.plant3d.scenegraph.InlineComponent;
+import org.simantics.plant3d.scenegraph.Nozzle;
+import org.simantics.plant3d.scenegraph.P3DRootNode;
+import org.simantics.plant3d.scenegraph.PipeRun;
+import org.simantics.plant3d.scenegraph.PipelineComponent;
+import org.simantics.plant3d.scenegraph.TurnComponent;
+import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction;
+import org.simantics.plant3d.utils.ComponentUtils;
+import org.simantics.utils.ui.ErrorLogger;
+
+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 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
+       };
+       
+       private static boolean enabled = true;
+       private static boolean updating = false;
+       private static boolean allowInsertRemove = true;
+       private static boolean triedIR = false;
+
+       
+       private static List<PipeControlPoint> updates = new ArrayList<PipeControlPoint>();
+       
+       private static Object mutex = new Object();
+       
+       public static void requestUpdate(PipeControlPoint pcp) {
+               if (DEBUG) System.out.println("PipingRules request " + pcp);
+               synchronized (mutex) {
+               if (!updates.contains(pcp))
+                       updates.add(pcp);
+               }
+       }
+       
+       public static synchronized boolean update() throws Exception {
+               if (updates.size() == 0)
+                       return false;
+               List<PipeControlPoint> temp = new ArrayList<PipeControlPoint>(updates.size());
+               synchronized(mutex) {
+                       temp.addAll(updates);
+                       updates.clear();
+               }
+               
+               for (PipeControlPoint pcp : temp)
+                       positionUpdate(pcp);
+               return true;
+       }
+       
+       public static boolean positionUpdate(PipeControlPoint pcp) throws Exception {
+               
+               return positionUpdate(pcp, true);
+       }
+       
+       public static boolean positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception {
+               if (updating || !enabled)
+                       return true;
+               if (pcp.getPipeRun() == null)
+                       return false;
+               try {
+                       if (DEBUG) System.out.println("PipingRules " + pcp);
+                       updating = true;
+                       allowInsertRemove = allowIR;
+                       triedIR = false;
+                       validate(pcp.getPipeRun());
+                       if (pcp.isPathLegEnd()) {
+                               updatePathLegEndControlPoint(pcp); // FXIME: Rules won't work properly, if they are not run twice.
+                               updatePathLegEndControlPoint(pcp);
+                       } else {
+                               updateInlineControlPoint(pcp);
+                               updateInlineControlPoint(pcp);
+                       }
+                       validate(pcp.getPipeRun());
+                       if (!allowInsertRemove)
+                               return !triedIR;
+                       return true;
+               } finally {
+                       updating = false;
+//                     System.out.println("PipingRules done " + pcp);
+               }
+       }
+       
+       public static void setEnabled(boolean enabled) {
+               PipingRules.enabled = enabled;
+               if(!enabled)
+                       updates.clear();
+       }
+       
+       public static boolean isEnabled() {
+               return enabled;
+       }
+       
+//     private void commit() {
+//             root.getNodeMap().commit();
+//     }
+
+       public static class ExpandIterInfo {
+               // these two are turn control points
+               private PipeControlPoint start;
+               private PipeControlPoint end;
+               private int type;
+
+               public ExpandIterInfo() {
+
+               }
+
+               public ExpandIterInfo(PipeControlPoint tcp, int type) {
+                       if (type == REMOVE_START)
+                               start = tcp;
+                       else
+                               end = tcp;
+                       this.type = type;
+               }
+
+               public ExpandIterInfo(PipeControlPoint start, PipeControlPoint end) {
+                       this.start = start;
+                       this.end = end;
+                       this.type = REMOVE_BOTH;
+               }
+
+               public PipeControlPoint getEnd() {
+                       return end;
+               }
+
+               public void setEnd(PipeControlPoint end) {
+                       this.end = end;
+               }
+
+               public PipeControlPoint getStart() {
+                       return start;
+               }
+
+               public void setStart(PipeControlPoint start) {
+                       this.start = start;
+               }
+
+               public int getType() {
+                       return type;
+               }
+
+               public void setType(int type) {
+                       this.type = type;
+               }
+
+       }
+
+       private static void updatePathLegEndControlPoint(PipeControlPoint pcp) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updatePathLegEndControlPoint() " + pcp);
+               if (pcp.getNext() != null) {
+                       updatePathLegNext(pcp, pcp, PathLegUpdateType.NEXT_S);
+               }
+               if (pcp.getPrevious() != null) {
+                       updatePathLegPrev(pcp, pcp, PathLegUpdateType.PREV_S);
+               }
+
+       }
+
+       private static void updateInlineControlPoint(PipeControlPoint pcp) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateInlineControlPoint() " + pcp);
+               PipeControlPoint start = pcp.findPreviousEnd();
+               updatePathLegNext(start, pcp, 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 (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
+                       
+               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
+                       pcp1 = pcp1.getSubPoint().get(0);       
+               } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
+                       PipeControlPoint t = pcp1;
+                       pcp1 = pcp2;
+                       pcp2 = t;
+               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
+                       PipeControlPoint t = pcp1;
+                       pcp1 = pcp2.getSubPoint().get(0);
+                       pcp2 = t;
+               } else {
+                       throw new RuntimeException();
+               }
+               TurnComponent elbow = ComponentUtils.createTurn((P3DRootNode)pcp1.getRootNode());
+               PipeControlPoint pcp = elbow.getControlPoint();
+               if (pcp1.isDualInline())
+                       pcp1 = pcp1.getSubPoint().get(0);
+               String name = pcp1.getPipeRun().getUniqueName("Elbow");
+               elbow.setName(name);
+               pcp1.getPipeRun().addChild(elbow);
+
+               pcp.insert(pcp1, pcp2);
+
+               pcp.setWorldPosition(pos);
+               validate(pcp.getPipeRun());
+               return pcp;
+       }
+       
+       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 (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
+                       
+               } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
+                       pcp1 = pcp1.getSubPoint().get(0);       
+               } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
+                       PipeControlPoint t = pcp1;
+                       pcp1 = pcp2;
+                       pcp2 = t;
+               } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
+                       PipeControlPoint t = pcp1;
+                       pcp1 = pcp2.getSubPoint().get(0);
+                       pcp2 = t;
+               } else {
+                       throw new RuntimeException();
+               }
+               InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp1.getRootNode());
+               PipeControlPoint scp = component.getControlPoint();
+               if (pcp1.isDualInline())
+                       pcp1 = pcp1.getSubPoint().get(0);
+               String name = pcp1.getPipeRun().getUniqueName("Pipe");
+               component.setName(name);
+               pcp1.getPipeRun().addChild(component);
+               
+               scp.insert(pcp1, pcp2);
+
+               scp.setWorldPosition(pos);
+               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);
+               
+               InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode());
+               PipeControlPoint scp = component.getControlPoint();
+               if (pcp.isDualInline() && direction == Direction.NEXT)
+                       pcp = pcp.getSubPoint().get(0);
+               String name = pcp.getPipeRun().getUniqueName("Pipe");
+               component.setName(name);
+               pcp.getPipeRun().addChild(component);
+               
+               scp.insert(pcp,direction);
+
+               scp.setWorldPosition(pos);
+               scp.setLength(length);
+               validate(scp.getPipeRun());
+               return scp;
+       }
+
+       private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
+               ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
+               PipeControlPoint end = start.findNextEnd(list);
+               // this is for inline cp that is also path leg end
+               if (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 class UpdateStruct2 {
+               public PipeControlPoint start;
+               public Vector3d startPoint;
+               public ArrayList<PipeControlPoint> list;
+               public PipeControlPoint end;
+               public Vector3d endPoint;
+               public Vector3d dir;
+               public Vector3d offset;
+               public boolean hasOffsets;
+               public int iter;
+               public boolean reversed;
+               public ArrayList<ExpandIterInfo> toRemove;
+               public PipeControlPoint updated;
+
+               public UpdateStruct2(PipeControlPoint start, Vector3d startPoint, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d endPoint, Vector3d dir, Vector3d offset, boolean hasOffsets, int iter, boolean reversed, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) {
+                       if (start == null || end == null)
+                               throw new NullPointerException();
+                       this.start = start;
+                       this.startPoint = startPoint;
+                       this.list = list;
+                       this.end = end;
+                       this.endPoint = endPoint;
+                       this.dir = dir;
+                       this.offset = offset;
+                       this.hasOffsets = hasOffsets;
+                       this.iter = iter;
+                       this.reversed = reversed;
+                       this.toRemove = toRemove;
+                       this.updated = updated;
+                       
+                       if (!MathTools.isValid(startPoint) || 
+                               !MathTools.isValid(endPoint) || 
+                               !MathTools.isValid(dir)) {
+                               throw new RuntimeException();
+                       }
+               }
+
+               public String toString() {
+                       return start + " " + end+ " " + dir + " " + hasOffsets + " " + offset + " " + iter + " " + toRemove.size();
+               }
+
+       }
+
+       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);
+               for (PipeControlPoint icp : list) {
+                       if (icp.isOffset()) {
+                               hasOffsets = true;
+                               offset.add(icp.getSizeChangeOffsetVector(dir));
+                       } else if (icp.isDualSub())
+                               ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+               }
+               return hasOffsets;
+       }
+
+       /**
+        * @param start
+        *            starting point of the pipe run
+        * @param list
+        *            list of inline control points in the pipe run
+        * @param end
+        *            ending point of the pipe run
+        * @param reversed
+        *            boolean flag indicating wether start or end control point was
+        *            modified (if true then end point was modified)
+        * @throws TransactionException
+        */
+       private static void updatePathLeg(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, boolean reversed, int iter, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
+               if (start == end)
+                       return;
+               // FIXME: direction is calculated wrong way!
+               boolean hasOffsets = false;
+               Vector3d offset = new Vector3d();
+               Vector3d startPoint = start.getWorldPosition();
+               Vector3d endPoint = end.getWorldPosition();
+               Vector3d dir = new Vector3d();
+               hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
+               updatePathLeg(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, reversed, toRemove, updated), lengthChange);
+
+       }
+
+       private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+               int directed = 0;
+               if (u.start.isDirected())
+                       directed++;
+               if (u.end.isDirected())
+                       directed++;
+               switch (directed) {
+               case 0:
+                       updateFreePathLeg(u, lengthChange);
+                       break;
+               case 1:
+                       updateDirectedPathLeg(u, lengthChange);
+                       break;
+               case 2:
+                       updateDualDirectedPathLeg(u, lengthChange);
+                       break;
+               }
+
+       }
+
+       private static void updateFreePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
+               checkExpandPathLeg(u, lengthChange);
+               if (u.start.isInline() || u.end.isInline())
+                       processPathLeg(u, true, false);
+       }
+
+       private static void updateInlineControlPoints(UpdateStruct2 u, boolean checkSizes) throws Exception{
+               if (DEBUG)
+                       System.out.println("PipingRules.updateInlineControlPoints() " + u);
+
+               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);
+                               }
+                               return;
+                       }
+
+                       ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
+                       pathLegPoints.add(u.start);
+                       for (PipeControlPoint icp : u.list) {
+                               // updateInlineControlPoint(icp, u.startPoint,
+                               // u.endPoint,u.dir);
+                               updateBranchControlPointBranches(icp);
+                               pathLegPoints.add(icp);
+                       }
+                       pathLegPoints.add(u.end);
+
+                       // TODO : values can be cached in the loop
+                       for (int i = 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();
+
+                               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();
+                                               }
+
+                                       } 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.
+                                               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));
+                                       }
+
+
+                               } 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.
+                                       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;
+                                               }
+                                       }
+                               }
+                       }
+               } else {
+                       u.endPoint.sub(u.offset);
+                       // FIXME : straights
+                       for (PipeControlPoint icp : u.list) {
+                               updateInlineControlPoint(icp, u.startPoint, u.endPoint, u.dir);
+                               updateBranchControlPointBranches(icp);
+                               
+                               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);
+                               }
+                       }
+               }
+       }
+
+       private static void ppNoOffset(UpdateStruct2 u) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.ppNoOffset() " + u);
+               Vector3d offset = new Vector3d();
+               if (u.hasOffsets) {
+                       u.dir.normalize();
+                       for (PipeControlPoint icp : u.list) {
+                               if (icp.isOffset()) {
+                                       offset.add(icp.getSizeChangeOffsetVector(u.dir));
+                               } else if (icp.isDualSub())
+                                       ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+                       }
+               }
+               u.offset = offset;
+               checkExpandPathLeg(u, PathLegUpdateType.NONE);
+       }
+
+       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());
+               // FIXME : extra loop (dir should be calculated here)
+               Vector3d dir = new Vector3d();
+               Vector3d offset = new Vector3d();
+               hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
+               ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated));
+       }
+
+       private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+               checkExpandPathLeg(u, lengthChange, false);
+       }
+       
+       private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean forceUpdate) 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);
+                       int type = checkTurns(u, lengthChange);
+                       if (type == REMOVE_NONE) {
+                               processPathLeg(u, forceUpdate, true);
+                       } else {
+                               expandPathLeg(u, type);
+                       }
+               } else {
+                       processPathLeg(u, forceUpdate, true);
+               }
+       }
+
+       private static void updateDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange);
+               PipeControlPoint dcp;
+               PipeControlPoint other;
+               boolean canMoveOther = false;
+               boolean dcpStart = false;
+               boolean inlineEnd = false;
+               Vector3d position;
+               if (u.start.isDirected()) {
+                       dcp = u.start;
+                       other = u.end;
+                       position = u.startPoint;
+                       dcpStart = true;
+                       if (!u.reversed)
+                               canMoveOther = true;
+                       inlineEnd = u.end.isInline();
+                               
+               } else {
+                       dcp = u.end;
+                       other = u.start;
+                       position = u.endPoint;
+                       if (u.reversed)
+                               canMoveOther = true;
+                       inlineEnd = u.start.isInline();
+               }
+
+               Vector3d directedDirection = dcp.getDirection();
+               Point3d directedEndPoint = new Point3d(u.endPoint);
+               if (u.hasOffsets)
+                       directedEndPoint.add(u.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);
+               }
+
+               double distance = t.lengthSquared();
+               boolean aligned = (distance < 0.002);
+               if (aligned) {
+                       checkExpandPathLeg(u, lengthChange, inlineEnd);
+                       
+               } else {
+                       if (u.iter > 0) {
+                               backIter(u);
+                       } else {
+                               PipeControlPoint nextToMoved;
+
+                               if (u.list.size() > 0)
+                                       if (dcpStart)
+                                               nextToMoved = u.list.get(0);
+                                       else
+                                               nextToMoved = u.list.get(u.list.size() - 1);
+                               else if (dcpStart)
+                                       nextToMoved = u.end;
+                               else
+                                       nextToMoved = u.start;
+                               if (other.isVariableAngle()) {
+
+                                       // TODO calculate needed space from next run end.
+                                       if (mu[0] < 1.0) {
+                                               if (dcpStart) {
+                                                       closest.set(u.startPoint);
+                                               } else {
+                                                       closest.set(u.endPoint);
+                                               }
+                                               closest.add(directedDirection);
+                                       }
+
+                                       if (canMoveOther) {
+                                               if (DEBUG)
+                                                       System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest);
+                                               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));
+                                                       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));
+                                                       if (u.start.getPrevious() != null)
+                                                               updatePathLegPrev(u.start, u.updated, PathLegUpdateType.PREV);
+                                               }
+                                       } else {
+                                               // TODO : calculate needed space from next run end.
+                                               if (allowInsertRemove)
+                                                       insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
+                                                       
+                                               else
+                                                       triedIR = true;
+                                       }
+                               } else if (other.isNonDirected() && other.getParentPoint() != null) {
+                                       // FIXME : this code was for updating branches
+                                       Vector3d bintersect = new Vector3d();
+                                       PipeControlPoint bcp = other.getParentPoint();
+                                       if (bcp != null && canMoveOther) {
+                                               Point3d bstart = new Point3d();
+                                               Point3d bend = new Point3d();
+                                               Vector3d bdir = new Vector3d();
+                                               bcp.getInlineControlPointEnds(bstart, bend, bdir);
+                                               Vector3d nintersect = new Vector3d();
+
+                                               MathTools.intersectStraightStraight(position, directedDirection, bend, bdir, nintersect, bintersect, mu);
+                                               Vector3d dist = new Vector3d(nintersect);
+                                               dist.sub(bintersect);
+                                               canMoveOther = mu[1] > 0.0 && mu[1] < 1.0 && dist.lengthSquared() < 0.01;
+                                       } else {
+                                               // TODO : endControlPoints are undirected: calculcate
+                                               // correct position for it
+                                               throw new UnsupportedOperationException("not implemented");
+                                       }
+                                       if (canMoveOther) {
+                                               if (DEBUG)
+                                                       System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect);
+                                               // is required branch position is in possible range
+                                               bcp.setWorldPosition(bintersect);
+                                               if (dcpStart) {
+                                                       checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(bintersect), directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange);
+                                               } else {
+                                                       checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(bintersect), u.list, u.end, u.endPoint, directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange);
+                                               }
+                                       } else {
+                                               // branch cannot be moved into right position, new turn
+                                               // / elbow must be inserted
+                                               if (allowInsertRemove)
+                                                       insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
+                                               else
+                                                       triedIR = true;
+                                       }
+
+                               } else { // assume that control point cannot be moved, but can
+                                                       // be rotated
+                                       if (allowInsertRemove)
+                                               insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
+                                       else
+                                               triedIR = true;
+                               }
+                       }
+               }
+               
+               
+       }
+
+       private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange);
+               
+               PipeControlPoint dcp1 = u.start;
+               PipeControlPoint dcp2 = u.end;
+               Point3d position1 = new Point3d(u.startPoint);
+               Point3d position2 = new Point3d(u.endPoint);
+               Point3d position1offset = new Point3d(position1);
+               position1offset.sub(u.offset);
+               Point3d position2offset = new Point3d(position2);
+               position2offset.add(u.offset);
+               Vector3d dir1 = dcp1.getDirection();
+               Vector3d dir2 = dcp2.getDirection();
+               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);
+               if (aligned) {
+                       processPathLeg(u);
+               } else {
+                       if (u.iter > 0) {
+                               backIter(u);
+                       } else if (allowInsertRemove){
+                               PipeControlPoint dcp;
+                               PipeControlPoint next;
+                               if (!u.reversed) {
+                                       dcp = dcp1;
+                                       if (u.list.size() > 0)
+                                               next = u.list.get(0);
+                                       else
+                                               next = dcp2;
+                               } else {
+                                       dcp = dcp2;
+                                       if (u.list.size() > 0)
+                                               next = u.list.get(u.list.size() - 1);
+                                       else
+                                               next = dcp1;
+                               }
+                               
+                               p1 = dcp.getWorldPosition();
+                               // FIXME: calculate position of the elbows properly.
+                               if (!u.reversed)
+                                       p1.add(dir1);
+                               else
+                                       p1.add(dir2);
+
+                               if (!u.reversed)
+                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2, dir2);
+                               else
+                                       p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1, dir1);
+
+                               
+                               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 (!u.reversed) {
+                                       Vector3d dd = new Vector3d(p2);
+                                       dd.sub(p1);
+                                       dir2.negate();
+                                       updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE);
+                                       updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE);
+                                       if (!u.reversed)
+                                               updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE);
+                                       else
+                                               updatePathLegPrev(tcp2, u.updated, PathLegUpdateType.NONE);
+                               } else {
+                                       Vector3d dd = new Vector3d(p1);
+                                       dd.sub(p2);
+                                       dir2.negate();
+                                       updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE);
+                                       updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE);
+                                       if (!u.reversed)
+                                               updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE);
+                                       else
+                                               updatePathLegPrev(u.start, u.updated, PathLegUpdateType.NONE);
+                               }
+                       } else {
+                               triedIR = true;
+                       }
+               }
+               
+       }
+
+       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);
+               PipeControlPoint tcp = null;
+               if (dcpStart)
+                       tcp = insertElbow(dcp, next, new Vector3d(closest));
+               else
+                       tcp = insertElbow(next, dcp, new Vector3d(closest));
+
+               if (DEBUG)
+                       System.out.println("PipingRules.updateDirectedPipeRun() inserted " + tcp);
+
+               if (dcpStart) {
+                       // update pipe run from new turn to other end
+                       ppNoDir(tcp, new Vector3d(closest), u.list, u.end, u.endPoint, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated);
+                       // update pipe run from directed to new turn
+                       processPathLeg(new UpdateStruct2(u.start, u.startPoint, new ArrayList<PipeControlPoint>(), tcp, new Vector3d(closest), directedDirection, new Vector3d(), false, 0, false, new ArrayList<ExpandIterInfo>(), u.updated));
+               } else {
+                       // update pipe run from other end to new turn
+                       ppNoDir(u.start, u.startPoint, u.list, tcp, new Vector3d(closest), u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated);
+                       // update pipe run from new turn to directed
+                       processPathLeg(new UpdateStruct2(tcp, new Vector3d(closest), new ArrayList<PipeControlPoint>(), u.end, u.endPoint, directedDirection, new Vector3d(), false, 0, false, new ArrayList<ExpandIterInfo>(), u.updated));
+               }
+       }
+
+       /**
+        * 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);
+               boolean startRemoved = false;
+               boolean endRemoved = false;
+               if (u.start.isVariableAngle()) {
+                       // this won't work properly if inline control points are not updated
+                       PipeControlPoint startPrev = u.start.getPrevious();
+                       if (startPrev != null) {
+                               double a;
+                               if (!u.hasOffsets) {
+                                       a = updateTurnControlPointTurn(u.start, startPrev, u.end);
+                               } else {
+                                       Vector3d ep = new Vector3d(u.endPoint);
+                                       ep.add(u.offset);
+                                       a = updateTurnControlPointTurn(u.start, u.startPoint, startPrev.getPosition(), ep);
+
+                               }
+                               if (a < MIN_TURN_ANGLE && u.start.isDeletable())
+                                       startRemoved = true;
+                               else if (lengthChange == PathLegUpdateType.PREV || lengthChange == PathLegUpdateType.PREV_S) {
+                                       PathLegUpdateType type;
+                                       if (lengthChange == PathLegUpdateType.PREV_S)
+                                               type = PathLegUpdateType.PREV;
+                                       else
+                                               type = PathLegUpdateType.NONE;
+                                       updatePathLegPrev(u.start, u.start, type);
+                               }
+                       }
+               }
+               if (u.end.isVariableAngle()) {
+
+                       PipeControlPoint endNext = u.end.getNext();
+                       if (endNext != null) {
+                               double a;
+                               if (!u.hasOffsets) {
+                                       a = updateTurnControlPointTurn(u.end, u.start, endNext);
+                               } else {
+                                       Vector3d sp = new Vector3d(u.startPoint);
+                                       sp.sub(u.offset);
+                                       a = updateTurnControlPointTurn(u.end, u.endPoint, sp, endNext.getPosition());
+                               }
+                               if (a < MIN_TURN_ANGLE && u.end.isDeletable())
+                                       endRemoved = true;
+                               else if (lengthChange == PathLegUpdateType.NEXT || lengthChange == PathLegUpdateType.NEXT_S) {
+                                       PathLegUpdateType type;
+                                       if (lengthChange == PathLegUpdateType.NEXT_S)
+                                               type = PathLegUpdateType.NEXT;
+                                       else
+                                               type = PathLegUpdateType.NONE;
+                                       updatePathLegNext(u.end, u.end, type);
+                               }
+                       }
+               }
+               if (DEBUG)
+                       System.out.println("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved);
+               if (!startRemoved && !endRemoved)
+                       return REMOVE_NONE;
+               if (startRemoved && endRemoved)
+                       return REMOVE_BOTH;
+               if (startRemoved)
+                       return REMOVE_START;
+               return REMOVE_END;
+       }
+
+       /**
+        * Expands piperun search over turns that are going to be removed
+        * 
+        */
+       private static void expandPathLeg(UpdateStruct2 u, int type) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.expandPipeline " + u.start + " " + u.end);
+               ArrayList<PipeControlPoint> newList = new ArrayList<PipeControlPoint>();
+               switch (type) {
+               case REMOVE_NONE:
+                       throw new RuntimeException("Error in piping rules");
+               case REMOVE_START:
+                       u.toRemove.add(new ExpandIterInfo(u.start, REMOVE_START));
+                       u.start = u.start.findPreviousEnd();
+                       u.startPoint = u.start.getPosition();
+                       u.start.findNextEnd(newList);
+                       newList.addAll(u.list);
+                       u.list = newList;
+                       break;
+               case REMOVE_END:
+                       u.toRemove.add(new ExpandIterInfo(u.end, REMOVE_END));
+                       u.end = u.end.findNextEnd(newList);
+                       u.endPoint = u.end.getPosition();
+                       u.list.addAll(newList);
+                       break;
+               case REMOVE_BOTH:
+                       u.toRemove.add(new ExpandIterInfo(u.start, u.end));
+                       u.start = u.start.findPreviousEnd();
+                       u.startPoint = u.start.getPosition();
+                       u.start.findNextEnd(newList);
+                       newList.addAll(u.list);
+                       u.list = newList;
+                       newList = new ArrayList<PipeControlPoint>();
+                       u.end = u.end.findNextEnd(newList);
+                       u.endPoint = u.end.getPosition();
+                       u.list.addAll(newList);
+                       break;
+               default:
+                       throw new RuntimeException("Error in piping rules");
+
+               }
+               u.offset = new Vector3d();
+               if (u.hasOffsets) {
+                       u.dir.normalize();
+                       for (PipeControlPoint icp : u.list) {
+                               if (icp.isOffset()) {
+                                       u.offset.add(icp.getSizeChangeOffsetVector(u.dir));
+                               } else if (icp.isDualSub())
+                                       ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+                       }
+               }
+               if (DEBUG)
+                       System.out.println("PipingRules.expandPipeline expanded " + u.start + " " + u.end);
+               u.iter++;
+               updatePathLeg(u, PathLegUpdateType.NONE);
+       }
+
+       /**
+        * reverts one iteration of turn removing back)
+        */
+       private static void backIter(UpdateStruct2 u) throws Exception {
+
+               if (DEBUG)
+                       System.out.println("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);
+               u.toRemove.remove(u.toRemove.size() - 1);
+               if (info.getType() == REMOVE_START || info.getType() == REMOVE_BOTH) {
+                       while (u.list.size() > 0) {
+                               PipeControlPoint icp = u.list.get(0);
+                               if (icp.getPrevious().equals(info.getStart()))
+                                       break;
+                               u.list.remove(icp);
+                       }
+                       u.start = info.getStart();
+               }
+               if (info.getType() == REMOVE_END || info.getType() == REMOVE_BOTH) {
+                       while (u.list.size() > 0) {
+                               PipeControlPoint icp = u.list.get(u.list.size() - 1);
+                               if (icp.getNext().equals(info.getEnd()))
+                                       break;
+                               u.list.remove(icp);
+                       }
+                       u.end = info.getEnd();
+               }
+               u.offset = new Vector3d();
+               if (u.hasOffsets) {
+                       u.dir.normalize();
+                       for (PipeControlPoint icp : u.list) {
+                               if (icp.isOffset()) {
+                                       u.offset.add(icp.getSizeChangeOffsetVector(u.dir));
+                               } else if (icp.isDualSub())
+                                       ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+                       }
+               }
+               processPathLeg(u);
+
+       }
+
+       /**
+        * Processes pipe run (removes necessary turns and updates run ends)
+        */
+       // private static void processPathLeg(PipeControlPoint start, Point3d
+       // startPoint,ArrayList<InlineControlPoint> list, PipeControlPoint
+       // end,Point3d endPoint, Vector3d dir,Vector3d offset, boolean
+       // hasOffsets,int iter, boolean reversed, ArrayList<ExpandIterInfo>
+       // toRemove) throws TransactionException {
+
+       private static void processPathLeg(UpdateStruct2 u) throws Exception {
+               if (DEBUG)
+                       System.out.println("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 " + u.start + " " + u.end);
+
+               if (u.toRemove.size() > 0) {
+                       for (ExpandIterInfo info : u.toRemove) {
+                               if (info.getStart() != null) {
+                                       info.getStart()._remove();
+                               }
+                               if (info.getEnd() != null) {
+                                       info.getEnd()._remove();
+                               }
+                       }
+                       // ControlPointTools.removeControlPoint may remove mo0re than one
+                       // CP;
+                       // we must populate inline CP list again.
+                       u.list.clear();
+                       u.start.findNextEnd( u.list);
+               }
+               // FIXME : inline CPs are update twice because their positions must be
+               // updated before and after ends.
+               updateInlineControlPoints(u, false);
+               
+               if (updateEnds) {
+                       if (u.start.isTurn()) {
+                               updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext());
+//                             updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE);
+                       } else if (u.start.isEnd()) {
+                               updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
+                       } else if (u.start.isInline()) {
+                               updateControlPointOrientation(u.start);
+                       }
+                       if (u.end.isTurn()) {
+                               updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext());
+//                             updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE);
+                       } else if (u.end.isEnd()) {
+                               updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
+                       } else if (u.end.isInline()) {
+                               updateControlPointOrientation(u.end);
+                       }
+
+               } else {
+                       if (u.start.isEnd()) {
+                               updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
+                       }
+                       if (u.end.isEnd()) {
+                               updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
+                       }
+               }
+               if (updateInline)
+                       updateInlineControlPoints(u, true);
+
+       }
+
+       /**
+        * Processes pipe run and recalculates offset
+        */
+       // private static void processPathLeg(PipeControlPoint start, Point3d
+       // startPoint,ArrayList<InlineControlPoint> list, PipeControlPoint
+       // end,Point3d endPoint, Vector3d dir, boolean hasOffsets,int iter, boolean
+       // reversed, ArrayList<ExpandIterInfo> toRemove) throws TransactionException
+       // {
+       private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
+               Vector3d offset = new Vector3d();
+               if (u.hasOffsets) {
+                       u.dir.normalize();
+                       for (PipeControlPoint icp : u.list) {
+                               if (icp.isOffset()) {
+                                       offset.add(icp.getSizeChangeOffsetVector(u.dir));
+                               } else if (icp.isDualSub()) {
+                                       ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
+                               }
+                       }
+               }
+               processPathLeg(u);
+       }
+
+       private static void updateOffsetPoint(PipeControlPoint sccp, Vector3d offset) {
+               Vector3d world = sccp.getWorldPosition();
+               world.add(offset);
+               PipeControlPoint ocp = sccp.getSubPoint().iterator().next();
+               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)
+        * 
+        * @param pipeline
+        * @param icp
+        * @param nextPoint
+        * @param prevPoint
+        */
+       private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d nextPoint, Vector3d prevPoint, Vector3d dir) {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateInlineControlPoint() " + icp);
+
+               Vector3d inlinePoint = icp.getWorldPosition();
+               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));
+                       } 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);
+                               }
+                       }
+               }
+               if (DEBUG)
+                       System.out.println(" " + newInlinePoint);
+
+               icp.setWorldPosition(newInlinePoint);
+               updateControlPointOrientation(icp);
+       }
+
+       /**
+        * Updates InlineControlPoints position when straight pipe's end(s) have
+        * been changed)
+        * 
+        * @param pipeline
+        * @param icp
+        * @param nextPoint
+        * @param prevPoint
+        */
+       private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d start, Vector3d end) throws Exception {
+               if (DEBUG)
+                       System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp);
+               // PipeControlPoint next = ecp.getNext();
+               // PipeControlPoint prev = ecp.getPrevious();
+               // if (next != null) {
+               // end = G3DTools.getPoint(next.getLocalPosition());
+               // start = G3DTools.getPoint(ecp.getLocalPosition());
+               // } else if (prev != null) {
+               // end = G3DTools.getPoint(ecp.getLocalPosition());
+               // start = G3DTools.getPoint(prev.getLocalPosition());
+               // } else {
+               // // TODO : warning?
+               // return;
+               // }
+               // Vector3d dir = new Vector3d (end);
+               // dir.sub(start);
+               // dir.normalize();
+               // G3DTools.setTuple(ecp.getDirection(), dir);
+               if (!ecp.isFixed())
+                       updateControlPointOrientation(ecp);
+
+               for (PipeControlPoint pcp : ecp.getSubPoint()) {
+                       // TODO update position
+                       updatePathLegEndControlPoint(pcp);
+               }
+       }
+
+       private static void updateControlPointOrientation(PipeControlPoint pcp) {
+               // FIXME : hack to bypass variable length components orientation
+//             if (pcp.getAtMostOneRelatedObject(ProcessResource.g3dResource.HasWorldOrientation) == null)
+//                     return;
+//             if (pcp.rotationAngle == null)
+//                     return;
+               Double angleO = pcp.getRotationAngle();
+               double angle = 0.0;
+               if (angleO != null)
+                       angle = angleO;
+
+               Quat4d q = pcp.getControlPointOrientationQuat(angle);
+               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 (bcp.isDualInline())
+                       return;
+               Collection<PipeControlPoint> branches = bcp.getSubPoint();
+               if (branches.size() == 0) {
+                       if (DEBUG)
+                               System.out.println("No Branches found");
+                       return;
+               }
+               
+               for (PipeControlPoint pcp : branches) {
+                       updatePathLegEndControlPoint(pcp);
+               }
+       }
+
+       /**
+        * Recalculates turn control point's internal data (turn angle and offset)
+        * 
+        * @param tcp
+        * @param prev
+        * @param next
+        */
+       private static double updateTurnControlPointTurn(PipeControlPoint tcp, PipeControlPoint prev, PipeControlPoint next) {
+               if (DEBUG)
+                       System.out.println("PipingTools.updateTurnControlPointTurn()" + tcp);
+               if (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);
+       }
+
+       /**
+        * Recalculates turn control point's internal data (turn angle and offset)
+        * 
+        * @param tcp
+        * @param middlePoint
+        * @param nextPoint
+        * @param prevPoint
+        */
+       private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d middlePoint, Vector3d prevPoint, Vector3d nextPoint) {
+
+               Vector3d dir1 = new Vector3d(middlePoint);
+               dir1.sub(prevPoint);
+               Vector3d dir2 = new Vector3d(nextPoint);
+               dir2.sub(middlePoint);
+               if (DEBUG)
+                       System.out.println("PipingTools.updateTurnControlPointTurn " + tcp + " " + prevPoint + " " + middlePoint + " " + nextPoint);
+               return updateTurnControlPointTurn(tcp, dir1, dir2);
+       }
+
+       private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d dir1, Vector3d dir2) {
+               double turnAngle = dir1.angle(dir2);
+
+               double angle = Math.PI - turnAngle;
+
+               Vector3d turnAxis = new Vector3d();
+               turnAxis.cross(dir1, dir2);
+               if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) {
+                       double elbowRadius = tcp.getPipelineComponent().getPipeRun().getTurnRadius();
+                       double R = elbowRadius / Math.tan(angle * 0.5);
+                       
+                       turnAxis.normalize();
+                       tcp.setTurnAngle(turnAngle);
+                       tcp.setLength(R);// setComponentOffsetValue(R);
+                       tcp.setTurnAxis(turnAxis);
+//                     tcp.setPosition(tcp.getPosition());
+               } else {
+                       turnAngle = 0.0;
+                       tcp.setTurnAngle(0.0);
+                       tcp.setLength(0.0);
+                       tcp.setTurnAxis(MathTools.Y_AXIS);
+               }
+               updateControlPointOrientation(tcp);
+               if (DEBUG)
+                       System.out.println("PipingTools.updateTurnControlPointTurn " + dir1 + " " + dir2 + " " + turnAngle + " " + turnAxis);
+               return turnAngle;
+       }
+       
+       public static List<PipeControlPoint> getControlPoints(PipeRun pipeRun) {
+               List<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
+               if (pipeRun.getControlPoints().size() == 0)
+                       return list;
+               PipeControlPoint pcp = pipeRun.getControlPoints().iterator().next();
+               while (pcp.getPrevious() != null) {
+                       PipeControlPoint prev = pcp.getPrevious();
+                       if (prev.getPipeRun() != pipeRun)
+                               break;
+                       pcp = prev;
+               }
+               if (pcp.isDualSub()) {
+                       pcp = pcp.getParentPoint();
+               }
+               list.add(pcp);
+               while (pcp.getNext() != null) {
+                       pcp = pcp.getNext();
+                       if (pcp.getPipeRun() != pipeRun)
+                               break;
+                       list.add(pcp);
+               }
+               return list;
+       }
+       
+       public static void reverse(PipeRun pipeRun) {
+               List<PipeControlPoint> list = getControlPoints(pipeRun);
+               if (list.size() <= 1)
+                       return; // nothing to do.
+               
+               for (int i = 0 ; i < list.size(); i++) {
+                       boolean first = i == 0;
+                       boolean last = i == list.size() - 1;
+                       PipeControlPoint current = list.get(i);
+                       PipeControlPoint currentSub = null;
+                       if (current.isDualInline())
+                               currentSub = current.getSubPoint().get(0);
+                       if (first) {
+                               PipeControlPoint next = list.get(i+1);
+                               if (next.isDualInline())
+                                       next = next.getSubPoint().get(0);
+                               current.setNext(null);
+                               current.setPrevious(next);
+                               if (currentSub != null) {
+                                       currentSub.setNext(null);
+                                       currentSub.setPrevious(next);           
+                               }
+                       } else if (last) {
+                               PipeControlPoint prev = list.get(i-1);
+                               
+                               current.setPrevious(null);
+                               current.setNext(prev);
+                               
+                               if (currentSub != null) {
+                                       currentSub.setPrevious(null);
+                                       currentSub.setNext(prev);               
+                               }
+                       } else {
+                               PipeControlPoint prev = list.get(i-1);
+                               PipeControlPoint next = list.get(i+1);
+                               if (next.isDualInline())
+                                       next = next.getSubPoint().get(0);
+                               
+                               
+                               current.setPrevious(next);
+                               current.setNext(prev);
+                               
+                               if (currentSub != null) {
+                                       currentSub.setPrevious(next);
+                                       currentSub.setNext(prev);               
+                               }
+                               
+                       }
+               }
+       }
+       
+       public static void merge(PipeRun run1, PipeRun r2) {
+               Map<PipeControlPoint, Vector3d> positions = new HashMap<PipeControlPoint, Vector3d>();
+               Map<PipeControlPoint, Quat4d> orientations = new HashMap<PipeControlPoint, Quat4d>();
+               for (PipeControlPoint pcp : r2.getControlPoints()) {
+                       positions.put(pcp, pcp.getWorldPosition());
+                       orientations.put(pcp, pcp.getWorldOrientation());
+               }
+               for (PipeControlPoint pcp : r2.getControlPoints()) {
+                       r2.deattachChild(pcp);
+                       run1.addChild(pcp);
+                       PipelineComponent component = pcp.getPipelineComponent();
+                       if (component != null) {
+                               if (!(component instanceof Nozzle)) {
+                                       component.deattach();
+                                       run1.addChild(component);
+                               } else {
+                                       Nozzle n = (Nozzle)component;
+                                       n.setPipeRun(run1);
+                               }
+                       }
+               }
+               r2.remove();
+               
+       }
+       
+       public static void validate(PipeRun pipeRun) {
+               if (pipeRun == null)
+                       return;
+               Collection<PipeControlPoint> pcps = pipeRun.getControlPoints();
+               int count = 0;
+               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");
+               }
+               for (PipeControlPoint pcp : pcps) {
+                       if (pcp.getParentPoint() == null) {
+                               PipeControlPoint sub = null;
+                               if (pcp.isDualInline())
+                                       sub = pcp.getSubPoint().get(0);
+                               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 );
+                                       }
+                               }
+                               if (prev != null) {
+                                       PipeControlPoint prevParent = null;
+                                       if (prev.isDualSub()) {
+                                               prevParent = prev.getParentPoint();
+                                       } else if (prev.isDualInline()) {
+                                               System.out.println("Inconsistency between " + pcp + " <-- " +prev );
+                                       }
+                                       if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) {
+                                               System.out.println("Inconsistency between " + pcp + " <-- " +prev );
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       public static void splitVariableLengthComponent(PipelineComponent newComponent, InlineComponent splittingComponent, boolean assignPos) throws Exception{
+               assert(!splittingComponent.getControlPoint().isFixed());
+               assert(!(newComponent instanceof  InlineComponent && !newComponent.getControlPoint().isFixed()));
+               PipeControlPoint newCP = newComponent.getControlPoint();
+               PipeControlPoint splittingCP = splittingComponent.getControlPoint();
+               PipeControlPoint nextCP = splittingCP.getNext();
+               PipeControlPoint prevCP = splittingCP.getPrevious();
+               
+               /* there are many different cases to insert new component when
+                  it splits existing VariableLengthinlineComponent.
+           
+              1. VariableLengthComponet is connected from both sides:
+                 - insert new component between VariableLength component and component connected to it
+                 - insert new VariableLengthComponent between inserted component and component selected in previous step
+               
+                  2. VariableLengthComponent is connected from one side
+                    - Use previous case or:
+                    - Insert new component to empty end
+                    - Insert new VariableLength component to inserted components empty end
+                    
+                  3. VariableLength is not connected to any component.
+                    - Should not be possible, at least in current implementation.
+                    - Could be done using second case
+
+               */
+               
+               if (nextCP == null && prevCP == null) {
+                       // this should not be possible
+                       throw new RuntimeException("VariableLengthComponent " + splittingComponent + " is not connected to anything.");
+               }
+               double reservedLength = splittingComponent.getControlPoint().getLength();
+               double newLength = newComponent.getControlPoint().getLength();
+               
+               
+               Point3d next = new Point3d();
+               Point3d prev = new Point3d();
+               splittingCP.getInlineControlPointEnds(prev, next);
+               
+               Vector3d newPos = null;
+               if (assignPos) {
+                       newPos = new Vector3d(prev);
+                       Vector3d dir = new Vector3d(next);
+                       dir.sub(prev);
+                       dir.scale(0.5);
+                       newPos.add(dir);
+                       newComponent.setWorldPosition(newPos);
+               } else {
+                       newPos = newComponent.getWorldPosition();
+               }
+               
+               
+
+               Vector3d dir = new Vector3d(next);
+               dir.sub(prev);
+               dir.normalize();
+               dir.scale(newLength * 0.5);
+               Point3d vn = new Point3d(newPos);
+               Point3d vp = new Point3d(newPos);
+               vn.add(dir);
+               vp.sub(dir);
+               double ln = vn.distance(next);
+               double lp = vp.distance(prev);
+               vp.interpolate(prev, 0.5);
+               vn.interpolate(next, 0.5);
+               
+               
+               PipeControlPoint newVariableLengthCP = null;//insertStraight(pcp1, pcp2, pos, length);
+               if (nextCP == null) {
+                       newCP.insert(splittingCP, Direction.NEXT);
+                       newVariableLengthCP = insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln);
+                       splittingCP.setWorldPosition(new Vector3d(vp));
+//                     ControlPointTools.setWorldPosition(splittingCP, vp);
+//                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
+               } else if (prevCP == null) {
+                       newCP.insert(splittingCP, Direction.PREVIOUS);
+                       newVariableLengthCP = insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp);
+                       splittingCP.setWorldPosition(new Vector3d(vn));
+//                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, ln);
+               } else {
+                       newCP.insert(splittingCP, nextCP);
+                       newVariableLengthCP = insertStraight(newCP, nextCP, new Vector3d(vn), ln);
+                       splittingCP.setWorldPosition(new Vector3d(vp));
+//                     splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
+               }
+               positionUpdate(newCP);
+
+       }
+       
+       public static void addSizeChange(boolean reversed, PipeRun pipeRun, PipeRun other, InlineComponent reducer, PipeControlPoint previous, PipeControlPoint next) {
+               PipeControlPoint pcp = reducer.getControlPoint();
+               PipeControlPoint ocp = pcp.getSubPoint().get(0);
+               if (!reversed) {
+                       String name = pipeRun.getUniqueName("Reducer");
+                       reducer.setName(name);
+                       pipeRun.addChild(reducer);
+                       other.addChild(ocp);
+                       reducer.setAlternativePipeRun(other);
+                       
+                       previous.setNext(pcp);
+                       pcp.setPrevious(previous);
+                       ocp.setPrevious(previous);
+                       if (next != null) {
+                               pcp.setNext(next);
+                               ocp.setNext(next);
+                               next.setPrevious(ocp);
+                       }
+               } else {
+                       String name = other.getUniqueName("Reducer");
+                       reducer.setName(name);
+                       other.addChild(reducer);
+                       pipeRun.addChild(ocp);
+                       reducer.setAlternativePipeRun(pipeRun);
+                       
+                       if (next != null) {
+                               next.setNext(pcp);
+                               pcp.setPrevious(next);
+                               ocp.setPrevious(next);
+                       }
+                       pcp.setNext(previous);
+                       ocp.setNext(previous);
+                       previous.setPrevious(ocp);
+               }
+       }
+}