Bond graph ports and drawing added
This commit is contained in:
@@ -59,7 +59,12 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
- Port orientation is presented as one unified list in the UI, while the model
|
- Port orientation is presented as one unified list in the UI, while the model
|
||||||
indexes inputs and outputs separately for connection semantics.
|
indexes inputs and outputs separately for connection semantics.
|
||||||
- Port types are registered in `core/port_types.py`. Only compatible types may be
|
- Port types are registered in `core/port_types.py`. Only compatible types may be
|
||||||
connected. `signal` is currently the only type.
|
connected. Signal ports connect by type; power ports additionally require the
|
||||||
|
same domain. Editable power domains and causalities live in
|
||||||
|
`core/power_domains.py`.
|
||||||
|
- Power ports may use `indifferent` orientation and can act as either connection
|
||||||
|
endpoint. For two indifferent ports, click order determines source/arrow
|
||||||
|
direction; otherwise output/input semantics determine direction.
|
||||||
- Port connector type and signal value type are separate. Signal ports and
|
- Port connector type and signal value type are separate. Signal ports and
|
||||||
parameters carry editable value type, quantity, unit, row/column dimensions,
|
parameters carry editable value type, quantity, unit, row/column dimensions,
|
||||||
and description metadata. Editable quantity/unit suggestions live in
|
and description metadata. Editable quantity/unit suggestions live in
|
||||||
@@ -78,7 +83,8 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
|
|||||||
intersection of the owning hitbox and its center-to-adjacent-route-point ray.
|
intersection of the owning hitbox and its center-to-adjacent-route-point ray.
|
||||||
- Connection appearance is configured per port type in
|
- Connection appearance is configured per port type in
|
||||||
`gui/graphics/connection_styles.py`, including color, width, pen style, and
|
`gui/graphics/connection_styles.py`, including color, width, pen style, and
|
||||||
source/target arrowheads. Do not scatter those constants through painters.
|
source/target arrowheads. Arrow styles are `open`, `half`, or `filled`. Do not
|
||||||
|
scatter those constants through painters.
|
||||||
- Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`.
|
- Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`.
|
||||||
They use integer layers below or above graph layer 0. Annotation lines reuse
|
They use integer layers below or above graph layer 0. Annotation lines reuse
|
||||||
connection polyline and absolute `properties.waypoints` semantics. Graph
|
connection polyline and absolute `properties.waypoints` semantics. Graph
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Any
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from bedit.core.port_types import PortTypeRegistry
|
from bedit.core.port_types import PortTypeRegistry
|
||||||
|
from bedit.core.power_domains import POWER_CAUSALITIES, POWER_DOMAINS
|
||||||
|
|
||||||
|
|
||||||
def _dimensions(data: Any, subject: str) -> tuple[int, int]:
|
def _dimensions(data: Any, subject: str) -> tuple[int, int]:
|
||||||
@@ -36,6 +37,9 @@ class Port:
|
|||||||
rows: int = 1
|
rows: int = 1
|
||||||
columns: int = 1
|
columns: int = 1
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
orientation: str = "input"
|
||||||
|
domain: str = "power"
|
||||||
|
causality: str = "indifferent"
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -50,10 +54,13 @@ class Port:
|
|||||||
"unit": self.unit,
|
"unit": self.unit,
|
||||||
"dimensions": {"rows": self.rows, "columns": self.columns},
|
"dimensions": {"rows": self.rows, "columns": self.columns},
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
|
"orientation": self.orientation,
|
||||||
|
"domain": self.domain,
|
||||||
|
"causality": self.causality,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict[str, Any]) -> "Port":
|
def from_dict(cls, data: dict[str, Any], default_orientation: str = "input") -> "Port":
|
||||||
position = data.get("position", {})
|
position = data.get("position", {})
|
||||||
rows, columns = _dimensions(data.get("dimensions", {}), "Port")
|
rows, columns = _dimensions(data.get("dimensions", {}), "Port")
|
||||||
return cls(
|
return cls(
|
||||||
@@ -70,6 +77,9 @@ class Port:
|
|||||||
rows=rows,
|
rows=rows,
|
||||||
columns=columns,
|
columns=columns,
|
||||||
description=str(data.get("description", "")),
|
description=str(data.get("description", "")),
|
||||||
|
orientation=str(data.get("orientation", default_orientation)),
|
||||||
|
domain=str(data.get("domain", "power")),
|
||||||
|
causality=str(data.get("causality", "indifferent")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -417,8 +427,8 @@ class Component:
|
|||||||
x=float(position.get("x", 0.0)),
|
x=float(position.get("x", 0.0)),
|
||||||
y=float(position.get("y", 0.0)),
|
y=float(position.get("y", 0.0)),
|
||||||
rotation=float(data.get("rotation", 0.0)),
|
rotation=float(data.get("rotation", 0.0)),
|
||||||
inputs=[Port.from_dict(item) for item in interface.get("inputs", [])],
|
inputs=[Port.from_dict(item, "input") for item in interface.get("inputs", [])],
|
||||||
outputs=[Port.from_dict(item) for item in interface.get("outputs", [])],
|
outputs=[Port.from_dict(item, "output") for item in interface.get("outputs", [])],
|
||||||
parameters=[Parameter.from_dict(item) for item in parameters],
|
parameters=[Parameter.from_dict(item) for item in parameters],
|
||||||
icon=Icon.from_dict(data.get("icon")),
|
icon=Icon.from_dict(data.get("icon")),
|
||||||
properties=dict(data.get("properties", {})),
|
properties=dict(data.get("properties", {})),
|
||||||
@@ -528,6 +538,15 @@ class GraphDocument:
|
|||||||
raise ValueError(f"Component {owner.name} contains duplicate port IDs")
|
raise ValueError(f"Component {owner.name} contains duplicate port IDs")
|
||||||
for port in (*owner.inputs, *owner.outputs):
|
for port in (*owner.inputs, *owner.outputs):
|
||||||
PortTypeRegistry.get(port.type)
|
PortTypeRegistry.get(port.type)
|
||||||
|
if port.orientation not in {"input", "output", "indifferent"}:
|
||||||
|
raise ValueError(f"Port {port.name!r} has an invalid orientation")
|
||||||
|
if port.orientation == "indifferent" and port.type != "power":
|
||||||
|
raise ValueError("Only power ports may have indifferent orientation")
|
||||||
|
if port.type == "power":
|
||||||
|
if port.domain not in {domain.id for domain in POWER_DOMAINS}:
|
||||||
|
raise ValueError(f"Power port {port.name!r} has an unknown domain")
|
||||||
|
if port.causality not in POWER_CAUSALITIES:
|
||||||
|
raise ValueError(f"Power port {port.name!r} has invalid causality")
|
||||||
if not port.value_type.strip():
|
if not port.value_type.strip():
|
||||||
raise ValueError(f"Port {port.name!r} has no value type")
|
raise ValueError(f"Port {port.name!r} has no value type")
|
||||||
if port.rows < 1 or port.columns < 1:
|
if port.rows < 1 or port.columns < 1:
|
||||||
@@ -549,9 +568,17 @@ class GraphDocument:
|
|||||||
source_port = next(p for p in owner.inputs if p.id == connection.source.interface)
|
source_port = next(p for p in owner.inputs if p.id == connection.source.interface)
|
||||||
else:
|
else:
|
||||||
source = owner.graph.blocks.get(connection.source.block or "")
|
source = owner.graph.blocks.get(connection.source.block or "")
|
||||||
if source is None or connection.source.port not in {p.id for p in source.outputs}:
|
source_ports = (
|
||||||
|
[]
|
||||||
|
if source is None
|
||||||
|
else [
|
||||||
|
*source.outputs,
|
||||||
|
*(p for p in source.inputs if p.orientation == "indifferent"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if source is None or connection.source.port not in {p.id for p in source_ports}:
|
||||||
raise ValueError(f"Connection {connection.id} uses an unknown block output")
|
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)
|
source_port = next(p for p in source_ports if p.id == connection.source.port)
|
||||||
if connection.target.junction is not None:
|
if connection.target.junction is not None:
|
||||||
junction = owner.graph.junctions.get(connection.target.junction)
|
junction = owner.graph.junctions.get(connection.target.junction)
|
||||||
if junction is None:
|
if junction is None:
|
||||||
@@ -565,15 +592,27 @@ class GraphDocument:
|
|||||||
allows_multiple_connections=False,
|
allows_multiple_connections=False,
|
||||||
)
|
)
|
||||||
elif connection.target.interface is not None:
|
elif connection.target.interface is not None:
|
||||||
if connection.target.interface not in output_ids:
|
indifferent_ids = {
|
||||||
|
port.id for port in owner.inputs if port.orientation == "indifferent"
|
||||||
|
}
|
||||||
|
if connection.target.interface not in output_ids | indifferent_ids:
|
||||||
raise ValueError(f"Connection {connection.id} uses an unknown interface output")
|
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)
|
target_port = next(
|
||||||
|
p
|
||||||
|
for p in (*owner.outputs, *owner.inputs)
|
||||||
|
if p.id == connection.target.interface
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
target = owner.graph.blocks.get(connection.target.block or "")
|
target = owner.graph.blocks.get(connection.target.block or "")
|
||||||
if target is None or connection.target.port not in {p.id for p in target.inputs}:
|
if target is None or connection.target.port not in {p.id for p in target.inputs}:
|
||||||
raise ValueError(f"Connection {connection.id} uses an unknown block input")
|
raise ValueError(f"Connection {connection.id} uses an unknown block input")
|
||||||
target_port = next(p for p in target.inputs if p.id == connection.target.port)
|
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):
|
if not PortTypeRegistry.compatible(
|
||||||
|
source_port.type,
|
||||||
|
target_port.type,
|
||||||
|
source_port.domain,
|
||||||
|
target_port.domain,
|
||||||
|
):
|
||||||
raise ValueError(f"Connection {connection.id} joins incompatible port types")
|
raise ValueError(f"Connection {connection.id} joins incompatible port types")
|
||||||
source_key = (
|
source_key = (
|
||||||
"source-junction"
|
"source-junction"
|
||||||
@@ -670,27 +709,11 @@ def clone_component(source: Component) -> Component:
|
|||||||
x=current.x,
|
x=current.x,
|
||||||
y=current.y,
|
y=current.y,
|
||||||
inputs=[
|
inputs=[
|
||||||
Port(
|
Port.from_dict(port.to_dict(), "input")
|
||||||
port.id,
|
|
||||||
port.name,
|
|
||||||
port.x,
|
|
||||||
port.y,
|
|
||||||
deepcopy(port.properties),
|
|
||||||
port.type,
|
|
||||||
port.allows_multiple_connections,
|
|
||||||
)
|
|
||||||
for port in current.inputs
|
for port in current.inputs
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
Port(
|
Port.from_dict(port.to_dict(), "output")
|
||||||
port.id,
|
|
||||||
port.name,
|
|
||||||
port.x,
|
|
||||||
port.y,
|
|
||||||
deepcopy(port.properties),
|
|
||||||
port.type,
|
|
||||||
port.allows_multiple_connections,
|
|
||||||
)
|
|
||||||
for port in current.outputs
|
for port in current.outputs
|
||||||
],
|
],
|
||||||
parameters=deepcopy(current.parameters),
|
parameters=deepcopy(current.parameters),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class PortType:
|
|||||||
class PortTypeRegistry:
|
class PortTypeRegistry:
|
||||||
_types = {
|
_types = {
|
||||||
"signal": PortType("signal", "Signal", "A scalar signal connection"),
|
"signal": PortType("signal", "Signal", "A scalar signal connection"),
|
||||||
|
"power": PortType("power", "Power", "A two-variable power connection"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -28,5 +29,13 @@ class PortTypeRegistry:
|
|||||||
raise ValueError(f"Unknown port type: {type_id}") from error
|
raise ValueError(f"Unknown port type: {type_id}") from error
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def compatible(cls, first: str, second: str) -> bool:
|
def compatible(
|
||||||
return cls.get(first).accepts(cls.get(second))
|
cls,
|
||||||
|
first: str,
|
||||||
|
second: str,
|
||||||
|
first_domain: str = "",
|
||||||
|
second_domain: str = "",
|
||||||
|
) -> bool:
|
||||||
|
if not cls.get(first).accepts(cls.get(second)):
|
||||||
|
return False
|
||||||
|
return first != "power" or first_domain == second_domain
|
||||||
|
|||||||
31
BEdit/src/bedit/core/power_domains.py
Normal file
31
BEdit/src/bedit/core/power_domains.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"""Editable power-port domain definitions used by the port editor."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PowerDomain:
|
||||||
|
id: str
|
||||||
|
display_name: str
|
||||||
|
effort: str
|
||||||
|
flow: str
|
||||||
|
|
||||||
|
|
||||||
|
POWER_DOMAINS: tuple[PowerDomain, ...] = (
|
||||||
|
PowerDomain("power", "Power", "p.e", "p.f"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def power_domain(domain_id: str) -> PowerDomain:
|
||||||
|
return next((domain for domain in POWER_DOMAINS if domain.id == domain_id), POWER_DOMAINS[0])
|
||||||
|
|
||||||
|
|
||||||
|
POWER_CAUSALITIES = (
|
||||||
|
"fixed flow out",
|
||||||
|
"fixed effort out",
|
||||||
|
"preferred flow out",
|
||||||
|
"preferred effort out",
|
||||||
|
"likes flow out",
|
||||||
|
"likes effort out",
|
||||||
|
"indifferent",
|
||||||
|
)
|
||||||
@@ -285,7 +285,12 @@ class DocumentController(QObject):
|
|||||||
target_port = self._port_for_endpoint(target, "target")
|
target_port = self._port_for_endpoint(target, "target")
|
||||||
if source_port is None or target_port is None:
|
if source_port is None or target_port is None:
|
||||||
raise ValueError("A connection endpoint no longer exists")
|
raise ValueError("A connection endpoint no longer exists")
|
||||||
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
|
if not PortTypeRegistry.compatible(
|
||||||
|
source_port.type,
|
||||||
|
target_port.type,
|
||||||
|
source_port.domain,
|
||||||
|
target_port.domain,
|
||||||
|
):
|
||||||
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
|
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
|
||||||
if not self.endpoint_accepts_connection(source, "source"):
|
if not self.endpoint_accepts_connection(source, "source"):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -573,11 +578,24 @@ class DocumentController(QObject):
|
|||||||
)
|
)
|
||||||
if endpoint.interface is not None:
|
if endpoint.interface is not None:
|
||||||
ports = owner.inputs if role == "source" else owner.outputs
|
ports = owner.inputs if role == "source" else owner.outputs
|
||||||
|
ports = [
|
||||||
|
*ports,
|
||||||
|
*(
|
||||||
|
port
|
||||||
|
for port in owner.inputs
|
||||||
|
if port.orientation == "indifferent" and port not in ports
|
||||||
|
),
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
component = owner.graph.blocks.get(endpoint.block or "")
|
component = owner.graph.blocks.get(endpoint.block or "")
|
||||||
if component is None:
|
if component is None:
|
||||||
return None
|
return None
|
||||||
ports = component.outputs if role == "source" else component.inputs
|
ports = component.outputs if role == "source" else component.inputs
|
||||||
|
if role == "source":
|
||||||
|
ports = [
|
||||||
|
*ports,
|
||||||
|
*(port for port in component.inputs if port.orientation == "indifferent"),
|
||||||
|
]
|
||||||
return next(
|
return next(
|
||||||
(port for port in ports if port.id == (endpoint.interface or endpoint.port)), None
|
(port for port in ports if port.id == (endpoint.interface or endpoint.port)), None
|
||||||
)
|
)
|
||||||
@@ -664,6 +682,9 @@ class DocumentController(QObject):
|
|||||||
raise ValueError("Only text-defined components can be edited here")
|
raise ValueError("Only text-defined components can be edited here")
|
||||||
input_ids = [port.id for port in inputs]
|
input_ids = [port.id for port in inputs]
|
||||||
output_ids = [port.id for port in outputs]
|
output_ids = [port.id for port in outputs]
|
||||||
|
source_ids = output_ids + [
|
||||||
|
port.id for port in inputs if port.orientation == "indifferent"
|
||||||
|
]
|
||||||
if len(set(input_ids)) != len(input_ids) or len(set(output_ids)) != len(output_ids):
|
if len(set(input_ids)) != len(input_ids) or len(set(output_ids)) != len(output_ids):
|
||||||
raise ValueError("Input and output IDs must be unique")
|
raise ValueError("Input and output IDs must be unique")
|
||||||
if any(not port.name.strip() for port in (*inputs, *outputs)):
|
if any(not port.name.strip() for port in (*inputs, *outputs)):
|
||||||
@@ -686,7 +707,7 @@ class DocumentController(QObject):
|
|||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
connection.source.block == component.id
|
connection.source.block == component.id
|
||||||
and connection.source.port not in output_ids
|
and connection.source.port not in source_ids
|
||||||
):
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Output {connection.source.port!r} is still connected in the containing graph"
|
f"Output {connection.source.port!r} is still connected in the containing graph"
|
||||||
@@ -842,6 +863,9 @@ class DocumentController(QObject):
|
|||||||
return
|
return
|
||||||
input_ids = {port.id for port in inputs}
|
input_ids = {port.id for port in inputs}
|
||||||
output_ids = {port.id for port in outputs}
|
output_ids = {port.id for port in outputs}
|
||||||
|
source_ids = output_ids | {
|
||||||
|
port.id for port in inputs if port.orientation == "indifferent"
|
||||||
|
}
|
||||||
parent = self.document.find_parent(component_id)
|
parent = self.document.find_parent(component_id)
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
for connection in parent.graph.connections.values():
|
for connection in parent.graph.connections.values():
|
||||||
@@ -852,7 +876,7 @@ class DocumentController(QObject):
|
|||||||
raise ValueError("An input cannot be removed or reoriented while connected")
|
raise ValueError("An input cannot be removed or reoriented while connected")
|
||||||
if (
|
if (
|
||||||
connection.source.block == component_id
|
connection.source.block == component_id
|
||||||
and connection.source.port not in output_ids
|
and connection.source.port not in source_ids
|
||||||
):
|
):
|
||||||
raise ValueError("An output cannot be removed or reoriented while connected")
|
raise ValueError("An output cannot be removed or reoriented while connected")
|
||||||
for connection in component.graph.connections.values():
|
for connection in component.graph.connections.values():
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from PySide6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem, QMessa
|
|||||||
|
|
||||||
from bedit.core.model import Component, Port
|
from bedit.core.model import Component, Port
|
||||||
from bedit.core.physical_types import QUANTITIES, UNITS
|
from bedit.core.physical_types import QUANTITIES, UNITS
|
||||||
|
from bedit.core.power_domains import POWER_CAUSALITIES, POWER_DOMAINS, power_domain
|
||||||
from bedit.core.port_types import PortTypeRegistry
|
from bedit.core.port_types import PortTypeRegistry
|
||||||
from bedit.gui.generated.ui_port_options_dialog import Ui_PortOptionsDialog
|
from bedit.gui.generated.ui_port_options_dialog import Ui_PortOptionsDialog
|
||||||
|
|
||||||
@@ -13,6 +14,13 @@ from bedit.gui.generated.ui_port_options_dialog import Ui_PortOptionsDialog
|
|||||||
PORT_ROLE = Qt.ItemDataRole.UserRole
|
PORT_ROLE = Qt.ItemDataRole.UserRole
|
||||||
|
|
||||||
|
|
||||||
|
def _oriented_copy(port: Port, fallback: str) -> Port:
|
||||||
|
copied = deepcopy(port)
|
||||||
|
if copied.orientation != "indifferent":
|
||||||
|
copied.orientation = fallback
|
||||||
|
return copied
|
||||||
|
|
||||||
|
|
||||||
class PortOptionsDialog(QDialog):
|
class PortOptionsDialog(QDialog):
|
||||||
"""Unified editor for a component's typed, oriented ports."""
|
"""Unified editor for a component's typed, oriented ports."""
|
||||||
|
|
||||||
@@ -25,8 +33,8 @@ class PortOptionsDialog(QDialog):
|
|||||||
self.setWindowTitle(f"Port Options — {component.name}")
|
self.setWindowTitle(f"Port Options — {component.name}")
|
||||||
self.read_only = read_only
|
self.read_only = read_only
|
||||||
self.ports: list[tuple[Port, str]] = [
|
self.ports: list[tuple[Port, str]] = [
|
||||||
*((deepcopy(port), "input") for port in component.inputs),
|
*((_oriented_copy(port, "input"), port.orientation or "input") for port in component.inputs),
|
||||||
*((deepcopy(port), "output") for port in component.outputs),
|
*((_oriented_copy(port, "output"), "output") for port in component.outputs),
|
||||||
]
|
]
|
||||||
self._loading = False
|
self._loading = False
|
||||||
self.ui.typeCombo.clear()
|
self.ui.typeCombo.clear()
|
||||||
@@ -36,6 +44,10 @@ class PortOptionsDialog(QDialog):
|
|||||||
self.ui.unitCombo.addItems(UNITS)
|
self.ui.unitCombo.addItems(UNITS)
|
||||||
self.ui.orientationCombo.setItemData(0, "input")
|
self.ui.orientationCombo.setItemData(0, "input")
|
||||||
self.ui.orientationCombo.setItemData(1, "output")
|
self.ui.orientationCombo.setItemData(1, "output")
|
||||||
|
self.ui.orientationCombo.setItemData(2, "indifferent")
|
||||||
|
for domain in POWER_DOMAINS:
|
||||||
|
self.ui.domainCombo.addItem(domain.display_name, domain.id)
|
||||||
|
self.ui.causalityCombo.addItems(POWER_CAUSALITIES)
|
||||||
self.ui.portList.currentRowChanged.connect(self._load_current)
|
self.ui.portList.currentRowChanged.connect(self._load_current)
|
||||||
self.ui.addPortButton.clicked.connect(self.add_port)
|
self.ui.addPortButton.clicked.connect(self.add_port)
|
||||||
self.ui.removePortButton.clicked.connect(self.remove_port)
|
self.ui.removePortButton.clicked.connect(self.remove_port)
|
||||||
@@ -49,6 +61,9 @@ class PortOptionsDialog(QDialog):
|
|||||||
self.ui.rowsSpin.valueChanged.connect(self._store_current)
|
self.ui.rowsSpin.valueChanged.connect(self._store_current)
|
||||||
self.ui.columnsSpin.valueChanged.connect(self._store_current)
|
self.ui.columnsSpin.valueChanged.connect(self._store_current)
|
||||||
self.ui.descriptionEdit.textChanged.connect(self._store_current)
|
self.ui.descriptionEdit.textChanged.connect(self._store_current)
|
||||||
|
self.ui.domainCombo.currentIndexChanged.connect(self._power_domain_changed)
|
||||||
|
self.ui.causalityCombo.currentTextChanged.connect(self._store_current)
|
||||||
|
self.ui.powerDescriptionEdit.textChanged.connect(self._store_current)
|
||||||
self.ui.portSplitter.setSizes([250, 370])
|
self.ui.portSplitter.setSizes([250, 370])
|
||||||
if read_only:
|
if read_only:
|
||||||
self.ui.addPortButton.setEnabled(False)
|
self.ui.addPortButton.setEnabled(False)
|
||||||
@@ -64,7 +79,11 @@ class PortOptionsDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def inputs(self) -> list[Port]:
|
def inputs(self) -> list[Port]:
|
||||||
return [port for port, orientation in self.ports if orientation == "input"]
|
return [
|
||||||
|
port
|
||||||
|
for port, orientation in self.ports
|
||||||
|
if orientation in {"input", "indifferent"}
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outputs(self) -> list[Port]:
|
def outputs(self) -> list[Port]:
|
||||||
@@ -96,6 +115,9 @@ class PortOptionsDialog(QDialog):
|
|||||||
self.ui.rowsSpin.setValue(port.rows)
|
self.ui.rowsSpin.setValue(port.rows)
|
||||||
self.ui.columnsSpin.setValue(port.columns)
|
self.ui.columnsSpin.setValue(port.columns)
|
||||||
self.ui.descriptionEdit.setPlainText(port.description)
|
self.ui.descriptionEdit.setPlainText(port.description)
|
||||||
|
self.ui.domainCombo.setCurrentIndex(self.ui.domainCombo.findData(port.domain))
|
||||||
|
self.ui.causalityCombo.setCurrentText(port.causality)
|
||||||
|
self.ui.powerDescriptionEdit.setPlainText(port.description)
|
||||||
self._show_type_options(port.type)
|
self._show_type_options(port.type)
|
||||||
else:
|
else:
|
||||||
self.ui.nameEdit.clear()
|
self.ui.nameEdit.clear()
|
||||||
@@ -113,15 +135,27 @@ class PortOptionsDialog(QDialog):
|
|||||||
self.ui.typeOptionsStack.setEnabled(enabled and not self.read_only)
|
self.ui.typeOptionsStack.setEnabled(enabled and not self.read_only)
|
||||||
|
|
||||||
def _port_type_changed(self) -> None:
|
def _port_type_changed(self) -> None:
|
||||||
self._show_type_options(self.ui.typeCombo.currentData())
|
port_type = self.ui.typeCombo.currentData()
|
||||||
|
indifferent_item = self.ui.orientationCombo.model().item(2)
|
||||||
|
indifferent_item.setEnabled(port_type == "power")
|
||||||
|
if port_type != "power" and self.ui.orientationCombo.currentData() == "indifferent":
|
||||||
|
self.ui.orientationCombo.setCurrentIndex(0)
|
||||||
|
self._show_type_options(port_type)
|
||||||
self._store_current()
|
self._store_current()
|
||||||
|
|
||||||
def _show_type_options(self, port_type: str) -> None:
|
def _show_type_options(self, port_type: str) -> None:
|
||||||
self.ui.typeOptionsStack.setCurrentWidget(
|
self.ui.orientationCombo.model().item(2).setEnabled(port_type == "power")
|
||||||
self.ui.signalOptionsPage
|
page = {
|
||||||
if port_type == "signal"
|
"signal": self.ui.signalOptionsPage,
|
||||||
else self.ui.unsupportedTypePage
|
"power": self.ui.powerOptionsPage,
|
||||||
)
|
}.get(port_type, self.ui.unsupportedTypePage)
|
||||||
|
self.ui.typeOptionsStack.setCurrentWidget(page)
|
||||||
|
|
||||||
|
def _power_domain_changed(self) -> None:
|
||||||
|
domain = power_domain(self.ui.domainCombo.currentData())
|
||||||
|
self.ui.effortValueLabel.setText(domain.effort)
|
||||||
|
self.ui.flowValueLabel.setText(domain.flow)
|
||||||
|
self._store_current()
|
||||||
|
|
||||||
def _store_current(self) -> None:
|
def _store_current(self) -> None:
|
||||||
row = self.ui.portList.currentRow()
|
row = self.ui.portList.currentRow()
|
||||||
@@ -136,8 +170,16 @@ class PortOptionsDialog(QDialog):
|
|||||||
port.unit = self.ui.unitCombo.currentText().strip()
|
port.unit = self.ui.unitCombo.currentText().strip()
|
||||||
port.rows = self.ui.rowsSpin.value()
|
port.rows = self.ui.rowsSpin.value()
|
||||||
port.columns = self.ui.columnsSpin.value()
|
port.columns = self.ui.columnsSpin.value()
|
||||||
port.description = self.ui.descriptionEdit.toPlainText()
|
port.domain = self.ui.domainCombo.currentData() or "power"
|
||||||
self.ports[row] = (port, self.ui.orientationCombo.currentData())
|
port.causality = self.ui.causalityCombo.currentText()
|
||||||
|
port.description = (
|
||||||
|
self.ui.powerDescriptionEdit.toPlainText()
|
||||||
|
if port.type == "power"
|
||||||
|
else self.ui.descriptionEdit.toPlainText()
|
||||||
|
)
|
||||||
|
orientation = self.ui.orientationCombo.currentData()
|
||||||
|
port.orientation = orientation
|
||||||
|
self.ports[row] = (port, orientation)
|
||||||
self.ui.portList.item(row).setText(
|
self.ui.portList.item(row).setText(
|
||||||
f"{port.name} [{self.ports[row][1]}, {port.type}]"
|
f"{port.name} [{self.ports[row][1]}, {port.type}]"
|
||||||
)
|
)
|
||||||
@@ -146,8 +188,8 @@ class PortOptionsDialog(QDialog):
|
|||||||
def set_ports(self, inputs: list[Port], outputs: list[Port]) -> None:
|
def set_ports(self, inputs: list[Port], outputs: list[Port]) -> None:
|
||||||
self._loading = True
|
self._loading = True
|
||||||
self.ports = [
|
self.ports = [
|
||||||
*((deepcopy(port), "input") for port in inputs),
|
*((_oriented_copy(port, "input"), port.orientation or "input") for port in inputs),
|
||||||
*((deepcopy(port), "output") for port in outputs),
|
*((_oriented_copy(port, "output"), "output") for port in outputs),
|
||||||
]
|
]
|
||||||
self._loading = False
|
self._loading = False
|
||||||
self._rebuild_list(0 if self.ports else -1)
|
self._rebuild_list(0 if self.ports else -1)
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class Ui_PortOptionsDialog(object):
|
|||||||
self.orientationCombo = QComboBox(self.portDetailsPanel)
|
self.orientationCombo = QComboBox(self.portDetailsPanel)
|
||||||
self.orientationCombo.addItem("")
|
self.orientationCombo.addItem("")
|
||||||
self.orientationCombo.addItem("")
|
self.orientationCombo.addItem("")
|
||||||
|
self.orientationCombo.addItem("")
|
||||||
self.orientationCombo.setObjectName(u"orientationCombo")
|
self.orientationCombo.setObjectName(u"orientationCombo")
|
||||||
|
|
||||||
self.portDetailsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.orientationCombo)
|
self.portDetailsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.orientationCombo)
|
||||||
@@ -185,6 +186,61 @@ class Ui_PortOptionsDialog(object):
|
|||||||
self.signalOptionsForm.setWidget(4, QFormLayout.ItemRole.FieldRole, self.descriptionEdit)
|
self.signalOptionsForm.setWidget(4, QFormLayout.ItemRole.FieldRole, self.descriptionEdit)
|
||||||
|
|
||||||
self.typeOptionsStack.addWidget(self.signalOptionsPage)
|
self.typeOptionsStack.addWidget(self.signalOptionsPage)
|
||||||
|
self.powerOptionsPage = QWidget()
|
||||||
|
self.powerOptionsPage.setObjectName(u"powerOptionsPage")
|
||||||
|
self.powerOptionsForm = QFormLayout(self.powerOptionsPage)
|
||||||
|
self.powerOptionsForm.setObjectName(u"powerOptionsForm")
|
||||||
|
self.domainLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.domainLabel.setObjectName(u"domainLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(0, QFormLayout.ItemRole.LabelRole, self.domainLabel)
|
||||||
|
|
||||||
|
self.domainCombo = QComboBox(self.powerOptionsPage)
|
||||||
|
self.domainCombo.setObjectName(u"domainCombo")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(0, QFormLayout.ItemRole.FieldRole, self.domainCombo)
|
||||||
|
|
||||||
|
self.effortLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.effortLabel.setObjectName(u"effortLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(1, QFormLayout.ItemRole.LabelRole, self.effortLabel)
|
||||||
|
|
||||||
|
self.effortValueLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.effortValueLabel.setObjectName(u"effortValueLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(1, QFormLayout.ItemRole.FieldRole, self.effortValueLabel)
|
||||||
|
|
||||||
|
self.flowLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.flowLabel.setObjectName(u"flowLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(2, QFormLayout.ItemRole.LabelRole, self.flowLabel)
|
||||||
|
|
||||||
|
self.flowValueLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.flowValueLabel.setObjectName(u"flowValueLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.flowValueLabel)
|
||||||
|
|
||||||
|
self.causalityLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.causalityLabel.setObjectName(u"causalityLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(3, QFormLayout.ItemRole.LabelRole, self.causalityLabel)
|
||||||
|
|
||||||
|
self.causalityCombo = QComboBox(self.powerOptionsPage)
|
||||||
|
self.causalityCombo.setObjectName(u"causalityCombo")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(3, QFormLayout.ItemRole.FieldRole, self.causalityCombo)
|
||||||
|
|
||||||
|
self.powerDescriptionLabel = QLabel(self.powerOptionsPage)
|
||||||
|
self.powerDescriptionLabel.setObjectName(u"powerDescriptionLabel")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(4, QFormLayout.ItemRole.LabelRole, self.powerDescriptionLabel)
|
||||||
|
|
||||||
|
self.powerDescriptionEdit = QPlainTextEdit(self.powerOptionsPage)
|
||||||
|
self.powerDescriptionEdit.setObjectName(u"powerDescriptionEdit")
|
||||||
|
|
||||||
|
self.powerOptionsForm.setWidget(4, QFormLayout.ItemRole.FieldRole, self.powerDescriptionEdit)
|
||||||
|
|
||||||
|
self.typeOptionsStack.addWidget(self.powerOptionsPage)
|
||||||
self.unsupportedTypePage = QWidget()
|
self.unsupportedTypePage = QWidget()
|
||||||
self.unsupportedTypePage.setObjectName(u"unsupportedTypePage")
|
self.unsupportedTypePage.setObjectName(u"unsupportedTypePage")
|
||||||
self.unsupportedTypeLayout = QVBoxLayout(self.unsupportedTypePage)
|
self.unsupportedTypeLayout = QVBoxLayout(self.unsupportedTypePage)
|
||||||
@@ -228,6 +284,7 @@ class Ui_PortOptionsDialog(object):
|
|||||||
self.orientationLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Orientation:", None))
|
self.orientationLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Orientation:", None))
|
||||||
self.orientationCombo.setItemText(0, QCoreApplication.translate("PortOptionsDialog", u"Input", None))
|
self.orientationCombo.setItemText(0, QCoreApplication.translate("PortOptionsDialog", u"Input", None))
|
||||||
self.orientationCombo.setItemText(1, QCoreApplication.translate("PortOptionsDialog", u"Output", None))
|
self.orientationCombo.setItemText(1, QCoreApplication.translate("PortOptionsDialog", u"Output", None))
|
||||||
|
self.orientationCombo.setItemText(2, QCoreApplication.translate("PortOptionsDialog", u"Indifferent", None))
|
||||||
|
|
||||||
self.multipleConnectionsCheckBox.setText(QCoreApplication.translate("PortOptionsDialog", u"Allow multiple connections", None))
|
self.multipleConnectionsCheckBox.setText(QCoreApplication.translate("PortOptionsDialog", u"Allow multiple connections", None))
|
||||||
self.positionHintLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"New ports start at (0, 0) in the icon editor.", None))
|
self.positionHintLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"New ports start at (0, 0) in the icon editor.", None))
|
||||||
@@ -241,6 +298,13 @@ class Ui_PortOptionsDialog(object):
|
|||||||
self.unitLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Unit:", None))
|
self.unitLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Unit:", None))
|
||||||
self.rowsColumnsLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Matrix size:", None))
|
self.rowsColumnsLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Matrix size:", None))
|
||||||
self.descriptionLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Description:", None))
|
self.descriptionLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Description:", None))
|
||||||
|
self.domainLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Domain:", None))
|
||||||
|
self.effortLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Effort:", None))
|
||||||
|
self.effortValueLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"p.e", None))
|
||||||
|
self.flowLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Flow:", None))
|
||||||
|
self.flowValueLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"p.f", None))
|
||||||
|
self.causalityLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Causality:", None))
|
||||||
|
self.powerDescriptionLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Description:", None))
|
||||||
self.unsupportedTypeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"This port type does not have an options editor yet.", None))
|
self.unsupportedTypeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"This port type does not have an options editor yet.", None))
|
||||||
# retranslateUi
|
# retranslateUi
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
ArrowStyle = Literal["open", "half", "filled"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ConnectionStyle:
|
class ConnectionStyle:
|
||||||
color: str = "#285f9e"
|
color: str = "#285f9e"
|
||||||
@@ -13,11 +17,17 @@ class ConnectionStyle:
|
|||||||
arrow_at_source: bool = False
|
arrow_at_source: bool = False
|
||||||
arrow_at_target: bool = True
|
arrow_at_target: bool = True
|
||||||
arrow_size: float = 10.0
|
arrow_size: float = 10.0
|
||||||
|
arrow_style: ArrowStyle = "filled"
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if self.arrow_style not in {"open", "half", "filled"}:
|
||||||
|
raise ValueError(f"Unknown connection arrow style: {self.arrow_style}")
|
||||||
|
|
||||||
|
|
||||||
# This is the intentional code-level styling point for every port/connection type.
|
# This is the intentional code-level styling point for every port/connection type.
|
||||||
CONNECTION_STYLES: dict[str, ConnectionStyle] = {
|
CONNECTION_STYLES: dict[str, ConnectionStyle] = {
|
||||||
"signal": ConnectionStyle(),
|
"signal": ConnectionStyle(),
|
||||||
|
"power": ConnectionStyle(width=3.0, arrow_style="half", color="#000000"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from PySide6.QtCore import QPointF, QRectF, QSizeF, Qt
|
from PySide6.QtCore import QPointF, QRectF, QSizeF, Qt, QTimer
|
||||||
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPolygonF
|
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPolygonF
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QColorDialog,
|
QColorDialog,
|
||||||
@@ -487,7 +487,13 @@ class IconEditorDialog(QDialog):
|
|||||||
self.scene.addItem(ShapeItem(element))
|
self.scene.addItem(ShapeItem(element))
|
||||||
self._add_ports(self.inputs, "input", 0.0)
|
self._add_ports(self.inputs, "input", 0.0)
|
||||||
self._add_ports(self.outputs, "output", self.icon.width)
|
self._add_ports(self.outputs, "output", self.icon.width)
|
||||||
self.view.center_icon()
|
self._initial_fit_pending = True
|
||||||
|
|
||||||
|
def showEvent(self, event) -> None: # noqa: N802 (Qt API name)
|
||||||
|
super().showEvent(event)
|
||||||
|
if self._initial_fit_pending:
|
||||||
|
self._initial_fit_pending = False
|
||||||
|
QTimer.singleShot(0, self.view.center_icon)
|
||||||
|
|
||||||
def _add_ports(self, ports: list[Port], direction: str, default_x: float) -> None:
|
def _add_ports(self, ports: list[Port], direction: str, default_x: float) -> None:
|
||||||
spacing = self.icon.height / (len(ports) + 1)
|
spacing = self.icon.height / (len(ports) + 1)
|
||||||
|
|||||||
@@ -485,23 +485,39 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
|||||||
handle.setVisible(self.isSelected())
|
handle.setVisible(self.isSelected())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _arrow(end: QPointF, direction: QPointF, size: float) -> QPolygonF:
|
def _arrow_points(
|
||||||
|
end: QPointF, direction: QPointF, size: float
|
||||||
|
) -> tuple[QPointF, QPointF, QPointF]:
|
||||||
length = max(0.001, (direction.x() ** 2 + direction.y() ** 2) ** 0.5)
|
length = max(0.001, (direction.x() ** 2 + direction.y() ** 2) ** 0.5)
|
||||||
unit = QPointF(direction.x() / length, direction.y() / length)
|
unit = QPointF(direction.x() / length, direction.y() / length)
|
||||||
normal = QPointF(-unit.y(), unit.x())
|
normal = QPointF(-unit.y(), unit.x())
|
||||||
base = end - unit * size
|
base = end - unit * size
|
||||||
return QPolygonF([end, base + normal * size * 0.45, base - normal * size * 0.45])
|
return end, base + normal * size * 0.45, base - normal * size * 0.45
|
||||||
|
|
||||||
|
def _draw_arrow(
|
||||||
|
self, painter: QPainter, end: QPointF, direction: QPointF
|
||||||
|
) -> None:
|
||||||
|
tip, left, right = self._arrow_points(
|
||||||
|
end, direction, self.style.arrow_size
|
||||||
|
)
|
||||||
|
color = self.pen().color()
|
||||||
|
if self.style.arrow_style == "filled":
|
||||||
|
painter.setPen(Qt.PenStyle.NoPen)
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.drawPolygon(QPolygonF([tip, left, right]))
|
||||||
|
return
|
||||||
|
painter.setPen(QPen(color, self.pen().widthF(), Qt.PenStyle.SolidLine))
|
||||||
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
|
painter.drawLine(tip, left)
|
||||||
|
if self.style.arrow_style == "open":
|
||||||
|
painter.drawLine(tip, right)
|
||||||
|
|
||||||
def paint(self, painter: QPainter, option, widget=None) -> None:
|
def paint(self, painter: QPainter, option, widget=None) -> None:
|
||||||
super().paint(painter, option, widget)
|
super().paint(painter, option, widget)
|
||||||
painter.setPen(Qt.PenStyle.NoPen)
|
|
||||||
painter.setBrush(self.pen().color())
|
|
||||||
if self.style.arrow_at_target and not self.target_is_junction:
|
if self.style.arrow_at_target and not self.target_is_junction:
|
||||||
painter.drawPolygon(self._arrow(self.end, self.end_direction, self.style.arrow_size))
|
self._draw_arrow(painter, self.end, self.end_direction)
|
||||||
if self.style.arrow_at_source and not self.source_is_junction:
|
if self.style.arrow_at_source and not self.source_is_junction:
|
||||||
painter.drawPolygon(
|
self._draw_arrow(painter, self.start, -self.start_direction)
|
||||||
self._arrow(self.start, -self.start_direction, self.style.arrow_size)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WaypointHandle(QGraphicsEllipseItem):
|
class WaypointHandle(QGraphicsEllipseItem):
|
||||||
@@ -1207,9 +1223,22 @@ class GraphScene(QGraphicsScene):
|
|||||||
def add_pairs(
|
def add_pairs(
|
||||||
source_item: ComponentGraphicsItem, target_item: ComponentGraphicsItem
|
source_item: ComponentGraphicsItem, target_item: ComponentGraphicsItem
|
||||||
) -> None:
|
) -> None:
|
||||||
for output in source_item.component.outputs:
|
source_ports = [
|
||||||
|
*source_item.component.outputs,
|
||||||
|
*(
|
||||||
|
port
|
||||||
|
for port in source_item.component.inputs
|
||||||
|
if port.orientation == "indifferent"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for output in source_ports:
|
||||||
for input_port in target_item.component.inputs:
|
for input_port in target_item.component.inputs:
|
||||||
if not PortTypeRegistry.compatible(output.type, input_port.type):
|
if not PortTypeRegistry.compatible(
|
||||||
|
output.type,
|
||||||
|
input_port.type,
|
||||||
|
output.domain,
|
||||||
|
input_port.domain,
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
source = Endpoint(block=source_item.component_id, port=output.id)
|
source = Endpoint(block=source_item.component_id, port=output.id)
|
||||||
target = Endpoint(block=target_item.component_id, port=input_port.id)
|
target = Endpoint(block=target_item.component_id, port=input_port.id)
|
||||||
@@ -1240,7 +1269,9 @@ class GraphScene(QGraphicsScene):
|
|||||||
if terminal.direction == "input":
|
if terminal.direction == "input":
|
||||||
for port in component.component.inputs:
|
for port in component.component.inputs:
|
||||||
target = Endpoint(block=component.component_id, port=port.id)
|
target = Endpoint(block=component.component_id, port=port.id)
|
||||||
if not PortTypeRegistry.compatible(terminal.port.type, port.type):
|
if not PortTypeRegistry.compatible(
|
||||||
|
terminal.port.type, port.type, terminal.port.domain, port.domain
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if not self.controller.endpoint_accepts_connection(interface, "source"):
|
if not self.controller.endpoint_accepts_connection(interface, "source"):
|
||||||
continue
|
continue
|
||||||
@@ -1254,9 +1285,19 @@ class GraphScene(QGraphicsScene):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for port in component.component.outputs:
|
ports = [
|
||||||
|
*component.component.outputs,
|
||||||
|
*(
|
||||||
|
port
|
||||||
|
for port in component.component.inputs
|
||||||
|
if port.orientation == "indifferent"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for port in ports:
|
||||||
source = Endpoint(block=component.component_id, port=port.id)
|
source = Endpoint(block=component.component_id, port=port.id)
|
||||||
if not PortTypeRegistry.compatible(port.type, terminal.port.type):
|
if not PortTypeRegistry.compatible(
|
||||||
|
port.type, terminal.port.type, port.domain, terminal.port.domain
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if not self.controller.endpoint_accepts_connection(source, "source"):
|
if not self.controller.endpoint_accepts_connection(source, "source"):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -94,6 +94,11 @@
|
|||||||
<string>Output</string>
|
<string>Output</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Indifferent</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="3" column="0" colspan="2">
|
||||||
@@ -222,6 +227,20 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="powerOptionsPage">
|
||||||
|
<layout class="QFormLayout" name="powerOptionsForm">
|
||||||
|
<item row="0" column="0"><widget class="QLabel" name="domainLabel"><property name="text"><string>Domain:</string></property></widget></item>
|
||||||
|
<item row="0" column="1"><widget class="QComboBox" name="domainCombo"/></item>
|
||||||
|
<item row="1" column="0"><widget class="QLabel" name="effortLabel"><property name="text"><string>Effort:</string></property></widget></item>
|
||||||
|
<item row="1" column="1"><widget class="QLabel" name="effortValueLabel"><property name="text"><string>p.e</string></property></widget></item>
|
||||||
|
<item row="2" column="0"><widget class="QLabel" name="flowLabel"><property name="text"><string>Flow:</string></property></widget></item>
|
||||||
|
<item row="2" column="1"><widget class="QLabel" name="flowValueLabel"><property name="text"><string>p.f</string></property></widget></item>
|
||||||
|
<item row="3" column="0"><widget class="QLabel" name="causalityLabel"><property name="text"><string>Causality:</string></property></widget></item>
|
||||||
|
<item row="3" column="1"><widget class="QComboBox" name="causalityCombo"/></item>
|
||||||
|
<item row="4" column="0"><widget class="QLabel" name="powerDescriptionLabel"><property name="text"><string>Description:</string></property></widget></item>
|
||||||
|
<item row="4" column="1"><widget class="QPlainTextEdit" name="powerDescriptionEdit"/></item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
<widget class="QWidget" name="unsupportedTypePage">
|
<widget class="QWidget" name="unsupportedTypePage">
|
||||||
<layout class="QVBoxLayout" name="unsupportedTypeLayout">
|
<layout class="QVBoxLayout" name="unsupportedTypeLayout">
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"roots": [
|
"roots": [
|
||||||
{
|
{
|
||||||
"id": "4014ba90-5538-4050-a9be-552318197ce1",
|
"id": "4aefc7e3-ac23-4b31-9087-bbad16156be0",
|
||||||
"name": "New Graph Block 1",
|
"name": "New Graph Block 1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
@@ -61,19 +61,47 @@
|
|||||||
"graph": {
|
"graph": {
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "86ba2bf6-0da5-40a4-98e9-fb0d9c12c576",
|
"id": "c6169e52-e993-4293-8fc8-5b97e8ad59be",
|
||||||
"name": "Constant0",
|
"name": "C",
|
||||||
"position": {
|
"position": {
|
||||||
"x": -224.0,
|
"x": -96.0,
|
||||||
"y": -160.0
|
"y": -288.0
|
||||||
},
|
},
|
||||||
"rotation": 0.0,
|
"rotation": 0.0,
|
||||||
"interface": {
|
"interface": {
|
||||||
"inputs": [],
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "port-2e1d884f",
|
||||||
|
"name": "p",
|
||||||
|
"position": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 64.0,
|
||||||
|
"y": 64.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "power",
|
||||||
|
"multipleConnections": false,
|
||||||
|
"valueType": "real",
|
||||||
|
"quantity": "",
|
||||||
|
"unit": "",
|
||||||
|
"dimensions": {
|
||||||
|
"rows": 1,
|
||||||
|
"columns": 1
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"orientation": "input",
|
||||||
|
"domain": "power",
|
||||||
|
"causality": "preferred effort out"
|
||||||
|
}
|
||||||
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"id": "port-ddc97277",
|
"id": "port-ca7716d5",
|
||||||
"name": "y",
|
"name": "state",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
@@ -87,22 +115,25 @@
|
|||||||
"type": "signal",
|
"type": "signal",
|
||||||
"multipleConnections": false,
|
"multipleConnections": false,
|
||||||
"valueType": "real",
|
"valueType": "real",
|
||||||
"quantity": "Length",
|
"quantity": "",
|
||||||
"unit": "m",
|
"unit": "",
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
"rows": 1,
|
"rows": 1,
|
||||||
"columns": 1
|
"columns": 1
|
||||||
},
|
},
|
||||||
"description": ""
|
"description": "",
|
||||||
|
"orientation": "output",
|
||||||
|
"domain": "power",
|
||||||
|
"causality": "indifferent"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"id": "parameter-022177fc",
|
"id": "parameter-d056bc27",
|
||||||
"name": "v",
|
"name": "c",
|
||||||
"type": "real",
|
"type": "real",
|
||||||
"value": "2",
|
"value": "1",
|
||||||
"quantity": "",
|
"quantity": "",
|
||||||
"unit": "",
|
"unit": "",
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
@@ -123,54 +154,23 @@
|
|||||||
},
|
},
|
||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"cornerRadius": 5.0,
|
"type": "text",
|
||||||
"fill": "#f4f4f4",
|
"x": 40.0,
|
||||||
"height": 64.0,
|
"y": 40.0,
|
||||||
|
"width": 48.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"text": "C",
|
||||||
|
"color": "#303030",
|
||||||
|
"fontSize": 32.0,
|
||||||
"lineStyle": "solid",
|
"lineStyle": "solid",
|
||||||
"lineWidth": 1.5,
|
"lineWidth": 1.5,
|
||||||
"stroke": "#303030",
|
"stroke": "#303030",
|
||||||
"type": "rectangle",
|
"fill": "#ffffff"
|
||||||
"width": 64.0,
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fill": "none",
|
|
||||||
"height": 48.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"type": "line",
|
|
||||||
"width": 0.0,
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fill": "none",
|
|
||||||
"height": 0.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"type": "line",
|
|
||||||
"width": 48.0,
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 88.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fill": "none",
|
|
||||||
"height": 0.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.0,
|
|
||||||
"stroke": "#ffaa00",
|
|
||||||
"type": "line",
|
|
||||||
"width": 48.0,
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 64.0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"showName": true
|
"showName": false
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": true
|
"showSubtree": true
|
||||||
@@ -178,15 +178,15 @@
|
|||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
"source": {
|
"source": {
|
||||||
"equations": "y = v;",
|
"equations": "der(state) = p.f;\np.e = state/c;",
|
||||||
"declarations": "",
|
"declarations": "",
|
||||||
"initialEquations": ""
|
"initialEquations": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "7805cb2f-c938-44b2-a1f8-e1b4af13f7bf",
|
"id": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||||
"name": "Gain0",
|
"name": "1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": -96.0,
|
"x": -96.0,
|
||||||
"y": -160.0
|
"y": -160.0
|
||||||
@@ -195,8 +195,8 @@
|
|||||||
"interface": {
|
"interface": {
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"id": "port-df34ce84",
|
"id": "port-3e53bf80",
|
||||||
"name": "u",
|
"name": "p",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
@@ -207,8 +207,8 @@
|
|||||||
"y": 64.0
|
"y": 64.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "signal",
|
"type": "power",
|
||||||
"multipleConnections": false,
|
"multipleConnections": true,
|
||||||
"valueType": "real",
|
"valueType": "real",
|
||||||
"quantity": "",
|
"quantity": "",
|
||||||
"unit": "",
|
"unit": "",
|
||||||
@@ -216,24 +216,80 @@
|
|||||||
"rows": 1,
|
"rows": 1,
|
||||||
"columns": 1
|
"columns": 1
|
||||||
},
|
},
|
||||||
"description": ""
|
"description": "",
|
||||||
|
"orientation": "indifferent",
|
||||||
|
"domain": "power",
|
||||||
|
"causality": "indifferent"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": []
|
||||||
|
},
|
||||||
|
"parameters": [],
|
||||||
|
"icon": {
|
||||||
|
"shape": "rectangle",
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"border": "#303030",
|
||||||
|
"text": "Text",
|
||||||
|
"size": {
|
||||||
|
"width": 128.0,
|
||||||
|
"height": 128.0
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
{
|
{
|
||||||
"id": "port-fe9e6486",
|
"type": "text",
|
||||||
"name": "y",
|
"x": 40.0,
|
||||||
|
"y": 40.0,
|
||||||
|
"width": 48.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"text": "1",
|
||||||
|
"color": "#303030",
|
||||||
|
"fontSize": 32.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"fill": "#ffffff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"showName": false
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"showSubtree": true
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"kind": "text",
|
||||||
|
"source": {
|
||||||
|
"equations": "flow = p[1].f;\nsum(p[i].e for i in 1:$p_N$) = 0;\nfor i in 2:$p_N$ loop\n p[i].f = p[i-1].f;\nend for;",
|
||||||
|
"declarations": "Real flow;",
|
||||||
|
"initialEquations": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ea95e8ee-7f2d-4e14-a65d-d56bec5b4eb2",
|
||||||
|
"name": "R",
|
||||||
|
"position": {
|
||||||
|
"x": 32.0,
|
||||||
|
"y": -160.0
|
||||||
|
},
|
||||||
|
"rotation": 0.0,
|
||||||
|
"interface": {
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "port-2e1d884f",
|
||||||
|
"name": "p",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0
|
"y": 0.0
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"iconPosition": {
|
"iconPosition": {
|
||||||
"x": 88.0,
|
"x": 64.0,
|
||||||
"y": 40.0
|
"y": 64.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "signal",
|
"type": "power",
|
||||||
"multipleConnections": false,
|
"multipleConnections": false,
|
||||||
"valueType": "real",
|
"valueType": "real",
|
||||||
"quantity": "",
|
"quantity": "",
|
||||||
@@ -242,18 +298,22 @@
|
|||||||
"rows": 1,
|
"rows": 1,
|
||||||
"columns": 1
|
"columns": 1
|
||||||
},
|
},
|
||||||
"description": ""
|
"description": "",
|
||||||
|
"orientation": "input",
|
||||||
|
"domain": "power",
|
||||||
|
"causality": "indifferent"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"outputs": []
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"id": "parameter-61a43a86",
|
"id": "parameter-d056bc27",
|
||||||
"name": "k",
|
"name": "r",
|
||||||
"type": "real",
|
"type": "real",
|
||||||
"value": "2.5",
|
"value": "1",
|
||||||
"quantity": "Current",
|
"quantity": "",
|
||||||
"unit": "A",
|
"unit": "",
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
"rows": 1,
|
"rows": 1,
|
||||||
"columns": 1
|
"columns": 1
|
||||||
@@ -272,35 +332,23 @@
|
|||||||
},
|
},
|
||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"cornerRadius": 5.0,
|
"type": "text",
|
||||||
"fill": "#f4f4f4",
|
"x": 40.0,
|
||||||
"height": 64.0,
|
"y": 40.0,
|
||||||
|
"width": 48.0,
|
||||||
|
"height": 48.0,
|
||||||
|
"text": "R",
|
||||||
|
"color": "#303030",
|
||||||
|
"fontSize": 32.0,
|
||||||
"lineStyle": "solid",
|
"lineStyle": "solid",
|
||||||
"lineWidth": 1.5,
|
"lineWidth": 1.5,
|
||||||
"stroke": "#303030",
|
"stroke": "#303030",
|
||||||
"type": "rectangle",
|
"fill": "#ffffff"
|
||||||
"width": 64.0,
|
|
||||||
"x": 32.0,
|
|
||||||
"y": 32.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "#00007f",
|
|
||||||
"fill": "#ffffff",
|
|
||||||
"fontSize": 24.0,
|
|
||||||
"height": 48.0,
|
|
||||||
"lineStyle": "solid",
|
|
||||||
"lineWidth": 1.5,
|
|
||||||
"stroke": "#00007f",
|
|
||||||
"text": "K",
|
|
||||||
"type": "text",
|
|
||||||
"width": 48.0,
|
|
||||||
"x": 40.0,
|
|
||||||
"y": 40.0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"showName": true
|
"showName": false
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"showSubtree": true
|
"showSubtree": true
|
||||||
@@ -308,23 +356,149 @@
|
|||||||
"implementation": {
|
"implementation": {
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
"source": {
|
"source": {
|
||||||
"equations": "y = k*u;",
|
"equations": "p.e = r*p.f;",
|
||||||
"declarations": "",
|
"declarations": "",
|
||||||
"initialEquations": ""
|
"initialEquations": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "063a6341-cb18-4bfe-a1e4-00957a88a874",
|
||||||
|
"name": "Se",
|
||||||
|
"position": {
|
||||||
|
"x": -224.0,
|
||||||
|
"y": -160.0
|
||||||
|
},
|
||||||
|
"rotation": 0.0,
|
||||||
|
"interface": {
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "port-2e1d884f",
|
||||||
|
"name": "p",
|
||||||
|
"position": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"iconPosition": {
|
||||||
|
"x": 64.0,
|
||||||
|
"y": 64.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "power",
|
||||||
|
"multipleConnections": false,
|
||||||
|
"valueType": "real",
|
||||||
|
"quantity": "",
|
||||||
|
"unit": "",
|
||||||
|
"dimensions": {
|
||||||
|
"rows": 1,
|
||||||
|
"columns": 1
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"orientation": "output",
|
||||||
|
"domain": "power",
|
||||||
|
"causality": "fixed effort out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "parameter-d056bc27",
|
||||||
|
"name": "effort",
|
||||||
|
"type": "real",
|
||||||
|
"value": "1",
|
||||||
|
"quantity": "",
|
||||||
|
"unit": "",
|
||||||
|
"dimensions": {
|
||||||
|
"rows": 1,
|
||||||
|
"columns": 1
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"shape": "rectangle",
|
||||||
|
"fill": "#f4f4f4",
|
||||||
|
"border": "#303030",
|
||||||
|
"text": "Text",
|
||||||
|
"size": {
|
||||||
|
"width": 128.0,
|
||||||
|
"height": 128.0
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"x": 32.0,
|
||||||
|
"y": 32.0,
|
||||||
|
"width": 64.0,
|
||||||
|
"height": 64.0,
|
||||||
|
"text": "Se",
|
||||||
|
"color": "#303030",
|
||||||
|
"fontSize": 32.0,
|
||||||
|
"lineStyle": "solid",
|
||||||
|
"lineWidth": 1.5,
|
||||||
|
"stroke": "#303030",
|
||||||
|
"fill": "#ffffff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"showName": false
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"showSubtree": true
|
||||||
|
},
|
||||||
|
"implementation": {
|
||||||
|
"kind": "text",
|
||||||
|
"source": {
|
||||||
|
"equations": "p.e = effort;\nflow = p.f;",
|
||||||
|
"declarations": "Real flow;",
|
||||||
|
"initialEquations": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"id": "5582d4ad-7c7a-40fe-87d2-9b78410336b3",
|
"id": "4b09ba21-baa0-4db7-9431-4c919478bce4",
|
||||||
"source": {
|
"source": {
|
||||||
"block": "86ba2bf6-0da5-40a4-98e9-fb0d9c12c576",
|
"block": "063a6341-cb18-4bfe-a1e4-00957a88a874",
|
||||||
"port": "port-ddc97277"
|
"port": "port-2e1d884f"
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"block": "7805cb2f-c938-44b2-a1f8-e1b4af13f7bf",
|
"block": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||||
"port": "port-df34ce84"
|
"port": "port-3e53bf80"
|
||||||
|
},
|
||||||
|
"name": "",
|
||||||
|
"properties": {
|
||||||
|
"waypoints": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a73a99a1-54e2-461d-a9a7-2d6d11d500f4",
|
||||||
|
"source": {
|
||||||
|
"block": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||||
|
"port": "port-3e53bf80"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"block": "ea95e8ee-7f2d-4e14-a65d-d56bec5b4eb2",
|
||||||
|
"port": "port-2e1d884f"
|
||||||
|
},
|
||||||
|
"name": "",
|
||||||
|
"properties": {
|
||||||
|
"waypoints": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dd5fb1e2-b125-4b5f-a792-dac89dd89ad3",
|
||||||
|
"source": {
|
||||||
|
"block": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||||
|
"port": "port-3e53bf80"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"block": "c6169e52-e993-4293-8fc8-5b97e8ad59be",
|
||||||
|
"port": "port-2e1d884f"
|
||||||
},
|
},
|
||||||
"name": "",
|
"name": "",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
Reference in New Issue
Block a user