From 22bb24d2a7e26c70b0dd4c57080f2c25ac3d40a8 Mon Sep 17 00:00:00 2001 From: Marko Luukkainen Date: Thu, 4 Apr 2019 15:55:24 +0300 Subject: [PATCH] Publish Plant3D feature Change-Id: If41206e5e25a27b027e4fb260d9ed43b1cbdf951 --- .../.project | 17 + .../build.properties | 1 + .../feature.xml | 58 + org.simantics.plant3d.ontology/.classpath | 7 + org.simantics.plant3d.ontology/.gitignore | 1 + org.simantics.plant3d.ontology/.project | 34 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 19 + .../build.properties | 5 + .../graph/images/Component.png | Bin 0 -> 511 bytes .../graph/images/Component.svg | 128 ++ .../graph/images/Elbow.png | Bin 0 -> 668 bytes .../graph/images/Elbow.svg | 127 ++ .../graph/images/Nozzle.png | Bin 0 -> 421 bytes .../graph/images/Nozzle.svg | 110 ++ .../graph/images/Straight.png | Bin 0 -> 298 bytes .../graph/images/Straight.svg | 96 + .../graph/images/eye.png | Bin 0 -> 310 bytes .../graph/images/eye.svg | 122 ++ .../graph/images/factory.png | Bin 0 -> 468 bytes .../graph/images/factory.svg | 308 +++ .../graph/images/point.png | Bin 0 -> 518 bytes .../graph/images/point.svg | 112 ++ .../graph/images/rotate.png | Bin 0 -> 718 bytes .../graph/images/rotate.svg | 108 ++ .../graph/images/tank.png | Bin 0 -> 428 bytes .../graph/images/tank.svg | 184 ++ .../graph/images/translate.png | Bin 0 -> 573 bytes .../graph/images/translate.svg | 117 ++ .../graph/images/translate_d.png | Bin 0 -> 363 bytes .../graph/images/translate_d.svg | 118 ++ .../graph/plant3d.pgraph | 158 ++ .../graph/plant3d_builtins.pgraph | 124 ++ .../graph/plant3d_viewpoint.pgraph | 73 + .../simantics/plant3d/ontology/Plant3D.java | 349 ++++ .../.project | 17 + .../build.properties | 1 + .../feature.xml | 46 + org.simantics.plant3d.product/.classpath | 7 + org.simantics.plant3d.product/.gitignore | 1 + org.simantics.plant3d.product/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 11 + .../build.properties | 6 + org.simantics.plant3d.product/plant3d.product | 52 + org.simantics.plant3d.product/plugin.xml | 21 + org.simantics.plant3d.product/splash.bmp | Bin 0 -> 679370 bytes .../simantics/plant3d/product/Activator.java | 30 + org.simantics.plant3d/.classpath | 7 + org.simantics.plant3d/.gitignore | 1 + org.simantics.plant3d/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + org.simantics.plant3d/META-INF/MANIFEST.MF | 39 + org.simantics.plant3d/adapters.xml | 45 + org.simantics.plant3d/build.properties | 7 + org.simantics.plant3d/icons/Component.png | Bin 0 -> 511 bytes org.simantics.plant3d/icons/Elbow.png | Bin 0 -> 668 bytes org.simantics.plant3d/icons/Nozzle.png | Bin 0 -> 421 bytes org.simantics.plant3d/icons/Straight.png | Bin 0 -> 298 bytes org.simantics.plant3d/icons/factory.png | Bin 0 -> 468 bytes org.simantics.plant3d/icons/middle.png | Bin 0 -> 2273 bytes org.simantics.plant3d/icons/middle.svg | 190 ++ org.simantics.plant3d/icons/plus.png | Bin 0 -> 2098 bytes org.simantics.plant3d/icons/plus.svg | 184 ++ org.simantics.plant3d/icons/tank.png | Bin 0 -> 428 bytes org.simantics.plant3d/icons/translate.png | Bin 0 -> 573 bytes org.simantics.plant3d/icons/translate_d.png | Bin 0 -> 363 bytes org.simantics.plant3d/icons/x-axis.png | Bin 0 -> 380 bytes org.simantics.plant3d/icons/x-axis.svg | 82 + org.simantics.plant3d/icons/y-axis.png | Bin 0 -> 340 bytes org.simantics.plant3d/icons/y-axis.svg | 83 + org.simantics.plant3d/icons/z-axis.png | Bin 0 -> 357 bytes org.simantics.plant3d/icons/z-axis.svg | 83 + org.simantics.plant3d/plugin.xml | 259 +++ .../src/org/simantics/plant3d/Activator.java | 50 + .../plant3d/actions/AddComponentAction.java | 268 +++ .../plant3d/actions/AddEquipmentAction.java | 33 + .../plant3d/actions/AddNozzleAction.java | 49 + .../plant3d/actions/RoutePipeAction.java | 1188 ++++++++++++ .../simantics/plant3d/browser/P3DBrowser.java | 15 + .../dialog/ComponentContentProvider.java | 26 + .../dialog/ComponentLabelProvider.java | 16 + .../dialog/ComponentSelectionDialog.java | 332 ++++ .../plant3d/editor/P3DContentOutlinePage.java | 151 ++ .../simantics/plant3d/editor/P3DNodeMap.java | 246 +++ .../plant3d/editor/Plant3DEditor.java | 498 +++++ .../geometry/BallValveGeometryProvider.java | 48 + .../geometry/BuiltinGeometryProvider.java | 29 + .../plant3d/geometry/CapGeometryProvider.java | 96 + .../geometry/CheckValveGeometryProvider.java | 47 + .../geometry/ElbowGeometryProvider.java | 47 + .../HorizontalTankGeometryProvider.java | 101 + .../geometry/NozzleGeometryProvider.java | 40 + .../geometry/PumpGeometryProvider.java | 72 + .../geometry/ReducerGeometryProvider.java | 57 + .../geometry/StraightGeometryProvider.java | 44 + .../VerticalTankGeometryProvider.java | 87 + .../gizmo/SplitPointSelectionGizmo.java | 151 ++ .../plant3d/gizmo/TerminalSelectionGizmo.java | 251 +++ .../plant3d/handlers/NewPlantHandler.java | 54 + .../project/P3DPerspectiveFactory.java | 14 + .../plant3d/project/P3DProjectFeature.java | 21 + .../property/P3DSelectionProcessor.java | 169 ++ .../plant3d/scenegraph/EndComponent.java | 53 + .../plant3d/scenegraph/Equipment.java | 125 ++ .../plant3d/scenegraph/GeometryNode.java | 239 +++ .../plant3d/scenegraph/IP3DNode.java | 11 + .../plant3d/scenegraph/IP3DVisualNode.java | 19 + .../plant3d/scenegraph/InlineComponent.java | 81 + .../simantics/plant3d/scenegraph/Nozzle.java | 153 ++ .../simantics/plant3d/scenegraph/P3DNode.java | 34 + .../scenegraph/P3DParentGeometryNode.java | 235 +++ .../plant3d/scenegraph/P3DParentNode.java | 194 ++ .../plant3d/scenegraph/P3DRootNode.java | 170 ++ .../plant3d/scenegraph/ParameterizedNode.java | 14 + .../simantics/plant3d/scenegraph/PipeRun.java | 161 ++ .../plant3d/scenegraph/PipelineComponent.java | 394 ++++ .../plant3d/scenegraph/SchemaBuilder.java | 45 + .../plant3d/scenegraph/TurnComponent.java | 83 + .../controlpoint/ControlPointFactory.java | 134 ++ .../controlpoint/PipeControlPoint.java | 1077 +++++++++++ .../scenegraph/controlpoint/PipingRules.java | 1651 +++++++++++++++++ .../plant3d/utils/ComponentUtils.java | 154 ++ .../src/org/simantics/plant3d/utils/Item.java | 79 + .../org/simantics/plant3d/utils/P3DUtil.java | 162 ++ 125 files changed, 13093 insertions(+) create mode 100644 org.simantics.plant3d.modeling.feature/.project create mode 100644 org.simantics.plant3d.modeling.feature/build.properties create mode 100644 org.simantics.plant3d.modeling.feature/feature.xml create mode 100644 org.simantics.plant3d.ontology/.classpath create mode 100644 org.simantics.plant3d.ontology/.gitignore create mode 100644 org.simantics.plant3d.ontology/.project create mode 100644 org.simantics.plant3d.ontology/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.simantics.plant3d.ontology/META-INF/MANIFEST.MF create mode 100644 org.simantics.plant3d.ontology/build.properties create mode 100644 org.simantics.plant3d.ontology/graph/images/Component.png create mode 100644 org.simantics.plant3d.ontology/graph/images/Component.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/Elbow.png create mode 100644 org.simantics.plant3d.ontology/graph/images/Elbow.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/Nozzle.png create mode 100644 org.simantics.plant3d.ontology/graph/images/Nozzle.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/Straight.png create mode 100644 org.simantics.plant3d.ontology/graph/images/Straight.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/eye.png create mode 100644 org.simantics.plant3d.ontology/graph/images/eye.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/factory.png create mode 100644 org.simantics.plant3d.ontology/graph/images/factory.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/point.png create mode 100644 org.simantics.plant3d.ontology/graph/images/point.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/rotate.png create mode 100644 org.simantics.plant3d.ontology/graph/images/rotate.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/tank.png create mode 100644 org.simantics.plant3d.ontology/graph/images/tank.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/translate.png create mode 100644 org.simantics.plant3d.ontology/graph/images/translate.svg create mode 100644 org.simantics.plant3d.ontology/graph/images/translate_d.png create mode 100644 org.simantics.plant3d.ontology/graph/images/translate_d.svg create mode 100644 org.simantics.plant3d.ontology/graph/plant3d.pgraph create mode 100644 org.simantics.plant3d.ontology/graph/plant3d_builtins.pgraph create mode 100644 org.simantics.plant3d.ontology/graph/plant3d_viewpoint.pgraph create mode 100644 org.simantics.plant3d.ontology/src/org/simantics/plant3d/ontology/Plant3D.java create mode 100644 org.simantics.plant3d.product.feature/.project create mode 100644 org.simantics.plant3d.product.feature/build.properties create mode 100644 org.simantics.plant3d.product.feature/feature.xml create mode 100644 org.simantics.plant3d.product/.classpath create mode 100644 org.simantics.plant3d.product/.gitignore create mode 100644 org.simantics.plant3d.product/.project create mode 100644 org.simantics.plant3d.product/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.simantics.plant3d.product/META-INF/MANIFEST.MF create mode 100644 org.simantics.plant3d.product/build.properties create mode 100644 org.simantics.plant3d.product/plant3d.product create mode 100644 org.simantics.plant3d.product/plugin.xml create mode 100644 org.simantics.plant3d.product/splash.bmp create mode 100644 org.simantics.plant3d.product/src/org/simantics/plant3d/product/Activator.java create mode 100644 org.simantics.plant3d/.classpath create mode 100644 org.simantics.plant3d/.gitignore create mode 100644 org.simantics.plant3d/.project create mode 100644 org.simantics.plant3d/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.simantics.plant3d/META-INF/MANIFEST.MF create mode 100644 org.simantics.plant3d/adapters.xml create mode 100644 org.simantics.plant3d/build.properties create mode 100644 org.simantics.plant3d/icons/Component.png create mode 100644 org.simantics.plant3d/icons/Elbow.png create mode 100644 org.simantics.plant3d/icons/Nozzle.png create mode 100644 org.simantics.plant3d/icons/Straight.png create mode 100644 org.simantics.plant3d/icons/factory.png create mode 100644 org.simantics.plant3d/icons/middle.png create mode 100644 org.simantics.plant3d/icons/middle.svg create mode 100644 org.simantics.plant3d/icons/plus.png create mode 100644 org.simantics.plant3d/icons/plus.svg create mode 100644 org.simantics.plant3d/icons/tank.png create mode 100644 org.simantics.plant3d/icons/translate.png create mode 100644 org.simantics.plant3d/icons/translate_d.png create mode 100644 org.simantics.plant3d/icons/x-axis.png create mode 100644 org.simantics.plant3d/icons/x-axis.svg create mode 100644 org.simantics.plant3d/icons/y-axis.png create mode 100644 org.simantics.plant3d/icons/y-axis.svg create mode 100644 org.simantics.plant3d/icons/z-axis.png create mode 100644 org.simantics.plant3d/icons/z-axis.svg create mode 100644 org.simantics.plant3d/plugin.xml create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/Activator.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/actions/AddComponentAction.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/actions/AddEquipmentAction.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/actions/AddNozzleAction.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/actions/RoutePipeAction.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/browser/P3DBrowser.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentContentProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentLabelProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentSelectionDialog.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DContentOutlinePage.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DNodeMap.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/editor/Plant3DEditor.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/BallValveGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/BuiltinGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/CapGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/CheckValveGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/ElbowGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/HorizontalTankGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/NozzleGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/PumpGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/ReducerGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/StraightGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/geometry/VerticalTankGeometryProvider.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/gizmo/SplitPointSelectionGizmo.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/gizmo/TerminalSelectionGizmo.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/handlers/NewPlantHandler.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/project/P3DPerspectiveFactory.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/project/P3DProjectFeature.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/property/P3DSelectionProcessor.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/EndComponent.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Equipment.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/GeometryNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DVisualNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/InlineComponent.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Nozzle.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentGeometryNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DRootNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/ParameterizedNode.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipeRun.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipelineComponent.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/SchemaBuilder.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/TurnComponent.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/ControlPointFactory.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/utils/Item.java create mode 100644 org.simantics.plant3d/src/org/simantics/plant3d/utils/P3DUtil.java diff --git a/org.simantics.plant3d.modeling.feature/.project b/org.simantics.plant3d.modeling.feature/.project new file mode 100644 index 00000000..e40eea0c --- /dev/null +++ b/org.simantics.plant3d.modeling.feature/.project @@ -0,0 +1,17 @@ + + + org.simantics.plant3d.modeling.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/org.simantics.plant3d.modeling.feature/build.properties b/org.simantics.plant3d.modeling.feature/build.properties new file mode 100644 index 00000000..64f93a9f --- /dev/null +++ b/org.simantics.plant3d.modeling.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/org.simantics.plant3d.modeling.feature/feature.xml b/org.simantics.plant3d.modeling.feature/feature.xml new file mode 100644 index 00000000..e307342b --- /dev/null +++ b/org.simantics.plant3d.modeling.feature/feature.xml @@ -0,0 +1,58 @@ + + + + + [Enter Feature Description here.] + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/.classpath b/org.simantics.plant3d.ontology/.classpath new file mode 100644 index 00000000..ad32c83a --- /dev/null +++ b/org.simantics.plant3d.ontology/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.plant3d.ontology/.gitignore b/org.simantics.plant3d.ontology/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/org.simantics.plant3d.ontology/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/org.simantics.plant3d.ontology/.project b/org.simantics.plant3d.ontology/.project new file mode 100644 index 00000000..bbacda45 --- /dev/null +++ b/org.simantics.plant3d.ontology/.project @@ -0,0 +1,34 @@ + + + org.simantics.plant3d.ontology + + + + + + org.simantics.graph.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.simantics.graph.nature + + diff --git a/org.simantics.plant3d.ontology/.settings/org.eclipse.jdt.core.prefs b/org.simantics.plant3d.ontology/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..c537b630 --- /dev/null +++ b/org.simantics.plant3d.ontology/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.simantics.plant3d.ontology/META-INF/MANIFEST.MF b/org.simantics.plant3d.ontology/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f74fbe6b --- /dev/null +++ b/org.simantics.plant3d.ontology/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Plant3D Ontology +Bundle-SymbolicName: org.simantics.plant3d.ontology +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: VTT +Require-Bundle: org.eclipse.core.runtime, + org.simantics.layer0;bundle-version="1.1.0", + org.simantics.g3d.ontology;bundle-version="1.0.0", + org.simantics.simulation.ontology;bundle-version="1.1.0", + org.simantics.viewpoint.ontology;bundle-version="1.2.0", + org.simantics.g3d.csg.ontology;bundle-version="1.0.0", + org.simantics.structural.ontology;bundle-version="1.2.0", + org.simantics.action.ontology;bundle-version="1.1.0", + org.simantics.image2.ontology;bundle-version="1.2.0", + org.simantics.silk.ontology;bundle-version="1.1.0", + org.simantics.project.ontology;bundle-version="1.2.0" +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Export-Package: org.simantics.plant3d.ontology diff --git a/org.simantics.plant3d.ontology/build.properties b/org.simantics.plant3d.ontology/build.properties new file mode 100644 index 00000000..e85b630a --- /dev/null +++ b/org.simantics.plant3d.ontology/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + graph.tg diff --git a/org.simantics.plant3d.ontology/graph/images/Component.png b/org.simantics.plant3d.ontology/graph/images/Component.png new file mode 100644 index 0000000000000000000000000000000000000000..0d43ef5b4718a1d7d94eb0bf8ce357d53cfef487 GIT binary patch literal 511 zcmVjEtjR z14t(?M05(k+v9&{W`8F@O8Mxz?sYz&U)i<|+qMzMF_cnJN}=2BGJwLq1Xit9%T}vZ zlu9Kyj)O1^5d;B(Ab{`t!OuvP9HNx+%CfBcR4R2Qgz%)4yj(7!lp3&JAs!ly2DjU7ZZ@0jdEN+s_{*DKM=t=l_=09Kne*Xrh)$=2D2gzj&usvgM6^iM zer4u2VrFEs*{4dSax0|-04x>@gkeZT1ZIX|7|7*vn_lBMMz7a + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/Elbow.png b/org.simantics.plant3d.ontology/graph/images/Elbow.png new file mode 100644 index 0000000000000000000000000000000000000000..bbaa16eaade500534559ad606a0c4a129d3f0925 GIT binary patch literal 668 zcmV;N0%QG&P)S4k@H7!oJO(3WkC^zv>^4&=lk8G zb1o^RaGOg1ZzB=i<(xlbj6EVEmx$hv$K#j(1c>N?l(Ox4-fp#8-7S~Pnh*l5R_o>o zh)Bz3v#&hQdwzU;?B?@%aL!>E26SBqW9)i>hz_KbuMZCo4{EhqMhF4RvS6Af48s6p z47#pw2Z*R#EEeB2o6SAfbs?ohE|-IC+b~TNoO7&JD-h9kKnU^X0fo!0C1hm}eNj^iMo&!bQ%AW0JVzK<{rF&qxj>-E%f zxl~sGBDz;9l};OthLy|ZAf<%kIEdpIqtOWcejn|2``c_b`w&IZ+r?sWegzOhoE;q< zl`PAGZQBq+fQT@eOmJ~=vFLWYAA=xxs+977pE~vH^%Up){?5)0Ow&X*n}u!L2*VJA z!2sQE_e&52kN*sB006c9{rx+cOa`2DaLy6OF`_6!r_=c!1i=%f)Q@c^PXU0YX + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/Nozzle.png b/org.simantics.plant3d.ontology/graph/images/Nozzle.png new file mode 100644 index 0000000000000000000000000000000000000000..48cf03e84d06cb054c09a8b71616c29ca1953d46 GIT binary patch literal 421 zcmV;W0b2fvP)L z)4ys0K@i9B@3|{-_y<9ED-#k3NE)j=LdtaZR%!Drd4d%75wSM-9#)bf&54EhXK%UP zyA&a36V4p|`uQ-!%mi7MaY-r{RhI=CjmDeSde!ZAKby_wD}XEmNGY#}!{Lp!mf37} zRT>aNpp+txV}uaoK&#by7DBuL{!9mVT5F6k1VM0{1GQT1VKf>Ilv0HqOs7+{)*KE8 zs?};c2f{F<-|y4w^$LN-Vu4bMFbt7Wo<>O16z3eyxk4aKQ;abfWB9!v31F>dxm>c@ zYzl$xc1sjR9FIqmBsl?b9M8w&@qJ-6Af+6v*Xy=12Jd~81H0XB0!+>Uola*SMbV?R z7VrI64*WOHIaaF`Ns{3G&&Go?&s4A1pDLBg6F`<_@B97!qZII0F8;=EYQ})S1$e9A P00000NkvXXu0mjf3ox$a literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/Nozzle.svg b/org.simantics.plant3d.ontology/graph/images/Nozzle.svg new file mode 100644 index 00000000..0dd815c8 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/Nozzle.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/Straight.png b/org.simantics.plant3d.ontology/graph/images/Straight.png new file mode 100644 index 0000000000000000000000000000000000000000..459919e55ac8003536d5cef3d71e67a2b3f4de6a GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ zFsXwu<36Df4xpf9iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0w^JzX3_ zEPCHwQsiqk5IOeIUYm0zL;K;!%XXZsx%#GTMrzSRHfsTQ2LX;1YPD~Vxa3aWm3qE@ z|NFg9L={@oc)I$ztaD0e0s!{CbJqX> literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/Straight.svg b/org.simantics.plant3d.ontology/graph/images/Straight.svg new file mode 100644 index 00000000..117e6ec1 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/Straight.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/eye.png b/org.simantics.plant3d.ontology/graph/images/eye.png new file mode 100644 index 0000000000000000000000000000000000000000..0e78cfd616eaac75e4461560d6d933cbb5a21716 GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#VfERCL1A#WBR9_v|D?u0sI=F88|_`4g)4 zHl;e8;4Na+_U?Yb`9S?fo;pfdKu8&*UbQ-lcD9iZY zyu#{t$TR0v(?bn5zImHj-rP*!p3}Hf<;0XnDvNH)`Y8p!+R43uY1^bYCliH?xk-n) zs@oRtxXLB(w8kUs>2`s&FQ?v~v+?2r-@pH+T1($y5UHM&7UQ@6E6`62p00i_>zopr E05N`ceE + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/factory.png b/org.simantics.plant3d.ontology/graph/images/factory.png new file mode 100644 index 0000000000000000000000000000000000000000..aad23e326f7983c5bfabdfb00dc7f54b0570cb63 GIT binary patch literal 468 zcmV;_0W1EAP)T z)4_|)Q5eSY&$-<@3(Z6!W@@IaSjkotWn{A8+S%Cp50p~FM9Rj(A0S!SAX&+zS&c23 zjc}D_)R^IC#t3uon0YPcjDFoAH$0oC&U@bPdEfV(lOh77l)5p11}fNNRa4l6R}s189 zPVB|@44lMtu41y>LKXVizmMTQZoS|Uwq^KoKCK(iBciEI)%JymSiy8k=^et)HP zfD&locd@c6oowo;f));Q?qW(sCyB3la3f;$C!mR=oSbo>_RLB4T# ze`BeiS|=^s=Iltsm46=M|G9;NqQR3nYVTJsI=V8eJ0000< KMNUMnLSTYCW87;1 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/factory.svg b/org.simantics.plant3d.ontology/graph/images/factory.svg new file mode 100644 index 00000000..e05205cb --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/factory.svg @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/point.png b/org.simantics.plant3d.ontology/graph/images/point.png new file mode 100644 index 0000000000000000000000000000000000000000..54bf5e492a4ca9c2e3270eb610bdd5e5fe177132 GIT binary patch literal 518 zcmV+h0{Q)kP)T zlg~;UQ5431r)Zf;2U8iw5gHdk+fM98`%m2|xaoRUd4fKZPmopKKrj!GMHkfyDx#H; zn23!Gxp$J$>B6fWbQFbtaNwMKzx#3iT>-uUhup_FFlGExV3flt0|aTBa@T+bAT_q| z04I2nqxvk2J#x1XtY!S?30Ri(*>RjIKoms)jVBBEp1-mD>1!Cgf9DQ|L$-H+g}&$2 zJKgU0Jb{-YvTWiV%d$SMd|3)VR4Q(}(_x?n%)BYNNs@%NZP&EcO<>OOFUJ56*fa-g zj^kA4gTU?fdK{np=KRk&ldmV4_NU#$qobmNy1jUl_Fep!zfcyDyt_+a{l*uOAcs@_3pO6EA5%lXSO5S307*qo IM6N<$g3dnWaR2}S literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/point.svg b/org.simantics.plant3d.ontology/graph/images/point.svg new file mode 100644 index 00000000..ee80b683 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/point.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/rotate.png b/org.simantics.plant3d.ontology/graph/images/rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..bcc829f0a0976dfab22de448a3ab974598d19035 GIT binary patch literal 718 zcmV;<0x|uGP)L zl09fsQ547j=e?WvlDwCsO;TIaf`S#nrAko{1h<0dAQVyD`hmDf2Ny?C5f{-(#a7Y5 z(ZR_<2cZm`C|GZM=XUkrPwaourxGo3iusj+vP;2vt8?Ow<~c z9ia{)ZK$iH=_WH1kMU8laoJLd*V~<4Iw{g~0DAYd$z;sc~@-|Gu1}%hGXO| z`FdgEpC?!7*U*6*%YMwnvR*#k_qEIJxfp4~^H&#I4rq!7 zI<4-*Ns+1pfEWDetcCMc$?_UB_snlrt1%-B@<^zbTjyN8+ZV0Kku`mgMOAUoVCfFWp%7!%yk=t~Hm^T7`ay$Oj|Q za=U>eo!q%ffL9J?;YXD(P2Ic&4K@sOgb0I*)i96p{3ZYZ*bkCV`kdYS z62dKMzzgJxS_&(+);daC9iL3Hzt63GKFvy<;j>)Y1faW=v8?!Xud^qL3#a(!BY9x* z0PPXTD>uFsYC&m8kLlx^002Ofloh5m>59(Yw3RD6A~ge;o-Y@z<)Bgs + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/tank.png b/org.simantics.plant3d.ontology/graph/images/tank.png new file mode 100644 index 0000000000000000000000000000000000000000..44015a37104357c720234e16a8932442708ceb4e GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4w z)A}ZAbKmKs=PpCfl%|8ft8Df?fBI@>-1~+9zD+AjOG`hQG9~9)_xZB5Vy4E$4oXwc zyK9Kph%Twwcfxh`)DMjROrO`x%Sm|lL-s;u*y`rXA1e-fJ8lk>W{_3StP=$HvPd$HI_DI4keB%0tTe(viR1B+oH!@7=GSEDv!mw1}iPMDWCk}5e z&*tAesf76o1LMp4J5+4)>kab+w@jCbIrjE-zRBjV25*>o9{OGS?f)nGVEiY>huZ!A U?w#r_z>sC|boFyt=akR{0MgH`0ssI2 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/tank.svg b/org.simantics.plant3d.ontology/graph/images/tank.svg new file mode 100644 index 00000000..8e8784f1 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/tank.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/translate.png b/org.simantics.plant3d.ontology/graph/images/translate.png new file mode 100644 index 0000000000000000000000000000000000000000..21f528479e112a28df292e9c4839846348daa870 GIT binary patch literal 573 zcmV-D0>b@?P)L zQoU-FQ4l?6=6;`vO;*7|g0Q;o#&j00+Qmu`MD`Un8xU4R(rBl>2wG?*cOM}n5Vi3I z1QUpjCh;$bA|jF$qMPsDI~KBm=w?lD+BtLPoH;`?!~c{FYSQyEJ8TWa?4*DlUUkez zVUUo*noY$AyH=oH+Yb3e?O^@USgHQRiMya)+x@Vv?{?wGm_h|9Rg)FN5#g|apZ=*p zyr&a4Vq|W}SBp?dX+l%X&AU=d{wDPiBWiWZ96;oG6`LOcwU9 zt}l)Y`8-;vs^s5d+?t5mZ8%vv=e%o#sW3Az#w|5wl}z%lM!Z&&mrk

+ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/images/translate_d.png b/org.simantics.plant3d.ontology/graph/images/translate_d.png new file mode 100644 index 0000000000000000000000000000000000000000..f4b3b6088d19e913aacba188588c001ca91bd2a9 GIT binary patch literal 363 zcmV-x0hIoUP)hM_!|ulpyOe}JWxb~VAi!(CWD|$KHAfxC-uQXfdYr9jS-I2IWa0}!nkp}|jkX}SRI(_yuzu$Y$ zi#v_lNqlco0Duu8IbbvaC4m9pl>rZeN{|7m0L+jTD-rxEEcTPy4QD)q2m>9U0m?Zm zW#wVv#p`EYOw%Y`0{|o~WsQx!@uRNSl~4S{YP;xq{D0;CfG^}Oi8C(bXEXo+002ov JPDHLkV1iKplV$(_ literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.ontology/graph/images/translate_d.svg b/org.simantics.plant3d.ontology/graph/images/translate_d.svg new file mode 100644 index 00000000..16669294 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/images/translate_d.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/org.simantics.plant3d.ontology/graph/plant3d.pgraph b/org.simantics.plant3d.ontology/graph/plant3d.pgraph new file mode 100644 index 00000000..8edc9f61 --- /dev/null +++ b/org.simantics.plant3d.ontology/graph/plant3d.pgraph @@ -0,0 +1,158 @@ +L0 = +PROJ = +SIM = +//STR = +G3D = + +P3D = : L0.Ontology + @L0.new + L0.HasResourceClass "org.simantics.plant3d.ontology.Plant3D" : L0.String + +P3D.ImportedOntologies : PROJ.NamespaceRequirement + L0.HasDescription "Specifies the ontologies required by a Plant3D Feature." : L0.String + PROJ.RequiresNamespace + "http://www.simantics.org/Layer0-1.0" : L0.URI + "http://www.simantics.org/Simulation-1.0" : L0.URI + +P3D.Node +P3D = + + +P3D.Builtin : L0.Library + +P3D.Builtin.GeometryProvider +VP = +PROJ = +SIM = +IMAGE = +ACT = +P3D = +CSG = +SILK = + + +PBC = P3D.P3DBrowseContext : VP.BrowseContext + VP.BrowseContext.IsIncludedIn PROJ.ProjectBrowseContext + @VP.constantImageRule P3D.Plant IMAGES.Factory + @VP.constantImageRule P3D.Equipment IMAGES.Tank + @VP.constantImageRule P3D.TurnComponent IMAGES.Elbow + @VP.constantImageRule P3D.Nozzle IMAGES.Nozzle + @VP.constantImageRule P3D.InlineComponent IMAGES.Component + @VP.constantImageRule P3D.Builtin.Straight IMAGES.Straight + @VP.constantImageRule P3D.EndComponent IMAGES.Component + @VP.constantImageRule P3D.PipeRun IMAGES.Straight + @VP.relationChildRule PROJ.Project L0.ConsistsOf P3D.Plant + @VP.relationChildRule P3D.Node P3D.childen P3D.Node + @VP.relationChildRule P3D.Node P3D.HasNozzle P3D.Node + + +IMAGES = P3D.Images : L0.Library +IMAGES.Component : IMAGE.PngImage + @L0.loadBytes "images/Component.png" +IMAGES.Elbow : IMAGE.PngImage + @L0.loadBytes "images/Elbow.png" +IMAGES.Factory : IMAGE.PngImage + @L0.loadBytes "images/factory.png" +IMAGES.Nozzle : IMAGE.PngImage + @L0.loadBytes "images/Nozzle.png" +IMAGES.Straight : IMAGE.PngImage + @L0.loadBytes "images/Straight.png" +IMAGES.Tank : IMAGE.PngImage + @L0.loadBytes "images/tank.png" + +MAC = P3D.P3DActionContext : VP.BrowseContext + VP.BrowseContext.IsIncludedIn PROJ.ProjectActionContext + + +MAC.newContribution : L0.Template + @template %actionContext %label %action %image + %actionContext + VP.BrowseContext.HasActionContribution _ : VP.ActionContribution + L0.HasLabel %label + VP.ActionContribution.HasImage %image + VP.ActionContribution.HasCategory VP.NewActionCategory + VP.ActionContribution.HasNodeType P3D.Plant + VP.ActionContribution.HasAction %action + +MAC.newTypeContribution : L0.Template + @template %actionContext %label %action %type %image + %actionContext + VP.BrowseContext.HasActionContribution _ : VP.ActionContribution + L0.HasLabel %label + VP.ActionContribution.HasImage %image + VP.ActionContribution.HasCategory VP.NewActionCategory + VP.ActionContribution.HasNodeType %type + VP.ActionContribution.HasAction %action + +MAC.editTypeContribution : L0.Template + @template %actionContext %label %action %type %image + %actionContext + VP.BrowseContext.HasActionContribution _ : VP.ActionContribution + L0.HasLabel %label + VP.ActionContribution.HasImage %image + VP.ActionContribution.HasCategory VP.EditActionCategory + VP.ActionContribution.HasNodeType %type + VP.ActionContribution.HasAction %action \ No newline at end of file diff --git a/org.simantics.plant3d.ontology/src/org/simantics/plant3d/ontology/Plant3D.java b/org.simantics.plant3d.ontology/src/org/simantics/plant3d/ontology/Plant3D.java new file mode 100644 index 00000000..394da937 --- /dev/null +++ b/org.simantics.plant3d.ontology/src/org/simantics/plant3d/ontology/Plant3D.java @@ -0,0 +1,349 @@ +package org.simantics.plant3d.ontology; + +import org.simantics.db.RequestProcessor; +import org.simantics.db.Resource; +import org.simantics.db.ReadGraph; +import org.simantics.db.request.Read; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.service.QueryControl; + +public class Plant3D { + + public final Resource Builtin; + public final Resource Builtin_BallValve; + public final Resource Builtin_BallValveGeometryProvider; + public final Resource Builtin_BranchSplitComponent; + public final Resource Builtin_Cap; + public final Resource Builtin_CapGeometryProvider; + public final Resource Builtin_CheckValve; + public final Resource Builtin_CheckValveGeometryProvider; + public final Resource Builtin_ConcentricReducer; + public final Resource Builtin_EccentricReducer; + public final Resource Builtin_Elbow; + public final Resource Builtin_ElbowGeometryProvider; + public final Resource Builtin_GeometryProvider; + public final Resource Builtin_HorizontalTank; + public final Resource Builtin_HorizontalTankGeometryProvider; + public final Resource Builtin_Nozzle; + public final Resource Builtin_NozzleGeometryProvider; + public final Resource Builtin_Pump; + public final Resource Builtin_PumpGeometryProvider; + public final Resource Builtin_ReducerGeometryProvider; + public final Resource Builtin_Straight; + public final Resource Builtin_StraightGeometryProvider; + public final Resource Builtin_VerticalTank; + public final Resource Builtin_VerticalTankGeometryProvider; + public final Resource CodeComponent; + public final Resource Connects; + public final Resource DualConnectedComponent; + public final Resource EndComponent; + public final Resource Equipment; + public final Resource FixedAngleTurnComponent; + public final Resource FixedLengthInlineComponent; + public final Resource HasAlternativePipeRun; + public final Resource HasBranch0; + public final Resource HasBranch1; + public final Resource HasBranch2; + public final Resource HasBranch3; + public final Resource HasBranch4; + public final Resource HasBranch5; + public final Resource HasBranch6; + public final Resource HasBranch7; + public final Resource HasBranch8; + public final Resource HasBranch9; + public final Resource HasNext; + public final Resource HasNozzle; + public final Resource HasNozzleDefinition; + public final Resource HasNozzleDefinition_Inverse; + public final Resource HasNozzleId; + public final Resource HasNozzleId_Inverse; + public final Resource HasNozzleRestriction; + public final Resource HasNozzleRestriction_Inverse; + public final Resource HasPipeDiameter; + public final Resource HasPipeDiameter_Inverse; + public final Resource HasPipeRun; + public final Resource HasPrevious; + public final Resource HasTurnAxis; + public final Resource HasTurnAxis_Inverse; + public final Resource HasTurnRadius; + public final Resource HasTurnRadius_Inverse; + public final Resource Images; + public final Resource Images_Component; + public final Resource Images_Elbow; + public final Resource Images_Factory; + public final Resource Images_Nozzle; + public final Resource Images_Straight; + public final Resource Images_Tank; + public final Resource ImportedOntologies; + public final Resource InlineComponent; + public final Resource IsReversed; + public final Resource IsReversed_Inverse; + public final Resource LibraryComponent; + public final Resource MultiConnectedComponent; + public final Resource NextInverse; + public final Resource Node; + public final Resource NonVisibleComponent; + public final Resource Nozzle; + public final Resource NozzleOf; + public final Resource OffsetComponent; + public final Resource P3DActionContext; + public final Resource P3DActionContext_editTypeContribution; + public final Resource P3DActionContext_newContribution; + public final Resource P3DActionContext_newTypeContribution; + public final Resource P3DBrowseContext; + public final Resource Parameter; + public final Resource PipeRun; + public final Resource PipelineComponent; + public final Resource PipelineComponentTag; + public final Resource Plant; + public final Resource PreviousInverse; + public final Resource SingleConnectedComponent; + public final Resource SizeChangeComponent; + public final Resource TurnComponent; + public final Resource VariableAngleTurnComponent; + public final Resource VariableLengthInlineComponent; + public final Resource childen; + public final Resource childen_Inverse; + public final Resource hasGeometry; + public final Resource hasParameter; + public final Resource hasParameterValue; + + public static class URIs { + public static final String Builtin = "http://www.simantics.org/Plant3D-0.1/Builtin"; + public static final String Builtin_BallValve = "http://www.simantics.org/Plant3D-0.1/Builtin/BallValve"; + public static final String Builtin_BallValveGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/BallValveGeometryProvider"; + public static final String Builtin_BranchSplitComponent = "http://www.simantics.org/Plant3D-0.1/Builtin/BranchSplitComponent"; + public static final String Builtin_Cap = "http://www.simantics.org/Plant3D-0.1/Builtin/Cap"; + public static final String Builtin_CapGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/CapGeometryProvider"; + public static final String Builtin_CheckValve = "http://www.simantics.org/Plant3D-0.1/Builtin/CheckValve"; + public static final String Builtin_CheckValveGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/CheckValveGeometryProvider"; + public static final String Builtin_ConcentricReducer = "http://www.simantics.org/Plant3D-0.1/Builtin/ConcentricReducer"; + public static final String Builtin_EccentricReducer = "http://www.simantics.org/Plant3D-0.1/Builtin/EccentricReducer"; + public static final String Builtin_Elbow = "http://www.simantics.org/Plant3D-0.1/Builtin/Elbow"; + public static final String Builtin_ElbowGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/ElbowGeometryProvider"; + public static final String Builtin_GeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/GeometryProvider"; + public static final String Builtin_HorizontalTank = "http://www.simantics.org/Plant3D-0.1/Builtin/HorizontalTank"; + public static final String Builtin_HorizontalTankGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/HorizontalTankGeometryProvider"; + public static final String Builtin_Nozzle = "http://www.simantics.org/Plant3D-0.1/Builtin/Nozzle"; + public static final String Builtin_NozzleGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/NozzleGeometryProvider"; + public static final String Builtin_Pump = "http://www.simantics.org/Plant3D-0.1/Builtin/Pump"; + public static final String Builtin_PumpGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/PumpGeometryProvider"; + public static final String Builtin_ReducerGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/ReducerGeometryProvider"; + public static final String Builtin_Straight = "http://www.simantics.org/Plant3D-0.1/Builtin/Straight"; + public static final String Builtin_StraightGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/StraightGeometryProvider"; + public static final String Builtin_VerticalTank = "http://www.simantics.org/Plant3D-0.1/Builtin/VerticalTank"; + public static final String Builtin_VerticalTankGeometryProvider = "http://www.simantics.org/Plant3D-0.1/Builtin/VerticalTankGeometryProvider"; + public static final String CodeComponent = "http://www.simantics.org/Plant3D-0.1/CodeComponent"; + public static final String Connects = "http://www.simantics.org/Plant3D-0.1/Connects"; + public static final String DualConnectedComponent = "http://www.simantics.org/Plant3D-0.1/DualConnectedComponent"; + public static final String EndComponent = "http://www.simantics.org/Plant3D-0.1/EndComponent"; + public static final String Equipment = "http://www.simantics.org/Plant3D-0.1/Equipment"; + public static final String FixedAngleTurnComponent = "http://www.simantics.org/Plant3D-0.1/FixedAngleTurnComponent"; + public static final String FixedLengthInlineComponent = "http://www.simantics.org/Plant3D-0.1/FixedLengthInlineComponent"; + public static final String HasAlternativePipeRun = "http://www.simantics.org/Plant3D-0.1/HasAlternativePipeRun"; + public static final String HasBranch0 = "http://www.simantics.org/Plant3D-0.1/HasBranch0"; + public static final String HasBranch1 = "http://www.simantics.org/Plant3D-0.1/HasBranch1"; + public static final String HasBranch2 = "http://www.simantics.org/Plant3D-0.1/HasBranch2"; + public static final String HasBranch3 = "http://www.simantics.org/Plant3D-0.1/HasBranch3"; + public static final String HasBranch4 = "http://www.simantics.org/Plant3D-0.1/HasBranch4"; + public static final String HasBranch5 = "http://www.simantics.org/Plant3D-0.1/HasBranch5"; + public static final String HasBranch6 = "http://www.simantics.org/Plant3D-0.1/HasBranch6"; + public static final String HasBranch7 = "http://www.simantics.org/Plant3D-0.1/HasBranch7"; + public static final String HasBranch8 = "http://www.simantics.org/Plant3D-0.1/HasBranch8"; + public static final String HasBranch9 = "http://www.simantics.org/Plant3D-0.1/HasBranch9"; + public static final String HasNext = "http://www.simantics.org/Plant3D-0.1/HasNext"; + public static final String HasNozzle = "http://www.simantics.org/Plant3D-0.1/HasNozzle"; + public static final String HasNozzleDefinition = "http://www.simantics.org/Plant3D-0.1/HasNozzleDefinition"; + public static final String HasNozzleDefinition_Inverse = "http://www.simantics.org/Plant3D-0.1/HasNozzleDefinition/Inverse"; + public static final String HasNozzleId = "http://www.simantics.org/Plant3D-0.1/HasNozzleId"; + public static final String HasNozzleId_Inverse = "http://www.simantics.org/Plant3D-0.1/HasNozzleId/Inverse"; + public static final String HasNozzleRestriction = "http://www.simantics.org/Plant3D-0.1/HasNozzleRestriction"; + public static final String HasNozzleRestriction_Inverse = "http://www.simantics.org/Plant3D-0.1/HasNozzleRestriction/Inverse"; + public static final String HasPipeDiameter = "http://www.simantics.org/Plant3D-0.1/HasPipeDiameter"; + public static final String HasPipeDiameter_Inverse = "http://www.simantics.org/Plant3D-0.1/HasPipeDiameter/Inverse"; + public static final String HasPipeRun = "http://www.simantics.org/Plant3D-0.1/HasPipeRun"; + public static final String HasPrevious = "http://www.simantics.org/Plant3D-0.1/HasPrevious"; + public static final String HasTurnAxis = "http://www.simantics.org/Plant3D-0.1/HasTurnAxis"; + public static final String HasTurnAxis_Inverse = "http://www.simantics.org/Plant3D-0.1/HasTurnAxis/Inverse"; + public static final String HasTurnRadius = "http://www.simantics.org/Plant3D-0.1/HasTurnRadius"; + public static final String HasTurnRadius_Inverse = "http://www.simantics.org/Plant3D-0.1/HasTurnRadius/Inverse"; + public static final String Images = "http://www.simantics.org/Plant3D-0.1/Images"; + public static final String Images_Component = "http://www.simantics.org/Plant3D-0.1/Images/Component"; + public static final String Images_Elbow = "http://www.simantics.org/Plant3D-0.1/Images/Elbow"; + public static final String Images_Factory = "http://www.simantics.org/Plant3D-0.1/Images/Factory"; + public static final String Images_Nozzle = "http://www.simantics.org/Plant3D-0.1/Images/Nozzle"; + public static final String Images_Straight = "http://www.simantics.org/Plant3D-0.1/Images/Straight"; + public static final String Images_Tank = "http://www.simantics.org/Plant3D-0.1/Images/Tank"; + public static final String ImportedOntologies = "http://www.simantics.org/Plant3D-0.1/ImportedOntologies"; + public static final String InlineComponent = "http://www.simantics.org/Plant3D-0.1/InlineComponent"; + public static final String IsReversed = "http://www.simantics.org/Plant3D-0.1/IsReversed"; + public static final String IsReversed_Inverse = "http://www.simantics.org/Plant3D-0.1/IsReversed/Inverse"; + public static final String LibraryComponent = "http://www.simantics.org/Plant3D-0.1/LibraryComponent"; + public static final String MultiConnectedComponent = "http://www.simantics.org/Plant3D-0.1/MultiConnectedComponent"; + public static final String NextInverse = "http://www.simantics.org/Plant3D-0.1/NextInverse"; + public static final String Node = "http://www.simantics.org/Plant3D-0.1/Node"; + public static final String NonVisibleComponent = "http://www.simantics.org/Plant3D-0.1/NonVisibleComponent"; + public static final String Nozzle = "http://www.simantics.org/Plant3D-0.1/Nozzle"; + public static final String NozzleOf = "http://www.simantics.org/Plant3D-0.1/NozzleOf"; + public static final String OffsetComponent = "http://www.simantics.org/Plant3D-0.1/OffsetComponent"; + public static final String P3DActionContext = "http://www.simantics.org/Plant3D-0.1/P3DActionContext"; + public static final String P3DActionContext_editTypeContribution = "http://www.simantics.org/Plant3D-0.1/P3DActionContext/editTypeContribution"; + public static final String P3DActionContext_newContribution = "http://www.simantics.org/Plant3D-0.1/P3DActionContext/newContribution"; + public static final String P3DActionContext_newTypeContribution = "http://www.simantics.org/Plant3D-0.1/P3DActionContext/newTypeContribution"; + public static final String P3DBrowseContext = "http://www.simantics.org/Plant3D-0.1/P3DBrowseContext"; + public static final String Parameter = "http://www.simantics.org/Plant3D-0.1/Parameter"; + public static final String PipeRun = "http://www.simantics.org/Plant3D-0.1/PipeRun"; + public static final String PipelineComponent = "http://www.simantics.org/Plant3D-0.1/PipelineComponent"; + public static final String PipelineComponentTag = "http://www.simantics.org/Plant3D-0.1/PipelineComponentTag"; + public static final String Plant = "http://www.simantics.org/Plant3D-0.1/Plant"; + public static final String PreviousInverse = "http://www.simantics.org/Plant3D-0.1/PreviousInverse"; + public static final String SingleConnectedComponent = "http://www.simantics.org/Plant3D-0.1/SingleConnectedComponent"; + public static final String SizeChangeComponent = "http://www.simantics.org/Plant3D-0.1/SizeChangeComponent"; + public static final String TurnComponent = "http://www.simantics.org/Plant3D-0.1/TurnComponent"; + public static final String VariableAngleTurnComponent = "http://www.simantics.org/Plant3D-0.1/VariableAngleTurnComponent"; + public static final String VariableLengthInlineComponent = "http://www.simantics.org/Plant3D-0.1/VariableLengthInlineComponent"; + public static final String childen = "http://www.simantics.org/Plant3D-0.1/childen"; + public static final String childen_Inverse = "http://www.simantics.org/Plant3D-0.1/childen/Inverse"; + public static final String hasGeometry = "http://www.simantics.org/Plant3D-0.1/hasGeometry"; + public static final String hasParameter = "http://www.simantics.org/Plant3D-0.1/hasParameter"; + public static final String hasParameterValue = "http://www.simantics.org/Plant3D-0.1/hasParameterValue"; + } + + public static Resource getResourceOrNull(ReadGraph graph, String uri) { + try { + return graph.getResource(uri); + } catch(DatabaseException e) { + System.err.println(e.getMessage()); + return null; + } + } + + public Plant3D(ReadGraph graph) { + Builtin = getResourceOrNull(graph, URIs.Builtin); + Builtin_BallValve = getResourceOrNull(graph, URIs.Builtin_BallValve); + Builtin_BallValveGeometryProvider = getResourceOrNull(graph, URIs.Builtin_BallValveGeometryProvider); + Builtin_BranchSplitComponent = getResourceOrNull(graph, URIs.Builtin_BranchSplitComponent); + Builtin_Cap = getResourceOrNull(graph, URIs.Builtin_Cap); + Builtin_CapGeometryProvider = getResourceOrNull(graph, URIs.Builtin_CapGeometryProvider); + Builtin_CheckValve = getResourceOrNull(graph, URIs.Builtin_CheckValve); + Builtin_CheckValveGeometryProvider = getResourceOrNull(graph, URIs.Builtin_CheckValveGeometryProvider); + Builtin_ConcentricReducer = getResourceOrNull(graph, URIs.Builtin_ConcentricReducer); + Builtin_EccentricReducer = getResourceOrNull(graph, URIs.Builtin_EccentricReducer); + Builtin_Elbow = getResourceOrNull(graph, URIs.Builtin_Elbow); + Builtin_ElbowGeometryProvider = getResourceOrNull(graph, URIs.Builtin_ElbowGeometryProvider); + Builtin_GeometryProvider = getResourceOrNull(graph, URIs.Builtin_GeometryProvider); + Builtin_HorizontalTank = getResourceOrNull(graph, URIs.Builtin_HorizontalTank); + Builtin_HorizontalTankGeometryProvider = getResourceOrNull(graph, URIs.Builtin_HorizontalTankGeometryProvider); + Builtin_Nozzle = getResourceOrNull(graph, URIs.Builtin_Nozzle); + Builtin_NozzleGeometryProvider = getResourceOrNull(graph, URIs.Builtin_NozzleGeometryProvider); + Builtin_Pump = getResourceOrNull(graph, URIs.Builtin_Pump); + Builtin_PumpGeometryProvider = getResourceOrNull(graph, URIs.Builtin_PumpGeometryProvider); + Builtin_ReducerGeometryProvider = getResourceOrNull(graph, URIs.Builtin_ReducerGeometryProvider); + Builtin_Straight = getResourceOrNull(graph, URIs.Builtin_Straight); + Builtin_StraightGeometryProvider = getResourceOrNull(graph, URIs.Builtin_StraightGeometryProvider); + Builtin_VerticalTank = getResourceOrNull(graph, URIs.Builtin_VerticalTank); + Builtin_VerticalTankGeometryProvider = getResourceOrNull(graph, URIs.Builtin_VerticalTankGeometryProvider); + CodeComponent = getResourceOrNull(graph, URIs.CodeComponent); + Connects = getResourceOrNull(graph, URIs.Connects); + DualConnectedComponent = getResourceOrNull(graph, URIs.DualConnectedComponent); + EndComponent = getResourceOrNull(graph, URIs.EndComponent); + Equipment = getResourceOrNull(graph, URIs.Equipment); + FixedAngleTurnComponent = getResourceOrNull(graph, URIs.FixedAngleTurnComponent); + FixedLengthInlineComponent = getResourceOrNull(graph, URIs.FixedLengthInlineComponent); + HasAlternativePipeRun = getResourceOrNull(graph, URIs.HasAlternativePipeRun); + HasBranch0 = getResourceOrNull(graph, URIs.HasBranch0); + HasBranch1 = getResourceOrNull(graph, URIs.HasBranch1); + HasBranch2 = getResourceOrNull(graph, URIs.HasBranch2); + HasBranch3 = getResourceOrNull(graph, URIs.HasBranch3); + HasBranch4 = getResourceOrNull(graph, URIs.HasBranch4); + HasBranch5 = getResourceOrNull(graph, URIs.HasBranch5); + HasBranch6 = getResourceOrNull(graph, URIs.HasBranch6); + HasBranch7 = getResourceOrNull(graph, URIs.HasBranch7); + HasBranch8 = getResourceOrNull(graph, URIs.HasBranch8); + HasBranch9 = getResourceOrNull(graph, URIs.HasBranch9); + HasNext = getResourceOrNull(graph, URIs.HasNext); + HasNozzle = getResourceOrNull(graph, URIs.HasNozzle); + HasNozzleDefinition = getResourceOrNull(graph, URIs.HasNozzleDefinition); + HasNozzleDefinition_Inverse = getResourceOrNull(graph, URIs.HasNozzleDefinition_Inverse); + HasNozzleId = getResourceOrNull(graph, URIs.HasNozzleId); + HasNozzleId_Inverse = getResourceOrNull(graph, URIs.HasNozzleId_Inverse); + HasNozzleRestriction = getResourceOrNull(graph, URIs.HasNozzleRestriction); + HasNozzleRestriction_Inverse = getResourceOrNull(graph, URIs.HasNozzleRestriction_Inverse); + HasPipeDiameter = getResourceOrNull(graph, URIs.HasPipeDiameter); + HasPipeDiameter_Inverse = getResourceOrNull(graph, URIs.HasPipeDiameter_Inverse); + HasPipeRun = getResourceOrNull(graph, URIs.HasPipeRun); + HasPrevious = getResourceOrNull(graph, URIs.HasPrevious); + HasTurnAxis = getResourceOrNull(graph, URIs.HasTurnAxis); + HasTurnAxis_Inverse = getResourceOrNull(graph, URIs.HasTurnAxis_Inverse); + HasTurnRadius = getResourceOrNull(graph, URIs.HasTurnRadius); + HasTurnRadius_Inverse = getResourceOrNull(graph, URIs.HasTurnRadius_Inverse); + Images = getResourceOrNull(graph, URIs.Images); + Images_Component = getResourceOrNull(graph, URIs.Images_Component); + Images_Elbow = getResourceOrNull(graph, URIs.Images_Elbow); + Images_Factory = getResourceOrNull(graph, URIs.Images_Factory); + Images_Nozzle = getResourceOrNull(graph, URIs.Images_Nozzle); + Images_Straight = getResourceOrNull(graph, URIs.Images_Straight); + Images_Tank = getResourceOrNull(graph, URIs.Images_Tank); + ImportedOntologies = getResourceOrNull(graph, URIs.ImportedOntologies); + InlineComponent = getResourceOrNull(graph, URIs.InlineComponent); + IsReversed = getResourceOrNull(graph, URIs.IsReversed); + IsReversed_Inverse = getResourceOrNull(graph, URIs.IsReversed_Inverse); + LibraryComponent = getResourceOrNull(graph, URIs.LibraryComponent); + MultiConnectedComponent = getResourceOrNull(graph, URIs.MultiConnectedComponent); + NextInverse = getResourceOrNull(graph, URIs.NextInverse); + Node = getResourceOrNull(graph, URIs.Node); + NonVisibleComponent = getResourceOrNull(graph, URIs.NonVisibleComponent); + Nozzle = getResourceOrNull(graph, URIs.Nozzle); + NozzleOf = getResourceOrNull(graph, URIs.NozzleOf); + OffsetComponent = getResourceOrNull(graph, URIs.OffsetComponent); + P3DActionContext = getResourceOrNull(graph, URIs.P3DActionContext); + P3DActionContext_editTypeContribution = getResourceOrNull(graph, URIs.P3DActionContext_editTypeContribution); + P3DActionContext_newContribution = getResourceOrNull(graph, URIs.P3DActionContext_newContribution); + P3DActionContext_newTypeContribution = getResourceOrNull(graph, URIs.P3DActionContext_newTypeContribution); + P3DBrowseContext = getResourceOrNull(graph, URIs.P3DBrowseContext); + Parameter = getResourceOrNull(graph, URIs.Parameter); + PipeRun = getResourceOrNull(graph, URIs.PipeRun); + PipelineComponent = getResourceOrNull(graph, URIs.PipelineComponent); + PipelineComponentTag = getResourceOrNull(graph, URIs.PipelineComponentTag); + Plant = getResourceOrNull(graph, URIs.Plant); + PreviousInverse = getResourceOrNull(graph, URIs.PreviousInverse); + SingleConnectedComponent = getResourceOrNull(graph, URIs.SingleConnectedComponent); + SizeChangeComponent = getResourceOrNull(graph, URIs.SizeChangeComponent); + TurnComponent = getResourceOrNull(graph, URIs.TurnComponent); + VariableAngleTurnComponent = getResourceOrNull(graph, URIs.VariableAngleTurnComponent); + VariableLengthInlineComponent = getResourceOrNull(graph, URIs.VariableLengthInlineComponent); + childen = getResourceOrNull(graph, URIs.childen); + childen_Inverse = getResourceOrNull(graph, URIs.childen_Inverse); + hasGeometry = getResourceOrNull(graph, URIs.hasGeometry); + hasParameter = getResourceOrNull(graph, URIs.hasParameter); + hasParameterValue = getResourceOrNull(graph, URIs.hasParameterValue); + } + + public static Plant3D getInstance(ReadGraph graph) { + Session session = graph.getSession(); + Plant3D ret = session.peekService(Plant3D.class); + if(ret == null) { + QueryControl qc = graph.getService(QueryControl.class); + ret = new Plant3D(qc.getIndependentGraph(graph)); + session.registerService(Plant3D.class, ret); + } + return ret; + } + + public static Plant3D getInstance(RequestProcessor session) throws DatabaseException { + Plant3D ret = session.peekService(Plant3D.class); + if(ret == null) { + ret = session.syncRequest(new Read() { + public Plant3D perform(ReadGraph graph) throws DatabaseException { + QueryControl qc = graph.getService(QueryControl.class); + return new Plant3D(qc.getIndependentGraph(graph)); + } + }); + session.registerService(Plant3D.class, ret); + } + return ret; + } + +} + diff --git a/org.simantics.plant3d.product.feature/.project b/org.simantics.plant3d.product.feature/.project new file mode 100644 index 00000000..e08a90ed --- /dev/null +++ b/org.simantics.plant3d.product.feature/.project @@ -0,0 +1,17 @@ + + + org.simantics.plant3d.product.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/org.simantics.plant3d.product.feature/build.properties b/org.simantics.plant3d.product.feature/build.properties new file mode 100644 index 00000000..64f93a9f --- /dev/null +++ b/org.simantics.plant3d.product.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/org.simantics.plant3d.product.feature/feature.xml b/org.simantics.plant3d.product.feature/feature.xml new file mode 100644 index 00000000..d5fcf108 --- /dev/null +++ b/org.simantics.plant3d.product.feature/feature.xml @@ -0,0 +1,46 @@ + + + + + [Enter Feature Description here.] + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.product/.classpath b/org.simantics.plant3d.product/.classpath new file mode 100644 index 00000000..ad32c83a --- /dev/null +++ b/org.simantics.plant3d.product/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.plant3d.product/.gitignore b/org.simantics.plant3d.product/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/org.simantics.plant3d.product/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/org.simantics.plant3d.product/.project b/org.simantics.plant3d.product/.project new file mode 100644 index 00000000..5ace73e5 --- /dev/null +++ b/org.simantics.plant3d.product/.project @@ -0,0 +1,28 @@ + + + org.simantics.plant3d.product + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.simantics.plant3d.product/.settings/org.eclipse.jdt.core.prefs b/org.simantics.plant3d.product/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..c537b630 --- /dev/null +++ b/org.simantics.plant3d.product/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.simantics.plant3d.product/META-INF/MANIFEST.MF b/org.simantics.plant3d.product/META-INF/MANIFEST.MF new file mode 100644 index 00000000..4730f353 --- /dev/null +++ b/org.simantics.plant3d.product/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Product +Bundle-SymbolicName: org.simantics.plant3d.product;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.simantics.plant3d.product.Activator +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.ui;bundle-version="3.7.0" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Vendor: VTT diff --git a/org.simantics.plant3d.product/build.properties b/org.simantics.plant3d.product/build.properties new file mode 100644 index 00000000..e1d2dead --- /dev/null +++ b/org.simantics.plant3d.product/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + splash.bmp diff --git a/org.simantics.plant3d.product/plant3d.product b/org.simantics.plant3d.product/plant3d.product new file mode 100644 index 00000000..66e34df5 --- /dev/null +++ b/org.simantics.plant3d.product/plant3d.product @@ -0,0 +1,52 @@ + + + + + + + + Simantics Plant3D + + + + + + + + --launcher.XXMaxPermSize +192m +-fixerrors + -ea +-Xmx600M +-Xshare:off +-XX:MaxPermSize=192m +-Djava.net.preferIPv4Stack=true +-Dide.gc=true + -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.product/plugin.xml b/org.simantics.plant3d.product/plugin.xml new file mode 100644 index 00000000..c7215caf --- /dev/null +++ b/org.simantics.plant3d.product/plugin.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/org.simantics.plant3d.product/splash.bmp b/org.simantics.plant3d.product/splash.bmp new file mode 100644 index 0000000000000000000000000000000000000000..aca08ba3357bc766d481a05d502f97d6e24b9979 GIT binary patch literal 679370 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 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d.product/src/org/simantics/plant3d/product/Activator.java b/org.simantics.plant3d.product/src/org/simantics/plant3d/product/Activator.java new file mode 100644 index 00000000..87c1fac3 --- /dev/null +++ b/org.simantics.plant3d.product/src/org/simantics/plant3d/product/Activator.java @@ -0,0 +1,30 @@ +package org.simantics.plant3d.product; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator { + + private static BundleContext context; + + static BundleContext getContext() { + return context; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + Activator.context = null; + } + +} diff --git a/org.simantics.plant3d/.classpath b/org.simantics.plant3d/.classpath new file mode 100644 index 00000000..ad32c83a --- /dev/null +++ b/org.simantics.plant3d/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.plant3d/.gitignore b/org.simantics.plant3d/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/org.simantics.plant3d/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/org.simantics.plant3d/.project b/org.simantics.plant3d/.project new file mode 100644 index 00000000..c40cd1af --- /dev/null +++ b/org.simantics.plant3d/.project @@ -0,0 +1,28 @@ + + + org.simantics.plant3d + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.simantics.plant3d/.settings/org.eclipse.jdt.core.prefs b/org.simantics.plant3d/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..c537b630 --- /dev/null +++ b/org.simantics.plant3d/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.simantics.plant3d/META-INF/MANIFEST.MF b/org.simantics.plant3d/META-INF/MANIFEST.MF new file mode 100644 index 00000000..6f2e691d --- /dev/null +++ b/org.simantics.plant3d/META-INF/MANIFEST.MF @@ -0,0 +1,39 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Plant3d +Bundle-SymbolicName: org.simantics.plant3d;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.simantics.plant3d.Activator +Bundle-Vendor: VTT +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.ui, + org.eclipse.ui, + org.simantics.db;bundle-version="1.1.0", + org.simantics.db.common;bundle-version="1.1.0", + org.simantics.db.layer0;bundle-version="1.1.0", + org.simantics.project;bundle-version="1.0.1", + org.simantics.plant3d.ontology;bundle-version="1.0.0", + org.simantics.g3d;bundle-version="1.0.0", + org.simantics.g3d.ontology;bundle-version="1.0.0", + org.simantics.g3d.csg.ontology;bundle-version="1.0.0", + org.simantics.selectionview;bundle-version="1.0.0", + org.simantics.selectionview.ontology;bundle-version="1.0.0", + org.simantics.browsing.ui.swt;bundle-version="1.1.0", + vtk;bundle-version="5.8.0", + org.simantics.g3d.vtk;bundle-version="1.0.0", + javax.vecmath;bundle-version="1.5.2", + org.eclipse.ui.views;bundle-version="3.5.1", + org.simantics.opencascade;bundle-version="1.0.0", + org.jcae.opencascade;bundle-version="6.5.2", + org.eclipse.ui.console;bundle-version="3.5.100", + org.simantics.objmap2;bundle-version="1.0.0", + org.apache.log4j;bundle-version="1.2.15", + org.simantics.ui;bundle-version="1.0.0", + org.simantics.opencascade.vtk;bundle-version="1.0.0", + org.simantics.browsing.ui.platform;bundle-version="1.1.0", + org.simantics.structural.ui;bundle-version="1.1.1" +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ActivationPolicy: lazy +Export-Package: org.simantics.plant3d.editor, + org.simantics.plant3d.property, + org.simantics.plant3d.scenegraph diff --git a/org.simantics.plant3d/adapters.xml b/org.simantics.plant3d/adapters.xml new file mode 100644 index 00000000..4fc03c1e --- /dev/null +++ b/org.simantics.plant3d/adapters.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.simantics.plant3d/build.properties b/org.simantics.plant3d/build.properties new file mode 100644 index 00000000..c6d4e0ab --- /dev/null +++ b/org.simantics.plant3d/build.properties @@ -0,0 +1,7 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + adapters.xml,\ + icons/ diff --git a/org.simantics.plant3d/icons/Component.png b/org.simantics.plant3d/icons/Component.png new file mode 100644 index 0000000000000000000000000000000000000000..0d43ef5b4718a1d7d94eb0bf8ce357d53cfef487 GIT binary patch literal 511 zcmVjEtjR z14t(?M05(k+v9&{W`8F@O8Mxz?sYz&U)i<|+qMzMF_cnJN}=2BGJwLq1Xit9%T}vZ zlu9Kyj)O1^5d;B(Ab{`t!OuvP9HNx+%CfBcR4R2Qgz%)4yj(7!lp3&JAs!ly2DjU7ZZ@0jdEN+s_{*DKM=t=l_=09Kne*Xrh)$=2D2gzj&usvgM6^iM zer4u2VrFEs*{4dSax0|-04x>@gkeZT1ZIX|7|7*vn_lBMMz7aS4k@H7!oJO(3WkC^zv>^4&=lk8G zb1o^RaGOg1ZzB=i<(xlbj6EVEmx$hv$K#j(1c>N?l(Ox4-fp#8-7S~Pnh*l5R_o>o zh)Bz3v#&hQdwzU;?B?@%aL!>E26SBqW9)i>hz_KbuMZCo4{EhqMhF4RvS6Af48s6p z47#pw2Z*R#EEeB2o6SAfbs?ohE|-IC+b~TNoO7&JD-h9kKnU^X0fo!0C1hm}eNj^iMo&!bQ%AW0JVzK<{rF&qxj>-E%f zxl~sGBDz;9l};OthLy|ZAf<%kIEdpIqtOWcejn|2``c_b`w&IZ+r?sWegzOhoE;q< zl`PAGZQBq+fQT@eOmJ~=vFLWYAA=xxs+977pE~vH^%Up){?5)0Ow&X*n}u!L2*VJA z!2sQE_e&52kN*sB006c9{rx+cOa`2DaLy6OF`_6!r_=c!1i=%f)Q@c^PXU0YXL z)4ys0K@i9B@3|{-_y<9ED-#k3NE)j=LdtaZR%!Drd4d%75wSM-9#)bf&54EhXK%UP zyA&a36V4p|`uQ-!%mi7MaY-r{RhI=CjmDeSde!ZAKby_wD}XEmNGY#}!{Lp!mf37} zRT>aNpp+txV}uaoK&#by7DBuL{!9mVT5F6k1VM0{1GQT1VKf>Ilv0HqOs7+{)*KE8 zs?};c2f{F<-|y4w^$LN-Vu4bMFbt7Wo<>O16z3eyxk4aKQ;abfWB9!v31F>dxm>c@ zYzl$xc1sjR9FIqmBsl?b9M8w&@qJ-6Af+6v*Xy=12Jd~81H0XB0!+>Uola*SMbV?R z7VrI64*WOHIaaF`Ns{3G&&Go?&s4A1pDLBg6F`<_@B97!qZII0F8;=EYQ})S1$e9A P00000NkvXXu0mjf3ox$a literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/Straight.png b/org.simantics.plant3d/icons/Straight.png new file mode 100644 index 0000000000000000000000000000000000000000..459919e55ac8003536d5cef3d71e67a2b3f4de6a GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ zFsXwu<36Df4xpf9iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0w^JzX3_ zEPCHwQsiqk5IOeIUYm0zL;K;!%XXZsx%#GTMrzSRHfsTQ2LX;1YPD~Vxa3aWm3qE@ z|NFg9L={@oc)I$ztaD0e0s!{CbJqX> literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/factory.png b/org.simantics.plant3d/icons/factory.png new file mode 100644 index 0000000000000000000000000000000000000000..aad23e326f7983c5bfabdfb00dc7f54b0570cb63 GIT binary patch literal 468 zcmV;_0W1EAP)T z)4_|)Q5eSY&$-<@3(Z6!W@@IaSjkotWn{A8+S%Cp50p~FM9Rj(A0S!SAX&+zS&c23 zjc}D_)R^IC#t3uon0YPcjDFoAH$0oC&U@bPdEfV(lOh77l)5p11}fNNRa4l6R}s189 zPVB|@44lMtu41y>LKXVizmMTQZoS|Uwq^KoKCK(iBciEI)%JymSiy8k=^et)HP zfD&locd@c6oowo;f));Q?qW(sCyB3la3f;$C!mR=oSbo>_RLB4T# ze`BeiS|=^s=Iltsm46=M|G9;NqQR3nYVTJsI=V8eJ0000< KMNUMnLSTYCW87;1 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/middle.png b/org.simantics.plant3d/icons/middle.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3d03e7280286f7d28c28f26d64f3c4a2c4e76a GIT binary patch literal 2273 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 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/middle.svg b/org.simantics.plant3d/icons/middle.svg new file mode 100644 index 00000000..670c3b42 --- /dev/null +++ b/org.simantics.plant3d/icons/middle.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/org.simantics.plant3d/icons/plus.png b/org.simantics.plant3d/icons/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..1d6b816ff72c869fcb19beae7dc84d4e506f9a6c GIT binary patch literal 2098 zcmV-22+jA2P)j%Zi6Us(k6A{sQXpjR~l9@-{S+B$U|$4=AKZtA8D zX-Fw>Z{M659=>&a$yQ0rkw)LQv*$bi|D4Or8D*{IYD6jJ0kKRb6KJi2Fbunm zF}(nekWLDz4T9$iAYcV!gjP#fC=2sNp->WvC1Ymi zOXWi^{QKq6H^v?V&UFgQWdxK`emb2VeB`?i{rTX*dv+uf#?7XL-mI{3gAjOv?+Qvq z;Hyc5p-^uMrLu7Lyzs%S*tvqybu4bChaUXXjXSUJ?@HF3>#h-USs~`Ftk@V~t{_ajE!67K@4y#^b_iQ~ zBa*%=WYa=C7QIU-Zgr_|`xkGy`H9y@#~xHleXyJp-ik9w4-ens6{g=4N)=T!nV%{qJ^1vXWX+h)skJY%lR_8XQ)Qi^IaM=T?1H*Uyd}G2%^>o$f%?fdU6)AAf#X6Z z4V!zR*;;GE+Gw?l68s5>#rS4P?6D z(MRB$UyVLkEnx^J-h@B?6&6a-TqX^VJ`8(yf$y)?cOB^KjcjTYVN{ktge9Qh0Xo#= z=kl1T8QdM)p(_J>c7y5At27!>fyIKgs~p?~2M#p~xBp>s+l=G#5`qN!Lk?fkFu;PC>qi7Q;y(bby92JX@`j zpFRU;|4Wp3>I!4MTtUCljBHPzfohe!F+2-2oOYWu*=#ZK&J5wpN8!Reh>ccz+2}-K z^<0N<#6~t>J__&55H?#(0F%~Q?X*o)wC2f+1!hM^v15}^s(}3%gE(9+AERegWNT~^ zMnzMTWa_uYmX z9)@k#MMr8f8P)ZrM6{qaRI5-Z!rAxX@L_mu1bcdlMybSWTJuw`)RwOz;Q{@w%P(Rv zhBx)F<@zt+4Qz+OK^WK`ooa47fTE#NfvIVjn24Td-a%h1aG}}cun526YA+_MNH{?P;oY0y-0LS>GSF;WwNdY%G z4&QPdb_D^OT$hAWBmu0YDnhN*;-WFUX$;Q*ll(uI(+)&DAO`qA0CWSr?Z4-NBG3Xt cKEd_;f0wF@FkrQV4FCWD07*qoM6N<$f?ay!P5=M^ literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/plus.svg b/org.simantics.plant3d/icons/plus.svg new file mode 100644 index 00000000..7338a019 --- /dev/null +++ b/org.simantics.plant3d/icons/plus.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.simantics.plant3d/icons/tank.png b/org.simantics.plant3d/icons/tank.png new file mode 100644 index 0000000000000000000000000000000000000000..44015a37104357c720234e16a8932442708ceb4e GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4w z)A}ZAbKmKs=PpCfl%|8ft8Df?fBI@>-1~+9zD+AjOG`hQG9~9)_xZB5Vy4E$4oXwc zyK9Kph%Twwcfxh`)DMjROrO`x%Sm|lL-s;u*y`rXA1e-fJ8lk>W{_3StP=$HvPd$HI_DI4keB%0tTe(viR1B+oH!@7=GSEDv!mw1}iPMDWCk}5e z&*tAesf76o1LMp4J5+4)>kab+w@jCbIrjE-zRBjV25*>o9{OGS?f)nGVEiY>huZ!A U?w#r_z>sC|boFyt=akR{0MgH`0ssI2 literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/translate.png b/org.simantics.plant3d/icons/translate.png new file mode 100644 index 0000000000000000000000000000000000000000..21f528479e112a28df292e9c4839846348daa870 GIT binary patch literal 573 zcmV-D0>b@?P)L zQoU-FQ4l?6=6;`vO;*7|g0Q;o#&j00+Qmu`MD`Un8xU4R(rBl>2wG?*cOM}n5Vi3I z1QUpjCh;$bA|jF$qMPsDI~KBm=w?lD+BtLPoH;`?!~c{FYSQyEJ8TWa?4*DlUUkez zVUUo*noY$AyH=oH+Yb3e?O^@USgHQRiMya)+x@Vv?{?wGm_h|9Rg)FN5#g|apZ=*p zyr&a4Vq|W}SBp?dX+l%X&AU=d{wDPiBWiWZ96;oG6`LOcwU9 zt}l)Y`8-;vs^s5d+?t5mZ8%vv=e%o#sW3Az#w|5wl}z%lM!Z&&mrk

hM_!|ulpyOe}JWxb~VAi!(CWD|$KHAfxC-uQXfdYr9jS-I2IWa0}!nkp}|jkX}SRI(_yuzu$Y$ zi#v_lNqlco0Duu8IbbvaC4m9pl>rZeN{|7m0L+jTD-rxEEcTPy4QD)q2m>9U0m?Zm zW#wVv#p`EYOw%Y`0{|o~WsQx!@uRNSl~4S{YP;xq{D0;CfG^}Oi8C(bXEXo+002ov JPDHLkV1iKplV$(_ literal 0 HcmV?d00001 diff --git a/org.simantics.plant3d/icons/x-axis.png b/org.simantics.plant3d/icons/x-axis.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb8a0d501db42ac4a22fcac9306857c57fcf42c GIT binary patch literal 380 zcmV-?0fYXDP)L z(!VRlVH5@M&-IE(QDnAA5{pHV + + + + + + + + image/svg+xml + + + + + + + X + + + diff --git a/org.simantics.plant3d/icons/y-axis.png b/org.simantics.plant3d/icons/y-axis.png new file mode 100644 index 0000000000000000000000000000000000000000..ca60724b69e1fba257632d01d2f964f9d6ac2767 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#VfERP@Z##WBR9cj+ZVFXlvnqaW`dnc(Eo z%@X*5sY`5;c?5U<14)~Pr3n+YBO)p|yn|Owy|F@l!&ad!Q#Te&OquL^=gC2l{2A}h ze|Xk?W=luZVKt>P + + + + + + + + image/svg+xml + + + + + + + Y + + + diff --git a/org.simantics.plant3d/icons/z-axis.png b/org.simantics.plant3d/icons/z-axis.png new file mode 100644 index 0000000000000000000000000000000000000000..11a574179c2b2cea5920ef2a9f50b2f89c7217dd GIT binary patch literal 357 zcmV-r0h<1aP)L z(y=atQ4j^-Z`i186hxvEjVMH-@Bmh!(})M)8N7i+sZw|V51?cpK%$a}XlaGShH4W# znl1KU|K80q$rSg_oH=vOy^)#z6OF+`L^RPZ^dDvBV+Av~K}0mLQv*E4GBz`_Q>kQT zF>!!Ttk>4ZfH&}k{gFKd@B*H3QX;8%FyuwM&%T$Ly-}_ + + + + + + + + image/svg+xml + + + + + + + Z + + + diff --git a/org.simantics.plant3d/plugin.xml b/org.simantics.plant3d/plugin.xml new file mode 100644 index 00000000..e256683a --- /dev/null +++ b/org.simantics.plant3d/plugin.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/Activator.java b/org.simantics.plant3d/src/org/simantics/plant3d/Activator.java new file mode 100644 index 00000000..dcb18b5b --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/Activator.java @@ -0,0 +1,50 @@ +package org.simantics.plant3d; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.simantics.plant3d"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddComponentAction.java b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddComponentAction.java new file mode 100644 index 00000000..bf220dd8 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddComponentAction.java @@ -0,0 +1,268 @@ +package org.simantics.plant3d.actions; + +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.HashSet; +import java.util.Set; + +import javax.vecmath.Vector3d; + +import org.eclipse.swt.widgets.Display; +import org.simantics.g3d.scenegraph.NodeMap; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.vtk.action.vtkAction; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.dialog.ComponentSelectionDialog; +import org.simantics.plant3d.gizmo.TerminalSelectionGizmo; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; +import org.simantics.plant3d.utils.ComponentUtils; +import org.simantics.plant3d.utils.Item; +import org.simantics.plant3d.utils.Item.Type; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.ExceptionUtils; + +import vtk.vtkProp; + +public class AddComponentAction extends vtkAction { + + + private P3DRootNode root; + private PipelineComponent component; + private NodeMap nodeMap; + + private TerminalSelectionGizmo gizmo; + + private Set allowed = new HashSet(); + + private Item toAdd = null; + + public AddComponentAction(InteractiveVtkPanel panel, P3DRootNode root) { + super(panel); + this.root = root; + setText("Add Component"); + setImageDescriptor(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Component.png")); + nodeMap = root.getNodeMap(); + gizmo = new TerminalSelectionGizmo(panel); + } + + public void setComponent(PipelineComponent component) { + this.component = component; + + allowed.clear(); + if (component.getNext() == null) { + allowed.add(PositionType.NEXT); + } + if (component.getPrevious() == null) { + allowed.add(PositionType.PREVIOUS); + } + if (component instanceof InlineComponent && !component.getControlPoint().isFixed()){ + allowed.add(PositionType.SPLIT); + } + setEnabled(allowed.size() > 0); + } + + private Double length; + private Double angle; + private Double diameter; + private Double turnRadius; + + @Override + public void run() { + ComponentSelectionDialog dialog = new ComponentSelectionDialog(Display.getCurrent().getActiveShell(), allowed); + if (dialog.open() == ComponentSelectionDialog.CANCEL) + return; + toAdd = dialog.getSelected(); + if (toAdd == null) + return; + this.length = dialog.getLength(); + this.angle = dialog.getAngle(); + this.diameter = dialog.getDiameter(); + this.turnRadius = dialog.getTurnRadius(); + allowed = dialog.filterAllowed(); + gizmo.setComponent(component, allowed); + super.run(); + panel.repaint(); + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) + panel.useDefaultAction(); + + + } + + public void attach() { + if (component == null) + return; + + super.attach(); + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + public void run() { + attachUI(); + } + }); + + } + + public void deattach() { +// deactivate(); + component = null; + nodeMap.commit(); + deattachUI(); + super.deattach(); + panel.repaint(); + } + + private void attachUI() { + //panel.setCursor(activeCursor); + gizmo.attach(panel.GetRenderer()); + } + + private void deattachUI() { + //panel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + gizmo.deattach(); + } + + @Override + public void mouseMoved(MouseEvent e) { + panel.getDefaultAction().mouseMoved(e); + } + + @Override + public void mousePressed(MouseEvent e) { + panel.getDefaultAction().mousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + panel.getDefaultAction().mouseReleased(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + panel.getDefaultAction().mouseDragged(e); + } + + public void doInsert(PositionType position) { + try { + PipelineComponent newComponent = ComponentUtils.createComponent(root,toAdd.getUri()); + PipeControlPoint newPcp = newComponent.getControlPoint(); + + PipeControlPoint toPcp = component.getControlPoint(); + Vector3d start = new Vector3d(); + Vector3d end = new Vector3d(); + Vector3d dir = new Vector3d(); + toPcp.getInlineControlPointEnds(start, end, dir); + dir.normalize(); + + switch (position) { + case NEXT: + if (toPcp.isDualInline()) + toPcp = toPcp.getSubPoint().get(0); + + break; + case PREVIOUS: + if (toPcp.isDualSub()) + toPcp = toPcp.parent; + } + PipeRun pipeRun = toPcp.getPipeRun(); + + if (!toAdd.isSizeChange()) { + String name = component.getPipeRun().getUniqueName(toAdd.getName()); + newComponent.setName(name); + + pipeRun.addChild(newComponent); + if (toAdd.isVariable()) { + // TODO: these options are not stored into DB. Should they?! + if (toAdd.getType() == Type.INLINE) { + newPcp.setLength(length); +// newPcp.setFixed(true); + } else if (toAdd.getType() == Type.TURN) { + newPcp.setTurnAngle(angle); +// newPcp.setFixed(true); + } + } + newComponent.updateParameters(); + + dir.scale(newComponent.getControlPoint().getLength()*0.5); + start.sub(dir); + end.add(dir); + switch (position) { + case NEXT: + if (toPcp.isDualInline()) + toPcp = toPcp.getSubPoint().get(0); + newPcp.insert(toPcp, Direction.NEXT); + newPcp.setWorldPosition(end); + break; + case PREVIOUS: + if (toPcp.isDualSub()) + toPcp = toPcp.parent; + newPcp.insert(toPcp, Direction.PREVIOUS); + newPcp.setWorldPosition(start); + break; + case SPLIT: + PipingRules.splitVariableLengthComponent(newComponent, (InlineComponent)component, true); + } + } else { + + + PipeRun other = new PipeRun(); + String n = root.getUniqueName("PipeRun"); + other.setName(n); + other.setPipeDiameter(diameter); + other.setTurnRadius(turnRadius); + root.addChild(other); + + + + if (position == PositionType.NEXT) { + PipingRules.addSizeChange(false, pipeRun, other, (InlineComponent)newComponent, toPcp, null); + newPcp.setWorldPosition(end); + } else if (position == PositionType.PREVIOUS){ + PipingRules.addSizeChange(true, pipeRun, other, (InlineComponent)newComponent, toPcp, null); + newPcp.setWorldPosition(start); + } + // TODO : chicken-egg problem + newComponent.updateParameters(); + dir.scale(newComponent.getControlPoint().getLength()*0.5); + start.sub(dir); + end.add(dir); + if (position == PositionType.NEXT) { + newPcp.setWorldPosition(end); + } else if (position == PositionType.PREVIOUS){ + newPcp.setWorldPosition(start); + } + } + + + } catch (Exception e) { + ExceptionUtils.logAndShowError("Cannot add component", e); + } + } + + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { + int type = panel.getPickType(); + panel.setPickType(0); + vtkProp[] picked = panel.pick(e.getX(), e.getY()); + panel.setPickType(type); + PositionType position = gizmo.getPickedPosition(picked); + if (position != null) { + doInsert(position); + panel.useDefaultAction(); + return; + } + } + panel.getDefaultAction().mouseClicked(e); + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddEquipmentAction.java b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddEquipmentAction.java new file mode 100644 index 00000000..6c66909a --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddEquipmentAction.java @@ -0,0 +1,33 @@ +package org.simantics.plant3d.actions; + +import org.eclipse.jface.action.Action; +import org.simantics.plant3d.scenegraph.Equipment; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.utils.Item; +import org.simantics.utils.ui.ExceptionUtils; + +public class AddEquipmentAction extends Action { + + P3DRootNode root; + private Item item; + + public AddEquipmentAction(P3DRootNode root, Item item) { + this.root = root; + this.item = item; + setText("Add " + item.getName()); + } + + @Override + public void run() { + try { + Equipment equipment = root.createEquipment(); + equipment.setType(item.getUri()); + String n = root.getUniqueName(item.getName()); + equipment.setName(n); + root.addChild(equipment); + root.getNodeMap().commit(); + } catch (Exception e) { + ExceptionUtils.logAndShowError("Cannot create equipment",e); + } + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddNozzleAction.java b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddNozzleAction.java new file mode 100644 index 00000000..e6b8ecd6 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/actions/AddNozzleAction.java @@ -0,0 +1,49 @@ +package org.simantics.plant3d.actions; + +import org.eclipse.jface.action.Action; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.scenegraph.Equipment; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.utils.Item; +import org.simantics.utils.ui.ExceptionUtils; + +public class AddNozzleAction extends Action { + + P3DRootNode root; + private Item item; + private Equipment equipment; + + public AddNozzleAction(P3DRootNode root, Item item) { + this.root = root; + this.item = item; + setText("Add " + item.getName()); + setImageDescriptor(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Nozzle.png")); + } + + public void setEquipment(Equipment equipment) { + this.equipment = equipment; + } + + @Override + public void run() { + try { + Nozzle nozzle = root.createNozzle(); + nozzle.setType(item.getUri()); + String n = root.getUniqueName(item.getName()); + nozzle.setName(n); + PipeRun pipeRun = new PipeRun(); + n = root.getUniqueName("PipeRun"); + pipeRun.setName(n); + nozzle.setPipeRun(pipeRun); + + equipment.addChild(nozzle); + //root.addChild(nozzle); + root.addChild(pipeRun); + root.getNodeMap().commit(); + } catch (Exception e) { + ExceptionUtils.logAndShowError("Cannot create equipment",e); + } + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/actions/RoutePipeAction.java b/org.simantics.plant3d/src/org/simantics/plant3d/actions/RoutePipeAction.java new file mode 100644 index 00000000..0310311d --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/actions/RoutePipeAction.java @@ -0,0 +1,1188 @@ +package org.simantics.plant3d.actions; + +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.vecmath.Point3d; +import javax.vecmath.Tuple3d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.math.Ray; +import org.simantics.g3d.scenegraph.NodeMap; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.tools.ConstraintDetector; +import org.simantics.g3d.tools.DummyConstraintDetector; +import org.simantics.g3d.vtk.action.vtkAction; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.g3d.vtk.gizmo.TranslateAxisGizmo; +import org.simantics.g3d.vtk.utils.vtkUtil; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.gizmo.SplitPointSelectionGizmo; +import org.simantics.plant3d.gizmo.TerminalSelectionGizmo; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.EndComponent; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.TurnComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; +import org.simantics.plant3d.utils.ComponentUtils; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.ExceptionUtils; + +import vtk.vtkProp; +import vtk.vtkTextActor; + +public class RoutePipeAction extends vtkAction { + enum LockType { + X, Y, Z, XY, YZ, XZ, NONE, CUSTOM + }; + + LockType lock = LockType.NONE; + private double BRANCH_SNAP_DISTANCE = 0.05; + private double NOZZLE_SNAP_DISTANCE = 0.05; + + private double istep = 10.0; + private int decimals = 2; + + private P3DRootNode root; + private PipelineComponent startComponent; + private PipeRun pipeRun; + + private TranslateAxisGizmo translateAxisGizmo = new TranslateAxisGizmo(); + private SplitPointSelectionGizmo splitPointSelectionGizmo; + private TerminalSelectionGizmo terminalSelectionGizmo; + private NodeMap nodeMap; + + private enum ToolState{NOT_ACTIVE, INITIALIZING, SELECTING_POSITION, SELECTING_SPLIT, ROUTING}; + private ToolState state = ToolState.NOT_ACTIVE; + + private ConstraintDetector detector = new DummyConstraintDetector(); + + private boolean useDefault = false; + private Vector3d direction = null; + private Vector3d previousPosition = null; + private Vector3d currentPosition = null; + + boolean step = false; + + PipelineComponent endTo = null; + PositionType endType = null; + PipeControlPoint endPort = null; + + boolean reversed = false; + + private Set allowed = new HashSet(); + + + public RoutePipeAction(InteractiveVtkPanel panel, P3DRootNode root) { + super(panel); + this.root = root; + setText("Route Pipe"); + setImageDescriptor(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Straight.png")); + nodeMap = root.getNodeMap(); + splitPointSelectionGizmo = new SplitPointSelectionGizmo(panel); + terminalSelectionGizmo = new TerminalSelectionGizmo(panel); + } + + public void setComponent(PipelineComponent component) { + this.startComponent = component; + allowed.clear(); + if (this.startComponent.getNext() == null) + allowed.add(PositionType.NEXT); + if (this.startComponent.getPrevious() == null && !(this.startComponent instanceof Nozzle)) + allowed.add(PositionType.PREVIOUS); + if (this.startComponent instanceof InlineComponent && !this.startComponent.getControlPoint().isFixed()) + allowed.add(PositionType.SPLIT); + setEnabled(allowed.size() > 0); + } + + public void deattach() { + deactivate(); + startComponent = null; + nodeMap.commit(); + deattachUI(); + super.deattach(); + panel.repaint(); + } + + public void attach() { + if (startComponent == null) + return; + + super.attach(); + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + public void run() { +// attachUI(); + try { + activate(); + } catch (Exception e) { + deattach(); + ExceptionUtils.logAndShowError(e); + } + + } + }); + + } + +// private void attachUI() { +// //panel.setCursor(activeCursor); +// translateAxisGizmo.attach(panel.GetRenderer()); +// } + + private void deattachUI() { + //panel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (translateAxisGizmo.isAttached()) + translateAxisGizmo.deattach(); + if (splitPointSelectionGizmo.isAttached()) + splitPointSelectionGizmo.deattach(); + if (terminalSelectionGizmo.isAttached()) + terminalSelectionGizmo.deattach(); + if (infoActor != null) { + panel.GetRenderer().RemoveActor(infoActor); + infoActor.Delete(); + infoActor = null; + } + } + + private List added = new ArrayList(); + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) + panel.useDefaultAction(); + if (lock != LockType.CUSTOM) { + if ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) > 0) { + if (e.getKeyCode() == KeyEvent.VK_X) { + if (lock != LockType.XY && lock != LockType.XZ) { + setLockType(LockType.XY, false); + } else if (lock == LockType.XY) { + setLockType(LockType.XZ, false); + } else { + setLockType(LockType.NONE, false); + } + } + if (e.getKeyCode() == KeyEvent.VK_Y) { + if (lock != LockType.XY && lock != LockType.YZ) { + setLockType(LockType.XY, false); + } else if (lock == LockType.XY) { + setLockType(LockType.YZ, false); + } else { + setLockType(LockType.NONE, false); + } + } + if (e.getKeyCode() == KeyEvent.VK_Z) { + if (lock != LockType.XZ && lock != LockType.YZ) { + setLockType(LockType.XZ, false); + } else if (lock == LockType.XZ) { + setLockType(LockType.YZ, false); + } else { + setLockType(LockType.NONE, false); + } + } + } else { + if (e.getKeyCode() == KeyEvent.VK_X) { + if (lock != LockType.X) + setLockType(LockType.X,false); + else + setLockType(LockType.NONE,false); + } + if (e.getKeyCode() == KeyEvent.VK_Y) { + if (lock != LockType.Y) + setLockType(LockType.Y,false); + else + setLockType(LockType.NONE, false); + } + if (e.getKeyCode() == KeyEvent.VK_Z) { + if (lock != LockType.Z) + setLockType(LockType.Z, false); + else + setLockType(LockType.NONE, false); + } + } + } + if (e.getKeyCode() == KeyEvent.VK_C) { + useDefault = !useDefault; + System.out.println("UseDefault " + useDefault); + } + + + + + update(); + + } + + + private void update() { + panel.repaint(); + } + private void update(double x, double y) { + switch (state) { + case NOT_ACTIVE: + return; // TODO : throw Exception? + case INITIALIZING: + return; + case SELECTING_POSITION: + return; + case SELECTING_SPLIT: + return; + case ROUTING: + updateRouting(x,y); + break; + } + return; + } + + boolean startRemovable = false; + + private void activate() throws Exception { + state = ToolState.INITIALIZING; + added.clear(); + + if (allowed.size() == 1) { + pipeRun = startComponent.getPipeRun(); + PipeControlPoint start = startComponent.getControlPoint(); + boolean requiresBranching = false; + if (start.getNext() == null) + reversed = false; + else if (start.getPrevious() == null) { + reversed = true; + } else { + requiresBranching = true; + } + + if (requiresBranching) { + activateSplit(start); + } else { + activateNextPrev(start); + } + } else if (allowed.size() == 0) { + panel.useDefaultAction(); + state = ToolState.NOT_ACTIVE; + return; + } else { + terminalSelectionGizmo.setComponent(startComponent, allowed); + terminalSelectionGizmo.attach(panel.GetRenderer()); + state = ToolState.SELECTING_POSITION; + panel.repaint(); + } + + } + + + + private void activateNextPrev(PipeControlPoint start) throws Exception{ + if (!reversed && start.isDualInline()) + start = start.getSubPoint().get(0); + else if (reversed && start.isDualSub()) + start = start.parent; + + pipeRun = start.getPipeRun(); + setPreviousPosition(start.getWorldPosition()); + + boolean startWithTurn = false; + if (startComponent instanceof Nozzle) { + direction = startComponent.getControlPoint().getDirectedControlPointDirection(); + lock = LockType.CUSTOM; + } else if (startComponent instanceof PipelineComponent){ + if (startComponent instanceof InlineComponent) { + direction = startComponent.getControlPoint().getPathLegDirection(reversed ? Direction.PREVIOUS : Direction.NEXT); + lock = LockType.CUSTOM; + if (startComponent.getType().equals(Plant3D.URIs.Builtin_Straight)) { + startWithTurn = true; + direction = null; + lock = LockType.NONE; + } + Vector3d v = new Vector3d(); + if (!reversed) { + start.getControlPointEnds(v, previousPosition); + } else { + start.getControlPointEnds(previousPosition,v); + } + } else if (startComponent instanceof TurnComponent) { + if (start.isFixed()) { + direction = startComponent.getControlPoint().getPathLegDirection(reversed ? Direction.PREVIOUS : Direction.NEXT); + lock = LockType.CUSTOM; + } else { + direction = null; + lock = LockType.NONE; + } + } else if (startComponent instanceof EndComponent) { + throw new Exception("Not supported"); + } + + } else { + throw new Exception("Not supported"); + } + currentPosition = new Vector3d(previousPosition); + state = ToolState.ROUTING; + if (direction != null) { + direction.normalize(); + + } + startRemovable = start.isDeletable(); + start.setDeletable(false); + + if (startWithTurn) { + addPoint(); + } else { + if (direction != null) + currentPosition.add(direction); + InlineComponent straight = ComponentUtils.createStraight(root); + PipeControlPoint straightCP = straight.getControlPoint(); + straight.setName(pipeRun.getUniqueName("Pipe")); + pipeRun.addChild(straight); + added.add(straight); + + if (!reversed) { + start.setNext(straightCP); + straightCP.setPrevious(start); + } else { + start.setPrevious(straightCP); + straightCP.setNext(start); + } + } + translateAxisGizmo.attach(panel.GetRenderer()); + setPreviousPosition(previousPosition); + updateCurrentPoint(); + } + + private void setPreviousPosition(Vector3d v) { + previousPosition = new Vector3d(v); + if (translateAxisGizmo.isAttached()) + translateAxisGizmo.setPosition(previousPosition); + } + + private void activateBranch(PipeControlPoint start) throws Exception{ + pipeRun = start.getPipeRun(); + setPreviousPosition(start.getWorldPosition()); + + direction = null; + lock = LockType.NONE; + + currentPosition = new Vector3d(previousPosition); + state = ToolState.ROUTING; + if (direction != null) { + direction.normalize(); + + } + startRemovable = start.isDeletable(); + start.setDeletable(false); + + + if (direction != null) + currentPosition.add(direction); + InlineComponent straight = ComponentUtils.createStraight(root); + PipeControlPoint straightCP = straight.getControlPoint(); + straight.setName(pipeRun.getUniqueName("Pipe")); + pipeRun.addChild(straight); + added.add(straight); + + if (!reversed) { + start.setNext(straightCP); + straightCP.setPrevious(start); + + } else { + start.setPrevious(straightCP); + straightCP.setNext(start); + + } + + translateAxisGizmo.attach(panel.GetRenderer()); + setPreviousPosition(previousPosition); + updateCurrentPoint(); + } + + private void activateSplit(PipeControlPoint start) throws Exception{ + Point3d p1 = new Point3d(); + Point3d p2 = new Point3d(); + start.getInlineControlPointEnds(p1, p2); + splitPointSelectionGizmo.setSplit(p1, p2); + splitPointSelectionGizmo.attach(panel.GetRenderer()); + state = ToolState.SELECTING_SPLIT; + } + public void deactivate() { + for (PipelineComponent component : added) { + component.getControlPoint().setDeletable(true); + } + + added.clear(); + startComponent.getControlPoint().setDeletable(startRemovable); + + direction = null; + + setLockType(LockType.NONE, true); + startComponent = null; + endTo = null; + endPort = null; + endType = null; + pipeRun = null; + allowed.clear(); + currentPosition = null; + previousPosition = null; + startRemovable = false; + detector.clearConstraintHighlights(); + state = ToolState.NOT_ACTIVE; + setEnabled(false); + + + } + + private void setLockType(LockType type, boolean force) { + if (force || lock != LockType.CUSTOM) { + lock = type; + + switch (lock) { + case CUSTOM: + case NONE: + translateAxisGizmo.setType(6); + break; + case X: + translateAxisGizmo.setType(0); + break; + case Y: + translateAxisGizmo.setType(1); + break; + case Z: + translateAxisGizmo.setType(2); + break; + case XY: + translateAxisGizmo.setType(3); + break; + case XZ: + translateAxisGizmo.setType(4); + break; + case YZ: + translateAxisGizmo.setType(5); + break; + + } + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (useDefault) { + panel.getDefaultAction().mousePressed(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (useDefault) { + panel.getDefaultAction().mouseReleased(e); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (useDefault) { + panel.getDefaultAction().mouseClicked(e); + return; + } + if (state == ToolState.ROUTING) { + try { + if (e.getClickCount() == 1) { + if (e.getButton() == MouseEvent.BUTTON1) { + if (this.added.size() > 0) { + + setLockType(LockType.NONE,true); + if (endTo != null) { + + endPiping(); + } else { + addPoint(); + } + } else { + throw new RuntimeException("kjf"); + // // user was selecting position of branch + // lastPoint.set(startPoint); + // controlPoints.add(new Point3d(startPoint)); + // if (selectionLine != null) + // selectionLine.removeFromParent(); + // selectionLine = null; + } + } else if (e.getButton() ==MouseEvent.BUTTON2){ + // detector.updateConstraintReference(); + } else if (e.getButton() == MouseEvent.BUTTON3){ + endPiping(); + } + } + } catch(Exception err) { + err.printStackTrace(); + } + } else if (state == ToolState.SELECTING_POSITION) { + if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { + int type = panel.getPickType(); + panel.setPickType(0); + vtkProp[] picked = panel.pick(e.getX(), e.getY()); + panel.setPickType(type); + PositionType position = terminalSelectionGizmo.getPickedPosition(picked); + if (position != null) { + terminalSelectionGizmo.deattach(); + try { + if (position == PositionType.SPLIT) { + activateSplit(startComponent.getControlPoint()); + } else if (position == PositionType.NEXT || position == PositionType.PREVIOUS) { + reversed = position == PositionType.PREVIOUS; + activateNextPrev(startComponent.getControlPoint()); + } else { + panel.useDefaultAction(); + } + } catch (Exception err) { + ExceptionUtils.logAndShowError(err); + panel.useDefaultAction(); + } + } + } + } else if (state == ToolState.SELECTING_SPLIT) { + if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { + Tuple3d t = splitPointSelectionGizmo.getSplitPoint(); + splitPointSelectionGizmo.deattach(); + if (t == null) { + panel.useDefaultAction(); + return; + } + try { + Vector3d pos = new Vector3d(t); + InlineComponent branchSplit = createBranchSplit((InlineComponent)startComponent, pos); + PipeControlPoint branchSplitCP = branchSplit.getControlPoint(); + reversed = false; + PipeRun newRun = new PipeRun(); + String n = root.getUniqueName("PipeRun"); + newRun.setName(n); + root.addChild(newRun); + PipeControlPoint pcp = new PipeControlPoint(branchSplit,newRun); + branchSplitCP.children.add(pcp); + pcp.parent = branchSplitCP; + pcp.setWorldOrientation(branchSplitCP.getWorldOrientation()); + pcp.setWorldPosition(branchSplitCP.getWorldPosition()); + startComponent = branchSplit; + activateBranch(pcp); + } catch (Exception err) { + ExceptionUtils.logAndShowError(err); + panel.useDefaultAction(); + } + } + } + + } + + private InlineComponent createBranchSplit(InlineComponent component, Vector3d pos) throws Exception{ + InlineComponent branchSplit = ComponentUtils.createBranchSplit(root); + String branchName = component.getPipeRun().getUniqueName("Branch"); + branchSplit.setName(branchName); + component.getPipeRun().addChild(branchSplit); + PipeControlPoint branchSplitCP = branchSplit.getControlPoint(); + branchSplitCP.setWorldPosition(pos); + PipingRules.splitVariableLengthComponent(branchSplit, component, false); + return branchSplit; + } + + @Override + public void mouseMoved(MouseEvent e) { + if (useDefault) { + panel.getDefaultAction().mouseMoved(e); + return; + } + step = ((e.getModifiers() & MouseEvent.CTRL_DOWN_MASK) > 0); + update(e.getX(), e.getY()); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (useDefault) + panel.getDefaultAction().mouseDragged(e); + } + + + + + private List isOverNode(int x, int y) { + List nodes = new ArrayList(); + vtkProp picked[] = panel.pick2(x, y); + if (picked !=null) { + for (int i = 0; i < picked.length; i++) { + nodes.add(nodeMap.getNode(picked[i])); + } + } + return nodes; + } + + + + private void updateRouting(double x, double y) { +// if(input.keyPressed(KeyEvent.VK_ESCAPE)) { +// controlPoints.clear(); +// end(); +// return; +// } +// if (input.keyPressed(KeyEvent.VK_C)) { +// useCamera = !useCamera; +// cameraAction.setChecked(useCamera); +// } + if (useDefault) { + //panel.getDefaultAction().update(); + return; + } + + endTo = null; + endType = null; + endPort = null; + + Ray ray = vtkUtil.createMouseRay(panel.GetRenderer(),x, y); + Vector3d o = new Vector3d(ray.pos); + Vector3d d = ray.dir; + + + if (!updateCurrentPoint(o, d)) + return; + //Point3d startPoint = new Point3d(); + double mu[] = new double[2]; + + + + INode hoverObject = null; + + List hover = isOverNode((int)x,(int)y); + if (hover.size() > 0) { + hoverObject = hover.get(0); + } +// System.out.println(hoverObject + " " + getLast()); + if (hoverObject != null) { + if (hoverObject.equals(getLast()) ) { + boolean set = false; + for (int i = 1; i < hover.size(); i++) { + hoverObject = hover.get(i); + if (!getLast().equals(hoverObject)) { + set = true; + break; + } + } + if (!set) + hoverObject = null; + } + } +// System.out.println(hoverObject); + if (hoverObject != null) { + + if (lock == LockType.NONE) { + if (hoverObject instanceof Nozzle && endingToNozzle(hoverObject,o,d)) { + endTo = (Nozzle)hoverObject; + } else if (hoverObject instanceof InlineComponent && ((InlineComponent)hoverObject).isVariableLength()) { + endTo = (InlineComponent)hoverObject; + endType = endingToStraight(endTo,mu,o,d); + } else if (hoverObject instanceof PipelineComponent && (endPort = endingToComponent(hoverObject,o,d)) != null) { + endTo = (PipelineComponent)hoverObject; + } else { + updateRoute(o,d); + } + } else { + if (hoverObject instanceof InlineComponent && ((InlineComponent)hoverObject).isVariableLength() && (endType = endingLockToStraight(hoverObject,mu)) != null) { + endTo = (InlineComponent)hoverObject;; + } else if (hoverObject instanceof Nozzle && endingLockToNozzle(hoverObject)) { + endTo = (Nozzle)hoverObject; + } else if ((hoverObject instanceof PipelineComponent) && ((endPort = endingLockToComponent(hoverObject)) != null)) { + endTo = (PipelineComponent)hoverObject; + } else { + updateRoute(o,d); + } + } + if (added.contains(endTo)) + endTo = null; + + } else { + updateRoute(o,d); + } + + panel.repaint(); + + + + + } + + private boolean updateCurrentPoint(Vector3d o, Vector3d d) { + + Vector3d point = new Vector3d(this.previousPosition); + + switch(lock) { + case X: + MathTools.intersectStraightStraight(point, new Vector3d(1.0,0.0,0.0), o,d, currentPosition, new Vector3d()); + if (step) { + currentPosition.x = Math.round(istep * currentPosition.x) / istep; + BigDecimal bx = new BigDecimal(currentPosition.x); + bx.setScale(decimals, BigDecimal.ROUND_HALF_UP); + currentPosition.x = bx.doubleValue(); + } + break; + case Y: + MathTools.intersectStraightStraight(point, new Vector3d(0.0,1.0,0.0), o,d, currentPosition, new Vector3d()); + if (step) { + currentPosition.y = Math.round(istep * currentPosition.y) / istep; + BigDecimal bx = new BigDecimal(currentPosition.y); + bx.setScale(decimals, BigDecimal.ROUND_HALF_UP); + currentPosition.y = bx.doubleValue(); + } + break; + case Z: + MathTools.intersectStraightStraight(point, new Vector3d(0.0,0.0,1.0), o,d, currentPosition, new Vector3d()); + if (step) { + currentPosition.z = Math.round(istep * currentPosition.z) / istep; + BigDecimal bx = new BigDecimal(currentPosition.z); + bx.setScale(decimals, BigDecimal.ROUND_HALF_UP); + currentPosition.z = bx.doubleValue(); + }break; + case XY: + MathTools.intersectStraightPlane(o, d, point, new Vector3d(0.0,0.0,1.0), currentPosition); + break; + case XZ: + MathTools.intersectStraightPlane(o, d, point, new Vector3d(0.0,1.0,0.0), currentPosition); + break; + case YZ: + MathTools.intersectStraightPlane(o, d, point, new Vector3d(1.0,0.0,0.0), currentPosition); + break; + case NONE: + Vector3d normal = new Vector3d(panel.GetRenderer().GetActiveCamera().GetDirectionOfProjection()); + normal.normalize(); + + MathTools.intersectStraightPlane(o, d, point, normal, currentPosition); + break; + case CUSTOM: + MathTools.intersectStraightStraight(point, new Vector3d(direction), o,d, currentPosition, new Vector3d()); + double dist = MathTools.distanceFromPlane(new Vector3d(currentPosition), direction, previousPosition); + if (dist < 0.0) + currentPosition.set(previousPosition); + break; + default: + return false; + } + return true; + } + + private Vector3d getLockDir() { + switch (lock) { + case CUSTOM: + return direction; + case X: + return new Vector3d(1,0,0); + case Y: + return new Vector3d(0,1,0); + case Z: + return new Vector3d(0,0,1); + } + return null; + } + + private void updateRoute(Vector3d o, Vector3d d) { + detector.clearConstraintHighlights(); + Point3d previousPipePoint = new Point3d(previousPosition); + String s = ""; + if (lock == LockType.NONE) { + Point3d p = detector.getSnappedPoint(o, d, new Vector3d(previousPipePoint)); + if (p != null) + currentPosition = new Vector3d(p); + s += detector.getSnapString(); + + } else { + Vector3d dir = new Vector3d(currentPosition); + dir.sub(previousPipePoint); + Point3d p = detector.getPointSnap(new Vector3d(previousPipePoint), dir); + if (p != null) + currentPosition = new Vector3d(p); + s += detector.getSnapString(); + + } + + updateCurrentPoint(); + s += currentPosition.toString(); + setInfoText(s); + } + + vtkTextActor infoActor; + + private void setInfoText(String text) { + //System.out.println(text); + if (infoActor == null) { + infoActor = new vtkTextActor(); + infoActor.GetTextProperty().SetColor(0.0, 0.0, 0.0); + infoActor.GetTextProperty().ShadowOff(); + infoActor.GetTextProperty().ItalicOff(); + infoActor.GetTextProperty().BoldOff(); + infoActor.GetTextProperty().SetFontSize(18); + infoActor.GetTextProperty().Delete(); + infoActor.GetProperty().SetColor(0.0, 0.0, 0.0); + infoActor.GetProperty().Delete(); + + + infoActor.SetPosition(10,10); + panel.GetRenderer().AddActor(infoActor); + } + infoActor.SetInput(text); + } + + private boolean endingToNozzle(INode nozzleNode,Vector3d o, Vector3d d) { + Nozzle nozzle = (Nozzle)nozzleNode; + PipeControlPoint pcp =nozzle.getControlPoint(); + if (pcp != null && (pcp.getNext() != null || + pcp.getPrevious() != null)) + return false; // nozzle is already connected to pipe + currentPosition = pcp.getWorldPosition(); + Point3d previousPipePoint = new Point3d(previousPosition); + Point3d p = detector.getSnappedPoint(o, d, new Vector3d(previousPipePoint)); + if (p != null) { + if (MathTools.distance(p, currentPosition) > NOZZLE_SNAP_DISTANCE) { + return false; + } + } + + updateCurrentPoint(); + + setInfoText("Connect to nozzle " + currentPosition); + return true; + + } + + private PositionType endingToStraight(INode straightNode, double mu[], Vector3d o, Vector3d d) { + InlineComponent s = (InlineComponent)straightNode; + String info = ""; + Point3d sStart = new Point3d(); + Point3d sEnd = new Point3d(); + s.getControlPointEnds(sStart, sEnd); + //detector.clearConstraintHighlights(); + + Point3d previousPipePoint = new Point3d(previousPosition); + //String st = ""; + if (lock == LockType.NONE) { + Point3d p = detector.getSnappedPoint(o, d, new Vector3d(previousPipePoint)); + if (p != null) { + currentPosition = new Vector3d(p); + // snapping is detected, check if snapped point can create branch with straight + PositionType t = endingLockToStraight(s, mu); + if (t != null) + return t; + // if not, we'll have to remove highlight that was added when snapped point was detected + detector.clearConstraintHighlights(); + } + + + Vector3d sDir = new Vector3d(sEnd); + sDir.sub(sStart); + MathTools.intersectStraightStraight(sStart, sDir, o, d, currentPosition, new Point3d(), mu); + + + } else { + throw new RuntimeException("Lock shouldn't be on"); + + } + + updateCurrentPoint(); + + // branch point must lie between straight's ends. If connection point is exactly + // on straight end user may want to connect pipes to each other + // TODO : take account sizes of inline components) + // TODO : actually make connection if its detected + boolean connectPrev = false; + boolean connectNext = false; + + if (mu[0] < 0.0) { + currentPosition.set(sStart); + connectPrev = true; + } + else if (mu[0] > 1.0) { + currentPosition.set(sEnd); + connectNext = true; + } + boolean connect = false; + if (connectPrev) { + PipeControlPoint pcp = s.getControlPoint(); + if (pcp.getPrevious() == null) + connect = true; + } else if (connectNext) { + PipeControlPoint pcp = s.getControlPoint(); + if (pcp.getNext() == null) + connect = true; + } + + updateCurrentPoint(); + + if (connect) + info += "Connect pipes :"; + else + info += "Make Branch :"; + + setInfoText(info + currentPosition + " " + Math.max(0.0, Math.min(mu[0], 1.0))); + if (connect) { + if (connectNext) { + return PositionType.NEXT; + } else { + return PositionType.PREVIOUS; + } + + } + return PositionType.SPLIT; + + } + + private PipeControlPoint endingToComponent(INode componentNode, Vector3d o, Vector3d d) { + PipelineComponent component = (PipelineComponent)componentNode; + PipeControlPoint pcp = component.getControlPoint(); + if (component instanceof EndComponent) { + if (pcp.getNext() != null || pcp.getPrevious() != null) + return null; + return pcp; + } else if (component instanceof TurnComponent) { + if (pcp.getNext() == null || pcp.getPrevious() == null) + return pcp; + return null; + } else if (component instanceof InlineComponent) { + // TODO : scan all empty pcps of the component and select closest one. + if (pcp.getNext() == null || pcp.getPrevious() == null) + return pcp; + return null; + } + + return null; + } + + private PositionType endingLockToStraight(INode straightNode, double mu[]) { + InlineComponent s = (InlineComponent)straightNode; + Point3d sStart = new Point3d();//G3DTools.getPoint(s.getHasControlPoint().getPreviousPoint().getLocalPosition()); + Point3d sEnd = new Point3d(); //G3DTools.getPoint(s.getHasControlPoint().getNextPoint().getLocalPosition()); + s.getControlPoint().getInlineControlPointEnds(sStart, sEnd); + Vector3d sDir = new Vector3d(sEnd); + sDir.sub(sStart); + Vector3d dir = new Vector3d(currentPosition); + Point3d prev = new Point3d(previousPosition); + dir.sub(prev); + // intersection point in pipe where branch would be inserted to + Vector3d branchPoint = new Vector3d(); + // intersection point in straight pipe that is currently routed + Vector3d routePoint = new Vector3d(); + MathTools.intersectStraightStraight(sStart, sDir, new Vector3d(prev), dir, branchPoint, routePoint, mu); + routePoint.sub(branchPoint); + // startPoint of branch must be between pipe ends + // TODO : take account sizes of elbows (or other components) + // branch point must be between pipe ends and intersection points must be quite close to each othert + if (mu[0] > 0.0 && mu[0] < 1.0 && routePoint.lengthSquared() < BRANCH_SNAP_DISTANCE) { + currentPosition.set(branchPoint); + + updateCurrentPoint(); + + setInfoText("Make branch (l) :" + currentPosition + " " + Math.max(0.0, Math.min(mu[0], 1.0)) + " " + routePoint.lengthSquared()); + return PositionType.SPLIT; + } + return null; + } + + private boolean endingLockToNozzle(INode nozzleNode) { + Nozzle nozzle = (Nozzle)nozzleNode; + Vector3d dir = new Vector3d(currentPosition); + Point3d prev = new Point3d(previousPosition); + dir.sub(prev); + Vector3d nozzleLoc = nozzle.getWorldPosition(); + double u[] = new double[1]; + Vector3d closest = MathTools.closestPointOnStraight(new Point3d(nozzleLoc), new Point3d(prev), new Vector3d(dir), u); + double dist = MathTools.distanceSquared(nozzleLoc,closest); + if (dist < BRANCH_SNAP_DISTANCE) { + // FIXME : directions should be checked (insert an elbow) + currentPosition.set(nozzleLoc); + updateCurrentPoint(); + setInfoText("Connect to nozzle (l) :" + currentPosition); + return true; + } + //System.out.println(u[0]); + return false; + } + + private PipeControlPoint endingLockToComponent(INode componentNode) { + // we'll must scan all free pcp's and their direction to accept the connection. + return null; + } + + private void addPoint() throws Exception { + InlineComponent previous = (InlineComponent)getLast(); + PipeControlPoint previousCP = previous.getControlPoint(); + TurnComponent turn = ComponentUtils.createTurn(root); + InlineComponent straight = ComponentUtils.createStraight(root); + PipeControlPoint turnCP = turn.getControlPoint(); + PipeControlPoint straightCP = straight.getControlPoint(); + straight.setName(pipeRun.getUniqueName("Pipe")); + turn.setName(pipeRun.getUniqueName("Elbow")); + pipeRun.addChild(turn); + pipeRun.addChild(straight); + added.add(turn); + added.add(straight); + + turnCP.setDeletable(false); // mark turnCP nonDeletable so that PipingRules won't delete it immediately. + + if (!reversed) { + previousCP.setNext(turnCP); + turnCP.setPrevious(previousCP); + turnCP.setNext(straightCP); + straightCP.setPrevious(turnCP); + } else { + previousCP.setPrevious(turnCP); + turnCP.setNext(previousCP); + turnCP.setPrevious(straightCP); + straightCP.setNext(turnCP); + } + + turnCP.setWorldPosition(currentPosition); + turnCP.setTurnAngle(0.0); + turnCP.setLength(0.0); + straightCP.setWorldPosition(currentPosition); + straightCP.setLength(0.0); + + setPreviousPosition(currentPosition); + updateCurrentPoint(); + + + + } + + /** + * Updates tool graphics for current point + */ + private void updateCurrentPoint() { +// PipeComponentProvider.createStraightEdges(pipeShapes.get(pipeShapes.size() - 1), controlPoints.get(controlPoints.size() - 1), currentPoint, pipeDiameter*0.5); + InlineComponent straight = (InlineComponent)added.get(added.size()-1); + // FIXME : does not take account space the the previous elbow reserves. + Vector3d v = new Vector3d(); + v.sub(currentPosition, previousPosition); + double length = v.length(); + v.scale(0.5); + v.add(previousPosition); + straight.getControlPoint().setWorldPosition(v); + straight.getControlPoint().setLength(length); + try { + PipingRules.positionUpdate(straight.getControlPoint(),false); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private PipelineComponent getLast() { + if (added.size() == 0) + return startComponent; + return added.get(added.size()-1); + } + + + /** + * Removes last point from pipeline + */ + public void removePoint() { + if (added.size() < 3) + return; + InlineComponent straight = (InlineComponent)added.remove(added.size()-1); + TurnComponent turn = (TurnComponent)added.remove(added.size()-1); + straight.getControlPoint().remove(); + turn.getControlPoint().remove(); + if (added.size() > 1) { + setPreviousPosition(added.get(added.size()-2).getWorldPosition()); + } else { + setPreviousPosition(startComponent.getWorldPosition()); + if (direction != null) + setLockType(LockType.CUSTOM, true); + } + + } + + private void endPiping() throws Exception { + state = ToolState.NOT_ACTIVE; + + if (endTo != null) { + PipeControlPoint endCP = endTo.getControlPoint(); + if (endType == null || endType == PositionType.NEXT || endType == PositionType.PREVIOUS) { + + PipelineComponent current = getLast(); + PipeControlPoint currentCP = current.getControlPoint(); + + boolean requiresReverse = false; + if (!reversed && endCP.getPrevious() != null) { + requiresReverse = true; + } else if (reversed && endCP.getNext() != null) { + requiresReverse = true; + } + PipeRun other = endCP.getPipeRun(); + boolean mergeRuns = pipeRun.equalSpecs(other); + + if (requiresReverse) { + // Pipe line must be traversible with next/previous relations without direction change. + // Now the component, where we are connecting the created pipeline is defined in different order. + PipingRules.reverse(other); + + } + if (mergeRuns) { + // Runs have compatible specs and must be merged + if (pipeRun != other) // FIXME: temporary workaround. + PipingRules.merge(pipeRun, other); + if (!reversed) { + currentCP.setNext(endCP); + endCP.setPrevious(currentCP); + } else { + currentCP.setPrevious(endCP); + endCP.setNext(currentCP); + } + } else { + // Runs do not have compatible specs, and a reducer must be attached in between. + InlineComponent reducer = ComponentUtils.createReducer(root); + PipeControlPoint pcp = reducer.getControlPoint(); + PipeControlPoint ocp = pcp.getSubPoint().get(0); + + Vector3d endPos = endCP.getWorldPosition(); + Vector3d currentPos = currentCP.getWorldPosition(); + Vector3d v = new Vector3d(endPos); + v.sub(currentPos); + v.scale(0.5); + v.add(currentPos); + + PipingRules.addSizeChange(reversed, pipeRun, other, reducer, currentCP, endCP); + + pcp.setWorldPosition(v); + reducer.updateParameters(); + } + + } else if (endType == PositionType.SPLIT) { + InlineComponent branchSplit = createBranchSplit((InlineComponent)endTo, currentPosition); + PipeControlPoint branchSplitCP = branchSplit.getControlPoint(); + PipeControlPoint pcp = new PipeControlPoint(branchSplit,pipeRun); + branchSplitCP.children.add(pcp); + pcp.parent = branchSplitCP; + pcp.setWorldOrientation(branchSplitCP.getWorldOrientation()); + pcp.setWorldPosition(branchSplitCP.getWorldPosition()); + + PipelineComponent current = getLast(); + PipeControlPoint currentCP = current.getControlPoint(); + + + if(!reversed) { + pcp.setPrevious(currentCP); + currentCP.setNext(pcp); + } else { + pcp.setNext(currentCP); + currentCP.setPrevious(pcp); + } + + } + PipingRules.positionUpdate(endCP); + } + panel.useDefaultAction(); + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/browser/P3DBrowser.java b/org.simantics.plant3d/src/org/simantics/plant3d/browser/P3DBrowser.java new file mode 100644 index 00000000..176fbaaa --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/browser/P3DBrowser.java @@ -0,0 +1,15 @@ +package org.simantics.plant3d.browser; + +import java.util.Set; + +import org.simantics.structural.ui.modelBrowser.ModelBrowser2; + +public class P3DBrowser extends ModelBrowser2 { + + + @Override + protected Set getBrowseContexts() { + return browseContexts; + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentContentProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentContentProvider.java new file mode 100644 index 00000000..fe55fdfa --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentContentProvider.java @@ -0,0 +1,26 @@ +package org.simantics.plant3d.dialog; + +import java.util.List; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.simantics.plant3d.utils.Item; + +public class ComponentContentProvider implements IStructuredContentProvider { + + @Override + public Object[] getElements(Object inputElement) { + List list = (List)inputElement; + return list.toArray(); + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + + } + @Override + public void dispose() { + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentLabelProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentLabelProvider.java new file mode 100644 index 00000000..ab32c894 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentLabelProvider.java @@ -0,0 +1,16 @@ +package org.simantics.plant3d.dialog; + +import org.eclipse.jface.viewers.LabelProvider; +import org.simantics.plant3d.utils.Item; + +public class ComponentLabelProvider extends LabelProvider{ + + @Override + public String getText(Object element) { + Item item = (Item)element; + return item.getName(); + } + + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentSelectionDialog.java b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentSelectionDialog.java new file mode 100644 index 00000000..33d5882c --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/dialog/ComponentSelectionDialog.java @@ -0,0 +1,332 @@ +package org.simantics.plant3d.dialog; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ListViewer; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.ExpandBar; +import org.eclipse.swt.widgets.ExpandItem; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.simantics.db.exception.DatabaseException; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; +import org.simantics.plant3d.utils.Item; +import org.simantics.plant3d.utils.Item.Type; +import org.simantics.plant3d.utils.P3DUtil; +import org.simantics.utils.ui.ExceptionUtils; + +public class ComponentSelectionDialog extends Dialog implements ISelectionChangedListener{ + + private Item selected; + private Set allowed; + private Set filterAllowed; + private Double angle; + private Double length; + + private Text lengthText; + private Text angleText; + + private Double diameter; + private Double turnRadius; + + private Text diameterText; + private Text turnRadiusText; + + private boolean inlineSplit = false; + + + public ComponentSelectionDialog(Shell parentShell, Set allowed) { + super(parentShell); + this.allowed = allowed; + filterAllowed = new HashSet(); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2,false); + layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); + layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); + layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); + layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); + composite.setLayout(layout); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + applyDialogFont(composite); + + // TODO : we need better classification than inline,turn, and end: + // * fixed length inlines + // * fixed angle turns + // * size changes (requires input for pipe run specs) + // * variable length inlines (input for length) + // * variable angle turns (input for angle) + // * ends + + List ends = null; + List turns = null; + List inlines = null; + try { + ends = P3DUtil.getEnds(); + turns= P3DUtil.getTurns(); + inlines = P3DUtil.getInlines(); + } catch (DatabaseException e) { + Label label = new Label(composite, SWT.NONE); + label.setText("Cannot load pipeline components: " + e.getMessage()); + ExceptionUtils.logError(e); + return composite; + } + + ExpandBar expandBar = new ExpandBar(composite, SWT.NONE); + + + ExpandItem inlineItem = new ExpandItem(expandBar, SWT.NONE); + inlineItem.setText("Inline"); + ListViewer inlineViewer = new ListViewer(expandBar); + inlineViewer.setLabelProvider(new ComponentLabelProvider()); + inlineViewer.setContentProvider(new ComponentContentProvider()); + + ExpandItem turnItem = new ExpandItem(expandBar, SWT.NONE); + turnItem.setText("Turn"); + ListViewer turnViewer = new ListViewer(expandBar); + turnViewer.setLabelProvider(new ComponentLabelProvider()); + turnViewer.setContentProvider(new ComponentContentProvider()); + + ExpandItem endItem = new ExpandItem(expandBar, SWT.NONE); + endItem.setText("End"); + ListViewer endViewer = new ListViewer(expandBar); + endViewer.setLabelProvider(new ComponentLabelProvider()); + endViewer.setContentProvider(new ComponentContentProvider()); + + + inlineItem.setControl(inlineViewer.getList()); + turnItem.setControl(turnViewer.getList()); + endItem.setControl(endViewer.getList()); + + inlineViewer.setInput(inlines); + turnViewer.setInput(turns); + endViewer.setInput(ends); + + inlineItem.setHeight(inlineViewer.getList().computeSize(SWT.DEFAULT, SWT.DEFAULT).y); + turnItem.setHeight(turnViewer.getList().computeSize(SWT.DEFAULT, SWT.DEFAULT).y); + endItem.setHeight(endViewer.getList().computeSize(SWT.DEFAULT, SWT.DEFAULT).y); + + inlineViewer.addSelectionChangedListener(this); + turnViewer.addSelectionChangedListener(this); + endViewer.addSelectionChangedListener(this); + + GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).span(2, 1).applyTo(expandBar); + GridDataFactory.fillDefaults().minSize(500, 500).hint(500, 500).applyTo(composite); + + + Label label = new Label(composite, SWT.NONE); + label.setText("Length"); + lengthText = new Text(composite, SWT.SINGLE|SWT.BORDER); + label = new Label(composite, SWT.NONE); + label.setText("Angle"); + angleText = new Text(composite, SWT.SINGLE|SWT.BORDER); + + label = new Label(composite, SWT.NONE); + label.setText("Diameter"); + diameterText = new Text(composite, SWT.SINGLE|SWT.BORDER); + label = new Label(composite, SWT.NONE); + label.setText("Turn Radius"); + turnRadiusText = new Text(composite, SWT.SINGLE|SWT.BORDER); + + lengthText.setEnabled(false); + angleText.setEnabled(false); + turnRadiusText.setEnabled(false); + diameterText.setEnabled(false); + + lengthText.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + try { + length = Double.parseDouble(lengthText.getText()); + } catch (NumberFormatException err) { + length = null; + } + validate(); + } + }); + + angleText.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + try { + angle = Double.parseDouble(angleText.getText()); + } catch (NumberFormatException err) { + angle = null; + } + validate(); + } + }); + + diameterText.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + try { + diameter = Double.parseDouble(diameterText.getText()); + } catch (NumberFormatException err) { + diameter = null; + } + validate(); + } + }); + + turnRadiusText.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + try { + turnRadius = Double.parseDouble(turnRadiusText.getText()); + } catch (NumberFormatException err) { + turnRadius = null; + } + validate(); + } + }); + + GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.TOP).applyTo(lengthText); + GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.TOP).applyTo(angleText); + GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.TOP).applyTo(diameterText); + GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.TOP).applyTo(turnRadiusText); + + if (!allowed.contains(PositionType.NEXT) && !allowed.contains(PositionType.PREVIOUS)) { + turnViewer.getList().setEnabled(false); + endViewer.getList().setEnabled(false); + inlineSplit = true; + } + + return composite; + } + + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection sel = (IStructuredSelection)event.getSelection(); + selected = (Item)sel.getFirstElement(); + validate(); + } + + private void validate() { + filterAllowed.clear(); + Set filterAllowed = new HashSet(); + boolean ok = true; + if (selected.isCode())// TODO : instead of disabling the button, we should filter the content. + ok = false; + + if (selected.isVariable()) { + if (selected.getType() == Type.INLINE) { + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + if (inlineSplit) { + lengthText.setEnabled(false); + angleText.setEnabled(false); + ok = false; + + } else { + lengthText.setEnabled(true); + angleText.setEnabled(false); + if (length == null) + ok = false; + } + } else if (selected.getType() == Type.TURN) { + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + lengthText.setEnabled(false); + angleText.setEnabled(true); + if (angle == null) + ok = false; + } else { + // this should not happen + lengthText.setEnabled(false); + angleText.setEnabled(false); + } + } else { + lengthText.setEnabled(false); + angleText.setEnabled(false); + } + if (selected.isSizeChange()) { + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + if (inlineSplit) { + turnRadiusText.setEnabled(false); + diameterText.setEnabled(false); + ok = false; + } else { + turnRadiusText.setEnabled(true); + diameterText.setEnabled(true); + if (diameter == null || turnRadius == null) + ok = false; + } + + } else { + turnRadiusText.setEnabled(false); + diameterText.setEnabled(false); + } + if (!selected.isSizeChange() && !selected.isVariable()) { + switch (selected.getType()) { + case END: + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + break; + case NOZZLE: + case EQUIPMENT: + break; + case INLINE: + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + filterAllowed.add(PositionType.SPLIT); + case TURN: + filterAllowed.add(PositionType.NEXT); + filterAllowed.add(PositionType.PREVIOUS); + } + } + + for (PositionType t : filterAllowed) { + if (allowed.contains(t)) + this.filterAllowed.add(t); + } + + getButton(OK).setEnabled(ok); + } + + public Item getSelected() { + return selected; + } + + public Double getAngle() { + return angle; + } + + public Double getLength() { + return length; + } + + public Double getDiameter() { + return diameter; + } + + public Double getTurnRadius() { + return turnRadius; + } + + public Set filterAllowed() { + return filterAllowed; + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DContentOutlinePage.java b/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DContentOutlinePage.java new file mode 100644 index 00000000..c675b111 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DContentOutlinePage.java @@ -0,0 +1,151 @@ +package org.simantics.plant3d.editor; + +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Menu; +import org.simantics.db.Resource; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.g3d.vtk.common.NodeSelectionProvider2; +import org.simantics.g3d.vtk.common.VTKContentOutlinePage; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.scenegraph.EndComponent; +import org.simantics.plant3d.scenegraph.Equipment; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.TurnComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; + +public class P3DContentOutlinePage extends VTKContentOutlinePage{ + + private static final boolean DEBUG = false; + protected Menu contextMenu; + + private LocalResourceManager manager = new LocalResourceManager(JFaceResources.getResources()); + + private Image nozzleImage; + private Image pipeImage; + private Image tankImage; + private Image elbowImage; + private Image componentImage; + + public P3DContentOutlinePage(ParentNode rootNode, NodeSelectionProvider2 provider) { + super(rootNode,provider); + + nozzleImage = manager.createImage(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Nozzle.png")); + pipeImage = manager.createImage(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Straight.png")); + tankImage = manager.createImage(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/tank.png")); + elbowImage = manager.createImage(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Elbow.png")); + componentImage = manager.createImage(Activator.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/Component.png")); + } + + @Override + protected void createProviders(TreeViewer viewer) { + viewer.setContentProvider(new ScenegraphContentProvider() { + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof P3DRootNode) { + return ((P3DRootNode)parentElement).getChild().toArray(); + } + if (!DEBUG) { + if (parentElement instanceof PipeRun) { + return ((PipeRun)parentElement).getSortedChild().toArray(); + } + } else { + if (parentElement instanceof PipelineComponent) { + return new Object[]{((PipelineComponent) parentElement).getControlPoint()}; + } else if (parentElement instanceof PipeControlPoint) { + return new Object[]{((PipeControlPoint) parentElement).getPipelineComponent()}; + } + } + return super.getChildren(parentElement); + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof P3DRootNode) { + return ((P3DRootNode)element).getChild().size() > 0; + } + if (!DEBUG) { + if (element instanceof PipeRun) { + return ((PipeRun)element).getChild().size() > 0; + } + } else { + if (element instanceof PipelineComponent) { + return ((PipelineComponent) element).getControlPoint() != null; + } else if (element instanceof PipeControlPoint) { + return ((PipeControlPoint) element).getPipelineComponent() != null; + } + } + return super.hasChildren(element); + } + }); + viewer.setLabelProvider(new P3DLabelProvider()); + + hookContextMenu(viewer); + viewer.getTree().addMenuDetectListener(new MenuDetectListener() { + + @Override + public void menuDetected(MenuDetectEvent e) { + contextMenu.setLocation(e.x, e.y); + contextMenu.setVisible(true); + } + }); + + } + + @Override + public void dispose() { + manager.dispose(); + super.dispose(); + } + + protected void hookContextMenu(TreeViewer viewer) { + MenuManager menuMgr = new MenuManager("#PopupMenu"); + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + createContextMenu(manager); + } + }); + contextMenu = menuMgr.createContextMenu(viewer.getTree()); + } + + protected void createContextMenu(IMenuManager manager) { + + } + + private class P3DLabelProvider extends ScenegraphLabelProvider { + @Override + public Image getImage(Object element) { + if (element instanceof PipelineComponent) { + PipelineComponent comp = (PipelineComponent)element; + if (comp instanceof TurnComponent) { + return elbowImage; + } else if (comp instanceof EndComponent) { + return componentImage; + } else if (comp instanceof Nozzle) { + return nozzleImage; + } else if (comp.getControlPoint().isFixed()) { + return componentImage; + } + return pipeImage; + } else if (element instanceof Equipment) { + return tankImage; + } else if (element instanceof PipeRun) { + return pipeImage; + } + return null; + } + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DNodeMap.java b/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DNodeMap.java new file mode 100644 index 00000000..b0989513 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/editor/P3DNodeMap.java @@ -0,0 +1,246 @@ +package org.simantics.plant3d.editor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.g3d.ontology.G3D; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.g3d.vtk.common.AbstractVTKNodeMap; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.objmap.graph.IMapping; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.IP3DNode; +import org.simantics.plant3d.scenegraph.IP3DVisualNode; +import org.simantics.plant3d.scenegraph.P3DParentNode; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.ParameterizedNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; +import org.simantics.utils.threads.AWTThread; + +import vtk.vtkProp; +import vtk.vtkProp3D; + +public class P3DNodeMap extends AbstractVTKNodeMap { + + private static final boolean DEBUG = false; + + public P3DNodeMap(Session session, IMapping mapping, InteractiveVtkPanel panel, P3DRootNode rootNode) { + super(session, mapping, panel, rootNode); + rootNode.setNodeMap(this); + } + @Override + protected void updateActor(INode n, Set ids) { + if (DEBUG) System.out.println("P3DNodeMap update " + n); + if (!(n instanceof IP3DVisualNode)) { + if (n instanceof PipeControlPoint) { + n = ((PipeControlPoint)n).getPipelineComponent(); + if (n == null) + return; + } else { + return; + } + } + + IP3DVisualNode node = (IP3DVisualNode)n; + + if (DEBUG) { + System.out.print("P3DNodeMap update " + node); + for (String s : ids) + System.out.print(" " + s); + System.out.println(); + } + + if (ids.contains(Plant3D.URIs.hasGeometry)) { + node.visualize(panel); + updateRenderObjectsFor(node); + updateTransform(node); + } + if (n instanceof ParameterizedNode) { + ParameterizedNode geom = (ParameterizedNode)n; + for (String id : geom.getParameterMap().keySet()) { + if (ids.contains(id)) { + node.visualize(panel); + updateRenderObjectsFor(node); + updateTransform(node); + break; + } + } + } else if (n instanceof PipeRun) { + // FIXME: may require rule based update! + PipeRun run = (PipeRun)n; + Set ids2 = new HashSet(); + ids2.add(Plant3D.URIs.hasGeometry); + for (PipeControlPoint pcp : run.getControlPoints()) { + updateActor(pcp, ids2); + } + } + + if (ids.contains(G3D.URIs.hasPosition) || + ids.contains(G3D.URIs.hasOrientation) || + ids.contains(G3D.URIs.hasWorldPosition) || + ids.contains(G3D.URIs.hasWorldOrientation)) { + updateTransform(node); + } + } + + private void updateTransform(IP3DNode node) { + if (DEBUG) System.out.println("P3DNodeMap update Transform " + node); + + node.update(panel.GetRenderer()); + + if (node instanceof ParentNode) { + ParentNode p = (ParentNode)node; + for (IP3DNode n : p.getNodes()) + updateTransform(n); + } + } + + @Override + protected Collection getActors(INode n) { + List props = new ArrayList(); + if (!(n instanceof IP3DVisualNode)) + return props; + IP3DVisualNode node = (IP3DVisualNode)n; + for (vtkProp3D p : ((IP3DVisualNode)node).getActors()) + props.add(p); + + return props; + } + + @Override + protected void removeActor(INode n) { + if (DEBUG) System.out.println("P3DNodeMap.removeActor " + n); + if (!(n instanceof IP3DVisualNode)) + return; + IP3DVisualNode node = (IP3DVisualNode)n; + remActor(node); + + if (node instanceof P3DParentNode) { + for (IP3DNode n2 : ((P3DParentNode)node).getNodes()) + if (n2 instanceof IP3DVisualNode) + removeActor((IP3DVisualNode)n2); + } + } + + @Override + protected void addActor(INode n) { + if (DEBUG) System.out.println("P3DNodeMap.addActor " + n); + if (!(n instanceof IP3DVisualNode)) + return; + IP3DVisualNode node = (IP3DVisualNode)n; + + if (hasActor(node)) + return; + if (Thread.currentThread() != AWTThread.getThreadAccess().getThread()) + throw new RuntimeException("Illegal thread."); + + panel.lock(); + + node.visualize(panel); + + for (vtkProp3D act : node.getActors()) { + nodeToActor.add(node, act); + actorToNode.put(act, node); + } + + if (node instanceof P3DParentNode) { + for (IP3DNode n2 : ((P3DParentNode)node).getNodes()) + if (n2 instanceof IP3DVisualNode) + addActor((IP3DVisualNode)n2); + } + + updateTransform(node); + + panel.unlock(); + + } + + + + private boolean hasActor(IP3DVisualNode node) { + List list = nodeToActor.getValues(node); + if (list == null || list.size() == 0) + return false; + return true; + } + + private void remActor(IP3DVisualNode node) { + if (Thread.currentThread() != AWTThread.getThreadAccess().getThread()) + throw new RuntimeException("Illegal thread."); + + List list = nodeToActor.getValues(node); + if (list != null) { + for (vtkProp obj : list) { + actorToNode.remove(obj); + } + nodeToActor.remove(node); + panel.lock(); + + node.stopVisualize(); + + panel.unlock(); + } + } + + @Override + protected void update(ReadGraph graph) throws DatabaseException { + validate(); +// System.out.println("Graph updates"); + super.update(graph); + validate(); + } + + @Override + public void commit() { + validate(); +// System.out.println("Graph commit"); + super.commit(); + + } + @Override + protected void doCommit() { +// System.out.println("Do commit"); + validate(); + super.doCommit(); + } + + private void validate() { + for (INode node : rootNode.getNodes()) { + if (node instanceof PipeRun) + PipingRules.validate((PipeRun)node); + } + } + + @Override + public synchronized void preRender() { +// System.out.println("P3DNodeMap preRender"); +// super.preRender(); + try { +// boolean b = false; +// synchronized (syncMutex) { +// b = PipingRules.update(); +// } +// if (b) +// super.preRender(); + boolean b = true; + while (b) { + updateCycle(); + b = PipingRules.update(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/editor/Plant3DEditor.java b/org.simantics.plant3d/src/org/simantics/plant3d/editor/Plant3DEditor.java new file mode 100644 index 00000000..9d0c3b82 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/editor/Plant3DEditor.java @@ -0,0 +1,498 @@ +package org.simantics.plant3d.editor; + +import java.awt.Component; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.g3d.scenegraph.IG3DNode; +import org.simantics.g3d.scenegraph.NodeMap; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.vtk.action.RemoveAction; +import org.simantics.g3d.vtk.action.RotateAction; +import org.simantics.g3d.vtk.action.TranslateAction; +import org.simantics.g3d.vtk.action.vtkCameraAndSelectorAction; +import org.simantics.g3d.vtk.common.HoverHighlighter; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.g3d.vtk.common.NodeSelectionProvider2; +import org.simantics.g3d.vtk.common.SelectionHighlighter; +import org.simantics.g3d.vtk.shape.vtkShape; +import org.simantics.g3d.vtk.utils.vtkPanelUtil; +import org.simantics.objmap.graph.IMapping; +import org.simantics.objmap.graph.Mappings; +import org.simantics.objmap.graph.schema.IMappingSchema; +import org.simantics.plant3d.actions.AddComponentAction; +import org.simantics.plant3d.actions.AddEquipmentAction; +import org.simantics.plant3d.actions.AddNozzleAction; +import org.simantics.plant3d.actions.RoutePipeAction; +import org.simantics.plant3d.scenegraph.EndComponent; +import org.simantics.plant3d.scenegraph.Equipment; +import org.simantics.plant3d.scenegraph.IP3DNode; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.SchemaBuilder; +import org.simantics.plant3d.scenegraph.TurnComponent; +import org.simantics.plant3d.scenegraph.controlpoint.ControlPointFactory; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; +import org.simantics.plant3d.utils.ComponentUtils; +import org.simantics.plant3d.utils.Item; +import org.simantics.plant3d.utils.P3DUtil; +import org.simantics.selectionview.StandardPropertyPage; +import org.simantics.ui.workbench.IPropertyPage; +import org.simantics.ui.workbench.IResourceEditorInput; +import org.simantics.ui.workbench.ResourceEditorPart; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.ExceptionUtils; +import org.simantics.utils.ui.SWTAWTComponent; + +import vtk.vtkActor; +import vtk.vtkAxesActor; +import vtk.vtkCameraPass; +import vtk.vtkDefaultPass; +import vtk.vtkLightsPass; +import vtk.vtkOrientationMarkerWidget; +import vtk.vtkRenderPassCollection; +import vtk.vtkRenderer; +import vtk.vtkSequencePass; + + +public class Plant3DEditor extends ResourceEditorPart { + + private Composite parent; + private Resource input; + private InteractiveVtkPanel panel; + private SWTAWTComponent component; + + private P3DRootNode rootNode; + private IMapping mapping; + + private NodeSelectionProvider2 selectionProvider; + + private vtkCameraAndSelectorAction cameraAction; + private TranslateAction translateAction; + private RotateAction rotateAction; + private RemoveAction removeAction; + private RoutePipeAction routePipeAction; + private AddComponentAction addComponentAction; + + private P3DNodeMap nodeMap; + + @Override + public void createPartControl(Composite parent) { + this.parent = parent; + parent.setLayout (new FillLayout ()); + component = new SWTAWTComponent(parent,SWT.NONE) { + + @Override + protected Component createSwingComponent() { + if (panel == null) { + panel = new InteractiveVtkPanel(); + vtkPanelUtil.registerPanel(panel); + createScene(); + } + return panel; + } + }; + + IResourceEditorInput rei = (IResourceEditorInput)getEditorInput(); + input = rei.getResource(); + + + //IActionBars actionBars = getEditorSite().getActionBars(); + + hookContextMenu(); + + component.syncPopulate(); + + panel.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseClicked(final java.awt.event.MouseEvent e) { + if (e.getButton() == java.awt.event.MouseEvent.BUTTON3) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + contextMenu.setLocation(e.getXOnScreen(), e.getYOnScreen()); + contextMenu.setVisible(true); + } + }); + } + } + }); + + + cameraAction = new vtkCameraAndSelectorAction(panel); + panel.setDefaultAction(cameraAction); + panel.useDefaultAction(); + panel.setPickType(4); + + try { + ControlPointFactory.preloadCache(); + ComponentUtils.preloadCache(); + } catch (Exception e) { + ExceptionUtils.logAndShowError("Cannot open Plant3D editor",e); + return; + } + + try { + getSession().syncRequest(new ReadRequest() { + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void run(ReadGraph graph) throws DatabaseException { + PipingRules.setEnabled(false); + IMappingSchema schema = getSchema(graph); + mapping = Mappings.createWithListening(schema); + rootNode = (P3DRootNode)mapping.map(graph, input); + // update control points. + // TODO : this should be optimized. + try { + for (INode node : rootNode.getChild()) { + if (node instanceof PipeRun) { + for (PipelineComponent pc : ((PipeRun) node).getChild()) + pc.sync(); + } else if (node instanceof Equipment) { + for (PipelineComponent pc : ((Equipment) node).getChild()) + pc.sync(); + } + } + + for (INode node : rootNode.getChild()) { + if (node instanceof PipeRun) { + for (PipelineComponent pc : ((PipeRun) node).getChild()) + pc.sync2(); + } else if (node instanceof Equipment) { + for (PipelineComponent pc : ((Equipment) node).getChild()) + pc.sync2(); + } + } + for (INode node : rootNode.getChild()) { + if (node instanceof PipeRun) { + PipingRules.validate((PipeRun)node); + } + } + PipingRules.setEnabled(true); + for (INode node : rootNode.getChild()) { + if (node instanceof PipeRun) { + PipeRun run = (PipeRun)node; + for (PipeControlPoint pcp : run.getControlPoints()) + PipingRules.positionUpdate(pcp); + + } + } + } catch (Exception e) { + throw new DatabaseException(e); + } + nodeMap = createNodeMap(getSession(), mapping, panel,rootNode); + } + }); + + if (rootNode == null) + throw new RuntimeException("Scenegraph loading failed."); + populate(); + + selectionProvider = new NodeSelectionProvider2(this,mapping,nodeMap); + + cameraAction.addSelectionChangedListener(selectionProvider); + + cameraAction.addHoverChangedListener(new HoverHighlighter(panel,nodeMap)); + selectionProvider.addSelectionChangedListener(new SelectionHighlighter(panel,nodeMap)); + + getSite().setSelectionProvider(selectionProvider); + getSite().getPage().addPostSelectionListener(selectionProvider); + + //outlinePage = new ScenegraphOutlinePage(rootNode); + + + parent.addDisposeListener(new DisposeListener() { + + @Override + public void widgetDisposed(DisposeEvent e) { + getSite().getPage().removePostSelectionListener(selectionProvider); + + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + + @Override + public void run() { + PipingRules.setEnabled(false); + nodeMap.delete(); + PipingRules.setEnabled(true); + vtkPanelUtil.unregisterPanel(panel); + + } + }); + mapping.dispose(); + component.dispose(); + + + } + }); + } catch (DatabaseException e1) { + ExceptionUtils.logAndShowError("Cannot open Plant3D editor",e1); + return; + } + + translateAction = new TranslateAction(panel,nodeMap); + rotateAction = new RotateAction(panel,nodeMap); + removeAction = new RemoveAction(nodeMap) { + public void setNode(IG3DNode node) { + super.setNode(node); + + + } + }; + routePipeAction = new RoutePipeAction(panel,rootNode); + addComponentAction = new AddComponentAction(panel, rootNode); + + } + + public void populate() { + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + + @Override + public void run() { + nodeMap.populate(); + } + }); + + } + + protected IMappingSchema getSchema(ReadGraph graph) throws DatabaseException { + IMappingSchema schema = SchemaBuilder.getSchema(graph); + return schema; + } + + protected P3DNodeMap createNodeMap(Session session, IMapping mapping, InteractiveVtkPanel panel, P3DRootNode rootNode) { + return new P3DNodeMap(session, mapping, panel,rootNode); + } + + @Override + public void setFocus() { + component.setFocus(); + } + + private void createScene() { + vtkRenderer ren1 = panel.GetRenderer(); + + boolean multiPass = false; + if (multiPass) { + + vtkLightsPass lightsPass = new vtkLightsPass(); + vtkDefaultPass defaultPass = new vtkDefaultPass(); + + + vtkRenderPassCollection passes = new vtkRenderPassCollection(); + passes.AddItem(lightsPass); + passes.AddItem(defaultPass); + + vtkSequencePass seq = new vtkSequencePass(); + seq.SetPasses(passes); + + + + vtkCameraPass cameraPass = new vtkCameraPass(); + cameraPass.SetDelegatePass(seq); + + ren1.SetPass(cameraPass); + + } +// ren1.GetRenderWindow().LineSmoothingOn(); +// ren1.GetRenderWindow().PointSmoothingOn(); +// ren1.GetRenderWindow().PolygonSmoothingOn(); +// ren1.GetRenderWindow().SetMultiSamples(2); + + + + ren1.SetBackground2(1,1,1); // background color white + ren1.SetBackground(0.9,0.9,0.9); + ren1.SetGradientBackground(true); + + // vtkActor grid = vtkShape.createGridActor(8,1.0,1|2|4); + vtkActor grid = vtkShape.createGridActor(8,1.0, 2 ); + grid.SetPickable(0); + ren1.AddActor(grid); + panel.addDeletable(grid); + + { + vtkAxesActor axes = new vtkAxesActor(); + axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(0,0,0); + axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(0,0,0); + axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(0,0,0); + axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetShadow(0); + axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetShadow(0); + axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetShadow(0); + axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff(); + axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff(); + axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff(); + axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().Delete(); + axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().Delete(); + axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().Delete(); + axes.GetXAxisCaptionActor2D().Delete(); + axes.GetYAxisCaptionActor2D().Delete(); + axes.GetZAxisCaptionActor2D().Delete(); + vtkOrientationMarkerWidget widget = new vtkOrientationMarkerWidget(); + widget.SetOutlineColor(0.9300, 0.5700, 0.1300 ); + widget.SetOrientationMarker(axes); + widget.SetInteractor(panel.getRenderWindowInteractor()); + //widget.SetViewport(0.8, 0.0, 1.0, 0.2); // bottom right + //widget.SetViewport(0.0, 0.0, 0.4, 0.4); + widget.SetViewport(0.0, 0.0, 0.2, 0.2); // bottom left + widget.SetEnabled(1); + widget.InteractiveOff(); + } + + + } + + protected Menu contextMenu; + + protected void hookContextMenu() { + MenuManager menuMgr = new MenuManager("#PopupMenu"); + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + createContextMenu(manager); + } + }); + + contextMenu = menuMgr.createContextMenu(parent); + } + + protected void createContextMenu(IMenuManager m) { + List selected = selectionProvider.getSelectedNodes(); + try { + if (selected.size() == 0) { + for (Item eq : P3DUtil.getEquipments()) { + m.add(new AddEquipmentAction(rootNode, eq)); + } + } else if (selected.size() == 1) { + IP3DNode node = (IP3DNode)selected.get(0); + if (node instanceof Equipment) { + m.add(translateAction); + m.add(rotateAction); + for (Item eq : P3DUtil.getNozzles()) { + AddNozzleAction add = new AddNozzleAction(rootNode, eq); + add.setEquipment((Equipment)node); + m.add(add); + } + } else if (node instanceof Nozzle) { + m.add(translateAction); + m.add(rotateAction); + Nozzle nozzle = (Nozzle)node; + m.add(routePipeAction); + routePipeAction.setComponent(nozzle); + routePipeAction.setEnabled(nozzle.getNext() == null && nozzle.getPrevious() == null); + m.add(addComponentAction); + addComponentAction.setComponent(nozzle); + } else if (node instanceof TurnComponent) { + m.add(translateAction); + TurnComponent component = (TurnComponent)node; + m.add(routePipeAction); + routePipeAction.setComponent(component); + routePipeAction.setEnabled(component.getNext() == null || component.getPrevious() == null); + m.add(addComponentAction); + addComponentAction.setComponent(component); + } else if (node instanceof EndComponent) { + m.add(translateAction); + m.add(addComponentAction); + addComponentAction.setComponent((PipelineComponent)node); + } else if (node instanceof InlineComponent) { + //m.add(translateInlineAction); + InlineComponent component = (InlineComponent)node; + m.add(routePipeAction); + routePipeAction.setComponent(component); + m.add(addComponentAction); + addComponentAction.setComponent(component); + } + + m.add(removeAction); + translateAction.setNode(node); + rotateAction.setNode(node); + removeAction.setNode(node); + + } + } catch (DatabaseException e) { + ExceptionUtils.logAndShowError(e); + } + } + + private IContentOutlinePage createOutline() { + if (rootNode == null || selectionProvider == null) + return null; + //IContentOutlinePage outlinePage = new VTKContentOutlinePage(rootNode, selectionProvider); + IContentOutlinePage outlinePage = new P3DContentOutlinePage(rootNode, selectionProvider) { + protected void createContextMenu(IMenuManager manager) { + Plant3DEditor.this.createContextMenu(manager); + }; + }; + outlinePage.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + selectionProvider.selectionChanged(event); + } + }); + return outlinePage; + } + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapter) { + if (IPropertyPage.class.equals(adapter)) + return new StandardPropertyPage(getSite(),getPropertyContexts()); + if (IContentOutlinePage.class.equals(adapter)) { + return createOutline(); + } + if (NodeMap.class.equals(adapter)) { + return nodeMap; + } + if (INode.class.equals(adapter)) { + return rootNode; + } + if (IMapping.class.equals(adapter)) { + return mapping; + } + if (InteractiveVtkPanel.class.equals(adapter)) { + return panel; + } + if (ISelectionProvider.class.equals(adapter)) + return selectionProvider; + return super.getAdapter(adapter); + } + + public Set getPropertyContexts() { + Set result = new HashSet(); + result.add("http://www.simantics.org/Project-1.0/ProjectBrowseContext"); + return result; + } + + public P3DRootNode getRootNode() { + return rootNode; + } + + public IMapping getMapping() { + return mapping; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BallValveGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BallValveGeometryProvider.java new file mode 100644 index 00000000..b0da882a --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BallValveGeometryProvider.java @@ -0,0 +1,48 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; +import org.simantics.opencascade.OccTriangulator; + +public class BallValveGeometryProvider extends BuiltinGeometryProvider { + + public BallValveGeometryProvider(Resource resource) { + super(resource); + } + + private double radius = 0.01; + + @Override + public Collection getModel() throws Exception { + TopoDS_Shape cyl = OccTriangulator.makeCylinder(new double[] {-radius*2, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, radius*4); + TopoDS_Shape sph = OccTriangulator.makeSphere(0, 0, 0, radius*1.5); + TopoDS_Shape shape = OccTriangulator.makeCompound(new TopoDS_Shape[]{cyl, sph}); + cyl.delete(); + sph.delete(); + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (radius < MathTools.NEAR_ZERO) + radius = MathTools.NEAR_ZERO; + + } + + @Override + public void updateCalculatedProperties(Map returnProps) { + returnProps.put("length", radius*4); + + } + + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BuiltinGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BuiltinGeometryProvider.java new file mode 100644 index 00000000..5250cc9e --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/BuiltinGeometryProvider.java @@ -0,0 +1,29 @@ +package org.simantics.plant3d.geometry; + +import java.util.Map; + +import org.eclipse.core.runtime.IAdaptable; +import org.simantics.db.Resource; +import org.simantics.opencascade.ParametricSolidModelProvider; + +public abstract class BuiltinGeometryProvider implements ParametricSolidModelProvider, IAdaptable{ + + private Resource resource; + + public BuiltinGeometryProvider(Resource resource) { + this.resource = resource; + } + + @Override + public Object getAdapter(Class adapter) { + if (Resource.class == adapter) + return resource; + return null; + } + + @Override + public void updateCalculatedProperties(Map returnProps) { + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CapGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CapGeometryProvider.java new file mode 100644 index 00000000..4161d941 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CapGeometryProvider.java @@ -0,0 +1,96 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeEdge; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeFace; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeWire; +import org.jcae.opencascade.jni.BRepPrimAPI_MakeRevol; +import org.jcae.opencascade.jni.GC_MakeArcOfCircle; +import org.jcae.opencascade.jni.GC_MakeSegment; +import org.jcae.opencascade.jni.TopoDS_Edge; +import org.jcae.opencascade.jni.TopoDS_Face; +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.jcae.opencascade.jni.TopoDS_Wire; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; + +public class CapGeometryProvider extends BuiltinGeometryProvider { + + public CapGeometryProvider(Resource resource) { + super(resource); + } + + private double radius = 0.01; + + @Override + public Collection getModel() throws Exception { +// TopoDS_Shape cyl = OccTriangulator.makeCylinder(new double[] {0.0, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, radius*0.25); +// return Collections.singletonList(cyl); + double length = radius * 0.5; + double p0[] = new double[]{ length, 0.0,0.0}; + double p1[] = new double[]{length*0.8, radius*0.6,0.0}; + double p2[] = new double[]{length*0.5, radius,0.0}; + double p3[] = new double[]{ 0.0, radius,0.0}; + double p4[] = new double[]{ 0.0, 0.0,0.0}; + GC_MakeArcOfCircle m1 = new GC_MakeArcOfCircle(p0,p1,p2); + GC_MakeSegment s1 = new GC_MakeSegment(p2,p3); + GC_MakeSegment s2 = new GC_MakeSegment(p3,p4); + + BRepBuilderAPI_MakeEdge edge = new BRepBuilderAPI_MakeEdge(m1.value()); + TopoDS_Edge e1 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(s1.value()); + TopoDS_Edge e2 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(s2.value()); + TopoDS_Edge e3 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + BRepBuilderAPI_MakeWire wire = new BRepBuilderAPI_MakeWire(e1,e2,e3); + TopoDS_Wire w = (TopoDS_Wire)wire.shape(); + wire.delete(); + + BRepBuilderAPI_MakeFace face = new BRepBuilderAPI_MakeFace(w); + TopoDS_Face F = (TopoDS_Face) face.shape(); + face.delete(); + + BRepPrimAPI_MakeRevol revol = new BRepPrimAPI_MakeRevol(F,new double[]{0.0,0.0,0.0,1.0,0.0,0.0}); + TopoDS_Shape shape = revol.shape(); + revol.delete(); + + m1.delete(); + s1.delete(); + s2.delete(); + e1.delete(); + e2.delete(); + e3.delete(); + w.delete(); + F.delete(); + + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (radius < MathTools.NEAR_ZERO) + radius = MathTools.NEAR_ZERO; + + } + + @Override + public void updateCalculatedProperties(Map returnProps) { + returnProps.put("length", radius*4); + + } + + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CheckValveGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CheckValveGeometryProvider.java new file mode 100644 index 00000000..85f4c7aa --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/CheckValveGeometryProvider.java @@ -0,0 +1,47 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; +import org.simantics.opencascade.OccTriangulator; + +public class CheckValveGeometryProvider extends BuiltinGeometryProvider { + + public CheckValveGeometryProvider(Resource resource) { + super(resource); + } + + private double radius = 0.01; + + @Override + public Collection getModel() throws Exception { + double l = radius*0.2; + TopoDS_Shape cyl = OccTriangulator.makeCylinder(new double[] {-radius, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, l); + TopoDS_Shape con = OccTriangulator.makeCone(new double[] {-radius, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius*0.1, radius,radius*2); + TopoDS_Shape shape = OccTriangulator.makeFuse(cyl, con); + cyl.delete(); + con.delete(); + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (radius < MathTools.NEAR_ZERO) + radius = MathTools.NEAR_ZERO; + + } + + @Override + public void updateCalculatedProperties(Map returnProps) { + returnProps.put("length", radius*2); + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ElbowGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ElbowGeometryProvider.java new file mode 100644 index 00000000..46aea58c --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ElbowGeometryProvider.java @@ -0,0 +1,47 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; +import org.simantics.opencascade.OccTriangulator; + +public class ElbowGeometryProvider extends BuiltinGeometryProvider { + + public ElbowGeometryProvider(Resource resource) { + super(resource); + } + + private double radius = 0.01; + private double turnRadius = 0.05; + private double turnAngle = Math.PI * 0.5; + + @Override + public Collection getModel() throws Exception { + double t = Math.tan((Math.PI - turnAngle) * 0.5); + double R = 0.0; + if (t > MathTools.NEAR_ZERO) + R = turnRadius / t; + TopoDS_Shape shape = OccTriangulator.makeTorus(new double[]{-R,0.0,-turnRadius}, new double[] { 0.0, 1.0, 0.0 }, turnRadius, radius,0.0,Math.PI*2.0,turnAngle); +// System.out.println("Create elbow tr:" + turnRadius + " r:" + radius + " angle:" +turnAngle + " " + R); + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("turnRadius")) + turnRadius = (Double)props.get("turnRadius"); + if (props.containsKey("turnAngle")) + turnAngle = (Double)props.get("turnAngle"); + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (radius < MathTools.NEAR_ZERO) + radius = MathTools.NEAR_ZERO; + } + +} + diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/HorizontalTankGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/HorizontalTankGeometryProvider.java new file mode 100644 index 00000000..7a3dc41b --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/HorizontalTankGeometryProvider.java @@ -0,0 +1,101 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeEdge; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeFace; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeWire; +import org.jcae.opencascade.jni.BRepPrimAPI_MakeRevol; +import org.jcae.opencascade.jni.GC_MakeArcOfCircle; +import org.jcae.opencascade.jni.GC_MakeSegment; +import org.jcae.opencascade.jni.TopoDS_Edge; +import org.jcae.opencascade.jni.TopoDS_Face; +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.jcae.opencascade.jni.TopoDS_Wire; +import org.simantics.db.Resource; +import org.simantics.opencascade.OccTriangulator; + +public class HorizontalTankGeometryProvider extends BuiltinGeometryProvider { + + public HorizontalTankGeometryProvider(Resource resource) { + super(resource); + } + + private double length = 1.0; + private double radius = 0.2; + + @Override + public Collection getModel() throws Exception { + + double p0[] = new double[]{-length*0.50, 0.0,0.0}; + double p1[] = new double[]{-length*0.488, radius*0.6,0.0}; + double p2[] = new double[]{-length*0.46, radius,0.0}; + double p3[] = new double[]{ length*0.46, radius,0.0}; + double p4[] = new double[]{ length*0.488, radius*0.6,0.0}; + double p5[] = new double[]{ length*0.50, 0.0,0.0}; + + GC_MakeArcOfCircle m1 = new GC_MakeArcOfCircle(p0,p1,p2); + GC_MakeSegment s1 = new GC_MakeSegment(p2,p3); + GC_MakeArcOfCircle m2 = new GC_MakeArcOfCircle(p3,p4,p5); + + BRepBuilderAPI_MakeEdge edge = new BRepBuilderAPI_MakeEdge(m1.value()); + TopoDS_Edge e1 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(s1.value()); + TopoDS_Edge e2 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(m2.value()); + TopoDS_Edge e3 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + BRepBuilderAPI_MakeWire wire = new BRepBuilderAPI_MakeWire(e1,e2,e3); + TopoDS_Wire w = (TopoDS_Wire)wire.shape(); + wire.delete(); + + BRepBuilderAPI_MakeFace face = new BRepBuilderAPI_MakeFace(w); + TopoDS_Face F = (TopoDS_Face) face.shape(); + face.delete(); + + BRepPrimAPI_MakeRevol revol = new BRepPrimAPI_MakeRevol(F,new double[]{0.0,0.0,0.0,1.0,0.0,0.0}); + TopoDS_Shape shape = revol.shape(); + revol.delete(); + + m1.delete(); + s1.delete(); + m2.delete(); + e1.delete(); + e2.delete(); + e3.delete(); + w.delete(); + F.delete(); + + TopoDS_Shape shape2 = OccTriangulator.makeTranslation(shape, 0.0, radius, 0.0); + shape.delete(); + shape = shape2; + TopoDS_Shape box = OccTriangulator.makeBox(-length*0.4, 0.0, -radius*0.5, -length*0.3, radius, radius*0.5); + shape2 = OccTriangulator.makeFuse(shape, box); + shape.delete(); + box.delete(); + box = OccTriangulator.makeBox(length*0.3, 0.0, -radius*0.5, length*0.4, radius, radius*0.5); + shape = OccTriangulator.makeFuse(shape2, box); + shape2.delete(); + box.delete(); + + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("length")) + length = (Double)props.get("length"); + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/NozzleGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/NozzleGeometryProvider.java new file mode 100644 index 00000000..cdfe73cc --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/NozzleGeometryProvider.java @@ -0,0 +1,40 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.opencascade.OccTriangulator; + +public class NozzleGeometryProvider extends BuiltinGeometryProvider { + + public NozzleGeometryProvider(Resource resource) { + super(resource); + } + + private double length = 0.1; + private double radius = 0.01; + + @Override + public Collection getModel() throws Exception { + TopoDS_Shape shape = OccTriangulator.makeCylinder(new double[] {-length, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, length); + TopoDS_Shape shape2 = OccTriangulator.makeCylinder(new double[] {-length*0.25, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius*1.2, length*0.25); + TopoDS_Shape shape3 = OccTriangulator.makeCompound(new TopoDS_Shape[]{shape,shape2}); + shape.delete(); + shape2.delete(); + return Collections.singletonList(shape3); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("length")) + length = (Double)props.get("length"); + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/PumpGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/PumpGeometryProvider.java new file mode 100644 index 00000000..a7c32a66 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/PumpGeometryProvider.java @@ -0,0 +1,72 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeEdge; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeFace; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeWire; +import org.jcae.opencascade.jni.BRepPrimAPI_MakeRevol; +import org.jcae.opencascade.jni.GC_MakeArcOfCircle; +import org.jcae.opencascade.jni.GC_MakeSegment; +import org.jcae.opencascade.jni.TopoDS_Edge; +import org.jcae.opencascade.jni.TopoDS_Face; +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.jcae.opencascade.jni.TopoDS_Wire; +import org.simantics.db.Resource; +import org.simantics.opencascade.OccTriangulator; + +public class PumpGeometryProvider extends BuiltinGeometryProvider { + + public PumpGeometryProvider(Resource resource) { + super(resource); + } + + private double length = 0.5; + private double width = 0.25; + + @Override + public Collection getModel() throws Exception { + double h = width * 0.5; + double h2 = width * 0.1; + double ld2 = length * 0.5; + double wd2 = width * 0.5; + + double r1 = width * 0.5; + double r2 = r1 *0.2; + double r3 = r1 *0.5; + + double l1 = length * 0.2; + double l2 = length * 0.2; + double l2b = l2 + length * 0.1; + double l3 = length * 0.6; + + double dir[] = new double[]{1.0,0.0,0.0}; + + TopoDS_Shape foundation = OccTriangulator.makeBox(-ld2, 0.0, -wd2, ld2, h2, wd2); + TopoDS_Shape rotator = OccTriangulator.makeCylinder(new double[]{-ld2,h+h2,0.0}, dir, r1, l1); + TopoDS_Shape axis = OccTriangulator.makeCylinder(new double[]{-ld2+l1,h+h2,0.0}, dir, r2, l2b); + TopoDS_Shape motor = OccTriangulator.makeCylinder(new double[]{-ld2+l1+l2,h+h2,0.0}, dir, r3, l3); + TopoDS_Shape motorBox = OccTriangulator.makeBox(-ld2+l1+l2, h2, -r3, ld2, h2+h-r3, r3); + + TopoDS_Shape shape = OccTriangulator.makeCompound(new TopoDS_Shape[]{foundation,rotator,axis,motor,motorBox}); + foundation.delete(); + rotator.delete(); + axis.delete(); + motor.delete(); + motorBox.delete(); + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("length")) + length = (Double)props.get("length"); + if (props.containsKey("width")) { + width = (Double)props.get("width"); + } + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ReducerGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ReducerGeometryProvider.java new file mode 100644 index 00000000..d3387f79 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/ReducerGeometryProvider.java @@ -0,0 +1,57 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; +import org.simantics.opencascade.OccTriangulator; + +public class ReducerGeometryProvider extends BuiltinGeometryProvider { + + public ReducerGeometryProvider(Resource resource) { + super(resource); + } + + private double radius = 0.01; + private double radius2 = 0.02; + + @Override + public Collection getModel() throws Exception { +// GP_Circ circ = new GP_Circ(new double[]{-length*0.5, 0.0, 0.0,1.0,0.0,0.0}, radius); +// GP_Circ circ2 = new GP_Circ(new double[]{length*0.5, 0.0, 0.0,1.0,0.0,0.0}, radius2); +// System.out.println("Reducer " + length + " " + radius + " " + radius2); + double length = Math.max(0.1, Math.abs(radius-radius2)*4.0); + TopoDS_Shape shape; + if (Math.abs(radius-radius2) < MathTools.NEAR_ZERO) { + shape = OccTriangulator.makeCylinder(new double[] {-length*0.5, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, length); + } else { + shape = OccTriangulator.makeCone(new double[] {-length*0.5, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius,radius2, length); + } + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + + + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (props.containsKey("radius2")) { + radius2 = (Double)props.get("radius2"); + } + + + + } + + @Override + public void updateCalculatedProperties(Map returnProps) { + returnProps.put("length", Math.max(0.1, Math.abs(radius-radius2)*4.0)); + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/StraightGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/StraightGeometryProvider.java new file mode 100644 index 00000000..ac63f915 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/StraightGeometryProvider.java @@ -0,0 +1,44 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.db.Resource; +import org.simantics.g3d.math.MathTools; +import org.simantics.opencascade.OccTriangulator; + +public class StraightGeometryProvider extends BuiltinGeometryProvider { + + public StraightGeometryProvider(Resource resource) { + super(resource); + } + + private double length = 1.0; + private double radius = 0.01; + + @Override + public Collection getModel() throws Exception { + TopoDS_Shape shape = OccTriangulator.makeCylinder(new double[] {-length*0.5, 0.0, 0.0}, new double[] { 1.0, 0.0, 0.0 }, radius, length); +// System.out.println("Create straight l:" + length + " r:" + radius); + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("length")) + length = (Double)props.get("length"); + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + if (length < 0.0) + length = 0.0; + if (radius < MathTools.NEAR_ZERO) + radius = MathTools.NEAR_ZERO; + + } + + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/geometry/VerticalTankGeometryProvider.java b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/VerticalTankGeometryProvider.java new file mode 100644 index 00000000..5cb078e0 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/geometry/VerticalTankGeometryProvider.java @@ -0,0 +1,87 @@ +package org.simantics.plant3d.geometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeEdge; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeFace; +import org.jcae.opencascade.jni.BRepBuilderAPI_MakeWire; +import org.jcae.opencascade.jni.BRepPrimAPI_MakeRevol; +import org.jcae.opencascade.jni.GC_MakeArcOfCircle; +import org.jcae.opencascade.jni.GC_MakeSegment; +import org.jcae.opencascade.jni.TopoDS_Edge; +import org.jcae.opencascade.jni.TopoDS_Face; +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.jcae.opencascade.jni.TopoDS_Wire; +import org.simantics.db.Resource; + +public class VerticalTankGeometryProvider extends BuiltinGeometryProvider { + + public VerticalTankGeometryProvider(Resource resource) { + super(resource); + } + + private double height = 1.0; + private double radius = 0.2; + + @Override + public Collection getModel() throws Exception { + + double p0[] = new double[]{ 0.0, height,0.0}; + double p1[] = new double[]{radius*0.6,height*0.98,0.0}; + double p2[] = new double[]{ radius,height*0.94,0.0}; + double p3[] = new double[]{ radius, 0.0,0.0}; + double p4[] = new double[]{ 0.0, 0.0,0.0}; + + GC_MakeArcOfCircle m1 = new GC_MakeArcOfCircle(p0,p1,p2); + GC_MakeSegment s1 = new GC_MakeSegment(p2,p3); + GC_MakeSegment s2 = new GC_MakeSegment(p3,p4); + + BRepBuilderAPI_MakeEdge edge = new BRepBuilderAPI_MakeEdge(m1.value()); + TopoDS_Edge e1 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(s1.value()); + TopoDS_Edge e2 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + edge = new BRepBuilderAPI_MakeEdge(s2.value()); + TopoDS_Edge e3 = (TopoDS_Edge)edge.shape(); + edge.delete(); + + BRepBuilderAPI_MakeWire wire = new BRepBuilderAPI_MakeWire(e1,e2,e3); + TopoDS_Wire w = (TopoDS_Wire)wire.shape(); + wire.delete(); + + BRepBuilderAPI_MakeFace face = new BRepBuilderAPI_MakeFace(w); + TopoDS_Face F = (TopoDS_Face) face.shape(); + face.delete(); + + BRepPrimAPI_MakeRevol revol = new BRepPrimAPI_MakeRevol(F,new double[]{0.0,0.0,0.0,0.0,1.0,0.0}); + TopoDS_Shape shape = revol.shape(); + revol.delete(); + + m1.delete(); + s1.delete(); + s2.delete(); + e1.delete(); + e2.delete(); + e3.delete(); + w.delete(); + F.delete(); + + return Collections.singletonList(shape); + } + + @Override + public void setProperties(Map props) { + if (props.containsKey("height")) + height = (Double)props.get("height"); + if (props.containsKey("radius")) { + radius = (Double)props.get("radius"); + } + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/SplitPointSelectionGizmo.java b/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/SplitPointSelectionGizmo.java new file mode 100644 index 00000000..336fdbd5 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/SplitPointSelectionGizmo.java @@ -0,0 +1,151 @@ +package org.simantics.plant3d.gizmo; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.Collection; + +import javax.vecmath.AxisAngle4d; +import javax.vecmath.Point3d; +import javax.vecmath.Tuple3d; +import javax.vecmath.Vector2d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.math.Ray; +import org.simantics.g3d.scenegraph.RenderListener; +import org.simantics.g3d.shape.Color4d; +import org.simantics.g3d.shape.Cone; +import org.simantics.g3d.shape.Cylinder; +import org.simantics.g3d.shape.Mesh; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.g3d.vtk.gizmo.vtkGizmo; +import org.simantics.g3d.vtk.shape.MeshActor; +import org.simantics.g3d.vtk.utils.vtkUtil; + +import vtk.vtkProp; + +public class SplitPointSelectionGizmo extends vtkGizmo { + + MeshActor actor; + + InteractiveVtkPanel panel; + private RenderListener listener; + private MouseMotionListener mouseListener; + + Point3d start; + Point3d end; + Vector3d dir; + + Vector2d mousePos = new Vector2d(); + + Vector3d pa = new Vector3d(); + Vector3d pb = new Vector3d(); + + Tuple3d splitPoint = null; + + public SplitPointSelectionGizmo(InteractiveVtkPanel panel) { + this.panel = panel; + + int res = 16; + + Mesh cone_x = Cone.create(0.1, res); + cone_x.rotate(MathTools.getQuat(new AxisAngle4d(0,0,-1,Math.PI*0.5))); + cone_x.translate(new Vector3d(0.8,0,0)); + + Mesh tube_x = Cylinder.create(MathTools.ORIGIN, new Vector3d(0.8,0,0), 0.05, res); + tube_x.add(cone_x); + + Color4d z_col = new Color4d(0,1,0,1); + tube_x.setColor(z_col); + + actor = new MeshActor(); + actor.setMesh(tube_x); + + this.listener = new RenderListener() { + @Override + public void preRender() { + Ray ray = vtkUtil.createMouseRay(getRenderer(), mousePos.x, mousePos.y); + //ray.dir.add(ray.pos); + //if (MathTools.intersectLineLine(start, end, ray.pos, ray.dir, pa, pb)) { + double mu[] = new double[2]; + if (MathTools.intersectStraightStraight(start, dir, ray.pos, ray.dir, pa, pb,mu)) { + splitPoint = pa; + if (mu[0] < 0.0) + splitPoint = start; + else if (mu[0] > 1.0) + splitPoint = end; + Vector3d dir = new Vector3d(splitPoint); + dir.sub(pb); + double length = dir.length(); + dir.scale(1.0/length); + AxisAngle4d aa = MathTools.createRotation(MathTools.X_AXIS, dir); + setRotation(aa); + setScale(length); + setPosition(pb); + actor.SetVisibility(1); + } else { + splitPoint = null; + actor.SetVisibility(0); + } + + } + + @Override + public void postRender() { + + } + }; + + this.mouseListener = new MouseMotionListener() { + + @Override + public void mouseMoved(MouseEvent e) { + mousePos.x = e.getX(); + mousePos.y = e.getY(); + SplitPointSelectionGizmo.this.panel.repaint(); + } + + @Override + public void mouseDragged(MouseEvent e) { + mousePos.x = e.getX(); + mousePos.y = e.getY(); + SplitPointSelectionGizmo.this.panel.repaint(); + } + }; + + } + + public void setSplit(Point3d start, Point3d end) { + this.start = start; + this.end = end; + dir = new Vector3d(end); + dir.sub(start); + } + + @Override + public void attach(Object renderingPart) { + super.attach(renderingPart); + panel.addListener(listener); + panel.addMouseMotionListener(mouseListener); + } + + @Override + public void deattach() { + panel.removeListener(listener); + panel.removeMouseMotionListener(mouseListener); + super.deattach(); + } + + @Override + public Collection getGizmo() { + Collection coll = new ArrayList(); + coll.add(actor); + return coll; + } + + public Tuple3d getSplitPoint() { + return splitPoint; + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/TerminalSelectionGizmo.java b/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/TerminalSelectionGizmo.java new file mode 100644 index 00000000..422e022a --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/gizmo/TerminalSelectionGizmo.java @@ -0,0 +1,251 @@ +package org.simantics.plant3d.gizmo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.vecmath.Point2d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.scenegraph.RenderListener; +import org.simantics.g3d.tools.PluginTools; +import org.simantics.g3d.vtk.common.InteractiveVtkPanel; +import org.simantics.g3d.vtk.gizmo.vtkGizmo; +import org.simantics.g3d.vtk.utils.vtkUtil; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.PositionType; + +import vtk.vtkCellArray; +import vtk.vtkFloatArray; +import vtk.vtkPNGReader; +import vtk.vtkPoints; +import vtk.vtkPolyData; +import vtk.vtkPolyDataMapper2D; +import vtk.vtkPolygon; +import vtk.vtkProp; +import vtk.vtkRenderer; +import vtk.vtkTexture; +import vtk.vtkTexturedActor2D; + +public class TerminalSelectionGizmo extends vtkGizmo { + + + + vtkTexturedActor2D prevProp; + vtkTexturedActor2D nextProp; + vtkTexturedActor2D middleProp; + + boolean showPrev = false; + boolean showNext = false; + boolean showMiddle = false; + + Vector3d middle = new Vector3d(); + Vector3d prev = new Vector3d(); + Vector3d next = new Vector3d(); + + InteractiveVtkPanel panel; + + private RenderListener listener; + public TerminalSelectionGizmo(InteractiveVtkPanel panel) { + this.panel = panel; + this.listener = new RenderListener() { + + @Override + public void preRender() { + + if (showMiddle) { + Point2d p = vtkUtil.getScreenCoordinates(getRenderer(), middle); + middleProp.SetDisplayPosition((int)p.x, (int)p.y); + } + if (showPrev) { + Point2d p = vtkUtil.getScreenCoordinates(getRenderer(), prev); + prevProp.SetDisplayPosition((int)p.x, (int)p.y); + } + if (showNext) { + Point2d p = vtkUtil.getScreenCoordinates(getRenderer(), next); + nextProp.SetDisplayPosition((int)p.x, (int)p.y); + } + } + + @Override + public void postRender() { + + + } + }; + } + + @Override + public void attach(Object renderingPart) { + if (nextProp == null) { + loadData(); + + } + panel.addListener(listener); + + super.attach(renderingPart); + + } + + @Override + public Collection getGizmo() { + List list = new ArrayList(); + if (showPrev) { + list.add(prevProp); + } + if (showNext) { + list.add(nextProp); + + } + if (showMiddle) { + list.add(middleProp); + } + return list; + } + + protected void attachActors() { + vtkRenderer ren = getRenderer(); + if (showPrev) { + ren.AddActor(prevProp); + } + if (showNext) { + ren.AddActor(nextProp); + } + if (showMiddle) { + ren.AddActor(middleProp); + } + + } + + @Override + protected void deattachActors() { + panel.removeListener(listener); + vtkRenderer ren = getRenderer(); + ren.RemoveActor(prevProp); + ren.RemoveActor(nextProp); + ren.RemoveActor(middleProp); + } + + public void setComponent(PipelineComponent component, Set allowed) { +// showPrev = component.getPrevious() == null; +// showNext = component.getNext() == null; +// showMiddle = (component instanceof InlineComponent) && !component.getControlPoint().isFixed(); + showPrev = allowed.contains(PositionType.PREVIOUS); + showNext = allowed.contains(PositionType.NEXT); + showMiddle = allowed.contains(PositionType.SPLIT); + + middle = component.getControlPoint().getWorldPosition(); + component.getControlPoint().getControlPointEnds(prev, next); + + } + + private void loadData() { + String middleTexFile = PluginTools.getAbsolutePath(Activator.getDefault().getBundle(), "icons/middle.png"); + String plusTexFile = PluginTools.getAbsolutePath(Activator.getDefault().getBundle(), "icons/plus.png"); + if (middleTexFile == null || plusTexFile == null) + throw new RuntimeException("Cannot resolve required image files."); + + vtkPoints points = new vtkPoints(); + points.InsertNextPoint(-8, -8, 0.0); + points.InsertNextPoint( 8, -8, 0.0); + points.InsertNextPoint( 8, 8, 0.0); + points.InsertNextPoint(-8, 8, 0.0); + + + vtkCellArray cellArray = new vtkCellArray(); + vtkPolygon polygon = new vtkPolygon(); + polygon.GetPointIds().SetNumberOfIds(4); + polygon.GetPointIds().SetId(0, 0); + polygon.GetPointIds().SetId(1, 1); + polygon.GetPointIds().SetId(2, 2); + polygon.GetPointIds().SetId(3, 3); + + cellArray.InsertNextCell(polygon); + + vtkPolyData quad = new vtkPolyData(); + quad.SetPoints(points); + quad.SetPolys(cellArray); + + vtkFloatArray texCoords = new vtkFloatArray(); + + texCoords.SetNumberOfComponents(2); + + texCoords.InsertNextTuple2(0.0, 0.0); + texCoords.InsertNextTuple2(1.0, 0.0); + texCoords.InsertNextTuple2(1.0, 1.0); + texCoords.InsertNextTuple2(0.0, 1.0); + + quad.GetPointData().SetTCoords(texCoords); + + vtkPNGReader middleReader = new vtkPNGReader(); + middleReader.SetFileName(middleTexFile); + + vtkPNGReader plusReader = new vtkPNGReader(); + plusReader.SetFileName(plusTexFile); + + vtkTexture middleTex = new vtkTexture(); + middleTex.SetInputConnection(middleReader.GetOutputPort()); + middleTex.SetInterpolate(1); + + vtkTexture plusTex = new vtkTexture(); + plusTex.SetInputConnection(plusReader.GetOutputPort()); + plusTex.SetInterpolate(1); + + vtkPolyDataMapper2D mapper = new vtkPolyDataMapper2D(); + mapper.SetInput(quad); + + nextProp = new vtkTexturedActor2D(); + prevProp = new vtkTexturedActor2D(); + middleProp = new vtkTexturedActor2D(); + + nextProp.SetMapper(mapper); + nextProp.SetTexture(plusTex); + nextProp.SetPickable(1); + + prevProp.SetMapper(mapper); + prevProp.SetTexture(plusTex); + prevProp.SetPickable(1); + + middleProp.SetMapper(mapper); + middleProp.SetTexture(middleTex); + middleProp.SetPickable(1); + + + plusReader.GetOutputPort().Delete(); + plusReader.Delete(); + middleReader.GetOutputPort().Delete(); + middleReader.Delete(); + middleTex.Delete(); + plusTex.Delete(); + + mapper.Delete(); + quad.GetPointData().Delete(); + quad.Delete(); + points.Delete(); + polygon.GetPointIds().Delete(); + polygon.Delete(); + cellArray.Delete(); + texCoords.Delete(); + + } + + + public PositionType getPickedPosition(vtkProp[] picked) { + if (picked == null) + return null; + for (vtkProp p : picked) { + if (p.equals(middleProp)) + return PositionType.SPLIT; + if (p.equals(nextProp)) + return PositionType.NEXT; + if (p.equals(prevProp)) + return PositionType.PREVIOUS; + } + return null; + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/handlers/NewPlantHandler.java b/org.simantics.plant3d/src/org/simantics/plant3d/handlers/NewPlantHandler.java new file mode 100644 index 00000000..42e64cfe --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/handlers/NewPlantHandler.java @@ -0,0 +1,54 @@ +package org.simantics.plant3d.handlers; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; +import org.simantics.plant3d.Activator; +import org.simantics.plant3d.utils.P3DUtil; + +public class NewPlantHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + final Resource library = Simantics.getProject().get(); + + Job job = new Job("Create Plant ") { + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask("Create Plant" , IProgressMonitor.UNKNOWN); + try { + Simantics.getSession().syncRequest(new WriteRequest() { + + @Override + public void perform(WriteGraph graph) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + String modelName = NameUtils.findFreshName(graph, "Plant", library); + Resource model = P3DUtil.createModel(graph, modelName); + graph.claim(library, l0.ConsistsOf, model); + + } + }); + return Status.OK_STATUS; + } catch (DatabaseException e) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, getName() + " failed.",e); + } + } + + + }; + job.setUser(true); + job.schedule(); + return null; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DPerspectiveFactory.java b/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DPerspectiveFactory.java new file mode 100644 index 00000000..fd2e5d1f --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DPerspectiveFactory.java @@ -0,0 +1,14 @@ +package org.simantics.plant3d.project; + +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.IPerspectiveFactory; + +public class P3DPerspectiveFactory implements IPerspectiveFactory { + + @Override + public void createInitialLayout(IPageLayout layout) { + layout.setEditorAreaVisible(true); + + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DProjectFeature.java b/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DProjectFeature.java new file mode 100644 index 00000000..a39292f9 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/project/P3DProjectFeature.java @@ -0,0 +1,21 @@ +package org.simantics.plant3d.project; + +import org.simantics.project.ProjectKeys; +import org.simantics.project.exception.ProjectException; +import org.simantics.project.features.AbstractProjectFeature; +import org.simantics.project.features.IProjectFeature; + +public class P3DProjectFeature extends AbstractProjectFeature implements IProjectFeature { + + private static final String DEFAULT_PERSPECTIVE = "org.simantics.plant3d.perspective"; + + public P3DProjectFeature() { + // TODO Auto-generated constructor stub + } + + @Override + public void configure() throws ProjectException { + getProjectElement().setHint(ProjectKeys.DEFAULT_PERSPECTIVE, DEFAULT_PERSPECTIVE); + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/property/P3DSelectionProcessor.java b/org.simantics.plant3d/src/org/simantics/plant3d/property/P3DSelectionProcessor.java new file mode 100644 index 00000000..54941609 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/property/P3DSelectionProcessor.java @@ -0,0 +1,169 @@ +package org.simantics.plant3d.property; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbenchSite; +import org.simantics.Simantics; +import org.simantics.browsing.ui.swt.PartNameListener; +import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.management.ISessionContext; +import org.simantics.db.request.Read; +import org.simantics.g3d.property.PropertyTabContributor; +import org.simantics.g3d.property.PropertyTabUtil; +import org.simantics.g3d.scenegraph.IG3DNode; +import org.simantics.g3d.tools.AdaptationUtils; +import org.simantics.g3d.vtk.property.VTKPropertyTabContributor; +import org.simantics.objmap.structural.StructuralResource; +import org.simantics.selectionview.BasicPropertyTab; +import org.simantics.selectionview.ComparableTabContributor; +import org.simantics.selectionview.PropertyTabContributorImpl; +import org.simantics.selectionview.SelectionProcessor; +import org.simantics.utils.datastructures.Callback; + +import vtk.vtkProp; + +public class P3DSelectionProcessor implements SelectionProcessor { + + private static final boolean DEBUG = false; + @Override + public Collection process(Object selection, ReadGraph backend) { +// System.out.println(getClass().getSimpleName() + " incoming selection: " + ObjectUtils.toString(selection)); + + + Collection result = new ArrayList(); + Collection resourceCollection = AdaptationUtils.adaptToCollection(selection, Resource.class); + Collection structuralResourceCollection = AdaptationUtils.adaptToCollection(selection, StructuralResource.class); + Collection propCollection = AdaptationUtils.adaptToCollection(selection, vtkProp.class); + Collection nodeCollection = AdaptationUtils.adaptToCollection(selection, IG3DNode.class); + boolean readOnly = false; + if (resourceCollection.size() == 0 && structuralResourceCollection.size() > 0) { + for (StructuralResource sr : structuralResourceCollection) { + if (sr.isStructural() && !sr.isStructuralRoot()) + readOnly = true; + resourceCollection.add(sr.getResource()); + } + } + + + if (nodeCollection.size() == 1) { + IG3DNode node = nodeCollection.iterator().next(); + List contributors = PropertyTabUtil.getContributors(node); + int i = 100; + for (PropertyTabContributor c : contributors) { + result.add(new ComparableTabContributor(c, i--, node, c.getId())); + } + } + + if (DEBUG) { + if (propCollection.size() == 1) { + vtkProp prop = propCollection.iterator().next(); + if (prop == null) + throw new NullPointerException(); + result.add(new ComparableTabContributor(new VTKPropertyTabContributor(), -2, prop, "VTK")); + } + + if (resourceCollection.size() > 0) { + if (resourceCollection.size() > 1) + result.add(new ComparableTabContributor(new MultiSelectionTabContibutor(),0, resourceCollection, "Graph")); + else if (resourceCollection.size() == 1){ + try { + Resource r = resourceCollection.iterator().next(); + result.add(new ComparableTabContributor(new P3DBasicPropertyTab(!readOnly), 0, r, "Graph")); + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + if(result.size() == 0) { + result.add(new ComparableTabContributor(new NoneSelectionTabContributor(),0, resourceCollection, "Empty")); + } + + return result; + } + + public class P3DBasicPropertyTab extends BasicPropertyTab { + + boolean enabled; + public P3DBasicPropertyTab(boolean enabled) { + this.enabled = enabled; + } + public void updatePartName(ISelection forSelection, Callback updateCallback) { + Read read = getPartNameReadRequest(forSelection); + if (read == null) { + updateCallback.run("Override to control part name (PropertyTabContributorImpl.updatePartName)"); + } else { + Simantics.getSession().asyncRequest(read, new PartNameListener(updateCallback)); + } + } + public Read getPartNameReadRequest(ISelection forSelection) { + final Resource r = AdaptationUtils.adaptToSingle(forSelection, Resource.class); + if (r == null) + return null; + return new Read() { + @Override + public String perform(ReadGraph graph) throws DatabaseException { + return NameUtils.getSafeName(graph, r); + } + }; + } + + @Override + public void createControls(Composite body, IWorkbenchSite site, + ISessionContext context, WidgetSupport support) { + // TODO Auto-generated method stub + super.createControls(body, site, context, support); + ((Composite)parameterExplorer.getExplorerControl()).setEnabled(enabled); + } + } + + + + public class MultiSelectionTabContibutor extends PropertyTabContributorImpl { + public void createControls(org.eclipse.swt.widgets.Composite body, org.eclipse.ui.IWorkbenchSite site, org.simantics.db.management.ISessionContext context, org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport support) { + //Composite composite = new Composite(body, SWT.NONE); + } + + public Read getPartNameReadRequest(ISelection forSelection) { + final Collection coll = AdaptationUtils.adaptToCollection(forSelection, Resource.class); + if (coll.size() == 0) + return null; + return new Read() { + @Override + public String perform(ReadGraph graph) throws DatabaseException { + String title = ""; + for (Resource r : coll) { + title += NameUtils.getSafeName(graph, r) +","; + } + System.out.println(title); + return title.substring(0,title.length()-1); + } + }; + } + } + + public class NoneSelectionTabContributor extends PropertyTabContributorImpl { + @Override + public void createControls(Composite body, IWorkbenchSite site, + ISessionContext context, WidgetSupport support) { + //Composite composite = new Composite(body, SWT.NONE); + + } + + public void updatePartName(ISelection forSelection, Callback updateCallback) { + updateCallback.run("No Selection"); + } + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/EndComponent.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/EndComponent.java new file mode 100644 index 00000000..5df76c0f --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/EndComponent.java @@ -0,0 +1,53 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.HashMap; +import java.util.Map; + +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.objmap.graph.annotations.DynamicGraphType; +import org.simantics.objmap.graph.annotations.GetType; +import org.simantics.objmap.graph.annotations.SetType; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.ControlPointFactory; + +@DynamicGraphType(Plant3D.URIs.EndComponent) +public class EndComponent extends PipelineComponent { + + private String type; + PipeControlPoint controlPoint; + + @GetType(Plant3D.URIs.EndComponent) + public String getType() { + return type; + } + + @SetType(Plant3D.URIs.EndComponent) + public void setType(String type) throws Exception { + this.type = type; + controlPoint = ControlPointFactory.create(this); + + } + + @Override + public PipeControlPoint getControlPoint() { + return controlPoint; + } + + @Override + public void setParent(ParentNode parent, String name) { + super.setParent(parent, name); + setPipeRun((PipeRun)parent); + } + + @Override + public Map updateParameterMap() { + Map map = new HashMap(); + + PipeRun pipeRun = getPipeRun(); + if (pipeRun != null) { + map.put("radius", pipeRun.getPipeDiameter() * 0.5); + } + return map; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Equipment.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Equipment.java new file mode 100644 index 00000000..3bac765c --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Equipment.java @@ -0,0 +1,125 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.vecmath.Quat4d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.property.annotations.CompoundGetPropertyValue; +import org.simantics.g3d.property.annotations.CompoundSetPropertyValue; +import org.simantics.objmap.graph.annotations.CompoundRelatedGetValue; +import org.simantics.objmap.graph.annotations.CompoundRelatedSetValue; +import org.simantics.objmap.graph.annotations.DynamicGraphType; +import org.simantics.objmap.graph.annotations.GetType; +import org.simantics.objmap.graph.annotations.RelatedElementsAdd; +import org.simantics.objmap.graph.annotations.RelatedElementsGet; +import org.simantics.objmap.graph.annotations.RelatedElementsRem; +import org.simantics.objmap.graph.annotations.SetType; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; + + +@DynamicGraphType(Plant3D.URIs.Equipment) +public class Equipment extends P3DParentGeometryNode { + + private String type; + + @GetType(Plant3D.URIs.Equipment) + public String getType() { + return type; + } + + @SetType(Plant3D.URIs.Equipment) + public void setType(String type) { + this.type = type; + } + + + @RelatedElementsAdd(Plant3D.URIs.HasNozzle) + public void addChild(Nozzle node) { + Set ids = new HashSet(); + for (Nozzle n : getChild()) { + ids.add(n.getNozzleId()); + } + int newId = 0; + while (ids.contains(newId)) + newId++; + addNode(Plant3D.URIs.HasNozzle,node); + node.setNozzleId(newId); + } + + @RelatedElementsGet(Plant3D.URIs.HasNozzle) + public Collection getChild() { + return getNodes(Plant3D.URIs.HasNozzle); + } + + @RelatedElementsRem(Plant3D.URIs.HasNozzle) + public void remChild(Nozzle node) { + removeNode(Plant3D.URIs.HasNozzle, node); + } + + @Override + @CompoundRelatedGetValue(objRelation=Plant3D.URIs.hasParameter,objType=Plant3D.URIs.Parameter,valRelation=Plant3D.URIs.hasParameterValue) + @CompoundGetPropertyValue(name="Parameters",tabId="Parameters",value="parameters") + public Map getParameterMap() { + return super.getParameterMap(); + } + + @Override + @CompoundRelatedSetValue(Plant3D.URIs.hasParameter) + @CompoundSetPropertyValue(value="parameters") + public void setParameterMap(Map parameters) { + super.setParameterMap(parameters); + } + + @Override + protected double[] getColor() { + return new double[]{1,0,0}; + } + + @Override + protected double[] getSelectedColor() { + return new double[]{0.5,0,0.5}; + } + + @Override + public void setPosition(Vector3d position) { + if (MathTools.equals(position, getPosition())) + return; + super.setPosition(position); + for (Nozzle n : getChild()) { + n.getControlPoint()._setWorldPosition(n.getWorldPosition()); + n.getControlPoint()._setWorldOrientation(n.getWorldOrientation()); + } + for (Nozzle n : getChild()) { + try { + PipingRules.requestUpdate(n.getControlPoint()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void setOrientation(Quat4d orientation) { + if (MathTools.equals(orientation, getOrientation())) + return; + super.setOrientation(orientation); + for (Nozzle n : getChild()) { + n.getControlPoint()._setWorldPosition(n.getWorldPosition()); + n.getControlPoint()._setWorldOrientation(n.getWorldOrientation()); + } + for (Nozzle n : getChild()) { + try { + PipingRules.requestUpdate(n.getControlPoint()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/GeometryNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/GeometryNode.java new file mode 100644 index 00000000..8d7ba9d9 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/GeometryNode.java @@ -0,0 +1,239 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.scenegraph.NodeHighlighter; +import org.simantics.g3d.vtk.utils.vtkUtil; +import org.simantics.objmap.graph.annotations.RelatedGetObj; +import org.simantics.objmap.graph.annotations.RelatedSetObj; +import org.simantics.opencascade.OccTriangulator; +import org.simantics.opencascade.ParametricSolidModelProvider; +import org.simantics.opencascade.SolidModelProvider; +import org.simantics.opencascade.vtk.vtkSolidObject; +import org.simantics.plant3d.ontology.Plant3D; + +import vtk.vtkActor; +import vtk.vtkPanel; +import vtk.vtkProp3D; +import vtk.vtkProperty; +import vtk.vtkRenderer; + +public abstract class GeometryNode extends P3DNode implements ParameterizedNode, NodeHighlighter { + + private TopoDS_Shape solidModel; + private vtkSolidObject solidObject; + + private Map currentParameters; + private Map calculatedParameters; + + private boolean parametersUpdated = true; + + public GeometryNode() { + currentParameters = new HashMap(); + calculatedParameters = new HashMap(); + } + + @Override + public void visualize(vtkPanel panel) { + if (solidModelProvider != null) { + + updateParameters(); + if (solidModel == null || parametersUpdated) { + createGeometry(); + } + } + if ((solidObject == null || parametersUpdated) && solidModel != null) { + solidObject = new vtkSolidObject(panel, solidModel); + } + if (solidObject != null) { + solidObject.visualizeSolid(true,true, false,false); + updateVisuals(true, true); + } + parametersUpdated = false; + + } + + public void updateParameters() { + if (solidModelProvider instanceof ParametricSolidModelProvider) { + ((ParametricSolidModelProvider)solidModelProvider).setProperties(currentParameters); + ((ParametricSolidModelProvider)solidModelProvider).updateCalculatedProperties(calculatedParameters); + } + } + + private void createGeometry() { + Collection shapes; + try { + shapes = solidModelProvider.getModel(); + if (shapes.size() == 1) { + solidModel = shapes.iterator().next(); + } else { + solidModel = OccTriangulator.makeCompound(shapes.toArray(new TopoDS_Shape[shapes.size()])); + for (TopoDS_Shape shape : shapes) { + shape.delete(); + } + } +// shapes.clear(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public Map getParameterMap() { + return Collections.unmodifiableMap(currentParameters); + } + + public Map getCalculatedParameters() { + return calculatedParameters; + } + + public void setParametersUpdated(boolean parametersUpdated) { + this.parametersUpdated = parametersUpdated; + } + + public void setParameterMap(Map parameters) { + for (String id : parameters.keySet()) { + Object currentValue = currentParameters.get(id); + Object newValue = parameters.get(id); + if (currentValue == newValue) + continue; + if (currentValue instanceof Double) { + if (Math.abs((Double)currentValue-(Double)newValue) < MathTools.NEAR_ZERO) + continue; + } + currentParameters.put(id, newValue); + parametersUpdated = true; + firePropertyChanged(id); + } + } + + private SolidModelProvider solidModelProvider; + + @RelatedGetObj(Plant3D.URIs.hasGeometry) + public SolidModelProvider getGeometry() { + return solidModelProvider; + } + + @RelatedSetObj(Plant3D.URIs.hasGeometry) + public void setGeometry(final SolidModelProvider provider) { + if (provider != null && provider.equals(solidModelProvider)) + return; + + if (solidModelProvider != null) { + deleteData(); + } + solidModelProvider = provider; + firePropertyChanged(Plant3D.URIs.hasGeometry); + } + + private void deleteData() { + solidModelProvider = null; + if (solidObject != null) { + solidObject.clearActors(); + solidObject = null; + } + if (solidModel != null) { + solidModel.delete(); + solidModel = null; + } + } + + @Override + public Collection getActors() { + List list = new ArrayList(); + if (solidObject != null) + list.addAll(solidObject.getActors()); + return list; + } + + @Override + public void stopVisualize() { + if (solidObject != null) { + solidObject.clearActorsAWT(); + solidObject = null; + } + if (solidModel != null) { + solidModel.delete(); + solidModel = null; + } + + } + + private boolean selected = false; + private boolean hover = false; + + @Override + public void highlight(HighlightEventType type) { + if (type == HighlightEventType.Selection || type == HighlightEventType.ClearSelection) { + selected = type == HighlightEventType.Selection; +// hingeA.visualizeSolid(selected,true,false); +// hingeB.visualizeSolid(selected,true,false); + updateVisuals(true, false); +// update(null); + } else { + hover = type == HighlightEventType.Hover; + updateVisuals(false, true); + } + + + } + + protected double[] getSelectedColor() { + return new double[]{1,0,0}; + } + + protected double[] getColor() { + return new double[]{1,1,0}; + } + + private void updateVisuals(boolean s, boolean h) { + if (solidObject != null) { + if (s) { + double color[]; + if (selected) { + color = getSelectedColor(); + + } else { + color = getColor(); + + } + for (vtkProp3D prop : solidObject.getSolid()) { + vtkProperty property = ((vtkActor)prop).GetProperty(); + property.SetColor(color); + property.Delete(); + } + } + if (h) { + double color[] = new double[]{0,0,0}; + if (hover) + color = new double[]{1,0,1}; + for (vtkProp3D prop : solidObject.getEdges()) { + vtkProperty property = ((vtkActor)prop).GetProperty(); + property.SetColor(color); + property.Delete(); + } + + } + } else { +// if (s) { +// axes.addToRenderer(); +// axes.setAxesVisibility(selected); +// } + } + } + + public void update(vtkRenderer ren) { + vtkUtil.updateTransform(getActors(), getWorldPosition(), getWorldOrientation()); + } + + public TopoDS_Shape getSolidModel() { + return solidModel; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DNode.java new file mode 100644 index 00000000..56086afc --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DNode.java @@ -0,0 +1,11 @@ +package org.simantics.plant3d.scenegraph; + +import org.simantics.g3d.scenegraph.IG3DNode; + +import vtk.vtkRenderer; + +public interface IP3DNode extends IG3DNode { + + public void update(vtkRenderer ren); + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DVisualNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DVisualNode.java new file mode 100644 index 00000000..ff568b5c --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/IP3DVisualNode.java @@ -0,0 +1,19 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.Collection; + +import vtk.vtkPanel; +import vtk.vtkProp3D; +import vtk.vtkRenderer; + +public interface IP3DVisualNode extends IP3DNode { + public String getName(); + public void setName(String name); + + public void visualize(vtkPanel panel); + public void stopVisualize(); + + public void update(vtkRenderer ren); + + public Collection getActors(); +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/InlineComponent.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/InlineComponent.java new file mode 100644 index 00000000..88b1fab3 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/InlineComponent.java @@ -0,0 +1,81 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.HashMap; +import java.util.Map; + +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.objmap.graph.annotations.DynamicGraphType; +import org.simantics.objmap.graph.annotations.GetType; +import org.simantics.objmap.graph.annotations.SetType; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.ControlPointFactory; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; + +@DynamicGraphType(Plant3D.URIs.InlineComponent) +public class InlineComponent extends PipelineComponent { + + private String type; + private PipeControlPoint controlPoint; + + @GetType(Plant3D.URIs.InlineComponent) + public String getType() { + return type; + } + + @SetType(Plant3D.URIs.InlineComponent) + public void setType(String type) throws Exception{ + this.type = type; + controlPoint = ControlPointFactory.create(this); + + } + + @Override + public PipeControlPoint getControlPoint() { + return controlPoint; + } + + @Override + public void setParent(ParentNode parent, String name) { + super.setParent(parent, name); + setPipeRun((PipeRun)parent); + } + + public boolean isVariableLength() { + return !controlPoint.isFixed(); + } + + @Override + public void updateParameters() { + super.updateParameters(); + if (!isVariableLength()) { + Map calculated = getCalculatedParameters(); + if (calculated.containsKey("length")) { + controlPoint.setLength((Double)calculated.get("length")); + } + } + } + + @Override + public Map updateParameterMap() { + Map map = new HashMap(); + if (controlPoint != null) { + if (!Double.isNaN(controlPoint.getLength())) + map.put("length", controlPoint.getLength()); + if (controlPoint.isDualInline()) { + PipeControlPoint sub = controlPoint.getSubPoint().get(0); + PipeRun pipeRun = sub.getPipeRun(); + if (pipeRun != null) { + map.put("radius2", pipeRun.getPipeDiameter() * 0.5); + } + } + } + + PipeRun pipeRun = getPipeRun(); + if (pipeRun != null) { + map.put("radius", pipeRun.getPipeDiameter() * 0.5); + } + return map; + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Nozzle.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Nozzle.java new file mode 100644 index 00000000..9c4ad6f6 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/Nozzle.java @@ -0,0 +1,153 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.HashMap; +import java.util.Map; + +import javax.vecmath.Quat4d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.objmap.graph.annotations.DynamicGraphType; +import org.simantics.objmap.graph.annotations.GetType; +import org.simantics.objmap.graph.annotations.RelatedGetObj; +import org.simantics.objmap.graph.annotations.RelatedGetValue; +import org.simantics.objmap.graph.annotations.RelatedSetObj; +import org.simantics.objmap.graph.annotations.RelatedSetValue; +import org.simantics.objmap.graph.annotations.SetType; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.ControlPointFactory; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; + + +@DynamicGraphType(Plant3D.URIs.Nozzle) +public class Nozzle extends PipelineComponent { + + private String type; + private PipeControlPoint controlPoint; + + @GetType(Plant3D.URIs.Nozzle) + public String getType() { + return type; + } + + @SetType(Plant3D.URIs.Nozzle) + public void setType(String type) throws Exception{ + this.type = type; + _createCP(); + + } + + private int id = 0; + + @RelatedGetValue(Plant3D.URIs.HasNozzleId) + @GetPropertyValue(name="Nozzle ID", value=Plant3D.URIs.HasNozzleId, tabId="Default") + public int getNozzleId() { + return id; + } + + @RelatedSetValue(Plant3D.URIs.HasNozzleId) + public void setNozzleId(int id) { + if (id == this.id) + return; + this.id = id; + firePropertyChanged(Plant3D.URIs.HasNozzleId); + } + + private void _createCP() throws Exception{ + if (controlPoint != null) + return; + if (getPipeRun() != null) { + controlPoint = ControlPointFactory.create(this); + // TODO : these should not be needed. + controlPoint.setDeletable(false); + controlPoint.setFixed(true); + } + } + + @RelatedSetObj(Plant3D.URIs.HasPipeRun) + @Override + public void setPipeRun(PipeRun pipeRun) { + super.setPipeRun(pipeRun); + try { + _createCP(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + firePropertyChanged(Plant3D.URIs.HasPipeRun); + } + + @RelatedGetObj(Plant3D.URIs.HasPipeRun) + @Override + public PipeRun getPipeRun() { + return super.getPipeRun(); + } + + @Override + public PipeControlPoint getControlPoint() { + return controlPoint; + } + + + @Override + public void setPosition(Vector3d position) { + super.setPosition(position); + updateCP(); + } + + @Override + public void setOrientation(Quat4d orientation) { + super.setOrientation(orientation); + updateCP(); + } + + private void updateCP() { + if (controlPoint == null) + return; + if (controlPoint.getPipeRun() == null) + return; + controlPoint._setWorldPosition(getWorldPosition()); + controlPoint._setWorldOrientation(getWorldOrientation()); + } + + + @Override + public Map updateParameterMap() { + Map map = new HashMap(); + + PipeRun pipeRun = getPipeRun(); + if (pipeRun != null) { + map.put("length", pipeRun.getPipeDiameter() * 2.0); + map.put("radius", pipeRun.getPipeDiameter() * 0.5); + } + return map; + } + + @Override + protected double[] getColor() { + return new double[]{0.7,0.7,0.7}; + } + + @Override + protected double[] getSelectedColor() { + return new double[]{0.5,0,0.5}; + } + + public boolean isConnected() { + PipeControlPoint pcp = getControlPoint(); + return (pcp.getNext() != null || pcp.getPrevious() != null); + } + + public boolean isNextConnected() { + PipeControlPoint pcp = getControlPoint(); + return (pcp.getNext() != null); + } + + public boolean isPrevConnected() { + PipeControlPoint pcp = getControlPoint(); + return (pcp.getNext() != null); + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DNode.java new file mode 100644 index 00000000..84e1535e --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DNode.java @@ -0,0 +1,34 @@ +package org.simantics.plant3d.scenegraph; + +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.property.annotations.SetPropertyValue; +import org.simantics.g3d.scenegraph.G3DNode; +import org.simantics.layer0.Layer0; +import org.simantics.objmap.graph.annotations.RelatedGetValue; +import org.simantics.objmap.graph.annotations.RelatedSetValue; + +public abstract class P3DNode extends G3DNode implements IP3DVisualNode { + private String name; + + + @RelatedGetValue(Layer0.URIs.HasName) + @GetPropertyValue(value = Layer0.URIs.HasName, tabId = "Default", name = "Name") + public String getName() { + return name; + } + + @RelatedSetValue(Layer0.URIs.HasName) + @SetPropertyValue(Layer0.URIs.HasName) + public void setName(String name) { + if (name == null) + return; + this.name = name; + firePropertyChanged(Layer0.URIs.HasName); + } + + @Override + public String toString() { + return getName(); + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentGeometryNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentGeometryNode.java new file mode 100644 index 00000000..c7492008 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentGeometryNode.java @@ -0,0 +1,235 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jcae.opencascade.jni.TopoDS_Shape; +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.scenegraph.NodeHighlighter; +import org.simantics.g3d.vtk.utils.vtkUtil; +import org.simantics.objmap.graph.annotations.RelatedGetObj; +import org.simantics.objmap.graph.annotations.RelatedSetObj; +import org.simantics.opencascade.OccTriangulator; +import org.simantics.opencascade.ParametricSolidModelProvider; +import org.simantics.opencascade.SolidModelProvider; +import org.simantics.opencascade.vtk.vtkSolidObject; +import org.simantics.plant3d.ontology.Plant3D; + +import vtk.vtkActor; +import vtk.vtkPanel; +import vtk.vtkProp3D; +import vtk.vtkProperty; +import vtk.vtkRenderer; + +public class P3DParentGeometryNode extends P3DParentNode implements ParameterizedNode, NodeHighlighter{ + private TopoDS_Shape solidModel; + private vtkSolidObject solidObject; + + private Map currentParameters; + private Map calculatedParameters; + + private boolean parametersUpdated = true; + + + public P3DParentGeometryNode() { + currentParameters = new HashMap(); + calculatedParameters = new HashMap(); + } + + @Override + public void visualize(vtkPanel ren) { + if (solidModelProvider != null) { + + updateParameters(); + + if (solidModel == null || parametersUpdated) { + createGeometry(); + } + } + if ((solidObject == null || parametersUpdated) && solidModel != null) { + solidObject = new vtkSolidObject(ren, solidModel); + } + if (solidObject != null) { + solidObject.visualizeSolid(true,true, false,false); + updateVisuals(true, true); + } + parametersUpdated = false; + + } + + public void updateParameters() { + if (solidModelProvider instanceof ParametricSolidModelProvider) { + ((ParametricSolidModelProvider)solidModelProvider).setProperties(currentParameters); + ((ParametricSolidModelProvider)solidModelProvider).updateCalculatedProperties(calculatedParameters); + } + } + + private void createGeometry() { + Collection shapes; + try { + shapes = solidModelProvider.getModel(); + if (shapes.size() == 1) { + solidModel = shapes.iterator().next(); + } else { + solidModel = OccTriangulator.makeCompound(shapes.toArray(new TopoDS_Shape[shapes.size()])); + for (TopoDS_Shape shape : shapes) { + shape.delete(); + } + } +// shapes.clear(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + + public Map getParameterMap() { + return Collections.unmodifiableMap(currentParameters); + } + + public void setParameterMap(Map parameters) { + for (String id : parameters.keySet()) { + Object currentValue = currentParameters.get(id); + Object newValue = parameters.get(id); + if (currentValue == newValue) + continue; + if (currentValue instanceof Double) { + if (Math.abs((Double)currentValue-(Double)newValue) < MathTools.NEAR_ZERO) + continue; + } + currentParameters.put(id, newValue); + parametersUpdated = true; + firePropertyChanged(id); + } + } + + + private SolidModelProvider solidModelProvider; + + @RelatedGetObj(Plant3D.URIs.hasGeometry) + public SolidModelProvider getGeometry() { + return solidModelProvider; + } + + @RelatedSetObj(Plant3D.URIs.hasGeometry) + public void setGeometry(final SolidModelProvider provider) { + if (provider != null && provider.equals(solidModelProvider)) + return; + + if (solidModelProvider != null) { + deleteData(); + } + solidModelProvider = provider; + firePropertyChanged(Plant3D.URIs.hasGeometry); + } + + private void deleteData() { + solidModelProvider = null; + if (solidObject != null) { + solidObject.clearActors(); + solidObject = null; + } + if (solidModel != null) { + solidModel.delete(); + solidModel = null; + } + } + + @Override + public Collection getActors() { + List list = new ArrayList(); + if (solidObject != null) + list.addAll(solidObject.getActors()); + return list; + } + + @Override + public void stopVisualize() { + if (solidObject != null) { + solidObject.clearActorsAWT(); + solidObject = null; + } + if (solidModel != null) { + solidModel.delete(); + solidModel = null; + } + + } + + private boolean selected = false; + private boolean hover = false; + + @Override + public void highlight(HighlightEventType type) { + if (type == HighlightEventType.Selection || type == HighlightEventType.ClearSelection) { + selected = type == HighlightEventType.Selection; +// hingeA.visualizeSolid(selected,true,false); +// hingeB.visualizeSolid(selected,true,false); + updateVisuals(true, false); +// update(null); + } else { + hover = type == HighlightEventType.Hover; + updateVisuals(false, true); + } + + + } + + protected double[] getSelectedColor() { + return new double[]{1,0,0}; + } + + protected double[] getColor() { + return new double[]{1,1,0}; + } + + private void updateVisuals(boolean s, boolean h) { + if (solidObject != null) { + if (s) { + double color[]; + if (selected) { + color = getSelectedColor(); + + } else { + color = getColor(); + + } + for (vtkProp3D prop : solidObject.getSolid()) { + vtkProperty property = ((vtkActor)prop).GetProperty(); + property.SetColor(color); + property.Delete(); + } + } + if (h) { + double color[] = new double[]{0,0,0}; + if (hover) + color = new double[]{1,0,1}; + for (vtkProp3D prop : solidObject.getEdges()) { + vtkProperty property = ((vtkActor)prop).GetProperty(); + property.SetColor(color); + property.Delete(); + } + + } + } else { +// if (s) { +// axes.addToRenderer(); +// axes.setAxesVisibility(selected); +// } + } + } + + public void update(vtkRenderer ren) { + vtkUtil.updateTransform(getActors(), getWorldPosition(), getWorldOrientation()); + } + + public TopoDS_Shape getSolidModel() { + return solidModel; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentNode.java new file mode 100644 index 00000000..08c289da --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DParentNode.java @@ -0,0 +1,194 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.HashSet; +import java.util.Set; + +import javax.vecmath.Quat4d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.ontology.G3D; +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.property.annotations.PropertyContributor; +import org.simantics.g3d.property.annotations.SetPropertyValue; +import org.simantics.g3d.scenegraph.IG3DNode; +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.g3d.tools.NodeTools; +import org.simantics.layer0.Layer0; +import org.simantics.objmap.graph.annotations.RelatedGetValue; +import org.simantics.objmap.graph.annotations.RelatedSetValue; + +@PropertyContributor +public abstract class P3DParentNode extends ParentNode implements IP3DVisualNode { + + private String name; + + + + + @RelatedGetValue(Layer0.URIs.HasName) + @GetPropertyValue(value = Layer0.URIs.HasName, tabId = "Default", name = "Name") + public String getName() { + return name; + } + + @RelatedSetValue(Layer0.URIs.HasName) + @SetPropertyValue(Layer0.URIs.HasName) + public void setName(String name) { + if (name == null) + return; + this.name = name; + firePropertyChanged(Layer0.URIs.HasName); + } + + @Override + public String toString() { + return getName(); + } + + private Vector3d position = new Vector3d(); + private Quat4d orientation = MathTools.getIdentityQuat(); + + @Override + @GetPropertyValue(value = G3D.URIs.hasOrientation, tabId = "Transform", name = "Orientation") + public Quat4d getOrientation() { + return orientation; + } + + @RelatedGetValue(G3D.URIs.hasOrientation) + public double[] getOrientationArr() { + double arr[] = new double[4]; + orientation.get(arr); + return arr; + + } + + @Override + @GetPropertyValue(value = G3D.URIs.hasPosition, tabId = "Transform", name = "Position") + public Vector3d getPosition() { + return position; + } + + @RelatedGetValue(G3D.URIs.hasPosition) + public double[] getPositionArr() { + double arr[] = new double[3]; + position.get(arr); + return arr; + } + + @Override + @SetPropertyValue(G3D.URIs.hasOrientation) + public void setOrientation(Quat4d orientation) { + assert(orientation != null); + this.orientation = orientation; + + firePropertyChanged(G3D.URIs.hasOrientation); + } + + @Override + @SetPropertyValue(G3D.URIs.hasPosition) + public void setPosition(Vector3d position) { + assert(position != null); + this.position = position; + + firePropertyChanged(G3D.URIs.hasPosition); + } + + @RelatedSetValue(G3D.URIs.hasOrientation) + public void setOrientation(double[] arr) { + if (arr == null) + return; + setOrientation(new Quat4d(arr)); + } + + @RelatedSetValue(G3D.URIs.hasPosition) + public void setPosition(double[] arr) { + if (arr == null) + return; + setPosition(new Vector3d(arr)); + } + + @Override + @GetPropertyValue(value = G3D.URIs.hasWorldPosition, tabId = "Transform", name = "World Position") + public Vector3d getWorldPosition() { + IG3DNode parent = (IG3DNode)getParent(); + if (parent == null) + return position; + return NodeTools.getWorldPosition(parent, new Vector3d(position)); + } + + + public Vector3d getWorldPosition(Vector3d localPosition) { + return NodeTools.getWorldPosition(this,localPosition); + } + + + @Override + @GetPropertyValue(value = G3D.URIs.hasWorldOrientation, tabId = "Transform", name = "World Orientation") + public Quat4d getWorldOrientation() { + return getWorldOrientation(new Quat4d(orientation)); + } + + public Quat4d getWorldOrientation(Quat4d localOrientation) { + IG3DNode parent = (IG3DNode)getParent(); + if (parent == null) + return localOrientation; + return NodeTools.getWorldOrientation(parent, localOrientation); + } + + @Override + public Vector3d getLocalPosition(Vector3d worldPosition) { + IG3DNode parent = (IG3DNode)getParent(); + if (parent == null) + return worldPosition; + return NodeTools.getLocalPosition(parent,new Vector3d(worldPosition)); + } + + @Override + public Quat4d getLocalOrientation(Quat4d worldOrientation) { + IG3DNode parent = (IG3DNode)getParent(); + if (parent == null) + return worldOrientation; + return NodeTools.getLocalOrientation(parent, new Quat4d(worldOrientation)); + } + + @Override + @SetPropertyValue(G3D.URIs.hasWorldPosition) + public void setWorldPosition(Vector3d position) { + Vector3d localPos = getLocalPosition(position); + setPosition(localPos); + } + + @Override + @SetPropertyValue(G3D.URIs.hasWorldOrientation) + public void setWorldOrientation(Quat4d orientation) { + Quat4d localOr = getLocalOrientation(orientation); + setOrientation(localOr); + } + + @Override + public Object getAdapter(Class adapter) { + if (IG3DNode.class == adapter) + return this; + return null; + } + + public String getUniqueName(String prefix) { + Set names = new HashSet(); + for (IP3DNode node : getNodes()) { + if (!(node instanceof IP3DVisualNode)) + continue; + IP3DVisualNode n = (IP3DVisualNode)node; + names.add(n.getName()); + } + int i = 1; + while (true) { + String genName = prefix + "_" + i; + if (!names.contains(genName)) + return genName; + i++; + } + } + + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DRootNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DRootNode.java new file mode 100644 index 00000000..5a968604 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/P3DRootNode.java @@ -0,0 +1,170 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.vecmath.Quat4d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.scenegraph.IG3DNode; +import org.simantics.g3d.scenegraph.NodeMap; +import org.simantics.g3d.scenegraph.NodeMapProvider; +import org.simantics.g3d.scenegraph.base.INode; +import org.simantics.g3d.scenegraph.base.NodeException; +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.objmap.graph.annotations.GraphType; +import org.simantics.objmap.graph.annotations.RelatedElementsAdd; +import org.simantics.objmap.graph.annotations.RelatedElementsGet; +import org.simantics.objmap.graph.annotations.RelatedElementsRem; +import org.simantics.plant3d.editor.P3DNodeMap; +import org.simantics.plant3d.ontology.Plant3D; + +import vtk.vtkProp; + +@GraphType(Plant3D.URIs.Plant) +public class P3DRootNode extends ParentNode implements IG3DNode, NodeMapProvider { + + + @RelatedElementsAdd(Plant3D.URIs.childen) + public void addChild(IP3DVisualNode node) { + addNode(Plant3D.URIs.childen,node); + } + + @RelatedElementsGet(Plant3D.URIs.childen) + public Collection getChild() { + return getNodes(Plant3D.URIs.childen); + } + + @RelatedElementsRem(Plant3D.URIs.childen) + public void remChild(IP3DNode node) { + removeNode(Plant3D.URIs.childen, node); + } + + private P3DNodeMap nodeMap; + + public void setNodeMap(P3DNodeMap nodeMap) { + this.nodeMap = nodeMap; + } + + @Override + public NodeMap getNodeMap() { + return nodeMap; + } + + @Override + public ParentNode getParent() { + return null; + } + + @Override + public ParentNode getRootNode() { + return this; + } + + public javax.vecmath.Quat4d getOrientation() { + return MathTools.getIdentityQuat(); + }; + + @Override + public Vector3d getPosition() { + return new Vector3d(); + } + + @Override + public Quat4d getWorldOrientation() { + return MathTools.getIdentityQuat(); + } + + @Override + public Vector3d getWorldPosition() { + return new Vector3d(); + } + + @Override + public Quat4d getWorldOrientation(Quat4d localOrientation) { + return localOrientation; + } + + @Override + public Vector3d getWorldPosition(Vector3d localPosition) { + return localPosition; + } + + @Override + public Quat4d getLocalOrientation(Quat4d worldOrientation) { + return worldOrientation; + } + + @Override + public Vector3d getLocalPosition(Vector3d worldPosition) { + return worldPosition; + } + + @Override + public void setPosition(Vector3d position) { + throw new NodeException("Cannot set root node position"); + } + + @Override + public void setOrientation(Quat4d orientation) { + throw new NodeException("Cannot set root node orientation"); + } + + @Override + public void setWorldOrientation(Quat4d orientation) { + throw new NodeException("Cannot set root node orientation"); + } + + @Override + public void setWorldPosition(Vector3d position) { + throw new NodeException("Cannot set root node orientation"); + } + + public String getUniqueName(String prefix) { + Set names = new HashSet(); + for (INode node : getChild()) { + if (!(node instanceof IP3DVisualNode)) + continue; + IP3DVisualNode n = (IP3DVisualNode)node; + names.add(n.getName()); + } + int i = 1; + while (true) { + String genName = prefix + "_" + i; + if (!names.contains(genName)) + return genName; + i++; + } + } + + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapter) { + if (NodeMap.class == adapter) + return nodeMap; + return null; + } + + public Nozzle createNozzle() { + return new Nozzle(); + } + + public Equipment createEquipment() { + return new Equipment(); + } + + public InlineComponent createInline() { + return new InlineComponent(); + } + + public EndComponent createEnd() { + return new EndComponent(); + } + + public TurnComponent createTurn() { + return new TurnComponent(); + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/ParameterizedNode.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/ParameterizedNode.java new file mode 100644 index 00000000..7aa0e808 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/ParameterizedNode.java @@ -0,0 +1,14 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.Map; +import java.util.Set; + +import org.simantics.g3d.scenegraph.IG3DNode; + +public interface ParameterizedNode extends IG3DNode { + + + public Map getParameterMap(); + public void setParameterMap(Map parameters); + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipeRun.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipeRun.java new file mode 100644 index 00000000..2932f1d2 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipeRun.java @@ -0,0 +1,161 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.property.annotations.PropertyTabBlacklist; +import org.simantics.g3d.property.annotations.SetPropertyValue; +import org.simantics.g3d.scenegraph.IG3DNode; +import org.simantics.objmap.graph.annotations.GraphType; +import org.simantics.objmap.graph.annotations.RelatedElementsAdd; +import org.simantics.objmap.graph.annotations.RelatedElementsGet; +import org.simantics.objmap.graph.annotations.RelatedElementsRem; +import org.simantics.objmap.graph.annotations.RelatedGetValue; +import org.simantics.objmap.graph.annotations.RelatedSetValue; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; + +import vtk.vtkPanel; +import vtk.vtkProp3D; +import vtk.vtkRenderer; + +@GraphType(Plant3D.URIs.PipeRun) +@PropertyTabBlacklist("Transform") +public class PipeRun extends P3DParentNode { + + private double pipeDiameter = 0.1; + private double turnRadius = 0.2; + + @Override + public void update(vtkRenderer ren) { + + } + + @Override + public void visualize(vtkPanel panel) { + + } + + @Override + public Collection getActors() { + return Collections.EMPTY_LIST; + } + + @Override + public void stopVisualize() { + + } + + @RelatedGetValue(Plant3D.URIs.HasTurnRadius) + @GetPropertyValue(value=Plant3D.URIs.HasTurnRadius, name = "Elbow radius") + public double getTurnRadius() { + return turnRadius; + } + + @RelatedSetValue(Plant3D.URIs.HasTurnRadius) + @SetPropertyValue(Plant3D.URIs.HasTurnRadius) + public void setTurnRadius(double turnRadius) { + this.turnRadius = turnRadius; + firePropertyChanged(Plant3D.URIs.HasTurnRadius); + } + + @RelatedGetValue(Plant3D.URIs.HasPipeDiameter) + @GetPropertyValue(value=Plant3D.URIs.HasPipeDiameter, name = "Diameter") + public double getPipeDiameter() { + return pipeDiameter; + } + + @RelatedSetValue(Plant3D.URIs.HasPipeDiameter) + @SetPropertyValue(Plant3D.URIs.HasPipeDiameter) + public void setPipeDiameter(double pipeDiameter) { + this.pipeDiameter = pipeDiameter; + firePropertyChanged(Plant3D.URIs.HasPipeDiameter); + } + + @RelatedElementsAdd(Plant3D.URIs.childen) + public void addChild(PipelineComponent node) { + addNode(Plant3D.URIs.childen,node); + } + + @RelatedElementsGet(Plant3D.URIs.childen) + public Collection getChild() { + Collection coll = new ArrayList(); + for (IG3DNode n : getNodes(Plant3D.URIs.childen)) { + coll.add((PipelineComponent)n); + } + return coll; + } + + @RelatedElementsRem(Plant3D.URIs.childen) + public void remChild(PipelineComponent node) { + removeNode(Plant3D.URIs.childen, node); + } + + + public List getSortedChild() { + List coll = new ArrayList(); + for (IG3DNode n : getNodes(Plant3D.URIs.childen)) { + coll.add((PipelineComponent)n); + } + Collections.sort(coll, new ComponentComparator()); + return coll; + } + public void addChild(PipeControlPoint node) { + addNode("pipecp",node); + } + + public void remChild(PipeControlPoint node) { + removeNode("pipecp", node); + } + + public void deattachChild(PipeControlPoint node) { + deattachNode("pipecp", node); + } + + public Collection getControlPoints() { + Collection coll = new ArrayList(); + for (IG3DNode n : getNodes("pipecp")) { + coll.add((PipeControlPoint)n); + } + return coll; + } + + public boolean equalSpecs(PipeRun other) { + if (!MathTools.equals(pipeDiameter,other.pipeDiameter)) + return false; + if (!MathTools.equals(turnRadius,other.turnRadius)) + return false; + return true; + } + + private class ComponentComparator implements Comparator { + @Override + public int compare(PipelineComponent o1, PipelineComponent o2) { + if (o1 == o2) + return 0; + int i = 1; + PipelineComponent c = o1.getPrevious(); + while (c != null) { + if (c == o2) + return i; + c = c.getPrevious(); + i++; + } + i = -1; + c = o1.getNext(); + while (c != null) { + if (c == o2) + return i; + c = c.getNext(); + i--; + } + return 0; + + } + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipelineComponent.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipelineComponent.java new file mode 100644 index 00000000..8fe597ca --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/PipelineComponent.java @@ -0,0 +1,394 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.Collections; +import java.util.Map; + +import javax.vecmath.Quat4d; +import javax.vecmath.Tuple3d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.property.annotations.PropertyContributor; +import org.simantics.objmap.graph.annotations.RelatedGetObj; +import org.simantics.objmap.graph.annotations.RelatedSetObj; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Type; +import org.simantics.plant3d.scenegraph.controlpoint.PipingRules; + +/** + * + * @author Marko Luukkainen + * + */ +@PropertyContributor +public abstract class PipelineComponent extends GeometryNode { + + + private PipeRun pipeRun; + private PipeRun alternativePipeRun; + private PipelineComponent next; + private PipelineComponent previous; + + /** + * Sets the pipe run. + * + * With in-line,turn, and end components, the pipe run is the parent object in the scene-graph. + * With nozzles, the pipe run setting is explicit (nozzle has to be linked to the piperun, since the parent object is equipment). + * With size change components (in-line), there is also alternative pipe run, which must match the next component's pipe run. + * + * @param pipeRun + */ + public void setPipeRun(PipeRun pipeRun) { + if (pipeRun == this.pipeRun) + return; + this.pipeRun = pipeRun; + if (getControlPoint() != null) { + getControlPoint().deattach(); + if (pipeRun != null) { + pipeRun.addChild(getControlPoint()); + } + } + updateParameters(); + } + + @RelatedGetObj(Plant3D.URIs.HasAlternativePipeRun) + public PipeRun getAlternaitvePipeRun() { + return alternativePipeRun; + } + + @RelatedSetObj(Plant3D.URIs.HasAlternativePipeRun) + public void setAlternativePipeRun(PipeRun pipeRun) { + if (this.alternativePipeRun == pipeRun) + return; + this.alternativePipeRun = pipeRun; + if (getControlPoint().isDualInline()) { + PipeControlPoint sub = getControlPoint().getSubPoint().get(0); + if (sub.getParent() != this.alternativePipeRun) + this.alternativePipeRun.addChild(sub); + } + firePropertyChanged(Plant3D.URIs.HasAlternativePipeRun); + } + + @Override + public void updateParameters() { + setParameterMap(updateParameterMap()); + super.updateParameters(); + } + + public abstract void setType(String typeURI) throws Exception; + + @RelatedGetObj(Plant3D.URIs.HasNext) + public PipelineComponent getNext() { + return next; + } + + @RelatedSetObj(Plant3D.URIs.HasNext) + public void setNext(PipelineComponent comp) { + if (next == comp) + return; + this.next = comp; + syncNext(); + firePropertyChanged(Plant3D.URIs.HasNext); + if (comp != null) + comp.sync(); +// System.out.println(this + " next " + comp); + } + + + @RelatedGetObj(Plant3D.URIs.HasPrevious) + public PipelineComponent getPrevious() { + return previous; + } + + @RelatedSetObj(Plant3D.URIs.HasPrevious) + public void setPrevious(PipelineComponent comp) { + if (previous == comp) + return; + this.previous = comp; + + firePropertyChanged(Plant3D.URIs.HasPrevious); + if (comp != null) + comp.sync(); +// System.out.println(this + " prev " + comp); + } + private PipelineComponent branch0; + + @RelatedGetObj(Plant3D.URIs.HasBranch0) + public PipelineComponent getBranch0() { + return branch0; + } + + @RelatedSetObj(Plant3D.URIs.HasBranch0) + public void setBranch0(PipelineComponent comp) { + if (branch0 == comp) + return; + this.branch0 = comp; + syncBranch0(); + firePropertyChanged(Plant3D.URIs.HasBranch0); + if (comp != null) + comp.sync(); +// System.out.println(this + " next " + comp); + } + + + + private PipeControlPoint getBranchPoint() { + PipeControlPoint branchPoint; + if (getControlPoint().getSubPoint().size() > 0) { + branchPoint = getControlPoint().getSubPoint().get(0); + } else { + if (branch0.getPipeRun() == null) + return null; + branchPoint = new PipeControlPoint(this,branch0.getPipeRun()); + branchPoint.setFixed(false); + branchPoint.setType(Type.END); + branchPoint.parent = getControlPoint(); + getControlPoint().children.add(branchPoint); + branchPoint.setWorldOrientation(getControlPoint().getWorldOrientation()); + branchPoint.setWorldPosition(getControlPoint().getWorldPosition()); + } + return branchPoint; + } + + private boolean _connectNext(PipeControlPoint pcp, PipeControlPoint nextPCP) { + if (nextPCP == null) + return false; + if (pcp.getNext() != nextPCP) { + pcp.setNext(nextPCP); + } + if (pcp.isDualInline()) { + PipeControlPoint sub = pcp.getSubPoint().get(0); + if (sub.getNext() != nextPCP) + sub.setNext(nextPCP); + } + return true; + } + + private boolean _connectPrev(PipeControlPoint pcp, PipeControlPoint prevPCP) { + if (prevPCP == null) + return false; + if (prevPCP.isDualInline()) + prevPCP = prevPCP.getSubPoint().get(0); + if (pcp.getPrevious() != prevPCP) { + pcp.setPrevious(prevPCP); + } + if (pcp.isDualInline()) { + PipeControlPoint sub = pcp.getSubPoint().get(0); + if (sub.getPrevious() != prevPCP) + sub.setPrevious(prevPCP); + } + return true; + } + + + + private boolean syncNext() { + + if (getControlPoint() != null) { + if (next != null ) { + if (next.getControlPoint() != null && next.getPipeRun() != null) { + + // TODO, relying that the other direction is connected. + boolean nxt = next.getPrevious() == this; + boolean br0 = next.getBranch0() == this; + if (nxt){ + return _connectNext(getControlPoint(), next.getControlPoint()); + } else if (br0) { + return _connectNext(getControlPoint(), next.getBranchPoint()); + } + } else { + return false; + } + + } else if (getControlPoint().getPrevious() != null) { + getControlPoint().setNext(null); + } + } else { + return false; + } + return true; + } + + private boolean syncPrevious() { + + if (getControlPoint() != null) { + if (previous != null ) { + if (previous.getControlPoint() != null && previous.getPipeRun() != null) { + + // TODO, relying that the other direction is connected. + boolean prev = previous.getNext() == this; + boolean br0 = previous.getBranch0() == this; + if (prev){ + return _connectPrev(getControlPoint(), previous.getControlPoint()); + } else if (br0) { + return _connectPrev(getControlPoint(), previous.getBranchPoint()); + } + } else { + return false; + } + + } else if (getControlPoint().getPrevious() != null) { + getControlPoint().setPrevious(null); + } + } else { + return false; + } + return true; + } + + private boolean syncBranch0() { + if (getControlPoint() != null) { + if (getControlPoint().isDualInline()) { + branch0 = null; + return false; + } + if (branch0 != null) { + if (branch0.getControlPoint() != null && branch0.getPipeRun() != null) { + PipeControlPoint branchPoint = getBranchPoint(); + PipeControlPoint pcp = branch0.getControlPoint(); + // TODO, relying that the other direction is connected. + boolean next = branch0.getPrevious() == this; // this --> branch0 + boolean prev = branch0.getNext() == this; + if (next) { + _connectNext(branchPoint, pcp); + } else if (prev){ + _connectPrev(branchPoint, pcp); + } + } else { + return false; + } + + } else if (getControlPoint().getSubPoint().size() > 0) { // TODO : this may cause problems? (Removes branch point, before branch has been set?) + getControlPoint().getSubPoint().get(0).remove(); + getControlPoint().children.clear(); + } + } else { + return false; + } + return true; + } + + public void sync() { + syncPrevious(); + syncNext(); + syncBranch0(); + } + + public void sync2() { +// if (getControlPoint().isDualInline()) { +// PipeControlPoint sub = getControlPoint().getSubPoint().get(0); +// next.getControlPoint().getPipeRun().addChild(sub); +// } + getControlPoint()._setWorldOrientation(getWorldOrientation()); + getControlPoint()._setWorldPosition(getWorldPosition()); + } + + public Map updateParameterMap() { + return Collections.EMPTY_MAP; + } + + public PipeRun getPipeRun() { + return pipeRun; + } + + public abstract String getType(); + public abstract PipeControlPoint getControlPoint(); + + @Override + public void remove() { + PipeControlPoint pcp = getControlPoint(); + if (pcp != null) { + pcp.remove(); + } + super.remove(); + } + + @Override + protected double[] getColor() { + if (getControlPoint() == null || !getControlPoint().isFixed()) + return new double[]{0.7,0.7,0.7}; + else + return new double[]{1.0,0.0,0.0}; + } + + @Override + protected double[] getSelectedColor() { + return new double[]{0.5,0,0.5}; + } + + @Override + public void setOrientation(Quat4d orientation) { + if (MathTools.equals(orientation, getOrientation())) + return; + super.setOrientation(orientation); + if (getControlPoint() != null) { + getControlPoint()._setWorldOrientation(getWorldOrientation()); + try { + PipingRules.requestUpdate(getControlPoint()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + @Override + public void setPosition(Vector3d position) { + if (MathTools.equals(position, getPosition())) + return; + super.setPosition(position); + if (getControlPoint() != null) { + getControlPoint()._setWorldPosition(getWorldPosition()); + try { + PipingRules.requestUpdate(getControlPoint()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + + public void _setWorldPosition(Vector3d position) { + Vector3d localPos = getLocalPosition(position); + super.setPosition(localPos); + } + + public void _setWorldOrientation(Quat4d orientation) { + Quat4d localOr = getLocalOrientation(orientation); + super.setOrientation(localOr); + } + + @GetPropertyValue(name="Flow Length", value="flowlength", tabId = "Default") + public Double getFlowLength() { + PipeControlPoint pcp = getControlPoint(); + if (pcp == null) + return null; + switch (pcp.getType()) { + case INLINE: + return pcp.getLength(); + case END: + return null; + case TURN: { + double r = getPipeRun().getTurnRadius(); + double a = pcp.getTurnAngle(); + return a*r; + } + default: + return null; + } + } + + public void getControlPointEnds(Tuple3d p1, Tuple3d p2) { + getControlPoint().getControlPointEnds(p1, p2); + } + + public Vector3d getNormal() { + Vector3d v = new Vector3d(); + MathTools.rotate(getWorldOrientation(), MathTools.Z_AXIS, v); + return v; + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/SchemaBuilder.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/SchemaBuilder.java new file mode 100644 index 00000000..761e1707 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/SchemaBuilder.java @@ -0,0 +1,45 @@ +package org.simantics.plant3d.scenegraph; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.request.Read; +import org.simantics.objmap.graph.schema.IMappingSchema; +import org.simantics.objmap.graph.schema.MappingSchemas; +import org.simantics.objmap.graph.schema.SimpleSchema; +import org.simantics.opencascade.SolidModelProvider; +import org.simantics.plant3d.ontology.Plant3D; + +public class SchemaBuilder { + public static IMappingSchema getSchema() throws DatabaseException{ + return Simantics.getSession().syncRequest(new Read>() { + @Override + public IMappingSchema perform(ReadGraph g) + throws DatabaseException { + return getSchema(g); + } + }); + } + + public static IMappingSchema getSchema(ReadGraph g) throws DatabaseException{ + try { + SimpleSchema schema = new SimpleSchema(); + schema.addLinkType(MappingSchemas.fromAnnotations(g, Equipment.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, InlineComponent.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, TurnComponent.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, EndComponent.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, Nozzle.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, P3DRootNode.class)); + schema.addLinkType(MappingSchemas.fromAnnotations(g, PipeRun.class)); + + schema.addLinkType(MappingSchemas.fromAdaptable(g, Plant3D.URIs.Builtin_GeometryProvider, SolidModelProvider.class)); + return schema; + } catch (IllegalAccessException e) { + throw new DatabaseException(e); + } catch (InstantiationException e) { + throw new DatabaseException(e); + } + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/TurnComponent.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/TurnComponent.java new file mode 100644 index 00000000..6050b60f --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/TurnComponent.java @@ -0,0 +1,83 @@ +package org.simantics.plant3d.scenegraph; + +import java.util.HashMap; +import java.util.Map; + +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.scenegraph.base.ParentNode; +import org.simantics.objmap.graph.annotations.DynamicGraphType; +import org.simantics.objmap.graph.annotations.GetType; +import org.simantics.objmap.graph.annotations.SetType; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.controlpoint.ControlPointFactory; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint; + +@DynamicGraphType(Plant3D.URIs.TurnComponent) +public class TurnComponent extends PipelineComponent { + + + private String type; + private PipeControlPoint controlPoint; + + @GetType(Plant3D.URIs.TurnComponent) + public String getType() { + return type; + } + + @SetType(Plant3D.URIs.TurnComponent) + public void setType(String type) throws Exception{ + this.type = type; + controlPoint = ControlPointFactory.create(this); + } + + @Override + public PipeControlPoint getControlPoint() { + return controlPoint; + } + + @Override + public void setParent(ParentNode parent, String name) { + super.setParent(parent, name); + setPipeRun((PipeRun)parent); + } + + @Override + public Map updateParameterMap() { + Map map = new HashMap(); + + PipeRun pipeRun = getPipeRun(); + if (pipeRun != null) { + map.put("turnRadius", pipeRun.getTurnRadius()); + map.put("radius", pipeRun.getPipeDiameter() * 0.5); + } + if (controlPoint != null && controlPoint.getTurnAngle() != null && !Double.isNaN(controlPoint.getTurnAngle())) { + map.put("turnAngle", controlPoint.getTurnAngle()); + } + return map; + } + + + public Double getTurnAngle() { + return getControlPoint().getTurnAngle(); + } + + @GetPropertyValue(name="Turn Angle", value="turn angle", tabId = "Default") + public Double getTurnAngleDeg() { + Double d = getControlPoint().getTurnAngle(); + if (d == null) + return null; + return MathTools.radToDeg(d); + } + + public Vector3d getTurnAxis() { + return getControlPoint().getTurnAxis(); + } + + @Override + public Vector3d getNormal() { + return getTurnAxis(); + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/ControlPointFactory.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/ControlPointFactory.java new file mode 100644 index 00000000..193cdd27 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/ControlPointFactory.java @@ -0,0 +1,134 @@ +package org.simantics.plant3d.scenegraph.controlpoint; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.request.Read; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Type; +import org.simantics.plant3d.utils.Item; +import org.simantics.plant3d.utils.P3DUtil; + +public class ControlPointFactory { + + private static Map cache = new HashMap(); + + + public static void preloadCache() throws Exception { + List items = P3DUtil.getEnds(); + items.addAll(P3DUtil.getInlines()); + items.addAll(P3DUtil.getNozzles()); + items.addAll(P3DUtil.getTurns()); + + for (Item item : items) { + Instruction inst = createInstruction(item.getUri()); + cache.put(item.getUri(), inst); + } + } + + public static PipeControlPoint create(PipelineComponent component) throws Exception { + Instruction inst = cache.get(component.getType()); + if (inst == null) { + inst = createInstruction(component.getType()); + cache.put(component.getType(), inst); + } + if (inst == null) { + return null; + } + + PipeControlPoint pcp = new PipeControlPoint(component); + pcp.setType(inst.type); + pcp.setFixed(inst.fixed); + switch(inst.type) { + case END: + + break; + case INLINE: + if (inst.isOffset || inst.isSizeChange) { + PipeControlPoint sub = new PipeControlPoint(component); + pcp.children.add(sub); + sub.parent = pcp; + sub.setType(inst.type); + sub.setFixed(inst.fixed); + sub.setSub(true); +// pcp.setOffset(0.0); + if (inst.isOffset) + pcp.setOffset(0.0); + } + + break; + case TURN: + + break; + } + return pcp; + } + + + private static class Instruction { + Type type; + boolean fixed; + boolean isOffset; + boolean isSizeChange; + + + } + + private static Instruction createInstruction(final String type) throws Exception { + return Simantics.getSession().syncRequest(new Read() { + @Override + public Instruction perform(ReadGraph graph) throws DatabaseException { + Resource res = graph.getResource(type); + Plant3D p3d = Plant3D.getInstance(graph); + Instruction i = new Instruction(); + i.fixed = false; + i.isOffset = false; + i.isSizeChange = false; + i.type = Type.INLINE; + if (graph.isInheritedFrom(res, p3d.Nozzle)) { + i.fixed = true; + i.isOffset = false; + i.isSizeChange = false; + i.type = Type.END; + } else if (graph.isInheritedFrom(res, p3d.InlineComponent)){ + + i.type = Type.INLINE; + if (graph.hasStatement(res,p3d.VariableLengthInlineComponent)) { + i.fixed = false; + } else if (graph.hasStatement(res,p3d.FixedLengthInlineComponent)) { + i.fixed = true; + } + + if (graph.hasStatement(res,p3d.SizeChangeComponent)) { + i.isSizeChange = true; + } + + if (graph.hasStatement(res,p3d.OffsetComponent)) { + i.isOffset = true; + } + + } else if (graph.isInheritedFrom(res, p3d.TurnComponent)) { + i.type = Type.TURN; + if (graph.hasStatement(res,p3d.VariableAngleTurnComponent)) { + i.fixed = false; + } else if (graph.hasStatement(res,p3d.FixedAngleTurnComponent)) { + i.fixed = true; + } + } else if (graph.isInheritedFrom(res, p3d.EndComponent)) { + i.fixed = false; + i.type = Type.END; + } else { + return null; + } + return i; + } + }); + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java new file mode 100644 index 00000000..02bce622 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java @@ -0,0 +1,1077 @@ +package org.simantics.plant3d.scenegraph.controlpoint; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.vecmath.AxisAngle4d; +import javax.vecmath.Matrix3d; +import javax.vecmath.Quat4d; +import javax.vecmath.Tuple3d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.g3d.property.annotations.GetPropertyValue; +import org.simantics.g3d.scenegraph.G3DNode; +import org.simantics.plant3d.scenegraph.IP3DNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; + +import vtk.vtkRenderer; + + +public class PipeControlPoint extends G3DNode implements IP3DNode { + + public enum Type{INLINE,TURN,END}; + public enum Direction{NEXT,PREVIOUS}; + public enum PositionType {SPLIT,NEXT,PREVIOUS,PORT} + + private PipelineComponent component; + + private Type type; + private boolean fixed = true; + private boolean deletable = true; + private boolean sub = false; + + public PipeControlPoint(PipelineComponent component) { + this.component = component; + if (component.getPipeRun() != null) + component.getPipeRun().addChild(this); + + } + + public PipeControlPoint(PipelineComponent component, PipeRun piperun) { + this.component = component; + piperun.addChild(this); + } + + @Override + public void update(vtkRenderer ren) { + try { + PipingRules.requestUpdate(this); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public PipeRun getPipeRun() { + return (PipeRun)getParent(); + } + + public PipelineComponent getPipelineComponent() { + return component; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + @GetPropertyValue(name="Fixed",tabId="Debug",value="fixed") + public boolean isFixed() { + return fixed; + } + + + public void setFixed(boolean fixed) { + this.fixed = fixed; + } + + public void setSub(boolean sub) { + this.sub = sub; + } + + @GetPropertyValue(name="Deletable",tabId="Debug",value="deletable") + public boolean isDeletable() { + return deletable; + } + + public void setDeletable(boolean deletable) { + this.deletable = deletable; + } + + public boolean isPathLegEnd() { + return type != Type.INLINE; + } + + public boolean isEnd() { + return type == Type.END; + } + + public boolean isTurn() { + return type == Type.TURN; + } + + public boolean isInline() { + return type == Type.INLINE; + } + + public boolean isDirected() { + return fixed && isEnd(); + } + + public boolean isNonDirected() { + return !fixed && isEnd(); + } + + public boolean isVariableLength() { + return !fixed && isInline(); + } + + public boolean isVariableAngle() { + return !fixed && isTurn(); + } + + public boolean isBranchEnd() { + return deletable && isEnd(); + } + + public boolean isOffset() { + return offset != null; + } + + public boolean isDualSub() { + return parent != null && sub; + } + + public boolean isDualInline() { + return children.size() == 1 && children.get(0).isDualSub(); + } + + public boolean isSizeChange() { + if (children.size() == 0) + return false; + if (!isDualInline()) + return false; + return getPipeRun() != children.get(0).getPipeRun(); + } + + + private PipeControlPoint next; + private PipeControlPoint previous; + + public PipeControlPoint getNext() { + return next; + } + + public PipeControlPoint getPrevious() { + return previous; + } + + public void setNext(PipeControlPoint next) { + if (isEnd() && previous != null && next != null) + throw new RuntimeException("End control points are allowed to have only one connection"); +// if (next != null && getPipeRun() == null) +// throw new RuntimeException("Cannot connect control point befor piperun has been set"); + this.next = next; + if (component != null) { + if (parent == null || sub) + component.setNext(next != null ? next.component : null); + else + component.setBranch0(next != null ? next.component : null); + } + } + + public void setPrevious(PipeControlPoint previous) { + if (isEnd() && next != null && previous != null) + throw new RuntimeException("End control points are allowed to have only one connection"); +// if (previous != null && getPipeRun() == null) +// throw new RuntimeException("Cannot connect control point befor piperun has been set"); + this.previous = previous; + if (component != null) + if (parent == null || sub) + component.setPrevious(previous != null ? previous.component : null); + else + component.setBranch0(previous != null ? previous.component : null); + } + + public PipeControlPoint parent; + public List children = new ArrayList(); + + public List getSubPoint() { + return children; + } + + public PipeControlPoint getParentPoint() { + return parent; + } + + + + + + + + + private double length; + private Double turnAngle; + private Vector3d turnAxis; + + private Double offset; + private Double rotationAngle; + + @GetPropertyValue(name="Length",tabId="Debug",value="length") + public double getLength() { + return length; + } + + public void setLength(double l) { + if (Double.isInfinite(l) || Double.isNaN(l)) { + return; + } + if (Math.abs(this.length-l) < MathTools.NEAR_ZERO) + return; + this.length = l; + firePropertyChanged("length"); + if (isDualInline()) + getSubPoint().get(0).setLength(l); + } + + @GetPropertyValue(name="Turn Angle",tabId="Debug",value="turnAngle") + public Double getTurnAngle() { + return turnAngle; + } + + @GetPropertyValue(name="Turn Axis",tabId="Debug",value="turnAxis") + public Vector3d getTurnAxis() { + return turnAxis; + } + + @GetPropertyValue(name="Offset",tabId="Debug",value="offset") + public Double getOffset() { + return offset; + } + + @GetPropertyValue(name="Rotation Angle",tabId="Debug",value="rotationAngle") + public Double getRotationAngle() { + return rotationAngle; + } + + public void setTurnAngle(Double turnAngle) { + if (Double.isInfinite(turnAngle) || Double.isNaN(turnAngle)) { + return; + } + if (this.turnAngle != null && Math.abs(this.turnAngle-turnAngle) < MathTools.NEAR_ZERO) + return; + this.turnAngle = turnAngle; + firePropertyChanged("turnAngle"); + } + + public void setTurnAxis(Vector3d turnAxis) { + this.turnAxis = turnAxis; + firePropertyChanged("turnAxis"); + } + + public void setOffset(Double offset) { + if (Double.isInfinite(offset) || Double.isNaN(offset)) { + return; + } + if (this.offset != null && Math.abs(this.offset-offset) < MathTools.NEAR_ZERO) + return; + this.offset = offset; + firePropertyChanged("offset"); + } + + public void setRotationAngle(Double rotationAngle) { + if (Double.isInfinite(rotationAngle) || Double.isNaN(rotationAngle)) { + return; + } + if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO) + return; + this.rotationAngle = rotationAngle; + firePropertyChanged("rotationAngle"); + } + + public Vector3d getSizeChangeOffsetVector(Vector3d dir) { + if (rotationAngle == null) + rotationAngle = 0.0; + Quat4d q = getControlPointOrientationQuat(dir, rotationAngle); + Vector3d v = new Vector3d(0.0,0.0,offset); + Vector3d offset = new Vector3d(); + MathTools.rotate(q, v, offset); + return offset; + } + + @GetPropertyValue(name="Next",tabId="Debug",value="next") + private String getNextString() { + if (next == null) + return null; + return next.toString(); + } + + @GetPropertyValue(name="Previous",tabId="Debug",value="previous") + private String getPrevString() { + if (previous == null) + return null; + return previous.toString(); + } + + public Quat4d getControlPointOrientationQuat(double angle) { + + if (turnAxis == null) { + Vector3d dir = getPathLegDirection(Direction.NEXT); + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + return getControlPointOrientationQuat(dir, angle); + } else { + Vector3d dir = getPathLegDirection(Direction.PREVIOUS); + dir.negate(); + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + return getControlPointOrientationQuat(dir, turnAxis, angle); + } + } + + + + public static Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) { + if (dir.lengthSquared() < MathTools.NEAR_ZERO) + return MathTools.getIdentityQuat(); + + + Vector3d up = new Vector3d(0.0, 1.0, 0.0); + double a = up.angle(dir); + if (a < 0.1 || (Math.PI - a) < 0.1) { + up.set(1.0, 0.0, 0.0); + } + + + return getControlPointOrientationQuat(dir, up, angle); + } + + public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up, double angle) { + if (dir.lengthSquared() < MathTools.NEAR_ZERO) + return MathTools.getIdentityQuat(); + + final Vector3d front = new Vector3d(1.0,0.0,0.0); + + Quat4d q1 = new Quat4d(); + + + Vector3d right = new Vector3d(); + + right.cross(dir, up); + up.cross(right, dir); + right.normalize(); + up.normalize(); + + Matrix3d m = new Matrix3d(); + m.m00 = dir.x; + m.m10 = dir.y; + m.m20 = dir.z; + m.m01 = up.x; + m.m11 = up.y; + m.m21 = up.z; + m.m02 = right.x; + m.m12 = right.y; + m.m22 = right.z; + + //q1.set(m); MathTools contains more stable conversion + MathTools.getQuat(m, q1); + +// if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right); + + Quat4d q2 = new Quat4d(); + q2.set(new AxisAngle4d(front, angle)); + q1.mul(q2); + return q1; + } + + public Vector3d getDirection() { + return getDirectedControlPointDirection(); + } + + + + + + + + public void insert(PipeControlPoint previous, PipeControlPoint next) { + // inserting an offsetpoint is error, + if (isDualSub()) + throw new RuntimeException(); + // size change control point cannot be inserted this way, because it ends PipeRun + if (isSizeChange()) + throw new RuntimeException(); + PipeRun piperun = previous.getPipeRun(); + // and just to make sure that control point structure is not corrupted + if (getPipeRun() != null) { + if (piperun != getPipeRun() || piperun != next.getPipeRun()) + throw new RuntimeException(); + } else { + piperun.addChild(this); + } + + // insert new BranchControlPoint between straight's control points + PipeControlPoint previousNext = previous.getNext(); + PipeControlPoint previousPrevious = previous.getPrevious(); + + PipeControlPoint offsetCP = null; + if (isOffset()) { + offsetCP = getSubPoint().get(0); + } + if (previousNext != null && previousNext == next) { + if (previous.isDualInline()) { + throw new RuntimeException(); + } + if (next.isDualSub()) { + throw new RuntimeException(); + } + previous.setNext(this); + this.setPrevious(previous); + if (previous.isDualSub()) { + previous.getParentPoint().setNext(this); + } + this.setNext(next); + + if (offsetCP == null) { + next.setPrevious(this); + } else { + next.setPrevious(offsetCP); + offsetCP.setNext(next); + offsetCP.setPrevious(previous); + } + + if (next.isDualInline()) { + next.getSubPoint().get(0).setPrevious(this); + } + } else if (previousPrevious != null && previousPrevious == next) { + // control point were given in reverse order + if (next.isDualInline()) + throw new RuntimeException(); + if (previous.isDualSub()) + throw new RuntimeException(); + + this.setNext(previous); + if (offsetCP == null) { + previous.setNext(this); + } else { + previous.setPrevious(offsetCP); + offsetCP.setNext(previous); + offsetCP.setPrevious(next); + } + if (previous.isDualInline()) { + previous.getSubPoint().get(0).setPrevious(this); + } + this.setPrevious(next); + next.setNext(this); + if (next.isDualSub()) { + next.getParentPoint().setNext(this); + } + + } else { + throw new RuntimeException(); + } + + PipingRules.validate(piperun); + } + + + + public void insert(PipeControlPoint pcp, Direction direction) { + if (isDualSub()) + throw new RuntimeException(); + if (direction == Direction.NEXT) { + // if direction is next, user must have given OffsetPoint + if (pcp.isDualInline()) + throw new RuntimeException(); + // basic next/prev links + pcp.setNext(this); + this.setPrevious(pcp); + // and last take care of sizechange / offset points + if (pcp.isDualSub()) { + pcp.getParentPoint().setNext(this); + } + if (isDualInline()) { + getSubPoint().get(0).setPrevious(this); + } + } else { + // if direction is previous, user must have given sizechange + if (pcp.isDualSub()) + throw new RuntimeException(); + // previous direction is more complicated, since if newCP is SizeChangeControlPoint, + // we must link pcp to newCP's OffsetPoint + PipeControlPoint nocp = null; + if (isDualInline()) { + nocp = getSubPoint().get(0); + nocp.setNext(pcp); + } + if (nocp == null) { + pcp.setPrevious(this); + } else { + pcp.setPrevious(nocp); + } + this.setNext(pcp); + if (pcp.isDualInline()) { + PipeControlPoint ocp = pcp.getSubPoint().get(0); + if (nocp == null) + ocp.setPrevious(this); + else + ocp.setPrevious(nocp); + } + + } + PipingRules.validate(getPipeRun()); + } + + public Vector3d getDirectedControlPointDirection() { + assert (isDirected()); + Vector3d dir = new Vector3d(); + MathTools.rotate(getWorldOrientation(), new Vector3d(1.0, 0.0, 0.0), dir); + dir.normalize(); + return dir; + } + + public Vector3d getPathLegDirection(Direction direction) { + if (direction == Direction.NEXT) { + if (next != null) { + PipeControlPoint pcp = this; + if (pcp.isDualInline()) { + pcp = pcp.getSubPoint().get(0); + } + Vector3d v = new Vector3d(); + v.sub(next.getWorldPosition(),pcp.getWorldPosition()); + return v; + } else { + if (isVariableAngle()) + throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point"); + if (previous == null) { + if (!isDirected()) + throw new RuntimeException("Cannot calculate path leg direction for unconnected control point"); + return getDirectedControlPointDirection(); + + } else { + if (isInline()) { + Vector3d v = new Vector3d(); + v.sub(getWorldPosition(),previous.getWorldPosition()); + return v; + } else if (isDirected()) { + return getDirectedControlPointDirection(); + } else if (isEnd()) { + Vector3d v = new Vector3d(); + v.sub(getWorldPosition(),previous.getWorldPosition()); + return v; + } + throw new RuntimeException("Missing implementation"); + } + } + } else { + if (previous != null) { + PipeControlPoint pcp = this; + if (isDualSub()) + pcp = getParentPoint(); + Vector3d v = new Vector3d(); + v.sub(previous.getWorldPosition(),pcp.getWorldPosition()); + return v; + } else { + if (isVariableAngle()) + throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point"); + if (next == null) { + if (!isDirected()) + throw new RuntimeException("Cannot calculate path leg direction for unconnected control point"); + Vector3d v = getDirectedControlPointDirection(); + v.negate(); + return v; + } else { + if (isInline()) { + Vector3d v = new Vector3d(); + v.sub(getWorldPosition(),next.getWorldPosition()); + return v; + } else if (isDirected()) { + Vector3d v = getDirectedControlPointDirection(); + v.negate(); + return v; + } else if (isEnd()) { + Vector3d v = new Vector3d(); + v.sub(getWorldPosition(),next.getWorldPosition()); + return v; + } + throw new RuntimeException("Missing implementation"); + } + } + } + } + + public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) { + assert (isInline()); + + Vector3d pos = getWorldPosition(); + Vector3d dir = getPathLegDirection(Direction.NEXT); + dir.normalize(); + dir.scale(length * 0.5); + p1.set(pos); + p2.set(pos); + p1.sub(dir); + p2.add(dir); + } + + public void getControlPointEnds(Tuple3d p1, Tuple3d p2) { + Vector3d pos = getWorldPosition(); + Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS); + dir1.normalize(); + Vector3d dir2 = getPathLegDirection(Direction.NEXT); + dir2.normalize(); + if (isInline()) { + dir1.scale(length * 0.5); + dir2.scale(length * 0.5); + } else { + dir1.scale(length); + dir2.scale(length); + } + p1.set(pos); + p2.set(pos); + p1.add(dir1); + p2.add(dir2); + } + + public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) { + assert (isInline()); + + Vector3d pos = getWorldPosition(); + dir.set(getPathLegDirection(Direction.NEXT)); + dir.normalize(); + dir.scale(length * 0.5); + p1.set(pos); + p2.set(pos); + p1.sub(dir); + p2.add(dir); + } + + public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) { + assert (isInline()); + + Vector3d pos = getWorldPosition(); + center.set(pos); + dir.set(getPathLegDirection(Direction.NEXT)); + dir.normalize(); + dir.scale(length * 0.5); + p1.set(pos); + p2.set(pos); + p1.sub(dir); + p2.add(dir); + } + + public double getInlineLength() { + if (type == Type.TURN) + return length; + else if (type == Type.INLINE) + return length * 0.5; + return 0; + } + + public Vector3d getRealPosition(PositionType type) { + Vector3d pos = getWorldPosition(); + switch (type) { + case NEXT: { + Vector3d dir = getPathLegDirection(Direction.NEXT); + double length = getInlineLength(); + dir.normalize(); + dir.scale(length); + pos.add(dir); + break; + } + case PREVIOUS: { + Vector3d dir = getPathLegDirection(Direction.PREVIOUS); + double length = getInlineLength(); + dir.normalize(); + dir.scale(length); + pos.add(dir); + break; + } + case PORT: + // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection); + // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not? + break; + case SPLIT: + // do nothing + break; + + } + return pos; + } + + public void getInlineMovement(Tuple3d start, Tuple3d end) { + // FIXME : check type of neighbor components and allow movement on top of variable length components, + // find proper range for movement (pcp's position is not) + PipeControlPoint p = previous.getPrevious(); + PipeControlPoint n = next.getNext(); + start.set(p.getWorldPosition()); + end.set(n.getWorldPosition()); + } + + public PipeControlPoint findNextEnd() { + ArrayList t = new ArrayList(); + return findNextEnd( t); + } + + public PipeControlPoint findPreviousEnd() { + ArrayList t = new ArrayList(); + return findPreviousEnd(t); + } + + public PipeControlPoint findNextEnd(List nextList) { + while (true) { + PipeControlPoint pcp = null; + PipeControlPoint p = null; + if (nextList.size() == 0) + p = this; + + else + p = nextList.get(nextList.size() - 1); + + pcp = p.getNext(); + if (pcp == null) { + pcp = p; + if (nextList.size() > 0) + nextList.remove(nextList.size() - 1); +// if (DEBUG) System.out.println(" " + pcp.getResource() + " not full"); + return pcp; + //break; + } + if (pcp.isPathLegEnd()) { + //if (DEBUG) System.out.println(" " + pcp.getResource()); + return pcp; + } else { + nextList.add(pcp); + // if (DEBUG) System.out.print(" " + pcp.getResource()); + } + } + } + + public PipeControlPoint findPreviousEnd(List prevList) { + while (true) { + PipeControlPoint pcp = null; + PipeControlPoint p = null; + if (prevList.size() == 0) + p = this; + + else + p = prevList.get(prevList.size() - 1); + + pcp = p.getPrevious(); + if (pcp == null) { + pcp = p; + if (prevList.size() > 0) + prevList.remove(prevList.size() - 1); +// if (DEBUG) System.out.println(" " + pcp.getResource() + " not full"); + return pcp; + } + if (pcp.isPathLegEnd()) { +// if (DEBUG) System.out.println(" " + pcp.getResource()); + return pcp; + } else { + prevList.add(pcp); +// if (DEBUG)System.out.print(" " + pcp.getResource()); + } + } + } + + public void _remove() { + if (component == null && next == null && previous == null) + return; + if (isDualInline() || isDualSub()) { + removeDualPoint(); + return; + } + PipeRun pipeRun = getPipeRun(); + if (pipeRun == null) + return; + + PipeControlPoint additionalRemove = null; + if (!PipingRules.isEnabled()) { + setPrevious(null); + setNext(null); + } else { + + PipeControlPoint currentPrev = previous; + PipeControlPoint currentNext = next; + if (currentNext == null && currentPrev == null) { + removeComponent(); + pipeRun.remChild(this); + return; + } + if (currentNext != null && currentPrev != null) { + boolean link = true; + if (currentNext.isBranchEnd()) { + link = false; +// currentNext.setPrevious(null); +// currentNext.setNext(null); + currentNext.remove(); + currentNext = null; + setNext(null); + } + if (currentPrev.isBranchEnd()) { + link = false; +// currentPrev.setPrevious(null); +// currentPrev.setNext(null); + currentPrev.remove(); + currentPrev = null; + setPrevious(null); + } + if (link && currentPrev.isDirected() && currentNext.isDirected()) { + link = false; + } + if (currentNext == null) { + // Nothing to do + } else if (currentNext.isDualInline()) { + PipeControlPoint sccp = currentNext; + PipeControlPoint ocp = sccp.getSubPoint().get(0); + if (ocp == null) { + throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point"); + } + if (link) { + sccp.setPrevious(currentPrev); + ocp.setPrevious(currentPrev); + } else { + sccp.setPrevious(null); + ocp.setPrevious(null); + } + setNext(null); + } else if (currentNext.isDualSub()) { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point"); + } else if (currentNext.previous == this) { + if (link) { + currentNext.setPrevious(currentPrev); + } else { + currentNext.setPrevious(null); + } + setNext(null); + } else { + throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged"); + } + if (currentPrev == null) { + // Nothing to do + } else if (currentPrev.isDualInline()) { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point"); + } else if (currentPrev.isDualSub()) { + PipeControlPoint ocp = currentPrev; + PipeControlPoint sccp = ocp.getParentPoint(); + if (sccp == null) + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point"); + if (link) { + ocp.setNext(currentNext); + sccp.setNext(currentNext); + } else { + ocp.setNext(null); + sccp.setNext(null); + } + setPrevious(null); + } else if (currentPrev.next == this) { + if (link) + currentPrev.setNext(currentNext); + else + currentPrev.setNext(null); + + setPrevious(null); + } else { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged"); + } + if (link) { + if (currentNext.isVariableLength() && currentPrev.isVariableLength()) { + // we have to join them into single variable length component. + additionalRemove = currentPrev; + //currentPrev.remove(); + } + } else { + // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous. + } + } else if (next != null) { + if (next.isDualInline()) { + PipeControlPoint sccp = next; + PipeControlPoint ocp = sccp.getSubPoint().get(0); + if (ocp == null) { + throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point"); + } + sccp.setPrevious(null); + ocp.setPrevious(null); + } else if (next.isDualSub()) { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point"); + } else if (next.previous == this) { + next.setPrevious(null); + } else { + throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged"); + } + setNext(null); + } else { //(previous != null) + if(previous.isDualInline()) { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point"); + } else if (previous.isDualSub()) { + PipeControlPoint ocp = previous; + PipeControlPoint sccp = ocp.getParentPoint(); + if (sccp == null) { + throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point"); + } + ocp.setNext(null); + sccp.setNext(null); + } else if (previous.next == this) { + previous.setNext(null); + } else { + throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged"); + } + setPrevious(null); + } + if (children.size() > 0 ) { + removeSubPoints(); + } else if (parent!= null) { + removeParentPoint(); + } + + } + + removeComponent(); + pipeRun.remChild(this); + checkRemove(pipeRun); + if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0) + PipingRules.validate(pipeRun); + if (additionalRemove != null) + additionalRemove.remove(); + } + + public void remove() { + PipeControlPoint currentPrev = previous; + PipeControlPoint currentNext = next; + _remove(); + try { + if (currentNext != null) + PipingRules.requestUpdate(currentNext); + if (currentPrev != null) + PipingRules.requestUpdate(currentPrev); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void checkRemove(PipeRun pipeRun) { + Collection points = pipeRun.getControlPoints(); + if (points.size() == 0) { + pipeRun.remove(); + } else if (points.size() == 1) { + PipeControlPoint pcp = points.iterator().next(); + if (pcp.isDeletable()) + pcp._remove(); + } + } + + private void removeDualPoint() { + if (previous != null) + previous.setNext(null); + if (next != null) + next.setPrevious(null); + PipeControlPoint ocp; + PipeControlPoint sccp; + if (isDualInline()) { + sccp = this; + ocp = getSubPoint().get(0); + } else { + ocp = this; + sccp = getParentPoint(); + } + PipeRun p1 = ocp.getPipeRun(); + PipeRun p2 = sccp.getPipeRun(); + + ocp.removeComponent(); + sccp.removeComponent(); + + p1.remChild(ocp); + p2.remChild(sccp); + + ocp.setNext(null); + ocp.setPrevious(null); + sccp.setNext(null); + sccp.setPrevious(null); + + checkRemove(p1); + checkRemove(p2); + } + + private void removeSubPoints() { + for (PipeControlPoint p : children) { + // TODO : this may affect delete routine, since classification of the point changes. + p.parent = null; + p._remove(); + } + children.clear(); + } + + private void removeParentPoint() { + throw new RuntimeException("Child points cannot be removed directly"); + } + + private void removeComponent() { + if (component == null) + return; + PipelineComponent next = component.getNext(); + PipelineComponent prev = component.getNext(); + if (next != null) { + if (next.getNext() == component) + next.setNext(null); + else if (next.getPrevious() == component) + next.setPrevious(null); + } + if (prev != null) { + if (prev.getNext() == component) + prev.setNext(null); + else if (prev.getPrevious() == component) + prev.setPrevious(null); + } + PipelineComponent comp = component; + component = null; + comp.remove(); + } + + @Override + public void setOrientation(Quat4d orientation) { + if (MathTools.equals(orientation, getOrientation())) + return; + super.setOrientation(orientation); + if (getParentPoint() == null && component != null) + component._setWorldOrientation(getWorldOrientation()); + for (PipeControlPoint sub : getSubPoint()) { + sub.setWorldPosition(getWorldPosition()); + sub.setWorldOrientation(getWorldOrientation()); + } + } + + @Override + public void setPosition(Vector3d position) { + if (MathTools.equals(position, getPosition())) + return; + super.setPosition(position); + if (getParentPoint() == null && component != null) + component._setWorldPosition(getWorldPosition()); + for (PipeControlPoint sub : getSubPoint()) { + sub.setWorldPosition(getWorldPosition()); + sub.setWorldOrientation(getWorldOrientation()); + } + } + + + public void _setWorldPosition(Vector3d position) { + Vector3d localPos = getLocalPosition(position); + super.setPosition(localPos); + for (PipeControlPoint sub : getSubPoint()) { + sub.setWorldPosition(getWorldPosition()); + sub.setWorldOrientation(getWorldOrientation()); + } + } + + public void _setWorldOrientation(Quat4d orientation) { + Quat4d localOr = getLocalOrientation(orientation); + super.setOrientation(localOr); + for (PipeControlPoint sub : getSubPoint()) { + sub.setWorldPosition(getWorldPosition()); + sub.setWorldOrientation(getWorldOrientation()); + } + } + + @Override + public String toString() { + return getClass().getName() + "@" + Integer.toHexString(hashCode()); + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java new file mode 100644 index 00000000..d7c53d4a --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java @@ -0,0 +1,1651 @@ +package org.simantics.plant3d.scenegraph.controlpoint; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.vecmath.Point3d; +import javax.vecmath.Quat4d; +import javax.vecmath.Vector3d; + +import org.simantics.g3d.math.MathTools; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipeRun; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.TurnComponent; +import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction; +import org.simantics.plant3d.utils.ComponentUtils; +import org.simantics.utils.ui.ErrorLogger; + +public class PipingRules { + private static final boolean DEBUG = false; + private static final boolean DUMMY = false; + + private static final double MIN_TURN_ANGLE = 0.01; + + private static final int REMOVE_NONE = 0; + private static final int REMOVE_START = 1; + private static final int REMOVE_END = 2; + private static final int REMOVE_BOTH = 3; + +// private P3DRootNode root; + +// public PipingRules(P3DRootNode root) { +// this.root = root; +// } + + private enum PathLegUpdateType { + NONE, PREV, NEXT, PREV_S, NEXT_S + }; + + private static boolean enabled = true; + private static boolean updating = false; + private static boolean allowInsertRemove = true; + private static boolean triedIR = false; + + + private static List updates = new ArrayList(); + + private static Object mutex = new Object(); + + public static void requestUpdate(PipeControlPoint pcp) { + if (DEBUG) System.out.println("PipingRules request " + pcp); + synchronized (mutex) { + if (!updates.contains(pcp)) + updates.add(pcp); + } + } + + public static synchronized boolean update() throws Exception { + if (updates.size() == 0) + return false; + List temp = new ArrayList(updates.size()); + synchronized(mutex) { + temp.addAll(updates); + updates.clear(); + } + + for (PipeControlPoint pcp : temp) + positionUpdate(pcp); + return true; + } + + public static boolean positionUpdate(PipeControlPoint pcp) throws Exception { + + return positionUpdate(pcp, true); + } + + public static boolean positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception { + if (updating || !enabled) + return true; + if (pcp.getPipeRun() == null) + return false; + try { + if (DEBUG) System.out.println("PipingRules " + pcp); + updating = true; + allowInsertRemove = allowIR; + triedIR = false; + validate(pcp.getPipeRun()); + if (pcp.isPathLegEnd()) { + updatePathLegEndControlPoint(pcp); // FXIME: Rules won't work properly, if they are not run twice. + updatePathLegEndControlPoint(pcp); + } else { + updateInlineControlPoint(pcp); + updateInlineControlPoint(pcp); + } + validate(pcp.getPipeRun()); + if (!allowInsertRemove) + return !triedIR; + return true; + } finally { + updating = false; +// System.out.println("PipingRules done " + pcp); + } + } + + public static void setEnabled(boolean enabled) { + PipingRules.enabled = enabled; + if(!enabled) + updates.clear(); + } + + public static boolean isEnabled() { + return enabled; + } + +// private void commit() { +// root.getNodeMap().commit(); +// } + + public static class ExpandIterInfo { + // these two are turn control points + private PipeControlPoint start; + private PipeControlPoint end; + private int type; + + public ExpandIterInfo() { + + } + + public ExpandIterInfo(PipeControlPoint tcp, int type) { + if (type == REMOVE_START) + start = tcp; + else + end = tcp; + this.type = type; + } + + public ExpandIterInfo(PipeControlPoint start, PipeControlPoint end) { + this.start = start; + this.end = end; + this.type = REMOVE_BOTH; + } + + public PipeControlPoint getEnd() { + return end; + } + + public void setEnd(PipeControlPoint end) { + this.end = end; + } + + public PipeControlPoint getStart() { + return start; + } + + public void setStart(PipeControlPoint start) { + this.start = start; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + } + + private static void updatePathLegEndControlPoint(PipeControlPoint pcp) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updatePathLegEndControlPoint() " + pcp); + if (pcp.getNext() != null) { + updatePathLegNext(pcp, pcp, PathLegUpdateType.NEXT_S); + } + if (pcp.getPrevious() != null) { + updatePathLegPrev(pcp, pcp, PathLegUpdateType.PREV_S); + } + + } + + private static void updateInlineControlPoint(PipeControlPoint pcp) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateInlineControlPoint() " + pcp); + PipeControlPoint start = pcp.findPreviousEnd(); + updatePathLegNext(start, pcp, PathLegUpdateType.NONE); + } + + private static PipeControlPoint insertElbow(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos) throws Exception{ + if (DEBUG) + System.out.println("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos); + if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) { + + } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) { + pcp1 = pcp1.getSubPoint().get(0); + } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) { + PipeControlPoint t = pcp1; + pcp1 = pcp2; + pcp2 = t; + } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) { + PipeControlPoint t = pcp1; + pcp1 = pcp2.getSubPoint().get(0); + pcp2 = t; + } else { + throw new RuntimeException(); + } + TurnComponent elbow = ComponentUtils.createTurn((P3DRootNode)pcp1.getRootNode()); + PipeControlPoint pcp = elbow.getControlPoint(); + if (pcp1.isDualInline()) + pcp1 = pcp1.getSubPoint().get(0); + String name = pcp1.getPipeRun().getUniqueName("Elbow"); + elbow.setName(name); + pcp1.getPipeRun().addChild(elbow); + + pcp.insert(pcp1, pcp2); + + pcp.setWorldPosition(pos); + validate(pcp.getPipeRun()); + return pcp; + } + + private static PipeControlPoint insertStraight(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos, double length) throws Exception { + if (DEBUG) + System.out.println("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos); + if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) { + + } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) { + pcp1 = pcp1.getSubPoint().get(0); + } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) { + PipeControlPoint t = pcp1; + pcp1 = pcp2; + pcp2 = t; + } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) { + PipeControlPoint t = pcp1; + pcp1 = pcp2.getSubPoint().get(0); + pcp2 = t; + } else { + throw new RuntimeException(); + } + InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp1.getRootNode()); + PipeControlPoint scp = component.getControlPoint(); + if (pcp1.isDualInline()) + pcp1 = pcp1.getSubPoint().get(0); + String name = pcp1.getPipeRun().getUniqueName("Pipe"); + component.setName(name); + pcp1.getPipeRun().addChild(component); + + scp.insert(pcp1, pcp2); + + scp.setWorldPosition(pos); + scp.setLength(length); + validate(scp.getPipeRun()); + return scp; + } + + private static PipeControlPoint insertStraight(PipeControlPoint pcp, Direction direction , Vector3d pos, double length) throws Exception { + if (DEBUG) + System.out.println("PipingRules.insertStraight() " + pcp + " " + direction + " " + pos); + + InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode()); + PipeControlPoint scp = component.getControlPoint(); + if (pcp.isDualInline() && direction == Direction.NEXT) + pcp = pcp.getSubPoint().get(0); + String name = pcp.getPipeRun().getUniqueName("Pipe"); + component.setName(name); + pcp.getPipeRun().addChild(component); + + scp.insert(pcp,direction); + + scp.setWorldPosition(pos); + scp.setLength(length); + validate(scp.getPipeRun()); + return scp; + } + + private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { + ArrayList list = new ArrayList(); + PipeControlPoint end = start.findNextEnd(list); + // this is for inline cp that is also path leg end + if (start.equals(updated)) + lengthChange = PathLegUpdateType.NEXT; + else if (end.equals(updated)) + lengthChange = PathLegUpdateType.PREV; + updatePathLegNext(start, list, end, updated, lengthChange); + } + + private static void updatePathLegNext(PipeControlPoint start, ArrayList list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { + updatePathLeg(start, list, end, false, 0, new ArrayList(), updated, lengthChange); + } + + private static class UpdateStruct2 { + public PipeControlPoint start; + public Vector3d startPoint; + public ArrayList list; + public PipeControlPoint end; + public Vector3d endPoint; + public Vector3d dir; + public Vector3d offset; + public boolean hasOffsets; + public int iter; + public boolean reversed; + public ArrayList toRemove; + public PipeControlPoint updated; + + public UpdateStruct2(PipeControlPoint start, Vector3d startPoint, ArrayList list, PipeControlPoint end, Vector3d endPoint, Vector3d dir, Vector3d offset, boolean hasOffsets, int iter, boolean reversed, ArrayList toRemove, PipeControlPoint updated) { + if (start == null || end == null) + throw new NullPointerException(); + this.start = start; + this.startPoint = startPoint; + this.list = list; + this.end = end; + this.endPoint = endPoint; + this.dir = dir; + this.offset = offset; + this.hasOffsets = hasOffsets; + this.iter = iter; + this.reversed = reversed; + this.toRemove = toRemove; + this.updated = updated; + + if (!MathTools.isValid(startPoint) || + !MathTools.isValid(endPoint) || + !MathTools.isValid(dir)) { + throw new RuntimeException(); + } + } + + public String toString() { + return start + " " + end+ " " + dir + " " + hasOffsets + " " + offset + " " + iter + " " + toRemove.size(); + } + + } + + private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, ArrayList list, Vector3d dir, Vector3d offset) { + boolean hasOffsets = false; + dir.set(startPoint); + dir.sub(endPoint); + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + offset.set(0.0, 0.0, 0.0); + for (PipeControlPoint icp : list) { + if (icp.isOffset()) { + hasOffsets = true; + offset.add(icp.getSizeChangeOffsetVector(dir)); + } else if (icp.isDualSub()) + ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); + } + return hasOffsets; + } + + /** + * @param start + * starting point of the pipe run + * @param list + * list of inline control points in the pipe run + * @param end + * ending point of the pipe run + * @param reversed + * boolean flag indicating wether start or end control point was + * modified (if true then end point was modified) + * @throws TransactionException + */ + private static void updatePathLeg(PipeControlPoint start, ArrayList list, PipeControlPoint end, boolean reversed, int iter, ArrayList toRemove, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { + if (start == end) + return; + // FIXME: direction is calculated wrong way! + boolean hasOffsets = false; + Vector3d offset = new Vector3d(); + Vector3d startPoint = start.getWorldPosition(); + Vector3d endPoint = end.getWorldPosition(); + Vector3d dir = new Vector3d(); + hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset); + updatePathLeg(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, reversed, toRemove, updated), lengthChange); + + } + + private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + int directed = 0; + if (u.start.isDirected()) + directed++; + if (u.end.isDirected()) + directed++; + switch (directed) { + case 0: + updateFreePathLeg(u, lengthChange); + break; + case 1: + updateDirectedPathLeg(u, lengthChange); + break; + case 2: + updateDualDirectedPathLeg(u, lengthChange); + break; + } + + } + + private static void updateFreePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange); + checkExpandPathLeg(u, lengthChange); + if (u.start.isInline() || u.end.isInline()) + processPathLeg(u, true, false); + } + + private static void updateInlineControlPoints(UpdateStruct2 u, boolean checkSizes) throws Exception{ + if (DEBUG) + System.out.println("PipingRules.updateInlineControlPoints() " + u); + + if (!u.hasOffsets) { + // FIXME : cache positions + if (!checkSizes) { + Vector3d start = new Vector3d(u.startPoint); + Vector3d end = new Vector3d(u.endPoint); + // create offsets. + MathTools.mad(start, u.dir, 0.1); + MathTools.mad(end, u.dir, -0.1); + for (PipeControlPoint icp : u.list) { + updateInlineControlPoint(icp, start, end, u.dir); + } + return; + } + + ArrayList pathLegPoints = new ArrayList(); + pathLegPoints.add(u.start); + for (PipeControlPoint icp : u.list) { + // updateInlineControlPoint(icp, u.startPoint, + // u.endPoint,u.dir); + updateBranchControlPointBranches(icp); + pathLegPoints.add(icp); + } + pathLegPoints.add(u.end); + + // TODO : values can be cached in the loop + for (int i = 1; i < pathLegPoints.size(); i++) { + PipeControlPoint icp = pathLegPoints.get(i); + + PipeControlPoint prev; + Vector3d prevPos; + prev = pathLegPoints.get(i - 1); + prevPos = prev.getWorldPosition(); + Vector3d currentPos = icp.getWorldPosition(); + + if (icp.isVariableLength()) { + if (i != pathLegPoints.size() - 1) { + PipeControlPoint next; + Vector3d nextPos; + next = pathLegPoints.get(i + 1); + nextPos = next.getWorldPosition(); + Vector3d dir = new Vector3d(nextPos); + dir.sub(prevPos); + double l = dir.lengthSquared(); // distance between + // control points + // (square) + double l2prev = prev.getInlineLength(); // distance + // taken + // by + // components + double l2next = next.getInlineLength(); + double l2 = l2prev + l2next; + double l2s = MathTools.square(l2); + if (l2s < l) { // check if there is enough space for + // variable length component. + // components fit + dir.normalize(); + double length = Math.sqrt(l) - l2; // true length of + // the variable + // length + // component + dir.scale(length * 0.5 + l2prev); // calculate + // center + // position of + // the component + dir.add(prevPos); + icp.setWorldPosition(dir); + icp.setLength(length); + } else { + // components leave no space to the component and it + // must be removed + if (icp.isDeletable()) + icp._remove(); + } + + } else { + // this is variable length component at the end of the + // piperun. + // the problem is that we want to keep unconnected end + // of the component in the same + // place, but center of the component must be moved. + double currentLength = icp.getLength(); + + Vector3d dir = new Vector3d(); + dir.sub(currentPos, prevPos); + + if (currentLength < MathTools.NEAR_ZERO) { + currentLength = (dir.length() - prev.getInlineLength()) * 2.0; + } + + if (dir.lengthSquared() > MathTools.NEAR_ZERO) + dir.normalize(); + Point3d endPos = new Point3d(dir); + endPos.scale(currentLength * 0.5); + endPos.add(currentPos); // this is the free end of the + // component + + double offset = prev.getInlineLength(); + Point3d beginPos = new Point3d(dir); + beginPos.scale(offset); + beginPos.add(prevPos); // this is the connected end of + // the component + + double l = beginPos.distance(endPos); + + if (Double.isNaN(l)) + System.out.println(); + + dir.scale(l * 0.5); + beginPos.add(dir); // center position + + if (DEBUG) + System.out.println("PipingRules.updateInlineControlPoints() setting variable length to " + l); + icp.setLength(l); + + icp.setWorldPosition(new Vector3d(beginPos)); + } + + + } else if (!prev.isVariableLength()) { + // If this and previous control point are not variable + // length pcps, we'll have to check if there is no empty + // space between them. + // I there is, we'll have to create new variable length + // component between them. + Vector3d dir = new Vector3d(currentPos); + dir.sub(prevPos); + double l = dir.lengthSquared(); + double l2prev = prev.getInlineLength(); + double l2next = icp.getInlineLength(); + double l2 = l2prev + l2next; + double l2s = l2 * l2; + if (l > l2s) { + if (allowInsertRemove) { + dir.normalize(); + double length = Math.sqrt(l) - l2; // true length of the + // variable length + // component + dir.scale(length * 0.5 + l2prev); // calculate center + // position of the + // component + dir.add(prevPos); + PipeControlPoint scp = insertStraight(prev, icp, dir, length); + } else { + triedIR = true; + } + } + } + } + } else { + u.endPoint.sub(u.offset); + // FIXME : straights + for (PipeControlPoint icp : u.list) { + updateInlineControlPoint(icp, u.startPoint, u.endPoint, u.dir); + updateBranchControlPointBranches(icp); + + if (icp.isOffset()) { + // TODO : offset vector is already calculated and should be + // cached + u.offset = icp.getSizeChangeOffsetVector(u.dir); + updateOffsetPoint(icp, u.offset); + u.startPoint.add(u.offset); + u.endPoint.add(u.offset); + } + } + } + } + + private static void ppNoOffset(UpdateStruct2 u) throws Exception { + if (DEBUG) + System.out.println("PipingRules.ppNoOffset() " + u); + Vector3d offset = new Vector3d(); + if (u.hasOffsets) { + u.dir.normalize(); + for (PipeControlPoint icp : u.list) { + if (icp.isOffset()) { + offset.add(icp.getSizeChangeOffsetVector(u.dir)); + } else if (icp.isDualSub()) + ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); + } + } + u.offset = offset; + checkExpandPathLeg(u, PathLegUpdateType.NONE); + } + + private static void ppNoDir(PipeControlPoint start, Vector3d startPoint, ArrayList list, PipeControlPoint end, Vector3d endPoint, boolean hasOffsets, int iter, boolean reversed, ArrayList toRemove, PipeControlPoint updated) throws Exception { + if (DEBUG) + System.out.println("PipingRules.ppNoDir() " + start + " " + end + " " + iter + " " + toRemove.size()); + // FIXME : extra loop (dir should be calculated here) + Vector3d dir = new Vector3d(); + Vector3d offset = new Vector3d(); + hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset); + ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated)); + } + + private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + checkExpandPathLeg(u, lengthChange, false); + } + + private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean forceUpdate) throws Exception { + if (DEBUG) + System.out.println("PipingRules.checkExpandPathLeg() " + u + " " + lengthChange); + if (lengthChange != PathLegUpdateType.NONE) { + // FIXME : turns cannot be checked before inline cps are updated, + // since their position affects calculation of turns + processPathLeg(u, forceUpdate, false); + int type = checkTurns(u, lengthChange); + if (type == REMOVE_NONE) { + processPathLeg(u, forceUpdate, true); + } else { + expandPathLeg(u, type); + } + } else { + processPathLeg(u, forceUpdate, true); + } + } + + private static void updateDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange); + PipeControlPoint dcp; + PipeControlPoint other; + boolean canMoveOther = false; + boolean dcpStart = false; + boolean inlineEnd = false; + Vector3d position; + if (u.start.isDirected()) { + dcp = u.start; + other = u.end; + position = u.startPoint; + dcpStart = true; + if (!u.reversed) + canMoveOther = true; + inlineEnd = u.end.isInline(); + + } else { + dcp = u.end; + other = u.start; + position = u.endPoint; + if (u.reversed) + canMoveOther = true; + inlineEnd = u.start.isInline(); + } + + Vector3d directedDirection = dcp.getDirection(); + Point3d directedEndPoint = new Point3d(u.endPoint); + if (u.hasOffsets) + directedEndPoint.add(u.offset); + + double mu[] = new double[2]; + + Vector3d closest; + Vector3d t = new Vector3d(); + + if (dcpStart) { + closest = MathTools.closestPointOnStraight(directedEndPoint, u.startPoint, directedDirection, mu); + t.sub(closest, directedEndPoint); + } else { + closest = MathTools.closestPointOnStraight(u.startPoint, directedEndPoint, directedDirection, mu); + t.sub(closest, u.startPoint); + } + + double distance = t.lengthSquared(); + boolean aligned = (distance < 0.002); + if (aligned) { + checkExpandPathLeg(u, lengthChange, inlineEnd); + + } else { + if (u.iter > 0) { + backIter(u); + } else { + PipeControlPoint nextToMoved; + + if (u.list.size() > 0) + if (dcpStart) + nextToMoved = u.list.get(0); + else + nextToMoved = u.list.get(u.list.size() - 1); + else if (dcpStart) + nextToMoved = u.end; + else + nextToMoved = u.start; + if (other.isVariableAngle()) { + + // TODO calculate needed space from next run end. + if (mu[0] < 1.0) { + if (dcpStart) { + closest.set(u.startPoint); + } else { + closest.set(u.endPoint); + } + closest.add(directedDirection); + } + + if (canMoveOther) { + if (DEBUG) + System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest); + other.setWorldPosition(closest); + if (dcpStart) { + ppNoOffset(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(closest), directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated)); + if (u.end.getNext() != null) + updatePathLegNext(u.end, u.updated, PathLegUpdateType.NEXT); + } else { + ppNoOffset(new UpdateStruct2(u.start, new Vector3d(closest), u.list, u.end, u.endPoint, directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated)); + if (u.start.getPrevious() != null) + updatePathLegPrev(u.start, u.updated, PathLegUpdateType.PREV); + } + } else { + // TODO : calculate needed space from next run end. + if (allowInsertRemove) + insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); + + else + triedIR = true; + } + } else if (other.isNonDirected() && other.getParentPoint() != null) { + // FIXME : this code was for updating branches + Vector3d bintersect = new Vector3d(); + PipeControlPoint bcp = other.getParentPoint(); + if (bcp != null && canMoveOther) { + Point3d bstart = new Point3d(); + Point3d bend = new Point3d(); + Vector3d bdir = new Vector3d(); + bcp.getInlineControlPointEnds(bstart, bend, bdir); + Vector3d nintersect = new Vector3d(); + + MathTools.intersectStraightStraight(position, directedDirection, bend, bdir, nintersect, bintersect, mu); + Vector3d dist = new Vector3d(nintersect); + dist.sub(bintersect); + canMoveOther = mu[1] > 0.0 && mu[1] < 1.0 && dist.lengthSquared() < 0.01; + } else { + // TODO : endControlPoints are undirected: calculcate + // correct position for it + throw new UnsupportedOperationException("not implemented"); + } + if (canMoveOther) { + if (DEBUG) + System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect); + // is required branch position is in possible range + bcp.setWorldPosition(bintersect); + if (dcpStart) { + checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(bintersect), directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange); + } else { + checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(bintersect), u.list, u.end, u.endPoint, directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange); + } + } else { + // branch cannot be moved into right position, new turn + // / elbow must be inserted + if (allowInsertRemove) + insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); + else + triedIR = true; + } + + } else { // assume that control point cannot be moved, but can + // be rotated + if (allowInsertRemove) + insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection); + else + triedIR = true; + } + } + } + + + } + + private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange); + + PipeControlPoint dcp1 = u.start; + PipeControlPoint dcp2 = u.end; + Point3d position1 = new Point3d(u.startPoint); + Point3d position2 = new Point3d(u.endPoint); + Point3d position1offset = new Point3d(position1); + position1offset.sub(u.offset); + Point3d position2offset = new Point3d(position2); + position2offset.add(u.offset); + Vector3d dir1 = dcp1.getDirection(); + Vector3d dir2 = dcp2.getDirection(); + Vector3d p1 = MathTools.closestPointOnStraight(position1offset, position2, dir2); + Vector3d p2 = MathTools.closestPointOnStraight(position2offset, position1, dir1); + double d1 = position1.distance(new Point3d(p1)); + double d2 = position2.distance(new Point3d(p2)); + + boolean aligned = (d1 < 0.01 && d2 < 0.01); + if (aligned) { + processPathLeg(u); + } else { + if (u.iter > 0) { + backIter(u); + } else if (allowInsertRemove){ + PipeControlPoint dcp; + PipeControlPoint next; + if (!u.reversed) { + dcp = dcp1; + if (u.list.size() > 0) + next = u.list.get(0); + else + next = dcp2; + } else { + dcp = dcp2; + if (u.list.size() > 0) + next = u.list.get(u.list.size() - 1); + else + next = dcp1; + } + + p1 = dcp.getWorldPosition(); + // FIXME: calculate position of the elbows properly. + if (!u.reversed) + p1.add(dir1); + else + p1.add(dir2); + + if (!u.reversed) + p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2, dir2); + else + p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1, dir1); + + + PipeControlPoint tcp1 = insertElbow(dcp, next, p1); + PipeControlPoint tcp2 = insertElbow(tcp1, next, p2); + + if (DEBUG) + System.out.println("PipingRules.updateDualDirectedPipeRun() created two turns " + tcp1 + " " + tcp2); + + if (!u.reversed) { + Vector3d dd = new Vector3d(p2); + dd.sub(p1); + dir2.negate(); + updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE); + updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE); + if (!u.reversed) + updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE); + else + updatePathLegPrev(tcp2, u.updated, PathLegUpdateType.NONE); + } else { + Vector3d dd = new Vector3d(p1); + dd.sub(p2); + dir2.negate(); + updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE); + updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE); + if (!u.reversed) + updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE); + else + updatePathLegPrev(u.start, u.updated, PathLegUpdateType.NONE); + } + } else { + triedIR = true; + } + } + + } + + private static void insertElbowUpdate(UpdateStruct2 u, PipeControlPoint dcp, PipeControlPoint next, boolean dcpStart, Vector3d position, Vector3d directedDirection) throws Exception{ + + Vector3d closest = new Vector3d(position); + closest.add(directedDirection); + PipeControlPoint tcp = null; + if (dcpStart) + tcp = insertElbow(dcp, next, new Vector3d(closest)); + else + tcp = insertElbow(next, dcp, new Vector3d(closest)); + + if (DEBUG) + System.out.println("PipingRules.updateDirectedPipeRun() inserted " + tcp); + + if (dcpStart) { + // update pipe run from new turn to other end + ppNoDir(tcp, new Vector3d(closest), u.list, u.end, u.endPoint, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated); + // update pipe run from directed to new turn + processPathLeg(new UpdateStruct2(u.start, u.startPoint, new ArrayList(), tcp, new Vector3d(closest), directedDirection, new Vector3d(), false, 0, false, new ArrayList(), u.updated)); + } else { + // update pipe run from other end to new turn + ppNoDir(u.start, u.startPoint, u.list, tcp, new Vector3d(closest), u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated); + // update pipe run from new turn to directed + processPathLeg(new UpdateStruct2(tcp, new Vector3d(closest), new ArrayList(), u.end, u.endPoint, directedDirection, new Vector3d(), false, 0, false, new ArrayList(), u.updated)); + } + } + + /** + * Checks if turns can be removed (turn angle near zero) + */ + private static int checkTurns(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception { + if (DEBUG) + System.out.println("PipingRules.checkTurns() " + u.start + " " + u.end); + boolean startRemoved = false; + boolean endRemoved = false; + if (u.start.isVariableAngle()) { + // this won't work properly if inline control points are not updated + PipeControlPoint startPrev = u.start.getPrevious(); + if (startPrev != null) { + double a; + if (!u.hasOffsets) { + a = updateTurnControlPointTurn(u.start, startPrev, u.end); + } else { + Vector3d ep = new Vector3d(u.endPoint); + ep.add(u.offset); + a = updateTurnControlPointTurn(u.start, u.startPoint, startPrev.getPosition(), ep); + + } + if (a < MIN_TURN_ANGLE && u.start.isDeletable()) + startRemoved = true; + else if (lengthChange == PathLegUpdateType.PREV || lengthChange == PathLegUpdateType.PREV_S) { + PathLegUpdateType type; + if (lengthChange == PathLegUpdateType.PREV_S) + type = PathLegUpdateType.PREV; + else + type = PathLegUpdateType.NONE; + updatePathLegPrev(u.start, u.start, type); + } + } + } + if (u.end.isVariableAngle()) { + + PipeControlPoint endNext = u.end.getNext(); + if (endNext != null) { + double a; + if (!u.hasOffsets) { + a = updateTurnControlPointTurn(u.end, u.start, endNext); + } else { + Vector3d sp = new Vector3d(u.startPoint); + sp.sub(u.offset); + a = updateTurnControlPointTurn(u.end, u.endPoint, sp, endNext.getPosition()); + } + if (a < MIN_TURN_ANGLE && u.end.isDeletable()) + endRemoved = true; + else if (lengthChange == PathLegUpdateType.NEXT || lengthChange == PathLegUpdateType.NEXT_S) { + PathLegUpdateType type; + if (lengthChange == PathLegUpdateType.NEXT_S) + type = PathLegUpdateType.NEXT; + else + type = PathLegUpdateType.NONE; + updatePathLegNext(u.end, u.end, type); + } + } + } + if (DEBUG) + System.out.println("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved); + if (!startRemoved && !endRemoved) + return REMOVE_NONE; + if (startRemoved && endRemoved) + return REMOVE_BOTH; + if (startRemoved) + return REMOVE_START; + return REMOVE_END; + } + + /** + * Expands piperun search over turns that are going to be removed + * + */ + private static void expandPathLeg(UpdateStruct2 u, int type) throws Exception { + if (DEBUG) + System.out.println("PipingRules.expandPipeline " + u.start + " " + u.end); + ArrayList newList = new ArrayList(); + switch (type) { + case REMOVE_NONE: + throw new RuntimeException("Error in piping rules"); + case REMOVE_START: + u.toRemove.add(new ExpandIterInfo(u.start, REMOVE_START)); + u.start = u.start.findPreviousEnd(); + u.startPoint = u.start.getPosition(); + u.start.findNextEnd(newList); + newList.addAll(u.list); + u.list = newList; + break; + case REMOVE_END: + u.toRemove.add(new ExpandIterInfo(u.end, REMOVE_END)); + u.end = u.end.findNextEnd(newList); + u.endPoint = u.end.getPosition(); + u.list.addAll(newList); + break; + case REMOVE_BOTH: + u.toRemove.add(new ExpandIterInfo(u.start, u.end)); + u.start = u.start.findPreviousEnd(); + u.startPoint = u.start.getPosition(); + u.start.findNextEnd(newList); + newList.addAll(u.list); + u.list = newList; + newList = new ArrayList(); + u.end = u.end.findNextEnd(newList); + u.endPoint = u.end.getPosition(); + u.list.addAll(newList); + break; + default: + throw new RuntimeException("Error in piping rules"); + + } + u.offset = new Vector3d(); + if (u.hasOffsets) { + u.dir.normalize(); + for (PipeControlPoint icp : u.list) { + if (icp.isOffset()) { + u.offset.add(icp.getSizeChangeOffsetVector(u.dir)); + } else if (icp.isDualSub()) + ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); + } + } + if (DEBUG) + System.out.println("PipingRules.expandPipeline expanded " + u.start + " " + u.end); + u.iter++; + updatePathLeg(u, PathLegUpdateType.NONE); + } + + /** + * reverts one iteration of turn removing back) + */ + private static void backIter(UpdateStruct2 u) throws Exception { + + if (DEBUG) + System.out.println("PipingRules.backIter" + u.start + " " + u.end); + if (u.iter == 0) + throw new RuntimeException("Error in piping rules"); + ExpandIterInfo info = u.toRemove.get(u.toRemove.size() - 1); + u.toRemove.remove(u.toRemove.size() - 1); + if (info.getType() == REMOVE_START || info.getType() == REMOVE_BOTH) { + while (u.list.size() > 0) { + PipeControlPoint icp = u.list.get(0); + if (icp.getPrevious().equals(info.getStart())) + break; + u.list.remove(icp); + } + u.start = info.getStart(); + } + if (info.getType() == REMOVE_END || info.getType() == REMOVE_BOTH) { + while (u.list.size() > 0) { + PipeControlPoint icp = u.list.get(u.list.size() - 1); + if (icp.getNext().equals(info.getEnd())) + break; + u.list.remove(icp); + } + u.end = info.getEnd(); + } + u.offset = new Vector3d(); + if (u.hasOffsets) { + u.dir.normalize(); + for (PipeControlPoint icp : u.list) { + if (icp.isOffset()) { + u.offset.add(icp.getSizeChangeOffsetVector(u.dir)); + } else if (icp.isDualSub()) + ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); + } + } + processPathLeg(u); + + } + + /** + * Processes pipe run (removes necessary turns and updates run ends) + */ + // private static void processPathLeg(PipeControlPoint start, Point3d + // startPoint,ArrayList list, PipeControlPoint + // end,Point3d endPoint, Vector3d dir,Vector3d offset, boolean + // hasOffsets,int iter, boolean reversed, ArrayList + // toRemove) throws TransactionException { + + private static void processPathLeg(UpdateStruct2 u) throws Exception { + if (DEBUG) + System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end); + processPathLeg(u, true, true); + } + + private static void processPathLeg(UpdateStruct2 u, boolean updateEnds, boolean updateInline) throws Exception { + if (DEBUG) + System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end); + + if (u.toRemove.size() > 0) { + for (ExpandIterInfo info : u.toRemove) { + if (info.getStart() != null) { + info.getStart()._remove(); + } + if (info.getEnd() != null) { + info.getEnd()._remove(); + } + } + // ControlPointTools.removeControlPoint may remove mo0re than one + // CP; + // we must populate inline CP list again. + u.list.clear(); + u.start.findNextEnd( u.list); + } + // FIXME : inline CPs are update twice because their positions must be + // updated before and after ends. + updateInlineControlPoints(u, false); + + if (updateEnds) { + if (u.start.isTurn()) { + updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext()); +// updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE); + } else if (u.start.isEnd()) { + updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint); + } else if (u.start.isInline()) { + updateControlPointOrientation(u.start); + } + if (u.end.isTurn()) { + updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext()); +// updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE); + } else if (u.end.isEnd()) { + updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint); + } else if (u.end.isInline()) { + updateControlPointOrientation(u.end); + } + + } else { + if (u.start.isEnd()) { + updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint); + } + if (u.end.isEnd()) { + updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint); + } + } + if (updateInline) + updateInlineControlPoints(u, true); + + } + + /** + * Processes pipe run and recalculates offset + */ + // private static void processPathLeg(PipeControlPoint start, Point3d + // startPoint,ArrayList list, PipeControlPoint + // end,Point3d endPoint, Vector3d dir, boolean hasOffsets,int iter, boolean + // reversed, ArrayList toRemove) throws TransactionException + // { + private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception { + if (DEBUG) + System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end); + Vector3d offset = new Vector3d(); + if (u.hasOffsets) { + u.dir.normalize(); + for (PipeControlPoint icp : u.list) { + if (icp.isOffset()) { + offset.add(icp.getSizeChangeOffsetVector(u.dir)); + } else if (icp.isDualSub()) { + ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!")); + } + } + } + processPathLeg(u); + } + + private static void updateOffsetPoint(PipeControlPoint sccp, Vector3d offset) { + Vector3d world = sccp.getWorldPosition(); + world.add(offset); + PipeControlPoint ocp = sccp.getSubPoint().iterator().next(); + ocp.setWorldPosition(world); + } + + private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { + ArrayList list = new ArrayList(); + PipeControlPoint end = start.findPreviousEnd(list); + updatePathLegPrev(start, list, end, updated, lengthChange); + } + + private static void updatePathLegPrev(PipeControlPoint start, ArrayList list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception { + // reverses the list + ArrayList nextList = new ArrayList(); + for (PipeControlPoint icp : list) { + if (icp.isDualSub()) { + nextList.add(0, icp.getParentPoint()); + } else { + nextList.add(0, icp); + } + + } + updatePathLeg(end, nextList, start, true, 0, new ArrayList(), updated, lengthChange); + + } + + /** + * Updates InlineControlPoints position when straight pipe's end(s) have + * been changed) + * + * @param pipeline + * @param icp + * @param nextPoint + * @param prevPoint + */ + private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d nextPoint, Vector3d prevPoint, Vector3d dir) { + if (DEBUG) + System.out.println("PipingRules.updateInlineControlPoint() " + icp); + + Vector3d inlinePoint = icp.getWorldPosition(); + if (DEBUG) + System.out.print("InlineControlPoint update " + icp + " " + inlinePoint + " " + prevPoint + " " + nextPoint); + Vector3d newInlinePoint = null; + boolean branchUpdate = false; + PipeControlPoint becp = null; + for (PipeControlPoint pcp : icp.getSubPoint()) + if (pcp.isNonDirected()) { + branchUpdate = true; + becp = pcp; + break; + } + + if (DUMMY || !branchUpdate) { + newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), new Vector3d(nextPoint), new Vector3d(prevPoint)); + } else { + + // FIXME : can only handle one branch + PipeControlPoint p = null; + if (becp.getNext() != null) { + p = becp.findNextEnd(); + } else if (becp.getPrevious() != null) { + p = becp.findPreviousEnd(); + } + if (p == null) { + newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), new Vector3d(nextPoint), new Vector3d(prevPoint)); + } else { + Vector3d branchLegEnd = p.getWorldPosition(); + Vector3d dir2 = new Vector3d(inlinePoint); + dir2.sub(branchLegEnd); + Vector3d dir1 = new Vector3d(nextPoint); + dir1.sub(prevPoint); + newInlinePoint = new Vector3d(); + double mu[] = new double[2]; + MathTools.intersectStraightStraight(new Vector3d(prevPoint), dir1, new Vector3d(branchLegEnd), dir2, newInlinePoint, new Vector3d(), mu); + if (DEBUG) + System.out.println(mu[0]); + // FIXME : reserve space + if (mu[0] < 0.0) { + newInlinePoint = new Vector3d(prevPoint); + } else if (mu[0] > 1.0) { + newInlinePoint = new Vector3d(nextPoint); + } + } + } + if (DEBUG) + System.out.println(" " + newInlinePoint); + + icp.setWorldPosition(newInlinePoint); + updateControlPointOrientation(icp); + } + + /** + * Updates InlineControlPoints position when straight pipe's end(s) have + * been changed) + * + * @param pipeline + * @param icp + * @param nextPoint + * @param prevPoint + */ + private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d start, Vector3d end) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp); + // PipeControlPoint next = ecp.getNext(); + // PipeControlPoint prev = ecp.getPrevious(); + // if (next != null) { + // end = G3DTools.getPoint(next.getLocalPosition()); + // start = G3DTools.getPoint(ecp.getLocalPosition()); + // } else if (prev != null) { + // end = G3DTools.getPoint(ecp.getLocalPosition()); + // start = G3DTools.getPoint(prev.getLocalPosition()); + // } else { + // // TODO : warning? + // return; + // } + // Vector3d dir = new Vector3d (end); + // dir.sub(start); + // dir.normalize(); + // G3DTools.setTuple(ecp.getDirection(), dir); + if (!ecp.isFixed()) + updateControlPointOrientation(ecp); + + for (PipeControlPoint pcp : ecp.getSubPoint()) { + // TODO update position + updatePathLegEndControlPoint(pcp); + } + } + + private static void updateControlPointOrientation(PipeControlPoint pcp) { + // FIXME : hack to bypass variable length components orientation +// if (pcp.getAtMostOneRelatedObject(ProcessResource.g3dResource.HasWorldOrientation) == null) +// return; +// if (pcp.rotationAngle == null) +// return; + Double angleO = pcp.getRotationAngle(); + double angle = 0.0; + if (angleO != null) + angle = angleO; + + Quat4d q = pcp.getControlPointOrientationQuat(angle); + pcp.setWorldOrientation(q); + } + + /** + * Updates all branches when branch's position has been changed + * + * @param bcp + */ + private static void updateBranchControlPointBranches(PipeControlPoint bcp) throws Exception { + if (DEBUG) + System.out.println("PipingRules.updateBranchControlPointBranches() " + bcp); + if (bcp.isDualInline()) + return; + Collection branches = bcp.getSubPoint(); + if (branches.size() == 0) { + if (DEBUG) + System.out.println("No Branches found"); + return; + } + + for (PipeControlPoint pcp : branches) { + updatePathLegEndControlPoint(pcp); + } + } + + /** + * Recalculates turn control point's internal data (turn angle and offset) + * + * @param tcp + * @param prev + * @param next + */ + private static double updateTurnControlPointTurn(PipeControlPoint tcp, PipeControlPoint prev, PipeControlPoint next) { + if (DEBUG) + System.out.println("PipingTools.updateTurnControlPointTurn()" + tcp); + if (next == null || prev == null) + return Math.PI; // FIXME : argh + Vector3d middlePoint = tcp.getWorldPosition(); + Vector3d nextPoint = next.getWorldPosition(); + Vector3d prevPoint = prev.getWorldPosition(); + return updateTurnControlPointTurn(tcp, middlePoint, prevPoint, nextPoint); + } + + /** + * Recalculates turn control point's internal data (turn angle and offset) + * + * @param tcp + * @param middlePoint + * @param nextPoint + * @param prevPoint + */ + private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d middlePoint, Vector3d prevPoint, Vector3d nextPoint) { + + Vector3d dir1 = new Vector3d(middlePoint); + dir1.sub(prevPoint); + Vector3d dir2 = new Vector3d(nextPoint); + dir2.sub(middlePoint); + if (DEBUG) + System.out.println("PipingTools.updateTurnControlPointTurn " + tcp + " " + prevPoint + " " + middlePoint + " " + nextPoint); + return updateTurnControlPointTurn(tcp, dir1, dir2); + } + + private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d dir1, Vector3d dir2) { + double turnAngle = dir1.angle(dir2); + + double angle = Math.PI - turnAngle; + + Vector3d turnAxis = new Vector3d(); + turnAxis.cross(dir1, dir2); + if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) { + double elbowRadius = tcp.getPipelineComponent().getPipeRun().getTurnRadius(); + double R = elbowRadius / Math.tan(angle * 0.5); + + turnAxis.normalize(); + tcp.setTurnAngle(turnAngle); + tcp.setLength(R);// setComponentOffsetValue(R); + tcp.setTurnAxis(turnAxis); +// tcp.setPosition(tcp.getPosition()); + } else { + turnAngle = 0.0; + tcp.setTurnAngle(0.0); + tcp.setLength(0.0); + tcp.setTurnAxis(MathTools.Y_AXIS); + } + updateControlPointOrientation(tcp); + if (DEBUG) + System.out.println("PipingTools.updateTurnControlPointTurn " + dir1 + " " + dir2 + " " + turnAngle + " " + turnAxis); + return turnAngle; + } + + public static List getControlPoints(PipeRun pipeRun) { + List list = new ArrayList(); + if (pipeRun.getControlPoints().size() == 0) + return list; + PipeControlPoint pcp = pipeRun.getControlPoints().iterator().next(); + while (pcp.getPrevious() != null) { + PipeControlPoint prev = pcp.getPrevious(); + if (prev.getPipeRun() != pipeRun) + break; + pcp = prev; + } + if (pcp.isDualSub()) { + pcp = pcp.getParentPoint(); + } + list.add(pcp); + while (pcp.getNext() != null) { + pcp = pcp.getNext(); + if (pcp.getPipeRun() != pipeRun) + break; + list.add(pcp); + } + return list; + } + + public static void reverse(PipeRun pipeRun) { + List list = getControlPoints(pipeRun); + if (list.size() <= 1) + return; // nothing to do. + + for (int i = 0 ; i < list.size(); i++) { + boolean first = i == 0; + boolean last = i == list.size() - 1; + PipeControlPoint current = list.get(i); + PipeControlPoint currentSub = null; + if (current.isDualInline()) + currentSub = current.getSubPoint().get(0); + if (first) { + PipeControlPoint next = list.get(i+1); + if (next.isDualInline()) + next = next.getSubPoint().get(0); + current.setNext(null); + current.setPrevious(next); + if (currentSub != null) { + currentSub.setNext(null); + currentSub.setPrevious(next); + } + } else if (last) { + PipeControlPoint prev = list.get(i-1); + + current.setPrevious(null); + current.setNext(prev); + + if (currentSub != null) { + currentSub.setPrevious(null); + currentSub.setNext(prev); + } + } else { + PipeControlPoint prev = list.get(i-1); + PipeControlPoint next = list.get(i+1); + if (next.isDualInline()) + next = next.getSubPoint().get(0); + + + current.setPrevious(next); + current.setNext(prev); + + if (currentSub != null) { + currentSub.setPrevious(next); + currentSub.setNext(prev); + } + + } + } + } + + public static void merge(PipeRun run1, PipeRun r2) { + Map positions = new HashMap(); + Map orientations = new HashMap(); + for (PipeControlPoint pcp : r2.getControlPoints()) { + positions.put(pcp, pcp.getWorldPosition()); + orientations.put(pcp, pcp.getWorldOrientation()); + } + for (PipeControlPoint pcp : r2.getControlPoints()) { + r2.deattachChild(pcp); + run1.addChild(pcp); + PipelineComponent component = pcp.getPipelineComponent(); + if (component != null) { + if (!(component instanceof Nozzle)) { + component.deattach(); + run1.addChild(component); + } else { + Nozzle n = (Nozzle)component; + n.setPipeRun(run1); + } + } + } + r2.remove(); + + } + + public static void validate(PipeRun pipeRun) { + if (pipeRun == null) + return; + Collection pcps = pipeRun.getControlPoints(); + int count = 0; + for (PipeControlPoint pcp : pcps) { + if (pcp.getParentPoint() == null || pcp.getParentPoint().getPipeRun() != pipeRun) + count++; + } + List runPcps = getControlPoints(pipeRun); + if (runPcps.size() != count) { + System.out.println("Run is not connected"); + } + for (PipeControlPoint pcp : pcps) { + if (pcp.getParentPoint() == null) { + PipeControlPoint sub = null; + if (pcp.isDualInline()) + sub = pcp.getSubPoint().get(0); + PipeControlPoint next = pcp.getNext(); + PipeControlPoint prev = pcp.getPrevious(); + if (next != null) { + if (!(next.getPrevious() == pcp || next.getPrevious() == sub)) { + System.out.println("Inconsistency between " + pcp + " -> " +next ); + } + } + if (prev != null) { + PipeControlPoint prevParent = null; + if (prev.isDualSub()) { + prevParent = prev.getParentPoint(); + } else if (prev.isDualInline()) { + System.out.println("Inconsistency between " + pcp + " <-- " +prev ); + } + if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) { + System.out.println("Inconsistency between " + pcp + " <-- " +prev ); + } + } + } + } + } + + public static void splitVariableLengthComponent(PipelineComponent newComponent, InlineComponent splittingComponent, boolean assignPos) throws Exception{ + assert(!splittingComponent.getControlPoint().isFixed()); + assert(!(newComponent instanceof InlineComponent && !newComponent.getControlPoint().isFixed())); + PipeControlPoint newCP = newComponent.getControlPoint(); + PipeControlPoint splittingCP = splittingComponent.getControlPoint(); + PipeControlPoint nextCP = splittingCP.getNext(); + PipeControlPoint prevCP = splittingCP.getPrevious(); + + /* there are many different cases to insert new component when + it splits existing VariableLengthinlineComponent. + + 1. VariableLengthComponet is connected from both sides: + - insert new component between VariableLength component and component connected to it + - insert new VariableLengthComponent between inserted component and component selected in previous step + + 2. VariableLengthComponent is connected from one side + - Use previous case or: + - Insert new component to empty end + - Insert new VariableLength component to inserted components empty end + + 3. VariableLength is not connected to any component. + - Should not be possible, at least in current implementation. + - Could be done using second case + + */ + + if (nextCP == null && prevCP == null) { + // this should not be possible + throw new RuntimeException("VariableLengthComponent " + splittingComponent + " is not connected to anything."); + } + double reservedLength = splittingComponent.getControlPoint().getLength(); + double newLength = newComponent.getControlPoint().getLength(); + + + Point3d next = new Point3d(); + Point3d prev = new Point3d(); + splittingCP.getInlineControlPointEnds(prev, next); + + Vector3d newPos = null; + if (assignPos) { + newPos = new Vector3d(prev); + Vector3d dir = new Vector3d(next); + dir.sub(prev); + dir.scale(0.5); + newPos.add(dir); + newComponent.setWorldPosition(newPos); + } else { + newPos = newComponent.getWorldPosition(); + } + + + + Vector3d dir = new Vector3d(next); + dir.sub(prev); + dir.normalize(); + dir.scale(newLength * 0.5); + Point3d vn = new Point3d(newPos); + Point3d vp = new Point3d(newPos); + vn.add(dir); + vp.sub(dir); + double ln = vn.distance(next); + double lp = vp.distance(prev); + vp.interpolate(prev, 0.5); + vn.interpolate(next, 0.5); + + + PipeControlPoint newVariableLengthCP = null;//insertStraight(pcp1, pcp2, pos, length); + if (nextCP == null) { + newCP.insert(splittingCP, Direction.NEXT); + newVariableLengthCP = insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln); + splittingCP.setWorldPosition(new Vector3d(vp)); +// ControlPointTools.setWorldPosition(splittingCP, vp); +// splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp); + } else if (prevCP == null) { + newCP.insert(splittingCP, Direction.PREVIOUS); + newVariableLengthCP = insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp); + splittingCP.setWorldPosition(new Vector3d(vn)); +// splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, ln); + } else { + newCP.insert(splittingCP, nextCP); + newVariableLengthCP = insertStraight(newCP, nextCP, new Vector3d(vn), ln); + splittingCP.setWorldPosition(new Vector3d(vp)); +// splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp); + } + positionUpdate(newCP); + + } + + public static void addSizeChange(boolean reversed, PipeRun pipeRun, PipeRun other, InlineComponent reducer, PipeControlPoint previous, PipeControlPoint next) { + PipeControlPoint pcp = reducer.getControlPoint(); + PipeControlPoint ocp = pcp.getSubPoint().get(0); + if (!reversed) { + String name = pipeRun.getUniqueName("Reducer"); + reducer.setName(name); + pipeRun.addChild(reducer); + other.addChild(ocp); + reducer.setAlternativePipeRun(other); + + previous.setNext(pcp); + pcp.setPrevious(previous); + ocp.setPrevious(previous); + if (next != null) { + pcp.setNext(next); + ocp.setNext(next); + next.setPrevious(ocp); + } + } else { + String name = other.getUniqueName("Reducer"); + reducer.setName(name); + other.addChild(reducer); + pipeRun.addChild(ocp); + reducer.setAlternativePipeRun(pipeRun); + + if (next != null) { + next.setNext(pcp); + pcp.setPrevious(next); + ocp.setPrevious(next); + } + pcp.setNext(previous); + ocp.setNext(previous); + previous.setPrevious(ocp); + } + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java b/org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java new file mode 100644 index 00000000..af64f180 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/utils/ComponentUtils.java @@ -0,0 +1,154 @@ +package org.simantics.plant3d.utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; +import org.simantics.opencascade.SolidModelProvider; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.scenegraph.EndComponent; +import org.simantics.plant3d.scenegraph.InlineComponent; +import org.simantics.plant3d.scenegraph.Nozzle; +import org.simantics.plant3d.scenegraph.P3DRootNode; +import org.simantics.plant3d.scenegraph.PipelineComponent; +import org.simantics.plant3d.scenegraph.TurnComponent; + +public class ComponentUtils { + + + private static Map> clazzes = new HashMap>(); + private static Map providers = new HashMap(); + + public static void preloadCache() { + Simantics.getSession().asyncRequest(new ReadRequest() { + + @Override + public void run(ReadGraph graph) throws DatabaseException { + List types = new ArrayList(); + types.add(Plant3D.URIs.Builtin_Straight); + types.add(Plant3D.URIs.Builtin_Elbow); + types.add(Plant3D.URIs.Builtin_ConcentricReducer); + types.add(Plant3D.URIs.Builtin_BranchSplitComponent); +// types.add(Plant3D.URIs.Builtin_EccentricReducer); + + for (String typeURI : types) { + load(graph, typeURI); + } + } + }); + } + + private static SolidModelProvider getProvider(ReadGraph graph, Resource type) throws DatabaseException { + + Layer0 l0 = Layer0.getInstance(graph); + Plant3D p3d = Plant3D.getInstance(graph); + Resource geom = graph.getPossibleObject(type,p3d.hasGeometry); + if (geom == null) { + for (Resource a : graph.getObjects(type, l0.Asserts)) { + if (p3d.hasGeometry.equals(graph.getPossibleObject(a, l0.HasPredicate))) { + geom = graph.getPossibleObject(a, l0.HasObject); + break; + } + } + } + if (geom != null) { + SolidModelProvider provider = graph.adapt(geom, SolidModelProvider.class); + return provider; + } + return null; + } + + private static Class getClazz(ReadGraph graph, Resource type) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + if (graph.isInheritedFrom(type, p3d.InlineComponent)) + return InlineComponent.class; + if (graph.isInheritedFrom(type, p3d.TurnComponent)) + return TurnComponent.class; + if (graph.isInheritedFrom(type, p3d.EndComponent)) + return EndComponent.class; + if (graph.isInheritedFrom(type, p3d.Nozzle)) + return Nozzle.class; + return null; + } + + private static void load(ReadGraph graph, String typeURI) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + Resource type = graph.getResource(typeURI); + + SolidModelProvider provider = getProvider(graph, type); + if (provider != null || graph.hasStatement(type,p3d.NonVisibleComponent)) { + providers.put(typeURI, provider); + ComponentUtils.clazzes.put(typeURI,getClazz(graph, type)); + return; + } + throw new DatabaseException("Cannot find component for " + typeURI); + } + + private static void load(final String typeURI) throws DatabaseException { + Simantics.getSession().syncRequest(new ReadRequest() { + + @Override + public void run(ReadGraph graph) throws DatabaseException { + load(graph,typeURI); + } + }); + } + + public static PipelineComponent createComponent(P3DRootNode root, String typeURI) throws Exception { + Class type = clazzes.get(typeURI); + SolidModelProvider provider = providers.get(typeURI); + if (type == null || provider == null) { + load(typeURI); + type = clazzes.get(typeURI); + provider = providers.get(typeURI); + } + //PipelineComponent component = type.newInstance(); + PipelineComponent component = null; + if (type == InlineComponent.class) { + component = root.createInline(); + } else if (type == TurnComponent.class) { + component = root.createTurn(); + } else if (type == EndComponent.class) { + component = root.createTurn(); + } else if (type == Nozzle.class) { + component = root.createNozzle(); + } + component.setType(typeURI); + component.setGeometry(provider); + return component; + } + + public static InlineComponent createStraight(P3DRootNode root) throws Exception{ + InlineComponent component = root.createInline(); + component.setType(Plant3D.URIs.Builtin_Straight); + component.setGeometry(providers.get(Plant3D.URIs.Builtin_Straight)); + return component; + } + + public static TurnComponent createTurn(P3DRootNode root) throws Exception { + TurnComponent elbow = root.createTurn(); + elbow.setType(Plant3D.URIs.Builtin_Elbow); + elbow.setGeometry(providers.get(Plant3D.URIs.Builtin_Elbow)); + return elbow; + } + + public static InlineComponent createReducer(P3DRootNode root) throws Exception { + InlineComponent component = root.createInline(); + component.setType(Plant3D.URIs.Builtin_ConcentricReducer); + component.setGeometry(providers.get(Plant3D.URIs.Builtin_ConcentricReducer)); + return component; + } + + public static InlineComponent createBranchSplit(P3DRootNode root) throws Exception { + InlineComponent component = root.createInline(); + component.setType(Plant3D.URIs.Builtin_BranchSplitComponent); + return component; + } +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/utils/Item.java b/org.simantics.plant3d/src/org/simantics/plant3d/utils/Item.java new file mode 100644 index 00000000..61ef6c9a --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/utils/Item.java @@ -0,0 +1,79 @@ +package org.simantics.plant3d.utils; + +public class Item { + + public enum Type{EQUIPMENT,INLINE,TURN,END,NOZZLE}; + + private String uri; + private String name; + + private Type type; + private boolean code = false; + private boolean variable = false; + private boolean sizeChange = false; + + + public Item(String type, String name) { + this.uri = type; + this.name = name; + } + + + + public String getUri() { + return uri; + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public boolean isCode() { + return code; + } + + + + public void setCode(boolean code) { + this.code = code; + } + + public boolean isVariable() { + return variable; + } + + public void setVariable(boolean variable) { + this.variable = variable; + } + + public boolean isSizeChange() { + return sizeChange; + } + + public void setSizeChange(boolean sizeChange) { + this.sizeChange = sizeChange; + } + + + + @Override + public boolean equals(Object obj) { + if (obj.getClass() != getClass()) + return false; + return uri.equals(((Item)obj).uri); + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + +} diff --git a/org.simantics.plant3d/src/org/simantics/plant3d/utils/P3DUtil.java b/org.simantics.plant3d/src/org/simantics/plant3d/utils/P3DUtil.java new file mode 100644 index 00000000..b34f4f07 --- /dev/null +++ b/org.simantics.plant3d/src/org/simantics/plant3d/utils/P3DUtil.java @@ -0,0 +1,162 @@ +package org.simantics.plant3d.utils; + +import java.util.ArrayList; +import java.util.List; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.request.Read; +import org.simantics.layer0.Layer0; +import org.simantics.plant3d.ontology.Plant3D; +import org.simantics.plant3d.utils.Item.Type; +import org.simantics.ui.SimanticsUI; + +public class P3DUtil { + + public static List getEquipments() throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + Resource project = Simantics.getProject().get(); + Resource builtins = graph.getResource(Plant3D.URIs.Builtin); + List actions = getItems(graph, project,p3d.Equipment); + actions.addAll(getItems(graph, builtins,p3d.Equipment)); + return actions; + } + + + }); + } + + public static List getNozzles() throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + ItemQuery query = new ItemQuery(p3d.Nozzle); + return graph.syncRequest(query); + } + }); + } + + private static class ItemQuery implements Read> { + private Resource type; + public ItemQuery(Resource type) { + this.type = type; + } + + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Resource project = Simantics.getProject().get(); + Resource builtins = graph.getResource(Plant3D.URIs.Builtin); + List actions = getItems(graph, project,type); + actions.addAll(getItems(graph, builtins,type)); + return actions; + } + } + + public static List getEnds() throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + ItemQuery query = new ItemQuery(p3d.EndComponent); + return graph.syncRequest(query); + } + }); + } + + public static List getTurns() throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + ItemQuery query = new ItemQuery(p3d.TurnComponent); + return graph.syncRequest(query); + } + }); + } + + public static List getInlines() throws DatabaseException { + return Simantics.getSession().syncRequest(new Read>() { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Plant3D p3d = Plant3D.getInstance(graph); + ItemQuery query = new ItemQuery(p3d.InlineComponent); + return graph.syncRequest(query); + } + }); + } + + private static List getItems(ReadGraph graph, Resource lib, Resource type) throws DatabaseException{ + Plant3D p3d = Plant3D.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + List result = new ArrayList(); + for (Resource r : graph.getObjects(lib, l0.ConsistsOf)) { + if (graph.isInstanceOf(r, type) ) { + Resource geom = graph.getPossibleObject(r,p3d.hasGeometry); + if (geom != null || graph.hasStatement(r,p3d.NonVisibleComponent)) { + + result.add(createItem(graph, r)); + } + } + if (graph.isInheritedFrom(r, type)) { + boolean asserts = false; + for (Resource a : graph.getObjects(r, l0.Asserts)) { + if (p3d.hasGeometry.equals(graph.getPossibleObject(a, l0.HasPredicate))) { + asserts = true; + break; + } + } + if (asserts) { + result.add(createItem(graph, r)); + } + } + } + return result; + } + + private static Item createItem(ReadGraph graph, Resource r) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + Plant3D p3d = Plant3D.getInstance(graph); + String name = graph.getRelatedValue(r, l0.HasName); + String uri = graph.getURI(r); + Item item = new Item(uri, name); + if (graph.isInstanceOf(r, p3d.Equipment)) + item.setType(Type.EQUIPMENT); + else if (graph.isInstanceOf(r, p3d.InlineComponent)) + item.setType(Type.INLINE); + else if (graph.isInstanceOf(r, p3d.EndComponent)) + item.setType(Type.END); + else if (graph.isInstanceOf(r, p3d.TurnComponent)) + item.setType(Type.TURN); + else if (graph.isInstanceOf(r, p3d.Nozzle)) + item.setType(Type.NOZZLE); + else + throw new RuntimeException("Cannot detect type for " + r); + + if (graph.hasStatement(r, p3d.CodeComponent)) + item.setCode(true); + if (graph.hasStatement(r, p3d.VariableAngleTurnComponent) || + graph.hasStatement(r, p3d.VariableLengthInlineComponent)) + item.setVariable(true); + if (graph.hasStatement(r, p3d.SizeChangeComponent)) + item.setSizeChange(true); + return item; + } + + public static Resource createModel(WriteGraph graph, String name) throws DatabaseException{ + Layer0 l0 = Layer0.getInstance(graph); + Plant3D p3d = Plant3D.getInstance(graph); + Resource model = graph.newResource(); + graph.claim(model, l0.InstanceOf, p3d.Plant); + graph.claimLiteral(model, l0.HasName, name); + + return model; + } + +} -- 2.47.1