Read SysML v2 and KerML models from C++17, header-only. Every metaclass of the specification is a
struct over a shared model; is_a<T>() follows the specification's generalizations and as<T>()
converts. Properties are read through getters, get and the specification name
(getOwnedFeature(), getQualifiedName(), and for Booleans getIsAbstract()); operations keep
their specification names (effectiveName()).
A model is read through one of two backends, with the same structs over both:
- the OpenMBEE SysML Toolkit reads your
.sysmland.kermlfiles, resolves names and computes derived properties, through the binding library (a native library, loaded at run time); - a payload is a model exported as full-form interchange JSON by any SysML v2 tool, read in plain C++ (with the bundled nlohmann/json).
A C++17 compiler. Download from the release:
sysml-sdk-cpp-0.1.0.zip, and put itsinclude/on your include path. For the SysML Toolkit, itslib/<target>/holds the binding library for Windows x64, Linux x64, and macOS on Apple silicon and on Intel: copy the one for your platform (sysmlv2_abi.dll,libsysmlv2_abi.soorlibsysmlv2_abi.dylib) beside your executable. Itssysml.library/holds the standard library models: copy that directory beside your executable too.
g++ -std=c++17 -I sysml-sdk-cpp-0.1.0/include read_model.cpp -o read_model # add -ldl on Linux, -static with MinGWMost models refer to the KerML and SysML standard library: ScalarValues::Real, ISQ::mass, and
implicitly Parts::parts and the like. Get it once, at the start of your program, in the form your
backend reads:
#include <sysml/library.hpp>
std::string library_dir = sysml::standard_library(); // the library's models, for the SysML Toolkit
std::string library_json = sysml::standard_library_json(); // the library as JSON, for payloadsstandard_library()finds thesysml.librarydirectory beside your executable, or the one the environment variableSYSML_LIBRARY_DIRnames.standard_library_json()is the fileSYSML_LIBRARY_JSONnames, else the copy in your user cache that the other SDK languages download (SYSML_CACHE_DIRmoves it). C++ downloads nothing: get it once with another SDK language, or downloadsysml_library-0.1.0.zipfrom the release, unpack it, and setSYSML_LIBRARY_JSONto thesysml.library.full.jsonit holds.- The library is under the Eclipse Public License 2.0: see the LICENSE and NOTICE beside it.
Copy this, and put in your file names:
#include <iostream>
#include <sysml/sdk.hpp>
#include <sysml/classes.g.hpp>
#include <sysml/library.hpp>
#include <sysml/toolkit.hpp>
using namespace sysml;
int main() {
Model model = Model::from_backend(ToolkitBackend::open(
{"model.sysml"}, // your model: one or more .sysml / .kerml files
{}, // or in-memory sources: {file name, text} pairs
standard_library())); // the standard library
// ... your code here ...
return 0;
}ToolkitBackend::open({"a.sysml", "b.sysml"})opens files without the standard library,ToolkitBackend::open_sources({{"model.sysml", text}})text you hold in memory.- To load the binding library from elsewhere, point the environment variable
SYSMLV2_ABIat it, or name it in code: passstd::make_shared<ToolkitBackend::Library>(path)as the last argument ofopen. - Without the standard library the model loads faster, but its references into it do not resolve,
and reading one throws
unresolved_reference.
A payload is the full-form interchange JSON of a model: one array of elements, each an object with
its @id, its @type and its properties. The SysML Toolkit writes one with
sysmlv2 convert model.sysml --to full-json --lib sysml.library > model.json, and so can other SysML
v2 tools. Its references into the standard library resolve against the library JSON:
#include <fstream>
#include <iostream>
#include <sstream>
#include <sysml/sdk.hpp>
#include <sysml/classes.g.hpp>
#include <sysml/library.hpp>
using namespace sysml;
static std::string read_file(const std::string& path) {
std::ifstream in(path, std::ios::binary);
if (!in) throw std::runtime_error("cannot open " + path);
std::stringstream text;
text << in.rdbuf();
return text.str();
}
int main() {
auto library = PayloadLibrary::parse(read_file(standard_library_json())); // the standard library; read once, share it
Model model = Model::from_full_json(read_file("model.json"), library); // your export
// ... your code here ...
return 0;
}A payload answers with what its writer wrote and nothing else. Operations need the SysML Toolkit:
on a payload they throw not_implemented_in_toolkit.
The snippets below go where // ... your code here ... is, with either backend. They read the
example model examples/vehicle.sysml; put in your own qualified names.
// An element by its qualified name (an empty optional when there is none), as its metaclass.
auto vehicle = model.resolve("Vehicles::Vehicle").value().as<PartDefinition>();
std::cout << vehicle.getQualifiedName().value() << " is a " << vehicle.metaclass_name() << "\n"; // ... is a PartDefinition
// The top-level packages.
for (const Element& root : model.roots())
for (const Element& member : root.as<Namespace>().getOwnedMember())
std::cout << "top level: " << member.getQualifiedName().value_or("(unnamed)") << "\n";
// Every element of a metaclass, its subclasses included.
for (const PartUsage& part : model.elements_of_type<PartUsage>())
std::cout << "part: " << part.getQualifiedName().value_or("(unnamed)") << " " << part.metaclass_name() << "\n";
// Properties keep their specification names behind get. A list property is a std::vector, a
// single one a std::optional of an element, a std::string, a bool, an integer or a double.
std::cout << vehicle.getDeclaredName().value_or("") << " abstract: " << vehicle.getIsAbstract().value_or(false) << "\n";
for (const Documentation& doc : vehicle.getDocumentation()) std::cout << "doc: " << doc.getBody().value_or("") << "\n";
for (const Feature& feature : vehicle.getOwnedFeature())
std::cout << " feature: " << feature.getDeclaredName().value_or("(unnamed)") << " " << feature.metaclass_name() << "\n";
// The structs follow the specification's generalization hierarchy.
std::cout << vehicle.is_a<Definition>() << vehicle.is_a<Type>() << vehicle.is_a<Usage>() << "\n"; // 110The SysML Toolkit answers a property only where it can derive it completely. Where it cannot
yet, reading the property throws not_implemented_in_toolkit with the SysML Toolkit's reason,
instead of returning a value that might be wrong; nothing is silently empty. Catch it where your
code can go on.
The relationships a derived property is computed from are part of the model, and the SysML Toolkit
answers them: a feature's types are the getType() of its getOwnedTyping() relationships, a
definition's supertypes the getSuperclassifier() of its getOwnedSubclassification()s, what a
feature subsets the getSubsettedFeature() of its getOwnedSubsetting()s.
auto wheels = model.resolve("Vehicles::Vehicle::wheels").value().as<PartUsage>();
std::vector<Type> types;
try {
types = wheels.getType(); // derived: the SysML Toolkit may refuse it
} catch (const not_implemented_in_toolkit& refusal) {
std::cout << "not answered: " << refusal.what() << "\n";
for (const FeatureTyping& typing : wheels.getOwnedTyping()) // what the model states
types.push_back(typing.getType().value());
}
for (const Type& t : types) std::cout << "wheels: " << t.getQualifiedName().value_or("?") << "\n"; // Vehicles::Wheel
auto sports = model.resolve("Vehicles::SportsCar").value().as<PartDefinition>();
for (const Subclassification& s : sports.getOwnedSubclassification())
std::cout << "SportsCar specializes " << s.getSuperclassifier().value().getQualifiedName().value_or("?") << "\n";
try {
for (const Feature& f : sports.getInheritedFeature())
if (f.getIsLibraryElement() != true) std::cout << "SportsCar inherits " << f.getDeclaredName().value_or("(unnamed)") << "\n";
} catch (const not_implemented_in_toolkit& refusal) {
std::cout << "not answered: " << refusal.what() << "\n";
}A reference that does not resolve throws unresolved_reference, and an element that is no longer
there gone. All derive from sysml::sdk_error, a std::runtime_error.
The specification's operations are member functions under their own names, with the specification's
parameter and result types. The SysML Toolkit has a body for a part of them (effectiveName(),
resolve, resolveGlobal, resolveLocal, visibleMemberships, supertypes, evaluate, ...),
and answers a call only where its evidence for your model is complete; otherwise, and for the
operations it has no body for (isCompatibleWith, specializes, allSupertypes, ...), the call
raises not_implemented_in_toolkit, as a property read does. Whether resolve is answered, for
example, depends on the names in your model: catch the refusal where your code can go on.
std::cout << vehicle.effectiveName().value_or("") << "\n"; // Vehicle
auto membership = model.resolve("Vehicles").value().as<Namespace>().resolve("Vehicle").value(); // Namespace::resolve
std::cout << membership.metaclass_name() << " " << membership.getMemberElement().value().getQualifiedName().value_or("?") << "\n";The SysML Toolkit writes the model as full-form interchange JSON, with the inherited and imported
members filled in (full_json(false) writes each element's own side only, as the command-line
tool does). full_json belongs to the backend, so keep it before handing it to the model:
auto backend = ToolkitBackend::open({"model.sysml"}, {}, standard_library());
std::cout << backend->full_json().size() << " bytes of JSON\n"; // write it where you need it
Model exported = Model::from_backend(std::move(backend)); // the same session, as a model- tutorial.cpp, a runnable walk through the example model with both backends; its header has the commands.
- ../queries/: parametric queries over whole models (reachable states, bills of materials, connectivity, requirements), the same in every language.
- The names of every class and member are the specification's; see Names for the rules.
- skills/sysml-sdk-cpp teaches a coding agent the same.