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
|
||||
indexes inputs and outputs separately for connection semantics.
|
||||
- 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
|
||||
parameters carry editable value type, quantity, unit, row/column dimensions,
|
||||
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.
|
||||
- Connection appearance is configured per port type in
|
||||
`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`.
|
||||
They use integer layers below or above graph layer 0. Annotation lines reuse
|
||||
connection polyline and absolute `properties.waypoints` semantics. Graph
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
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]:
|
||||
@@ -36,6 +37,9 @@ class Port:
|
||||
rows: int = 1
|
||||
columns: int = 1
|
||||
description: str = ""
|
||||
orientation: str = "input"
|
||||
domain: str = "power"
|
||||
causality: str = "indifferent"
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -50,10 +54,13 @@ class Port:
|
||||
"unit": self.unit,
|
||||
"dimensions": {"rows": self.rows, "columns": self.columns},
|
||||
"description": self.description,
|
||||
"orientation": self.orientation,
|
||||
"domain": self.domain,
|
||||
"causality": self.causality,
|
||||
}
|
||||
|
||||
@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", {})
|
||||
rows, columns = _dimensions(data.get("dimensions", {}), "Port")
|
||||
return cls(
|
||||
@@ -70,6 +77,9 @@ class Port:
|
||||
rows=rows,
|
||||
columns=columns,
|
||||
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)),
|
||||
y=float(position.get("y", 0.0)),
|
||||
rotation=float(data.get("rotation", 0.0)),
|
||||
inputs=[Port.from_dict(item) for item in interface.get("inputs", [])],
|
||||
outputs=[Port.from_dict(item) for item in interface.get("outputs", [])],
|
||||
inputs=[Port.from_dict(item, "input") for item in interface.get("inputs", [])],
|
||||
outputs=[Port.from_dict(item, "output") for item in interface.get("outputs", [])],
|
||||
parameters=[Parameter.from_dict(item) for item in parameters],
|
||||
icon=Icon.from_dict(data.get("icon")),
|
||||
properties=dict(data.get("properties", {})),
|
||||
@@ -528,6 +538,15 @@ class GraphDocument:
|
||||
raise ValueError(f"Component {owner.name} contains duplicate port IDs")
|
||||
for port in (*owner.inputs, *owner.outputs):
|
||||
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():
|
||||
raise ValueError(f"Port {port.name!r} has no value type")
|
||||
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)
|
||||
else:
|
||||
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")
|
||||
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:
|
||||
junction = owner.graph.junctions.get(connection.target.junction)
|
||||
if junction is None:
|
||||
@@ -565,15 +592,27 @@ class GraphDocument:
|
||||
allows_multiple_connections=False,
|
||||
)
|
||||
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")
|
||||
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:
|
||||
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}:
|
||||
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)
|
||||
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")
|
||||
source_key = (
|
||||
"source-junction"
|
||||
@@ -670,27 +709,11 @@ 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.allows_multiple_connections,
|
||||
)
|
||||
Port.from_dict(port.to_dict(), "input")
|
||||
for port in current.inputs
|
||||
],
|
||||
outputs=[
|
||||
Port(
|
||||
port.id,
|
||||
port.name,
|
||||
port.x,
|
||||
port.y,
|
||||
deepcopy(port.properties),
|
||||
port.type,
|
||||
port.allows_multiple_connections,
|
||||
)
|
||||
Port.from_dict(port.to_dict(), "output")
|
||||
for port in current.outputs
|
||||
],
|
||||
parameters=deepcopy(current.parameters),
|
||||
|
||||
@@ -14,6 +14,7 @@ class PortType:
|
||||
class PortTypeRegistry:
|
||||
_types = {
|
||||
"signal": PortType("signal", "Signal", "A scalar signal connection"),
|
||||
"power": PortType("power", "Power", "A two-variable power connection"),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -28,5 +29,13 @@ class PortTypeRegistry:
|
||||
raise ValueError(f"Unknown port type: {type_id}") from error
|
||||
|
||||
@classmethod
|
||||
def compatible(cls, first: str, second: str) -> bool:
|
||||
return cls.get(first).accepts(cls.get(second))
|
||||
def compatible(
|
||||
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")
|
||||
if source_port is None or target_port is None:
|
||||
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}")
|
||||
if not self.endpoint_accepts_connection(source, "source"):
|
||||
raise ValueError(
|
||||
@@ -573,11 +578,24 @@ class DocumentController(QObject):
|
||||
)
|
||||
if endpoint.interface is not None:
|
||||
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:
|
||||
component = owner.graph.blocks.get(endpoint.block or "")
|
||||
if component is None:
|
||||
return None
|
||||
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(
|
||||
(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")
|
||||
input_ids = [port.id for port in inputs]
|
||||
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):
|
||||
raise ValueError("Input and output IDs must be unique")
|
||||
if any(not port.name.strip() for port in (*inputs, *outputs)):
|
||||
@@ -686,7 +707,7 @@ class DocumentController(QObject):
|
||||
)
|
||||
if (
|
||||
connection.source.block == component.id
|
||||
and connection.source.port not in output_ids
|
||||
and connection.source.port not in source_ids
|
||||
):
|
||||
raise ValueError(
|
||||
f"Output {connection.source.port!r} is still connected in the containing graph"
|
||||
@@ -842,6 +863,9 @@ class DocumentController(QObject):
|
||||
return
|
||||
input_ids = {port.id for port in inputs}
|
||||
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)
|
||||
if parent is not None:
|
||||
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")
|
||||
if (
|
||||
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")
|
||||
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.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.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
|
||||
|
||||
|
||||
def _oriented_copy(port: Port, fallback: str) -> Port:
|
||||
copied = deepcopy(port)
|
||||
if copied.orientation != "indifferent":
|
||||
copied.orientation = fallback
|
||||
return copied
|
||||
|
||||
|
||||
class PortOptionsDialog(QDialog):
|
||||
"""Unified editor for a component's typed, oriented ports."""
|
||||
|
||||
@@ -25,8 +33,8 @@ class PortOptionsDialog(QDialog):
|
||||
self.setWindowTitle(f"Port Options — {component.name}")
|
||||
self.read_only = read_only
|
||||
self.ports: list[tuple[Port, str]] = [
|
||||
*((deepcopy(port), "input") for port in component.inputs),
|
||||
*((deepcopy(port), "output") for port in component.outputs),
|
||||
*((_oriented_copy(port, "input"), port.orientation or "input") for port in component.inputs),
|
||||
*((_oriented_copy(port, "output"), "output") for port in component.outputs),
|
||||
]
|
||||
self._loading = False
|
||||
self.ui.typeCombo.clear()
|
||||
@@ -36,6 +44,10 @@ class PortOptionsDialog(QDialog):
|
||||
self.ui.unitCombo.addItems(UNITS)
|
||||
self.ui.orientationCombo.setItemData(0, "input")
|
||||
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.addPortButton.clicked.connect(self.add_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.columnsSpin.valueChanged.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])
|
||||
if read_only:
|
||||
self.ui.addPortButton.setEnabled(False)
|
||||
@@ -64,7 +79,11 @@ class PortOptionsDialog(QDialog):
|
||||
|
||||
@property
|
||||
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
|
||||
def outputs(self) -> list[Port]:
|
||||
@@ -96,6 +115,9 @@ class PortOptionsDialog(QDialog):
|
||||
self.ui.rowsSpin.setValue(port.rows)
|
||||
self.ui.columnsSpin.setValue(port.columns)
|
||||
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)
|
||||
else:
|
||||
self.ui.nameEdit.clear()
|
||||
@@ -113,15 +135,27 @@ class PortOptionsDialog(QDialog):
|
||||
self.ui.typeOptionsStack.setEnabled(enabled and not self.read_only)
|
||||
|
||||
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()
|
||||
|
||||
def _show_type_options(self, port_type: str) -> None:
|
||||
self.ui.typeOptionsStack.setCurrentWidget(
|
||||
self.ui.signalOptionsPage
|
||||
if port_type == "signal"
|
||||
else self.ui.unsupportedTypePage
|
||||
)
|
||||
self.ui.orientationCombo.model().item(2).setEnabled(port_type == "power")
|
||||
page = {
|
||||
"signal": self.ui.signalOptionsPage,
|
||||
"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:
|
||||
row = self.ui.portList.currentRow()
|
||||
@@ -136,8 +170,16 @@ class PortOptionsDialog(QDialog):
|
||||
port.unit = self.ui.unitCombo.currentText().strip()
|
||||
port.rows = self.ui.rowsSpin.value()
|
||||
port.columns = self.ui.columnsSpin.value()
|
||||
port.description = self.ui.descriptionEdit.toPlainText()
|
||||
self.ports[row] = (port, self.ui.orientationCombo.currentData())
|
||||
port.domain = self.ui.domainCombo.currentData() or "power"
|
||||
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(
|
||||
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:
|
||||
self._loading = True
|
||||
self.ports = [
|
||||
*((deepcopy(port), "input") for port in inputs),
|
||||
*((deepcopy(port), "output") for port in outputs),
|
||||
*((_oriented_copy(port, "input"), port.orientation or "input") for port in inputs),
|
||||
*((_oriented_copy(port, "output"), "output") for port in outputs),
|
||||
]
|
||||
self._loading = False
|
||||
self._rebuild_list(0 if self.ports else -1)
|
||||
|
||||
@@ -92,6 +92,7 @@ class Ui_PortOptionsDialog(object):
|
||||
self.orientationCombo = QComboBox(self.portDetailsPanel)
|
||||
self.orientationCombo.addItem("")
|
||||
self.orientationCombo.addItem("")
|
||||
self.orientationCombo.addItem("")
|
||||
self.orientationCombo.setObjectName(u"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.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.setObjectName(u"unsupportedTypePage")
|
||||
self.unsupportedTypeLayout = QVBoxLayout(self.unsupportedTypePage)
|
||||
@@ -228,6 +284,7 @@ class Ui_PortOptionsDialog(object):
|
||||
self.orientationLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Orientation:", None))
|
||||
self.orientationCombo.setItemText(0, QCoreApplication.translate("PortOptionsDialog", u"Input", 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.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.rowsColumnsLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Matrix size:", 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))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
|
||||
ArrowStyle = Literal["open", "half", "filled"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConnectionStyle:
|
||||
color: str = "#285f9e"
|
||||
@@ -13,11 +17,17 @@ class ConnectionStyle:
|
||||
arrow_at_source: bool = False
|
||||
arrow_at_target: bool = True
|
||||
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.
|
||||
CONNECTION_STYLES: dict[str, ConnectionStyle] = {
|
||||
"signal": ConnectionStyle(),
|
||||
"power": ConnectionStyle(width=3.0, arrow_style="half", color="#000000"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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.QtWidgets import (
|
||||
QColorDialog,
|
||||
@@ -487,7 +487,13 @@ class IconEditorDialog(QDialog):
|
||||
self.scene.addItem(ShapeItem(element))
|
||||
self._add_ports(self.inputs, "input", 0.0)
|
||||
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:
|
||||
spacing = self.icon.height / (len(ports) + 1)
|
||||
|
||||
@@ -485,23 +485,39 @@ class ConnectionGraphicsItem(QGraphicsPathItem):
|
||||
handle.setVisible(self.isSelected())
|
||||
|
||||
@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)
|
||||
unit = QPointF(direction.x() / length, direction.y() / length)
|
||||
normal = QPointF(-unit.y(), unit.x())
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
painter.drawPolygon(
|
||||
self._arrow(self.start, -self.start_direction, self.style.arrow_size)
|
||||
)
|
||||
self._draw_arrow(painter, self.start, -self.start_direction)
|
||||
|
||||
|
||||
class WaypointHandle(QGraphicsEllipseItem):
|
||||
@@ -1207,9 +1223,22 @@ class GraphScene(QGraphicsScene):
|
||||
def add_pairs(
|
||||
source_item: ComponentGraphicsItem, target_item: ComponentGraphicsItem
|
||||
) -> 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:
|
||||
if not PortTypeRegistry.compatible(output.type, input_port.type):
|
||||
if not PortTypeRegistry.compatible(
|
||||
output.type,
|
||||
input_port.type,
|
||||
output.domain,
|
||||
input_port.domain,
|
||||
):
|
||||
continue
|
||||
source = Endpoint(block=source_item.component_id, port=output.id)
|
||||
target = Endpoint(block=target_item.component_id, port=input_port.id)
|
||||
@@ -1240,7 +1269,9 @@ class GraphScene(QGraphicsScene):
|
||||
if terminal.direction == "input":
|
||||
for port in component.component.inputs:
|
||||
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
|
||||
if not self.controller.endpoint_accepts_connection(interface, "source"):
|
||||
continue
|
||||
@@ -1254,9 +1285,19 @@ class GraphScene(QGraphicsScene):
|
||||
)
|
||||
)
|
||||
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)
|
||||
if not PortTypeRegistry.compatible(port.type, terminal.port.type):
|
||||
if not PortTypeRegistry.compatible(
|
||||
port.type, terminal.port.type, port.domain, terminal.port.domain
|
||||
):
|
||||
continue
|
||||
if not self.controller.endpoint_accepts_connection(source, "source"):
|
||||
continue
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
<string>Output</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Indifferent</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
@@ -222,6 +227,20 @@
|
||||
</item>
|
||||
</layout>
|
||||
</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">
|
||||
<layout class="QVBoxLayout" name="unsupportedTypeLayout">
|
||||
<item>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"roots": [
|
||||
{
|
||||
"id": "4014ba90-5538-4050-a9be-552318197ce1",
|
||||
"id": "4aefc7e3-ac23-4b31-9087-bbad16156be0",
|
||||
"name": "New Graph Block 1",
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
@@ -61,19 +61,47 @@
|
||||
"graph": {
|
||||
"blocks": [
|
||||
{
|
||||
"id": "86ba2bf6-0da5-40a4-98e9-fb0d9c12c576",
|
||||
"name": "Constant0",
|
||||
"id": "c6169e52-e993-4293-8fc8-5b97e8ad59be",
|
||||
"name": "C",
|
||||
"position": {
|
||||
"x": -224.0,
|
||||
"y": -160.0
|
||||
"x": -96.0,
|
||||
"y": -288.0
|
||||
},
|
||||
"rotation": 0.0,
|
||||
"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": [
|
||||
{
|
||||
"id": "port-ddc97277",
|
||||
"name": "y",
|
||||
"id": "port-ca7716d5",
|
||||
"name": "state",
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
@@ -87,22 +115,25 @@
|
||||
"type": "signal",
|
||||
"multipleConnections": false,
|
||||
"valueType": "real",
|
||||
"quantity": "Length",
|
||||
"unit": "m",
|
||||
"quantity": "",
|
||||
"unit": "",
|
||||
"dimensions": {
|
||||
"rows": 1,
|
||||
"columns": 1
|
||||
},
|
||||
"description": ""
|
||||
"description": "",
|
||||
"orientation": "output",
|
||||
"domain": "power",
|
||||
"causality": "indifferent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"id": "parameter-022177fc",
|
||||
"name": "v",
|
||||
"id": "parameter-d056bc27",
|
||||
"name": "c",
|
||||
"type": "real",
|
||||
"value": "2",
|
||||
"value": "1",
|
||||
"quantity": "",
|
||||
"unit": "",
|
||||
"dimensions": {
|
||||
@@ -123,54 +154,23 @@
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"cornerRadius": 5.0,
|
||||
"fill": "#f4f4f4",
|
||||
"height": 64.0,
|
||||
"type": "text",
|
||||
"x": 40.0,
|
||||
"y": 40.0,
|
||||
"width": 48.0,
|
||||
"height": 48.0,
|
||||
"text": "C",
|
||||
"color": "#303030",
|
||||
"fontSize": 32.0,
|
||||
"lineStyle": "solid",
|
||||
"lineWidth": 1.5,
|
||||
"stroke": "#303030",
|
||||
"type": "rectangle",
|
||||
"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
|
||||
"fill": "#ffffff"
|
||||
}
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"showName": true
|
||||
"showName": false
|
||||
},
|
||||
"library": {
|
||||
"showSubtree": true
|
||||
@@ -178,15 +178,15 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = v;",
|
||||
"equations": "der(state) = p.f;\np.e = state/c;",
|
||||
"declarations": "",
|
||||
"initialEquations": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7805cb2f-c938-44b2-a1f8-e1b4af13f7bf",
|
||||
"name": "Gain0",
|
||||
"id": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||
"name": "1",
|
||||
"position": {
|
||||
"x": -96.0,
|
||||
"y": -160.0
|
||||
@@ -195,8 +195,8 @@
|
||||
"interface": {
|
||||
"inputs": [
|
||||
{
|
||||
"id": "port-df34ce84",
|
||||
"name": "u",
|
||||
"id": "port-3e53bf80",
|
||||
"name": "p",
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
@@ -207,8 +207,8 @@
|
||||
"y": 64.0
|
||||
}
|
||||
},
|
||||
"type": "signal",
|
||||
"multipleConnections": false,
|
||||
"type": "power",
|
||||
"multipleConnections": true,
|
||||
"valueType": "real",
|
||||
"quantity": "",
|
||||
"unit": "",
|
||||
@@ -216,24 +216,80 @@
|
||||
"rows": 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",
|
||||
"name": "y",
|
||||
"type": "text",
|
||||
"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": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"properties": {
|
||||
"iconPosition": {
|
||||
"x": 88.0,
|
||||
"y": 40.0
|
||||
"x": 64.0,
|
||||
"y": 64.0
|
||||
}
|
||||
},
|
||||
"type": "signal",
|
||||
"type": "power",
|
||||
"multipleConnections": false,
|
||||
"valueType": "real",
|
||||
"quantity": "",
|
||||
@@ -242,18 +298,22 @@
|
||||
"rows": 1,
|
||||
"columns": 1
|
||||
},
|
||||
"description": ""
|
||||
"description": "",
|
||||
"orientation": "input",
|
||||
"domain": "power",
|
||||
"causality": "indifferent"
|
||||
}
|
||||
]
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"id": "parameter-61a43a86",
|
||||
"name": "k",
|
||||
"id": "parameter-d056bc27",
|
||||
"name": "r",
|
||||
"type": "real",
|
||||
"value": "2.5",
|
||||
"quantity": "Current",
|
||||
"unit": "A",
|
||||
"value": "1",
|
||||
"quantity": "",
|
||||
"unit": "",
|
||||
"dimensions": {
|
||||
"rows": 1,
|
||||
"columns": 1
|
||||
@@ -272,35 +332,23 @@
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"cornerRadius": 5.0,
|
||||
"fill": "#f4f4f4",
|
||||
"height": 64.0,
|
||||
"type": "text",
|
||||
"x": 40.0,
|
||||
"y": 40.0,
|
||||
"width": 48.0,
|
||||
"height": 48.0,
|
||||
"text": "R",
|
||||
"color": "#303030",
|
||||
"fontSize": 32.0,
|
||||
"lineStyle": "solid",
|
||||
"lineWidth": 1.5,
|
||||
"stroke": "#303030",
|
||||
"type": "rectangle",
|
||||
"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
|
||||
"fill": "#ffffff"
|
||||
}
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"showName": true
|
||||
"showName": false
|
||||
},
|
||||
"library": {
|
||||
"showSubtree": true
|
||||
@@ -308,23 +356,149 @@
|
||||
"implementation": {
|
||||
"kind": "text",
|
||||
"source": {
|
||||
"equations": "y = k*u;",
|
||||
"equations": "p.e = r*p.f;",
|
||||
"declarations": "",
|
||||
"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": [
|
||||
{
|
||||
"id": "5582d4ad-7c7a-40fe-87d2-9b78410336b3",
|
||||
"id": "4b09ba21-baa0-4db7-9431-4c919478bce4",
|
||||
"source": {
|
||||
"block": "86ba2bf6-0da5-40a4-98e9-fb0d9c12c576",
|
||||
"port": "port-ddc97277"
|
||||
"block": "063a6341-cb18-4bfe-a1e4-00957a88a874",
|
||||
"port": "port-2e1d884f"
|
||||
},
|
||||
"target": {
|
||||
"block": "7805cb2f-c938-44b2-a1f8-e1b4af13f7bf",
|
||||
"port": "port-df34ce84"
|
||||
"block": "bfa506d8-ee41-4bab-ae19-a97fd67bfcf6",
|
||||
"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": "",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user