]> 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 93186779e60d580aa31d23a95585ce4886ff9f7b..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;
 
@@ -33,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)
@@ -83,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() {
@@ -134,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();
        }
@@ -146,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();
@@ -197,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)
@@ -231,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;
        }
 
@@ -260,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")
@@ -288,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")
@@ -308,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");
        }
@@ -325,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");
        }
@@ -335,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(this.rotationAngle, rotationAngle))
+                       return;
                this.rotationAngle = rotationAngle;
                firePropertyChanged("rotationAngle");
        }
 
        public void setReversed(Boolean reversed) {
+               if (this.reversed == reversed)
+                       return;
                this.reversed = reversed;
                firePropertyChanged("reversed");
        }
@@ -394,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);
@@ -459,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();
@@ -492,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) {
@@ -509,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()) {
@@ -534,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 
@@ -552,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);
@@ -583,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())
@@ -594,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) {
@@ -604,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
@@ -623,10 +787,24 @@ 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() && isFixed()) {
+        if (isTurn() && asFixedAngle()) {
             if (direction == Direction.NEXT) {
                 if (previous != null) {
                     PipeControlPoint pcp = this;
@@ -643,6 +821,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 {
@@ -661,6 +840,8 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                     Vector3d offset = new Vector3d();
                     MathTools.rotate(q2, v, offset);
                     MathTools.rotate(q, offset, dir);
+                    dir.negate();
+                    dir.normalize();
                     return dir;
                 }
             }
@@ -668,96 +849,90 @@ 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);
-                               }
-                               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();
+                       return getPathLegDirectionNext();
+               } else {
+                       return getPathLegDirectionPrevious();
+               }
+       }
 
-                               } 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");
-                               }
-                       }
+       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 {
-                       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");
-                               }
+                       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 : 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);
@@ -766,17 +941,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);
                }
@@ -786,22 +963,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);
@@ -815,7 +1004,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);
@@ -824,6 +1013,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;
@@ -832,21 +1037,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;
@@ -938,20 +1159,38 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                        }
                }
        }
-
+       
        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 {
@@ -960,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()) {
@@ -1009,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");
                                }
@@ -1018,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) {
@@ -1060,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");
                                }
@@ -1104,89 +1366,147 @@ 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;
+        
+        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 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();
        }
@@ -1194,6 +1514,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)
@@ -1238,6 +1562,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());
@@ -1255,24 +1581,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());
                        }
@@ -1292,6 +1618,21 @@ public class PipeControlPoint extends G3DNode implements IP3DNode {
                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());