package org.simantics.plant3d.scenegraph.controlpoint; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.vecmath.Point3d; import javax.vecmath.Quat4d; import javax.vecmath.Vector3d; import org.simantics.g3d.math.MathTools; import org.simantics.plant3d.scenegraph.InlineComponent; import org.simantics.plant3d.scenegraph.P3DRootNode; import org.simantics.plant3d.scenegraph.PipeRun; import org.simantics.plant3d.scenegraph.PipelineComponent; import org.simantics.plant3d.scenegraph.TurnComponent; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; import org.simantics.plant3d.utils.ComponentUtils; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.ui.ErrorLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PipingRules { private static final Logger LOGGER = LoggerFactory.getLogger(PipingRules.class); private static final boolean DUMMY = false; private static double MIN_TURN_ANGLE = 0.001; // Threshold for removing turn components. private static double ALLOWED_OFFSET = 0.001; // Allowed offset for directed path legs private static double MIN_INLINE_LENGTH = 0.0005; // Minimum length of inline components, when component removal is not allowed. private static final int REMOVE_NONE = 0; private static final int REMOVE_START = 1; private static final int REMOVE_END = 2; private static final int REMOVE_BOTH = 3; // PathLeg iteration indicator. NEXT_S > NEXT > NONE PREV_S > PREV > NONE private enum PathLegUpdateType { NONE, // Only current path leg needs to be updated (for example, inline comp was moved) PREV, // Current and previous path leg need to be updated NEXT, // Current and next path leg need to be updated PREV_S, // Current and previous two path legs need to be updated (turn was moved, which affect other path leg end turns, and thus following path legs NEXT_S // Current and next two path legs need to be updated }; private static boolean enabled = true; // private static boolean updating = false; private static boolean allowInsertRemove = true; private static boolean triedIR = false; private static List requestUpdates = new ArrayList(); private static List currentUpdates = new ArrayList(); private static Object updateMutex = new Object(); private static Object ruleMutex = new Object(); public static void requestUpdate(PipeControlPoint pcp) { if (!PipingRules.enabled) return; if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules request " + pcp); synchronized (updateMutex) { if (!requestUpdates.contains(pcp)) requestUpdates.add(pcp); } } public static boolean update() throws Exception { if (!PipingRules.enabled) return false; List temp; synchronized(updateMutex) { if (requestUpdates.size() == 0) return false; temp = new ArrayList(requestUpdates.size()); temp.addAll(requestUpdates); requestUpdates.clear(); } synchronized (ruleMutex) { currentUpdates.clear(); currentUpdates.addAll(temp); // TODO : we should remove already processed control points from currentUpdates after each _positionUpdate call. for (PipeControlPoint pcp : currentUpdates) _positionUpdate(pcp, true); currentUpdates.clear(); } synchronized(updateMutex) { requestUpdates.removeAll(temp); } return true; } public static boolean positionUpdate(PipeControlPoint pcp) throws Exception { return positionUpdate(pcp, true); } public static boolean positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception { synchronized (ruleMutex) { currentUpdates.add(pcp); boolean b = _positionUpdate(pcp, allowIR); currentUpdates.clear(); return b; } } private static boolean _positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception { if (updating || !enabled) return true; if (pcp.getPipeRun() == null) return false; try { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules " + pcp); updating = true; allowInsertRemove = allowIR; triedIR = false; validate(pcp.getPipeRun()); if (pcp.getParentPoint() != null) pcp = pcp.getParentPoint(); if (pcp.asPathLegEnd()) { updatePathLegEndControlPoint(pcp); // FIXME: Rules won't work properly, if they are not run twice. //updatePathLegEndControlPoint(pcp); } else { updateInlineControlPoint(pcp); //updateInlineControlPoint(pcp); } validate(pcp.getPipeRun()); if (!allowInsertRemove) return !triedIR; return true; } finally { updating = false; // LOGGER.trace("PipingRules done " + pcp); } } public static void setEnabled(boolean enabled) { PipingRules.enabled = enabled; if(!enabled) { synchronized (ruleMutex) { currentUpdates.clear(); } } } public static boolean isEnabled() { return enabled; } public static class ExpandIterInfo { // these two are turn control points private PipeControlPoint start; private PipeControlPoint end; private int type; public ExpandIterInfo() { } public ExpandIterInfo(PipeControlPoint tcp, int type) { if (type == REMOVE_START) start = tcp; else end = tcp; this.type = type; } public ExpandIterInfo(PipeControlPoint start, PipeControlPoint end) { this.start = start; this.end = end; this.type = REMOVE_BOTH; } public PipeControlPoint getEnd() { return end; } public void setEnd(PipeControlPoint end) { this.end = end; } public PipeControlPoint getStart() { return start; } public void setStart(PipeControlPoint start) { this.start = start; } public int getType() { return type; } public void setType(int type) { this.type = type; } } private static void updatePathLegEndControlPoint(PipeControlPoint pcp) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updatePathLegEndControlPoint() " + pcp); if (pcp.getNext() != null) { updatePathLegNext(pcp, pcp, PathLegUpdateType.NEXT_S); } if (pcp.getPrevious() != null) { updatePathLegPrev(pcp, pcp, PathLegUpdateType.PREV_S); } } private static void updateInlineControlPoint(PipeControlPoint pcp) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateInlineControlPoint() " + pcp); PipeControlPoint start = pcp.findPreviousEnd(); updatePathLegNext(start, pcp, PathLegUpdateType.NONE); if (pcp.isOffset()) { // Adjusting the rotation angle of an offset component may change variable angle turns PipeControlPoint end = pcp.findNextEnd(); if (end.isVariableAngle()) { updatePathLegNext(end, end, PathLegUpdateType.NONE); } if (start.isVariableAngle()) { updatePathLegPrev(start, start, PathLegUpdateType.NONE); } } } private static PipeControlPoint insertElbow(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos) throws Exception{ if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos); if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) { } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) { pcp1 = pcp1.getDualSub(); } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) { PipeControlPoint t = pcp1; pcp1 = pcp2; pcp2 = t; } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getDualSub() && pcp2.getNext() == pcp1) { PipeControlPoint t = pcp1; pcp1 = pcp2.getDualSub(); pcp2 = t; } else { throw new RuntimeException(); } TurnComponent elbow = ComponentUtils.createTurn((P3DRootNode)pcp1.getRootNode()); PipeControlPoint pcp = elbow.getControlPoint(); if (pcp1.isDualInline()) pcp1 = pcp1.getDualSub(); String name = pcp1.getPipeRun().getUniqueName("Elbow"); elbow.setName(name); pcp1.getPipeRun().addChild(elbow); pcp.insert(pcp1, pcp2); pcp.setWorldPosition(pos); validate(pcp.getPipeRun()); return pcp; } private static PipeControlPoint insertStraight(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos, double length) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos); if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) { } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getDualSub()) { pcp1 = pcp1.getDualSub(); } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) { PipeControlPoint t = pcp1; pcp1 = pcp2; pcp2 = t; } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getDualSub() && pcp2.getNext() == pcp1) { PipeControlPoint t = pcp1; pcp1 = pcp2.getDualSub(); pcp2 = t; } else { throw new RuntimeException(); } InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp1.getRootNode()); PipeControlPoint scp = component.getControlPoint(); if (pcp1.isDualInline()) pcp1 = pcp1.getDualSub(); String name = pcp1.getPipeRun().getUniqueName("Pipe"); component.setName(name); pcp1.getPipeRun().addChild(component); scp.insert(pcp1, pcp2); scp.setWorldPosition(pos); Vector3d dir = new Vector3d(); dir.sub(pcp2.getWorldPosition(), pcp1.getWorldPosition()); scp.orientToDirection(dir); scp.setLength(length); validate(scp.getPipeRun()); return scp; } private static PipeControlPoint insertStraight(PipeControlPoint pcp, Direction direction , Vector3d pos, double length) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.insertStraight() " + pcp + " " + direction + " " + pos); InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode()); PipeControlPoint scp = component.getControlPoint(); if (pcp.isDualInline() && direction == Direction.NEXT) pcp = pcp.getDualSub(); String name = pcp.getPipeRun().getUniqueName("Pipe"); component.setName(name); pcp.getPipeRun().addChild(component); scp.insert(pcp,direction); scp.setWorldPosition(pos); scp.setLength(length); validate(scp.getPipeRun()); return scp; } private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { UpdateStruct2 us = createUS(start, Direction.NEXT, 0, new ArrayList(), updated); if (us == null) { LOGGER.trace("Null update struct " + start); return; } updatePathLeg(us, lengthChange); } private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { UpdateStruct2 us = createUS(start, Direction.PREVIOUS, 0, new ArrayList(), updated); if (us == null) { LOGGER.trace("Null update struct " + start); return; } updatePathLeg(us, lengthChange); } private static class UpdateStruct2 { public PipeControlPoint start; public Vector3d startPoint; public ArrayList list; public PipeControlPoint end; public Vector3d endPoint; public Vector3d dir; public Vector3d offset; public boolean hasOffsets; public int iter; public boolean reversed; public ArrayList toRemove; public PipeControlPoint updated; public UpdateStruct2(PipeControlPoint start, Vector3d startPoint, ArrayList list, PipeControlPoint end, Vector3d endPoint, Vector3d dir, Vector3d offset, boolean hasOffsets, int iter, boolean reversed, ArrayList toRemove, PipeControlPoint updated) { if (start == null || end == null) throw new NullPointerException(); this.start = start; this.startPoint = startPoint; this.list = list; this.end = end; this.endPoint = endPoint; this.dir = dir; this.offset = offset; this.hasOffsets = hasOffsets; this.iter = iter; this.reversed = reversed; this.toRemove = toRemove; this.updated = updated; if (!MathTools.isValid(startPoint) || !MathTools.isValid(endPoint) || !MathTools.isValid(dir)) { throw new RuntimeException(); } } public String toString() { return start + " " + end+ " " + dir + " " + hasOffsets + " " + offset + " " + iter + " " + toRemove.size(); } } /** * Calculate offset based on a given fixed component direction. * * The desired component direction is provided as an input to this method, * unlike the direction vector that is calculated by calculateOffset. * * The returned offset vector is always perpendicular to the given direction * vector. * * @param startPoint Start point of leg * @param endPoint End point of leg * @param start Starting component of leg * @param list Inline components between start and end * @param end Ending component of leg * @param dir Direction at which the offset is calculated * @param offset A vector object to receive the offset vector values * @return True if offsets are present */ public static boolean calculateDirectedOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList list, PipeControlPoint end, Vector3d dir, Vector3d offset) { return calculateOffset(startPoint, endPoint, start, list, end, dir, offset, true); } /** * Calculate offset and direction vectors for a path leg so that the given chain * of components starts and ends at the given coordinates * * The returned direction and offset vectors are perpendicular to each other. * * @param startPoint Start point of the leg * @param endPoint End point of the leg * @param start Starting component of the leg * @param list Inline components between start and end * @param end Ending component of the leg * @param dir A vector object to receive the component direction vector * @param offset A vector object to receive the offset vector * @return True if offsets are present */ public static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList list, PipeControlPoint end, Vector3d dir, Vector3d offset) { return calculateOffset(startPoint, endPoint, start, list, end, dir, offset, false); } private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, PipeControlPoint start, ArrayList list, PipeControlPoint end, Vector3d dir, Vector3d offset, boolean directed) { List offsets = getOffsetPoints(start, list); if (offsets.size() == 0) { setZeroOffset(startPoint, endPoint, dir, offset); return false; } else { Vector3d sp = new Vector3d(startPoint); Point3d ep = new Point3d(endPoint); if (!directed) { dir.set(ep); dir.sub(sp); } double l = dir.lengthSquared(); if (l > MathTools.NEAR_ZERO) dir.scale(1.0/Math.sqrt(l)); int iter = 100; while (true) { iter--; offset.set(0.0, 0.0, 0.0); for (PipeControlPoint icp : offsets) { Vector3d v = icp.getSizeChangeOffsetVector(dir); offset.add(v); } if (directed) break; Point3d nep = new Point3d(endPoint); nep.sub(offset); if (nep.distance(ep) < 0.0000000001 || iter <= 0) { break; } ep = nep; dir.set(ep); dir.sub(sp); l = dir.lengthSquared(); if (l > MathTools.NEAR_ZERO) dir.scale(1.0/Math.sqrt(l)); } if (LOGGER.isTraceEnabled()) LOGGER.trace("calcOffset s:"+ startPoint + " e:" + endPoint + " d:" + dir + " o:"+offset) ; return true; } } public static void setZeroOffset(Vector3d startPoint, Vector3d endPoint, Vector3d dir, Vector3d offset) { dir.set(endPoint); dir.sub(startPoint); double l = dir.lengthSquared(); if (l > MathTools.NEAR_ZERO) dir.scale(1.0/Math.sqrt(l)); offset.set(0.0, 0.0, 0.0); } public static List getOffsetPoints(PipeControlPoint start, ArrayList list) { List offsets = new ArrayList(list.size()); // Only start offset affects the calculation if (start.isOffset()) offsets.add(start); for (PipeControlPoint icp : list) { if (icp.isOffset()) { offsets.add(icp); } else if (icp.isDualSub()) ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); } return offsets; } private static UpdateStruct2 createUS(PipeControlPoint start, Direction direction, int iter, ArrayList toRemove, PipeControlPoint updated) { ArrayList list = new ArrayList(); PipeControlPoint end = null; if (direction == Direction.NEXT) { end = start.findNextEnd(list); } else { ArrayList prevList = new ArrayList(); PipeControlPoint tend = start.findPreviousEnd(prevList); for (PipeControlPoint icp : prevList) { if (icp.isDualSub()) { list.add(0, icp.getParentPoint()); } else { list.add(0, icp); } } end = start; start = tend; } if (start == end) return null; boolean hasOffsets = false; Vector3d offset = new Vector3d(); Vector3d startPoint = start.getWorldPosition(); Vector3d endPoint = end.getWorldPosition(); Vector3d dir = new Vector3d(); hasOffsets = calculateOffset(startPoint, endPoint, start, list, end, dir, offset); return new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, direction == Direction.PREVIOUS, toRemove, updated); } private static Vector3d pathLegDirection(PipeControlPoint start) { ArrayList list = new ArrayList(); PipeControlPoint end = start.findNextEnd(list); if (start == end) { return start.getDirection(Direction.NEXT); } Vector3d offset = new Vector3d(); Vector3d startPoint = start.getWorldPosition(); Vector3d endPoint = end.getWorldPosition(); Vector3d dir = new Vector3d(); calculateOffset(startPoint, endPoint, start, list, end, dir, offset); return dir; } private static boolean asDirected(PipeControlPoint pcp, Direction direction) { if (pcp.isDirected()) return true; if (pcp.asFixedAngle()) { if (!pcp._getReversed()) return direction == Direction.NEXT; else return direction == Direction.PREVIOUS; } return false; } private static Vector3d direction(PipeControlPoint pcp, Direction direction) { return pcp.getDirection(direction); } private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { boolean rs = true; boolean re = true; if (lengthChange == PathLegUpdateType.NONE) { rs = false; re = false; } updatePathLeg(u, lengthChange, rs, re); } private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean rs, boolean re) throws Exception { int directed = 0; if (asDirected(u.start, Direction.NEXT)) directed++; if (asDirected(u.end, Direction.PREVIOUS)) directed++; if (rs) setErrorForce(u.start, null); if (re) setErrorForce(u.end, null); for (PipeControlPoint pcp : u.list) setErrorForce(pcp, null); switch (directed) { case 0: updateFreePathLeg(u, lengthChange); break; case 1: updateDirectedPathLeg(u, lengthChange); break; case 2: updateDualDirectedPathLeg(u, lengthChange); break; } } private static void updateFreePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateFreePipeRun " + u + " " + lengthChange); checkExpandPathLeg(u, lengthChange); if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle()|| u.end.asFixedAngle()) processPathLeg(u, true, false); } private static void updateInlineControlPoints(UpdateStruct2 u, boolean checkSizes) throws Exception{ if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateInlineControlPoints() " + u); Vector3d start = new Vector3d(u.startPoint); Vector3d end = new Vector3d(u.endPoint); if (checkSizes) { // create offsets for leg ends. if (u.start.isTurn()) MathTools.mad(start, u.dir, u.start.getInlineLength()); if (u.end.isTurn()) MathTools.mad(end, u.dir, -u.end.getInlineLength()); } boolean recalcline = false; Vector3d sp = new Vector3d(start); Vector3d ep = new Vector3d(end); ep.sub(u.offset); if (u.start.isOffset()) { Vector3d offset = u.start.getSizeChangeOffsetVector(u.dir); updateOffsetPoint(u.start, offset); sp.add(offset); ep.add(offset); } for (PipeControlPoint icp : u.list) { updateInlineControlPoint(icp, sp, ep, u.dir); if (icp.isOffset()) { // TODO : offset vector is already calculated and should be cached Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); updateOffsetPoint(icp, offset); sp.add(offset); ep.add(offset); } } if (!checkSizes) return; // Collect all path leg points for updating variable length components. This list will also contain leg ends (usually turns) ArrayList pathLegPoints = new ArrayList<>(); // Collect all fixed length components with their offsets. ArrayList> fixedLengthPoints = new ArrayList<>(); pathLegPoints.add(u.start); fixedLengthPoints.add(new Pair(u.start, new Vector3d())); Vector3d off = new Vector3d(); for (PipeControlPoint icp : u.list) { pathLegPoints.add(icp); updateBranchControlPointBranches(icp); if (icp.isOffset()) { fixedLengthPoints.add(new Pair(icp, new Vector3d(off))); Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); off.add(offset); } else if (!icp.isVariableLength()) { fixedLengthPoints.add(new Pair(icp, new Vector3d(off))); } } pathLegPoints.add(u.end); fixedLengthPoints.add(new Pair(u.end, new Vector3d(off))); sp = new Vector3d(start); ep = new Vector3d(end); ep.sub(u.offset); updateFixedLengths(fixedLengthPoints, sp, ep, u.dir); for (int i = 0; i < pathLegPoints.size(); i++) { PipeControlPoint icp = pathLegPoints.get(i); PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null; PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null; if (prev != null && prev.isDualInline()) prev = prev.getDualSub(); if (icp.isVariableLength()) { if (prev != null && next != null) { recalcline = recalcline | updateVariableLength(icp, prev, next); } else { // this is variable length component at the end of the piperun. // the problem is that we want to keep unconnected end of the component in the same // place, but center of the component must be moved. updateVariableLengthEnd(icp, prev != null ? prev : next); } } else if (prev != null && !prev.isVariableLength()) { // If this and previous control point are not variable length pcps, // we'll have to check if there is no empty space between them. // I there is, we'll have to create new variable length component between them. recalcline = recalcline | possibleVaribleLengthInsert(icp, prev); } if (icp.isOffset()) { // TODO : offset vector is already calculated and should be cached Vector3d offset = icp.getSizeChangeOffsetVector(u.dir); sp.add(offset); ep.add(offset); } } if (recalcline) { u.list.clear(); u.start.findNextEnd(u.list); } if (checkSizes) { sp = new Vector3d(u.startPoint); ep = new Vector3d(u.endPoint); ep.sub(u.offset); double pathLegLength = MathTools.distance(sp, ep); double availableLength = pathLegLength; if (u.start.isTurn()) availableLength -= u.start.getInlineLength(); if (u.end.isTurn()) availableLength -= u.end.getInlineLength(); for (PipeControlPoint pcp : u.list) { if (!pcp.isVariableLength()) availableLength-= pcp.getLength(); } if (availableLength < 0.0) { setError(u.start, "Not enough available space"); setError(u.end, "Not enough available space"); for (PipeControlPoint pcp : u.list) setError(pcp, "Not enough available space"); } // LOGGER.trace(u.start.getPipelineComponent().toString() + " " + pathLegLength + " " + availableLength + " " + u.end.getPipelineComponent().toString() + " " + u.start.getInlineLength() + " " + u.end.getInlineLength()); } } private enum Gap{ATTACHED,OVERLAP,SPACE}; private static class GapObj { Gap gap; double d; Pair pcp1; Pair pcp2; } private static void updateFixedLengths(List> fixedLengthPoints, Vector3d s, Vector3d e, Vector3d dir) { double totalLength = MathTools.distance(s, e); double reservedLength = 0.0; List distances = new ArrayList<>(fixedLengthPoints.size()); distances.add(0.0); for (int i = 1; i < fixedLengthPoints.size()-1; i++) { Pair pcp = fixedLengthPoints.get(i); reservedLength += pcp.first.getLength(); Vector3d p = pcp.first.getWorldPosition(); p.sub(pcp.second); double d= MathTools.distance(s, p); distances.add(d); } distances.add(totalLength); if (totalLength >= reservedLength) { // There is enough space for all fixed length components. List gaps = new ArrayList<>(fixedLengthPoints.size()-1); int overlaps = 0; // Analyze gaps between components for (int i = 0; i < fixedLengthPoints.size()-1; i++) { Pair pcp1 = fixedLengthPoints.get(i); Pair pcp2 = fixedLengthPoints.get(i+1); double d1 = distances.get(i); double d2 = distances.get(i+1); double ld1 = i == 0 ? 0.0 :pcp1.first.getInlineLength(); double ld2 = i == fixedLengthPoints.size()-2 ? 0.0 : pcp2.first.getInlineLength(); double e1 = d1 + ld1; // End of comp1 double s2 = d2 - ld2; // Start of comp2 double diff =s2 - e1; GapObj obj = new GapObj(); obj.pcp1 = pcp1; obj.pcp2 = pcp2; obj.d = diff; if (diff < -MIN_INLINE_LENGTH) { obj.gap = Gap.OVERLAP; overlaps++; } else if (diff > MIN_INLINE_LENGTH) { obj.gap = Gap.SPACE; } else { obj.gap = Gap.ATTACHED; } gaps.add(obj); } // If there are no overlaps, there is nothing to do. if (overlaps == 0) return; // Get rid of overlapping components by using closest available free spaces. for (int i = 0; i < gaps.size(); i++) { GapObj gapObj = gaps.get(i); if (gapObj.gap != Gap.OVERLAP) continue; double curr = gapObj.d; int d = 1; while (d < gaps.size() && curr < -MIN_INLINE_LENGTH) { GapObj next = i+d < gaps.size() ? gaps.get(i+d) : null; GapObj prev = i-d >= 0 ? gaps.get(i-d) : null; if (next != null && next.gap == Gap.SPACE) { double move = Math.min(-curr, next.d); curr+= move; next.d -= move; if (next.d < MIN_INLINE_LENGTH) next.gap = Gap.ATTACHED; Vector3d mv = new Vector3d(dir); mv.normalize(); mv.scale(move); for (int j = i ; j < i+d; j++) { Pair pcp = gaps.get(j).pcp2; Vector3d p = new Vector3d(pcp.first.getWorldPosition()); p.add(mv); pcp.first.setWorldPosition(p); } } else if (prev != null && prev.gap == Gap.SPACE) { double move = Math.min(-curr, prev.d); curr+= move; prev.d -= move; if (prev.d < MIN_INLINE_LENGTH) prev.gap = Gap.ATTACHED; Vector3d mv = new Vector3d(dir); mv.normalize(); mv.scale(-move); for (int j = i ; j > i-d; j--) { Pair pcp = gaps.get(j).pcp1; Vector3d p = new Vector3d(pcp.first.getWorldPosition()); p.add(mv); pcp.first.setWorldPosition(p); } } else { d++; } } } } else { for (int i = 1; i < fixedLengthPoints.size()-1; i++) { Pair prev = i == 0 ? null : fixedLengthPoints.get(i-1); Pair curr = fixedLengthPoints.get(i); Pair next = i == fixedLengthPoints.size() -1 ? null : fixedLengthPoints.get(i+1); updateFixedLength(curr, prev, next, s,e, dir); } } } private static void updateFixedLength(Pair icp, Pair prev, Pair next, Vector3d s, Vector3d e, Vector3d dir) { if (prev != null) { checkOverlap(prev, icp, dir,true); } if (next != null) checkOverlap(icp, next, dir,true); } private static boolean checkOverlap(Pair icp, Pair icp2, Vector3d dir, boolean se) { Vector3d p1 = icp.first.getWorldPosition(); Vector3d p2 = icp2.first.getWorldPosition(); p1.add(icp.second); p2.add(icp2.second); double u[] = new double[1]; MathTools.closestPointOnStraight(p2, p1, dir, u); if (u[0] < 0.0) { p2.set(p1); p2.sub(icp.second); p2.add(icp2.second); MathTools.mad(p2, dir, MIN_INLINE_LENGTH); icp2.first.setWorldPosition(p2); } double d = MathTools.distance(p1, p2); double r = icp.first.getInlineLength() + icp2.first.getInlineLength(); if ((d-r) < - MIN_INLINE_LENGTH) { if (se) { setError(icp.first, "Overlapping"); setError(icp2.first, "Overlapping"); } return true; } return false; } /** * Overrides current error of a component * @param pcp * @param error */ private static void setErrorForce(PipeControlPoint pcp, String error) { PipelineComponent comp = pcp.getPipelineComponent(); if (comp == null) return; comp.setError(error); } /** * Sets error for a component, if there is no existing error. * @param pcp * @param error */ private static void setError(PipeControlPoint pcp, String error) { PipelineComponent comp = pcp.getPipelineComponent(); if (comp == null) return; if (comp.getError() != null) return; comp.setError(error); } private static boolean updateVariableLength(PipeControlPoint icp, PipeControlPoint prev, PipeControlPoint next) { Vector3d prevPos = prev.getWorldPosition(); Vector3d nextPos = next.getWorldPosition(); Vector3d dir = new Vector3d(nextPos); dir.sub(prevPos); double l = dir.length(); // distance between control points double l2prev = prev.getInlineLength(); // distance taken by components double l2next = next.getInlineLength(); double l2 = l2prev + l2next; double length = l - l2; // true length of the variable length component if (length >= MIN_INLINE_LENGTH) { // check if there is enough space for variable length component. // components fit dir.normalize(); dir.scale(length * 0.5 + l2prev); // calculate center position of the component dir.add(prevPos); icp.setWorldPosition(dir); icp.setLength(length); return false; } else { // components leave no space to the component and it must be removed if (icp.isDeletable()) { if (!allowInsertRemove) { icp.setLength(MIN_INLINE_LENGTH); setError(icp, "Not enough available space"); triedIR = true; return false; } if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateVariableLength removing " + icp); icp._remove(); return true; } else { icp.setLength(MIN_INLINE_LENGTH); icp.getPipelineComponent().setError("Not enough available space"); } return false; } } private static boolean possibleVaribleLengthInsert(PipeControlPoint icp, PipeControlPoint prev) throws Exception{ Vector3d currentPos = icp.getWorldPosition(); Vector3d prevPos = prev.getWorldPosition(); Vector3d dir = new Vector3d(currentPos); dir.sub(prevPos); double l = dir.lengthSquared(); double l2prev = prev.getInlineLength(); double l2next = icp.getInlineLength(); double l2 = l2prev + l2next; double l2s = l2 * l2; double diff = l - l2s; if (diff >= MIN_INLINE_LENGTH) { if (allowInsertRemove) { dir.normalize(); double length = Math.sqrt(l) - l2; // true length of the variable length component dir.scale(length * 0.5 + l2prev); // calculate center position of the component dir.add(prevPos); insertStraight(prev, icp, dir, length); return true; } else { triedIR = true; } } return false; } private static void updateVariableLengthEnd(PipeControlPoint icp, PipeControlPoint prev) { Vector3d currentPos = icp.getWorldPosition(); Vector3d prevPos = prev.getWorldPosition(); Vector3d dir = new Vector3d(); dir.sub(currentPos, prevPos); boolean simple; synchronized (ruleMutex) { simple = currentUpdates.contains(icp); } if (simple) { // Update based on position -> adjust length double currentLength = (dir.length() - prev.getInlineLength()) * 2.0; icp.setLength(currentLength); } else { // Update based on neighbour movement -> adjust length and position, so that free end stays in place. double currentLength = icp.getLength(); if (currentLength < MathTools.NEAR_ZERO) { currentLength = (dir.length() - prev.getInlineLength()) * 2.0; } if (dir.lengthSquared() > MathTools.NEAR_ZERO) dir.normalize(); Point3d endPos = new Point3d(dir); endPos.scale(currentLength * 0.5); endPos.add(currentPos); // this is the free end of the component double offset = prev.getInlineLength(); Point3d beginPos = new Point3d(dir); beginPos.scale(offset); beginPos.add(prevPos); // this is the connected end of the component double l = beginPos.distance(endPos); if (Double.isNaN(l)) LOGGER.debug("Length for " + icp + " is NaN"); dir.scale(l * 0.5); beginPos.add(dir); // center position if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateInlineControlPoints() setting variable length to " + l); icp.setLength(l); icp.setWorldPosition(new Vector3d(beginPos)); } } /** * Recalculates offset vector based on current direction, and calls checkExpandPathLeg * @param u * @param updateEnds * @throws Exception */ private static void ppNoOffset(UpdateStruct2 u, boolean updateEnds) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.ppNoOffset() " + u); Vector3d offset = new Vector3d(); if (u.hasOffsets) { for (PipeControlPoint icp : u.list) { if (icp.isOffset()) { offset.add(icp.getSizeChangeOffsetVector(u.dir)); } else if (icp.isDualSub()) ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); } } u.offset = offset; checkExpandPathLeg(u, PathLegUpdateType.NONE, updateEnds); } private static void ppNoDir(PipeControlPoint start, Vector3d startPoint, ArrayList list, PipeControlPoint end, Vector3d endPoint, boolean hasOffsets, int iter, boolean reversed, ArrayList toRemove, PipeControlPoint updated) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.ppNoDir() " + start + " " + end + " " + iter + " " + toRemove.size()); // FIXME : extra loop (dir should be calculated here) Vector3d dir = new Vector3d(); Vector3d offset = new Vector3d(); hasOffsets = calculateOffset(startPoint, endPoint, start, list, end, dir, offset); ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated),true); } private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { checkExpandPathLeg(u, lengthChange, u.updated.isInline() && u.updated.isOffset()); } private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean updateEnds) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.checkExpandPathLeg() " + u + " " + lengthChange); if (lengthChange != PathLegUpdateType.NONE) { // FIXME : turns cannot be checked before inline cps are updated, // since their position affects calculation of turns processPathLeg(u, updateEnds, false); int type = checkTurns(u, lengthChange); if (type == REMOVE_NONE) { processPathLeg(u, updateEnds, true); } else { expandPathLeg(u, type); } } else { processPathLeg(u, updateEnds, true); } } private static void updateDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange); PipeControlPoint dcp; PipeControlPoint other; boolean canMoveOther = false; boolean dcpStart = false; boolean inlineEnd = false; Vector3d position; if (asDirected(u.start, Direction.NEXT)) { dcp = u.start; other = u.end; position = u.startPoint; dcpStart = true; if (!u.reversed) canMoveOther = true; inlineEnd = u.end.isInline(); } else { dcp = u.end; other = u.start; position = u.endPoint; if (u.reversed) canMoveOther = true; inlineEnd = u.start.isInline(); } Vector3d directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS); if (directedDirection == null) { //updateTurnControlPointTurn(dcp, dcp.getPrevious(), dcp.getNext()); updateTurnControlPointTurn(dcp, null, null); directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS); if (directedDirection == null) { return; } } Point3d otherPosition = new Point3d(dcpStart ? u.endPoint : u.startPoint); if (u.hasOffsets) { Vector3d dir = dcp.getDirection(dcpStart ? Direction.NEXT : Direction.PREVIOUS); if (!dcpStart) dir.negate(); Vector3d offset = new Vector3d(); calculateDirectedOffset(u.startPoint, u.endPoint, u.start, u.list, u.end, dir, offset); u.dir = dir; u.offset = offset; if (dcpStart) otherPosition.sub(offset); else otherPosition.add(offset); } double mu[] = new double[2]; Vector3d closest; Vector3d t = new Vector3d(); closest = MathTools.closestPointOnStraight(otherPosition, position, directedDirection, mu); t.sub(closest, otherPosition); double distance = t.length(); boolean aligned = (distance < ALLOWED_OFFSET); double requiredSpace = 0.0; if (other.isVariableAngle()) { requiredSpace = spaceForTurn(other, dcp); } if (mu[0] < requiredSpace) { // At the moment, if next component is directly behind the nozzle, we must force moving the other component. // Trying to solve the situation by adding new turn creates infinite loop... aligned = false; canMoveOther = true; } if (aligned) { //if (u.start.isInline() || u.end.isInline() || u.start.asFixedAngle() || u.end.asFixedAngle()) // processPathLeg(u, true, false); checkExpandPathLeg(u, lengthChange, inlineEnd || u.start.isInline() || u.end.isInline() || u.start.asFixedAngle() || u.end.asFixedAngle()); } else { if (u.iter > 0) { backIter(u); } else { PipeControlPoint nextToMoved; if (u.list.size() > 0) if (dcpStart) nextToMoved = u.list.get(0); else nextToMoved = u.list.get(u.list.size() - 1); else if (dcpStart) nextToMoved = u.end; else nextToMoved = u.start; if (other.isVariableAngle()) { // TODO calculate needed space from next run end. if (mu[0] < requiredSpace) { if (dcpStart) { closest.set(u.startPoint); } else { closest.set(u.endPoint); } Vector3d v = new Vector3d(directedDirection); v.scale(requiredSpace); closest.add(v); } if (canMoveOther) { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest); // Not aligned - we need to recalculate the offset to reflect new end points. Vector3d offset; if (u.hasOffsets) { offset = new Vector3d(); Vector3d newDir = new Vector3d(); calculateDirectedOffset(position, closest, u.start, u.list, u.end, newDir, offset); closest.add(offset); } else { offset = new Vector3d(); } other.setWorldPosition(closest); if (dcpStart) { checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(closest), directedDirection, offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), PathLegUpdateType.NONE, true); if (u.end.getNext() != null) updatePathLegNext(u.end, u.updated, PathLegUpdateType.NEXT); } else { checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(closest), u.list, u.end, u.endPoint, directedDirection, offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), PathLegUpdateType.NONE, true); if (u.start.getPrevious() != null) updatePathLegPrev(u.start, u.updated, PathLegUpdateType.PREV); } } else { // TODO : calculate needed space from next run end. if (allowInsertRemove) insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); else triedIR = true; } } else if (other.isNonDirected() && other.getParentPoint() != null) { // FIXME : this code was for updating branches Vector3d bintersect = new Vector3d(); PipeControlPoint bcp = other.getParentPoint(); if (bcp != null && canMoveOther) { Point3d bstart = new Point3d(); Point3d bend = new Point3d(); Vector3d bdir = new Vector3d(); bcp.getInlineControlPointEnds(bstart, bend, bdir); Vector3d nintersect = new Vector3d(); MathTools.intersectStraightStraight(position, directedDirection, bend, bdir, nintersect, bintersect, mu); Vector3d dist = new Vector3d(nintersect); dist.sub(bintersect); canMoveOther = mu[1] > 0.0 && mu[1] < 1.0 && dist.lengthSquared() < 0.01; } else { // TODO : endControlPoints are undirected: calculcate // correct position for it throw new UnsupportedOperationException("not implemented"); } if (canMoveOther) { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect); // is required branch position is in possible range bcp.setWorldPosition(bintersect); if (dcpStart) { checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(bintersect), directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange); } else { checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(bintersect), u.list, u.end, u.endPoint, directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange); } } else { // branch cannot be moved into right position, new turn // / elbow must be inserted if (allowInsertRemove) insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); else triedIR = true; } } else { // assume that control point cannot be moved, but can // be rotated if (allowInsertRemove) insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); else triedIR = true; } } } } private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange); PipeControlPoint dcp1 = u.start; PipeControlPoint dcp2 = u.end; Point3d position1 = new Point3d(u.startPoint); Point3d position2 = new Point3d(u.endPoint); Vector3d dir = u.start.getDirection(Direction.NEXT), offset = new Vector3d(); calculateDirectedOffset(new Vector3d(position1), new Vector3d(position2), u.start, u.list, u.end, dir, offset); Point3d position1offset = new Point3d(position1); position1offset.add(offset); Point3d position2offset = new Point3d(position2); position2offset.sub(offset); Vector3d dir1 = direction(dcp1, Direction.NEXT); Vector3d dir2 = direction(dcp2, Direction.PREVIOUS); Vector3d p1 = MathTools.closestPointOnStraight(position1, position2offset, dir2); Vector3d p2 = MathTools.closestPointOnStraight(position2, position1offset, dir1); double d1 = position1.distance(new Point3d(p1)); double d2 = position2.distance(new Point3d(p2)); boolean aligned = (d1 < ALLOWED_OFFSET && d2 < ALLOWED_OFFSET); if (aligned) { processPathLeg(u); } else { if (u.iter > 0) { backIter(u); } else if (allowInsertRemove){ PipeControlPoint dcp; PipeControlPoint next; if (!u.reversed) { dcp = dcp1; if (u.list.size() > 0) next = u.list.get(0); else next = dcp2; } else { dcp = dcp2; if (u.list.size() > 0) next = u.list.get(u.list.size() - 1); else next = dcp1; } p1 = dcp.getWorldPosition(); Vector3d v = new Vector3d(); if (!u.reversed) v.set(dir1); else v.set(dir2); // Reserve space for 90 deg elbow double off = dcp1.getPipeRun().getTurnRadius(); v.scale(off); p1.add(v); if (!u.reversed) p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2offset, dir2); else p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1offset, dir1); // By default, the elbows are placed next to each other, by using 90 deg angles. // If the distance between elbows is not enough, we must move the other elbow (and create more shallow angle elbows) if (MathTools.distance(p1, p2) < off*2.05) { p2.add(v); } PipeControlPoint tcp1 = insertElbow(dcp, next, p1); PipeControlPoint tcp2 = insertElbow(tcp1, next, p2); if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDualDirectedPipeRun() created two turns " + tcp1 + " " + tcp2); if (!u.reversed) { Vector3d dd = new Vector3d(p2); dd.sub(p1); dir2.negate(); updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE); updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE); if (!u.reversed) updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE); else updatePathLegPrev(tcp2, u.updated, PathLegUpdateType.NONE); } else { Vector3d dd = new Vector3d(p1); dd.sub(p2); dir2.negate(); updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE); updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE); if (!u.reversed) updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE); else updatePathLegPrev(u.start, u.updated, PathLegUpdateType.NONE); } } else { triedIR = true; } } } private static double spaceForTurn(PipeControlPoint tcp, PipeControlPoint dcp) { // TODO : if the path legs contain offset, using just positions of opposite path leg ends is not enough. // TODO : current iterative way for calculating required space may return longer length that is required. double tr = ((TurnComponent)tcp.getPipelineComponent()).getTurnRadius(); if (dcp == null) return tr; // space for 90 deg PipeControlPoint ne = tcp.findNextEnd(); PipeControlPoint pe = tcp.findPreviousEnd(); PipeControlPoint other = null; if (dcp == ne) other = pe; else if (dcp == pe) other = ne; else return tr; // space for 90 deg if (other == null) return tr; // space for 90 deg Vector3d dir = dcp.getDirectedControlPointDirection(); Vector3d dir2; if (other == ne) { dir2 = pathLegDirection(tcp); } else { dir2 = pathLegDirection(pe); dir2.negate(); } double d = dir.dot(dir2); if (d > 0.9999) return 0.0; // point following turn is directly in the front of the nozzle. else if (d < -0.9999) return tr*2.0; // point following turn is directly behind the nozzle, in theory, we should return Double.Inf... double curr = 0.0; int iter = 10; Vector3d tp0 = tcp.getPosition(); try { Vector3d dp = dcp.getWorldPosition(); while (iter > 0) { Vector3d tp = new Vector3d(dir); tp.scaleAdd(curr, dp); tcp._setPosition(tp); // no firing of listeners here if (other == ne) { dir2 = pathLegDirection(tcp); } else { dir2 = pathLegDirection(pe); dir2.negate(); } double a = dir.angle(dir2); // other is directly between dcp and tcp, a zero angle turn should do if (Math.PI - a <= MathTools.NEAR_ZERO) return 0.0; double R = tr * Math.tan(a * 0.5); if (R <= curr) break; curr = R*1.001; iter--; } } finally { tcp._setPosition(tp0); // return the original value } return curr; } private static void insertElbowUpdate(UpdateStruct2 u, PipeControlPoint dcp, PipeControlPoint next, boolean dcpStart, Vector3d position, Vector3d directedDirection) throws Exception{ // Vector3d closest = new Vector3d(position); // closest.add(directedDirection); PipeControlPoint tcp = null; Vector3d closest = new Vector3d(directedDirection); closest.scaleAdd(dcp.getPipeRun().getTurnRadius(), position); if (dcpStart) { tcp = insertElbow(dcp, next, closest); } else { tcp = insertElbow(next, dcp, closest); } double s = spaceForTurn(tcp,dcp); Vector3d p = new Vector3d(directedDirection); p.scaleAdd(s, position); tcp.setPosition(p); closest = p; if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateDirectedPipeRun() inserted " + tcp); if (dcpStart) { // update pipe run from new turn to other end ppNoDir(tcp, new Vector3d(closest), u.list, u.end, u.endPoint, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated); // update pipe run from directed to new turn processPathLeg(new UpdateStruct2(u.start, u.startPoint, new ArrayList(), tcp, new Vector3d(closest), directedDirection, new Vector3d(), false, 0, false, new ArrayList(), u.updated)); } else { // update pipe run from other end to new turn ppNoDir(u.start, u.startPoint, u.list, tcp, new Vector3d(closest), u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated); // update pipe run from new turn to directed processPathLeg(new UpdateStruct2(tcp, new Vector3d(closest), new ArrayList(), u.end, u.endPoint, directedDirection, new Vector3d(), false, 0, false, new ArrayList(), u.updated)); } } /** * Checks if turns can be removed (turn angle near zero) */ private static int checkTurns(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.checkTurns() " + u.start + " " + u.end); boolean startRemoved = false; boolean endRemoved = false; if (u.start.isVariableAngle()) { // this won't work properly if inline control points are not updated PipeControlPoint startPrev = u.start.getPrevious(); if (startPrev != null) { double a = updateTurnControlPointTurn(u.start, null, u.dir); if (a < MIN_TURN_ANGLE && u.start.isDeletable()) startRemoved = true; else if (lengthChange == PathLegUpdateType.PREV || lengthChange == PathLegUpdateType.PREV_S) { PathLegUpdateType type; if (lengthChange == PathLegUpdateType.PREV_S) type = PathLegUpdateType.PREV; else type = PathLegUpdateType.NONE; updatePathLegPrev(u.start, u.start, type); } } } if (u.end.isVariableAngle()) { PipeControlPoint endNext = u.end.getNext(); if (endNext != null) { // TODO: u.end, u.dir, null double a = updateTurnControlPointTurn(u.end, null, null); if (a < MIN_TURN_ANGLE && u.end.isDeletable()) endRemoved = true; else if (lengthChange == PathLegUpdateType.NEXT || lengthChange == PathLegUpdateType.NEXT_S) { PathLegUpdateType type; if (lengthChange == PathLegUpdateType.NEXT_S) type = PathLegUpdateType.NEXT; else type = PathLegUpdateType.NONE; updatePathLegNext(u.end, u.end, type); } } } if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved); if (!startRemoved && !endRemoved) return REMOVE_NONE; if (startRemoved && endRemoved) return REMOVE_BOTH; if (startRemoved) return REMOVE_START; return REMOVE_END; } /** * Expands piperun search over turns that are going to be removed * */ private static void expandPathLeg(UpdateStruct2 u, int type) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.expandPipeline " + u.start + " " + u.end); ArrayList newList = new ArrayList(); switch (type) { case REMOVE_NONE: throw new RuntimeException("Error in piping rules"); case REMOVE_START: u.toRemove.add(new ExpandIterInfo(u.start, REMOVE_START)); u.start = u.start.findPreviousEnd(); u.startPoint = u.start.getPosition(); u.start.findNextEnd(newList); newList.addAll(u.list); u.list = newList; break; case REMOVE_END: u.toRemove.add(new ExpandIterInfo(u.end, REMOVE_END)); u.end = u.end.findNextEnd(newList); u.endPoint = u.end.getPosition(); u.list.addAll(newList); break; case REMOVE_BOTH: u.toRemove.add(new ExpandIterInfo(u.start, u.end)); u.start = u.start.findPreviousEnd(); u.startPoint = u.start.getPosition(); u.start.findNextEnd(newList); newList.addAll(u.list); u.list = newList; newList = new ArrayList(); u.end = u.end.findNextEnd(newList); u.endPoint = u.end.getPosition(); u.list.addAll(newList); break; default: throw new RuntimeException("Error in piping rules"); } u.offset = new Vector3d(); if (u.hasOffsets) { u.dir.normalize(); for (PipeControlPoint icp : u.list) { if (icp.isOffset()) { u.offset.add(icp.getSizeChangeOffsetVector(u.dir)); } else if (icp.isDualSub()) ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); } } if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.expandPipeline expanded " + u.start + " " + u.end); u.iter++; updatePathLeg(u, PathLegUpdateType.NONE); } /** * reverts one iteration of turn removing back) */ private static void backIter(UpdateStruct2 u) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.backIter" + u.start + " " + u.end); if (u.iter == 0) throw new RuntimeException("Error in piping rules"); ExpandIterInfo info = u.toRemove.get(u.toRemove.size() - 1); u.toRemove.remove(u.toRemove.size() - 1); if (info.getType() == REMOVE_START || info.getType() == REMOVE_BOTH) { while (u.list.size() > 0) { PipeControlPoint icp = u.list.get(0); if (icp.getPrevious().equals(info.getStart())) break; u.list.remove(icp); } u.start = info.getStart(); } if (info.getType() == REMOVE_END || info.getType() == REMOVE_BOTH) { while (u.list.size() > 0) { PipeControlPoint icp = u.list.get(u.list.size() - 1); if (icp.getNext().equals(info.getEnd())) break; u.list.remove(icp); } u.end = info.getEnd(); } u.offset = new Vector3d(); if (u.hasOffsets) { u.dir.normalize(); for (PipeControlPoint icp : u.list) { if (icp.isOffset()) { u.offset.add(icp.getSizeChangeOffsetVector(u.dir)); } else if (icp.isDualSub()) ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); } } processPathLeg(u); } /** * Processes pipe run (removes necessary turns and updates run ends) */ // private static void processPathLeg(PipeControlPoint start, Point3d // startPoint,ArrayList list, PipeControlPoint // end,Point3d endPoint, Vector3d dir,Vector3d offset, boolean // hasOffsets,int iter, boolean reversed, ArrayList // toRemove) throws TransactionException { private static void processPathLeg(UpdateStruct2 u) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.processPathLeg " + u.start + " " + u.end); processPathLeg(u, true, true); } private static void processPathLeg(UpdateStruct2 u, boolean updateEnds, boolean updateInline) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.processPathLeg " + (updateEnds ? "ends " : "") + (updateInline ? "inline " : "") + u.start + " " + u.end); if (u.toRemove.size() > 0) { for (ExpandIterInfo info : u.toRemove) { if (info.getStart() != null) { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.processPathLeg removing start " + info.getStart()); info.getStart()._remove(); } if (info.getEnd() != null) { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.processPathLeg removing end " + info.getEnd()); info.getEnd()._remove(); } } // ControlPointTools.removeControlPoint may remove more than one CP; // we must populate inline CP list again. u.list.clear(); u.start.findNextEnd( u.list); } // FIXME : inline CPs are update twice because their positions must be // updated before and after ends. updateInlineControlPoints(u, false); if (updateEnds) { if (u.start.isTurn()) { //updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext()); updateTurnControlPointTurn(u.start, null, null); // updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE); } else if (u.start.isEnd()) { updateEndComponentControlPoint(u.start, u.dir); } else if (u.start.isInline()) { u.start.orientToDirection(u.dir); } if (u.end.isTurn()) { //updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext()); updateTurnControlPointTurn(u.end, null, null); // updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE); } else if (u.end.isEnd()) { updateEndComponentControlPoint(u.end, u.dir); } else if (u.end.isInline()) { u.end.orientToDirection(u.dir); } } else { if (u.start.isEnd()) { updateEndComponentControlPoint(u.start, u.dir); } if (u.end.isEnd()) { updateEndComponentControlPoint(u.end, u.dir); } } if (updateInline) updateInlineControlPoints(u, true); } /** * Processes pipe run and recalculates offset */ // private static void processPathLeg(PipeControlPoint start, Point3d // startPoint,ArrayList list, PipeControlPoint // end,Point3d endPoint, Vector3d dir, boolean hasOffsets,int iter, boolean // reversed, ArrayList toRemove) throws TransactionException // { @SuppressWarnings("unused") private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.processPathLeg " + u.start + " " + u.end); Vector3d offset = new Vector3d(); if (u.hasOffsets) { u.dir.normalize(); for (PipeControlPoint icp : u.list) { if (icp.isOffset()) { offset.add(icp.getSizeChangeOffsetVector(u.dir)); } else if (icp.isDualSub()) { ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); } } } processPathLeg(u); } private static void updateOffsetPoint(PipeControlPoint sccp, Vector3d offset) { Vector3d world = sccp.getWorldPosition(); world.add(offset); PipeControlPoint ocp = sccp.getDualSub(); ocp.setWorldPosition(world); } /** * Updates InlineControlPoints position when straight pipe's end(s) have * been changed) * * @param pipeline * @param icp * @param nextPoint * @param prevPoint */ private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d prev, Vector3d next, Vector3d dir) { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateInlineControlPoint() " + icp); Vector3d inlinePoint = icp.getWorldPosition(); Vector3d prevPoint = new Vector3d(prev); Vector3d nextPoint = new Vector3d(next); if (!icp.isVariableLength()) { // Reserve space for fixed length components. MathTools.mad(prevPoint, dir, icp.getInlineLength()); MathTools.mad(nextPoint, dir, -icp.getInlineLength()); if (MathTools.distance(prevPoint, nextPoint) < ALLOWED_OFFSET) { prevPoint = prev; nextPoint = next; } } boolean canCalc = MathTools.distance(prevPoint, nextPoint) > ALLOWED_OFFSET; if (LOGGER.isTraceEnabled()) System.out.print("InlineControlPoint update " + icp + " " + inlinePoint + " " + prevPoint + " " + nextPoint); Vector3d newInlinePoint = null; if (canCalc) { boolean branchUpdate = false; PipeControlPoint becp = null; for (PipeControlPoint pcp : icp.getChildPoints()) if (pcp.isNonDirected()) { branchUpdate = true; becp = pcp; break; } if (DUMMY || !branchUpdate) { newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint); } else { // FIXME : can only handle one branch PipeControlPoint p = null; if (becp.getNext() != null) { p = becp.findNextEnd(); } else if (becp.getPrevious() != null) { p = becp.findPreviousEnd(); } if (p == null) { newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint); } else if (canCalc){ Vector3d branchLegEnd = p.getWorldPosition(); Vector3d dir2 = new Vector3d(inlinePoint); dir2.sub(branchLegEnd); Vector3d dir1 = new Vector3d(nextPoint); dir1.sub(prevPoint); newInlinePoint = new Vector3d(); double mu[] = new double[2]; MathTools.intersectStraightStraight(new Vector3d(prevPoint), dir1, new Vector3d(branchLegEnd), dir2, newInlinePoint, new Vector3d(), mu); if (LOGGER.isTraceEnabled()) LOGGER.trace(Double.toString(mu[0])); // FIXME : reserve space if (mu[0] < 0.0) { newInlinePoint = new Vector3d(prevPoint); } else if (mu[0] > 1.0) { newInlinePoint = new Vector3d(nextPoint); } } } } else { // prevPoint == nextPoint newInlinePoint = new Vector3d(prevPoint); } if (LOGGER.isTraceEnabled()) LOGGER.trace(" " + newInlinePoint); icp.setWorldPosition(newInlinePoint); icp.orientToDirection(dir); } /** * Updates InlineControlPoints position when straight pipe's end(s) have * been changed) * * @param pipeline * @param icp * @param nextPoint * @param prevPoint */ private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d dir) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateEndComponentControlPoint() " + ecp); if (!ecp.isFixed()) // prevent overriding nozzle orientations.. ecp.orientToDirection(dir); for (PipeControlPoint pcp : ecp.getChildPoints()) { // TODO update position updatePathLegEndControlPoint(pcp); } } /** * Updates all branches when branch's position has been changed * * @param bcp */ private static void updateBranchControlPointBranches(PipeControlPoint bcp) throws Exception { if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingRules.updateBranchControlPointBranches() " + bcp); if (bcp.isDualInline()) return; Collection branches = bcp.getChildPoints(); if (branches.size() == 0) { if (LOGGER.isTraceEnabled()) LOGGER.trace("No Branches found"); return; } for (PipeControlPoint pcp : branches) { updatePathLegEndControlPoint(pcp); } } private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d prev, Vector3d next) { if (next == null) { UpdateStruct2 us = createUS(tcp, Direction.NEXT, 0, new ArrayList(), tcp); if (us != null) next = us.dir; } if (prev == null) { UpdateStruct2 us = createUS(tcp, Direction.PREVIOUS, 0, new ArrayList(), tcp); if (us != null) { prev = us.dir; } } if (!tcp.asFixedAngle()) { if (next == null || prev == null) { if (tcp.getTurnAngle() != null) return tcp.getTurnAngle(); return Math.PI; // FIXME : argh } final boolean isDegenerate = prev.lengthSquared() < MathTools.NEAR_ZERO || next.lengthSquared() < MathTools.NEAR_ZERO; double turnAngle = isDegenerate ? 0.0 : prev.angle(next); Vector3d turnAxis = new Vector3d(); turnAxis.cross(prev, next); if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) { double elbowRadius = ((TurnComponent)tcp.getPipelineComponent()).getTurnRadius(); double R = elbowRadius * Math.tan(turnAngle * 0.5); turnAxis.normalize(); tcp.setTurnAngle(turnAngle); tcp.setLength(R);// setComponentOffsetValue(R); tcp.setTurnAxis(turnAxis); // tcp.setPosition(tcp.getPosition()); } else { turnAngle = 0.0; tcp.setTurnAngle(0.0); tcp.setLength(0.0); tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS)); } tcp.orientToDirection(prev); if (LOGGER.isTraceEnabled()) LOGGER.trace("PipingTools.updateTurnControlPointTurn " + prev + " " + next + " " + turnAngle + " " + turnAxis); return turnAngle; } else { if (prev != null && next != null) { // Nothing to do } else if (prev == null) { if (!tcp._getReversed()) tcp.setReversed(true); } else if (next == null) { if (tcp._getReversed()) tcp.setReversed(false); } Vector3d dir = null; if (!tcp._getReversed()) { dir = prev; } else { dir = next; dir.negate(); } if (dir == null) { return Math.PI; // FIXME : argh } Quat4d q = tcp.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0); Vector3d v = new Vector3d(); MathTools.rotate(q, MathTools.Y_AXIS,v); tcp.setTurnAxis(v); tcp.setWorldOrientation(q); if (tcp.getTurnAngle() != null) return tcp.getTurnAngle(); return Math.PI; // FIXME : argh } } public static List getControlPoints(PipeRun pipeRun) { List list = new ArrayList(); if (pipeRun.getControlPoints().size() == 0) return list; PipeControlPoint pcp = pipeRun.getControlPoints().iterator().next(); while (pcp.getPrevious() != null) { PipeControlPoint prev = pcp.getPrevious(); if (prev.getPipeRun() != pipeRun && prev.getPipeRun() != null) { // bypass possible corruption break; } pcp = prev; } if (pcp.isDualSub()) { pcp = pcp.getParentPoint(); } list.add(pcp); while (pcp.getNext() != null) { pcp = pcp.getNext(); if (pcp.getPipeRun() != pipeRun) break; list.add(pcp); } return list; } public static void reverse(PipeRun pipeRun) { while (true) { List points = getControlPoints(pipeRun); PipeControlPoint pcp = points.get(0); if (pcp.isSizeChange() && pcp.getChildPoints().size() > 0) { PipeRun pr = pcp.getPipeRun(); if (pr != pipeRun) pipeRun = pr; else break; } else { break; } } List all = new ArrayList(); List> pcps = new ArrayList>(); while (true) { all.add(pipeRun); List points = getControlPoints(pipeRun); pcps.add(points); PipeControlPoint pcp = points.get(points.size()-1); if (pcp.getChildPoints().size() > 0) { PipeRun pipeRun2 = pcp.getChildPoints().get(0).getPipeRun(); if (pipeRun == pipeRun2) break; else pipeRun = pipeRun2; } else { break; } } for (int i = 0 ; i < all.size(); i++) { List list = pcps.get(i); _reverse(list); } for (int i = 0 ; i < all.size(); i++) { boolean last = i == all.size() - 1; List list = pcps.get(i); if (!last) { List list2 = pcps.get(i+1); PipeControlPoint prev = list.get(list.size()-1); PipeControlPoint next = list2.get(0); if (prev == next) { // Reverse the component on the boundary. InlineComponent ic = (InlineComponent)prev.getPipelineComponent(); PipeRun r1 = ic.getPipeRun(); PipeRun r2 = ic.getAlternativePipeRun(); if (r1 == null || r2 == null) throw new RuntimeException("Components on PipeRun changes should refer to bot PipeRuns"); ic.deattach(); r2.addChild(ic); ic.setPipeRun(r2); ic.setAlternativePipeRun(r1); } else { throw new RuntimeException("PipeRun changes should contain shared control points"); } } } } private static void _reverse(List list) { if (list.size() <= 1) return; // nothing to do. for (int i = 0 ; i < list.size(); i++) { boolean first = i == 0; boolean last = i == list.size() - 1; PipeControlPoint current = list.get(i); PipeControlPoint currentSub = null; if (current.isDualInline()) currentSub = current.getDualSub(); if (first) { PipeControlPoint next = list.get(i+1); if (next.isDualInline()) next = next.getDualSub(); if (current.getNext() == next) current.setNext(null); current.setPrevious(next); if (currentSub != null) { if (currentSub.getNext() == next) currentSub.setNext(null); currentSub.setPrevious(next); } } else if (last) { PipeControlPoint prev = list.get(i-1); if (current.getPrevious() == prev) current.setPrevious(null); current.setNext(prev); if (currentSub != null) { if (currentSub.getPrevious() == prev) currentSub.setPrevious(null); currentSub.setNext(prev); } } else { PipeControlPoint prev = list.get(i-1); PipeControlPoint next = list.get(i+1); if (next.isDualInline()) next = next.getDualSub(); current.setPrevious(next); current.setNext(prev); if (currentSub != null) { currentSub.setPrevious(next); currentSub.setNext(prev); } } //if (current.isTurn() && current.isFixed()) { if (current.asFixedAngle()) { current.setReversed(!current._getReversed()); } if (current.isInline() && current.isReverse()) { current.setReversed(!current._getReversed()); } } } public static void validate(PipeRun pipeRun) { if (pipeRun == null) return; synchronized (ruleMutex) { Collection pcps = pipeRun.getControlPoints(); int count = 0; //LOGGER.trace("Validate " + pipeRun.getName()); for (PipeControlPoint pcp : pcps) { if (pcp.getParentPoint() == null || pcp.getParentPoint().getPipeRun() != pipeRun) count++; } List runPcps = getControlPoints(pipeRun); if (runPcps.size() != count) { LOGGER.debug("Run " + pipeRun.getName() + " contains unconnected control points, found " + runPcps.size() + " connected, " + pcps.size() + " total."); for (PipeControlPoint pcp : pcps) { if (!runPcps.contains(pcp)) { LOGGER.debug("Unconnected " + pcp + " " + pcp.getPipelineComponent()); } } } for (PipeControlPoint pcp : pcps) { if (pcp.getPipeRun() == null) { LOGGER.debug("PipeRun ref missing " + pcp + " " + pcp.getPipelineComponent()); } if (!pcp.isDirected() && pcp.getNext() == null && pcp.getPrevious() == null) LOGGER.debug("Orphan undirected " + pcp + " " + pcp.getPipelineComponent()); } for (PipeControlPoint pcp : pcps) { if (pcp.getParentPoint() == null) { PipeControlPoint sub = null; if (pcp.isDualInline()) sub = pcp.getDualSub(); PipeControlPoint next = pcp.getNext(); PipeControlPoint prev = pcp.getPrevious(); if (next != null) { if (!(next.getPrevious() == pcp || next.getPrevious() == sub)) { LOGGER.debug("Inconsistency between " + pcp + " -> " +next ); } } if (prev != null) { PipeControlPoint prevParent = null; if (prev.isDualSub()) { prevParent = prev.getParentPoint(); } else if (prev.isDualInline()) { LOGGER.debug("Inconsistency between " + pcp + " <-- " +prev ); } if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) { LOGGER.debug("Inconsistency between " + pcp + " <-- " +prev ); } } } } } } public static void splitVariableLengthComponent(PipelineComponent newComponent, InlineComponent splittingComponent, boolean assignPos) throws Exception{ assert(!splittingComponent.getControlPoint().isFixedLength()); assert(!(newComponent instanceof InlineComponent && !newComponent.getControlPoint().isFixedLength())); PipeControlPoint newCP = newComponent.getControlPoint(); PipeControlPoint splittingCP = splittingComponent.getControlPoint(); PipeControlPoint nextCP = splittingCP.getNext(); PipeControlPoint prevCP = splittingCP.getPrevious(); /* there are many different cases to insert new component when it splits existing VariableLengthinlineComponent. 1. VariableLengthComponet is connected from both sides: - insert new component between VariableLength component and component connected to it - insert new VariableLengthComponent between inserted component and component selected in previous step 2. VariableLengthComponent is connected from one side - Use previous case or: - Insert new component to empty end - Insert new VariableLength component to inserted components empty end 3. VariableLength is not connected to any component. - Should not be possible, at least in current implementation. - Could be done using second case */ if (nextCP == null && prevCP == null) { // this should not be possible throw new RuntimeException("VariableLengthComponent " + splittingComponent + " is not connected to anything."); } double newLength = newComponent.getControlPoint().getLength(); Point3d next = new Point3d(); Point3d prev = new Point3d(); splittingCP.getInlineControlPointEnds(prev, next); Vector3d newPos = null; if (assignPos) { newPos = new Vector3d(prev); Vector3d dir = new Vector3d(next); dir.sub(prev); dir.scale(0.5); newPos.add(dir); newComponent.setWorldPosition(newPos); } else { newPos = newComponent.getWorldPosition(); } Vector3d dir = new Vector3d(next); dir.sub(prev); dir.normalize(); dir.scale(newLength * 0.5); Point3d vn = new Point3d(newPos); Point3d vp = new Point3d(newPos); vn.add(dir); vp.sub(dir); double ln = vn.distance(next); double lp = vp.distance(prev); vp.interpolate(prev, 0.5); vn.interpolate(next, 0.5); if (nextCP == null) { newCP.insert(splittingCP, Direction.NEXT); insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln); splittingCP.setWorldPosition(new Vector3d(vp)); // ControlPointTools.setWorldPosition(splittingCP, vp); // splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp); } else if (prevCP == null) { newCP.insert(splittingCP, Direction.PREVIOUS); insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp); splittingCP.setWorldPosition(new Vector3d(vn)); // splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, ln); } else { newCP.insert(splittingCP, nextCP); insertStraight(newCP, nextCP, new Vector3d(vn), ln); splittingCP.setWorldPosition(new Vector3d(vp)); // splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp); } positionUpdate(newCP); } public static void addSizeChange(boolean reversed, PipeRun pipeRun, PipeRun other, InlineComponent reducer, PipeControlPoint previous, PipeControlPoint next) { PipeControlPoint pcp = reducer.getControlPoint(); PipeControlPoint ocp = pcp.getDualSub(); if (!reversed) { String name = pipeRun.getUniqueName("Reducer"); reducer.setName(name); pipeRun.addChild(reducer); other.addChild(ocp); reducer.setAlternativePipeRun(other); previous.setNext(pcp); pcp.setPrevious(previous); ocp.setPrevious(previous); if (next != null) { pcp.setNext(next); ocp.setNext(next); next.setPrevious(ocp); } } else { String name = other.getUniqueName("Reducer"); reducer.setName(name); other.addChild(reducer); pipeRun.addChild(ocp); reducer.setAlternativePipeRun(pipeRun); if (next != null) { next.setNext(pcp); pcp.setPrevious(next); ocp.setPrevious(next); } pcp.setNext(previous); ocp.setNext(previous); previous.setPrevious(ocp); } } }