Move Element

This example demonstrates how to move an element from one parent to another in a SysML v2 model using Syside Automator.

The model contains a sensor system with two packages: Processing and Sensing. A processor part usage is initially placed in Sensing but logically belongs in Processing. The script extracts it from Sensing and appends it to Processing, then prints the model before and after the move.

SensorSystem
├── Processing
│   ├── SignalProcessor
│   │                  <─ ─ ┐
│   └── DataLogger          ┆
└── Sensing                 ┆
    ├── TemperatureSensor   ┆ Move here
    ├── PressureSensor      ┆
    └── processor       ─ ─ ┘

Concepts Used

  • extract_element removes an element from its current parent without clearing its subtree, returning it as an orphan. The element can then be inserted elsewhere in the model tree.

    Warning

    extract_element can only move elements within the same document. Attempting to insert an extracted element into a different document will fail.

  • children.append inserts the orphaned element under a new parent using the specified relationship type.

  • After modification, pprint serializes the updated model back to SysML text.

Warning

It is the caller’s responsibility to ensure that an extracted element is not leaked (i.e., it must be inserted into a new parent before the lock is released).

Example Model

package SensorSystem {
    package Processing {
        part def SignalProcessor;
        part def DataLogger;
    }

    package Sensing {
        part def TemperatureSensor;
        part def PressureSensor;

        // SignalProcessor belongs here conceptually, not in Processing
        part processor : Processing::SignalProcessor;
    }
}

Example Script

import pathlib
import syside

EXAMPLE_DIR = pathlib.Path(__file__).parent
MODEL_FILE_PATH = EXAMPLE_DIR / "example_model.sysml"


def print_package_members(package: syside.Package, indent: int = 0) -> None:
    """Print all direct children of a package by name."""
    prefix = "  " * indent

    for member in package.children.elements:
        name = member.declared_name or "<unnamed>"
        print(f"{prefix}- {name} ({type(member).__name__})")


def main() -> None:
    (model, diagnostics) = syside.load_model([MODEL_FILE_PATH])
    assert not diagnostics.contains_errors(warnings_as_errors=True)

    with model.user_docs[0].lock() as doc:
        root = doc.root_node

        # Locate the two packages and the element to move.
        # next() with a generator expression is a common Python idiom for finding
        # the first element that matches a condition. It raises StopIteration if
        # no match is found, which is appropriate here since the model is known.
        sensor_system = next(
            e
            for e in root.children.elements
            if isinstance(e, syside.Package)
            and e.declared_name == "SensorSystem"
        )
        processing_pkg = next(
            e
            for e in sensor_system.children.elements
            if isinstance(e, syside.Package) and e.declared_name == "Processing"
        )
        sensing_pkg = next(
            e
            for e in sensor_system.children.elements
            if isinstance(e, syside.Package) and e.declared_name == "Sensing"
        )
        processor_part = next(
            e
            for e in sensing_pkg.children.elements
            if isinstance(e, syside.PartUsage)
            and e.declared_name == "processor"
        )

        print("Before move:")
        print("  Processing:")
        print_package_members(processing_pkg, indent=2)
        print("  Sensing:")
        print_package_members(sensing_pkg, indent=2)

        # Extract the element from its current parent without clearing its subtree,
        # then append it to the new parent.
        sensing_pkg.children.extract_element(processor_part)
        processing_pkg.children.append(syside.OwningMembership, processor_part)

        print("\nAfter move:")
        print("  Processing:")
        print_package_members(processing_pkg, indent=2)
        print("  Sensing:")
        print_package_members(sensing_pkg, indent=2)

        # Serialize the updated model to SysML text
        printer = syside.ModelPrinter.sysml()
        cfg = syside.PrinterConfig(line_width=80, tab_width=4)
        print("\nUpdated model:")
        print(syside.pprint(root, printer, cfg))


if __name__ == "__main__":
    main()

Output

Before move:
  Processing:
    - SignalProcessor (PartDefinition)
    - DataLogger (PartDefinition)
  Sensing:
    - TemperatureSensor (PartDefinition)
    - PressureSensor (PartDefinition)
    - processor (PartUsage)

After move:
  Processing:
    - SignalProcessor (PartDefinition)
    - DataLogger (PartDefinition)
    - processor (PartUsage)
  Sensing:
    - TemperatureSensor (PartDefinition)
    - PressureSensor (PartDefinition)

Updated model:
package SensorSystem {
    package Processing {
        part def SignalProcessor;
        part def DataLogger;
        part processor : Processing::SignalProcessor;
    }

    package Sensing {
        part def TemperatureSensor;
        part def PressureSensor;
    }
}

Download

Download this example here.