+ // ------------------------------------------------------------------------
+
+ 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<Resource> connectionPoints();
+ Set<Resource> 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<Resource, AffineTransform> 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<Resource> connectionPoints() {
+ return elementTerminals.getConnectionPoints();
+ }
+ @Override
+ public Set<Resource> terminals() {
+ return elementTerminals.getTerminals();
+ }
+ };
+ }
+
+ private static Set<Terminal> connectedTo(ReadGraph graph, Resource element, Resource connectionPoint) throws DatabaseException {
+ BasicResources BR = BasicResources.getInstance(graph);
+ Set<Terminal> 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.
+ *
+ * <p>
+ * 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<Resource> connectedComponents = new HashSet<>();
+ Map<Resource, TerminalFunction> componentTerminals = new HashMap<>();
+ Map<Resource, Terminal> connectedTerminals = new HashMap<>();
+
+ for (Resource connectionPoint : elementTerminals.connectionPoints()) {
+ Set<Terminal> 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<Resource, Terminal> 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);
+ }
+