Added array ports and junctions

This commit is contained in:
2026-07-20 15:00:06 +02:00
parent a067c85994
commit 495edd42f4
15 changed files with 1686 additions and 72 deletions

View File

@@ -1,7 +1,7 @@
from PySide6.QtCore import QPointF
from PySide6.QtGui import QUndoCommand
from bedit.core.model import Annotation, Component, Connection, Port
from bedit.core.model import Annotation, Component, Connection, Junction, Port
class AddComponentCommand(QUndoCommand):
@@ -76,6 +76,32 @@ class AddConnectionCommand(QUndoCommand):
self.controller._remove_connection(self.owner_id, self.connection.id)
class SplitConnectionCommand(QUndoCommand):
def __init__(
self,
controller,
owner_id: str,
original: Connection,
junction: Junction,
first: Connection,
second: Connection,
) -> None:
super().__init__("Add connection junction")
self.controller, self.owner_id = controller, owner_id
self.original, self.junction = original, junction
self.first, self.second = first, second
def redo(self) -> None:
self.controller._split_connection(
self.owner_id, self.original.id, self.junction, self.first, self.second
)
def undo(self) -> None:
self.controller._restore_split_connection(
self.owner_id, self.original, self.junction.id, self.first.id, self.second.id
)
class AddAnnotationCommand(QUndoCommand):
def __init__(self, controller, owner_id: str, annotation: Annotation) -> None:
super().__init__(f"Draw {annotation.kind}")

View File

@@ -23,6 +23,7 @@ from bedit.gui.controllers.commands import (
RenameInterfacePortCommand,
ReplaceSourceCommand,
RotateComponentsCommand,
SplitConnectionCommand,
)
from bedit.core.model import (
Annotation,
@@ -31,6 +32,7 @@ from bedit.core.model import (
Endpoint,
GraphDocument,
Icon,
Junction,
Parameter,
Port,
clone_component,
@@ -228,6 +230,20 @@ class DocumentController(QObject):
MoveComponentCommand(self, self.active_component_id, component_id, old, new)
)
def move_junction(self, junction_id: str, old: QPointF, new: QPointF) -> None:
if old != new and self.active_component_id is not None:
self.undo_stack.push(
EditGraphItemCommand(
self,
self.active_component_id,
"junction_geometry",
junction_id,
{"x": old.x(), "y": old.y()},
{"x": new.x(), "y": new.y()},
"Move connection junction",
)
)
def rotate_components(self, component_ids: set[str]) -> None:
if self.active_component is None or self.active_component_id is None:
return
@@ -254,6 +270,14 @@ class DocumentController(QObject):
raise ValueError("A connection endpoint no longer exists")
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
if not self.endpoint_accepts_connection(source, "source"):
raise ValueError(
f"Port {source_port.name!r} already has a connection; enable multiple connections first"
)
if not self.endpoint_accepts_connection(target, "target"):
raise ValueError(
f"Port {target_port.name!r} already has a connection; enable multiple connections first"
)
connection = Connection(
str(uuid4()),
source,
@@ -265,6 +289,53 @@ class DocumentController(QObject):
self.undo_stack.push(AddConnectionCommand(self, self.active_component_id, connection))
return connection.id
def split_connection(
self,
connection_id: str,
position: QPointF,
first_waypoints: list[QPointF],
second_waypoints: list[QPointF],
) -> str:
if self.active_component_id is None:
raise ValueError("There is no active graph")
original = self.active_graph.connections.get(connection_id)
if original is None:
raise ValueError("The connection no longer exists")
port_type = self.connection_port_type(original)
junction = Junction(str(uuid4()), position.x(), position.y(), port_type)
first_properties = deepcopy(original.properties)
first_properties["waypoints"] = [
{"x": point.x(), "y": point.y()} for point in first_waypoints
]
second_properties = {
"waypoints": [{"x": point.x(), "y": point.y()} for point in second_waypoints]
}
first = Connection(
str(uuid4()),
original.source,
Endpoint(junction=junction.id),
original.name,
first_properties,
)
second = Connection(
str(uuid4()),
Endpoint(junction=junction.id),
original.target,
"",
second_properties,
)
self.undo_stack.push(
SplitConnectionCommand(
self,
self.active_component_id,
original,
junction,
first,
second,
)
)
return junction.id
def add_annotation(
self,
kind: str,
@@ -405,6 +476,16 @@ class DocumentController(QObject):
owner = self.active_component
if owner is None:
return None
if endpoint.junction is not None:
junction = owner.graph.junctions.get(endpoint.junction)
if junction is None:
return None
return Port(
junction.id,
"Junction",
type=junction.type,
allows_multiple_connections=role == "source",
)
if endpoint.interface is not None:
ports = owner.inputs if role == "source" else owner.outputs
else:
@@ -420,6 +501,17 @@ class DocumentController(QObject):
port = self._port_for_endpoint(connection.source, "source")
return port.type if port is not None else "signal"
def endpoint_accepts_connection(self, endpoint: Endpoint, role: str) -> bool:
port = self._port_for_endpoint(endpoint, role)
if port is None:
return False
if role == "source" or port.allows_multiple_connections:
return True
return not any(
endpoint == (connection.source if role == "source" else connection.target)
for connection in self.active_graph.connections.values()
)
def add_interface_port(self, direction: str, position: QPointF) -> str:
component = self.active_component
if component is None or component.implementation_kind != "graph":
@@ -526,6 +618,12 @@ class DocumentController(QObject):
},
}
if old != new:
candidate = deepcopy(self.document)
candidate_component = candidate.find_component(component.id)
candidate_component.inputs = deepcopy(inputs)
candidate_component.outputs = deepcopy(outputs)
candidate_component.source = deepcopy(new["source"])
candidate.validate()
self.undo_stack.push(EditTextDefinitionCommand(self, component.id, old, new))
def edit_component_appearance(
@@ -849,6 +947,36 @@ class DocumentController(QObject):
self.connectionAdded.emit(connection.id)
self.documentReset.emit()
def _split_connection(
self,
owner_id: str,
original_id: str,
junction: Junction,
first: Connection,
second: Connection,
) -> None:
graph = self._graph_for(owner_id)
graph.connections.pop(original_id, None)
graph.junctions[junction.id] = junction
graph.connections[first.id] = first
graph.connections[second.id] = second
self.documentReset.emit()
def _restore_split_connection(
self,
owner_id: str,
original: Connection,
junction_id: str,
first_id: str,
second_id: str,
) -> None:
graph = self._graph_for(owner_id)
graph.connections.pop(first_id, None)
graph.connections.pop(second_id, None)
graph.junctions.pop(junction_id, None)
graph.connections[original.id] = original
self.documentReset.emit()
def _remove_connection(self, owner_id: str, connection_id: str) -> None:
self._graph_for(owner_id).connections.pop(connection_id, None)
if owner_id == self.active_component_id:
@@ -871,7 +999,11 @@ class DocumentController(QObject):
self, owner_id: str, item_kind: str, item_id: str, values: dict
) -> None:
graph = self._graph_for(owner_id)
if item_kind == "connection_data":
if item_kind == "junction_geometry":
item = graph.junctions.get(item_id)
if item is not None:
item.x, item.y = float(values["x"]), float(values["y"])
elif item_kind == "connection_data":
item = graph.connections.get(item_id)
if item is not None:
item.name = values["name"]