More types and units

This commit is contained in:
2026-07-22 11:38:07 +02:00
parent 48ac9e2f59
commit edefb23edc
21 changed files with 1355 additions and 760 deletions

View File

@@ -8,6 +8,19 @@ from uuid import uuid4
from bedit.core.port_types import PortTypeRegistry
def _dimensions(data: Any, subject: str) -> tuple[int, int]:
if not isinstance(data, dict):
raise ValueError(f"{subject} dimensions must be an object")
try:
rows = int(data.get("rows", 1))
columns = int(data.get("columns", 1))
except (TypeError, ValueError) as error:
raise ValueError(f"{subject} dimensions must be whole numbers") from error
if rows < 1 or columns < 1:
raise ValueError(f"{subject} dimensions must be at least 1")
return rows, columns
@dataclass
class Port:
id: str
@@ -17,6 +30,12 @@ class Port:
properties: dict[str, Any] = field(default_factory=dict)
type: str = "signal"
allows_multiple_connections: bool = False
value_type: str = "real"
quantity: str = ""
unit: str = ""
rows: int = 1
columns: int = 1
description: str = ""
def to_dict(self) -> dict[str, Any]:
return {
@@ -26,11 +45,17 @@ class Port:
"properties": self.properties,
"type": self.type,
"multipleConnections": self.allows_multiple_connections,
"valueType": self.value_type,
"quantity": self.quantity,
"unit": self.unit,
"dimensions": {"rows": self.rows, "columns": self.columns},
"description": self.description,
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Port":
position = data.get("position", {})
rows, columns = _dimensions(data.get("dimensions", {}), "Port")
return cls(
id=str(data["id"]),
name=str(data.get("name", data["id"])),
@@ -39,6 +64,12 @@ class Port:
properties=dict(data.get("properties", {})),
type=str(data.get("type", "signal")),
allows_multiple_connections=bool(data.get("multipleConnections", False)),
value_type=str(data.get("valueType", "real")),
quantity=str(data.get("quantity", "")),
unit=str(data.get("unit", "")),
rows=rows,
columns=columns,
description=str(data.get("description", "")),
)
@@ -166,9 +197,23 @@ class Parameter:
name: str
type: str = "real"
value: str = "0"
quantity: str = ""
unit: str = ""
rows: int = 1
columns: int = 1
description: str = ""
def to_dict(self) -> dict[str, str]:
return {"id": self.id, "name": self.name, "type": self.type, "value": self.value}
def to_dict(self) -> dict[str, Any]:
return {
"id": self.id,
"name": self.name,
"type": self.type,
"value": self.value,
"quantity": self.quantity,
"unit": self.unit,
"dimensions": {"rows": self.rows, "columns": self.columns},
"description": self.description,
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Parameter":
@@ -176,11 +221,17 @@ class Parameter:
raise ValueError("Each component parameter must be an object")
if "id" not in data:
raise ValueError("Each component parameter must have an ID")
rows, columns = _dimensions(data.get("dimensions", {}), "Parameter")
return cls(
id=str(data["id"]),
name=str(data.get("name", "")),
type=str(data.get("type", "real")),
value=str(data.get("value", "0")),
quantity=str(data.get("quantity", "")),
unit=str(data.get("unit", "")),
rows=rows,
columns=columns,
description=str(data.get("description", "")),
)
@@ -453,6 +504,13 @@ class GraphDocument:
raise ValueError(f"Component {component.name!r} has duplicate parameter names")
if any(not name.strip() for name in parameter_names):
raise ValueError(f"Component {component.name!r} has an unnamed parameter")
if any(not parameter.type.strip() for parameter in component.parameters):
raise ValueError(f"Component {component.name!r} has an untyped parameter")
if any(
parameter.rows < 1 or parameter.columns < 1
for parameter in component.parameters
):
raise ValueError(f"Component {component.name!r} has invalid parameter dimensions")
if component.implementation_kind == "text" and component.graph.blocks:
raise ValueError(f"Text component {component.name} cannot contain a graph")
self._validate_graph(component)
@@ -470,6 +528,10 @@ class GraphDocument:
raise ValueError(f"Component {owner.name} contains duplicate port IDs")
for port in (*owner.inputs, *owner.outputs):
PortTypeRegistry.get(port.type)
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:
raise ValueError(f"Port {port.name!r} has invalid dimensions")
for junction in owner.graph.junctions.values():
PortTypeRegistry.get(junction.type)
endpoint_counts: dict[tuple[str, str, str], int] = {}

View File

@@ -0,0 +1,47 @@
"""Editable suggestions for signal and parameter physical metadata.
These values populate editable combo boxes; documents may use values not listed
here. They are suggestions rather than a validation registry.
"""
VALUE_TYPES = ("real", "integer", "boolean", "string")
QUANTITIES = (
"",
"Angle",
"AngularVelocity",
"Current",
"Energy",
"Force",
"Frequency",
"Length",
"Mass",
"Power",
"Pressure",
"Temperature",
"Time",
"Torque",
"Velocity",
"Voltage",
)
UNITS = (
"",
"1",
"A",
"Hz",
"J",
"K",
"N",
"N.m",
"Pa",
"V",
"W",
"deg",
"kg",
"m",
"m/s",
"rad",
"rad/s",
"s",
)

View File

@@ -1,5 +1,5 @@
from bedit.core.simulation.service import Simulation
from bedit.core.simulation.openmodelica import OpenModelicaInterface
from bedit.core.simulation.openmodelica import ModelBuildResult, OpenModelicaInterface
from bedit.core.simulation.results import (
BerSimulationResultsSerializer,
JsonSimulationResultsSerializer,
@@ -12,6 +12,7 @@ from bedit.core.simulation.results import (
__all__ = [
"OpenModelicaInterface",
"ModelBuildResult",
"Simulation",
"SimulationResults",
"SimulationExecutionResult",

View File

@@ -119,8 +119,11 @@ def emit_model(
parameter_type = modelica_type(parameter.get("type", "real"))
parameter_name = identifier(parameter["name"])
value = expand_bevalues(str(parameter.get("value", "0")), macros)
dimensions = _fixed_dimensions(parameter)
attributes = _quantity_unit_attributes(parameter)
lines.append(
f"{body_indent}parameter {parameter_type} {parameter_name} = {value};"
f"{body_indent}parameter {parameter_type} {parameter_name}"
f"{dimensions}{attributes} = {value};"
)
if implementation_kind == "text":
@@ -213,14 +216,48 @@ def cleanup_graph(graph: dict[str, Any]) -> dict[str, Any]:
def _port_declaration(
port: dict[str, Any], direction: str, indent: int, macros: dict[str, str]
) -> str:
port_type = modelica_type(port.get("type", "signal"))
port_type = modelica_type(port.get("valueType", "real"))
indentation = "\t" * indent
port_name = identifier(port["name"])
dimension = f"[${port_name}_N$]" if port.get("multipleConnections", False) else ""
declaration = f"{indentation}{direction} {port_type} {port_name}{dimension};"
dimensions = _port_dimensions(port, port_name)
attributes = _quantity_unit_attributes(port)
declaration = (
f"{indentation}{direction} {port_type} {port_name}{dimensions}{attributes};"
)
return expand_bevalues(declaration, macros)
def _fixed_dimensions(item: dict[str, Any]) -> str:
dimensions = item.get("dimensions", {})
rows = max(1, int(dimensions.get("rows", 1)))
columns = max(1, int(dimensions.get("columns", 1)))
if rows == columns == 1:
return ""
if columns == 1:
return f"[{rows}]"
return f"[{rows},{columns}]"
def _port_dimensions(port: dict[str, Any], port_name: str) -> str:
fixed = _fixed_dimensions(port)
if not port.get("multipleConnections", False):
return fixed
entries = [f"${port_name}_N$"]
if fixed:
entries.extend(fixed[1:-1].split(","))
return f"[{','.join(entries)}]"
def _quantity_unit_attributes(item: dict[str, Any]) -> str:
attributes = []
for key in ("quantity", "unit"):
value = str(item.get(key, "")).strip()
if value:
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
attributes.append(f'{key}="{escaped}"')
return f"({', '.join(attributes)})" if attributes else ""
def _endpoint_expression(
endpoint: dict[str, Any],
owner: dict[str, Any],

View File

@@ -48,6 +48,14 @@ class SimulationMessage:
text: str
@dataclass(frozen=True)
class ModelBuildResult:
"""OMC's model check summary and subsequent build response."""
check_summary: str
build_result: Any
class OpenModelicaInterface:
"""Asynchronous, persistent interface to one OpenModelica session.
@@ -92,10 +100,21 @@ class OpenModelicaInterface:
"""Load and build one composed model as an ordered worker operation."""
def operation(omc, _temp_dir: Path):
unit_checking = omc.sendExpression(
'setCommandLineOptions("--unitChecking")'
)
if unit_checking is not True:
raise RuntimeError("OpenModelica could not enable unit checking")
loaded = omc.sendExpression(f"loadString({json.dumps(model)})")
if loaded is not True:
raise RuntimeError("OpenModelica could not load the composed model")
return omc.sendExpression(f"buildModel({model_name})")
check_summary = omc.sendExpression(f"checkModel({model_name})")
if not isinstance(check_summary, str) or not check_summary.strip():
raise RuntimeError(
"OpenModelica did not return a model-check summary"
)
build_result = omc.sendExpression(f"buildModel({model_name})")
return ModelBuildResult(check_summary, build_result)
self._submit("build model", operation, callback, error_callback)
@@ -334,16 +353,140 @@ def _run_model_with_tcp(
f"OpenModelica did not create the expected result file {result_path.name!r}"
)
data = load_openmodelica_csv(result_path)
init_path = temp_dir / f"{Path(executable).stem}_init.xml"
csv_column_count = len(data)
data = _add_openmodelica_alias_signals(init_path, data)
signal_metadata = _load_openmodelica_signal_metadata(init_path, data)
log.info(
"Loaded %d result columns from %s", len(data), result_path.name
"Loaded %d result columns, restored %d OMC aliases, and found %d "
"unit definitions from %s",
csv_column_count,
len(data) - csv_column_count,
len(signal_metadata),
result_path.name,
)
return SimulationExecutionResult(
return_code=int(return_code),
result_file=str(result_path),
data=data,
signal_metadata=signal_metadata,
)
def _add_openmodelica_alias_signals(
path: Path, data: dict[str, list[float]]
) -> dict[str, list[float]]:
"""Restore result aliases exactly as described by OMC's init XML."""
try:
root = ET.parse(path).getroot()
except (OSError, ET.ParseError) as error:
raise RuntimeError(
f"Could not read OpenModelica aliases from {path.name}: {error}"
) from error
variables: dict[str, dict[str, str]] = {}
for scalar in root.findall(".//ScalarVariable"):
name = scalar.get("name", "")
if not name:
continue
value = next(iter(scalar), None)
variables[name] = {
"alias": scalar.get("alias", "noAlias"),
"target": scalar.get("aliasVariable", ""),
"variability": scalar.get("variability", ""),
"start": value.get("start", "") if value is not None else "",
}
restored = {name: list(values) for name, values in data.items()}
sample_count = len(next(iter(data.values()), []))
def resolve(name: str, visited: set[str] | None = None) -> list[float] | None:
if name in restored:
return restored[name]
variable = variables.get(name)
if variable is None:
return None
visited = set() if visited is None else visited
if name in visited:
return None
visited.add(name)
alias_kind = variable["alias"]
target = variable["target"]
if alias_kind in {"alias", "negatedAlias"} and target:
values = resolve(target, visited)
if values is None:
return None
return [-value for value in values] if alias_kind == "negatedAlias" else list(values)
if variable["variability"] not in {"parameter", "constant"}:
return None
try:
value = float(variable["start"])
except ValueError:
return None
return [value] * sample_count
for name, variable in variables.items():
if variable["alias"] not in {"alias", "negatedAlias"} or name in restored:
continue
values = resolve(name)
if values is not None:
restored[name] = values
return restored
def _load_openmodelica_signal_metadata(
path: Path, signal_names: dict[str, list[float]]
) -> dict[str, dict[str, str]]:
"""Read units reported by OMC in its generated initialization XML."""
try:
root = ET.parse(path).getroot()
except (OSError, ET.ParseError) as error:
raise RuntimeError(
f"Could not read OpenModelica unit metadata from {path.name}: {error}"
) from error
variables: dict[str, tuple[dict[str, str], str]] = {}
for scalar in root.findall(".//ScalarVariable"):
name = scalar.get("name", "")
if not name:
continue
value = next(iter(scalar), None)
metadata: dict[str, str] = {}
if value is not None:
for key in ("unit", "displayUnit", "quantity"):
attribute = value.get(key, "")
if attribute:
metadata[key] = attribute
description = scalar.get("description", "")
if description:
metadata["description"] = description
variables[name] = (metadata, scalar.get("aliasVariable", ""))
def resolve(name: str, visited: set[str] | None = None) -> dict[str, str]:
metadata, alias = variables.get(name, ({}, ""))
if metadata.get("unit") or not alias:
return dict(metadata)
visited = set() if visited is None else visited
if name in visited:
return dict(metadata)
visited.add(name)
inherited = resolve(alias, visited)
return {**inherited, **metadata}
metadata: dict[str, dict[str, str]] = {}
for name in signal_names:
if name not in variables:
continue
resolved = resolve(name)
if resolved:
metadata[name] = resolved
if "time" in signal_names:
metadata["time"] = {"unit": "s", "quantity": "Time"}
return metadata
def _accept_simulation_connection(
server: socket.socket, command_finished: Event
) -> socket.socket:

View File

@@ -44,6 +44,7 @@ class SimulationResults:
status: dict[str, Any] = field(default_factory=dict)
messages: list[dict[str, str]] = field(default_factory=list)
data: dict[str, list[float]] = field(default_factory=dict)
signal_metadata: dict[str, dict[str, str]] = field(default_factory=dict)
graphs: list[SimulationGraph] = field(
default_factory=lambda: [SimulationGraph()]
)
@@ -57,6 +58,10 @@ class SimulationResults:
"status": dict(self.status),
"messages": [dict(message) for message in self.messages],
"data": {name: list(values) for name, values in self.data.items()},
"signalMetadata": {
name: dict(metadata)
for name, metadata in self.signal_metadata.items()
},
"graphs": [asdict(graph) for graph in self.graphs],
"metadata": dict(self.metadata),
}
@@ -87,6 +92,15 @@ class SimulationResults:
str(name): [float(value) for value in values]
for name, values in dict(data.get("data", {})).items()
},
signal_metadata={
str(name): {
str(key): str(value)
for key, value in dict(metadata).items()
}
for name, metadata in dict(
data.get("signalMetadata", {})
).items()
},
graphs=graphs,
metadata=dict(data.get("metadata", {})),
)
@@ -101,6 +115,7 @@ class SimulationExecutionResult:
return_code: int
result_file: str
data: dict[str, list[float]]
signal_metadata: dict[str, dict[str, str]] = field(default_factory=dict)
def load_openmodelica_csv(path: str | Path) -> dict[str, list[float]]:

View File

@@ -5,6 +5,7 @@ from bedit.core.application_log import get_logger
from bedit.core.simulation.composer import compose_graph
from bedit.core.simulation.openmodelica import (
ErrorCallback,
ModelBuildResult,
OpenModelicaInterface,
ResultCallback,
SimulationMessage,
@@ -50,9 +51,19 @@ class Simulation:
self.compose_source(graph)
def _model_compiled(result):
log.info("Compiling OK: %s", result)
if not isinstance(result, ModelBuildResult):
failure = RuntimeError(
f"OpenModelica returned an invalid build response: {result!r}"
)
if error_callback is not None:
error_callback(failure)
else:
log.error("%s", failure)
return
log.info("%s", result.check_summary.strip())
log.info("Compiling OK: %s", result.build_result)
try:
self.model_path = str(result[0])
self.model_path = str(result.build_result[0])
except (IndexError, TypeError) as error:
failure = RuntimeError(
f"OpenModelica returned an invalid build result: {result!r}"

View File

@@ -5,6 +5,7 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMessageBox
from bedit.core.model import Component, Parameter
from bedit.core.physical_types import QUANTITIES, UNITS
from bedit.gui.generated.ui_parameter_options_dialog import Ui_ParameterOptionsDialog
@@ -22,19 +23,31 @@ class ParameterOptionsDialog(QDialog):
self.parameters = deepcopy(component.parameters)
self.read_only = read_only
self._loading = False
self.ui.quantityCombo.addItems(QUANTITIES)
self.ui.unitCombo.addItems(UNITS)
self.ui.parameterList.currentRowChanged.connect(self._load_current)
self.ui.addParameterButton.clicked.connect(self.add_parameter)
self.ui.removeParameterButton.clicked.connect(self.remove_parameter)
self.ui.nameEdit.textEdited.connect(self._store_current)
self.ui.typeEdit.textEdited.connect(self._store_current)
self.ui.typeCombo.currentTextChanged.connect(self._store_current)
self.ui.valueEdit.textEdited.connect(self._store_current)
self.ui.quantityCombo.currentTextChanged.connect(self._store_current)
self.ui.unitCombo.currentTextChanged.connect(self._store_current)
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.parameterSplitter.setSizes([250, 370])
if read_only:
self.ui.addParameterButton.setEnabled(False)
self.ui.removeParameterButton.setEnabled(False)
self.ui.nameEdit.setReadOnly(True)
self.ui.typeEdit.setReadOnly(True)
self.ui.typeCombo.setEnabled(False)
self.ui.valueEdit.setReadOnly(True)
self.ui.quantityCombo.setEnabled(False)
self.ui.unitCombo.setEnabled(False)
self.ui.rowsSpin.setEnabled(False)
self.ui.columnsSpin.setEnabled(False)
self.ui.descriptionEdit.setReadOnly(True)
self._rebuild_list(0 if self.parameters else -1)
def _rebuild_list(self, row: int = -1) -> None:
@@ -51,12 +64,20 @@ class ParameterOptionsDialog(QDialog):
if 0 <= row < len(self.parameters):
parameter = self.parameters[row]
self.ui.nameEdit.setText(parameter.name)
self.ui.typeEdit.setText(parameter.type)
self.ui.typeCombo.setCurrentText(parameter.type)
self.ui.valueEdit.setText(parameter.value)
self.ui.quantityCombo.setCurrentText(parameter.quantity)
self.ui.unitCombo.setCurrentText(parameter.unit)
self.ui.rowsSpin.setValue(parameter.rows)
self.ui.columnsSpin.setValue(parameter.columns)
self.ui.descriptionEdit.setPlainText(parameter.description)
else:
self.ui.nameEdit.clear()
self.ui.typeEdit.clear()
self.ui.typeCombo.setCurrentText("")
self.ui.valueEdit.clear()
self.ui.quantityCombo.setCurrentText("")
self.ui.unitCombo.setCurrentText("")
self.ui.descriptionEdit.clear()
self._loading = False
self._update_enabled()
@@ -64,8 +85,13 @@ class ParameterOptionsDialog(QDialog):
enabled = self.ui.parameterList.currentRow() >= 0
self.ui.removeParameterButton.setEnabled(enabled and not self.read_only)
self.ui.nameEdit.setEnabled(enabled)
self.ui.typeEdit.setEnabled(enabled)
self.ui.typeCombo.setEnabled(enabled and not self.read_only)
self.ui.valueEdit.setEnabled(enabled)
self.ui.quantityCombo.setEnabled(enabled and not self.read_only)
self.ui.unitCombo.setEnabled(enabled and not self.read_only)
self.ui.rowsSpin.setEnabled(enabled and not self.read_only)
self.ui.columnsSpin.setEnabled(enabled and not self.read_only)
self.ui.descriptionEdit.setEnabled(enabled)
def _store_current(self) -> None:
row = self.ui.parameterList.currentRow()
@@ -73,8 +99,13 @@ class ParameterOptionsDialog(QDialog):
return
parameter = self.parameters[row]
parameter.name = self.ui.nameEdit.text()
parameter.type = self.ui.typeEdit.text()
parameter.type = self.ui.typeCombo.currentText().strip() or "real"
parameter.value = self.ui.valueEdit.text()
parameter.quantity = self.ui.quantityCombo.currentText().strip()
parameter.unit = self.ui.unitCombo.currentText().strip()
parameter.rows = self.ui.rowsSpin.value()
parameter.columns = self.ui.columnsSpin.value()
parameter.description = self.ui.descriptionEdit.toPlainText()
self.ui.parameterList.item(row).setText(
f"{parameter.name} [{parameter.type}] = {parameter.value}"
)

View File

@@ -5,6 +5,7 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem, QMessageBox
from bedit.core.model import Component, Port
from bedit.core.physical_types import QUANTITIES, UNITS
from bedit.core.port_types import PortTypeRegistry
from bedit.gui.generated.ui_port_options_dialog import Ui_PortOptionsDialog
@@ -20,6 +21,7 @@ class PortOptionsDialog(QDialog):
self.ui = Ui_PortOptionsDialog()
self.ui.setupUi(self)
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),
@@ -28,15 +30,23 @@ class PortOptionsDialog(QDialog):
self.ui.typeCombo.clear()
for port_type in PortTypeRegistry.all():
self.ui.typeCombo.addItem(port_type.display_name, port_type.id)
self.ui.quantityCombo.addItems(QUANTITIES)
self.ui.unitCombo.addItems(UNITS)
self.ui.orientationCombo.setItemData(0, "input")
self.ui.orientationCombo.setItemData(1, "output")
self.ui.portList.currentRowChanged.connect(self._load_current)
self.ui.addPortButton.clicked.connect(self.add_port)
self.ui.removePortButton.clicked.connect(self.remove_port)
self.ui.nameEdit.textEdited.connect(self._store_current)
self.ui.typeCombo.currentIndexChanged.connect(self._store_current)
self.ui.typeCombo.currentIndexChanged.connect(self._port_type_changed)
self.ui.orientationCombo.currentIndexChanged.connect(self._store_current)
self.ui.multipleConnectionsCheckBox.toggled.connect(self._store_current)
self.ui.valueTypeCombo.currentTextChanged.connect(self._store_current)
self.ui.quantityCombo.currentTextChanged.connect(self._store_current)
self.ui.unitCombo.currentTextChanged.connect(self._store_current)
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.portSplitter.setSizes([250, 370])
if read_only:
self.ui.addPortButton.setEnabled(False)
@@ -45,6 +55,7 @@ class PortOptionsDialog(QDialog):
self.ui.typeCombo.setEnabled(False)
self.ui.orientationCombo.setEnabled(False)
self.ui.multipleConnectionsCheckBox.setEnabled(False)
self.ui.typeOptionsStack.setEnabled(False)
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("Close")
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).hide()
self._rebuild_list(0 if self.ports else -1)
@@ -77,18 +88,38 @@ class PortOptionsDialog(QDialog):
self.ui.multipleConnectionsCheckBox.setChecked(
port.allows_multiple_connections
)
self.ui.valueTypeCombo.setCurrentText(port.value_type)
self.ui.quantityCombo.setCurrentText(port.quantity)
self.ui.unitCombo.setCurrentText(port.unit)
self.ui.rowsSpin.setValue(port.rows)
self.ui.columnsSpin.setValue(port.columns)
self.ui.descriptionEdit.setPlainText(port.description)
self._show_type_options(port.type)
else:
self.ui.nameEdit.clear()
self.ui.descriptionEdit.clear()
self._loading = False
self._update_enabled()
def _update_enabled(self) -> None:
enabled = self.ui.portList.currentRow() >= 0
self.ui.removePortButton.setEnabled(enabled)
self.ui.removePortButton.setEnabled(enabled and not self.read_only)
self.ui.nameEdit.setEnabled(enabled)
self.ui.typeCombo.setEnabled(enabled)
self.ui.orientationCombo.setEnabled(enabled)
self.ui.multipleConnectionsCheckBox.setEnabled(enabled)
self.ui.typeCombo.setEnabled(enabled and not self.read_only)
self.ui.orientationCombo.setEnabled(enabled and not self.read_only)
self.ui.multipleConnectionsCheckBox.setEnabled(enabled and not self.read_only)
self.ui.typeOptionsStack.setEnabled(enabled and not self.read_only)
def _port_type_changed(self) -> None:
self._show_type_options(self.ui.typeCombo.currentData())
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
)
def _store_current(self) -> None:
row = self.ui.portList.currentRow()
@@ -98,6 +129,12 @@ class PortOptionsDialog(QDialog):
port.name = self.ui.nameEdit.text()
port.type = self.ui.typeCombo.currentData()
port.allows_multiple_connections = self.ui.multipleConnectionsCheckBox.isChecked()
port.value_type = self.ui.valueTypeCombo.currentText().strip() or "real"
port.quantity = self.ui.quantityCombo.currentText().strip()
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())
self.ui.portList.item(row).setText(
f"{port.name} [{self.ports[row][1]}, {port.type}]"

View File

@@ -26,10 +26,10 @@ class TextDefinitionEditor(QWidget):
self._modified = False
self._loading = False
self.ui.portsTable.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch
QHeaderView.ResizeMode.Interactive
)
self.ui.parametersTable.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch
QHeaderView.ResizeMode.Interactive
)
self.ui.columnSplitter.setSizes([560, 340])
self.ui.sourceSplitter.setSizes([180, 180, 240])
@@ -78,6 +78,12 @@ class TextDefinitionEditor(QWidget):
type=type_combo.currentData(),
properties=deepcopy(name_item.data(PROPERTIES_ROLE) or {}),
allows_multiple_connections=multiple_check.isChecked(),
value_type=self.ui.portsTable.item(row, 4).text().strip() or "real",
quantity=self.ui.portsTable.item(row, 5).text().strip(),
unit=self.ui.portsTable.item(row, 6).text().strip(),
rows=int(self.ui.portsTable.item(row, 7).text()),
columns=int(self.ui.portsTable.item(row, 8).text()),
description=self.ui.portsTable.item(row, 9).text(),
)
target = inputs if orientation_combo.currentData() == "input" else outputs
target.append(port)
@@ -92,6 +98,11 @@ class TextDefinitionEditor(QWidget):
name=table.item(row, 0).text().strip(),
type=table.item(row, 1).text().strip(),
value=table.item(row, 2).text(),
quantity=table.item(row, 3).text().strip(),
unit=table.item(row, 4).text().strip(),
rows=int(table.item(row, 5).text()),
columns=int(table.item(row, 6).text()),
description=table.item(row, 7).text(),
)
for row in range(table.rowCount())
]
@@ -138,8 +149,17 @@ class TextDefinitionEditor(QWidget):
self._mark_modified()
def _refresh_editor_symbols(self) -> None:
inputs, outputs = self.ports
self._set_editor_symbols(inputs, outputs, self.parameters)
inputs: list[str] = []
outputs: list[str] = []
for row in range(self.ui.portsTable.rowCount()):
name_item = self.ui.portsTable.item(row, 0)
orientation = self.ui.portsTable.cellWidget(row, 2).currentData()
(inputs if orientation == "input" else outputs).append(name_item.text())
parameters = [
self.ui.parametersTable.item(row, 0).text()
for row in range(self.ui.parametersTable.rowCount())
]
self._apply_editor_symbols(inputs, outputs, parameters)
def _set_editor_symbols(
self,
@@ -147,11 +167,16 @@ class TextDefinitionEditor(QWidget):
outputs: list[Port],
parameters: list[Parameter],
) -> None:
symbols = (
self._apply_editor_symbols(
[port.name for port in inputs],
[port.name for port in outputs],
[parameter.name for parameter in parameters],
)
def _apply_editor_symbols(
self, inputs: list[str], outputs: list[str], parameters: list[str]
) -> None:
symbols = (inputs, outputs, parameters)
self.ui.declarationsEdit.set_symbols(*symbols)
self.ui.initialEquationsEdit.set_symbols(*symbols)
self.ui.equationsEdit.set_symbols(*symbols)
@@ -183,6 +208,12 @@ class TextDefinitionEditor(QWidget):
multiple.setChecked(port.allows_multiple_connections)
multiple.toggled.connect(self._symbols_modified)
table.setCellWidget(row, 3, multiple)
table.setItem(row, 4, QTableWidgetItem(port.value_type))
table.setItem(row, 5, QTableWidgetItem(port.quantity))
table.setItem(row, 6, QTableWidgetItem(port.unit))
table.setItem(row, 7, QTableWidgetItem(str(port.rows)))
table.setItem(row, 8, QTableWidgetItem(str(port.columns)))
table.setItem(row, 9, QTableWidgetItem(port.description))
def _append_parameter(self, parameter: Parameter) -> None:
table = self.ui.parametersTable
@@ -193,6 +224,11 @@ class TextDefinitionEditor(QWidget):
table.setItem(row, 0, name)
table.setItem(row, 1, QTableWidgetItem(parameter.type))
table.setItem(row, 2, QTableWidgetItem(parameter.value))
table.setItem(row, 3, QTableWidgetItem(parameter.quantity))
table.setItem(row, 4, QTableWidgetItem(parameter.unit))
table.setItem(row, 5, QTableWidgetItem(str(parameter.rows)))
table.setItem(row, 6, QTableWidgetItem(str(parameter.columns)))
table.setItem(row, 7, QTableWidgetItem(parameter.description))
def add_port(self) -> None:
port = Port(

View File

@@ -15,16 +15,17 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
QFormLayout, QHBoxLayout, QLabel, QLineEdit,
QListWidget, QListWidgetItem, QPushButton, QSizePolicy,
QSplitter, QVBoxLayout, QWidget)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QHBoxLayout, QLabel,
QLineEdit, QListWidget, QListWidgetItem, QPlainTextEdit,
QPushButton, QSizePolicy, QSpinBox, QSplitter,
QVBoxLayout, QWidget)
class Ui_ParameterOptionsDialog(object):
def setupUi(self, ParameterOptionsDialog):
if not ParameterOptionsDialog.objectName():
ParameterOptionsDialog.setObjectName(u"ParameterOptionsDialog")
ParameterOptionsDialog.resize(620, 380)
ParameterOptionsDialog.resize(720, 500)
self.dialogLayout = QVBoxLayout(ParameterOptionsDialog)
self.dialogLayout.setObjectName(u"dialogLayout")
self.parameterSplitter = QSplitter(ParameterOptionsDialog)
@@ -76,20 +77,82 @@ class Ui_ParameterOptionsDialog(object):
self.parameterDetailsForm.setWidget(1, QFormLayout.ItemRole.LabelRole, self.typeLabel)
self.typeEdit = QLineEdit(self.parameterDetailsPanel)
self.typeEdit.setObjectName(u"typeEdit")
self.typeCombo = QComboBox(self.parameterDetailsPanel)
self.typeCombo.addItem("")
self.typeCombo.addItem("")
self.typeCombo.addItem("")
self.typeCombo.addItem("")
self.typeCombo.setObjectName(u"typeCombo")
self.typeCombo.setEditable(True)
self.parameterDetailsForm.setWidget(1, QFormLayout.ItemRole.FieldRole, self.typeEdit)
self.parameterDetailsForm.setWidget(1, QFormLayout.ItemRole.FieldRole, self.typeCombo)
self.rowsLabel = QLabel(self.parameterDetailsPanel)
self.rowsLabel.setObjectName(u"rowsLabel")
self.parameterDetailsForm.setWidget(2, QFormLayout.ItemRole.LabelRole, self.rowsLabel)
self.rowsSpin = QSpinBox(self.parameterDetailsPanel)
self.rowsSpin.setObjectName(u"rowsSpin")
self.rowsSpin.setMinimum(1)
self.rowsSpin.setMaximum(9999)
self.parameterDetailsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.rowsSpin)
self.columnsLabel = QLabel(self.parameterDetailsPanel)
self.columnsLabel.setObjectName(u"columnsLabel")
self.parameterDetailsForm.setWidget(3, QFormLayout.ItemRole.LabelRole, self.columnsLabel)
self.columnsSpin = QSpinBox(self.parameterDetailsPanel)
self.columnsSpin.setObjectName(u"columnsSpin")
self.columnsSpin.setMinimum(1)
self.columnsSpin.setMaximum(9999)
self.parameterDetailsForm.setWidget(3, QFormLayout.ItemRole.FieldRole, self.columnsSpin)
self.valueLabel = QLabel(self.parameterDetailsPanel)
self.valueLabel.setObjectName(u"valueLabel")
self.parameterDetailsForm.setWidget(2, QFormLayout.ItemRole.LabelRole, self.valueLabel)
self.parameterDetailsForm.setWidget(4, QFormLayout.ItemRole.LabelRole, self.valueLabel)
self.valueEdit = QLineEdit(self.parameterDetailsPanel)
self.valueEdit.setObjectName(u"valueEdit")
self.parameterDetailsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.valueEdit)
self.parameterDetailsForm.setWidget(4, QFormLayout.ItemRole.FieldRole, self.valueEdit)
self.quantityLabel = QLabel(self.parameterDetailsPanel)
self.quantityLabel.setObjectName(u"quantityLabel")
self.parameterDetailsForm.setWidget(5, QFormLayout.ItemRole.LabelRole, self.quantityLabel)
self.quantityCombo = QComboBox(self.parameterDetailsPanel)
self.quantityCombo.setObjectName(u"quantityCombo")
self.quantityCombo.setEditable(True)
self.parameterDetailsForm.setWidget(5, QFormLayout.ItemRole.FieldRole, self.quantityCombo)
self.unitLabel = QLabel(self.parameterDetailsPanel)
self.unitLabel.setObjectName(u"unitLabel")
self.parameterDetailsForm.setWidget(6, QFormLayout.ItemRole.LabelRole, self.unitLabel)
self.unitCombo = QComboBox(self.parameterDetailsPanel)
self.unitCombo.setObjectName(u"unitCombo")
self.unitCombo.setEditable(True)
self.parameterDetailsForm.setWidget(6, QFormLayout.ItemRole.FieldRole, self.unitCombo)
self.descriptionLabel = QLabel(self.parameterDetailsPanel)
self.descriptionLabel.setObjectName(u"descriptionLabel")
self.parameterDetailsForm.setWidget(7, QFormLayout.ItemRole.LabelRole, self.descriptionLabel)
self.descriptionEdit = QPlainTextEdit(self.parameterDetailsPanel)
self.descriptionEdit.setObjectName(u"descriptionEdit")
self.descriptionEdit.setMaximumHeight(100)
self.parameterDetailsForm.setWidget(7, QFormLayout.ItemRole.FieldRole, self.descriptionEdit)
self.parameterSplitter.addWidget(self.parameterDetailsPanel)
@@ -115,6 +178,16 @@ class Ui_ParameterOptionsDialog(object):
self.removeParameterButton.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Remove Parameter", None))
self.nameLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Name:", None))
self.typeLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Type:", None))
self.typeCombo.setItemText(0, QCoreApplication.translate("ParameterOptionsDialog", u"real", None))
self.typeCombo.setItemText(1, QCoreApplication.translate("ParameterOptionsDialog", u"integer", None))
self.typeCombo.setItemText(2, QCoreApplication.translate("ParameterOptionsDialog", u"boolean", None))
self.typeCombo.setItemText(3, QCoreApplication.translate("ParameterOptionsDialog", u"string", None))
self.rowsLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Rows:", None))
self.columnsLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Columns:", None))
self.valueLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Value:", None))
self.quantityLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Quantity:", None))
self.unitLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Unit:", None))
self.descriptionLabel.setText(QCoreApplication.translate("ParameterOptionsDialog", u"Description:", None))
# retranslateUi

View File

@@ -18,14 +18,14 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QComboBox,
QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout,
QLabel, QLineEdit, QListWidget, QListWidgetItem,
QPushButton, QSizePolicy, QSplitter, QVBoxLayout,
QWidget)
QPlainTextEdit, QPushButton, QSizePolicy, QSpinBox,
QSplitter, QStackedWidget, QVBoxLayout, QWidget)
class Ui_PortOptionsDialog(object):
def setupUi(self, PortOptionsDialog):
if not PortOptionsDialog.objectName():
PortOptionsDialog.setObjectName(u"PortOptionsDialog")
PortOptionsDialog.resize(620, 380)
PortOptionsDialog.resize(760, 421)
self.dialogLayout = QVBoxLayout(PortOptionsDialog)
self.dialogLayout.setObjectName(u"dialogLayout")
self.portSplitter = QSplitter(PortOptionsDialog)
@@ -61,6 +61,7 @@ class Ui_PortOptionsDialog(object):
self.portDetailsPanel.setObjectName(u"portDetailsPanel")
self.portDetailsForm = QFormLayout(self.portDetailsPanel)
self.portDetailsForm.setObjectName(u"portDetailsForm")
self.portDetailsForm.setVerticalSpacing(3)
self.portDetailsForm.setContentsMargins(0, 0, 0, 0)
self.nameLabel = QLabel(self.portDetailsPanel)
self.nameLabel.setObjectName(u"nameLabel")
@@ -104,7 +105,99 @@ class Ui_PortOptionsDialog(object):
self.positionHintLabel.setObjectName(u"positionHintLabel")
self.positionHintLabel.setWordWrap(True)
self.portDetailsForm.setWidget(4, QFormLayout.ItemRole.SpanningRole, self.positionHintLabel)
self.portDetailsForm.setWidget(5, QFormLayout.ItemRole.SpanningRole, self.positionHintLabel)
self.typeOptionsStack = QStackedWidget(self.portDetailsPanel)
self.typeOptionsStack.setObjectName(u"typeOptionsStack")
self.signalOptionsPage = QWidget()
self.signalOptionsPage.setObjectName(u"signalOptionsPage")
self.signalOptionsForm = QFormLayout(self.signalOptionsPage)
self.signalOptionsForm.setObjectName(u"signalOptionsForm")
self.valueTypeLabel = QLabel(self.signalOptionsPage)
self.valueTypeLabel.setObjectName(u"valueTypeLabel")
self.signalOptionsForm.setWidget(0, QFormLayout.ItemRole.LabelRole, self.valueTypeLabel)
self.valueTypeCombo = QComboBox(self.signalOptionsPage)
self.valueTypeCombo.addItem("")
self.valueTypeCombo.addItem("")
self.valueTypeCombo.addItem("")
self.valueTypeCombo.addItem("")
self.valueTypeCombo.setObjectName(u"valueTypeCombo")
self.valueTypeCombo.setEditable(True)
self.signalOptionsForm.setWidget(0, QFormLayout.ItemRole.FieldRole, self.valueTypeCombo)
self.quantityLabel = QLabel(self.signalOptionsPage)
self.quantityLabel.setObjectName(u"quantityLabel")
self.signalOptionsForm.setWidget(1, QFormLayout.ItemRole.LabelRole, self.quantityLabel)
self.quantityCombo = QComboBox(self.signalOptionsPage)
self.quantityCombo.setObjectName(u"quantityCombo")
self.quantityCombo.setEditable(True)
self.signalOptionsForm.setWidget(1, QFormLayout.ItemRole.FieldRole, self.quantityCombo)
self.unitCombo = QComboBox(self.signalOptionsPage)
self.unitCombo.setObjectName(u"unitCombo")
self.unitCombo.setEditable(True)
self.signalOptionsForm.setWidget(2, QFormLayout.ItemRole.FieldRole, self.unitCombo)
self.unitLabel = QLabel(self.signalOptionsPage)
self.unitLabel.setObjectName(u"unitLabel")
self.signalOptionsForm.setWidget(2, QFormLayout.ItemRole.LabelRole, self.unitLabel)
self.rowsColumnsLabel = QLabel(self.signalOptionsPage)
self.rowsColumnsLabel.setObjectName(u"rowsColumnsLabel")
self.signalOptionsForm.setWidget(3, QFormLayout.ItemRole.LabelRole, self.rowsColumnsLabel)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.rowsSpin = QSpinBox(self.signalOptionsPage)
self.rowsSpin.setObjectName(u"rowsSpin")
self.rowsSpin.setMinimum(1)
self.rowsSpin.setMaximum(9999)
self.horizontalLayout.addWidget(self.rowsSpin)
self.columnsSpin = QSpinBox(self.signalOptionsPage)
self.columnsSpin.setObjectName(u"columnsSpin")
self.columnsSpin.setMinimum(1)
self.columnsSpin.setMaximum(9999)
self.horizontalLayout.addWidget(self.columnsSpin)
self.signalOptionsForm.setLayout(3, QFormLayout.ItemRole.FieldRole, self.horizontalLayout)
self.descriptionLabel = QLabel(self.signalOptionsPage)
self.descriptionLabel.setObjectName(u"descriptionLabel")
self.signalOptionsForm.setWidget(4, QFormLayout.ItemRole.LabelRole, self.descriptionLabel)
self.descriptionEdit = QPlainTextEdit(self.signalOptionsPage)
self.descriptionEdit.setObjectName(u"descriptionEdit")
self.signalOptionsForm.setWidget(4, QFormLayout.ItemRole.FieldRole, self.descriptionEdit)
self.typeOptionsStack.addWidget(self.signalOptionsPage)
self.unsupportedTypePage = QWidget()
self.unsupportedTypePage.setObjectName(u"unsupportedTypePage")
self.unsupportedTypeLayout = QVBoxLayout(self.unsupportedTypePage)
self.unsupportedTypeLayout.setObjectName(u"unsupportedTypeLayout")
self.unsupportedTypeLabel = QLabel(self.unsupportedTypePage)
self.unsupportedTypeLabel.setObjectName(u"unsupportedTypeLabel")
self.unsupportedTypeLabel.setWordWrap(True)
self.unsupportedTypeLayout.addWidget(self.unsupportedTypeLabel)
self.typeOptionsStack.addWidget(self.unsupportedTypePage)
self.portDetailsForm.setWidget(4, QFormLayout.ItemRole.SpanningRole, self.typeOptionsStack)
self.portSplitter.addWidget(self.portDetailsPanel)
@@ -129,7 +222,7 @@ class Ui_PortOptionsDialog(object):
self.addPortButton.setText(QCoreApplication.translate("PortOptionsDialog", u"Add Port", None))
self.removePortButton.setText(QCoreApplication.translate("PortOptionsDialog", u"Remove Port", None))
self.nameLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Name:", None))
self.typeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Type:", None))
self.typeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Port type:", None))
self.typeCombo.setItemText(0, QCoreApplication.translate("PortOptionsDialog", u"Signal", None))
self.orientationLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Orientation:", None))
@@ -138,5 +231,16 @@ class Ui_PortOptionsDialog(object):
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.valueTypeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Type:", None))
self.valueTypeCombo.setItemText(0, QCoreApplication.translate("PortOptionsDialog", u"real", None))
self.valueTypeCombo.setItemText(1, QCoreApplication.translate("PortOptionsDialog", u"integer", None))
self.valueTypeCombo.setItemText(2, QCoreApplication.translate("PortOptionsDialog", u"boolean", None))
self.valueTypeCombo.setItemText(3, QCoreApplication.translate("PortOptionsDialog", u"string", None))
self.quantityLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"Quantity:", None))
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.unsupportedTypeLabel.setText(QCoreApplication.translate("PortOptionsDialog", u"This port type does not have an options editor yet.", None))
# retranslateUi

View File

@@ -78,8 +78,8 @@ class Ui_TextDefinitionEditor(object):
self.portsLayout = QVBoxLayout(self.portsGroup)
self.portsLayout.setObjectName(u"portsLayout")
self.portsTable = QTableWidget(self.portsGroup)
if (self.portsTable.columnCount() < 4):
self.portsTable.setColumnCount(4)
if (self.portsTable.columnCount() < 10):
self.portsTable.setColumnCount(10)
__qtablewidgetitem = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(0, __qtablewidgetitem)
__qtablewidgetitem1 = QTableWidgetItem()
@@ -88,10 +88,22 @@ class Ui_TextDefinitionEditor(object):
self.portsTable.setHorizontalHeaderItem(2, __qtablewidgetitem2)
__qtablewidgetitem3 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(3, __qtablewidgetitem3)
__qtablewidgetitem4 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(4, __qtablewidgetitem4)
__qtablewidgetitem5 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(5, __qtablewidgetitem5)
__qtablewidgetitem6 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(6, __qtablewidgetitem6)
__qtablewidgetitem7 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(7, __qtablewidgetitem7)
__qtablewidgetitem8 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(8, __qtablewidgetitem8)
__qtablewidgetitem9 = QTableWidgetItem()
self.portsTable.setHorizontalHeaderItem(9, __qtablewidgetitem9)
self.portsTable.setObjectName(u"portsTable")
self.portsTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.portsTable.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.portsTable.setColumnCount(4)
self.portsTable.setColumnCount(10)
self.portsLayout.addWidget(self.portsTable)
@@ -120,18 +132,28 @@ class Ui_TextDefinitionEditor(object):
self.parametersLayout = QVBoxLayout(self.parametersGroup)
self.parametersLayout.setObjectName(u"parametersLayout")
self.parametersTable = QTableWidget(self.parametersGroup)
if (self.parametersTable.columnCount() < 3):
self.parametersTable.setColumnCount(3)
__qtablewidgetitem4 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(0, __qtablewidgetitem4)
__qtablewidgetitem5 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(1, __qtablewidgetitem5)
__qtablewidgetitem6 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(2, __qtablewidgetitem6)
if (self.parametersTable.columnCount() < 8):
self.parametersTable.setColumnCount(8)
__qtablewidgetitem10 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(0, __qtablewidgetitem10)
__qtablewidgetitem11 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(1, __qtablewidgetitem11)
__qtablewidgetitem12 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(2, __qtablewidgetitem12)
__qtablewidgetitem13 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(3, __qtablewidgetitem13)
__qtablewidgetitem14 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(4, __qtablewidgetitem14)
__qtablewidgetitem15 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(5, __qtablewidgetitem15)
__qtablewidgetitem16 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(6, __qtablewidgetitem16)
__qtablewidgetitem17 = QTableWidgetItem()
self.parametersTable.setHorizontalHeaderItem(7, __qtablewidgetitem17)
self.parametersTable.setObjectName(u"parametersTable")
self.parametersTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.parametersTable.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.parametersTable.setColumnCount(3)
self.parametersTable.setColumnCount(8)
self.parametersLayout.addWidget(self.parametersTable)
@@ -173,20 +195,42 @@ class Ui_TextDefinitionEditor(object):
___qtablewidgetitem = self.portsTable.horizontalHeaderItem(0)
___qtablewidgetitem.setText(QCoreApplication.translate("TextDefinitionEditor", u"Name", None))
___qtablewidgetitem1 = self.portsTable.horizontalHeaderItem(1)
___qtablewidgetitem1.setText(QCoreApplication.translate("TextDefinitionEditor", u"Type", None))
___qtablewidgetitem1.setText(QCoreApplication.translate("TextDefinitionEditor", u"Port Type", None))
___qtablewidgetitem2 = self.portsTable.horizontalHeaderItem(2)
___qtablewidgetitem2.setText(QCoreApplication.translate("TextDefinitionEditor", u"Orientation", None))
___qtablewidgetitem3 = self.portsTable.horizontalHeaderItem(3)
___qtablewidgetitem3.setText(QCoreApplication.translate("TextDefinitionEditor", u"Multiple", None))
___qtablewidgetitem4 = self.portsTable.horizontalHeaderItem(4)
___qtablewidgetitem4.setText(QCoreApplication.translate("TextDefinitionEditor", u"Value Type", None))
___qtablewidgetitem5 = self.portsTable.horizontalHeaderItem(5)
___qtablewidgetitem5.setText(QCoreApplication.translate("TextDefinitionEditor", u"Quantity", None))
___qtablewidgetitem6 = self.portsTable.horizontalHeaderItem(6)
___qtablewidgetitem6.setText(QCoreApplication.translate("TextDefinitionEditor", u"Unit", None))
___qtablewidgetitem7 = self.portsTable.horizontalHeaderItem(7)
___qtablewidgetitem7.setText(QCoreApplication.translate("TextDefinitionEditor", u"Rows", None))
___qtablewidgetitem8 = self.portsTable.horizontalHeaderItem(8)
___qtablewidgetitem8.setText(QCoreApplication.translate("TextDefinitionEditor", u"Columns", None))
___qtablewidgetitem9 = self.portsTable.horizontalHeaderItem(9)
___qtablewidgetitem9.setText(QCoreApplication.translate("TextDefinitionEditor", u"Description", None))
self.addPortButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Add Port", None))
self.removePortButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Remove Port", None))
self.parametersGroup.setTitle(QCoreApplication.translate("TextDefinitionEditor", u"Parameters", None))
___qtablewidgetitem4 = self.parametersTable.horizontalHeaderItem(0)
___qtablewidgetitem4.setText(QCoreApplication.translate("TextDefinitionEditor", u"Name", None))
___qtablewidgetitem5 = self.parametersTable.horizontalHeaderItem(1)
___qtablewidgetitem5.setText(QCoreApplication.translate("TextDefinitionEditor", u"Type", None))
___qtablewidgetitem6 = self.parametersTable.horizontalHeaderItem(2)
___qtablewidgetitem6.setText(QCoreApplication.translate("TextDefinitionEditor", u"Value", None))
___qtablewidgetitem10 = self.parametersTable.horizontalHeaderItem(0)
___qtablewidgetitem10.setText(QCoreApplication.translate("TextDefinitionEditor", u"Name", None))
___qtablewidgetitem11 = self.parametersTable.horizontalHeaderItem(1)
___qtablewidgetitem11.setText(QCoreApplication.translate("TextDefinitionEditor", u"Type", None))
___qtablewidgetitem12 = self.parametersTable.horizontalHeaderItem(2)
___qtablewidgetitem12.setText(QCoreApplication.translate("TextDefinitionEditor", u"Value", None))
___qtablewidgetitem13 = self.parametersTable.horizontalHeaderItem(3)
___qtablewidgetitem13.setText(QCoreApplication.translate("TextDefinitionEditor", u"Quantity", None))
___qtablewidgetitem14 = self.parametersTable.horizontalHeaderItem(4)
___qtablewidgetitem14.setText(QCoreApplication.translate("TextDefinitionEditor", u"Unit", None))
___qtablewidgetitem15 = self.parametersTable.horizontalHeaderItem(5)
___qtablewidgetitem15.setText(QCoreApplication.translate("TextDefinitionEditor", u"Rows", None))
___qtablewidgetitem16 = self.parametersTable.horizontalHeaderItem(6)
___qtablewidgetitem16.setText(QCoreApplication.translate("TextDefinitionEditor", u"Columns", None))
___qtablewidgetitem17 = self.parametersTable.horizontalHeaderItem(7)
___qtablewidgetitem17.setText(QCoreApplication.translate("TextDefinitionEditor", u"Description", None))
self.addParameterButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Add Parameter", None))
self.removeParameterButton.setText(QCoreApplication.translate("TextDefinitionEditor", u"Remove Parameter", None))
pass

View File

@@ -280,7 +280,20 @@ class SimulationWindow(QMainWindow):
items[path] = item
parent = item
parent.setData(0, Qt.ItemDataRole.UserRole, signal_name)
parent.setToolTip(0, signal_name)
metadata = self.results.signal_metadata.get(signal_name, {})
unit = metadata.get("unit", "")
if unit:
parent.setText(0, f"{parts[-1]} [{unit}]")
tooltip = signal_name
details = [
metadata.get("quantity", ""),
f"Unit: {unit}" if unit else "",
metadata.get("description", ""),
]
details = [detail for detail in details if detail]
if details:
tooltip += "\n" + "\n".join(details)
parent.setToolTip(0, tooltip)
parent.setFlags(parent.flags() | Qt.ItemFlag.ItemIsUserCheckable)
parent.setCheckState(0, Qt.CheckState.Unchecked)
tree.expandToDepth(0)
@@ -334,7 +347,14 @@ class SimulationWindow(QMainWindow):
(trace for trace in graph.traces if trace.name == signal_name), None
)
if enabled and existing is None:
graph.traces.append(SimulationTrace(name=signal_name))
metadata = self.results.signal_metadata.get(signal_name, {})
graph.traces.append(
SimulationTrace(
name=signal_name,
y_label=metadata.get("quantity", ""),
unit=metadata.get("unit", ""),
)
)
elif not enabled and existing is not None:
graph.traces.remove(existing)
page = self.ui.graphTabs.currentWidget()
@@ -492,6 +512,7 @@ class SimulationWindow(QMainWindow):
self.results.status.update(phase="Simulation finished", progress=10000)
if isinstance(result, SimulationExecutionResult):
self.results.data = result.data
self.results.signal_metadata = result.signal_metadata
available_signals = set(result.data)
for graph in self.results.graphs:
graph.traces = [
@@ -499,6 +520,10 @@ class SimulationWindow(QMainWindow):
for trace in graph.traces
if trace.name in available_signals
]
for trace in graph.traces:
metadata = result.signal_metadata.get(trace.name, {})
trace.unit = metadata.get("unit", "")
trace.y_label = metadata.get("quantity", "")
if graph.x_axis not in available_signals:
graph.x_axis = (
"time"
@@ -570,14 +595,38 @@ class GraphWorkspacePage(QWidget):
horizontal = x_values if x_values is not None else range(len(y_values))
sample_count = min(len(horizontal), len(y_values))
color = trace.properties.get("color")
unit = trace.unit or self.results.signal_metadata.get(
trace.name, {}
).get("unit", "")
label = f"{trace.name} [{unit}]" if unit else trace.name
self.axes.plot(
list(horizontal)[:sample_count],
y_values[:sample_count],
label=trace.name,
label=label,
color=color if isinstance(color, str) and color else None,
)
self.axes.set_title(self.graph.title)
self.axes.set_xlabel(self.graph.x_axis if x_values is not None else "sample")
if x_values is not None:
x_unit = self.results.signal_metadata.get(
self.graph.x_axis, {}
).get("unit", "")
x_label = (
f"{self.graph.x_axis} [{x_unit}]"
if x_unit
else self.graph.x_axis
)
else:
x_label = "sample"
self.axes.set_xlabel(x_label)
units = {
trace.unit
or self.results.signal_metadata.get(trace.name, {}).get("unit", "")
for trace in self.graph.traces
if self.results.data.get(trace.name) is not None
}
units.discard("")
if len(units) == 1:
self.axes.set_ylabel(next(iter(units)))
self.axes.grid(True, alpha=0.25)
if self.axes.lines:
self.axes.legend()