package org.simantics.plant3d.utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.vecmath.Vector3d; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.g3d.math.MathTools; import org.simantics.g3d.scenegraph.GeometryProvider; import org.simantics.g3d.scenegraph.ParametricGeometryProvider; import org.simantics.layer0.Layer0; import org.simantics.plant3d.geometry.ParameterRead; import org.simantics.plant3d.ontology.Plant3D; import org.simantics.plant3d.scenegraph.EndComponent; import org.simantics.plant3d.scenegraph.Equipment; import org.simantics.plant3d.scenegraph.InlineComponent; import org.simantics.plant3d.scenegraph.Nozzle; import org.simantics.plant3d.scenegraph.P3DRootNode; import org.simantics.plant3d.scenegraph.PipeRun; import org.simantics.plant3d.scenegraph.PipelineComponent; import org.simantics.plant3d.scenegraph.TurnComponent; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; public class ComponentUtils { private final static Logger LOGGER = LoggerFactory.getLogger(ComponentUtils.class); private static Map> clazzes = new HashMap>(); private static Map providers = new HashMap(); private static Map names = new HashMap(); public static void preloadCache(RequestProcessor session) { try { session.syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { List types = new ArrayList(); types.add(Plant3D.URIs.Builtin_Straight); types.add(Plant3D.URIs.Builtin_Elbow); types.add(Plant3D.URIs.Builtin_ConcentricReducer); types.add(Plant3D.URIs.Builtin_BranchSplitComponent); types.add(Plant3D.URIs.Builtin_EccentricReducer); types.add(Plant3D.URIs.Builtin_Elbow45); types.add(Plant3D.URIs.Builtin_Elbow90); for (String typeURI : types) { load(graph, typeURI); } } }); } catch (DatabaseException e) { LOGGER.error("ComponentUtils.preloadCache() failed unexpectedly", e); } } private static GeometryProvider getProvider(ReadGraph graph, Resource type) throws DatabaseException { Layer0 l0 = Layer0.getInstance(graph); Plant3D p3d = Plant3D.getInstance(graph); Resource geom = graph.getPossibleObject(type,p3d.hasGeometry); if (geom == null) { for (Resource a : graph.getObjects(type, l0.Asserts)) { if (p3d.hasGeometry.equals(graph.getPossibleObject(a, l0.HasPredicate))) { geom = graph.getPossibleObject(a, l0.HasObject); break; } } } if (geom != null) { GeometryProvider provider = graph.adapt(geom, GeometryProvider.class); if (provider instanceof ParametricGeometryProvider) { Map params = graph.syncRequest(new ParameterRead(type)); if (params.size() > 0) ((ParametricGeometryProvider)provider).setProperties(params); } return provider; } return null; } private static Class getClazz(ReadGraph graph, Resource type) throws DatabaseException { Plant3D p3d = Plant3D.getInstance(graph); if (graph.isInheritedFrom(type, p3d.InlineComponent)) return InlineComponent.class; if (graph.isInheritedFrom(type, p3d.TurnComponent)) return TurnComponent.class; if (graph.isInheritedFrom(type, p3d.EndComponent)) return EndComponent.class; if (graph.isInheritedFrom(type, p3d.Nozzle)) return Nozzle.class; return null; } private static void load(ReadGraph graph, String typeURI) throws DatabaseException { Plant3D p3d = Plant3D.getInstance(graph); Resource type = graph.getResource(typeURI); GeometryProvider provider = getProvider(graph, type); if (provider != null || graph.hasStatement(type,p3d.NonVisibleComponent)) { providers.put(typeURI, provider); if (graph.isInheritedFrom(type, p3d.PipelineComponent)) clazzes.put(typeURI,getClazz(graph, type)); names.put(typeURI, NameUtils.getSafeName(graph, type)); return; } throw new DatabaseException("Cannot find component for " + typeURI); } private static void load(final String typeURI) throws DatabaseException { Simantics.getSession().syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { load(graph,typeURI); } }); } /** * Creates a component * * Does not set the name or add the component to a piperun. * @param root * @param typeURI * @return * @throws Exception */ public static PipelineComponent createComponent(P3DRootNode root, String typeURI) throws Exception { Class type = clazzes.get(typeURI); GeometryProvider provider = providers.get(typeURI); if (type == null || provider == null) { load(typeURI); type = clazzes.get(typeURI); provider = providers.get(typeURI); } //PipelineComponent component = type.newInstance(); PipelineComponent component = null; if (type == InlineComponent.class) { component = root.createInline(); } else if (type == TurnComponent.class) { component = root.createTurn(); } else if (type == EndComponent.class) { component = root.createTurn(); } else if (type == Nozzle.class) { component = root.createNozzle(); } component.setType(typeURI); component.setGeometry(provider); return component; } /** * Creates a equipment * * Does not set the name * * @param root * @param typeURI * @return * @throws Exception */ public static Equipment createEquipment(P3DRootNode root, String typeURI) throws Exception { GeometryProvider provider = providers.get(typeURI); if (provider == null) { load(typeURI); provider = providers.get(typeURI); } Equipment equipment = root.createEquipment(); equipment.setType(typeURI); equipment.setGeometry(provider); root.addChild(equipment); return equipment; } public static Equipment createEquipmentWithNozzles(P3DRootNode root, String typeURI, String nozzleTypeUri) throws Exception { GeometryProvider provider = providers.get(typeURI); if (provider == null) { load(typeURI); provider = providers.get(typeURI); } Equipment equipment = root.createEquipment(); equipment.setType(typeURI); equipment.setGeometry(provider); root.addChild(equipment); for (int i = 0; i < equipment.numberOfFixedNozzles(); i++) { createNozzle(root, equipment, new Item(nozzleTypeUri, "Nozzle")); } return equipment; } public static InlineComponent createStraight(P3DRootNode root) throws Exception{ InlineComponent component = root.createInline(); component.setType(Plant3D.URIs.Builtin_Straight); component.setGeometry(providers.get(Plant3D.URIs.Builtin_Straight)); return component; } public static TurnComponent createTurn(P3DRootNode root) throws Exception { TurnComponent elbow = root.createTurn(); elbow.setType(Plant3D.URIs.Builtin_Elbow); elbow.setGeometry(providers.get(Plant3D.URIs.Builtin_Elbow)); return elbow; } public static InlineComponent createReducer(P3DRootNode root) throws Exception { InlineComponent component = root.createInline(); component.setType(Plant3D.URIs.Builtin_ConcentricReducer); component.setGeometry(providers.get(Plant3D.URIs.Builtin_ConcentricReducer)); return component; } public static InlineComponent createBranchSplit(P3DRootNode root) throws Exception { InlineComponent component = root.createInline(); component.setType(Plant3D.URIs.Builtin_BranchSplitComponent); return component; } public static Equipment createEquipment(P3DRootNode root, Item equipmentType) throws Exception { Equipment equipment = createEquipment(root, equipmentType.getUri()); String n = root.getUniqueName(equipmentType.getName()); equipment.setName(n); return equipment; } public static Equipment createEquipmentWithNozzles(P3DRootNode root, Item equipmentType, Item nozzleType) throws Exception { Equipment equipment = createEquipmentWithNozzles(root, equipmentType.getUri(), nozzleType.getUri()); String n = root.getUniqueName(equipmentType.getName()); equipment.setName(n); return equipment; } public static Nozzle createDefaultNozzle(P3DRootNode root, Equipment equipment) throws Exception { return createNozzle(root, equipment, new Item(Plant3D.URIs.Builtin_Nozzle, "Nozzle")); } public static Nozzle createNozzle(P3DRootNode root, Equipment equipment, Item nozzleType) throws Exception { Nozzle nozzle = root.createNozzle(); nozzle.setType(nozzleType.getUri()); String n = root.getUniqueName(nozzleType.getName()); nozzle.setName(n); PipeRun pipeRun = new PipeRun(); n = root.getUniqueName("PipeRun"); pipeRun.setName(n); nozzle.setPipeRun(pipeRun); equipment.addChild(nozzle); root.addChild(pipeRun); // root.getNodeMap().commit("Add nozzle " + n); return nozzle; } public static class InsertInstruction { public String typeUri; public PositionType position = PositionType.NEXT; public PositionType insertPosition = PositionType.NEXT; // Component name public String name; // Reducer requires pipe specs public Double diameter; public Double thickness; public Double turnRadius; // Variable length public Double length; // Variable angle public Double angle; // Rotation angle used with turns and rotated inline. public Double rotationAngle; public String getTypeUri() { return typeUri; } public void setTypeUri(String typeUri) { this.typeUri = typeUri; } public PositionType getPosition() { return position; } public void setPosition(PositionType position) { this.position = position; } public PositionType getInsertPosition() { return insertPosition; } public void setInsertPosition(PositionType insertPosition) { this.insertPosition = insertPosition; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getDiameter() { return diameter; } public void setDiameter(Double diameter) { this.diameter = diameter; } public double getThickness() { return thickness; } public void setThickness(double thickness) { this.thickness = thickness; } public Double getTurnRadius() { return turnRadius; } public void setTurnRadius(Double turnRadius) { this.turnRadius = turnRadius; } public Double getLength() { return length; } public void setLength(Double length) { this.length = length; } public Double getAngle() { return angle; } public void setAngle(Double angle) { this.angle = angle; } public Double getRotationAngle() { return rotationAngle; } public void setRotationAngle(Double rotationAngle) { this.rotationAngle = rotationAngle; } } public static PipelineComponent addComponent(P3DRootNode root, PipelineComponent component, InsertInstruction inst) throws Exception { PipelineComponent newComponent = ComponentUtils.createComponent(root, inst.typeUri); PipeControlPoint newPcp = newComponent.getControlPoint(); PipeControlPoint toPcp = component.getControlPoint(); PipeRun pipeRun = toPcp.getPipeRun(); String typeName = names.get(inst.typeUri); if (typeName == null) typeName = "Component"; Vector3d dir = null; Vector3d pos = null; PositionType position = inst.position; PositionType insertPosition = inst.insertPosition; boolean lengthAdjustable = false; if (newComponent instanceof InlineComponent) { lengthAdjustable = ((InlineComponent)newComponent).isVariableLength() || ((InlineComponent)newComponent).isModifialble(); } boolean insertAdjustable = false; if (component instanceof InlineComponent) { insertAdjustable = ((InlineComponent)component).isVariableLength(); } boolean sizeChange = false; if (newComponent instanceof InlineComponent) { sizeChange = ((InlineComponent)newComponent).isSizeChange(); } // Calculate component position and direction vectors // 'dir' is a unit vector that represents the direction from 'component' to 'newComponent' if (toPcp.isInline()) { switch (position) { case NEXT: if (toPcp.isDualInline()) { toPcp = toPcp.getDualSub(); pipeRun = toPcp.getPipeRun(); } break; case PREVIOUS: if (toPcp.isDualSub()) { toPcp = toPcp.parent; pipeRun = toPcp.getPipeRun(); } break; default: break; } Vector3d start = new Vector3d(); Vector3d end = new Vector3d(); dir = toPcp.getInlineDir(); toPcp.getControlPointEnds(start, end); switch (position) { case NEXT: pos = new Vector3d(end); break; case PREVIOUS: pos = new Vector3d(start); dir.negate(); break; case SPLIT: pos = new Vector3d(toPcp.getWorldPosition()); break; default: break; } } else if (toPcp.isDirected()) { // 'dir' always points out of a nozzle regardless of insertion direction dir = new Vector3d(toPcp.getDirectedControlPointDirection()); pos = new Vector3d(toPcp.getWorldPosition()); } else if (toPcp.isTurn() && toPcp.asFixedAngle()) { dir = new Vector3d(toPcp.getDirection(position == PositionType.NEXT ? Direction.NEXT : Direction.PREVIOUS)); pos = new Vector3d(toPcp.getWorldPosition()); if (!lengthAdjustable) { Vector3d v = new Vector3d(dir); v.scale(toPcp.getInlineLength()); pos.add(v); } else { if (insertPosition == PositionType.NEXT) { Vector3d v = new Vector3d(dir); v.scale(toPcp.getInlineLength()); pos.add(v); } else if (insertPosition == PositionType.SPLIT) { // scale 0.5*length so that we don't remove the length twice from the new component Vector3d v = new Vector3d(dir); v.scale(toPcp.getInlineLength()*0.5); pos.add(v); } } } if (inst.name != null) { newComponent.setName(inst.name); } else { String name = component.getPipeRun().getUniqueName(typeName); newComponent.setName(name); } pipeRun.addChild(newComponent); if (newPcp.isSizeChange()) newComponent.setAlternativePipeRun(pipeRun); if (newComponent instanceof InlineComponent) { InlineComponent inlineComponent = (InlineComponent)newComponent; if (inlineComponent.isVariableLength()|| inlineComponent.isModifialble()) { newPcp.setLength(inst.length); newComponent.setParameter("length", inst.length); } if (inst.rotationAngle != null) ((InlineComponent) newComponent).setRotationAngle(inst.rotationAngle); } else if (newComponent instanceof TurnComponent) { TurnComponent turnComponent = (TurnComponent)newComponent; if (turnComponent.isVariableAngle()) { newPcp.setTurnAngle(Math.toRadians(inst.angle)); newComponent.setParameter("turnAngle", inst.angle); } if (inst.rotationAngle != null) ((TurnComponent) newComponent).setRotationAngle(inst.rotationAngle); } newComponent.updateParameters(); Vector3d v = new Vector3d(dir); if (insertAdjustable) { // Prevent moving of adjacent components - always insert at end of a connected variable length component if (position == PositionType.NEXT && component.getNext() != null || position == PositionType.PREVIOUS && component.getPrevious() != null) insertPosition = PositionType.PREVIOUS; if (insertPosition == PositionType.NEXT) v.scale(newComponent.getControlPoint().getInlineLength()); else if (insertPosition == PositionType.SPLIT) v.set(0, 0, 0); else if (insertPosition == PositionType.PREVIOUS) v.scale(-newComponent.getControlPoint().getInlineLength()); } else { v.scale(newComponent.getControlPoint().getInlineLength()); } switch (position) { case NEXT: case PREVIOUS: pos.add(v); break; case SPLIT: break; default: break; } switch (position) { case NEXT: if (toPcp.isDualInline()) toPcp = toPcp.getDualSub(); newPcp.setWorldPosition(pos); if (toPcp.getNext() != null) PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, false); else newPcp.insert(toPcp, Direction.NEXT); break; case PREVIOUS: if (toPcp.isDualSub()) toPcp = toPcp.parent; newPcp.setWorldPosition(pos); if (toPcp.getPrevious() != null) PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, false); else newPcp.insert(toPcp, Direction.PREVIOUS); break; case SPLIT: PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, true); default: break; } // Move the size change and the rest of the components in the pipe run to a new pipe run if (sizeChange) { PipeRun other = new PipeRun(); String n = root.getUniqueName("PipeRun"); other.setName(n); other.setPipeDiameter(inst.diameter); other.setPipeThickness(inst.thickness); other.setTurnRadius(inst.turnRadius); root.addChild(other); other.addChild(newComponent.getControlPoint().getDualSub()); newComponent.setAlternativePipeRun(other); boolean forward = position != PositionType.PREVIOUS; PipelineComponent comp = forward ? newComponent.getNext() : newComponent.getPrevious(); while (comp != null && comp.getPipeRun() == pipeRun) { if (comp.getParent() == pipeRun) { comp.deattach(); other.addChild(comp); } else { comp.setPipeRun(other); } // Reset parameters to match new pipe run comp.updateParameters(); comp = forward ? comp.getNext() : comp.getPrevious(); } newComponent.updateParameters(); } return newComponent; } public static boolean connect(PipelineComponent current, PipelineComponent endTo) throws Exception { return connect(current, endTo, null, null); } /** * Connects component to another component * @param current * @param endTo * @param endType * @param position * @return * @throws Exception */ public static boolean connect(PipelineComponent current, PipelineComponent endTo, PositionType endType, Vector3d position) throws Exception{ PipeControlPoint endCP = endTo.getControlPoint(); boolean reversed; if (current.getNext() == null) reversed = false; else if (current.getPrevious() == null) reversed = true; else return false; PipeRun pipeRun = current.getPipeRun(); P3DRootNode root = (P3DRootNode)current.getRootNode(); PipeControlPoint currentCP = current.getControlPoint(); if (endType == null || endType == PositionType.NEXT || endType == PositionType.PREVIOUS) { boolean requiresReverse = false; if (!reversed && endCP.getPrevious() != null) { if (endCP.getNext() != null) return false; requiresReverse = true; } else if (reversed && endCP.getNext() != null) { if (endCP.getPrevious() != null) return false; requiresReverse = true; } PipeRun other = endCP.getPipeRun(); boolean mergeRuns = other == null ? true : pipeRun.canMerge(other); if (requiresReverse) { // Pipe line must be traversible with next/previous relations without direction change. // Now the component, where we are connecting the created pipeline is defined in different order. PipingRules.reverse(other); } if (mergeRuns) { // Runs have compatible specs and must be merged if (other != null && pipeRun != other) pipeRun.merge(other); else if (other == null) { if (!(endTo instanceof Nozzle)) { pipeRun.addChild(endTo); } else { endTo.setPipeRun(pipeRun); } } if (!reversed) { currentCP.setNext(endCP); endCP.setPrevious(currentCP); } else { currentCP.setPrevious(endCP); endCP.setNext(currentCP); } } else { // Runs do not have compatible specs, and a reducer must be attached in between. InlineComponent reducer = ComponentUtils.createReducer(root); PipeControlPoint pcp = reducer.getControlPoint(); Vector3d endPos = endCP.getWorldPosition(); Vector3d currentPos = currentCP.getWorldPosition(); Vector3d v = new Vector3d(endPos); v.sub(currentPos); v.scale(0.5); v.add(currentPos); PipingRules.addSizeChange(reversed, pipeRun, other, reducer, currentCP, endCP); pcp.setWorldPosition(v); reducer.updateParameters(); } PipingRules.positionUpdate(endCP); return true; } else if (endType == PositionType.SPLIT) { InlineComponent branchSplit = createBranchSplit((InlineComponent)endTo, position); if (branchSplit == null) return false; PipeControlPoint branchSplitCP = branchSplit.getControlPoint(); PipeControlPoint pcp = new PipeControlPoint(branchSplit,pipeRun); branchSplitCP.children.add(pcp); pcp.parent = branchSplitCP; pcp.setWorldOrientation(branchSplitCP.getWorldOrientation()); pcp.setWorldPosition(branchSplitCP.getWorldPosition()); if(!reversed) { pcp.setPrevious(currentCP); currentCP.setNext(pcp); } else { pcp.setNext(currentCP); currentCP.setPrevious(pcp); } PipingRules.positionUpdate(endCP); return true; } return false; } public static InlineComponent createBranchSplit(InlineComponent component, Vector3d pos) throws Exception{ if (!component.isVariableLength()) return null; PipeRun pipeRun = component.getPipeRun(); Vector3d sStart = new Vector3d(); Vector3d sEnd = new Vector3d(); component.getControlPoint().getInlineControlPointEnds(sStart, sEnd); if (MathTools.distance(sStart, sEnd) < (pipeRun.getPipeDiameter()*0.5)) return null; Vector3d p = MathTools.closestPointOnEdge(new Vector3d(pos), sStart, sEnd); if (p == sStart) { Vector3d v = new Vector3d(sEnd); v.sub(sStart); v.normalize(); v.scale(component.getPipeRun().getPipeDiameter()*0.5); p.add(v); } else if (p == sEnd) { Vector3d v = new Vector3d(sStart); v.sub(sEnd); v.normalize(); v.scale(component.getPipeRun().getPipeDiameter()*0.5); p.add(v); } P3DRootNode root = (P3DRootNode)component.getRootNode(); InlineComponent branchSplit = ComponentUtils.createBranchSplit(root); String branchName = component.getPipeRun().getUniqueName("Branch"); branchSplit.setName(branchName); component.getPipeRun().addChild(branchSplit); PipeControlPoint branchSplitCP = branchSplit.getControlPoint(); branchSplitCP.setWorldPosition(p); PipingRules.splitVariableLengthComponent(branchSplit, component, false); return branchSplit; } public static Collection getPipelineComponentNames(P3DRootNode root) { Collection usedNames = root.getChild().stream() .filter(n -> n instanceof PipeRun) .flatMap(n -> ((PipeRun)n).getChild().stream()) .filter(n -> n instanceof PipelineComponent) .map(n -> ((PipelineComponent)n).getName()) .collect(Collectors.toSet()); return usedNames; } }