]> 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 daab2d111b234d2da42a16f78671a543a9f23607..0c7e3b26ed4935f297f94c12ab7386a01faaf488 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,6 +16,7 @@ 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;
@@ -35,13 +37,16 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        private PipelineComponent component;
 
        private PointType type;
-       private boolean isFixed = true;
-       private boolean isRotate = false;
-       private boolean isReverse = false;
-       private boolean isDeletable = true;
-       private boolean isSizeChange = false;
-       private boolean isSub = 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)
@@ -85,10 +90,18 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                return isFixed;
        }
 
-
        public void setFixed(boolean 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() {
@@ -136,11 +149,26 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        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 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 !isFixed && isEnd();
        }
@@ -148,10 +176,35 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        public boolean isVariableLength() {
                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 !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 isDeletable && isEnd();
@@ -199,32 +252,70 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        }
 
        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 || 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 || isSub)
@@ -233,13 +324,40 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                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;
        }
 
@@ -262,15 +380,16 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        }
 
        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")
@@ -290,7 +409,9 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
 
        @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")
@@ -310,6 +431,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                }
                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");
        }
@@ -327,6 +450,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                }
                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");
        }
@@ -337,11 +462,15 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                }
                if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO)
                        return;
+               if (Objects.equals(rotationAngle, rotationAngle))
+                       return;
                this.rotationAngle = rotationAngle;
                firePropertyChanged("rotationAngle");
        }
 
        public void setReversed(Boolean reversed) {
+               if (this.reversed == reversed)
+                       return;
                this.reversed = reversed;
                firePropertyChanged("reversed");
        }
@@ -396,63 +525,98 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                return type.name();
        }
 
-       public Quat4d getControlPointOrientationQuat(double angle) {
+       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) {
-                       Vector3d dir = getPathLegDirection(Direction.NEXT);
-                       if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                               dir.normalize();
-                       return getControlPointOrientationQuat(dir, angle);
+                       return getPathLegDirection(Direction.NEXT);
                } else {
                        Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
-                       dir.negate();
-                       if (dir.lengthSquared() > MathTools.NEAR_ZERO)
-                               dir.normalize();
-                       return getControlPointOrientationQuat(dir, turnAxis, angle);
+                       if (dir != null) dir.negate();
+                       return dir;
                }
        }
-
-       public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
-
+       
+       public Quat4d getControlPointOrientationQuat(double angle) {
+               Vector3d dir = getPathLegDirection();
                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;
+                       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(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);
+        }
+       }
 
+       public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
+               Vector3d dir = getPathLegDirection();
+               return getControlPointOrientationQuat(dir, angle, reversed);
+       }
 
-
-       public static Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
-               if (dir.lengthSquared() < MathTools.NEAR_ZERO)
+       public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
+               if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
                        return MathTools.getIdentityQuat();
 
-
-               Vector3d up = new Vector3d(0.0, 1.0, 0.0);
-               double a = up.angle(dir);
+               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) {
-                       up.set(1.0, 0.0, 0.0);
+                       // 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.lengthSquared() < MathTools.NEAR_ZERO)
+               if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
                        return MathTools.getIdentityQuat();
 
                final Vector3d front = new Vector3d(1.0,0.0,0.0);
@@ -461,7 +625,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
 
 
                Vector3d right = new Vector3d();
-
+               
+               up = new Vector3d(up);
                right.cross(dir, up);
                up.cross(right, dir);
                right.normalize();
@@ -494,8 +659,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                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) {
@@ -511,7 +676,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
 
                PipeControlPoint offsetCP = null;
                if (isOffset()) {
-                       offsetCP = getSubPoint().get(0);
+                       offsetCP = getDualSub();
                }
                if (previousNext != null && previousNext == next) {
                        if (previous.isDualInline()) {
@@ -536,7 +701,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        }
 
                        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 
@@ -554,7 +719,7 @@ 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);
@@ -585,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())
@@ -596,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) {
@@ -606,7 +768,7 @@ 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
@@ -625,10 +787,18 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                return dir;
        }
        
+       /**
+        * Returns direction vector. 
+        * 
+        * For directed control points, always returns outwards pointing vector.
+        * 
+        * @param direction
+        * @return normalized vector, or null
+        */
        public Vector3d getDirection(Direction direction) {
         if (isDirected())
             return getDirectedControlPointDirection();
-        if (isTurn() && isFixed()) {
+        if (isTurn() && asFixedAngle()) {
             if (direction == Direction.NEXT) {
                 if (previous != null) {
                     PipeControlPoint pcp = this;
@@ -645,6 +815,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                     Vector3d offset = new Vector3d();
                     MathTools.rotate(q2, v, offset);
                     MathTools.rotate(q, offset, dir);
+                    dir.normalize();
                     return dir;
                 }
             } else {
@@ -663,6 +834,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                     Vector3d offset = new Vector3d();
                     MathTools.rotate(q2, v, offset);
                     MathTools.rotate(q, offset, dir);
+                    dir.normalize();
                     return dir;
                 }
             }
@@ -670,25 +842,37 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
         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);
+                                       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 (previous == null) {
                                        if (!isDirected())
-                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point");
+                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
                                        return getDirectedControlPointDirection();
 
                                } else {
-                                       if (isVariableAngle())
-                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point");
+                                       if (isVariableAngle() && !asFixedAngle())
+                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
                                        if (isInline()) {
                                                PipeControlPoint pcp = this;
                                                if (pcp.isDualSub()) {
@@ -696,17 +880,25 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                                }
                                                Vector3d v = new Vector3d();
                                                v.sub(pcp.getWorldPosition(),previous.getWorldPosition());
+                                               if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                               v.normalize();
+                                               else
+                                                   return null;
                                                return v;
                                        } else if (isDirected()) {
                                                return getDirectedControlPointDirection();
                                        } else if (isEnd()) {
                                                Vector3d v = new Vector3d();
                                                v.sub(getWorldPosition(),previous.getWorldPosition());
+                                               if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                            v.normalize();
+                        else
+                            return null;
                                                return v;
-                                       } else if (isTurn() && isFixed() && !_getReversed()) {
+                                       } else if (isTurn() && asFixedAngle() && !_getReversed()) {
                                                return getDirection(Direction.NEXT);
                                        }
-                                       throw new RuntimeException("Missing implementation");
+                                       throw new RuntimeException("Missing implementation " + this);
                                }
                        }
                } else {
@@ -716,24 +908,32 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                        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 (next == null)  {
                                        if (!isDirected())
-                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point");
+                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
                                        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 (isVariableAngle() && !asFixedAngle())
+                                               throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
                                        if (isInline()) {
                                                PipeControlPoint pcp = this;
                                                if (pcp.isDualInline()) {
-                                                       pcp = pcp.getSubPoint().get(0);
+                                                       pcp = pcp.getDualSub();
                                                }
                                                Vector3d v = new Vector3d();
                                                v.sub(pcp.getWorldPosition(),next.getWorldPosition());
+                                               if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                            v.normalize();
+                        else
+                            return null;
                                                return v;
                                        } else if (isDirected()) {
                                                Vector3d v = getDirectedControlPointDirection();
@@ -742,11 +942,15 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                                        } else if (isEnd()) {
                                                Vector3d v = new Vector3d();
                                                v.sub(getWorldPosition(),next.getWorldPosition());
+                                               if (v.lengthSquared() > MathTools.NEAR_ZERO)
+                            v.normalize();
+                        else
+                            return null;
                                                return v;
-                                       } else if (isTurn() && isFixed() && _getReversed()) {
+                                       } else if (isTurn() && asFixedAngle() && _getReversed()) {
                                                return getDirection(Direction.PREVIOUS);
                                        }
-                                       throw new RuntimeException("Missing implementation");
+                                       throw new RuntimeException("Missing implementation " + this);
                                }
                        }
                }
@@ -755,11 +959,10 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
                assert (isInline());
 
-               PipeControlPoint sub = isAxial() ? this : getSubPoint().get(0);
+               PipeControlPoint sub = isAxial() ? this : getDualSub();
                Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
-               Vector3d dir = sub.getPathLegDirection(Direction.NEXT);
+               Vector3d dir = sub.getInlineDir();
                
-               dir.normalize();
                dir.scale(length * 0.5);
                p1.set(pos);
                p2.set(pos2);
@@ -768,17 +971,19 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        }
 
        public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
-               PipeControlPoint sub = isAxial() ? this : getSubPoint().get(0);
+               PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
                Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
                
-               Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
-               dir1.normalize();
-               Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
-               dir2.normalize();
+               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);
                }
@@ -788,22 +993,34 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                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 : getSubPoint().get(0);
+               PipeControlPoint sub = isAxial() ? this : getDualSub();
                
                Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
-               dir1.normalize();
                Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
-               dir2.normalize();
-               v1.set(dir1);
-               v2.set(dir2);
+               
+               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);
@@ -817,7 +1034,7 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
 
                Vector3d pos = getWorldPosition();
                center.set(pos);
-               dir.set(getPathLegDirection(Direction.NEXT));
+               dir.set(getInlineDir());
                dir.normalize();
                dir.scale(length * 0.5);
                p1.set(pos);
@@ -826,6 +1043,22 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                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;
@@ -834,21 +1067,37 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                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;
@@ -944,20 +1193,34 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        public void _remove() {
            _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 (component == null && next == null && previous == null)
-                       return;
-               if (isDualInline() || isDualSub()) {
-                       removeDualPoint();
-                       return;
+           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 {
@@ -966,44 +1229,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 = 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()) {
@@ -1015,6 +1283,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");
                                }
@@ -1024,15 +1302,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) {
@@ -1066,36 +1346,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");
                                }
@@ -1110,28 +1396,43 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                }
 
                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();
                }
        }
        
+       
+       /**
+        * 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;
@@ -1144,10 +1445,13 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
             
             PipeRun previousRun = previous.getPipeRun();
             nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
-            nextPipeRun.setTurnRadius(previousRun.getTurnRadius());
+            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);
@@ -1159,75 +1463,80 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
         _remove(false);
         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();
         }
     }
+       
+       /**
+        * 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 void checkRemove(PipeRun pipeRun) {
+       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();
-
-               if (p1 != null)
-                       p1.remChild(ocp);
-               if (p2 != null)
-                       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);
-
-               if (p1 != null)
-                       checkRemove(p1);
-               if (p2 != null)
-                       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();
        }
@@ -1235,6 +1544,10 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        private void removeParentPoint() {
                throw new RuntimeException("Child points cannot be removed directly");
        }
+       
+       public boolean isRemoved() {
+           return component == null;
+       }
 
        private void removeComponent() {
                if (component == null)
@@ -1279,6 +1592,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
        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());
@@ -1296,24 +1611,24 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        component._setWorldPosition(getWorldPosition());
                updateSubPoint();
        }
-
+       
        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());
                        }