From 77fb2992f4eab37919e21af7f24a70a0a6ecc336 Mon Sep 17 00:00:00 2001 From: Reino Ruusu Date: Wed, 30 Jan 2019 15:34:26 +0200 Subject: [PATCH] Implementation of non-UI parts of network element queries gitlab #28 TODO: * Management of mapped properties for map diagram elements * UI for listing stored queries * UI for defining/editing queries * UI for executing queries Change-Id: I5de84629ed75ce1ae48cd424dc872bb3456ec62b --- org.simantics.district.feature/feature.xml | 7 + .../graph/DistrictNetworkProfiles.pgraph | 2 +- .../graph/DiagramRegions.pgraph | 1 + org.simantics.district.selection/.classpath | 7 + org.simantics.district.selection/.project | 34 + .../META-INF/MANIFEST.MF | 22 + .../build.properties | 6 + org.simantics.district.selection/graph.tg | Bin 0 -> 5822 bytes .../graph/DiagramElementSelection.pgraph | 72 +++ org.simantics.district.selection/pom.xml | 17 + .../scl/Simantics/District/Selection.scl | 89 +++ .../selection/ElementSelectionResource.java | 196 ++++++ .../district/selection/ElementSelector.java | 600 ++++++++++++++++++ pom.xml | 1 + 14 files changed, 1053 insertions(+), 1 deletion(-) create mode 100644 org.simantics.district.selection/.classpath create mode 100644 org.simantics.district.selection/.project create mode 100644 org.simantics.district.selection/META-INF/MANIFEST.MF create mode 100644 org.simantics.district.selection/build.properties create mode 100644 org.simantics.district.selection/graph.tg create mode 100644 org.simantics.district.selection/graph/DiagramElementSelection.pgraph create mode 100644 org.simantics.district.selection/pom.xml create mode 100644 org.simantics.district.selection/scl/Simantics/District/Selection.scl create mode 100644 org.simantics.district.selection/src/org/simantics/district/selection/ElementSelectionResource.java create mode 100644 org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java diff --git a/org.simantics.district.feature/feature.xml b/org.simantics.district.feature/feature.xml index 47cc1761..f08da0a1 100644 --- a/org.simantics.district.feature/feature.xml +++ b/org.simantics.district.feature/feature.xml @@ -70,4 +70,11 @@ version="0.0.0" unpack="false"/> + + diff --git a/org.simantics.district.network.ontology/graph/DistrictNetworkProfiles.pgraph b/org.simantics.district.network.ontology/graph/DistrictNetworkProfiles.pgraph index 1a468d12..d3734e63 100644 --- a/org.simantics.district.network.ontology/graph/DistrictNetworkProfiles.pgraph +++ b/org.simantics.district.network.ontology/graph/DistrictNetworkProfiles.pgraph @@ -30,5 +30,5 @@ DN.VertexSymbolStyle : DIA.Style // The output of the function should be SVG DN.HasSymbolFunction L0.ExternalValue + --> L0.Value ==> "Resource -> String" diff --git a/org.simantics.district.region.ontology/graph/DiagramRegions.pgraph b/org.simantics.district.region.ontology/graph/DiagramRegions.pgraph index c7774dba..db8b80fa 100644 --- a/org.simantics.district.region.ontology/graph/DiagramRegions.pgraph +++ b/org.simantics.district.region.ontology/graph/DiagramRegions.pgraph @@ -8,6 +8,7 @@ DIARegions = : L0.Ontology DIARegions.Region "Vector Double" DIARegions.hasRegion + + + + + + diff --git a/org.simantics.district.selection/.project b/org.simantics.district.selection/.project new file mode 100644 index 00000000..4401353c --- /dev/null +++ b/org.simantics.district.selection/.project @@ -0,0 +1,34 @@ + + + org.simantics.district.selection + + + + + + 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.district.selection/META-INF/MANIFEST.MF b/org.simantics.district.selection/META-INF/MANIFEST.MF new file mode 100644 index 00000000..ae19b9c4 --- /dev/null +++ b/org.simantics.district.selection/META-INF/MANIFEST.MF @@ -0,0 +1,22 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Diagram Element Selection +Bundle-SymbolicName: org.simantics.district.selection +Bundle-Version: 1.0.0.qualifier +Automatic-Module-Name: org.simantics.district.selection +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.simantics.district.route;bundle-version="1.0.0", + org.simantics.district.route.ontology;bundle-version="1.0.0", + org.simantics.district.region;bundle-version="1.0.0", + org.simantics.district.region.ontology;bundle-version="1.0.0", + org.simantics.db.layer0;bundle-version="1.1.0", + org.simantics.layer0;bundle-version="1.1.0", + org.simantics.diagram.ontology;bundle-version="2.2.0", + org.simantics.district.network.ontology;bundle-version="1.0.0", + org.slf4j.api;bundle-version="1.7.25", + org.junit, + org.simantics, + org.simantics.db.testing, + org.simantics.structural.ontology, + org.simantics.modeling.ontology +Export-Package: org.simantics.district.selection diff --git a/org.simantics.district.selection/build.properties b/org.simantics.district.selection/build.properties new file mode 100644 index 00000000..07dd878b --- /dev/null +++ b/org.simantics.district.selection/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + scl/,\ + graph.tg diff --git a/org.simantics.district.selection/graph.tg b/org.simantics.district.selection/graph.tg new file mode 100644 index 0000000000000000000000000000000000000000..d7f0261ef11e2a8ce4cc90b8c2d9e311f8bb8a43 GIT binary patch literal 5822 zcmai&eODA$62=jjVHn;)4dO=K1fz*@6rzb5SLMw(zJLKtHi=Q2nd{KnbdTLVA%2&9 z(fw=pd8%&>MW39roKtXrRnM(k_oZ(SQ|Y8ZcYk1D-~?@|6}0wUyRjc+`>mwy2H_ld z(ERH^BW(De%0%k2q?fkbTGESi(bV0axr?(fiL+74s{A$1U-hu<;ylblm(|EN(_l?A z6-nCeZ5TtE#AB5DpW)%{B*~BU8;*kmCu0ofAG<}Xwt*7TIEaoZtKA^Q_>%vqS8c1y zQ3`FP){KvOR_a+ElH7Fqe5?=J4>ak%b2V z7N(UolC%S7bqK#soZN_H>>U@m7Hhn^)VPkoO-8F@>tXh;7fS#d zHq+;Hog7&Y1Dvk|v_BI^ypWFw`fG)(owB#x=z4_1l$tO%=0wqx-|c2SLhByw#b&r{RHA)uH>2NqV=h zd(lM_)$P-L*XVAGZVLHyv`rjYh~6@A+bF-IqdMnj8>Gj|T9M$?w(DR9w%%0+Rz{J( zh=JbGwQ4diN$1&>PABCW;Bh@V5jP|@2A4Tcm&7@(6`MW3!pNmb87}KZxjgxB?a_b5 zjComv^APq=rKa->5KwX1bU8~$wPr?Z&~vu7`6%q{yNsvfs(3~=xjEwaLL5UY5l_=S z$uPN{^o~#RKK=J{OAZ>@ZBaxN` zOci;W@xDvLJcH*~@r?RqlCE=wh$|k%9S+~6XU;6hS0b6%P4XamcyyQ5ik@3>oq6Oqp9SqLJtM!!gGWHm`57w`(-EyU(*uH?^>EawURATi#F#RTB`y6Ddre8=$> zw;_%>XiN${t1*KcrnFSlWAf1PgN96gh|Uvu70bKr>T+Wle38x}^TIn{IF4Ro!_w`H zUId#iKTOj1axv*W@PLxxiodLeZh+pwPQl*8euIT2=0`Uh=Z0A34PlvkqufTjWtjQ* zN*t8G&PhFLw?>d99BQ%O%& z{av)ls^3NX1lS;>;sH(o>^Tl z)2_3-Xs+}1H^&)GWeJ!&V z%bYt|*K?=DFG|e2gL++W&9AKam6@MsO6xm=_P2(Y(7st>z5`UxIPVNyuM3`ay_{!V zuWvl-dY$#G>p?wpYJS#3*7@^pBG14+z&Q3bn2vwlvyRVq3FA6GpEt6O&-`Q^pZW%D z3dZN{66|V;dFSbPJilZeZ_l$Gk8vIEYaiF~sOO!i`FP&Qnvd@~vdu@jd@lO9)=T~8 zFwK9`v(~%iS?gi0&r14pU|SDa>v@ef+15kWdd~Z})*2eJto1M#_dxq| ztt|6wS?1XzYyN4p7hqaH&l_3mf92WM&$!mlTx1=edY*Z$@0w>j9^&@UC+qn9 zEJe2ClXZONB5OYCxrfSJE6ZFj%iK%LJPVc=J=^+c;j{IUwceL#lWo0Zt(UpUS|9b3 zFq@yOacV7du9ms>WIZ2GO3cq1HqOr~WZfUludMl%nSTPN^W}XoX80GhtFUoc2BVf- zhvhJ8$=bi>S?9-E7+2={Smyp(=6SQsyTtNRiI+=!qr^8$%y*N`$9I?ITP40%VtzI{ s0n_=g?;vb~je*v*G`D>VJ`>|4E`VmlDbU2VmG?lmGw# literal 0 HcmV?d00001 diff --git a/org.simantics.district.selection/graph/DiagramElementSelection.pgraph b/org.simantics.district.selection/graph/DiagramElementSelection.pgraph new file mode 100644 index 00000000..5a510864 --- /dev/null +++ b/org.simantics.district.selection/graph/DiagramElementSelection.pgraph @@ -0,0 +1,72 @@ +L0 = +DIA = +DNR = +REG = + +ES = : L0.Ontology + @L0.new + L0.Ontology.global true + L0.HasResourceClass "org.simantics.district.selection.ElementSelectionResource" + +ES.Selection -- ES.Selection.HasGenerator --> ES.Generator -- ES.Selection.HasCondition --> ES.Condition -- ES.Selection.HasSelector --> ES.Selector -- ES.Generator.HasSelectedElement --> DIA.Element -- ES.Generator.HasDiagram --> DIA.Diagram -- ES.PropertySelector.HasSelectionPropertyName --> L0.String -- ES.PropertySelector.HasResultCount --> L0.Integer -- ES.Condition.IsInverse --> ES.Condition -- ES.HasSubcondition --> ES.Selector -- ES.PropertyCondition.HasPropertyName --> L0.String -- ES.PropertyCondition.HasLowerLimit --> L0.Double -- ES.PropertyCondition.HasUpperLimit --> L0.Double -- ES.RouteCondition.HasRoute --> DNR.Route -- ES.RegionCondition.HasRegion --> REG.Region + + 4.0.0 + + + org.simantics.district + org.simantics.district.root + 1.0.0-SNAPSHOT + + + org.simantics.district.selection + eclipse-plugin + 1.0.0-SNAPSHOT + + diff --git a/org.simantics.district.selection/scl/Simantics/District/Selection.scl b/org.simantics.district.selection/scl/Simantics/District/Selection.scl new file mode 100644 index 00000000..4f1d2705 --- /dev/null +++ b/org.simantics.district.selection/scl/Simantics/District/Selection.scl @@ -0,0 +1,89 @@ +import "http://www.simantics.org/ElementSelection-1.0" as ES +import "Simantics/DB" + +importJava "org.simantics.district.selection.ElementSelector" where + data ElementSelector + + """ + Get a selection resource represented as an ElementSelector object. + """ + getSelector :: Resource -> ElementSelector + + """ + Get the name of the selection from an element selector object. + """ + @JavaName getName + getSelectorName :: ElementSelector -> String + + """ + Get an expression that represents the selection from an element selector object. + """ + @JavaName getExpression + getSelectorExpression :: ElementSelector -> String + + """ + Get the selection resource form an element selector object. + """ + @JavaName getResource + getSelectorResource :: ElementSelector -> Resource + + """ + Get the elements selected by an element selector object. + + `selectedElements = selectElementsFrom elementSelector model` + """ + @JavaName selectElementsFrom + selectElementsFrom :: ElementSelector -> Resource -> [Resource] + +data Generator = Model | Diagram Resource | Explicit [Resource] +data Selector = All | NLowest String Integer | NHighest String Integer +data Condition = PropertyCondition String (Maybe Double) (Maybe Double) + | Region Resource | Route Resource + | Conjunction [Condition] | Disjunction [Condition] | Negation [Condition] + | Not Condition +data Selection = Selection Generator Selector (Maybe Condition) + +""" +Write a selection expression into the database. + +`selectionResource = writeSelection parentResource selectionName selection` +""" +writeSelection :: Resource -> String -> Selection -> Resource +writeSelection root name (Selection generator selector maybeCondition) = let + selection = newEntity [hasName name, hasLabel name, hasType ES.Selection, hasParent root] + generator = match generator with + Model -> newEntity [hasType ES.Generator.Model] + Diagram diagram -> newEntity [hasType ES.Generator.Diagram, hasStatement ES.Generator.HasDiagram diagram] + Explicit elements -> let + g = newEntity [hasType ES.Generator.Explicit] + for elements (claim g ES.Generator.HasSelectedElement) + in g + claim selection ES.Selection.HasGenerator generator + selector = match selector with + All -> newEntity [hasType ES.Selector.All] + NLowest propertyName count -> newEntity [hasType ES.Selector.NLowest, + hasProperty ES.PropertySelector.HasSelectionPropertyName propertyName, + hasProperty ES.PropertySelector.HasResultCount count] + NHighest propertyName count -> newEntity [hasType ES.Selector.NHighest, + hasProperty ES.PropertySelector.HasSelectionPropertyName propertyName, + hasProperty ES.PropertySelector.HasResultCount count] + claim selection ES.Selection.HasSelector selector + for maybeCondition \condition -> claim selection ES.Selection.HasCondition $ writeCondition condition + in selection + where + writeCondition condition = match condition with + PropertyCondition propertyName lowerLimit upperLimit -> let + cond = newEntity [hasType ES.PropertyCondition, hasProperty ES.PropertyCondition.HasPropertyName propertyName] + for lowerLimit \limit -> claimRelatedValue cond ES.PropertyCondition.HasLowerLimit limit + for upperLimit \limit -> claimRelatedValue cond ES.PropertyCondition.HasUpperLimit limit + in cond + Region region -> newEntity [hasType ES.RegionCondition, hasProperty ES.RegionCondition.HasRegion region] + Route route -> newEntity [hasType ES.RouteCondition, hasProperty ES.RouteCondition.HasRoute route] + Conjunction conds -> writeAggregateCondition ES.Conjunction conds + Disjunction conds -> writeAggregateCondition ES.Disjunction conds + Negation conds -> writeAggregateCondition ES.Negation conds + Not cond -> writeAggregateCondition ES.Negation [cond] + writeAggregateCondition condType conds = let + cond = newEntity [hasType condType] + for conds \c -> claim cond ES.HasSubcondition $ writeCondition c + in cond diff --git a/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelectionResource.java b/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelectionResource.java new file mode 100644 index 00000000..e75673dd --- /dev/null +++ b/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelectionResource.java @@ -0,0 +1,196 @@ +package org.simantics.district.selection; + +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 ElementSelectionResource { + + public final Resource AggregateCondition; + public final Resource Condition; + public final Resource Condition_IsInverse; + public final Resource Condition_IsInverse_Inverse; + public final Resource Conjunction; + public final Resource Disjunction; + public final Resource ElementaryCondition; + public final Resource Generator; + public final Resource Generator_Diagram; + public final Resource Generator_Explicit; + public final Resource Generator_HasDiagram; + public final Resource Generator_HasDiagram_Inverse; + public final Resource Generator_HasSelectedElement; + public final Resource Generator_HasSelectedElement_Inverse; + public final Resource Generator_Model; + public final Resource HasSubcondition; + public final Resource HasSubcondition_Inverse; + public final Resource Negation; + public final Resource PropertyCondition; + public final Resource PropertyCondition_HasLowerLimit; + public final Resource PropertyCondition_HasLowerLimit_Inverse; + public final Resource PropertyCondition_HasPropertyName; + public final Resource PropertyCondition_HasPropertyName_Inverse; + public final Resource PropertyCondition_HasUpperLimit; + public final Resource PropertyCondition_HasUpperLimit_Inverse; + public final Resource PropertySelector; + public final Resource PropertySelector_HasResultCount; + public final Resource PropertySelector_HasResultCount_Inverse; + public final Resource PropertySelector_HasSelectionPropertyName; + public final Resource PropertySelector_HasSelectionPropertyName_Inverse; + public final Resource RegionCondition; + public final Resource RegionCondition_HasRegion; + public final Resource RegionCondition_HasRegion_Inverse; + public final Resource RouteCondition; + public final Resource RouteCondition_HasRoute; + public final Resource RouteCondition_HasRoute_Inverse; + public final Resource Selection; + public final Resource Selection_HasCondition; + public final Resource Selection_HasCondition_Inverse; + public final Resource Selection_HasGenerator; + public final Resource Selection_HasGenerator_Inverse; + public final Resource Selection_HasSelector; + public final Resource Selection_HasSelector_Inverse; + public final Resource Selector; + public final Resource Selector_All; + public final Resource Selector_NHighest; + public final Resource Selector_NLowest; + + public static class URIs { + public static final String AggregateCondition = "http://www.simantics.org/ElementSelection-1.0/AggregateCondition"; + public static final String Condition = "http://www.simantics.org/ElementSelection-1.0/Condition"; + public static final String Condition_IsInverse = "http://www.simantics.org/ElementSelection-1.0/Condition/IsInverse"; + public static final String Condition_IsInverse_Inverse = "http://www.simantics.org/ElementSelection-1.0/Condition/IsInverse/Inverse"; + public static final String Conjunction = "http://www.simantics.org/ElementSelection-1.0/Conjunction"; + public static final String Disjunction = "http://www.simantics.org/ElementSelection-1.0/Disjunction"; + public static final String ElementaryCondition = "http://www.simantics.org/ElementSelection-1.0/ElementaryCondition"; + public static final String Generator = "http://www.simantics.org/ElementSelection-1.0/Generator"; + public static final String Generator_Diagram = "http://www.simantics.org/ElementSelection-1.0/Generator/Diagram"; + public static final String Generator_Explicit = "http://www.simantics.org/ElementSelection-1.0/Generator/Explicit"; + public static final String Generator_HasDiagram = "http://www.simantics.org/ElementSelection-1.0/Generator/HasDiagram"; + public static final String Generator_HasDiagram_Inverse = "http://www.simantics.org/ElementSelection-1.0/Generator/HasDiagram/Inverse"; + public static final String Generator_HasSelectedElement = "http://www.simantics.org/ElementSelection-1.0/Generator/HasSelectedElement"; + public static final String Generator_HasSelectedElement_Inverse = "http://www.simantics.org/ElementSelection-1.0/Generator/HasSelectedElement/Inverse"; + public static final String Generator_Model = "http://www.simantics.org/ElementSelection-1.0/Generator/Model"; + public static final String HasSubcondition = "http://www.simantics.org/ElementSelection-1.0/HasSubcondition"; + public static final String HasSubcondition_Inverse = "http://www.simantics.org/ElementSelection-1.0/HasSubcondition/Inverse"; + public static final String Negation = "http://www.simantics.org/ElementSelection-1.0/Negation"; + public static final String PropertyCondition = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition"; + public static final String PropertyCondition_HasLowerLimit = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasLowerLimit"; + public static final String PropertyCondition_HasLowerLimit_Inverse = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasLowerLimit/Inverse"; + public static final String PropertyCondition_HasPropertyName = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasPropertyName"; + public static final String PropertyCondition_HasPropertyName_Inverse = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasPropertyName/Inverse"; + public static final String PropertyCondition_HasUpperLimit = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasUpperLimit"; + public static final String PropertyCondition_HasUpperLimit_Inverse = "http://www.simantics.org/ElementSelection-1.0/PropertyCondition/HasUpperLimit/Inverse"; + public static final String PropertySelector = "http://www.simantics.org/ElementSelection-1.0/PropertySelector"; + public static final String PropertySelector_HasResultCount = "http://www.simantics.org/ElementSelection-1.0/PropertySelector/HasResultCount"; + public static final String PropertySelector_HasResultCount_Inverse = "http://www.simantics.org/ElementSelection-1.0/PropertySelector/HasResultCount/Inverse"; + public static final String PropertySelector_HasSelectionPropertyName = "http://www.simantics.org/ElementSelection-1.0/PropertySelector/HasSelectionPropertyName"; + public static final String PropertySelector_HasSelectionPropertyName_Inverse = "http://www.simantics.org/ElementSelection-1.0/PropertySelector/HasSelectionPropertyName/Inverse"; + public static final String RegionCondition = "http://www.simantics.org/ElementSelection-1.0/RegionCondition"; + public static final String RegionCondition_HasRegion = "http://www.simantics.org/ElementSelection-1.0/RegionCondition/HasRegion"; + public static final String RegionCondition_HasRegion_Inverse = "http://www.simantics.org/ElementSelection-1.0/RegionCondition/HasRegion/Inverse"; + public static final String RouteCondition = "http://www.simantics.org/ElementSelection-1.0/RouteCondition"; + public static final String RouteCondition_HasRoute = "http://www.simantics.org/ElementSelection-1.0/RouteCondition/HasRoute"; + public static final String RouteCondition_HasRoute_Inverse = "http://www.simantics.org/ElementSelection-1.0/RouteCondition/HasRoute/Inverse"; + public static final String Selection = "http://www.simantics.org/ElementSelection-1.0/Selection"; + public static final String Selection_HasCondition = "http://www.simantics.org/ElementSelection-1.0/Selection/HasCondition"; + public static final String Selection_HasCondition_Inverse = "http://www.simantics.org/ElementSelection-1.0/Selection/HasCondition/Inverse"; + public static final String Selection_HasGenerator = "http://www.simantics.org/ElementSelection-1.0/Selection/HasGenerator"; + public static final String Selection_HasGenerator_Inverse = "http://www.simantics.org/ElementSelection-1.0/Selection/HasGenerator/Inverse"; + public static final String Selection_HasSelector = "http://www.simantics.org/ElementSelection-1.0/Selection/HasSelector"; + public static final String Selection_HasSelector_Inverse = "http://www.simantics.org/ElementSelection-1.0/Selection/HasSelector/Inverse"; + public static final String Selector = "http://www.simantics.org/ElementSelection-1.0/Selector"; + public static final String Selector_All = "http://www.simantics.org/ElementSelection-1.0/Selector/All"; + public static final String Selector_NHighest = "http://www.simantics.org/ElementSelection-1.0/Selector/NHighest"; + public static final String Selector_NLowest = "http://www.simantics.org/ElementSelection-1.0/Selector/NLowest"; + } + + public static Resource getResourceOrNull(ReadGraph graph, String uri) { + try { + return graph.getResource(uri); + } catch(DatabaseException e) { + System.err.println(e.getMessage()); + return null; + } + } + + public ElementSelectionResource(ReadGraph graph) { + AggregateCondition = getResourceOrNull(graph, URIs.AggregateCondition); + Condition = getResourceOrNull(graph, URIs.Condition); + Condition_IsInverse = getResourceOrNull(graph, URIs.Condition_IsInverse); + Condition_IsInverse_Inverse = getResourceOrNull(graph, URIs.Condition_IsInverse_Inverse); + Conjunction = getResourceOrNull(graph, URIs.Conjunction); + Disjunction = getResourceOrNull(graph, URIs.Disjunction); + ElementaryCondition = getResourceOrNull(graph, URIs.ElementaryCondition); + Generator = getResourceOrNull(graph, URIs.Generator); + Generator_Diagram = getResourceOrNull(graph, URIs.Generator_Diagram); + Generator_Explicit = getResourceOrNull(graph, URIs.Generator_Explicit); + Generator_HasDiagram = getResourceOrNull(graph, URIs.Generator_HasDiagram); + Generator_HasDiagram_Inverse = getResourceOrNull(graph, URIs.Generator_HasDiagram_Inverse); + Generator_HasSelectedElement = getResourceOrNull(graph, URIs.Generator_HasSelectedElement); + Generator_HasSelectedElement_Inverse = getResourceOrNull(graph, URIs.Generator_HasSelectedElement_Inverse); + Generator_Model = getResourceOrNull(graph, URIs.Generator_Model); + HasSubcondition = getResourceOrNull(graph, URIs.HasSubcondition); + HasSubcondition_Inverse = getResourceOrNull(graph, URIs.HasSubcondition_Inverse); + Negation = getResourceOrNull(graph, URIs.Negation); + PropertyCondition = getResourceOrNull(graph, URIs.PropertyCondition); + PropertyCondition_HasLowerLimit = getResourceOrNull(graph, URIs.PropertyCondition_HasLowerLimit); + PropertyCondition_HasLowerLimit_Inverse = getResourceOrNull(graph, URIs.PropertyCondition_HasLowerLimit_Inverse); + PropertyCondition_HasPropertyName = getResourceOrNull(graph, URIs.PropertyCondition_HasPropertyName); + PropertyCondition_HasPropertyName_Inverse = getResourceOrNull(graph, URIs.PropertyCondition_HasPropertyName_Inverse); + PropertyCondition_HasUpperLimit = getResourceOrNull(graph, URIs.PropertyCondition_HasUpperLimit); + PropertyCondition_HasUpperLimit_Inverse = getResourceOrNull(graph, URIs.PropertyCondition_HasUpperLimit_Inverse); + PropertySelector = getResourceOrNull(graph, URIs.PropertySelector); + PropertySelector_HasResultCount = getResourceOrNull(graph, URIs.PropertySelector_HasResultCount); + PropertySelector_HasResultCount_Inverse = getResourceOrNull(graph, URIs.PropertySelector_HasResultCount_Inverse); + PropertySelector_HasSelectionPropertyName = getResourceOrNull(graph, URIs.PropertySelector_HasSelectionPropertyName); + PropertySelector_HasSelectionPropertyName_Inverse = getResourceOrNull(graph, URIs.PropertySelector_HasSelectionPropertyName_Inverse); + RegionCondition = getResourceOrNull(graph, URIs.RegionCondition); + RegionCondition_HasRegion = getResourceOrNull(graph, URIs.RegionCondition_HasRegion); + RegionCondition_HasRegion_Inverse = getResourceOrNull(graph, URIs.RegionCondition_HasRegion_Inverse); + RouteCondition = getResourceOrNull(graph, URIs.RouteCondition); + RouteCondition_HasRoute = getResourceOrNull(graph, URIs.RouteCondition_HasRoute); + RouteCondition_HasRoute_Inverse = getResourceOrNull(graph, URIs.RouteCondition_HasRoute_Inverse); + Selection = getResourceOrNull(graph, URIs.Selection); + Selection_HasCondition = getResourceOrNull(graph, URIs.Selection_HasCondition); + Selection_HasCondition_Inverse = getResourceOrNull(graph, URIs.Selection_HasCondition_Inverse); + Selection_HasGenerator = getResourceOrNull(graph, URIs.Selection_HasGenerator); + Selection_HasGenerator_Inverse = getResourceOrNull(graph, URIs.Selection_HasGenerator_Inverse); + Selection_HasSelector = getResourceOrNull(graph, URIs.Selection_HasSelector); + Selection_HasSelector_Inverse = getResourceOrNull(graph, URIs.Selection_HasSelector_Inverse); + Selector = getResourceOrNull(graph, URIs.Selector); + Selector_All = getResourceOrNull(graph, URIs.Selector_All); + Selector_NHighest = getResourceOrNull(graph, URIs.Selector_NHighest); + Selector_NLowest = getResourceOrNull(graph, URIs.Selector_NLowest); + } + + public static ElementSelectionResource getInstance(ReadGraph graph) { + Session session = graph.getSession(); + ElementSelectionResource ret = session.peekService(ElementSelectionResource.class); + if(ret == null) { + QueryControl qc = graph.getService(QueryControl.class); + ret = new ElementSelectionResource(qc.getIndependentGraph(graph)); + session.registerService(ElementSelectionResource.class, ret); + } + return ret; + } + + public static ElementSelectionResource getInstance(RequestProcessor session) throws DatabaseException { + ElementSelectionResource ret = session.peekService(ElementSelectionResource.class); + if(ret == null) { + ret = session.syncRequest(new Read() { + public ElementSelectionResource perform(ReadGraph graph) throws DatabaseException { + QueryControl qc = graph.getService(QueryControl.class); + return new ElementSelectionResource(qc.getIndependentGraph(graph)); + } + }); + session.registerService(ElementSelectionResource.class, ret); + } + return ret; + } + +} + diff --git a/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java b/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java new file mode 100644 index 00000000..9de46638 --- /dev/null +++ b/org.simantics.district.selection/src/org/simantics/district/selection/ElementSelector.java @@ -0,0 +1,600 @@ +package org.simantics.district.selection; + +import java.awt.geom.Path2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.common.utils.ListUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.ServiceException; +import org.simantics.db.layer0.request.ActiveModels; +import org.simantics.db.layer0.request.ActiveRuns; +import org.simantics.db.layer0.request.Configuration; +import org.simantics.db.layer0.variable.RVI; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.layer0.variable.Variables; +import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.district.region.ontology.DiagramRegionsResource; +import org.simantics.district.selection.ElementSelector.AggregateCondition.Type; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingResources; +import org.simantics.scl.runtime.Lists; +import org.simantics.scl.runtime.function.FunctionImpl1; +import org.simantics.scl.runtime.tuple.Tuple2; +import org.simantics.structural.stubs.StructuralResource2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ElementSelector { + String name; + String expression; + Integer count; + Resource resource; + + Generator generator; + Selector selector; + Condition condition; + + static Logger LOG = LoggerFactory.getLogger(ElementSelector.class); + + static ElementSelectionResource ES; + static Layer0 L0; + static StructuralResource2 STR; + static ModelingResources MOD; + static DiagramResource DIA; + + ElementSelector(ReadGraph graph, Resource resource) throws DatabaseException { + super(); + + L0 = Layer0.getInstance(graph); + ES = ElementSelectionResource.getInstance(graph); + STR = StructuralResource2.getInstance(graph); + MOD = ModelingResources.getInstance(graph); + DIA = DiagramResource.getInstance(graph); + + this.resource = resource; + this.count = -1; + try { + this.name = graph.getRelatedValue(resource, L0.HasLabel); + this.expression = buildExpression(graph, resource); + } catch (DatabaseException e) { + LOG.error("Error reading element selector", e); + throw e; + } + } + + public static ElementSelector getSelector(ReadGraph graph, Resource resource) throws DatabaseException { + return graph.syncRequest(new ElementSelectorQuery(resource)); + } + + public String getName() { + return name; + } + + public String getExpression() { + return expression; + } + + public Resource getResource() { + return resource; + } + + public List selectElementsFrom(ReadGraph graph, Resource model) throws DatabaseException { + if (selector == null) { + buildSelection(graph); + } + + Collection result = gather(graph, model); + return result instanceof List ? (List) result : new ArrayList<>(result); + } + + private static Generator buildGenerator(ReadGraph graph, Resource resource) throws DatabaseException { + if (!graph.isInstanceOf(resource, ES.Generator)) + throw new IllegalArgumentException("Resource " + resource + " is not a valid generator"); + + if (graph.isInstanceOf(resource, ES.Generator_Model)) { + return new ModelGenerator(); + } + else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) { + return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram)); + } + else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) { + return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement)); + } + else { + throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource))); + } + } + + private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException { + if (!graph.isInstanceOf(resource, ES.Selector)) + throw new IllegalArgumentException("Resource " + resource + " is not a valid selector"); + + Selector s; + if (graph.isInstanceOf(resource, ES.Selector_All)) { + s = new All(); + } + else if (graph.isInstanceOf(resource, ES.PropertySelector)) { + String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName); + Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount); + boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest); + s = new PropertySelector(isSmallest, propertyName, resultCount); + } + else { + throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource))); + } + return s; + } + + private static Condition buildCondition(ReadGraph graph, Resource resource) throws DatabaseException { + if (resource == null) + return null; + + if (!graph.isInstanceOf(resource, ES.Condition)) + throw new IllegalArgumentException("Resource " + resource + " is not a valid condition"); + + Condition cond; + if (graph.isInstanceOf(resource, ES.PropertyCondition)) { + String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName); + Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit); + Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit); + cond = new PropertyCondition(propertyName, lowerLimit, upperLimit); + } + else if (graph.isInstanceOf(resource, ES.RegionCondition)) { + DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); + double[] region = graph.getRelatedValue(graph.getSingleObject(resource, ES.RegionCondition_HasRegion), DR.Region_area); + cond = new RegionCondition(region); + } + else if (graph.isInstanceOf(resource, ES.RouteCondition)) { + Set routePoints = new HashSet<>(ListUtils.toList(graph, graph.getSingleObject(resource, ES.RouteCondition_HasRoute))); + cond = new RouteCondition(routePoints); + } + else if (graph.isInstanceOf(resource, ES.AggregateCondition)) { + boolean isDisjunction = graph.isInstanceOf(resource, ES.Disjunction); + Collection conditionResources = graph.getObjects(resource, ES.HasSubcondition); + List conditions = new ArrayList<>(conditionResources.size()); + for (Resource c : conditionResources) { + conditions.add(buildCondition(graph, c)); + } + Type type; + if (graph.isInstanceOf(resource, ES.Conjunction)) + type = AggregateCondition.Type.CONJUNCTION; + else if (graph.isInstanceOf(resource, ES.Negation)) + type = AggregateCondition.Type.NEGATION; + else if (graph.isInstanceOf(resource, ES.Disjunction)) + type = AggregateCondition.Type.DISJUNCTION; + else + throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource))); + + cond = new AggregateCondition(type, conditions); + } + else { + throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource))); + } + + cond.isInverse = graph.hasStatement(resource, ES.Condition_IsInverse, resource); + return cond; + } + + private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException { + if (graph.isInstanceOf(r, ES.Selection)) { + String exp = "select " + buildExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + + " from " + buildExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator)); + + Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition); + return cond != null ? exp + " where {" + buildExpression(graph, cond) + "}" : exp; + } + else if (graph.isInstanceOf(r, ES.Condition)) { + if (graph.isInstanceOf(r, ES.PropertyCondition)) { + return buildPropertyConditionExpression(graph, r); + } + else if (graph.isInstanceOf(r, ES.RegionCondition)) { + Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion); + String name = graph.getRelatedValue(region, L0.HasLabel); + return "in region " + name; + } + else if (graph.isInstanceOf(r, ES.RouteCondition)) { + Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute); + String name = graph.getRelatedValue(route, L0.HasLabel); + return "in route " + name; + } + else if (graph.isInstanceOf(r, ES.AggregateCondition)) { + String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or "; + List exps = new ArrayList<>(); + Collection objects = graph.getObjects(r, ES.HasSubcondition); + for (Resource c : objects) { + String exp = buildExpression(graph, c); + exps.add(objects.size() > 1 ? "{" + exp + "}" : exp); + } + String result = String.join(op, exps); + if (graph.isInstanceOf(r, ES.Negation)) + result = "not {" + result + "}"; + return result; + } + else { + throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + } + else if (graph.isInstanceOf(r, ES.Selector)) { + if (graph.isInstanceOf(r, ES.Selector_All)) { + return "all"; + } + else if (graph.isInstanceOf(r, ES.PropertySelector)) { + String op; + if (graph.isInstanceOf(r, ES.Selector_NLowest)) + op = "bottom"; + else if (graph.isInstanceOf(r, ES.Selector_NHighest)) + op = "top"; + else + throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + + String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName); + Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount); + return op + " " + count + " of " + name; + } + else { + throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + } + else if (graph.isInstanceOf(r, ES.Generator)) { + if (graph.isInstanceOf(r, ES.Generator_Model)) { + return "model"; + } + else if (graph.isInstanceOf(r, ES.Generator_Diagram)) { + return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\""; + } + else if (graph.isInstanceOf(r, ES.Generator_Explicit)) { + return ""; + } + else { + throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + } + else { + throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + } + + private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException { + String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName); + Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit); + Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit); + if (lowerLimit == null && upperLimit == null) { + return "has property " + propertyName; + } + else { + StringBuilder result = new StringBuilder(); + if (lowerLimit != null) { + result.append(lowerLimit); + result.append(" < "); + } + result.append(propertyName); + if (upperLimit != null) { + result.append(" < "); + result.append(upperLimit); + } + return result.toString(); + } + } + + private static Collection elementsOfDiagram(ReadGraph graph, Resource diagram) throws ServiceException { + Collection elements = graph.getObjects(diagram, L0.ConsistsOf); + Collection result = new ArrayList<>(); + for (Resource r : elements) + if (graph.isInstanceOf(r, DIA.Element)) + result .add(r); + return result; + } + + private void buildSelection(ReadGraph graph) throws DatabaseException { + Resource selector = graph.getSingleObject(resource, ES.Selection_HasSelector); + Resource generator = graph.getSingleObject(resource, ES.Selection_HasGenerator); + Resource condition = graph.getPossibleObject(resource, ES.Selection_HasCondition); + + this.selector = buildSelector(graph, selector); + this.generator = buildGenerator(graph, generator); + this.condition = buildCondition(graph, condition); + } + + private Collection filterElementsFrom(ReadGraph graph, Collection elements) throws DatabaseException { + if (condition == null) return elements; + + ArrayList result = new ArrayList<>(); + for (Resource r : elements) { + if (condition.match(graph, r)) + result.add(r); + } + + return result; + } + + private Collection gather(ReadGraph graph, Resource model) throws DatabaseException { + return selector.select(graph, filterElementsFrom(graph, generator.generate(graph, model))); + } + + static Variable getVariableForElement(ReadGraph graph, Resource element) throws DatabaseException { + Resource component = graph.getPossibleObject(element, MOD.ElementToComponent); + if (component != null) { + Variable var = Variables.getVariable(graph, component); + RVI realRvi = var.getRVI(graph); + + for (Resource activeModel : graph.syncRequest(new ActiveModels(Simantics.getProjectResource()))) { + for (Variable run : graph.syncRequest(new ActiveRuns(activeModel))) { + Variable v = realRvi.resolvePossible(graph, run); + if (v != null) { + return v; + } + } + Variable configuration = Variables.getPossibleConfigurationContext(graph, activeModel); + if (configuration != null) { + Variable v = realRvi.resolvePossible(graph, configuration); + if (v != null) { + return v; + } + } + } + } + + return null; + } + + static Double getPropertyValue(ReadGraph graph, Resource element, String propertyName) { + try { + Variable v = getVariableForElement(graph, element); + if (v != null) { + Number value = v.getPossiblePropertyValue(graph, propertyName); + if (value != null) + return value.doubleValue(); + } + } + catch (DatabaseException e) { + } + + return null; + } + + // Generators + + static abstract class Generator { + abstract Collection generate(ReadGraph graph, Resource model) throws DatabaseException; + } + + static class ModelGenerator extends Generator { + @Override + Collection generate(ReadGraph graph, Resource model) throws DatabaseException { + Resource conf = graph.syncRequest(new Configuration(model)); + + ArrayList result = new ArrayList<>(); + + // Iterate over diagrams + for (Resource comp : graph.getObjects(conf, L0.ConsistsOf)) { + if (!graph.isInstanceOf(comp, STR.Composite)) continue; + + Resource diagram = graph.getPossibleObject(comp, MOD.CompositeToDiagram); + if (diagram != null) result.addAll(elementsOfDiagram(graph, diagram)); + } + + return result; + } + } + + static class DiagramGenerator extends Generator { + Resource diagram; + + public DiagramGenerator(Resource diagram) { + this.diagram = diagram; + } + + @Override + Collection generate(ReadGraph graph, Resource model) throws DatabaseException { + return elementsOfDiagram(graph, diagram); + } + } + + static class ExplicitGenerator extends Generator { + public ExplicitGenerator(Collection elements) { + this.elements = elements; + } + + Collection elements; + + @Override + Collection generate(ReadGraph graph, Resource model) { + return elements; + } + } + + // Selectors + + static abstract class Selector { + abstract Collection select(ReadGraph graph, Collection elements); + } + + static class All extends Selector { + public All() { + } + + @Override + Collection select(ReadGraph graph, Collection elements) { + return elements; + } + } + + static class PropertySelector extends Selector { + public PropertySelector(boolean smallest, String propertyName, int resultCount) { + this.smallest = smallest; + this.propertyName = propertyName; + this.resultCount = resultCount; + } + + boolean smallest; + String propertyName; + int resultCount; + + @SuppressWarnings("unchecked") + @Override + Collection select(ReadGraph graph, Collection elements) { + List result = new ArrayList<>(elements); + List result2 = Lists.map(new FunctionImpl1() { + @Override + public Tuple2 apply(Resource r) { + return new Tuple2(r, getPropertyValue(graph, r, propertyName)); + } + }, result); + + result2 = Lists.filter(new FunctionImpl1() { + @Override + public Boolean apply(Tuple2 t) { + return t.c1 != null; + } + }, result2); + + result2.sort((t1, t2) -> smallest ? Double.compare((Double) t1.c1, (Double) t2.c1) : Double.compare((Double) t2.c1, (Double) t1.c1)); + + result2 = result2.subList(0, resultCount); + + result = Lists.map(new FunctionImpl1() { + @Override + public Resource apply(Tuple2 p0) { + return (Resource) p0.c0; + } + }, result2); + + return result; + } + } + + // Conditions + + static abstract class Condition { + boolean isInverse; + abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException; + } + + static class PropertyCondition extends Condition { + public PropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) { + super(); + this.propertyName = propertyName; + this.lowerLimit = lowerLimit; + this.upperLimit = upperLimit; + } + + String propertyName; + Double lowerLimit; + Double upperLimit; + + @Override + boolean match(ReadGraph graph, Resource r) { + Double value = getPropertyValue(graph, r, propertyName); + return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit); + } + } + + static class RegionCondition extends Condition { + public RegionCondition(double[] region) { + super(); + this.region = region; + + Path2D path = new Path2D.Double(); + double startX = region[0]; + double startY = region[1]; + path.moveTo(startX, startY); + for (int i = 2; i < region.length; i+=2) + path.lineTo(region[i], region[i+1]); + path.closePath(); + + this.path = path; + } + + double[] region; + Path2D path; + + @Override + boolean match(ReadGraph graph, Resource r) throws DatabaseException { + double[] transform = graph.getRelatedValue(r, DIA.HasTransform); + double x = transform[4]; + double y = transform[5]; + return path.contains(x, y); + } + } + + static class RouteCondition extends Condition { + public RouteCondition(Set routePoints) { + super(); + this.routePoints = routePoints; + } + + Set routePoints; + + @Override + boolean match(ReadGraph graph, Resource r) throws DatabaseException { + return routePoints.contains(r); + } + } + + static class DiagramCondition extends Condition { + public DiagramCondition(Resource diagram) { + super(); + this.diagram = diagram; + } + + Resource diagram; + + @Override + boolean match(ReadGraph graph, Resource r) throws DatabaseException { + return graph.getSingleObject(r, L0.PartOf).equals(diagram); + } + } + + static class AggregateCondition extends Condition { + static enum Type { DISJUNCTION, CONJUNCTION, NEGATION }; + + public AggregateCondition(Type type, List conditions) { + super(); + this.type = type; + this.conditions = conditions; + } + + Type type; + List conditions; + + @Override + boolean match(ReadGraph graph, Resource r) throws DatabaseException { + switch (type) { + case DISJUNCTION: + for (Condition c : conditions) + if (c.match(graph, r)) return true; + return false; + case CONJUNCTION: + for (Condition c : conditions) + if (!c.match(graph, r)) return false; + return true; + case NEGATION: + for (Condition c : conditions) + if (c.match(graph, r)) return false; + return true; + default: + // Should not happen + throw new IllegalArgumentException("Unknown aggregate condition type " + type); + } + } + } + + public static class ElementSelectorQuery extends ResourceRead { + public ElementSelectorQuery(Resource resource) { + super(resource); + } + + @Override + public ElementSelector perform(ReadGraph graph) throws DatabaseException { + return new ElementSelector(graph, resource); + } + } +} diff --git a/pom.xml b/pom.xml index 65e92a3b..50cb2866 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,7 @@ org.simantics.district.route org.simantics.district.route.ontology org.simantics.district.route.ui + org.simantics.district.selection org.simantics.maps.server org.simantics.maps.server.ui -- 2.47.1