Source code for pySimBlocks.gui.services.group_boundary_service

# ******************************************************************************
#                                  pySimBlocks
#                     Copyright (c) 2026 Université de Lille & INRIA
# ******************************************************************************

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from pySimBlocks.gui.models.port_instance import PortInstance
from pySimBlocks.gui.models.visual_group import BoundaryPort, VisualGroup

if TYPE_CHECKING:
    from pySimBlocks.gui.models.connection_instance import ConnectionInstance
    from pySimBlocks.gui.models.project_state import ProjectState


[docs] def port_key(port: PortInstance) -> str: """Build a stable key for a simulation port.""" return f"{port.block.uid}:{port.name}"
[docs] def parse_port_key(key: str) -> tuple[str, str] | None: if not key or ":" not in key: return None block_uid, port_name = key.split(":", 1) return block_uid, port_name
[docs] def find_port(state: ProjectState, key: str) -> PortInstance | None: parsed = parse_port_key(key) if parsed is None: return None block_uid, port_name = parsed for block in state.blocks: if block.uid != block_uid: continue for port in block.ports: if port.name == port_name: return port return None
[docs] def is_manual(boundary: BoundaryPort) -> bool: return boundary.origin == "manual"
[docs] def is_complete(boundary: BoundaryPort) -> bool: return bool(boundary.linked_connection_uid)
[docs] @dataclass class BoundaryWiringState: """Snapshot of a manual boundary wiring state.""" linked_port_uid: str = "" external_port_uid: str = "" linked_connection_uid: str = ""
[docs] def capture_wiring_state(boundary: BoundaryPort) -> BoundaryWiringState: return BoundaryWiringState( linked_port_uid=boundary.linked_port_uid, external_port_uid=boundary.external_port_uid, linked_connection_uid=boundary.linked_connection_uid, )
[docs] def apply_wiring_state(boundary: BoundaryPort, state: BoundaryWiringState) -> None: boundary.linked_port_uid = state.linked_port_uid boundary.external_port_uid = state.external_port_uid boundary.linked_connection_uid = state.linked_connection_uid
[docs] def connection_endpoints( state: ProjectState, boundary: BoundaryPort, ) -> tuple[PortInstance, PortInstance] | None: """Return src/dst simulation ports when a manual boundary is ready to complete.""" member_port = find_port(state, boundary.linked_port_uid) external_port = find_port(state, boundary.external_port_uid) if member_port is None or external_port is None: return None if boundary.direction == "input": return external_port, member_port return member_port, external_port
[docs] def can_complete(state: ProjectState, boundary: BoundaryPort) -> bool: if is_complete(boundary): return False if boundary.origin not in ("manual", "auto"): return False if not boundary.linked_port_uid or not boundary.external_port_uid: return False endpoints = connection_endpoints(state, boundary) if endpoints is None: return False src_port, dst_port = endpoints if not src_port.is_compatible(dst_port): return False return True
[docs] def connection_key(connection: ConnectionInstance) -> str: """Build a stable key for one diagram connection.""" return ( f"{connection.src_block().uid}:{connection.src_port.name}->" f"{connection.dst_block().uid}:{connection.dst_port.name}" )
[docs] def find_connection_for_boundary( state: ProjectState, boundary: BoundaryPort, ) -> ConnectionInstance | None: endpoints = connection_endpoints(state, boundary) if endpoints is not None: src_port, dst_port = endpoints for connection in state.connections: if connection.src_port is src_port and connection.dst_port is dst_port: return connection if boundary.linked_connection_uid: for connection in state.connections: if connection_key(connection) == boundary.linked_connection_uid: return connection return None