]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java
Fix direction calculations in addComponent()
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / utils / ComponentUtils.java
1 package org.simantics.plant3d.utils;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.stream.Collectors;
9
10 import javax.vecmath.Vector3d;
11
12 import org.simantics.Simantics;
13 import org.simantics.db.ReadGraph;
14 import org.simantics.db.RequestProcessor;
15 import org.simantics.db.Resource;
16 import org.simantics.db.common.request.ReadRequest;
17 import org.simantics.db.common.utils.NameUtils;
18 import org.simantics.db.exception.DatabaseException;
19 import org.simantics.g3d.math.MathTools;
20 import org.simantics.g3d.scenegraph.GeometryProvider;
21 import org.simantics.g3d.scenegraph.ParametricGeometryProvider;
22 import org.simantics.layer0.Layer0;
23 import org.simantics.plant3d.geometry.ParameterRead;
24 import org.simantics.plant3d.ontology.Plant3D;
25 import org.simantics.plant3d.scenegraph.EndComponent;
26 import org.simantics.plant3d.scenegraph.Equipment;
27 import org.simantics.plant3d.scenegraph.InlineComponent;
28 import org.simantics.plant3d.scenegraph.Nozzle;
29 import org.simantics.plant3d.scenegraph.P3DRootNode;
30 import org.simantics.plant3d.scenegraph.PipeRun;
31 import org.simantics.plant3d.scenegraph.PipelineComponent;
32 import org.simantics.plant3d.scenegraph.TurnComponent;
33 import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint;
34 import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction;
35 import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.simantics.plant3d.scenegraph.controlpoint.PipingRules;
39
40 public class ComponentUtils {
41
42         private final static Logger LOGGER = LoggerFactory.getLogger(ComponentUtils.class);
43         
44         private static Map<String,Class<? extends PipelineComponent>> clazzes = new HashMap<String, Class<? extends PipelineComponent>>();
45         private static Map<String,GeometryProvider> providers = new HashMap<String,GeometryProvider>();
46         private static Map<String,String> names = new HashMap<String,String>();
47         
48         public static void preloadCache(RequestProcessor session) {
49                 try {
50                         session.syncRequest(new ReadRequest() {
51                                 
52                                 @Override
53                                 public void run(ReadGraph graph) throws DatabaseException {
54                                         List<String> types = new ArrayList<String>();
55                                         types.add(Plant3D.URIs.Builtin_Straight);
56                                         types.add(Plant3D.URIs.Builtin_Elbow);
57                                         types.add(Plant3D.URIs.Builtin_ConcentricReducer);
58                                         types.add(Plant3D.URIs.Builtin_BranchSplitComponent);
59                                         types.add(Plant3D.URIs.Builtin_EccentricReducer);
60                                         types.add(Plant3D.URIs.Builtin_Elbow45);
61                                         types.add(Plant3D.URIs.Builtin_Elbow90);
62                                         
63                                         for (String typeURI : types) {
64                                                 load(graph, typeURI);
65                                         }
66                                 }
67                         });
68                 } catch (DatabaseException e) {
69                         LOGGER.error("ComponentUtils.preloadCache() failed unexpectedly", e);
70                 }
71         }
72         
73         private static GeometryProvider getProvider(ReadGraph graph, Resource type) throws DatabaseException {
74                 
75                 Layer0 l0 = Layer0.getInstance(graph);
76                 Plant3D p3d = Plant3D.getInstance(graph);
77                 Resource geom = graph.getPossibleObject(type,p3d.hasGeometry);
78                 if (geom == null) {
79                         for (Resource a : graph.getObjects(type, l0.Asserts)) {
80                                 if (p3d.hasGeometry.equals(graph.getPossibleObject(a, l0.HasPredicate))) {
81                                         geom = graph.getPossibleObject(a, l0.HasObject);
82                                         break;
83                                 }
84                         }
85                 }
86                 if (geom != null) {
87                         GeometryProvider provider = graph.adapt(geom, GeometryProvider.class);
88                         if (provider instanceof ParametricGeometryProvider) {
89                         Map<String,Object> params = graph.syncRequest(new ParameterRead(type));
90                         if (params.size() > 0)
91                             ((ParametricGeometryProvider)provider).setProperties(params);
92                         }
93                         return provider;
94                 }
95                 return null;
96         }
97         
98         private static Class<? extends PipelineComponent> getClazz(ReadGraph graph, Resource type) throws DatabaseException {
99                 Plant3D p3d = Plant3D.getInstance(graph);
100                 if (graph.isInheritedFrom(type, p3d.InlineComponent))
101                         return InlineComponent.class;
102                 if (graph.isInheritedFrom(type, p3d.TurnComponent))
103                         return TurnComponent.class;
104                 if (graph.isInheritedFrom(type, p3d.EndComponent))
105                         return EndComponent.class;
106                 if (graph.isInheritedFrom(type, p3d.Nozzle))
107                         return Nozzle.class;
108                 return null;
109         }
110         
111         private static void load(ReadGraph graph, String typeURI) throws DatabaseException {
112                 Plant3D p3d = Plant3D.getInstance(graph);
113                 Resource type = graph.getResource(typeURI);
114                 
115                 GeometryProvider provider = getProvider(graph, type);
116                 if (provider != null || graph.hasStatement(type,p3d.NonVisibleComponent)) {
117                         providers.put(typeURI, provider);
118                         if (graph.isInheritedFrom(type, p3d.PipelineComponent))
119                                 clazzes.put(typeURI,getClazz(graph, type));
120                         names.put(typeURI, NameUtils.getSafeName(graph, type));
121                         return;
122                 }
123                 throw new DatabaseException("Cannot find component for " + typeURI);
124         }
125         
126         private static void load(final String typeURI) throws DatabaseException {
127                 Simantics.getSession().syncRequest(new ReadRequest() {
128                         
129                         @Override
130                         public void run(ReadGraph graph) throws DatabaseException {
131                                 load(graph,typeURI);    
132                         }
133                 });
134         }
135         
136         /**
137          * Creates a component
138          * 
139          * Does not set the name or add the component to a piperun.
140          * @param root
141          * @param typeURI
142          * @return
143          * @throws Exception
144          */
145         public static PipelineComponent createComponent(P3DRootNode root, String typeURI) throws Exception {
146                 Class<? extends PipelineComponent> type = clazzes.get(typeURI);
147                 GeometryProvider provider = providers.get(typeURI);
148                 if (type == null || provider == null) {
149                         load(typeURI);
150                         type = clazzes.get(typeURI);
151                         provider = providers.get(typeURI);
152                 }
153                 //PipelineComponent component = type.newInstance();
154                 PipelineComponent component = null;
155                 if (type == InlineComponent.class) {
156                         component = root.createInline();
157                 } else if (type == TurnComponent.class) {
158                         component = root.createTurn();
159                 } else if (type == EndComponent.class) {
160                         component = root.createTurn();
161                 } else if (type == Nozzle.class) {
162                         component = root.createNozzle();
163                 }
164                 component.setType(typeURI);
165                 component.setGeometry(provider);
166                 return component;
167         }
168         
169         /**
170          * Creates a equipment
171          * 
172          * Does not set the name
173          * 
174          * @param root
175          * @param typeURI
176          * @return
177          * @throws Exception
178          */
179         
180         public static Equipment createEquipment(P3DRootNode root, String typeURI) throws Exception {
181                 GeometryProvider provider = providers.get(typeURI);
182                 if (provider == null) {
183                         load(typeURI);
184                         provider = providers.get(typeURI);
185                 }
186                 Equipment equipment = root.createEquipment();
187                 equipment.setType(typeURI);
188                 equipment.setGeometry(provider);
189                 root.addChild(equipment);
190                 return equipment;
191         }
192         
193         public static Equipment createEquipmentWithNozzles(P3DRootNode root, String typeURI, String nozzleTypeUri) throws Exception {
194         GeometryProvider provider = providers.get(typeURI);
195         if (provider == null) {
196             load(typeURI);
197             provider = providers.get(typeURI);
198         }
199         Equipment equipment = root.createEquipment();
200         equipment.setType(typeURI);
201         equipment.setGeometry(provider);
202         root.addChild(equipment);
203         
204         for (int i = 0; i < equipment.numberOfFixedNozzles(); i++) {
205             createNozzle(root, equipment, new Item(nozzleTypeUri, "Nozzle"));
206             
207         }
208         
209         return equipment;
210     }
211         
212         public static InlineComponent createStraight(P3DRootNode root) throws Exception{
213                 InlineComponent component = root.createInline();
214                 component.setType(Plant3D.URIs.Builtin_Straight);
215                 component.setGeometry(providers.get(Plant3D.URIs.Builtin_Straight));
216                 return component;
217         }
218         
219         public static TurnComponent createTurn(P3DRootNode root) throws Exception {
220                 TurnComponent elbow = root.createTurn();
221                 elbow.setType(Plant3D.URIs.Builtin_Elbow);
222                 elbow.setGeometry(providers.get(Plant3D.URIs.Builtin_Elbow));
223                 return elbow;
224         }
225         
226         public static InlineComponent createReducer(P3DRootNode root) throws Exception {
227                 InlineComponent component = root.createInline();
228                 component.setType(Plant3D.URIs.Builtin_ConcentricReducer);
229                 component.setGeometry(providers.get(Plant3D.URIs.Builtin_ConcentricReducer));
230                 return component;
231         }
232         
233         public static InlineComponent createBranchSplit(P3DRootNode root) throws Exception {
234                 InlineComponent component = root.createInline();
235                 component.setType(Plant3D.URIs.Builtin_BranchSplitComponent);
236                 return component;
237         }
238         
239         public static Equipment createEquipment(P3DRootNode root, Item equipmentType) throws Exception {
240                 Equipment equipment = createEquipment(root, equipmentType.getUri());
241                 String n = root.getUniqueName(equipmentType.getName());
242                 equipment.setName(n);
243                 return equipment;
244         }
245         
246         public static Equipment createEquipmentWithNozzles(P3DRootNode root, Item equipmentType, Item nozzleType) throws Exception {
247         Equipment equipment = createEquipmentWithNozzles(root, equipmentType.getUri(), nozzleType.getUri());
248         String n = root.getUniqueName(equipmentType.getName());
249         equipment.setName(n);
250         return equipment;
251     }
252         
253         public static Nozzle createDefaultNozzle(P3DRootNode root, Equipment equipment) throws Exception {
254                 return createNozzle(root, equipment, new Item(Plant3D.URIs.Builtin_Nozzle, "Nozzle"));
255         }
256         
257         public static Nozzle createNozzle(P3DRootNode root, Equipment equipment, Item nozzleType) throws Exception {
258                 Nozzle nozzle = root.createNozzle();
259                 nozzle.setType(nozzleType.getUri());
260                 String n = root.getUniqueName(nozzleType.getName());
261                 nozzle.setName(n);
262                 PipeRun pipeRun = new PipeRun();
263                 n = root.getUniqueName("PipeRun");
264                 pipeRun.setName(n);
265                 nozzle.setPipeRun(pipeRun);
266                 
267                 equipment.addChild(nozzle);
268                 root.addChild(pipeRun);
269                 // root.getNodeMap().commit("Add nozzle " + n);
270                 return nozzle;
271         }
272         
273         public static class InsertInstruction {
274                 public String typeUri;
275                 
276                 public PositionType position = PositionType.NEXT;
277                 public PositionType insertPosition = PositionType.NEXT;
278
279                 // Component name
280                 public String name;
281                 
282                 // Reducer requires pipe specs
283                 public Double diameter;
284                 public Double thickness;
285                 public Double turnRadius;
286                 
287                 // Variable length 
288                 public Double length;
289                 
290                 // Variable angle
291                 public Double angle;
292                 
293                 // Rotation angle used with turns and rotated inline.
294                 public Double rotationAngle;
295
296                 public String getTypeUri() {
297                         return typeUri;
298                 }
299
300                 public void setTypeUri(String typeUri) {
301                         this.typeUri = typeUri;
302                 }
303
304                 public PositionType getPosition() {
305                         return position;
306                 }
307
308                 public void setPosition(PositionType position) {
309                         this.position = position;
310                 }
311
312                 public PositionType getInsertPosition() {
313                         return insertPosition;
314                 }
315
316                 public void setInsertPosition(PositionType insertPosition) {
317                         this.insertPosition = insertPosition;
318                 }
319
320                 public String getName() {
321                         return name;
322                 }
323
324                 public void setName(String name) {
325                         this.name = name;
326                 }
327
328                 public Double getDiameter() {
329                         return diameter;
330                 }
331
332                 public void setDiameter(Double diameter) {
333                         this.diameter = diameter;
334                 }
335                 
336                 public double getThickness() {
337                         return thickness;
338                 }
339                 
340                 public void setThickness(double thickness) {
341                         this.thickness = thickness;
342                 }
343
344                 public Double getTurnRadius() {
345                         return turnRadius;
346                 }
347
348                 public void setTurnRadius(Double turnRadius) {
349                         this.turnRadius = turnRadius;
350                 }
351
352                 public Double getLength() {
353                         return length;
354                 }
355
356                 public void setLength(Double length) {
357                         this.length = length;
358                 }
359
360                 public Double getAngle() {
361                         return angle;
362                 }
363
364                 public void setAngle(Double angle) {
365                         this.angle = angle;
366                 }
367                 
368                 public Double getRotationAngle() {
369             return rotationAngle;
370         }
371                 
372                 public void setRotationAngle(Double rotationAngle) {
373             this.rotationAngle = rotationAngle;
374         }
375
376         }
377         
378         public static PipelineComponent addComponent(P3DRootNode root, PipelineComponent component,  InsertInstruction inst) throws Exception {
379                 
380                 PipelineComponent newComponent = ComponentUtils.createComponent(root, inst.typeUri);
381                 PipeControlPoint newPcp = newComponent.getControlPoint();
382                 
383                 PipeControlPoint toPcp = component.getControlPoint();
384                 PipeRun pipeRun = toPcp.getPipeRun();
385                 
386                 String typeName = names.get(inst.typeUri);
387                 if (typeName == null)
388                         typeName = "Component";
389                 
390                 Vector3d dir = null;
391                 Vector3d pos = null;
392         
393                 PositionType position = inst.position;
394                 PositionType insertPosition = inst.insertPosition;
395                 boolean lengthAdjustable = false;
396                 if (newComponent instanceof InlineComponent) {
397                         lengthAdjustable = ((InlineComponent)newComponent).isVariableLength() || ((InlineComponent)newComponent).isModifialble(); 
398                 }
399                 boolean insertAdjustable = false;
400                 if (component instanceof InlineComponent) {
401                         insertAdjustable = ((InlineComponent)component).isVariableLength();
402                 }
403                 boolean sizeChange = false;
404                 if (newComponent instanceof InlineComponent) {
405                         sizeChange = ((InlineComponent)newComponent).isSizeChange();
406                 }
407                 
408                 // Calculate component position and direction vectors
409                 // 'dir' is a unit vector that represents the direction from 'component' to 'newComponent'
410                 if (toPcp.isInline()) {
411                         switch (position) {
412                         case NEXT: 
413                                 if (toPcp.isDualInline()) {
414                                         toPcp = toPcp.getDualSub();
415                                         pipeRun = toPcp.getPipeRun();
416                                 }
417                                 
418                                 break;
419                         case PREVIOUS:
420                                 if (toPcp.isDualSub()) {
421                                         toPcp = toPcp.parent;
422                                         pipeRun = toPcp.getPipeRun();
423                                 }
424                                 break;
425                         default:
426                                 break;
427                         }
428                         Vector3d start = new Vector3d();
429                         Vector3d end = new Vector3d();
430                         dir = new Vector3d();
431                         toPcp.getInlineControlPointEnds(start, end, dir);
432                         dir.normalize();
433                         switch (position) {
434                         case NEXT:
435                                 pos = new Vector3d(end);
436                                 break;
437                         case PREVIOUS:
438                                 pos = new Vector3d(start);
439                                 dir.negate();
440                                 break;
441                         case SPLIT:
442                                 pos = new Vector3d(toPcp.getWorldPosition());
443                                 break;
444                         default:
445                                 break;
446                         }
447                 } else if (toPcp.isDirected()) {
448                         // 'dir' always points out of a nozzle regardless of insertion direction
449                         dir = new Vector3d(toPcp.getDirectedControlPointDirection());
450                         pos = new Vector3d(toPcp.getWorldPosition());
451                 } else if (toPcp.isTurn() && toPcp.asFixedAngle()) {
452                         dir = new Vector3d(toPcp.getDirection(position == PositionType.NEXT ? Direction.NEXT : Direction.PREVIOUS));
453                         pos = new Vector3d(toPcp.getWorldPosition());
454                         if (!lengthAdjustable) {
455                                 Vector3d v = new Vector3d(dir);
456                                 v.scale(toPcp.getInlineLength());
457                                 pos.add(v);
458                         } else {
459                                 if (insertPosition == PositionType.NEXT) {
460                                         Vector3d v = new Vector3d(dir);
461                                         v.scale(toPcp.getInlineLength());
462                                         pos.add(v);
463                                 } else if (insertPosition == PositionType.SPLIT) {
464                                         // scale 0.5*length so that we don't remove the length twice from the new component
465                                         Vector3d v = new Vector3d(dir);
466                                         v.scale(toPcp.getInlineLength()*0.5);  
467                                         pos.add(v);
468                                 }
469                         }
470                 }
471                 
472                 if (inst.name != null) {
473                         newComponent.setName(inst.name);
474                 } else {
475                         String name = component.getPipeRun().getUniqueName(typeName);
476                         newComponent.setName(name);
477                 }
478
479                 pipeRun.addChild(newComponent);
480                 if (newPcp.isSizeChange())
481                         newComponent.setAlternativePipeRun(pipeRun);
482
483                 if (newComponent instanceof InlineComponent) {
484                     InlineComponent inlineComponent = (InlineComponent)newComponent;
485                     if (inlineComponent.isVariableLength()|| inlineComponent.isModifialble()) {
486                         newPcp.setLength(inst.length);
487                         newComponent.setParameter("length", inst.length);
488                     }
489                     if (inst.rotationAngle != null)
490                         ((InlineComponent) newComponent).setRotationAngle(inst.rotationAngle);
491                 } else if (newComponent instanceof TurnComponent) {
492                     TurnComponent turnComponent = (TurnComponent)newComponent;
493                     if  (turnComponent.isVariableAngle()) {
494                                 newPcp.setTurnAngle(Math.toRadians(inst.angle));
495                                 newComponent.setParameter("turnAngle", inst.angle);
496                     }
497                     if (inst.rotationAngle != null)
498                 ((TurnComponent) newComponent).setRotationAngle(inst.rotationAngle);
499                 }
500                 
501                 newComponent.updateParameters();
502                 
503                 Vector3d v = new Vector3d(dir);
504                 if (insertAdjustable) {
505                         // Prevent moving of adjacent components - always insert at end of a connected variable length component
506                         if (position == PositionType.NEXT && component.getNext() != null ||
507                                 position == PositionType.PREVIOUS && component.getPrevious() != null)
508                                 insertPosition = PositionType.PREVIOUS;
509                         
510                         if (insertPosition == PositionType.NEXT)
511                                 v.scale(newComponent.getControlPoint().getInlineLength());
512                         else if (insertPosition == PositionType.SPLIT)
513                                 v.set(0, 0, 0);
514                         else if (insertPosition == PositionType.PREVIOUS)
515                                 v.scale(-newComponent.getControlPoint().getInlineLength());
516                 } else {
517                         v.scale(newComponent.getControlPoint().getInlineLength());
518                 }
519                 
520                 switch (position) {
521                 case NEXT:
522                 case PREVIOUS:
523                         pos.add(v);
524                         break;
525                 case SPLIT:
526                         break;
527                 default:
528                         break;
529                 }
530                 
531                 switch (position) {
532                 case NEXT: 
533                         if (toPcp.isDualInline())
534                                 toPcp = toPcp.getDualSub();
535                         newPcp.setWorldPosition(pos);
536                         if (toPcp.getNext() != null)
537                                 PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, false);
538                         else
539                                 newPcp.insert(toPcp, Direction.NEXT);
540                         break;
541                 case PREVIOUS:
542                         if (toPcp.isDualSub())
543                                 toPcp = toPcp.parent;
544                         newPcp.setWorldPosition(pos);
545                         if (toPcp.getPrevious() != null)
546                                 PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, false);
547                         else
548                                 newPcp.insert(toPcp, Direction.PREVIOUS);
549                         break;
550                 case SPLIT:
551                         PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, true);
552                 default:
553                         break;
554                 }
555                 
556                 // Move the size change and the rest of the components in the pipe run to a new pipe run
557                 if (sizeChange) {
558                         PipeRun other = new PipeRun();
559                         String n = root.getUniqueName("PipeRun");
560                         other.setName(n);
561                         other.setPipeDiameter(inst.diameter);
562                         other.setPipeThickness(inst.thickness);
563                         other.setTurnRadius(inst.turnRadius);
564                         root.addChild(other);
565                         
566                         other.addChild(newComponent.getControlPoint().getDualSub());
567                         newComponent.setAlternativePipeRun(other);
568                         
569                         boolean forward = position != PositionType.PREVIOUS;
570                         PipelineComponent comp = forward ? newComponent.getNext() : newComponent.getPrevious();
571                         while (comp != null && comp.getPipeRun() == pipeRun) {
572                                 if (comp.getParent() == pipeRun) {
573                                         comp.deattach();
574                                         other.addChild(comp);
575                                 } else {
576                                         comp.setPipeRun(other);
577                                 }
578                                 
579                                 // Reset parameters to match new pipe run
580                                 comp.updateParameters();
581                                 
582                                 comp = forward ? comp.getNext() : comp.getPrevious();
583                         }
584                         
585                         newComponent.updateParameters();
586                 }
587                 
588                 return newComponent;
589         }
590         
591         public static boolean connect(PipelineComponent current, PipelineComponent endTo) throws Exception {
592                 return connect(current, endTo, null, null);
593         }
594         
595         /**
596          * Connects component to another component
597          * @param current
598          * @param endTo
599          * @param endType
600          * @param position
601          * @return
602          * @throws Exception
603          */
604         public static boolean connect(PipelineComponent current, PipelineComponent endTo, PositionType endType, Vector3d position) throws Exception{
605                 PipeControlPoint endCP = endTo.getControlPoint();
606                 boolean reversed;
607                 if (current.getNext() == null)
608                         reversed = false;
609                 else if (current.getPrevious() == null)
610                         reversed = true;
611                 else
612                         return false;
613                 
614                 PipeRun pipeRun = current.getPipeRun();
615                 P3DRootNode root = (P3DRootNode)current.getRootNode();
616                 PipeControlPoint currentCP = current.getControlPoint();
617                 
618                 if (endType == null || endType == PositionType.NEXT || endType == PositionType.PREVIOUS) {
619                         
620                         
621                         
622                         boolean requiresReverse = false;
623                         if (!reversed && endCP.getPrevious() != null) {
624                                 if (endCP.getNext() != null)
625                                         return false;
626                                 requiresReverse = true;
627                         } else if (reversed && endCP.getNext() != null) {
628                                 if (endCP.getPrevious() != null)
629                                         return false;
630                                 requiresReverse = true;
631                         }
632                         PipeRun other = endCP.getPipeRun();
633                         boolean mergeRuns = other == null ? true : pipeRun.canMerge(other);
634                         
635                         if (requiresReverse) {
636                                 // Pipe line must be traversible with next/previous relations without direction change.
637                                 // Now the component, where we are connecting the created pipeline is defined in different order.
638                                 PipingRules.reverse(other);
639                                 
640                         }
641
642                         if (mergeRuns) {
643                                 // Runs have compatible specs and must be merged
644                                 if (other != null && pipeRun != other)
645                                         pipeRun.merge(other);
646                                 else if (other == null) {
647                                         if (!(endTo instanceof Nozzle)) {
648                                                 pipeRun.addChild(endTo);
649                                         } else {
650                                                 endTo.setPipeRun(pipeRun);
651                                         }
652                                 }
653                                 if (!reversed) {
654                                         currentCP.setNext(endCP);
655                                         endCP.setPrevious(currentCP);
656                                 } else {
657                                         currentCP.setPrevious(endCP);
658                                         endCP.setNext(currentCP);
659                                 }
660                         } else {
661                                 // Runs do not have compatible specs, and a reducer must be attached in between.
662                                 InlineComponent reducer = ComponentUtils.createReducer(root);
663                                 PipeControlPoint pcp = reducer.getControlPoint();
664                                 
665                                 Vector3d endPos = endCP.getWorldPosition();
666                                 Vector3d currentPos = currentCP.getWorldPosition();
667                                 Vector3d v = new Vector3d(endPos);
668                                 v.sub(currentPos);
669                                 v.scale(0.5);
670                                 v.add(currentPos);
671                                 
672                                 PipingRules.addSizeChange(reversed, pipeRun, other, reducer, currentCP, endCP);
673                                 
674                                 pcp.setWorldPosition(v);
675                                 reducer.updateParameters();
676                         }
677                         PipingRules.positionUpdate(endCP);
678                         return true;
679                         
680                 } else if (endType == PositionType.SPLIT) {
681                         InlineComponent branchSplit = createBranchSplit((InlineComponent)endTo, position);
682                         if (branchSplit == null)
683                                 return false;
684                         PipeControlPoint branchSplitCP = branchSplit.getControlPoint();
685                         PipeControlPoint pcp = new PipeControlPoint(branchSplit,pipeRun);
686                         branchSplitCP.children.add(pcp);
687                         pcp.parent = branchSplitCP;
688                         pcp.setWorldOrientation(branchSplitCP.getWorldOrientation());
689                         pcp.setWorldPosition(branchSplitCP.getWorldPosition());
690                                                 
691                         
692                         if(!reversed) {
693                                 pcp.setPrevious(currentCP);
694                                 currentCP.setNext(pcp);
695                         } else {
696                                 pcp.setNext(currentCP);
697                                 currentCP.setPrevious(pcp);
698                         }
699                         PipingRules.positionUpdate(endCP);
700                         return true;
701                 }
702                 return false;
703         }
704         
705         public static InlineComponent createBranchSplit(InlineComponent component, Vector3d pos) throws Exception{
706                 if (!component.isVariableLength())
707                         return null;
708                 PipeRun pipeRun = component.getPipeRun();
709                 Vector3d sStart = new Vector3d();
710                 Vector3d sEnd = new Vector3d();
711                 component.getControlPoint().getInlineControlPointEnds(sStart, sEnd);
712                 
713                 if (MathTools.distance(sStart, sEnd) < (pipeRun.getPipeDiameter()*0.5))
714                         return null;
715                 
716                 
717                 Vector3d p = MathTools.closestPointOnEdge(new Vector3d(pos), sStart, sEnd);
718                 if (p == sStart) {
719                         Vector3d v = new Vector3d(sEnd);
720                         v.sub(sStart);
721                         v.normalize();
722                         v.scale(component.getPipeRun().getPipeDiameter()*0.5);
723                         p.add(v);
724                 } else if (p == sEnd) {
725                         Vector3d v = new Vector3d(sStart);
726                         v.sub(sEnd);
727                         v.normalize();
728                         v.scale(component.getPipeRun().getPipeDiameter()*0.5);
729                         p.add(v);
730                 }
731                 
732                 P3DRootNode root = (P3DRootNode)component.getRootNode();
733                 InlineComponent branchSplit = ComponentUtils.createBranchSplit(root);
734                 String branchName = component.getPipeRun().getUniqueName("Branch");
735                 branchSplit.setName(branchName);
736                 component.getPipeRun().addChild(branchSplit);
737                 PipeControlPoint branchSplitCP = branchSplit.getControlPoint();
738                 branchSplitCP.setWorldPosition(p);
739                 PipingRules.splitVariableLengthComponent(branchSplit, component, false);
740                 return branchSplit;
741         }
742
743         public static Collection<String> getPipelineComponentNames(P3DRootNode root) {
744                 Collection<String> usedNames = root.getChild().stream()
745                                 .filter(n -> n instanceof PipeRun)
746                                 .flatMap(n -> ((PipeRun)n).getChild().stream())
747                                 .filter(n -> n instanceof PipelineComponent)
748                                 .map(n -> ((PipelineComponent)n).getName())
749                                 .collect(Collectors.toSet());
750                 return usedNames;
751         }
752 }