More types and units
This commit is contained in:
@@ -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] = {}
|
||||
|
||||
47
BEdit/src/bedit/core/physical_types.py
Normal file
47
BEdit/src/bedit/core/physical_types.py
Normal 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",
|
||||
)
|
||||
@@ -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",
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]]:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
@@ -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}]"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user