]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/ElementTransforms.java
Added new diagram element transformation function rotateToNeighborSlope
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / ElementTransforms.java
1 package org.simantics.diagram.elements;
2
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;
13 import java.util.Map;
14 import java.util.Set;
15
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;
49
50 import gnu.trove.set.hash.THashSet;
51
52 /**
53  * Tools to align, rotate, and flip diagram elements.
54  * 
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.
57  * 
58  * 
59  * @author Marko Luukkainen <marko.luukkainen@vtt.fi> (implementation)
60  * @author Tuukka Lehtonen (documentation)
61  */
62 public final class ElementTransforms {
63
64     private static final Logger LOGGER = LoggerFactory.getLogger(ElementTransforms.class);
65
66     public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW };
67
68     /**
69      * Align the specified set of diagram element resources in line with each
70      * other calculated by the specified side.
71      * 
72      * <p>
73      * Alignment requires at least two elements to do anything.
74      * 
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.
77      */
78     public static void align(final Resource resources[], final SIDE side) {
79         if (resources.length < 2)
80             return;
81         if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW )
82             return;
83
84         Simantics.getSession().asyncRequest(new WriteRequest() {
85
86             @Override
87             public void perform(WriteGraph graph) throws DatabaseException {
88                 graph.markUndoPoint();
89                 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
90
91                 List<AlignElement> elements = new ArrayList<AlignElement>();
92                 for (Resource r : resources) {
93                     AlignElement e = create(graph, hints, r);
94                     if (e != null)
95                         elements.add(e);
96                 }
97                 if (elements.size() < 2)
98                     return;
99                 double mx = 0;
100                 double my = 0;
101                 for (AlignElement e : elements) {
102                     mx += e.transform[4];
103                     my += e.transform[5];
104                 }
105                 mx /= elements.size();
106                 my /= elements.size();
107
108                 // prevent moving symbols into the same position
109                 int count = 0;
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) {
113                             count++;
114                         }
115                     } else {
116                         if (Math.abs(e.transform[4] - mx) < 0.1) {
117                             count++;
118                         }
119                     }
120                 }
121                 if (count > 1)
122                     return;
123
124                 if (side == SIDE.HORIZ || side == SIDE.VERT) {
125
126
127                     for (AlignElement e : elements) {
128
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});
133                         }
134                     }
135                 } else {
136                     double lx, rx;
137                     double ty, by;
138                     lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX();
139                     rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX();
140
141                     ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY();
142                     by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY();
143
144                     for (int i = 1; i < elements.size(); i++) {
145                         double tlx, trx;
146                         double tty, tby;
147                         tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX();
148                         trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX();
149
150                         tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY();
151                         tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY();
152                         if (tlx < lx)
153                             lx = tlx;
154                         if (trx > rx)
155                             rx = trx;
156                         if (tty < ty)
157                             ty = tty;
158                         if (tby > by)
159                             by = tby;
160
161                     }
162
163                     for (AlignElement e : elements) {
164                         mx = e.transform[4];
165                         my = e.transform[5];
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();
172                         } else {
173                             my = by - e.rotatedBounds.getMaxY();
174                         }
175                         DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my});
176
177                     }
178
179                 }
180
181             }
182         });
183     }
184
185     /**
186      * Distance specified set of diagram element resources equally. Distancing
187      * is performed on the specified side of the each element.
188      * 
189      * <p>
190      * Distancing requires at least three elements to work.
191      * 
192      * @param resources diagram element resources to rotate
193      * @param side the side of each element to use for distancing
194      */
195     public static void dist(final Resource resources[], final SIDE side) {
196
197         if (resources.length < 3)
198             return;
199
200         Simantics.getSession().asyncRequest(new WriteRequest() {
201
202             @Override
203             public void perform(WriteGraph graph) throws DatabaseException {
204                 graph.markUndoPoint();
205                 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
206
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);
211                     if (e != null)
212                         elements.add(e);
213                 }
214                 if (elements.size() < 3)
215                     return;
216                 switch (side) {
217                     case LEFT: {
218                         Collections.sort(elements, new XComparator());
219                         AlignElement left = elements.get(0);
220                         AlignElement right = elements.get(elements.size() - 1);
221
222                         double leftEdge = left.transform[4] + left.rotatedBounds.getMinX();
223                         double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
224
225                         double totalDist = rightEdge - leftEdge;
226                         double dist = totalDist / (elements.size() - 1);
227                         double d = leftEdge;
228
229                         for (int i = 1; i < elements.size() -1; i++) {
230                             d += dist;
231                             AlignElement e = elements.get(i);
232
233                             double mx = d - e.rotatedBounds.getMinX();
234
235                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
236                         }
237
238                         break;
239                     }
240                     case VERT: {
241                         Collections.sort(elements, new XComparator());
242                         AlignElement left = elements.get(0);
243                         AlignElement right = elements.get(elements.size() - 1);
244
245                         double leftEdge = left.transform[4];
246                         double rightEdge = right.transform[4];
247
248                         double totalDist = rightEdge - leftEdge;
249                         double dist = totalDist / (elements.size() - 1);
250                         double d = leftEdge;
251
252                         for (int i = 1; i < elements.size() -1; i++) {
253                             d += dist;
254                             AlignElement e = elements.get(i);
255
256                             double mx = d;
257
258                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
259                         }
260
261                         break;
262                     }
263                     case RIGHT:{
264                         Collections.sort(elements, new XComparator());
265                         AlignElement left = elements.get(0);
266                         AlignElement right = elements.get(elements.size() - 1);
267
268                         double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
269                         double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX();
270
271                         double totalDist = rightEdge - leftEdge;
272                         double dist = totalDist / (elements.size() - 1);
273                         double d = leftEdge;
274
275                         for (int i = 1; i < elements.size() -1; i++) {
276                             d += dist;
277                             AlignElement e = elements.get(i);
278
279                             double mx = d - e.rotatedBounds.getMaxX();
280
281                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
282                         }
283                         break;
284                     }
285                     case VERT_BTW:{
286                         Collections.sort(elements, new XComparator());
287                         AlignElement left = elements.get(0);
288                         AlignElement right = elements.get(elements.size() - 1);
289
290                         double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX();
291                         double rightEdge = right.transform[4] + right.rotatedBounds.getMinX();
292
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();
297                         }
298                         double totalAvail = totalDist - totalElementSize;
299                         double dist = totalAvail / (elements.size() - 1);
300                         double d = leftEdge;
301
302                         for (int i = 1; i < elements.size() -1; i++) {
303                             d += dist;
304                             AlignElement e = elements.get(i);
305
306                             double mx = d - e.rotatedBounds.getMinX();
307                             d += e.bounds.getWidth();
308
309                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]});
310                         }
311
312                         break;
313                     }
314                     case BOTTOM: {
315                         Collections.sort(elements, new YComparator());
316                         AlignElement top = elements.get(0);
317                         AlignElement bottom = elements.get(elements.size() - 1);
318
319                         double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
320                         double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY();
321
322                         double totalDist = bottomEdge - topEdge;
323                         double dist = totalDist / (elements.size() - 1);
324                         double d = topEdge;
325
326                         for (int i = 1; i < elements.size() -1; i++) {
327                             d += dist;
328                             AlignElement e = elements.get(i);
329
330                             double my = d - e.rotatedBounds.getMaxY();
331
332                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
333                         }
334
335                         break;
336                     }
337                     case TOP: {
338                         Collections.sort(elements, new YComparator());
339                         AlignElement top = elements.get(0);
340                         AlignElement bottom = elements.get(elements.size() - 1);
341
342                         double topEdge = top.transform[5] + top.rotatedBounds.getMinY();
343                         double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
344
345                         double totalDist = bottomEdge - topEdge;
346                         double dist = totalDist / (elements.size() - 1);
347                         double d = topEdge;
348
349                         for (int i = 1; i < elements.size() -1; i++) {
350                             d += dist;
351                             AlignElement e = elements.get(i);
352
353                             double my = d - e.rotatedBounds.getMinY();
354
355                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
356                         }
357
358                         break;
359                     }
360                     case HORIZ: {
361                         Collections.sort(elements, new YComparator());
362                         AlignElement top = elements.get(0);
363                         AlignElement bottom = elements.get(elements.size() - 1);
364
365                         double topEdge = top.transform[5];
366                         double bottomEdge = bottom.transform[5];
367
368                         double totalDist = bottomEdge - topEdge;
369                         double dist = totalDist / (elements.size() - 1);
370                         double d = topEdge;
371
372                         for (int i = 1; i < elements.size() -1; i++) {
373                             d += dist;
374                             AlignElement e = elements.get(i);
375
376                             double my = d;
377
378                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
379                         }
380
381                         break;
382                     }
383                     case HORIZ_BTW: {
384                         Collections.sort(elements, new YComparator());
385                         AlignElement top = elements.get(0);
386                         AlignElement bottom = elements.get(elements.size() - 1);
387
388                         double topEdge = top.transform[5] + top.rotatedBounds.getMaxY();
389                         double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY();
390
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();
395                         }
396                         double totalAvail = totalDist - totalElementSize;
397                         double dist = totalAvail / (elements.size() - 1);
398                         double d = topEdge;
399
400                         for (int i = 1; i < elements.size() -1; i++) {
401                             d += dist;
402                             AlignElement e = elements.get(i);
403
404                             double my = d - e.rotatedBounds.getMinY();
405                             d += e.rotatedBounds.getHeight();
406
407                             DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my});
408                         }
409
410                         break;
411                     }
412
413                 }
414
415
416             }
417         });
418
419     }
420
421     /**
422      * Rotate specified set of diagram element resources around the center of
423      * mass of the specified element selection.
424      * 
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
428      */
429     public static void rotate(final Resource resources[], final boolean clockwise) {
430         Simantics.getSession().asyncRequest(new WriteRequest() {
431             @Override
432             public void perform(WriteGraph graph) throws DatabaseException {
433                 graph.markUndoPoint();
434                 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
435
436                 DiagramResource DIA = DiagramResource.getInstance(graph);
437                 
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);
442                     if (e != null)
443                         elements.add(e);
444                     else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
445                         connections.add(r);
446                 }
447                 if (elements.size() < 1)
448                     return;
449
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")) );
453
454                 AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1)
455                         : AffineTransform.getQuadrantRotateInstance(3);
456
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);
461                         
462                         DiagramGraphUtil.changeTransform(graph, e.element, 
463                                 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]});
464                     }
465                 } else {
466                     Rectangle2D selectionBounds = null;
467                     for (AlignElement e : elements) {
468                         if (selectionBounds != null) {
469                             selectionBounds.add(e.transform[4], e.transform[5]);
470                         } else {
471                             selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
472                         }
473                     }
474                     
475                     double cx = selectionBounds.getCenterX();
476                     double cy = selectionBounds.getCenterY();
477
478                     for (AlignElement e : elements) {
479                         double x = e.transform[4];
480                         double y = e.transform[5];                        
481                         double dx = x - cx;
482                         double dy = y - cy;
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);
488                         
489                         DiagramGraphUtil.changeTransform(graph, e.element, 
490                                 new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my});
491                     }
492                     
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);
498                     }
499                 }
500             }
501         });
502     }
503
504     /**
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.
508      * 
509      * @param resources diagram element resources to flip
510      * @param xAxis <code>true</code> to flip around x-axis, <code>false</code>
511      *        for y-axis
512      */
513     public static void flip(final Resource resources[], final boolean xAxis) {
514         Simantics.getSession().asyncRequest(new WriteRequest() {
515             @Override
516             public void perform(WriteGraph graph) throws DatabaseException {
517                 graph.markUndoPoint();
518                 IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT);
519                 
520                 DiagramResource DIA = DiagramResource.getInstance(graph);
521
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);
526                     if (e != null)
527                         elements.add(e);
528                     else if(graph.isInstanceOf(r, DIA.RouteGraphConnection))
529                         connections.add(r);
530                 }
531                 if (elements.size() < 1)
532                     return;
533
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")) );
537
538                 if (elements.size() == 1 && connections.isEmpty()) {
539                     for (AlignElement e : elements) {
540                         if (xAxis) {
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]});
545                         } else {
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]});
550                         }
551                     }
552                 } else {
553
554                     Rectangle2D selectionBounds = null;
555                     for (AlignElement e : elements) {
556                         if (selectionBounds != null) {
557                             selectionBounds.add(e.transform[4], e.transform[5]);
558                         } else {
559                             selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0);
560                         }
561                     }
562
563                     for (AlignElement e : elements) {
564                         if (xAxis) {
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});
572                         } else {
573                             double x = e.transform[4];
574                             double cx = selectionBounds.getCenterX();
575                             double mx = cx + cx - x;
576                             
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]});
581                         }
582                     }
583                     
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());
591                     }
592                 }
593
594             }
595         });
596     }
597
598     private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException {
599         DiagramResource dr = DiagramResource.getInstance(graph);
600
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);
608             }
609         }
610         return null;
611     }
612
613
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;
620
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();
627         }
628
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();
632 //        }
633
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();
637         }
638
639         @SuppressWarnings("unused")
640         public double getDeterminant() {
641             return transform[0] * transform[3] - transform[1] * transform[2];
642         }
643
644     }
645
646     private static class XComparator implements Comparator<AlignElement> {
647         @Override
648         public int compare(AlignElement o1, AlignElement o2) {
649             if (o1.transform[4] < o2.transform[4])
650                 return -1;
651             if (o1.transform[4] > o2.transform[4])
652                 return 1;
653             return 0;
654
655         }
656     }
657
658     private static class YComparator implements Comparator<AlignElement> {
659         @Override
660         public int compare(AlignElement o1, AlignElement o2) {
661             if (o1.transform[5] < o2.transform[5])
662                 return -1;
663             if (o1.transform[5] > o2.transform[5])
664                 return 1;
665             return 0;
666
667         }
668     }
669
670     /**
671      * Set 2D affine transforms for the listed diagram element resources.
672      * 
673      * @param elements diagram element resources to set transforms for
674      * @param transforms transforms for each element
675      */
676     public static Write setTransformRequest(final Collection<TransformedObject> elements)
677     {
678         return new WriteRequest() {
679             @Override
680             public void perform(WriteGraph graph) throws DatabaseException {
681                 for (TransformedObject element : elements)
682                     DiagramGraphUtil.changeTransform(graph, element.element, element.transform);
683             }
684         };
685     }
686
687     /**
688      * Set 2D affine transforms for the listed diagram element resources.
689      * 
690      * @param undoContext the database undo context to use for the returned
691      *        request
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
696      */
697     public static Write concatenateTransformRequest(
698             UndoContext undoContext,
699             final Collection<TransformedObject> elements,
700             final boolean preConcatenate)
701     {
702         return new WriteRequest() {
703             @Override
704             public void perform(WriteGraph graph) throws DatabaseException {
705                 for (TransformedObject element : elements) {
706                     AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element);
707                     if (preConcatenate)
708                         at.preConcatenate(element.transform);
709                     else
710                         at.concatenate(element.transform);
711                     DiagramGraphUtil.setTransform(graph, element.element, at);
712                 }
713             }
714         };
715     }
716
717     public static class TransformedObject {
718         public final Resource        element;
719         public final AffineTransform transform;
720
721         public TransformedObject(Resource element) {
722             this.element = element;
723             this.transform = new AffineTransform();
724         }
725         public TransformedObject(Resource element, AffineTransform transform) {
726             this.element = element;
727             this.transform = transform;
728         }
729     }
730
731     // ------------------------------------------------------------------------
732
733     private interface TerminalFunction {
734         /**
735          * @param terminal
736          * @param rotationTheta in radians
737          * @return
738          */
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());
743         }
744         Resource toConnectionPoint(Resource terminal);
745         Resource toTerminal(Resource connectionPoint);
746         Set<Resource> connectionPoints();
747         Set<Resource> terminals();
748     }
749
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;
757         }
758         AffineTransform elementTransform = transform;
759
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));
764
765         return new TerminalFunction() {
766             @Override
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);
774                 return tr;
775             }
776             @Override
777             public Resource toConnectionPoint(Resource terminal) {
778                 return elementTerminals.getConnectionPoint(terminal);
779             }
780             @Override
781             public Resource toTerminal(Resource connectionPoint) {
782                 return elementTerminals.getTerminal(connectionPoint);
783             }
784             @Override
785             public Set<Resource> connectionPoints() {
786                 return elementTerminals.getConnectionPoints();
787             }
788             @Override
789             public Set<Resource> terminals() {
790                 return elementTerminals.getTerminals();
791             }
792         };
793     }
794
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)
801                 continue;
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())) );
807                         }
808                     }
809                 }
810             }
811         }
812         return result;
813     }
814
815     /**
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.
818      * 
819      * <p>
820      * Note that this requires that the element is connected to exactly two other
821      * elements.
822      * 
823      * @param graph
824      * @param element
825      * @return the chosen rotation in degrees
826      * @throws DatabaseException
827      */
828     public static double rotateToNeighborSlope(WriteGraph graph, Resource element) throws DatabaseException {
829         
830         if (LOGGER.isDebugEnabled())
831             LOGGER.debug("rotateAccordingToNeighborPositions( {} )", graph.getPossibleURI(element));
832
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<>();
837
838         for (Resource connectionPoint : elementTerminals.connectionPoints()) {
839             Set<Terminal> connectedTo = connectedTo(graph, element, connectionPoint);
840             if (connectedTo.isEmpty())
841                 continue;
842             if  (connectedTo.size() > 1) {
843                 if (LOGGER.isWarnEnabled())
844                     LOGGER.warn("rotateToNeighbors only supports functional connection points, ignoring {}", NameUtils.getURIOrSafeNameInternal(graph, connectionPoint));
845                 continue;
846             }
847
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);
853
854             if (LOGGER.isDebugEnabled())
855                 LOGGER.info("{} {} is connected to {}", NameUtils.getSafeName(graph, element), NameUtils.getSafeName(graph, connectionPoint), t.toString(graph));
856         }
857
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());
860             return Double.NaN;
861         }
862
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)
867         };
868
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) {
874             double cost = 0;
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);
881             }
882
883             if (LOGGER.isDebugEnabled())
884                 LOGGER.info("total cost of theta {} is {}", Math.toDegrees(thetas[i]), cost);
885
886             if (cost < minCost) {
887                 minCost = cost;
888                 selectedRotation = thetas[i];
889                 if (LOGGER.isDebugEnabled())
890                     LOGGER.debug("new minimum cost {} found", cost);
891             }
892         }
893
894         AffineTransform newTr = elementTerminals.transform(null, 0);
895         newTr.rotate(selectedRotation);
896         DiagramGraphUtil.setTransform(graph, element, newTr);
897
898         if (LOGGER.isDebugEnabled())
899             LOGGER.debug("set rotation to {} degrees", Math.toDegrees(selectedRotation));
900
901         return Math.toDegrees(selectedRotation);
902     }
903
904 }