Here is my model (minimized and obfuscated):
package P1 {
part A {
out port o;
}
}
package P2 {
part B {
in port i;
connect P1::A.o to i;
}
}
I want to use SysIDE Automator to extract the ends of the connection in part B as Python strings, something like (["P1", "A", "o"], ["i"])
would be ideal. The .declared_ends
attribute sounds like exactly what I want, but I’m not sure how to get strings “P1”, “A” etc. from it. Here’s my attempt so far:
#!/usr/bin/env python3.13
import syside
m,_ = syside.load_model(["test.sysml"])
p2_matches = [n for n in m.nodes(syside.Package) if n.name == "P2"]
assert len(p2_matches) == 1
p2 = p2_matches[0]
partB = p2["B"]
assert(isinstance(partB, syside.PartUsage))
partB_connections = [n for n in partB.children.elements if isinstance(n, syside.ConnectionUsage)]
assert len(partB_connections) == 1
partB_connection = partB_connections[0]
print(partB_connection.declared_ends.elements)
# prints [P2::B::(anonymous ConnectionUsage)::source, P2::B::(anonymous ConnectionUsage)::target]
A hacky regex works to get the connection source and target names as strings (feeding the ConnectionUsage node into syside.pprint()
and parsing the result with a regex) but that’s not a great solution and I was wondering if there is a robust way to do this by walking the syntax tree?
Hi Ricardas!
You were on the right track, but stopped a bit too early! Here is the example script that can get you the ports that are connected. Afterwards, you can do what you want with those ports, not just print their names!
import syside
def main(model: syside.Model):
# Since the model only has one user document, we will only look into that.
with model.user_docs[0].lock() as locked:
# Let's get the part B from package 2
package2 = locked.root_node.get_member("P2")
assert isinstance(package2, syside.Package)
partB = package2.get_member("B")
assert isinstance(partB, syside.PartUsage)
# Now let's extract the connection
partB_connections = [
n for n in partB.children.elements if isinstance(n, syside.ConnectionUsage)
]
assert len(partB_connections) == 1
partB_connection = partB_connections[0]
# Do some magic with the connection
connection_ends: list[syside.Feature] = []
for end in partB_connection.declared_ends:
connection_ends.append(end[1]) # Only take the feature, not the membership
assert len(connection_ends) == 2
connection_end_1 = connection_ends[0]
connection_end_2 = connection_ends[1]
assert isinstance(connection_end_1, syside.Feature)
assert isinstance(connection_end_2, syside.Feature)
# Now, the actual targets of the features can be found as "referenced_feature_target"
# in the features.
port_1 = connection_end_1.referenced_feature_target
port_2 = connection_end_2.referenced_feature_target
assert isinstance(port_1, syside.PortUsage)
assert isinstance(port_2, syside.PortUsage)
# Let's print their names
print(port_1)
print(port_2)
if __name__ == "__main__":
model, diagnostics = syside.try_load_model(["test.sysml"])
if diagnostics.contains_errors(warnings_as_errors=True):
print(str(diagnostics))
exit(1)
main(model)
Alternatively, instead of using declared_ends
, we can also look at source
and target
features:
source_feature = partB_connection.get_member("source")
target_feature = partB_connection.get_member("target")
assert isinstance(source_feature, syside.Feature)
assert isinstance(target_feature, syside.Feature)
# Now, the actual targets of the features can be found as "referenced_feature_target"
# in the features.
source_port = source_feature.referenced_feature_target
target_port = target_feature.referenced_feature_target
assert isinstance(source_port, syside.PortUsage)
assert isinstance(target_port, syside.PortUsage)
# Let's print their names
print(source_port)
print(target_port)
I hope this helps you on your SysMLv2 adventures.
3 Likes
Looking at .referenced_feature_target.qualified_name
of the source/target feature gave me exactly what I wanted, thanks!
After some testing I’m still having issues in the actual model. Here’s another simplified example:
package P1 {
part def PD {
out port o;
}
part A defined by PD {}
}
package P2 {
part B {
in port i;
connect P1::A.o to i;
}
}
In this case the .referenced_feature_target
of the connection source points to P1::PD::o
, so this approach fails to tell me that out port o
of specifically part P1::A
is being connected. I’m not very familiar with the underlying concepts of SysMLv2, so correct me if I’m wrong, but my impression is that part A
inherits all attributes of part def PD
, so it makes sense to talk about port P1::A::o
being connected to port P2::B::i
The problem is that A
does not have an actual model element for the inherited port that can be linked to.
Therefore you need either to redefine the port in A
or track the fact that the feature chain is “inside A
” separately.
We plan to work on this as part of the work imlroving the querying/evaluation API, but are not quite there yet.
2 Likes