We’re putting the final touches on our new badges platform. Badge issuance remains temporarily paused, but all completions are being recorded and will be fulfilled once the platform is live. Thank you for your patience.
Embedded Software

Embedded Software

Introducing PyScadeOne

    • SolutionSolution
      Participant

      Introduction

      With this blog, we are proud to announce the first release of PyScadeOne, the unified library for all scripting needs related to Scade One. It is now available for free on GitHub, with the release of Scade One 2025 R1. So let us take a closer look at what PyScadeOne offers, including an example script to generate an instance tree from a model.



      Photo credit: Godfrey Atima @ Pexels

      About PyScadeOne

      SCADE has always been open and provided Application Programming Interfaces (APIs) to ease integration into any workflow and environment. Ansys application engineers and customers have created many tools and extensions thanks to these APIs. We have started making some of these utilities available on GitHub, to make it easier to for our users to access them.

      Scade One is no different and the goal of PyScadeOne is to take this openness to the next level. PyScadeOne is part of the PyAnsys initiative to provide easy access to Ansys tools from Python. That is why PyScadeOne is delivered as an open-source library on GitHub, that can be easily deployed in any compatible Python environment (it supports Windows and Linux, and any version of Python greater than 3.9). It can be installed like any other Python library, using for instance pip:

      > pip install ansys-scadeone-core
      

      PyScadeOne comes with an extensive user documentation, including examples, at https://scadeone.docs.pyansys.com/, so you can quickly get started with the library.

      Note that PyScadeOne is also delivered as a Python wheel file (.whl) inside the Scade One installation, that can be installed for instance with:

      > pip install pyscadeone/ansys_scadeone-0.6.0+43-py3-none-any.whl
      

      PyScadeOne in the 2025 R1 release

      PyScadeOne offers the following services:

      • Read or generate any data related to Scade One. In 2025 R1, you can already read or generate models, with advanced navigation services, as well as simulation data (that can be used as input or as expected values for testing).
      • Customize or extend Scade One and integrate with 3rd party tools:
        • Generate integration code by accessing information about the generated code (for instance the names of the functions and variables in the generated C code and how they map to the input model). In particular, Scade One 2025 R1 allows to export models as Functional Mock-up Units (FMUs), following the FMI 2.0 standard. This export uses PyScadeOne to automatically generate the required wrapping code
        • Access test results to generate custom reports or export these results to another format.

      PyScadeOne will continue evolving in future versions to provide even more services. For instance, future versions will include a rule checker, to define and verify modeling rules automatically.

      Using PyScadeOne to print an instance tree

      After this quick introduction to PyScadeOne, let us now look at an example of a Python script using this library. Our goal is to print an instance tree from a model: if an operator Op1 uses an instance of operator Op2, then Op2 is a child of Op1 in the instance tree. Here is an example of such tree, based on the QuadFlightControl example delivered with Scade One:



      Such a visualization could be useful for instance in a model report, to better understand the structure of the model.

      Our script will proceed as follows to create the instance tree:

      • Open the project and access the model
      • Compute the list of used operators for any operator in the model
      • Build the instance tree, starting from the chosen root operator
      • Print the tree

      We will now describe each of these steps.

      Accessing the model

      Our first step is to open a project and access the corresponding model. First, we import the PyScadeOne library:

      from ansys.scadeone.core.scadeone import ScadeOne
      

      Then, we load the project (given here as input of the script, using the standard argparse library):

      app = ScadeOne(install_dir=scade_one_install_dir)
      project = app.load_project(args.project)
      project.model.load_all_modules()
      
      Computing the list of used operators

      To build the instance tree, we first traverse the model to compute the list of operators called by each operator. We then use this information to build a tree starting from the root operator we have chosen.

      We are in luck, because one of the examples provided with PyScadeOne is already computing this information. This example uses the Swan model visitor, which is a standard way to traverse an Abstract Syntax Tree (AST) like a Swan model. Remember that Swan is the domain-specific language used for modeling in Scade One, that has been described in previous blogs. A visitor is a class that contains one method for each kind of object in the model. The visitor traverses the model and calls the corresponding methods for each object. Methods can be redefined to implement the desired behavior.

      In our case, we redefine two methods:

      • visit_Operator, which is called for each operator definition, to remember what operator we are currently traversing (in the self._current_op field)
      • visit_PathIdOpCall, which is called for each instance of a named operator. We will add this operator name to the list of operators called by self._current_op in the self._called dictionary.

      Here is the complete code of our visitor:

      class InstanceVisitor(SwanVisitor):
          """Visitor to compute the operators called by all operators in the model"""
      
          def __init__(self) -> None:
              super().__init__()
              self._current_op: swan.Operator = None
              self._called = {}
      
          def get_called(self, op):
              """Returns the list of operators called by 'op'"""
              return self._called.get(op, None)
      
          def visit_Operator(
              self, swan_obj: swan.Operator, owner: Union[swan.Any, None], property: Union[str, None]
          ) -> None:
              # Called when visiting an operator. Sets _current_op to the current operator and continues traversal
              self._current_op = swan_obj
              self._called[swan_obj.get_full_path()] = []
              super().visit_Operator(swan_obj, owner, property)
              self._current_op = None
      
          def visit_PathIdOpCall(
              self, swan_obj: swan.PathIdOpCall, owner: Union[swan.Any, None], property: Union[str, None]
          ) -> None:
              # ignore unknown operators
              if self._current_op is None:
                  return
              name = str(swan_obj.path_id)
              op = self._current_op.body.get_declaration(name)
              if op is None:
                  return
      
              # add called_operator to the list of called operators
              called_operator = cast(swan.Operator, op).get_full_path()
              current_op_name = self._current_op.get_full_path()
              if called_operator not in self._called[current_op_name]:
                  self._called[current_op_name].append(called_operator)
      

      We can then easily apply our visitor to the complete model:

      visitor = InstanceVisitor()
      for module in model.modules:
          visitor.visit(module)
      

      At this point, calling visitor.get_called(op) will return the list of operators called by any operator in the model.

      Building and printing the instance tree

      Now that we have computed the list of used operators, the rest of the code is standard Python, so we won’t go into too much detail. To simplify our task, we leverage the rich ecosystem of Python libraries. We use NetworkX to represent the tree/graph and pydot to print it, using the famous Graphviz tool. That is one of the main strengths of Python: whatever you want to do, there’s always a library to help you.

      For instance, the following function is used to add an operator to the tree, represented as a NetworkX graph, starting from the root operator. Note that we are considering library operators as leaves in the instance tree.

      def add_to_graph(G, op, visitor):
          """Add node `op` to the graph `G`, using info from `visitor`"""
          G.add_node(op)
          called_ops = visitor.get_called(op)
          # ignore called operators for library operators
          if not is_library_operator(op) and called_ops:
              for called_op in called_ops:
                  G.add_edge(op, called_op)
                  add_to_graph(G, called_op, visitor)
      

      Similarly, we have also created a function print_graph that takes as input a graph and an output path, and generates the PNG file using Graphviz (that we won’t show here).

      Running the script

      To be able to run the script, you first must install its dependencies using the included requirements.txt file. You just need to run, for instance in a virtual environment:

      > pip install -r requirements.txt
      

      You also must install the Graphviz tool, that will generate the PNG file from the graph description.

      Note: make sure that the path of your local Graphviz installation matches the one near the beginning of the instance_tree.py script:

      graphviz_path = r'C:\Program Files\Graphviz\bin' # Should be manually set if not already in PATH
      

      We can now apply our script to the QuadFlightControl example, with the QuadFlightControl::QuadFlightControl operator as root, with the following command line:

      > python instance_tree.py "C:\Program Files\ANSYS Inc\v251\Scade One\examples\QuadFlightControl\QuadFlightControl\QuadFlightControl.sproj" "QuadFlightControl::QuadFlightControl"
      

      It generates the following tree:



      Note that we have added the library operators in blue boxes as a bonus. We could get some inspiration from the Graphviz gallery to generate a better-looking tree (and we probably should), but that’s a topic for another day.

      Want to learn more?

      You can download the script from this blog here. And of course, you can install PyScadeOne using your favorite method, like pip, as shown at the beginning of the blog.

      If you want to learn more about PyScadeOne, a webinar will be hosted on April 3, 2025 (link). You may also schedule a live demo of Scade One using this link. Also, if you are a SCADE user, access Scade One Essential with your existing licenses on the Ansys Download Portal.

      PyScadeOne will continue to evolve in future versions, so expect regular blogs about it, demonstrating its capabilities and how it can be used to extend Scade One, for instance to connect it to 3rd party tools.

      Stay tuned for more information about the new features in Scade One 2025 R1, including model-based testing thanks to the test harness concept.

      About the author



      Cédric Pasteur (LinkedIn) is a Senior Product Manager at Ansys. He has been working on Scade One, Ansys’ latest-generation model-based embedded software development product, since its inception. He specializes in programming language theory and its application to safe embedded systems.