Source code for buffalo_panel.app.gui.internal.tree_helpers

"""Qt tree-model helpers for the Buffalo Panel GUI."""
# pyright: reportUnknownMemberType=false
# pyright: reportUnknownVariableType=false

from __future__ import annotations

from typing import Any

import numpy as np
from PyQt6 import QtCore, QtGui

from buffalo_panel.app.gui.internal.config_templates import (
    build_airfoil_parameter_mapping,
)


[docs] def model_to_data( parent: QtGui.QStandardItem | None, metadata_role: int, ) -> dict[str, Any] | list[Any]: """Serialize a tree-model branch back into nested Python data.""" if parent is None: return {} is_list = _item_represents_list(parent, metadata_role) container: Any = [] if is_list else {} for row in range(parent.rowCount()): key_item = parent.child(row, 0) val_item = parent.child(row, 1) if key_item is None: continue key = key_item.data(QtCore.Qt.ItemDataRole.UserRole) if key_item.hasChildren(): value = model_to_data(key_item, metadata_role) elif val_item is not None: value = _coerce_scalar_value(key, val_item.text()) else: value = {} if is_list: container.append(value) else: container[key] = value return container
[docs] def convert_angle_fields( root_item: QtGui.QStandardItem | None, *, to_rad: bool, ) -> None: """Convert freestream and placement angle fields in-place.""" if root_item is None: return factor = np.deg2rad(1.0) if to_rad else np.rad2deg(1.0) def traverse(parent_item: QtGui.QStandardItem) -> None: for row in range(parent_item.rowCount()): key_item = parent_item.child(row, 0) val_item = parent_item.child(row, 1) if key_item is None: continue key = key_item.data(QtCore.Qt.ItemDataRole.UserRole) parent = key_item.parent() parent_key = ( parent.data(QtCore.Qt.ItemDataRole.UserRole) if parent is not None else None ) if val_item is not None: is_angle = (key == "alpha" and parent_key == "freestream") or ( key == "rotation" and parent_key == "placement" ) if is_angle: try: value = float(val_item.text()) except ValueError: pass else: val_item.setText(str(value * factor)) if key_item.hasChildren(): traverse(key_item) traverse(root_item)
[docs] def sync_airfoil_definition_rows( airfoil_item: QtGui.QStandardItem, airfoil_type: str, ) -> None: """Replace airfoil parameter rows to match the selected airfoil type.""" rows_to_remove: list[int] = [] for row in range(airfoil_item.rowCount()): child_key_item = airfoil_item.child(row, 0) if child_key_item is None: continue if child_key_item.data(QtCore.Qt.ItemDataRole.UserRole) != "type": rows_to_remove.append(row) for row in reversed(rows_to_remove): airfoil_item.removeRow(row) for key, value in build_airfoil_parameter_mapping(airfoil_type).items(): key_item = QtGui.QStandardItem(key) key_item.setData(key, QtCore.Qt.ItemDataRole.UserRole) key_item.setEditable(False) value_item = QtGui.QStandardItem(str(value)) airfoil_item.appendRow([key_item, value_item])
def _item_represents_list( item: QtGui.QStandardItem, metadata_role: int, ) -> bool: meta = item.data(metadata_role) meta_mapping = meta if isinstance(meta, dict) else None if meta_mapping is not None and meta_mapping.get("value_kind") == "list": return True if item.rowCount() == 0: return False first_key_item = item.child(0, 0) if first_key_item is None: return False first_key = str(first_key_item.data(QtCore.Qt.ItemDataRole.UserRole)) return first_key.startswith("[") and first_key.endswith("]") def _coerce_scalar_value(key: Any, value_text: str) -> Any: """Convert GUI text back into a scalar config value.""" if key == "designation": return value_text normalized = value_text.lower() if normalized == "true": return True if normalized == "false": return False if value_text == "" or normalized == "none": return None try: return float(value_text) if "." in value_text else int(value_text) except ValueError: return value_text