]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java
Fix error in inserting dual inline components
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipeControlPoint.java
index 660558652c47c03c1897b7aa1a53761e6795a206..cd4f407b22b6f65e3e80a91c2bc4cc5fae2df9a6 100644 (file)
@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 
 import javax.vecmath.AxisAngle4d;
 import javax.vecmath.Matrix3d;
@@ -15,7 +16,10 @@ import javax.vecmath.Vector3d;
 import org.simantics.g3d.math.MathTools;
 import org.simantics.g3d.property.annotations.GetPropertyValue;
 import org.simantics.g3d.scenegraph.G3DNode;
+import org.simantics.g3d.scenegraph.base.INode;
 import org.simantics.plant3d.scenegraph.IP3DNode;
+import org.simantics.plant3d.scenegraph.Nozzle;
+import org.simantics.plant3d.scenegraph.P3DRootNode;
 import org.simantics.plant3d.scenegraph.PipeRun;
 import org.simantics.plant3d.scenegraph.PipelineComponent;
 
@@ -23,34 +27,38 @@ import vtk.vtkRenderer;
 
 
 public class PipeControlPoint extends G3DNode implements IP3DNode {
-    
-    private static boolean DEBUG = false;
-       
+
+       private static boolean DEBUG = false;
+
        public enum PointType{INLINE,TURN,END};
        public enum Direction{NEXT,PREVIOUS};
        public enum PositionType {SPLIT,NEXT,PREVIOUS,PORT}
-       
+
        private PipelineComponent component;
-       
+
        private PointType type;
-       private boolean fixed = true;
-       private boolean rotate = false;
-       private boolean reverse = false;
-       private boolean deletable = true;
-       private boolean sub = false;
+       private boolean isFixed = true;        // In-line: fixed-length Turn: fixed-angle
+       private boolean isMod = false;         // Can user modify fixed value manually 
+       private boolean isRotate = false;      // rotates around path leg axis.
+       private boolean isReverse = false;     // definition direction can be swapped
+       private boolean isDeletable = true;    // can be removed by rules
+       private boolean isSizeChange = false;  // changes size of the pipe. The next control point / component is on different PipeRun
+       private boolean isSub = false;         // child point for offset / size change
+
+       private boolean disposed = false;
        
        public PipeControlPoint(PipelineComponent component) {
                this.component = component;
                if (component.getPipeRun() != null)
                        component.getPipeRun().addChild(this);
-               
+
        }
-       
+
        public PipeControlPoint(PipelineComponent component, PipeRun piperun) {
                this.component = component;
                piperun.addChild(this);
        }
-       
+
        @Override
        public void update(vtkRenderer ren) {
                try {
@@ -58,180 +66,306 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                } catch (Exception e) {
                        e.printStackTrace();
                }
-               
+
        }
-       
+
        public PipeRun getPipeRun() {
                return (PipeRun)getParent();
        }
-       
+
        public PipelineComponent getPipelineComponent() {
                return component;
        }
-       
+
        public PointType getType() {
                return type;
        }
-       
+
        public void setType(PointType type) {
                this.type = type;
        }
-       
+
        @GetPropertyValue(name="Fixed",tabId="Debug",value="fixed")
        public boolean isFixed() {
-               return fixed;
+               return isFixed;
        }
-       
-       
+
        public void setFixed(boolean fixed) {
-               this.fixed = fixed;
+               this.isFixed = fixed;
        }
        
+       @GetPropertyValue(name="Mod",tabId="Debug",value="mod")
+       public boolean isMod() {
+        return isMod;
+    }
+    
+    public void setMod(boolean isMod) {
+        this.isMod = isMod;
+    }
+
        @GetPropertyValue(name="Rotate",tabId="Debug",value="rotate")
        public boolean isRotate() {
-               return rotate;
+               return isRotate;
        }
-       
+
        public void setRotate(boolean rotate) {
-               this.rotate = rotate;
+               this.isRotate = rotate;
        }
-       
+
        @GetPropertyValue(name="Reverse",tabId="Debug",value="reverse")
        public boolean isReverse() {
-               return reverse;
+               return isReverse;
        }
-       
+
        public void setReverse(boolean reverse) {
-               this.reverse = reverse;
+               this.isReverse = reverse;
        }
-       
+
        public void setSub(boolean sub) {
-               this.sub = sub;
+               this.isSub = sub;
        }
-               
+
        @GetPropertyValue(name="Deletable",tabId="Debug",value="deletable")
        public boolean isDeletable() {
-               return deletable;
+               return isDeletable;
        }
-       
+
        public void setDeletable(boolean deletable) {
-               this.deletable = deletable;
+               this.isDeletable = deletable;
        }
-       
+
        public boolean isPathLegEnd() {
                return type != PointType.INLINE;
        }
-       
+
        public boolean isEnd() {
                return type == PointType.END;
        }
-       
+
        public boolean isTurn() {
                return type == PointType.TURN;
        }
-       
+
        public boolean isInline() {
                return type == PointType.INLINE;
        }
        
+       public boolean asPathLegEnd() {
+           // Ends and Turns are path leg ends by default, but also unconnected inline are path leg ends.
+           return isPathLegEnd() || getNext() == null || getPrevious() == null;
+       }
+
+       /**
+        * True for end components, if control point defines absolute position direction, which rules cannot modify. 
+        * This is typical for nozzles.
+        * @return
+        */
        public boolean isDirected() {
-               return fixed && isEnd();
+               return isFixed && isEnd();
        }
-       
+
+       /**
+     * True for end components, if control is opposite to directed, and rules can modify position and orientation.
+     * This is typical for caps, and other end components.
+     * @return
+     */
        public boolean isNonDirected() {
-               return !fixed && isEnd();
+               return !isFixed && isEnd();
        }
-       
+
        public boolean isVariableLength() {
-               return !fixed && isInline();
+               return !isFixed && isInline();
        }
        
+       /**
+        * Fixed length in-line component is such that piping rules cannot modify the length.
+        * @return
+        */
+       public boolean isFixedLength() {
+        return isFixed && isInline();
+    }
+
        public boolean isVariableAngle() {
-               return !fixed && isTurn();
+               return !isFixed && isTurn();
        }
        
+       /**
+        * Fixed angle turn component is such that piping rules cannot modify the angle.
+        * @return
+        */
+       public boolean isFixedAngle() {
+        return isFixed && isTurn();
+    }
+       
+       /**
+        * Does the turn behave like fixed angle?
+        * For variable angle turns, the turn angle is defined by connected components, and without them, we must handle the component as fixed angle. 
+        * @return
+        */
+       public boolean asFixedAngle() {
+        return isTurn() && (isFixed || next == null || previous == null);
+    }
+
        public boolean isBranchEnd() {
-               return deletable && isEnd();
+               return isDeletable && isEnd();
        }
-       
+
        public boolean isOffset() {
                return offset != null;
        }
-       
+
        public boolean isDualSub() {
-               return parent != null && sub;
+               return parent != null && isSub;
        }
-       
+
        public boolean isDualInline() {
                return children.size() == 1 && children.get(0).isDualSub();
        }
-       
+
+       public boolean isAxial() {
+               return isInline() && !isDualInline();
+       }
+
        public boolean isSizeChange() {
-               if (children.size() == 0)
-                       return false;
-               if (!isDualInline())
-                       return false;
-               return getPipeRun() != children.get(0).getPipeRun();
+               return isSizeChange;
+               //              if (children.size() == 0)
+               //                      return false;
+               //              if (!isDualInline())
+               //                      return false;
+               //              return getPipeRun() != children.get(0).getPipeRun();
        }
 
-       
+       public void setSizeChange(boolean isSizeChange) {
+               this.isSizeChange = isSizeChange;
+       }
+
+
        private PipeControlPoint next;
        private PipeControlPoint previous;
-       
+
        public PipeControlPoint getNext() {
                return next;
        }
-       
+
        public PipeControlPoint getPrevious() {
                return previous;
        }
-       
+
        public void setNext(PipeControlPoint next) {
+           if (isSub) {
+               getParentPoint().setNext(next);
+               return;
+           }
+           if (next != null && next.isDualSub())
+               next = next.parent;
+           if (_setNext(next)) {
+               for (PipeControlPoint pcp : children) {
+                if (pcp.isSub)
+                    pcp._setNext(next);
+            }
+               updateSubPoint();
+           }
+       }
+       
+       public void setPrevious(PipeControlPoint prev) {
+        if (isSub) {
+            getParentPoint().setPrevious(prev);
+            return;
+        }
+        if (prev != null && prev.isDualInline())
+            prev = prev.children.get(0);
+        if (_setPrevious(prev)) {
+            for (PipeControlPoint pcp : children) {
+                if (pcp.isSub)
+                    pcp._setPrevious(prev);
+            }
+            updateSubPoint();
+        }
+    }
+       
+       protected boolean _setNext(PipeControlPoint next) {
                if (isEnd() && previous != null && next != null)
                        throw new RuntimeException("End control points are allowed to have only one connection");
+               if (next == this)
+                       throw new RuntimeException("Cannot connect to self");
                if (this.next == next)
-                   return;
+                       return false;
                if (DEBUG) System.out.println(this + " next " + next);
+               if (next == null && isVariableAngle() && previous != null && !isRemoved()) {
+                   convertVariableAngleToFixed(Direction.NEXT);
+               }
                this.next = next;
                if (component != null) {
-                       if (parent == null || sub)
+                       if (parent == null || isSub)
                                component.setNext(next != null ? next.component : null);
                        else
                                component.setBranch0(next != null ? next.component : null);
-                       updateSubPoint();
+                       
                }
-               
+               return true;
        }
-       
-       public void setPrevious(PipeControlPoint previous) {
+
+       protected boolean _setPrevious(PipeControlPoint previous) {
                if (isEnd() && next != null && previous != null)
                        throw new RuntimeException("End control points are allowed to have only one connection");
+               if (previous == this)
+                       throw new RuntimeException("Cannot connect to self");
                if (this.previous == previous)
-                   return;
+                       return false;
                if (DEBUG) System.out.println(this + " previous " + previous);
+               if (previous == null && isVariableAngle() && next != null && !isRemoved()) {
+            convertVariableAngleToFixed(Direction.PREVIOUS);
+        }
                this.previous = previous;
                if (component != null) {
-                       if (parent == null || sub)
+                       if (parent == null || isSub)
                                component.setPrevious(previous != null ? previous.component : null);
                        else
                                component.setBranch0(previous != null ? previous.component : null);
                        updateSubPoint();
                }
-               
+               return true;
        }
        
+       private void convertVariableAngleToFixed(Direction direction) {
+           // We are removing reference, which transforms variable angle to fixed angle.
+        // Since fixed angle is defined differently, we need to calculate fixed angle parameters based on current data
+        // We need to calculate turnAngle and rotationAngle
+           Vector3d dirOut = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
+        Vector3d dir = getPathLegDirection(direction == Direction.NEXT ? Direction.PREVIOUS : Direction.NEXT);
+        if (dir == null || dirOut == null)
+            return;
+        dir.negate();
+        double angle = dir.angle(dirOut);
+        //super._setNext(null);
+        if (direction == Direction.NEXT)
+            next = null;
+        else
+            previous = null;
+        setRotationAngle(0.0);
+        setReversed(direction == Direction.NEXT ? false : true);
+        Vector3d dirOutN = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
+        dirOutN.normalize();
+        AxisAngle4d aa = new AxisAngle4d();
+        if (MathTools.createRotation(dirOutN, dirOut, dir, aa)) {
+            setRotationAngle(aa.angle);
+            setTurnAngle(angle);
+            if (DEBUG) System.out.println("convertToFixed " + dir + " " + dirOut + " " +dirOutN + " " +angle + " "+ aa.angle);
+        }
+       }
+
        public PipeControlPoint parent;
        public List<PipeControlPoint> children = new ArrayList<PipeControlPoint>();
-       
-       public List<PipeControlPoint> getSubPoint() {
+
+       public List<PipeControlPoint> getChildPoints() {
                return children;
        }
-       
+
        public PipeControlPoint getParentPoint() {
                return parent;
        }
 
-       
+
        private double length;
        private Double turnAngle;
        private Vector3d turnAxis;
@@ -239,97 +373,108 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        private Double offset;
        private Double rotationAngle;
        private Boolean reversed;
-       
+
        @GetPropertyValue(name="Length",tabId="Debug",value="length")
        public double getLength() {
                return length;
        }
-       
+
        public void setLength(double l) {
-               if (Double.isInfinite(l) || Double.isNaN(l)) {
+               if (this.length == l)
+                       return;
+               if (Double.isInfinite(l) || Double.isNaN(l))
                        return;
-               }
                if (Math.abs(this.length-l) < MathTools.NEAR_ZERO)
                        return;
                this.length = l;
                firePropertyChanged("length");
                if (isDualInline())
-                       getSubPoint().get(0).setLength(l);
+                   getDualSub().setLength(l);
        }
-       
+
        @GetPropertyValue(name="Turn Angle",tabId="Debug",value="turnAngle")
        public Double getTurnAngle() {
                return turnAngle;
        }
-       
+
        @GetPropertyValue(name="Turn Axis",tabId="Debug",value="turnAxis")
        public Vector3d getTurnAxis() {
                return turnAxis;
        }
-       
+
        @GetPropertyValue(name="Offset",tabId="Debug",value="offset")
        public Double getOffset() {
                return offset;
        }
-       
+
        @GetPropertyValue(name="Rotation Angle",tabId="Debug",value="rotationAngle")
        public Double getRotationAngle() {
-               return rotationAngle;
+           if (isRotate || asFixedAngle())
+               return rotationAngle;
+           return null;
        }
-       
+
        @GetPropertyValue(name="Reversed",tabId="Debug",value="reversed")
        public Boolean getReversed() {
                return reversed;
        }
-       
+
        public boolean _getReversed() {
-           if (reversed == null)
-               return false;
-        return reversed;
-    }
-       
+               if (reversed == null)
+                       return false;
+               return reversed;
+       }
+
        public void setTurnAngle(Double turnAngle) {
                if (turnAngle == null || Double.isInfinite(turnAngle) || Double.isNaN(turnAngle)) {
                        return;
                }
                if (this.turnAngle != null && Math.abs(this.turnAngle-turnAngle) < MathTools.NEAR_ZERO)
                        return;
+               if (Objects.equals(this.turnAngle, turnAngle))
+                       return;
                this.turnAngle = turnAngle;
                firePropertyChanged("turnAngle");
        }
-       
+
        public void setTurnAxis(Vector3d turnAxis) {
-           if (this.turnAxis != null && MathTools.equals(turnAxis, this.turnAxis))
-            return;
-           this.turnAxis = turnAxis;
+               if (this.turnAxis != null && MathTools.equals(turnAxis, this.turnAxis))
+                       return;
+               this.turnAxis = turnAxis;
                firePropertyChanged("turnAxis");
        }
-       
+
        public void setOffset(Double offset) {
                if (Double.isInfinite(offset) || Double.isNaN(offset)) {
                        return;
                }
                if (this.offset != null && Math.abs(this.offset-offset) < MathTools.NEAR_ZERO)
                        return;
+               if (Objects.equals(this.offset, offset))
+                       return;
                this.offset = offset;
                firePropertyChanged("offset");
        }
-       
+
        public void setRotationAngle(Double rotationAngle) {
                if (Double.isInfinite(rotationAngle) || Double.isNaN(rotationAngle)) {
                        return;
                }
                if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO)
                        return;
+               if (Objects.equals(this.rotationAngle, rotationAngle))
+                       return;
                this.rotationAngle = rotationAngle;
                firePropertyChanged("rotationAngle");
        }
-       
+
        public void setReversed(Boolean reversed) {
+               if (this.reversed == reversed)
+                       return;
                this.reversed = reversed;
                firePropertyChanged("reversed");
        }
-       
+
        public Vector3d getSizeChangeOffsetVector(Vector3d dir) {
                Quat4d q;
                if (rotationAngle == null)
@@ -337,11 +482,11 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                else
                        q = getControlPointOrientationQuat(dir, rotationAngle);
                Vector3d v = new Vector3d(0.0,offset,0.0);
-       Vector3d offset = new Vector3d();
-       MathTools.rotate(q, v, offset);
-       return offset;
+               Vector3d offset = new Vector3d();
+               MathTools.rotate(q, v, offset);
+               return offset;
        }
-       
+
        public Vector3d getSizeChangeOffsetVector() {
                Quat4d q;
                if (rotationAngle == null)
@@ -349,182 +494,173 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                else
                        q = getControlPointOrientationQuat(rotationAngle);
                Vector3d v = new Vector3d(0.0,offset,0.0);
-       Vector3d offset = new Vector3d();
-       MathTools.rotate(q, v, offset);
-       return offset;
+               Vector3d offset = new Vector3d();
+               MathTools.rotate(q, v, offset);
+               return offset;
        }
-       
+
        @GetPropertyValue(name="Next",tabId="Debug",value="next")
        private String getNextString() {
                if (next == null)
                        return null;
                return next.toString();
        }
-       
+
        @GetPropertyValue(name="Previous",tabId="Debug",value="previous")
        private String getPrevString() {
                if (previous == null)
                        return null;
                return previous.toString();
        }
-       
+
        @GetPropertyValue(name="Sub",tabId="Debug",value="sub")
        private String getSubString() {
                if (children.size() == 0)
                        return "";
                return Arrays.toString(children.toArray());
        }
-       
+
        @GetPropertyValue(name="Type",tabId="Debug",value="type")
        public String getTypeString() {
                return type.name();
        }
+
+       public Vector3d getPathLegEndpointVector() {
+               PipeControlPoint a = findPreviousEnd();
+               PipeControlPoint b = findNextEnd();
+               
+               if (a == null || b == null) {
+                       return getPathLegDirection();
+               }
+               
+               Vector3d p1 = a.getWorldPosition();
+               Vector3d p2 = b.getWorldPosition();
+               p2.sub(p1);
+               double l = p2.length();
+               if (l != 0.0) {
+                       p2.scale(1.0 / l);
+                       return p2;
+               }
+               else {
+                       return getPathLegDirection();
+               }
+       }
+
+       public Vector3d getPathLegDirection() {
+               if (turnAxis == null) {
+                       return getPathLegDirection(Direction.NEXT);
+               } else {
+                       Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
+                       if (dir != null) dir.negate();
+                       return dir;
+               }
+       }
        
        public Quat4d getControlPointOrientationQuat(double angle) {
-                
-                if (turnAxis == null) {
-                        Vector3d dir = getPathLegDirection(Direction.NEXT);
-                        if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                                dir.normalize();
-                        return getControlPointOrientationQuat(dir, angle);
-                } else {
-                        Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
-                        dir.negate();
-                        if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                                dir.normalize();
-                        return getControlPointOrientationQuat(dir, turnAxis, angle);
-                }
-       }
-        
-    public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
-                
-                if (turnAxis == null) {
-                        Vector3d dir = getPathLegDirection(Direction.NEXT);
-                        if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                                dir.normalize();
-                        Quat4d q =  getControlPointOrientationQuat(dir, angle);
-                        if (reversed) {
-                                Quat4d q2 = new Quat4d();
-                               q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI));
-                               q.mulInverse(q2);
-                        }
-                        return q;
-                } else {
-                        Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
-                        dir.negate();
-                        if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                                dir.normalize();
-                        return getControlPointOrientationQuat(dir, turnAxis, angle);
-                }
-       }
-        
-       
+               Vector3d dir = getPathLegDirection();
+               if (turnAxis == null) {
+                       return getControlPointOrientationQuat(dir, angle);
+               } else {
+                       return getControlPointOrientationQuat(dir, turnAxis, angle);
+               }
+       }
        
-       public static Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
-                       if (dir.lengthSquared() < MathTools.NEAR_ZERO)
-                               return MathTools.getIdentityQuat();
-               
-               
-                       Vector3d up = new Vector3d(0.0, 1.0, 0.0);
-                       double a = up.angle(dir);
-                       if (a < 0.1 || (Math.PI - a) < 0.1) {
-                               up.set(1.0, 0.0, 0.0);
-                       }
-                       
+       public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle, boolean reversed) {
+           if (turnAxis == null) {
+            if (dir.lengthSquared() > MathTools.NEAR_ZERO)
+                dir.normalize();
+            Quat4d q =  getControlPointOrientationQuat(dir, angle);
+            if (reversed) {
+                Quat4d q2 = new Quat4d();
+                q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI));
+                q.mulInverse(q2);
+            }
+            return q;
+        } else {
+            if (dir.lengthSquared() > MathTools.NEAR_ZERO)
+                dir.normalize();
+            return getControlPointOrientationQuat(dir, turnAxis, angle);
+        }
+       }
 
-                       return getControlPointOrientationQuat(dir, up, angle);
-           }
-        
-        public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up,  double angle) {
-                       if (dir.lengthSquared() < MathTools.NEAR_ZERO)
-                               return MathTools.getIdentityQuat();
-               
-               final Vector3d front = new Vector3d(1.0,0.0,0.0);
-               
-               Quat4d q1 = new Quat4d();
-
-
-                       Vector3d right = new Vector3d();
-
-                       right.cross(dir, up);
-                       up.cross(right, dir);
-                       right.normalize();
-                       up.normalize();
-
-                       Matrix3d m = new Matrix3d();
-                       m.m00 = dir.x;
-                       m.m10 = dir.y;
-                       m.m20 = dir.z;
-                       m.m01 = up.x;
-                       m.m11 = up.y;
-                       m.m21 = up.z;
-                       m.m02 = right.x;
-                       m.m12 = right.y;
-                       m.m22 = right.z;
-
-                       //q1.set(m); MathTools contains more stable conversion
-                       MathTools.getQuat(m, q1);
-
-//                     if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right);
-
-                       Quat4d q2 = new Quat4d();
-                       q2.set(new AxisAngle4d(front, angle));
-                       q1.mul(q2);
-                       return q1;
-           }
-       
-       public Vector3d getDirection(Direction direction) {
-           if (isDirected())
-               return getDirectedControlPointDirection();
-           if (isTurn() && isFixed()) {
-               if (direction == Direction.NEXT) {
-                   if (previous != null) {
-                   PipeControlPoint pcp = this;
-                    Vector3d dir = new Vector3d();
-                    dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
-                    if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                         dir.normalize();
-                    else
-                        return null;
-                    Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
-                    AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
-                    Quat4d q2 = MathTools.getQuat(aa);
-                    Vector3d v = new Vector3d(1.,0.,0.);
-                    Vector3d offset = new Vector3d();
-                    MathTools.rotate(q2, v, offset);
-                    MathTools.rotate(q, offset, dir);
-                    return dir;
-                   }
-               } else {
-                   if (next != null) {
-                   PipeControlPoint pcp = this;
-                    Vector3d dir = new Vector3d();
-                    dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
-                    if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                         dir.normalize();
-                    else
-                        return null;
-                    Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
-                    AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
-                    Quat4d q2 = MathTools.getQuat(aa);
-                    Vector3d v = new Vector3d(1.,0.,0.);
-                    Vector3d offset = new Vector3d();
-                    MathTools.rotate(q2, v, offset);
-                    MathTools.rotate(q, offset, dir);
-                    return dir;
-                   }
-               }
-           }
-           return null;
+       public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
+               Vector3d dir = getPathLegDirection();
+               return getControlPointOrientationQuat(dir, angle, reversed);
        }
-       
+
+       public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
+               if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
+                       return MathTools.getIdentityQuat();
+
+               final P3DRootNode root = getRoot();
+               Vector3d up = root != null ? new Vector3d(root.getUpVector()) : new Vector3d(0.0, 1.0, 0.0);
+               final Vector3d legDir = getPathLegEndpointVector();
+               double a = up.angle(legDir);
+               if (a < 0.1 || (Math.PI - a) < 0.1) {
+                       // Rotate components
+                       up.set(up.getY(), up.getZ(), up.getX());
+               }
+               
+               // Project up vector into a normal of the leg direction before applying to 'dir'
+               MathTools.mad(up, legDir, -legDir.dot(up)/legDir.lengthSquared());
+               up.normalize();
+
+               return getControlPointOrientationQuat(dir, up, angle);
+       }
+
+       public P3DRootNode getRoot() {
+               INode n = getParent();
+               while (n != null && !(n instanceof P3DRootNode))
+                       n = n.getParent();
+               return (P3DRootNode) n;
+       }
+
+       public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up,  double angle) {
+               if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
+                       return MathTools.getIdentityQuat();
+
+               final Vector3d front = new Vector3d(1.0,0.0,0.0);
+
+               Quat4d q1 = new Quat4d();
+
+
+               Vector3d right = new Vector3d();
+               
+               up = new Vector3d(up);
+               right.cross(dir, up);
+               up.cross(right, dir);
+               right.normalize();
+               up.normalize();
+
+               Matrix3d m = new Matrix3d();
+               m.m00 = dir.x;
+               m.m10 = dir.y;
+               m.m20 = dir.z;
+               m.m01 = up.x;
+               m.m11 = up.y;
+               m.m21 = up.z;
+               m.m02 = right.x;
+               m.m12 = right.y;
+               m.m22 = right.z;
+
+               //q1.set(m); MathTools contains more stable conversion
+               MathTools.getQuat(m, q1);
+
+               //                      if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right);
+
+               Quat4d q2 = new Quat4d();
+               q2.set(new AxisAngle4d(front, angle));
+               q1.mul(q2);
+               return q1;
+       }
+
        public void insert(PipeControlPoint previous, PipeControlPoint next) {
                // inserting an offsetpoint is error, 
                if (isDualSub())
                        throw new RuntimeException("Dual sub points cannot be inserted.");
                // size change control point cannot be inserted this way, because it ends PipeRun
-               if (isSizeChange())
-                       throw new RuntimeException("Size change points cannot be inserted.");
+//             if (isSizeChange())
+//                     throw new RuntimeException("Size change points cannot be inserted.");
                PipeRun piperun = previous.getPipeRun();
                // and just to make sure that control point structure is not corrupted
                if (getPipeRun() != null) {
@@ -533,14 +669,14 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                } else {
                        piperun.addChild(this);
                }
-               
+
                // insert new BranchControlPoint between straight's control points
                PipeControlPoint previousNext = previous.getNext();
                PipeControlPoint previousPrevious = previous.getPrevious();
-               
+
                PipeControlPoint offsetCP = null;
                if (isOffset()) {
-                       offsetCP = getSubPoint().get(0);
+                       offsetCP = getDualSub();
                }
                if (previousNext != null && previousNext == next) {
                        if (previous.isDualInline()) {
@@ -555,7 +691,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                previous.getParentPoint().setNext(this);
                        }
                        this.setNext(next);
-                       
+
                        if (offsetCP == null) {
                                next.setPrevious(this);
                        } else {
@@ -563,17 +699,17 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                offsetCP.setNext(next);
                                offsetCP.setPrevious(previous);
                        }
-                       
+
                        if (next.isDualInline()) {
-                               next.getSubPoint().get(0).setPrevious(this);
+                               next.getDualSub().setPrevious(this);
                        }
                } else if (previousPrevious != null && previousPrevious == next) {
-                        // control point were given in reverse order 
+                       // control point were given in reverse order 
                        if (next.isDualInline())
                                throw new RuntimeException();
                        if (previous.isDualSub())
                                throw new RuntimeException();
-                       
+
                        this.setNext(previous);
                        if (offsetCP == null) {
                                previous.setNext(this);
@@ -583,23 +719,23 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                offsetCP.setPrevious(next);
                        }
                        if (previous.isDualInline()) {
-                               previous.getSubPoint().get(0).setPrevious(this);
+                               previous.getDualSub().setPrevious(this);
                        }
                        this.setPrevious(next);
                        next.setNext(this);
                        if (next.isDualSub()) {
                                next.getParentPoint().setNext(this);
                        }
-                       
+
                } else {
                        throw new RuntimeException();
                }       
-               
+
                PipingRules.validate(piperun);
        }
-       
-       
-       
+
+
+
        public void insert(PipeControlPoint pcp, Direction direction) {
                if (isDualSub())
                        throw new RuntimeException();
@@ -614,9 +750,6 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        if (pcp.isDualSub()) {
                                pcp.getParentPoint().setNext(this);
                        }
-                       if (isDualInline()) {
-                               getSubPoint().get(0).setPrevious(this);
-                       }
                } else {
                        // if direction is previous, user must have given sizechange
                        if (pcp.isDualSub())
@@ -625,7 +758,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        // we must link pcp to newCP's OffsetPoint
                        PipeControlPoint nocp = null;
                        if (isDualInline()) {
-                               nocp = getSubPoint().get(0);
+                               nocp = getDualSub();
                                nocp.setNext(pcp);
                        }
                        if (nocp == null) {
@@ -635,17 +768,17 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        }
                        this.setNext(pcp);
                        if (pcp.isDualInline()) {
-                               PipeControlPoint ocp = pcp.getSubPoint().get(0);
+                               PipeControlPoint ocp = pcp.getDualSub();
                                if (nocp == null)
                                        ocp.setPrevious(this);
                                else
                                        ocp.setPrevious(nocp);
                        }
-                       
+
                }
                PipingRules.validate(getPipeRun());
        }
-       
+
        public Vector3d getDirectedControlPointDirection() {
                assert (isDirected());
                Vector3d dir = new Vector3d();
@@ -654,125 +787,210 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                return dir;
        }
        
+       /**
+        * Returns direction vector pointing towards an adjacent component for
+        * directed control points or turn control points with one open end. 
+        * 
+        * Always returns an outwards pointing vector.
+        * 
+        * For any other type of component, the return value is null.
+        * 
+        * For turn components this only return a non-null value for the unconnected
+        * end of the component. 
+        * 
+        * @param direction
+        * @return normalized vector, or null
+        */
+       public Vector3d getDirection(Direction direction) {
+        if (isDirected())
+            return getDirectedControlPointDirection();
+        if (isTurn() && asFixedAngle()) {
+            if (direction == Direction.NEXT) {
+                if (previous != null) {
+                    PipeControlPoint pcp = this;
+                    Vector3d dir = new Vector3d();
+                    dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
+                    if (dir.lengthSquared() > MathTools.NEAR_ZERO)
+                        dir.normalize();
+                    else
+                        return null;
+                    Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
+                    AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
+                    Quat4d q2 = MathTools.getQuat(aa);
+                    Vector3d v = new Vector3d(1.,0.,0.);
+                    Vector3d offset = new Vector3d();
+                    MathTools.rotate(q2, v, offset);
+                    MathTools.rotate(q, offset, dir);
+                    dir.normalize();
+                    return dir;
+                }
+            } else {
+                if (next != null) {
+                    PipeControlPoint pcp = this;
+                    Vector3d dir = new Vector3d();
+                    dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
+                    if (dir.lengthSquared() > MathTools.NEAR_ZERO)
+                        dir.normalize();
+                    else
+                        return null;
+                    Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
+                    AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
+                    Quat4d q2 = MathTools.getQuat(aa);
+                    Vector3d v = new Vector3d(1.,0.,0.);
+                    Vector3d offset = new Vector3d();
+                    MathTools.rotate(q2, v, offset);
+                    MathTools.rotate(q, offset, dir);
+                    dir.negate();
+                    dir.normalize();
+                    return dir;
+                }
+            }
+        }
+        return null;
+    }
+
+       /**
+        * Returns path leg direction of the control point.
+        * 
+        * This method differs from getDirection by also returning inward pointing vectors for directed control points.
+        * 
+        * @param direction
+        * @return
+        */
        public Vector3d getPathLegDirection(Direction direction) {
                if (direction == Direction.NEXT) {
-                       if (next != null) {
-                               PipeControlPoint pcp = this;
-                               if (pcp.isDualInline()) {
-                                       pcp = pcp.getSubPoint().get(0);
-                               }
-                               Vector3d v = new Vector3d();
-                               v.sub(next.getWorldPosition(),pcp.getWorldPosition());
-                               return v;
-                       } else {
-                               if (previous == null) {
-                                       if (!isDirected())
-                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point");
-                                       return getDirectedControlPointDirection();
-                                       
-                               } else {
-                                   if (isVariableAngle())
-                           throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point");
-                                       if (isInline()) {
-                                               PipeControlPoint pcp = this;
-                                               if (pcp.isDualSub()) {
-                                                       pcp = pcp.getParentPoint();
-                                               }
-                                               Vector3d v = new Vector3d();
-                                               v.sub(pcp.getWorldPosition(),previous.getWorldPosition());
-                                               return v;
-                                       } else if (isDirected()) {
-                                               return getDirectedControlPointDirection();
-                                       } else if (isEnd()) {
-                                               Vector3d v = new Vector3d();
-                                               v.sub(getWorldPosition(),previous.getWorldPosition());
-                                               return v;
-                                       } else if (isTurn() && isFixed() && !_getReversed()) {
-                                          return getDirection(Direction.NEXT);
-                                       }
-                                       throw new RuntimeException("Missing implementation");
-                               }
-                       }
+                       return getPathLegDirectionNext();
                } else {
-                       if (previous != null) {
-                               PipeControlPoint pcp = this;
-                               if (isDualSub()) 
-                                       pcp = getParentPoint();
-                               Vector3d v = new Vector3d();
-                               v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
-                               return v;
-                       } else {
-                               if (next == null)  {
-                                       if (!isDirected())
-                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point");
-                                       Vector3d v = getDirectedControlPointDirection();
-                                       v.negate();
-                                       return v;
-                               } else {
-                                   if (isVariableAngle())
-                           throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point");
-                       if (isInline()) {
-                                               PipeControlPoint pcp = this;
-                                               if (pcp.isDualInline()) {
-                                                       pcp = pcp.getSubPoint().get(0);
-                                               }
-                                               Vector3d v = new Vector3d();
-                                               v.sub(pcp.getWorldPosition(),next.getWorldPosition());
-                                               return v;
-                                       } else if (isDirected()) {
-                                               Vector3d v = getDirectedControlPointDirection();
-                                               v.negate();
-                                               return v;
-                                       } else if (isEnd()) {
-                                               Vector3d v = new Vector3d();
-                                               v.sub(getWorldPosition(),next.getWorldPosition());
-                                               return v;
-                                       } else if (isTurn() && isFixed() && _getReversed()) {
-                                           return getDirection(Direction.PREVIOUS);
-                    }
-                                       throw new RuntimeException("Missing implementation");
-                               }
+                       return getPathLegDirectionPrevious();
+               }
+       }
+
+       public Vector3d getPathLegDirectionPrevious() {
+               if (previous != null) {
+                       PipeControlPoint pcp = this;
+                       if (isDualSub()) 
+                               pcp = getParentPoint();
+                       Vector3d v = new Vector3d();
+                       v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
+                       if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                       v.normalize();
+                   else
+                       return null;
+                       return v;
+               } else if (isDirected()) {
+                       Vector3d v = getDirectedControlPointDirection();
+                       v.negate();
+                       return v;
+               } else if (next == null)  {
+                       throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
+               } else if (isVariableAngle() && !asFixedAngle()) {
+                       throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
+               } else if (isInline() || isEnd()) {
+                       Vector3d v = getPathLegDirectionNext();
+                       if (v != null) v.negate();
+                       return v;
+               } else if (isTurn() && asFixedAngle() && _getReversed()) {
+                       return getDirection(Direction.PREVIOUS);
+               } else {
+                       throw new RuntimeException("Missing implementation " + this);
+               }
+       }
+
+       public Vector3d getPathLegDirectionNext() {
+               if (next != null) {
+                       PipeControlPoint pcp = this;
+                       if (pcp.isDualInline()) {
+                               pcp = pcp.getDualSub();
                        }
+                       Vector3d v = new Vector3d();
+                       v.sub(next.getWorldPosition(),pcp.getWorldPosition());
+                       if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                       v.normalize();
+                   else
+                       return null;
+                       return v;
+               } else if (isDirected()) {
+                       return getDirectedControlPointDirection();
+               } else if (previous == null) {
+                       throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
+               } else if (isVariableAngle() && !asFixedAngle()) {
+                       throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
+               } else if (isInline() || isEnd()) {
+                       Vector3d v = getPathLegDirectionPrevious();
+                       if (v != null) v.negate();
+                       return v;
+               } else if (isTurn() && asFixedAngle() && !_getReversed()) {
+                       return getDirection(Direction.NEXT);
+               } else {
+                       throw new RuntimeException("Missing implementation " + this);
                }
        }
-       
+
        public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
                assert (isInline());
+
+               PipeControlPoint sub = isAxial() ? this : getDualSub();
+               Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
+               Vector3d dir = sub.getInlineDir();
                
-               Vector3d pos = getWorldPosition();
-               Vector3d dir = getPathLegDirection(Direction.NEXT);
-               dir.normalize();
                dir.scale(length * 0.5);
                p1.set(pos);
-               p2.set(pos);
+               p2.set(pos2);
                p1.sub(dir);
                p2.add(dir);
        }
-       
+
        public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
-               Vector3d pos = getWorldPosition();
-               Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
-               dir1.normalize();
-               Vector3d dir2 = getPathLegDirection(Direction.NEXT);
-               dir2.normalize();
+               PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
+               Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
+               
+               Vector3d dir1;
+               Vector3d dir2;
                if (isInline()) {
-                       dir1.scale(length * 0.5);
+                       dir2 = getInlineDir();
                        dir2.scale(length * 0.5);
+                       dir1 = new Vector3d(dir2);
+                       dir1.negate();
                } else {
+                       dir1 = getPathLegDirection(Direction.PREVIOUS);
+                       dir2 = sub.getPathLegDirection(Direction.NEXT);
                        dir1.scale(length);
                        dir2.scale(length);
                }
                p1.set(pos);
-               p2.set(pos);
+               p2.set(pos2);
                p1.add(dir1);
                p2.add(dir2);
        }
-       
+
+       /**
+        * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
+        * 
+        * @param v1  Set to the direction towards the previous control point on output
+        * @param v2  Set to the direction towards the next control point on output
+        */
+       public void getEndDirections(Tuple3d v1, Tuple3d v2) {
+               PipeControlPoint sub = isAxial() ? this : getDualSub();
+               
+               Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
+               Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
+               
+               if (dir1 != null)
+                       v1.set(dir1);
+               else
+                       v1.set(0,0,0);
+               
+               if (dir2 != null)
+                       v2.set(dir2);
+               else
+                       v2.set(0,0,0);
+       }
+
        public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
                assert (isInline());
-               
+
                Vector3d pos = getWorldPosition();
-               dir.set(getPathLegDirection(Direction.NEXT));
+               dir.set(getInlineDir());
                dir.normalize();
                dir.scale(length * 0.5);
                p1.set(pos);
@@ -780,13 +998,13 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                p1.sub(dir);
                p2.add(dir);
        }
-       
+
        public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
                assert (isInline());
-               
+
                Vector3d pos = getWorldPosition();
                center.set(pos);
-               dir.set(getPathLegDirection(Direction.NEXT));
+               dir.set(getInlineDir());
                dir.normalize();
                dir.scale(length * 0.5);
                p1.set(pos);
@@ -794,7 +1012,23 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                p1.sub(dir);
                p2.add(dir);
        }
-       
+
+       public Vector3d getInlineDir() {
+               Vector3d dir = getPathLegDirection(Direction.NEXT);
+               if (dir == null) {
+                       dir = getPathLegDirection(Direction.PREVIOUS);
+                       if (dir != null) {
+                               // Use reverse direction
+                               dir.scale(-1.0);
+                       } else {
+                               // Control point is not connected at all, use current orientation
+                               dir = new Vector3d(1,0,0);
+                               MathTools.rotate(getWorldOrientation(), dir, dir);
+                       }
+               }
+               return dir;
+       }
+
        public double getInlineLength() {
                if (type == PointType.TURN)
                        return length;
@@ -802,22 +1036,38 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        return length * 0.5;
                return 0;
        }
-       
+
+       /**
+        * Return the position indicated by the argument. If the indicated direction is not connected, the
+        * control point's wolrd position is returned instead.
+        * 
+        * @param type  A selector for the position to be returned
+        * @return  The selected position
+        */
        public Vector3d getRealPosition(PositionType type) {
                Vector3d pos = getWorldPosition();
                switch (type) {
                case NEXT: {
-                       Vector3d dir = getPathLegDirection(Direction.NEXT);
                        double length = getInlineLength();
-                       dir.normalize();
+                       Vector3d dir;
+                       if (isInline()) {
+                               dir = getInlineDir();
+                       } else {
+                               dir = getPathLegDirection(Direction.NEXT);
+                       }
                        dir.scale(length);
                        pos.add(dir);
                        break;
                }
                case PREVIOUS: {
-                       Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
                        double length = getInlineLength();
-                       dir.normalize();
+                       Vector3d dir;
+                       if (isInline()) {
+                               dir = getInlineDir();
+                               dir.negate();
+                       } else {
+                               dir = getPathLegDirection(Direction.PREVIOUS);
+                       }
                        dir.scale(length);
                        pos.add(dir);
                        break;
@@ -829,11 +1079,11 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                case SPLIT:
                        // do nothing
                        break;
-                       
+
                }
                return pos;
        }
-       
+
        public void getInlineMovement(Tuple3d start, Tuple3d end) {
                // FIXME : check type of neighbor components and allow movement on top of variable length components,
                //         find proper range for movement (pcp's position is not)
@@ -842,46 +1092,46 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                start.set(p.getWorldPosition());
                end.set(n.getWorldPosition());
        }
-       
+
        public PipeControlPoint findNextEnd() {
                ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
-        return findNextEnd( t);
+               return findNextEnd( t);
        }
-       
+
        public PipeControlPoint findPreviousEnd() {
                ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
-        return findPreviousEnd(t);
+               return findPreviousEnd(t);
        }
-       
+
        public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
-                while (true) {
-            PipeControlPoint pcp = null;
-            PipeControlPoint p = null;
-            if (nextList.size() == 0)
-                p = this;
-                
-            else
-                p = nextList.get(nextList.size() - 1);
-
-            pcp = p.getNext();
-            if (pcp == null) {
-               pcp = p;
-               if (nextList.size() > 0)
-                       nextList.remove(nextList.size() - 1);
-//             if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
-               return pcp;
-               //break;
-            }
-            if (pcp.isPathLegEnd()) {
-               //if (DEBUG) System.out.println(" " + pcp.getResource());
-                return pcp;
-            } else {
-                nextList.add(pcp);
-               // if (DEBUG) System.out.print(" " + pcp.getResource());
-            }
-        }
+               while (true) {
+                       PipeControlPoint pcp = null;
+                       PipeControlPoint p = null;
+                       if (nextList.size() == 0)
+                               p = this;
+
+                       else
+                               p = nextList.get(nextList.size() - 1);
+
+                       pcp = p.getNext();
+                       if (pcp == null) {
+                               pcp = p;
+                               if (nextList.size() > 0)
+                                       nextList.remove(nextList.size() - 1);
+                               //              if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
+                               return pcp;
+                               //break;
+                       }
+                       if (pcp.isPathLegEnd()) {
+                               //if (DEBUG) System.out.println(" " + pcp.getResource());
+                               return pcp;
+                       } else {
+                               nextList.add(pcp);
+                               // if (DEBUG) System.out.print(" " + pcp.getResource());
+                       }
+               }
        }
-       
+
        public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
                while (true) {
                        PipeControlPoint pcp = null;
@@ -897,32 +1147,50 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                pcp = p;
                                if (prevList.size() > 0)
                                        prevList.remove(prevList.size() - 1);
-//                             if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
+                               //                              if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
                                return pcp;
                        }
                        if (pcp.isPathLegEnd()) {
-//                             if (DEBUG)      System.out.println(" " + pcp.getResource());
+                               //                              if (DEBUG)      System.out.println(" " + pcp.getResource());
                                return pcp;
                        } else {
                                prevList.add(pcp);
-//                             if (DEBUG)System.out.print(" " + pcp.getResource());
+                               //                              if (DEBUG)System.out.print(" " + pcp.getResource());
                        }
                }
        }
        
        public void _remove() {
-               if (component == null && next == null && previous == null)
-                       return;
-               if (isDualInline() || isDualSub()) {
-                       removeDualPoint();
-                       return;
+           _remove(true);
+       }
+       
+       
+       public PipeControlPoint getDualSub() {
+           if (isDualInline())
+               return getChildPoints().get(0);
+           else
+               throw new IllegalStateException("Current control point is not dual inline");
+       }
+       
+
+       public void _remove(boolean renconnect) {
+           if (disposed)
+               return;
+               
+           if (DEBUG) System.out.println(this + " Remove " + renconnect);
+
+               if (getParentPoint() != null) {
+                   getParentPoint()._remove(renconnect);
+                   return;
                }
                PipeRun pipeRun = getPipeRun();
-               if (pipeRun == null)
-                       return;
-               
+//             PipeRUn removal has been changed, so pipeRun may be null.
+//             if (pipeRun == null)
+//                     return;
+
                PipeControlPoint additionalRemove = null;
                if (!PipingRules.isEnabled()) {
+                   component = null;
                        setPrevious(null);
                        setNext(null);
                } else {
@@ -931,44 +1199,49 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        PipeControlPoint currentNext = next;
                        if (currentNext == null && currentPrev == null) {
                                removeComponent();
-                               pipeRun.remChild(this);
+                               if (pipeRun != null) {
+                                   pipeRun.remChild(this);
+                                   checkRemove(pipeRun);
+                               }
                                return;
                        }
                        if (currentNext != null && currentPrev != null) {
-                               boolean link = true;
+                               boolean link = renconnect;
                                if (currentNext.isBranchEnd()) {
                                        link = false;
-//                                     currentNext.setPrevious(null);
-//                                     currentNext.setNext(null);
                                        currentNext.remove();
                                        currentNext = null;
                                        setNext(null);
                                }
                                if (currentPrev.isBranchEnd()) {
                                        link = false;
-//                                     currentPrev.setPrevious(null);
-//                                     currentPrev.setNext(null);
                                        currentPrev.remove();
                                        currentPrev = null;
                                        setPrevious(null);
                                }
-                               if (link && currentPrev.isDirected() && currentNext.isDirected()) {
-                                       link = false;
+                               if (link) {
+                                   if (currentPrev.isDirected() && currentNext.isDirected())
+                                       link = false;
+                                   else if (this.isDualSub()) {
+                                       throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
+                                   }
                                }
                                if (currentNext == null) {
                                        // Nothing to do
                                } else if (currentNext.isDualInline()) {
                                        PipeControlPoint sccp = currentNext;
-                                       PipeControlPoint ocp = sccp.getSubPoint().get(0);
+                                       PipeControlPoint ocp = currentNext.getDualSub();
                                        if (ocp == null) {
                                                throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
                                        }
                                        if (link) {
                                                sccp.setPrevious(currentPrev);
-                                               ocp.setPrevious(currentPrev);
+                                               //ocp.setPrevious(currentPrev);
+                                               assert(ocp.getPrevious() == currentPrev);
                                        } else {
                                                sccp.setPrevious(null);
-                                               ocp.setPrevious(null);
+                                               //ocp.setPrevious(null);
+                                               assert(ocp.getPrevious() == null);
                                        }
                                        setNext(null);
                                } else if (currentNext.isDualSub()) {
@@ -980,6 +1253,16 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                                currentNext.setPrevious(null);
                                        }
                                        setNext(null);
+                               } else if (isDualInline()) {
+                                   if (currentNext.previous != getDualSub()) {
+                                       throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
+                                   }
+                                   if (link) {
+                        currentNext.setPrevious(currentPrev);
+                    } else {
+                        currentNext.setPrevious(null);
+                    }
+                    setNext(null);
                                } else {
                                        throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
                                }
@@ -989,15 +1272,17 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                        throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
                                } else if (currentPrev.isDualSub()) {
                                        PipeControlPoint ocp = currentPrev;
-                                       PipeControlPoint sccp = ocp.getParentPoint();
+                                       PipeControlPoint sccp = currentPrev.getParentPoint();
                                        if (sccp == null)
                                                throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
                                        if (link) {
-                                               ocp.setNext(currentNext);
+                                               //ocp.setNext(currentNext);
                                                sccp.setNext(currentNext);
+                                               assert(ocp.getNext() == currentNext);
                                        } else {
-                                               ocp.setNext(null);
+                                               //ocp.setNext(null);
                                                sccp.setNext(null);
+                                               assert(ocp.getNext() == null);
                                        }
                                        setPrevious(null);
                                } else if (currentPrev.next == this) {
@@ -1005,7 +1290,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                                currentPrev.setNext(currentNext);
                                        else
                                                currentPrev.setNext(null);
-                                       
+
                                        setPrevious(null);
                                } else {
                                        throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
@@ -1016,9 +1301,9 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                                additionalRemove = currentPrev;
                                                // combine lengths and set the location of remaining control point to the center.
                                                Point3d ps = new Point3d();
-                                           Point3d pe = new Point3d();
-                                           Point3d ns = new Point3d();
-                        Point3d ne = new Point3d();
+                                               Point3d pe = new Point3d();
+                                               Point3d ns = new Point3d();
+                                               Point3d ne = new Point3d();
                                                currentPrev.getInlineControlPointEnds(ps, pe);
                                                currentNext.getInlineControlPointEnds(ns, ne);
                                                double l = currentPrev.getLength() + currentNext.getLength();
@@ -1031,36 +1316,42 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                } else {
                                        // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous. 
                                }
-                       } else if (next != null) {
-                               if (next.isDualInline()) {
-                                       PipeControlPoint sccp = next;
-                                       PipeControlPoint ocp = sccp.getSubPoint().get(0);
+                       } else if (currentNext != null) {
+                               if (currentNext.isDualInline()) {
+                                       PipeControlPoint sccp = currentNext;
+                                       PipeControlPoint ocp = currentNext.getDualSub();
                                        if (ocp == null) {
                                                throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
                                        }
                                        sccp.setPrevious(null);
-                                       ocp.setPrevious(null);
-                               } else if (next.isDualSub()) {
+                                       assert(ocp.getPrevious() == null);
+                                       //ocp.setPrevious(null);
+                               } else if (currentNext.isDualSub()) {
                                        throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
-                               } else if (next.previous == this) {
-                                       next.setPrevious(null);
+                               } else if (currentNext.previous == this) {
+                                   currentNext.setPrevious(null);
+                               }  else if (isDualInline()) {
+                    if (currentNext.previous != getDualSub()) {
+                        throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
+                    }
+                    currentNext.setPrevious(null);
                                } else {
                                        throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
                                }
                                setNext(null);
                        } else {  //(previous != null)
-                               if(previous.isDualInline()) {
+                               if(currentPrev.isDualInline()) {
                                        throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
-                               } else if (previous.isDualSub()) {
-                                       PipeControlPoint ocp = previous;
-                                       PipeControlPoint sccp = ocp.getParentPoint();
+                               } else if (currentPrev.isDualSub()) {
+                                       PipeControlPoint ocp = currentPrev;
+                                       PipeControlPoint sccp = currentPrev.getParentPoint();
                                        if (sccp == null) {
                                                throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
                                        }
-                                       ocp.setNext(null);
                                        sccp.setNext(null);
-                               } else if (previous.next == this) {
-                                       previous.setNext(null);
+                                       assert(ocp.getNext() == null);
+                               } else if (currentPrev.next == this) {
+                                   currentPrev.setNext(null);
                                } else {
                                        throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
                                }
@@ -1071,97 +1362,163 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        } else if (parent!= null) {
                                removeParentPoint();
                        }
-                       
+
                }
-               
+
                removeComponent();
-               pipeRun.remChild(this);
-               checkRemove(pipeRun);
-               if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
-                       PipingRules.validate(pipeRun);
+               if (pipeRun != null) {
+               pipeRun.remChild(this);
+               checkRemove(pipeRun);
+               if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
+                       PipingRules.validate(pipeRun);
+               }
                if (additionalRemove != null)
                        additionalRemove.remove();
+               disposed = true;
        }
-       
+
+       /**
+        * Removes control point and attempts to reconnect next/prev
+        * 
+        * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
+        */
        public void remove() {
                PipeControlPoint currentPrev = previous;
                PipeControlPoint currentNext = next;
                _remove();
                try {
                        if (currentNext != null)
-                               PipingRules.requestUpdate(currentNext);
+                           if (!currentNext.checkRemove())
+                               PipingRules.requestUpdate(currentNext);
                        if (currentPrev != null)
-                               PipingRules.requestUpdate(currentPrev);
+                           if (!currentPrev.checkRemove())
+                               PipingRules.requestUpdate(currentPrev);
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
        
-       private void checkRemove(PipeRun pipeRun) {
+       
+       /**
+        * Removes control point without attempting to reconnect next/prev.
+        * This usually leads to creation of another PipeRun for the control points after this point. 
+        */
+       public void removeAndSplit() {
+        PipeControlPoint currentPrev = previous;
+        PipeControlPoint currentNext = next;
+        
+        if (next != null && previous != null) {
+            P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
+            PipeRun nextPipeRun = new PipeRun();
+            nextPipeRun.setName(root.getUniqueName("PipeRun"));
+            root.addChild(nextPipeRun);
+            
+            PipeRun previousRun = previous.getPipeRun();
+            nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
+            nextPipeRun.setPipeThickness(previousRun.getPipeThickness());
+            nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
+            
+            PipelineComponent n = next.getPipelineComponent();
+            while (n != null) {
+                if (n.getPipeRun() != previousRun)
+                    break;
+                if (! (n instanceof Nozzle)) {
+                    n.deattach();
+                    nextPipeRun.addChild(n);
+                } else
+                    n.setPipeRun(nextPipeRun);
+                n = n.getNext();
+            }
+        }
+        _remove(false);
+        try {
+            if (currentNext != null)
+                if (!currentNext.checkRemove())
+                    PipingRules.requestUpdate(currentNext);
+            if (currentPrev != null)
+                if (!currentPrev.checkRemove())
+                    PipingRules.requestUpdate(currentPrev);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+       
+       /**
+        * This is called when adjacent control point is removed.
+        * 
+        * This call should remove the give point, if the point cannot exist alone. 
+        * At the moment there is one such case: branch.
+        * 
+        * @return
+        */
+       protected boolean checkRemove() {
+           if (getParentPoint() != null) {
+               return getParentPoint().checkRemove();
+           } else {
+               if (getPipelineComponent() == null)
+                   return true; // already removed
+           if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
+               if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
+                       remove();
+                       return true;
+               }
+           }
+           return checkRemove(getPipeRun());
+           }
+    }
+
+       private boolean checkRemove(PipeRun pipeRun) {
+           if (pipeRun == null)
+               return false;
                Collection<PipeControlPoint> points = pipeRun.getControlPoints();
                if (points.size() == 0) {
                        pipeRun.remove();
+                       return true;
                } else if (points.size() == 1) {
                        PipeControlPoint pcp = points.iterator().next();
-                       if (pcp.isDeletable())
-                               pcp._remove();
-               }
-       }
-       
-       private void removeDualPoint() {
-               if (previous != null)
-                       previous.setNext(null);
-               if (next != null)
-                       next.setPrevious(null);
-               PipeControlPoint ocp;
-               PipeControlPoint sccp;
-               if (isDualInline()) {
-                       sccp = this;
-                       ocp = getSubPoint().get(0);
-               } else {
-                       ocp = this;
-                       sccp = getParentPoint();
+                       if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
+                               pcp._remove(); // This call will recursively call also this method...
+                               return true;
+                       }
+               } else if (points.size() == 2) {
+                   
                }
-               PipeRun p1 = ocp.getPipeRun();
-               PipeRun p2 = sccp.getPipeRun();
-               
-               ocp.removeComponent();
-               sccp.removeComponent();
-               
-               p1.remChild(ocp);
-               p2.remChild(sccp);
-               
-               // TODO : now we assume that this is size change, and we do
-               if (ocp.next != null)
-                       ocp.next.setPrevious(null);
-               if (ocp.previous != null)
-                       ocp.previous.setNext(null);
-               if (sccp.next != null)
-                       sccp.next.setPrevious(null);
-               if (sccp.previous != null)
-                       sccp.previous.setNext(null);
-               ocp.setNext(null);
-               ocp.setPrevious(null);
-               sccp.setNext(null);
-               sccp.setPrevious(null);
-               
-               checkRemove(p1);
-               checkRemove(p2);
+               return false;
        }
-       
+
        private void removeSubPoints() {
                for (PipeControlPoint p : children) {
-                       // TODO : this may affect delete routine, since classification of the point changes.
                        p.parent = null;
-                       p._remove();
+                       p.component = null;
+                       //p._remove();
+                       PipeControlPoint currentNext = p.getNext();
+                       PipeControlPoint currentPrev = p.getPrevious();
+                       p._setNext(null);
+                       p._setPrevious(null);
+                       PipeRun run = p.getPipeRun();
+                       if (run != null) {
+                           run.remChild(p);
+                           checkRemove(run);
+                       }
+                       if (currentNext != null)
+                if (!currentNext.checkRemove())
+                    PipingRules.requestUpdate(currentNext);
+            if (currentPrev != null)
+                if (!currentPrev.checkRemove())
+                    PipingRules.requestUpdate(currentPrev);
+            
                }
                children.clear();
        }
-       
+
        private void removeParentPoint() {
                throw new RuntimeException("Child points cannot be removed directly");
        }
        
+       public boolean isRemoved() {
+           return component == null;
+       }
+
        private void removeComponent() {
                if (component == null)
                        return;
@@ -1177,7 +1534,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        else if (next.getPrevious() == component)
                                next.setPrevious(null);
                        else if (next.getBranch0() == component)
-                           next.setBranch0(null);
+                               next.setBranch0(null);
                }
                if (prev != null) {
                        if (prev.getNext() == component)
@@ -1185,32 +1542,34 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        else if (prev.getPrevious() == component)
                                prev.setPrevious(null);
                        else if (prev.getBranch0() == component)
-                           prev.setBranch0(null);
+                               prev.setBranch0(null);
                }
                if (br0 != null) {
-                   if (br0.getNext() == component)
-                       br0.setNext(null);
-                   else if (br0.getPrevious() == component)
-                       br0.setPrevious(null);
-                   else if (br0.getBranch0() == component)
-                       br0.setBranch0(null);
+                       if (br0.getNext() == component)
+                               br0.setNext(null);
+                       else if (br0.getPrevious() == component)
+                               br0.setPrevious(null);
+                       else if (br0.getBranch0() == component)
+                               br0.setBranch0(null);
                }
                PipelineComponent comp = component;
                component = null;
-               
+
                comp.remove();
        }
-       
+
        @Override
        public void setOrientation(Quat4d orientation) {
                if (MathTools.equals(orientation, getOrientation()))
                        return;
+               if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
+                   System.out.println();
                super.setOrientation(orientation);
                if (getParentPoint() == null && component != null)
                        component._setWorldOrientation(getWorldOrientation());
                updateSubPoint();
        }
-       
+
        @Override
        public void setPosition(Vector3d position) {
                if (MathTools.equals(position, getPosition()))
@@ -1226,39 +1585,54 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        private void updateSubPoint() {
                if (isOffset()) {
                        if (next == null && previous == null) {
-                               for (PipeControlPoint sub : getSubPoint()) {
+                               for (PipeControlPoint sub : getChildPoints()) {
                                        sub.setWorldPosition(getWorldPosition());
                                        sub.setWorldOrientation(getWorldOrientation());
                                }
                                return;
                        }
-                       for (PipeControlPoint sub : getSubPoint()) {
+                       for (PipeControlPoint sub : getChildPoints()) {
                                Vector3d wp = getWorldPosition();
                                wp.add(getSizeChangeOffsetVector());
                                sub.setWorldPosition(wp);
                                sub.setWorldOrientation(getWorldOrientation());
                        }
                } else {
-                       for (PipeControlPoint sub : getSubPoint()) {
+                       for (PipeControlPoint sub : getChildPoints()) {
                                sub.setWorldPosition(getWorldPosition());
                                sub.setWorldOrientation(getWorldOrientation());
                        }
                }
        }
 
-       
+
        public void _setWorldPosition(Vector3d position) {
                Vector3d localPos = getLocalPosition(position);
                super.setPosition(localPos);
                updateSubPoint();
        }
-       
+
        public void _setWorldOrientation(Quat4d orientation) {
                Quat4d localOr = getLocalOrientation(orientation);
                super.setOrientation(localOr);
                updateSubPoint();
        }
-       
+
+       public void orientToDirection(Vector3d dir) {
+               Double angleO = getRotationAngle();
+               double angle = 0.0;
+               if (angleO != null)
+                       angle = angleO;
+               boolean reversed = _getReversed();
+               Quat4d q = null;
+               if (dir != null) {
+                   q = getControlPointOrientationQuat(dir, angle, reversed);
+               } else {
+                   q = getControlPointOrientationQuat(angle, reversed);
+               }
+               setWorldOrientation(q);
+       }
+
        @Override
        public String toString() {
                return getClass().getName() + "@" + Integer.toHexString(hashCode());