Added array ports and junctions
This commit is contained in:
@@ -16,6 +16,7 @@ class Port:
|
||||
y: float = 0.0
|
||||
properties: dict[str, Any] = field(default_factory=dict)
|
||||
type: str = "signal"
|
||||
allows_multiple_connections: bool = False
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -24,6 +25,7 @@ class Port:
|
||||
"position": {"x": self.x, "y": self.y},
|
||||
"properties": self.properties,
|
||||
"type": self.type,
|
||||
"multipleConnections": self.allows_multiple_connections,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -36,6 +38,7 @@ class Port:
|
||||
y=float(position.get("y", 0.0)),
|
||||
properties=dict(data.get("properties", {})),
|
||||
type=str(data.get("type", "signal")),
|
||||
allows_multiple_connections=bool(data.get("multipleConnections", False)),
|
||||
)
|
||||
|
||||
|
||||
@@ -109,10 +112,13 @@ class Endpoint:
|
||||
block: str | None = None
|
||||
port: str | None = None
|
||||
interface: str | None = None
|
||||
junction: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, str]:
|
||||
if self.interface is not None:
|
||||
return {"interface": self.interface}
|
||||
if self.junction is not None:
|
||||
return {"junction": self.junction}
|
||||
if self.block is None or self.port is None:
|
||||
raise ValueError("A block endpoint requires both block and port")
|
||||
return {"block": self.block, "port": self.port}
|
||||
@@ -121,6 +127,8 @@ class Endpoint:
|
||||
def from_dict(cls, data: dict[str, Any]) -> "Endpoint":
|
||||
if "interface" in data:
|
||||
return cls(interface=str(data["interface"]))
|
||||
if "junction" in data:
|
||||
return cls(junction=str(data["junction"]))
|
||||
return cls(block=str(data["block"]), port=str(data["port"]))
|
||||
|
||||
|
||||
@@ -176,6 +184,31 @@ class Parameter:
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Junction:
|
||||
id: str
|
||||
x: float
|
||||
y: float
|
||||
type: str = "signal"
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"position": {"x": self.x, "y": self.y},
|
||||
"type": self.type,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> "Junction":
|
||||
position = data.get("position", {})
|
||||
return cls(
|
||||
id=str(data["id"]),
|
||||
x=float(position.get("x", 0)),
|
||||
y=float(position.get("y", 0)),
|
||||
type=str(data.get("type", "signal")),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Annotation:
|
||||
id: str
|
||||
@@ -221,12 +254,14 @@ class Graph:
|
||||
blocks: dict[str, Component] = field(default_factory=dict)
|
||||
connections: dict[str, Connection] = field(default_factory=dict)
|
||||
annotations: dict[str, Annotation] = field(default_factory=dict)
|
||||
junctions: dict[str, Junction] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"blocks": [block.to_dict() for block in self.blocks.values()],
|
||||
"connections": [connection.to_dict() for connection in self.connections.values()],
|
||||
"annotations": [item.to_dict() for item in self.annotations.values()],
|
||||
"junctions": [junction.to_dict() for junction in self.junctions.values()],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -235,18 +270,22 @@ class Graph:
|
||||
blocks = [Component.from_dict(item) for item in data.get("blocks", [])]
|
||||
connections = [Connection.from_dict(item) for item in data.get("connections", [])]
|
||||
annotations = [Annotation.from_dict(item) for item in data.get("annotations", [])]
|
||||
junctions = [Junction.from_dict(item) for item in data.get("junctions", [])]
|
||||
if len({block.id for block in blocks}) != len(blocks):
|
||||
raise ValueError("A graph contains duplicate component IDs")
|
||||
if len({connection.id for connection in connections}) != len(connections):
|
||||
raise ValueError("A graph contains duplicate connection IDs")
|
||||
if len({item.id for item in annotations}) != len(annotations):
|
||||
raise ValueError("A graph contains duplicate annotation IDs")
|
||||
if len({item.id for item in junctions}) != len(junctions):
|
||||
raise ValueError("A graph contains duplicate junction IDs")
|
||||
if any(item.kind not in {"box", "line", "text"} for item in annotations):
|
||||
raise ValueError("A graph contains an unknown annotation kind")
|
||||
return cls(
|
||||
blocks={block.id: block for block in blocks},
|
||||
connections={connection.id: connection for connection in connections},
|
||||
annotations={item.id: item for item in annotations},
|
||||
junctions={junction.id: junction for junction in junctions},
|
||||
)
|
||||
|
||||
|
||||
@@ -332,7 +371,7 @@ class GraphDocument:
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "GraphDocument":
|
||||
return cls(metadata={"name": "Untitled"})
|
||||
return cls(metadata={"name": "Current Document"})
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -409,8 +448,18 @@ class GraphDocument:
|
||||
raise ValueError(f"Component {owner.name} contains duplicate port IDs")
|
||||
for port in (*owner.inputs, *owner.outputs):
|
||||
PortTypeRegistry.get(port.type)
|
||||
for junction in owner.graph.junctions.values():
|
||||
PortTypeRegistry.get(junction.type)
|
||||
endpoint_counts: dict[tuple[str, str, str], int] = {}
|
||||
for connection in owner.graph.connections.values():
|
||||
if connection.source.interface is not None:
|
||||
if connection.source.junction is not None:
|
||||
junction = owner.graph.junctions.get(connection.source.junction)
|
||||
if junction is None:
|
||||
raise ValueError(
|
||||
f"Connection {connection.id} uses an unknown source junction"
|
||||
)
|
||||
source_port = Port(junction.id, "Junction", type=junction.type)
|
||||
elif connection.source.interface is not None:
|
||||
if connection.source.interface not in input_ids:
|
||||
raise ValueError(f"Connection {connection.id} uses an unknown interface input")
|
||||
source_port = next(p for p in owner.inputs if p.id == connection.source.interface)
|
||||
@@ -419,7 +468,19 @@ class GraphDocument:
|
||||
if source is None or connection.source.port not in {p.id for p in source.outputs}:
|
||||
raise ValueError(f"Connection {connection.id} uses an unknown block output")
|
||||
source_port = next(p for p in source.outputs if p.id == connection.source.port)
|
||||
if connection.target.interface is not None:
|
||||
if connection.target.junction is not None:
|
||||
junction = owner.graph.junctions.get(connection.target.junction)
|
||||
if junction is None:
|
||||
raise ValueError(
|
||||
f"Connection {connection.id} uses an unknown target junction"
|
||||
)
|
||||
target_port = Port(
|
||||
junction.id,
|
||||
"Junction",
|
||||
type=junction.type,
|
||||
allows_multiple_connections=False,
|
||||
)
|
||||
elif connection.target.interface is not None:
|
||||
if connection.target.interface not in output_ids:
|
||||
raise ValueError(f"Connection {connection.id} uses an unknown interface output")
|
||||
target_port = next(p for p in owner.outputs if p.id == connection.target.interface)
|
||||
@@ -430,6 +491,40 @@ class GraphDocument:
|
||||
target_port = next(p for p in target.inputs if p.id == connection.target.port)
|
||||
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
|
||||
raise ValueError(f"Connection {connection.id} joins incompatible port types")
|
||||
source_key = (
|
||||
"source-junction"
|
||||
if connection.source.junction is not None
|
||||
else "source-interface"
|
||||
if connection.source.interface is not None
|
||||
else "source-block",
|
||||
connection.source.block or "",
|
||||
connection.source.junction
|
||||
or connection.source.interface
|
||||
or connection.source.port
|
||||
or "",
|
||||
)
|
||||
target_key = (
|
||||
"target-junction"
|
||||
if connection.target.junction is not None
|
||||
else "target-interface"
|
||||
if connection.target.interface is not None
|
||||
else "target-block",
|
||||
connection.target.block or "",
|
||||
connection.target.junction
|
||||
or connection.target.interface
|
||||
or connection.target.port
|
||||
or "",
|
||||
)
|
||||
endpoint_counts[source_key] = endpoint_counts.get(source_key, 0) + 1
|
||||
endpoint_counts[target_key] = endpoint_counts.get(target_key, 0) + 1
|
||||
if (
|
||||
endpoint_counts[target_key] > 1
|
||||
and not target_port.allows_multiple_connections
|
||||
):
|
||||
raise ValueError(
|
||||
f"Input {target_port.name!r} has multiple incoming connections "
|
||||
"but does not allow them"
|
||||
)
|
||||
|
||||
|
||||
def clone_component(source: Component) -> Component:
|
||||
@@ -438,10 +533,15 @@ def clone_component(source: Component) -> Component:
|
||||
def clone(current: Component) -> Component:
|
||||
child_pairs = [(child, clone(child)) for child in current.graph.blocks.values()]
|
||||
child_ids = {old.id: new.id for old, new in child_pairs}
|
||||
junction_ids = {
|
||||
junction.id: str(uuid4()) for junction in current.graph.junctions.values()
|
||||
}
|
||||
|
||||
def remap(endpoint: Endpoint) -> Endpoint:
|
||||
if endpoint.interface is not None:
|
||||
return endpoint
|
||||
if endpoint.junction is not None:
|
||||
return Endpoint(junction=junction_ids[endpoint.junction])
|
||||
return Endpoint(block=child_ids[endpoint.block or ""], port=endpoint.port)
|
||||
|
||||
graph = Graph(
|
||||
@@ -472,6 +572,12 @@ def clone_component(source: Component) -> Component:
|
||||
for annotation in current.graph.annotations.values()
|
||||
for new_id in [str(uuid4())]
|
||||
},
|
||||
junctions={
|
||||
junction_ids[junction.id]: Junction(
|
||||
junction_ids[junction.id], junction.x, junction.y, junction.type
|
||||
)
|
||||
for junction in current.graph.junctions.values()
|
||||
},
|
||||
)
|
||||
return Component(
|
||||
id=str(uuid4()),
|
||||
@@ -479,11 +585,27 @@ def clone_component(source: Component) -> Component:
|
||||
x=current.x,
|
||||
y=current.y,
|
||||
inputs=[
|
||||
Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type)
|
||||
Port(
|
||||
port.id,
|
||||
port.name,
|
||||
port.x,
|
||||
port.y,
|
||||
deepcopy(port.properties),
|
||||
port.type,
|
||||
port.allows_multiple_connections,
|
||||
)
|
||||
for port in current.inputs
|
||||
],
|
||||
outputs=[
|
||||
Port(port.id, port.name, port.x, port.y, deepcopy(port.properties), port.type)
|
||||
Port(
|
||||
port.id,
|
||||
port.name,
|
||||
port.x,
|
||||
port.y,
|
||||
deepcopy(port.properties),
|
||||
port.type,
|
||||
port.allows_multiple_connections,
|
||||
)
|
||||
for port in current.outputs
|
||||
],
|
||||
icon=Icon.from_dict(current.icon.to_dict()),
|
||||
|
||||
Reference in New Issue
Block a user