1 package org.simantics.diagram.elements;
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Point2D;
5 import java.awt.geom.Rectangle2D;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.Comparator;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.List;
16 import org.simantics.Simantics;
17 import org.simantics.db.ReadGraph;
18 import org.simantics.db.Resource;
19 import org.simantics.db.Statement;
20 import org.simantics.db.UndoContext;
21 import org.simantics.db.WriteGraph;
22 import org.simantics.db.common.CommentMetadata;
23 import org.simantics.db.common.request.IndexRoot;
24 import org.simantics.db.common.request.WriteRequest;
25 import org.simantics.db.common.utils.NameUtils;
26 import org.simantics.db.exception.DatabaseException;
27 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
28 import org.simantics.db.exception.NoSingleResultException;
29 import org.simantics.db.exception.ServiceException;
30 import org.simantics.db.request.Write;
31 import org.simantics.diagram.content.TerminalMap;
32 import org.simantics.diagram.query.DiagramRequests;
33 import org.simantics.diagram.stubs.DiagramResource;
34 import org.simantics.diagram.synchronization.graph.BasicResources;
35 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
36 import org.simantics.g2d.diagram.DiagramClass;
37 import org.simantics.g2d.diagram.IDiagram;
38 import org.simantics.g2d.diagram.impl.Diagram;
39 import org.simantics.g2d.element.ElementClass;
40 import org.simantics.g2d.element.ElementUtils;
41 import org.simantics.g2d.element.IElement;
42 import org.simantics.g2d.element.impl.Element;
43 import org.simantics.g2d.utils.GeometryUtils;
44 import org.simantics.scl.commands.Command;
45 import org.simantics.scl.commands.Commands;
46 import org.simantics.structural2.queries.Terminal;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
50 import gnu.trove.set.hash.THashSet;
53 * Tools to align, rotate, and flip diagram elements.
55 * TODO : We need to add capability hints to elements to prevent rotating and mirroring elements that do not support that.
56 * Example: mirrored text does not make any sense.
59 * @author Marko Luukkainen <marko.luukkainen@vtt.fi> (implementation)
60 * @author Tuukka Lehtonen (documentation)
62 public final class ElementTransforms {
64 private static final Logger LOGGER = LoggerFactory.getLogger(ElementTransforms.class);
66 public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW };
69 * Align the specified set of diagram element resources in line with each
70 * other calculated by the specified side.
73 * Alignment requires at least two elements to do anything.
75 * @param resources diagram element resources to rotate
76 * @param side the side of each element to use for distancing. Does not support between aligments.
78 public static void align(final Resource resources[], final SIDE side) {
79 if (resources.length < 2)
81 if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW )
84 Simantics.getSession().asyncRequest(new WriteRequest() {
87 public void perform(WriteGraph graph) throws DatabaseException {
88 graph.markUndoPoint();
89 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
91 List<AlignElement> elements = new ArrayList<AlignElement>();
92 for (Resource r : resources) {
93 AlignElement e = create(graph, hints, r);
97 if (elements.size() < 2)
101 for (AlignElement e : elements) {
102 mx += e.transform[4];
103 my += e.transform[5];
105 mx /= elements.size();
106 my /= elements.size();
108 // prevent moving symbols into the same position
110 for (AlignElement e : elements) {
111 if (side == SIDE.VERT || side == SIDE.LEFT || side == SIDE.RIGHT) {
112 if (Math.abs(e.transform[5] - my) < 0.1) {
116 if (Math.abs(e.transform[4] - mx) < 0.1) {
124 if (side == SIDE.HORIZ || side == SIDE.VERT) {
127 for (AlignElement e : elements) {
129 if (side == SIDE.VERT)
130 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
131 else if (side == SIDE.HORIZ) {
132 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
138 lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX();
139 rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX();
141 ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY();
142 by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY();
144 for (int i = 1; i < elements.size(); i++) {
147 tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX();
148 trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX();
150 tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY();
151 tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY();
163 for (AlignElement e : elements) {
166 if (side == SIDE.LEFT) {
167 mx = lx - e.rotatedBounds.getMinX() ;
168 } else if (side == SIDE.RIGHT) {
169 mx = rx - e.rotatedBounds.getMaxX();
170 } else if (side == SIDE.TOP) {
171 my = ty - e.rotatedBounds.getMinY();
173 my = by - e.rotatedBounds.getMaxY();
175 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my});
186 * Distance specified set of diagram element resources equally. Distancing
187 * is performed on the specified side of the each element.
190 * Distancing requires at least three elements to work.
192 * @param resources diagram element resources to rotate
193 * @param side the side of each element to use for distancing
195 public static void dist(final Resource resources[], final SIDE side) {
197 if (resources.length < 3)
200 Simantics.getSession().asyncRequest(new WriteRequest() {
203 public void perform(WriteGraph graph) throws DatabaseException {
204 graph.markUndoPoint();
205 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
207 List<AlignElement> elements = new ArrayList<AlignElement>();
208 for (Resource r : resources) {
209 //System.out.println(r + " " + GraphUtils.getReadableName(graph, r));
210 AlignElement e = create(graph, hints, r);
214 if (elements.size() < 3)
218 Collections.sort(elements, new XComparator());
219 AlignElement left = elements.get(0);
220 AlignElement right = elements.get(elements.size() - 1);
222 double leftEdge = left.transform[4] + left.rotatedBounds.getMinX();
223 double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
225 double totalDist = rightEdge - leftEdge;
226 double dist = totalDist / (elements.size() - 1);
229 for (int i = 1; i < elements.size() -1; i++) {
231 AlignElement e = elements.get(i);
233 double mx = d - e.rotatedBounds.getMinX();
235 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
241 Collections.sort(elements, new XComparator());
242 AlignElement left = elements.get(0);
243 AlignElement right = elements.get(elements.size() - 1);
245 double leftEdge = left.transform[4];
246 double rightEdge = right.transform[4];
248 double totalDist = rightEdge - leftEdge;
249 double dist = totalDist / (elements.size() - 1);
252 for (int i = 1; i < elements.size() -1; i++) {
254 AlignElement e = elements.get(i);
258 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
264 Collections.sort(elements, new XComparator());
265 AlignElement left = elements.get(0);
266 AlignElement right = elements.get(elements.size() - 1);
268 double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
269 double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX();
271 double totalDist = rightEdge - leftEdge;
272 double dist = totalDist / (elements.size() - 1);
275 for (int i = 1; i < elements.size() -1; i++) {
277 AlignElement e = elements.get(i);
279 double mx = d - e.rotatedBounds.getMaxX();
281 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
286 Collections.sort(elements, new XComparator());
287 AlignElement left = elements.get(0);
288 AlignElement right = elements.get(elements.size() - 1);
290 double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
291 double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
293 double totalDist = rightEdge - leftEdge;
294 double totalElementSize = 0;
295 for (int i = 1; i < elements.size() -1; i++) {
296 totalElementSize += elements.get(i).rotatedBounds.getWidth();
298 double totalAvail = totalDist - totalElementSize;
299 double dist = totalAvail / (elements.size() - 1);
302 for (int i = 1; i < elements.size() -1; i++) {
304 AlignElement e = elements.get(i);
306 double mx = d - e.rotatedBounds.getMinX();
307 d += e.bounds.getWidth();
309 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
315 Collections.sort(elements, new YComparator());
316 AlignElement top = elements.get(0);
317 AlignElement bottom = elements.get(elements.size() - 1);
319 double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
320 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY();
322 double totalDist = bottomEdge - topEdge;
323 double dist = totalDist / (elements.size() - 1);
326 for (int i = 1; i < elements.size() -1; i++) {
328 AlignElement e = elements.get(i);
330 double my = d - e.rotatedBounds.getMaxY();
332 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
338 Collections.sort(elements, new YComparator());
339 AlignElement top = elements.get(0);
340 AlignElement bottom = elements.get(elements.size() - 1);
342 double topEdge = top.transform[5] + top.rotatedBounds.getMinY();
343 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
345 double totalDist = bottomEdge - topEdge;
346 double dist = totalDist / (elements.size() - 1);
349 for (int i = 1; i < elements.size() -1; i++) {
351 AlignElement e = elements.get(i);
353 double my = d - e.rotatedBounds.getMinY();
355 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
361 Collections.sort(elements, new YComparator());
362 AlignElement top = elements.get(0);
363 AlignElement bottom = elements.get(elements.size() - 1);
365 double topEdge = top.transform[5];
366 double bottomEdge = bottom.transform[5];
368 double totalDist = bottomEdge - topEdge;
369 double dist = totalDist / (elements.size() - 1);
372 for (int i = 1; i < elements.size() -1; i++) {
374 AlignElement e = elements.get(i);
378 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
384 Collections.sort(elements, new YComparator());
385 AlignElement top = elements.get(0);
386 AlignElement bottom = elements.get(elements.size() - 1);
388 double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
389 double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
391 double totalDist = bottomEdge - topEdge;
392 double totalElementSize = 0;
393 for (int i = 1; i < elements.size() -1; i++) {
394 totalElementSize += elements.get(i).rotatedBounds.getHeight();
396 double totalAvail = totalDist - totalElementSize;
397 double dist = totalAvail / (elements.size() - 1);
400 for (int i = 1; i < elements.size() -1; i++) {
402 AlignElement e = elements.get(i);
404 double my = d - e.rotatedBounds.getMinY();
405 d += e.rotatedBounds.getHeight();
407 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
422 * Rotate specified set of diagram element resources around the center of
423 * mass of the specified element selection.
425 * @param resources diagram element resources to rotate
426 * @param clockwise <code>true</code> to rotate 90 degrees clockwise,
427 * <code>false</code> to rotate 90 degrees counter-clockwise
429 public static void rotate(final Resource resources[], final boolean clockwise) {
430 Simantics.getSession().asyncRequest(new WriteRequest() {
432 public void perform(WriteGraph graph) throws DatabaseException {
433 graph.markUndoPoint();
434 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
436 DiagramResource DIA = DiagramResource.getInstance(graph);
438 List<AlignElement> elements = new ArrayList<AlignElement>();
439 List<Resource> connections = new ArrayList<Resource>();
440 for (Resource r : resources) {
441 AlignElement e = create(graph, hints, r);
444 else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
447 if (elements.size() < 1)
450 // Add comment to change set.
451 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
452 graph.addMetadata( cm.add("Rotate " + elements.size() + " elements " + (clockwise ? "clockwise" : "counter-clockwise")) );
454 AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1)
455 : AffineTransform.getQuadrantRotateInstance(3);
457 if (elements.size() == 1 && connections.isEmpty()) {
458 for (AlignElement e : elements) {
459 AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
460 eat.preConcatenate(at);
462 DiagramGraphUtil.changeTransform(graph, e.element,
463 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]});
466 Rectangle2D selectionBounds = null;
467 for (AlignElement e : elements) {
468 if (selectionBounds != null) {
469 selectionBounds.add(e.transform[4], e.transform[5]);
471 selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
475 double cx = selectionBounds.getCenterX();
476 double cy = selectionBounds.getCenterY();
478 for (AlignElement e : elements) {
479 double x = e.transform[4];
480 double y = e.transform[5];
483 Point2D r = at.transform(new Point2D.Double(dx, dy), null);
484 double mx = r.getX() + cx;
485 double my = r.getY() + cy;
486 AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0);
487 eat.preConcatenate(at);
489 DiagramGraphUtil.changeTransform(graph, e.element,
490 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my});
493 if(!connections.isEmpty()) {
494 Command rotateConnection = Commands.get(graph, "Simantics/Diagram/rotateConnection");
495 Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
496 for(Resource r : connections)
497 rotateConnection.execute(graph, model, r, cx, cy, clockwise);
505 * Flip specified set of diagram element resources around either the x or
506 * y-axis specified by the mass center of the selection bounding box.
507 * Each element is considered to weigh an equal amount.
509 * @param resources diagram element resources to flip
510 * @param xAxis <code>true</code> to flip around x-axis, <code>false</code>
513 public static void flip(final Resource resources[], final boolean xAxis) {
514 Simantics.getSession().asyncRequest(new WriteRequest() {
516 public void perform(WriteGraph graph) throws DatabaseException {
517 graph.markUndoPoint();
518 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
520 DiagramResource DIA = DiagramResource.getInstance(graph);
522 List<AlignElement> elements = new ArrayList<AlignElement>();
523 List<Resource> connections = new ArrayList<Resource>();
524 for (Resource r : resources) {
525 AlignElement e = create(graph, hints, r);
528 else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
531 if (elements.size() < 1)
534 // Add comment to change set.
535 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
536 graph.addMetadata( cm.add("Flip " + elements.size() + " elements " + (xAxis ? "vertically" : "horizontally")) );
538 if (elements.size() == 1 && connections.isEmpty()) {
539 for (AlignElement e : elements) {
541 AffineTransform at = new AffineTransform(e.transform);
542 AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
543 at.preConcatenate(at2);
544 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
546 AffineTransform at = new AffineTransform(e.transform);
547 AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
548 at.preConcatenate(at2);
549 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]});
554 Rectangle2D selectionBounds = null;
555 for (AlignElement e : elements) {
556 if (selectionBounds != null) {
557 selectionBounds.add(e.transform[4], e.transform[5]);
559 selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
563 for (AlignElement e : elements) {
565 double y = e.transform[5];
566 double cy = selectionBounds.getCenterY();
567 double my = cy + cy - y;
568 AffineTransform at = new AffineTransform(e.transform);
569 AffineTransform at2 = AffineTransform.getScaleInstance(1, -1);
570 at.preConcatenate(at2);
571 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],my});
573 double x = e.transform[4];
574 double cx = selectionBounds.getCenterX();
575 double mx = cx + cx - x;
577 AffineTransform at = new AffineTransform(e.transform);
578 AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1);
579 at.preConcatenate(at2);
580 DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),mx,e.transform[5]});
584 if(!connections.isEmpty()) {
585 Command flipConnection = Commands.get(graph, "Simantics/Diagram/flipConnection");
586 Resource model = graph.syncRequest(new IndexRoot(connections.get(0)));
587 for(Resource r : connections)
588 flipConnection.execute(graph, model, r, xAxis,
589 xAxis ? selectionBounds.getCenterY()
590 : selectionBounds.getCenterX());
598 private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException {
599 DiagramResource dr = DiagramResource.getInstance(graph);
601 if (graph.isInstanceOf(r, dr.Element) && !graph.isInstanceOf(r, dr.Connection) /*&& !graph.isInstanceOf(r, dr.Monitor)*/) {
602 double transform[] = graph.getPossibleRelatedValue(r, dr.HasTransform);
603 ElementClass ec = graph.syncRequest(DiagramRequests.getElementClass(graph.getSingleType(r, dr.Element), hints));
604 IElement e = Element.spawnNew(ec);
605 Rectangle2D bounds = ElementUtils.getElementBounds(e);
606 if (transform != null && bounds != null) {
607 return new AlignElement(r, transform, bounds);
614 private static class AlignElement {
615 public Resource element;
616 public double[] transform;
617 public Rectangle2D bounds;
618 public Rectangle2D rotatedBounds;
619 // public Rectangle2D transformedBounds;
621 public AlignElement(Resource element, double[] transform, Rectangle2D bounds) {
622 this.element = element;
623 this.transform = transform;
624 this.bounds = bounds;
625 // this.transformedBounds = getBounds();
626 this.rotatedBounds = getRotatedBounds();
629 // public Rectangle2D getBounds() {
630 // AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
631 // return GeometryUtils.transformShape(bounds, at).getBounds2D();
634 public Rectangle2D getRotatedBounds() {
635 AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], 0.0, 0.0);
636 return GeometryUtils.transformShape(bounds, at).getBounds2D();
639 @SuppressWarnings("unused")
640 public double getDeterminant() {
641 return transform[0] * transform[3] - transform[1] * transform[2];
646 private static class XComparator implements Comparator<AlignElement> {
648 public int compare(AlignElement o1, AlignElement o2) {
649 if (o1.transform[4] < o2.transform[4])
651 if (o1.transform[4] > o2.transform[4])
658 private static class YComparator implements Comparator<AlignElement> {
660 public int compare(AlignElement o1, AlignElement o2) {
661 if (o1.transform[5] < o2.transform[5])
663 if (o1.transform[5] > o2.transform[5])
671 * Set 2D affine transforms for the listed diagram element resources.
673 * @param elements diagram element resources to set transforms for
674 * @param transforms transforms for each element
676 public static Write setTransformRequest(final Collection<TransformedObject> elements)
678 return new WriteRequest() {
680 public void perform(WriteGraph graph) throws DatabaseException {
681 for (TransformedObject element : elements)
682 DiagramGraphUtil.changeTransform(graph, element.element, element.transform);
688 * Set 2D affine transforms for the listed diagram element resources.
690 * @param undoContext the database undo context to use for the returned
692 * @param elements diagram element resources to set transforms for
693 * @param transforms transforms for each element
694 * @param preConcatenate <code>true</code> to pre-concatenate the
695 * transforms, <code>false</code> to concatenate
697 public static Write concatenateTransformRequest(
698 UndoContext undoContext,
699 final Collection<TransformedObject> elements,
700 final boolean preConcatenate)
702 return new WriteRequest() {
704 public void perform(WriteGraph graph) throws DatabaseException {
705 for (TransformedObject element : elements) {
706 AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element);
708 at.preConcatenate(element.transform);
710 at.concatenate(element.transform);
711 DiagramGraphUtil.setTransform(graph, element.element, at);
717 public static class TransformedObject {
718 public final Resource element;
719 public final AffineTransform transform;
721 public TransformedObject(Resource element) {
722 this.element = element;
723 this.transform = new AffineTransform();
725 public TransformedObject(Resource element, AffineTransform transform) {
726 this.element = element;
727 this.transform = transform;
731 // ------------------------------------------------------------------------
733 private interface TerminalFunction {
736 * @param rotationTheta in radians
739 AffineTransform transform(Resource connectionPoint, double rotationTheta);
740 default Point2D position(Resource connectionPoint, double rotationTheta) {
741 AffineTransform tr = transform(connectionPoint, rotationTheta);
742 return new Point2D.Double(tr.getTranslateX(), tr.getTranslateY());
744 Resource toConnectionPoint(Resource terminal);
745 Resource toTerminal(Resource connectionPoint);
746 Set<Resource> connectionPoints();
747 Set<Resource> terminals();
750 private static TerminalFunction terminalPositionFunction(ReadGraph graph, boolean ignoreElementRotation, Resource element) throws DatabaseException {
751 AffineTransform transform = DiagramGraphUtil.getAffineTransform(graph, element);
752 if (ignoreElementRotation) {
753 AffineTransform noRotation = AffineTransform.getTranslateInstance(transform.getTranslateX(), transform.getTranslateY());
754 Point2D scale = org.simantics.scenegraph.utils.GeometryUtils.getScale2D(transform);
755 noRotation.scale(scale.getX(), scale.getY());
756 transform = noRotation;
758 AffineTransform elementTransform = transform;
760 TerminalMap elementTerminals = DiagramGraphUtil.getElementTerminals(graph, element);
761 Map<Resource, AffineTransform> terminalTransforms = new HashMap<>();
762 for (Resource terminal : elementTerminals.getTerminals())
763 terminalTransforms.put(elementTerminals.getConnectionPoint(terminal), DiagramGraphUtil.getAffineTransform(graph, terminal));
765 return new TerminalFunction() {
767 public AffineTransform transform(Resource connectionPoint, double rotationTheta) {
768 AffineTransform tr = new AffineTransform(elementTransform);
769 AffineTransform terminalTr = terminalTransforms.get(connectionPoint);
770 if (rotationTheta != 0)
771 tr.rotate(rotationTheta);
772 if (terminalTr != null)
773 tr.concatenate(terminalTr);
777 public Resource toConnectionPoint(Resource terminal) {
778 return elementTerminals.getConnectionPoint(terminal);
781 public Resource toTerminal(Resource connectionPoint) {
782 return elementTerminals.getTerminal(connectionPoint);
785 public Set<Resource> connectionPoints() {
786 return elementTerminals.getConnectionPoints();
789 public Set<Resource> terminals() {
790 return elementTerminals.getTerminals();
795 private static Set<Terminal> connectedTo(ReadGraph graph, Resource element, Resource connectionPoint) throws DatabaseException {
796 BasicResources BR = BasicResources.getInstance(graph);
797 Set<Terminal> result = new THashSet<>();
798 for (Resource connector : graph.getObjects(element, connectionPoint)) {
799 Resource connection = graph.getPossibleObject(connector, BR.DIA.IsConnectorOf);
800 if (connection == null)
802 for (Resource otherConnector : graph.getObjects(connection, BR.DIA.HasConnector)) {
803 if (!otherConnector.equals(connector)) {
804 for (Statement s : graph.getStatements(otherConnector, BR.STR.Connects)) {
805 if (!s.getObject().equals(connection)) {
806 result.add( new Terminal(s.getObject(), graph.getInverse(s.getPredicate())) );
816 * Rotates provided element so that its rotation is the same as the slope of a
817 * line drawn between the two other elements the element is connected to.
820 * Note that this requires that the element is connected to exactly two other
825 * @return the chosen rotation in degrees
826 * @throws DatabaseException
828 public static double rotateToNeighborSlope(WriteGraph graph, Resource element) throws DatabaseException {
830 if (LOGGER.isDebugEnabled())
831 LOGGER.debug("rotateAccordingToNeighborPositions( {} )", graph.getPossibleURI(element));
833 TerminalFunction elementTerminals = terminalPositionFunction(graph, true, element);
834 Set<Resource> connectedComponents = new HashSet<>();
835 Map<Resource, TerminalFunction> componentTerminals = new HashMap<>();
836 Map<Resource, Terminal> connectedTerminals = new HashMap<>();
838 for (Resource connectionPoint : elementTerminals.connectionPoints()) {
839 Set<Terminal> connectedTo = connectedTo(graph, element, connectionPoint);
840 if (connectedTo.isEmpty())
842 if (connectedTo.size() > 1) {
843 if (LOGGER.isWarnEnabled())
844 LOGGER.warn("rotateToNeighbors only supports functional connection points, ignoring {}", NameUtils.getURIOrSafeNameInternal(graph, connectionPoint));
848 Terminal t = connectedTo.iterator().next();
849 TerminalFunction tf = terminalPositionFunction(graph, false, t.getComponent());
850 connectedComponents.add(t.getComponent());
851 componentTerminals.put(t.getComponent(), tf);
852 connectedTerminals.put(connectionPoint, t);
854 if (LOGGER.isDebugEnabled())
855 LOGGER.info("{} {} is connected to {}", NameUtils.getSafeName(graph, element), NameUtils.getSafeName(graph, connectionPoint), t.toString(graph));
858 if (connectedComponents.size() != 2) {
859 LOGGER.error("rotateToNeighborPositions only works for elements connected to exactly two other elements, {} is connected to {}", NameUtils.getURIOrSafeNameInternal(graph, element), connectedComponents.size());
863 Resource[] ends = connectedComponents.toArray(Resource.NONE);
864 AffineTransform[] endTr = {
865 componentTerminals.get(ends[0]).transform(null, 0),
866 componentTerminals.get(ends[1]).transform(null, 0)
869 double slopeTheta = Math.atan2(endTr[1].getTranslateY() - endTr[0].getTranslateY(), endTr[1].getTranslateX() - endTr[0].getTranslateX());
870 double[] thetas = { slopeTheta, slopeTheta + Math.PI };
871 double selectedRotation = slopeTheta;
872 double minCost = Double.MAX_VALUE;
873 for (int i = 0; i < 2; ++i) {
875 for (Map.Entry<Resource, Terminal> e : connectedTerminals.entrySet()) {
876 Terminal t = e.getValue();
877 TerminalFunction tf = componentTerminals.get(t.getComponent());
878 Point2D otp = tf.position(t.getRelation(), 0);
879 Point2D etp = elementTerminals.position(e.getKey(), thetas[i]);
880 cost += otp.distance(etp);
883 if (LOGGER.isDebugEnabled())
884 LOGGER.info("total cost of theta {} is {}", Math.toDegrees(thetas[i]), cost);
886 if (cost < minCost) {
888 selectedRotation = thetas[i];
889 if (LOGGER.isDebugEnabled())
890 LOGGER.debug("new minimum cost {} found", cost);
894 AffineTransform newTr = elementTerminals.transform(null, 0);
895 newTr.rotate(selectedRotation);
896 DiagramGraphUtil.setTransform(graph, element, newTr);
898 if (LOGGER.isDebugEnabled())
899 LOGGER.debug("set rotation to {} degrees", Math.toDegrees(selectedRotation));
901 return Math.toDegrees(selectedRotation);