Okay, this answer will be quite a long one since there are multiple things combining into one that makes it all break down in the end. I will try to tackle each issue one by one.
Issue #1: @ Metadata is not inherited according to SysMLv2 spec
The SysMLv2 spec says that metadata is an annotation (similar to doc and comment elements). Annotations are not inherited through specializations or defined by or other relationships. For example
part def A { // This element will have the @MyMetadata on it
@MyMetadata;
}
part b : A; // This element will not have the @MyMetadata even though its type has it.
This essentially means, that if you want to filter a view according to metadata, you need to attach the metadata tags on the part softwareItemN in each place it is being used, not on the part def SoftwareItemN. Of course, this means that these metadata tags are not really useful in your use case, where you want to define a type once and reuse it in many different parts of the model.
Issue #2: What about SemanticMetadata??
While it is true that SemanticMetadata (example below) creates implicit specializations when used on an element, filtering by these implicit specializations would require filters to work on the model-level and not metamodel-level. As mentioned in the previous reply, this is not supported now by the spec and we are looking for solutions.
action def UserAction;
action userActions : UserAction[*] nonunique;
metadata def CommandMetadata :> SemanticMetadata {
// The meta-cast operation "userAction meta SysML::Usage" has
// type Usage, which conforms to the type KermL::Type of baseType.
// Since userActions is an ActionUsage, the expression evaluates
// at model level to a value of type SysML::ActionUsage.
:>> baseType = userActions meta SysML::Usage;
}
// Save implicitly subclassifies UserAction
// (which is the definition of userActions).
action def Save {
@CommandMetadata;
}
// previousAction implicitly subsets userActions.
action previousAction[1] {
@CommandMetadata;
}
Issue #3: Okay, I want to create explicit metadata on each softwareItemN usage
view nodeRuntimeView {
expose ComputingNodes::ComputingNode1;
filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}
Above is the way you have currently defined your view. However, according to the expose statement, you are saying “Only expose the element ComputingNode1 without its children”. This means that when we apply the filter defined, we have no elements to show, as ComputingNode1 does not have a Metadata tag.
A way to fix it would be to do the following:
view nodeRuntimeView {
expose ComputingNodes::ComputingNode1::*;
filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}
This would expose ComputingNode1s children: softwareItem1, softwareItem2, port1, port2, and the interface. However, it would not expose the ComputingNode1 itself. After applying the filter, we would be left with softwareItem1 and softwareItem2 only.
If you want ComputingNode1 to show up as well, you would need to do something like:
view nodeRuntimeView {
expose ComputingNodes::ComputingNode1;
expose ComputingNodes::ComputingNode1::*;
filter (as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process;
}
However, in this case, the filter would apply to both exposes, and ComputingNode1 would be filtered out. The correct way would be to do inline filters:
view nodeRuntimeView {
expose ComputingNodes::ComputingNode1; // Leave this without a filter
expose ComputingNodes::ComputingNode1::*[(as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process]; // Apply the filter only on ComputingNode1's children
}
However, Syside 0.8.7 does not yet support this “inline” filtering. Support of this is scheduled for 0.9.0 release, which is currently going through internal QA and preparation for release stages (such as writing documentation similar to what I did above).
Post Scriptum
expose works similarly to import, therefore you can use the same syntax to specify what you want to expose to the view:
expose ComputingNodes::ComputingNode1; // Expose only 'ComputingNode1'
expose ComputingNodes::ComputingNode1::*; // Expose direct children of 'ComputingNode1'
expose ComputingNodes::ComputingNode1::**; // Expose children of 'ComputingNode1' recursively
expose ComputingNodes::ComputingNode1::*::**; // This is essentially a combination of 'ComputingNode1' and 'ComputingNode1::**'
Multiple exposes in a view definition also “stack” as seen in the issue #3 solution.
You can also quickly play around and see what elements are exposed in your view if you use Automator with the following script. It will print out qualified names of all exposed (but not yet filtered) elements.
import syside
model, _ = syside.load_model([<paths_to_your_model_files>])
for view in model.nodes(syside.ViewUsage):
if view.name == <your_view_usage_name>:
for element in view.exposed_elements.collect():
print(element)
Preview of 0.9.0's inline filters
Here I am also including an adapted version of your SysML model and a Python script that correctly filters out the elements to show only libraries or only processes with the ComputingNode1 preserved.
package Example {
package Ontology {
enum def SoftwareItemType {
TBD;
software_process;
software_library;
}
metadata def SoftwareItem {
attribute id : ScalarValues::String default "TBD";
attribute type : SoftwareItemType default SoftwareItemType::TBD;
}
}
package SoftwareItems {
part def SoftwareItem1 {
part softwareItem3 : SoftwareItem3 {
@Ontology::SoftwareItem {
id = "3";
type = Ontology::SoftwareItemType::software_library;
}
}
part softwareItem4 : SoftwareItem4 {
@Ontology::SoftwareItem {
id = "4";
type = Ontology::SoftwareItemType::software_library;
}
}
port port1 : SoftwareInterfaces::Port1;
port port2 : SoftwareInterfaces::Port2;
}
part def SoftwareItem2 {
part softwareItem3 : SoftwareItem3 {
@Ontology::SoftwareItem {
id = "3";
type = Ontology::SoftwareItemType::software_library;
}
}
port port2 : SoftwareInterfaces::Port2;
port port3 : SoftwareInterfaces::Port3;
}
part def SoftwareItem3 {
part softwareItem4 : SoftwareItem4 {
@Ontology::SoftwareItem {
id = "4";
type = Ontology::SoftwareItemType::software_library;
}
}
}
part def SoftwareItem4 {
part softwareItem3 : SoftwareItem3 {
@Ontology::SoftwareItem {
id = "3";
type = Ontology::SoftwareItemType::software_library;
}
}
}
}
package SoftwareInterfaces {
port def Port1;
port def Port2;
port def Port3;
}
package ComputingNodes {
part def ComputingNode1 {
part softwareItem1 : SoftwareItems::SoftwareItem1 {
@Ontology::SoftwareItem {
id = "1";
type = Ontology::SoftwareItemType::software_process;
}
}
part softwareItem2 : SoftwareItems::SoftwareItem2 {
@Ontology::SoftwareItem {
id = "2";
type = Ontology::SoftwareItemType::software_process;
}
}
port port1 : SoftwareInterfaces::Port1;
port port3 : SoftwareInterfaces::Port3;
binding bind port1 = softwareItem1.port1;
binding bind softwareItem2.port3 = port3;
interface connect softwareItem1.port2 to softwareItem2.port2;
}
}
package MyViews {
view process_view {
expose ComputingNodes::ComputingNode1;
expose ComputingNodes::ComputingNode1::**[
(as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_process
];
}
view library_view {
expose ComputingNodes::ComputingNode1;
expose ComputingNodes::ComputingNode1::**[
(as Ontology::SoftwareItem).type == Ontology::SoftwareItemType::software_library
];
}
}
}
Python script:
import syside
def filtered(
element: syside.ViewUsage,
compiler: syside.Compiler,
lib: syside.Stdlib | None = None,
) -> list[syside.Element]:
filter = syside.CompilerFilter(
compiler=compiler, lib=lib or syside.Environment.get_default().lib
)
return element.exposed_elements.with_import_filter(filter).collect()
model, _ = syside.load_model(["model.sysml"])
lib = model.lib
compiler = syside.Compiler()
for view in model.nodes(syside.ViewUsage):
if view.name == "process_view":
print("Process View:")
filtered_elements = filtered(view, compiler, lib)
for element in filtered_elements:
print(element)
elif view.name == "library_view":
print("Library View:")
filtered_elements = filtered(view, compiler, lib)
for element in filtered_elements:
print(element)
Output:
Process View:
Example::ComputingNodes::ComputingNode1
Example::ComputingNodes::ComputingNode1::softwareItem1
Example::ComputingNodes::ComputingNode1::softwareItem2
Library View:
Example::ComputingNodes::ComputingNode1
Example::SoftwareItems::SoftwareItem1::softwareItem3
Example::SoftwareItems::SoftwareItem1::softwareItem4
Example::SoftwareItems::SoftwareItem2::softwareItem3