X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Felements%2FElementTransforms.java;h=1e141c63643cdb108458a2555f8d94359582cdb9;hp=916c274886e2b40a3474f32db1c4af838235fb4f;hb=3e877b48594c98cff85f4db64964f86fe14c0f03;hpb=60509f4629d5ca644dd9533dd2abc64349aad328 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java index 916c27488..1e141c636 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java @@ -7,23 +7,31 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; +import org.simantics.db.Statement; import org.simantics.db.UndoContext; import org.simantics.db.WriteGraph; import org.simantics.db.common.CommentMetadata; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.NoSingleResultException; import org.simantics.db.exception.ServiceException; import org.simantics.db.request.Write; +import org.simantics.diagram.content.TerminalMap; import org.simantics.diagram.query.DiagramRequests; import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.diagram.synchronization.graph.BasicResources; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.diagram.DiagramClass; import org.simantics.g2d.diagram.IDiagram; @@ -35,6 +43,11 @@ import org.simantics.g2d.element.impl.Element; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.scl.commands.Command; import org.simantics.scl.commands.Commands; +import org.simantics.structural2.queries.Terminal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import gnu.trove.set.hash.THashSet; /** * Tools to align, rotate, and flip diagram elements. @@ -48,6 +61,8 @@ import org.simantics.scl.commands.Commands; */ public final class ElementTransforms { + private static final Logger LOGGER = LoggerFactory.getLogger(ElementTransforms.class); + public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW }; /** @@ -621,6 +636,7 @@ public final class ElementTransforms { return GeometryUtils.transformShape(bounds, at).getBounds2D(); } + @SuppressWarnings("unused") public double getDeterminant() { return transform[0] * transform[3] - transform[1] * transform[2]; } @@ -712,4 +728,177 @@ public final class ElementTransforms { } } + // ------------------------------------------------------------------------ + + private interface TerminalFunction { + /** + * @param terminal + * @param rotationTheta in radians + * @return + */ + AffineTransform transform(Resource connectionPoint, double rotationTheta); + default Point2D position(Resource connectionPoint, double rotationTheta) { + AffineTransform tr = transform(connectionPoint, rotationTheta); + return new Point2D.Double(tr.getTranslateX(), tr.getTranslateY()); + } + Resource toConnectionPoint(Resource terminal); + Resource toTerminal(Resource connectionPoint); + Set connectionPoints(); + Set terminals(); + } + + private static TerminalFunction terminalPositionFunction(ReadGraph graph, boolean ignoreElementRotation, Resource element) throws DatabaseException { + AffineTransform transform = DiagramGraphUtil.getAffineTransform(graph, element); + if (ignoreElementRotation) { + AffineTransform noRotation = AffineTransform.getTranslateInstance(transform.getTranslateX(), transform.getTranslateY()); + Point2D scale = org.simantics.scenegraph.utils.GeometryUtils.getScale2D(transform); + noRotation.scale(scale.getX(), scale.getY()); + transform = noRotation; + } + AffineTransform elementTransform = transform; + + TerminalMap elementTerminals = DiagramGraphUtil.getElementTerminals(graph, element); + Map terminalTransforms = new HashMap<>(); + for (Resource terminal : elementTerminals.getTerminals()) + terminalTransforms.put(elementTerminals.getConnectionPoint(terminal), DiagramGraphUtil.getAffineTransform(graph, terminal)); + + return new TerminalFunction() { + @Override + public AffineTransform transform(Resource connectionPoint, double rotationTheta) { + AffineTransform tr = new AffineTransform(elementTransform); + AffineTransform terminalTr = terminalTransforms.get(connectionPoint); + if (rotationTheta != 0) + tr.rotate(rotationTheta); + if (terminalTr != null) + tr.concatenate(terminalTr); + return tr; + } + @Override + public Resource toConnectionPoint(Resource terminal) { + return elementTerminals.getConnectionPoint(terminal); + } + @Override + public Resource toTerminal(Resource connectionPoint) { + return elementTerminals.getTerminal(connectionPoint); + } + @Override + public Set connectionPoints() { + return elementTerminals.getConnectionPoints(); + } + @Override + public Set terminals() { + return elementTerminals.getTerminals(); + } + }; + } + + private static Set connectedTo(ReadGraph graph, Resource element, Resource connectionPoint) throws DatabaseException { + BasicResources BR = BasicResources.getInstance(graph); + Set result = new THashSet<>(); + for (Resource connector : graph.getObjects(element, connectionPoint)) { + Resource connection = graph.getPossibleObject(connector, BR.DIA.IsConnectorOf); + if (connection == null) + continue; + for (Resource otherConnector : graph.getObjects(connection, BR.DIA.HasConnector)) { + if (!otherConnector.equals(connector)) { + for (Statement s : graph.getStatements(otherConnector, BR.STR.Connects)) { + if (!s.getObject().equals(connection)) { + result.add( new Terminal(s.getObject(), graph.getInverse(s.getPredicate())) ); + } + } + } + } + } + return result; + } + + /** + * Rotates provided element so that its rotation is the same as the slope of a + * line drawn between the two other elements the element is connected to. + * + *

+ * Note that this requires that the element is connected to exactly two other + * elements. + * + * @param graph + * @param element + * @return the chosen rotation in degrees + * @throws DatabaseException + */ + public static double rotateToNeighborSlope(WriteGraph graph, Resource element) throws DatabaseException { + + if (LOGGER.isDebugEnabled()) + LOGGER.debug("rotateAccordingToNeighborPositions( {} )", graph.getPossibleURI(element)); + + TerminalFunction elementTerminals = terminalPositionFunction(graph, true, element); + Set connectedComponents = new HashSet<>(); + Map componentTerminals = new HashMap<>(); + Map connectedTerminals = new HashMap<>(); + + for (Resource connectionPoint : elementTerminals.connectionPoints()) { + Set connectedTo = connectedTo(graph, element, connectionPoint); + if (connectedTo.isEmpty()) + continue; + if (connectedTo.size() > 1) { + if (LOGGER.isWarnEnabled()) + LOGGER.warn("rotateToNeighbors only supports functional connection points, ignoring {}", NameUtils.getURIOrSafeNameInternal(graph, connectionPoint)); + continue; + } + + Terminal t = connectedTo.iterator().next(); + TerminalFunction tf = terminalPositionFunction(graph, false, t.getComponent()); + connectedComponents.add(t.getComponent()); + componentTerminals.put(t.getComponent(), tf); + connectedTerminals.put(connectionPoint, t); + + if (LOGGER.isDebugEnabled()) + LOGGER.info("{} {} is connected to {}", NameUtils.getSafeName(graph, element), NameUtils.getSafeName(graph, connectionPoint), t.toString(graph)); + } + + if (connectedComponents.size() != 2) { + LOGGER.error("rotateToNeighborPositions only works for elements connected to exactly two other elements, {} is connected to {}", NameUtils.getURIOrSafeNameInternal(graph, element), connectedComponents.size()); + return Double.NaN; + } + + Resource[] ends = connectedComponents.toArray(Resource.NONE); + AffineTransform[] endTr = { + componentTerminals.get(ends[0]).transform(null, 0), + componentTerminals.get(ends[1]).transform(null, 0) + }; + + double slopeTheta = Math.atan2(endTr[1].getTranslateY() - endTr[0].getTranslateY(), endTr[1].getTranslateX() - endTr[0].getTranslateX()); + double[] thetas = { slopeTheta, slopeTheta + Math.PI }; + double selectedRotation = slopeTheta; + double minCost = Double.MAX_VALUE; + for (int i = 0; i < 2; ++i) { + double cost = 0; + for (Map.Entry e : connectedTerminals.entrySet()) { + Terminal t = e.getValue(); + TerminalFunction tf = componentTerminals.get(t.getComponent()); + Point2D otp = tf.position(t.getRelation(), 0); + Point2D etp = elementTerminals.position(e.getKey(), thetas[i]); + cost += otp.distance(etp); + } + + if (LOGGER.isDebugEnabled()) + LOGGER.info("total cost of theta {} is {}", Math.toDegrees(thetas[i]), cost); + + if (cost < minCost) { + minCost = cost; + selectedRotation = thetas[i]; + if (LOGGER.isDebugEnabled()) + LOGGER.debug("new minimum cost {} found", cost); + } + } + + AffineTransform newTr = elementTerminals.transform(null, 0); + newTr.rotate(selectedRotation); + DiagramGraphUtil.setTransform(graph, element, newTr); + + if (LOGGER.isDebugEnabled()) + LOGGER.debug("set rotation to {} degrees", Math.toDegrees(selectedRotation)); + + return Math.toDegrees(selectedRotation); + } + }