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

"""Default GUI case templates and geometry-preview helpers."""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from dataclasses import fields, is_dataclass
from typing import Any, cast

import buffalo_wings.airfoil as bwa
import numpy as np
import numpy.typing as npt

from buffalo_panel.config import AirfoilBody2DSpec, PanelCaseSpec


[docs] def build_initial_case_mapping() -> dict[str, Any]: """Return the default case mapping used to seed the GUI.""" return { "schema_version": 1, "units": {"length": "m", "angle": "deg"}, "case": { "name": "GUI Case", "description": "Interactive case defined in the GUI", }, "solver": { "formulation": "hess_smith", "backend": "python", }, "freestream": { "speed": 1.0, "alpha": 2.0, "mach": 0.0, "reynolds": 1000000.0, }, "reference": { "speed": 1.0, "length": 1.0, "moment_point": [0.25, 0.0], }, "geometry": {"bodies": [build_default_body_mapping(body_id="airfoil")]}, "post": { "surface": { "quantities": [ "velocity", "cp", ] }, "integrated": { "quantities": [ "cl_pressure", "cl_circulation", "cd_pressure", "cm_pressure", ] }, }, }
[docs] def build_default_body_mapping( body_id: str, airfoil_name: str | None = None, ) -> dict[str, Any]: """Return the default body mapping used for new geometry entries.""" return { "id": body_id, "airfoil": build_airfoil_schema_mapping("naca4"), "discretization": "thick_body", "sampling": { "num_points_per_surface": 41, "spacing": "cosine", }, "placement": { "scale": 1.0, "rotation": 0.0, "rotation_point": [0.0, 0.0], "translation": [0.0, 0.0], }, }
[docs] def build_airfoil_parameter_mapping( airfoil_type: str, ) -> dict[str, str | list[float]]: """Return the schema-dependent airfoil parameters for one type.""" mapping = build_airfoil_schema_mapping(airfoil_type) mapping.pop("type", None) return mapping
[docs] def build_airfoil_schema_mapping(airfoil_type: str) -> dict[str, Any]: """Return one canonical Buffalo Wings starter mapping.""" spec = bwa.AirfoilFactory.default_spec(airfoil_type) return _serialize_schema_value(spec)
def _serialize_schema_value(value: object) -> Any: """Convert one schema dataclass tree to builtin containers.""" if is_dataclass(value): serialized: dict[str, Any] = {} for field in fields(value): field_value = getattr(value, field.name) if field_value is None: continue serialized[field.name] = _serialize_schema_value(field_value) return serialized if isinstance(value, Mapping): value_map = cast(Mapping[object, object], value) return { str(key): _serialize_schema_value(item) for key, item in value_map.items() } if isinstance(value, Sequence) and not isinstance(value, str): items = cast(Sequence[object], value) return [_serialize_schema_value(item) for item in items] return value
[docs] def body_preview_coordinates( spec: PanelCaseSpec, body_spec: AirfoilBody2DSpec, ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Return transformed airfoil coordinates for preview plotting.""" airfoil = bwa.AirfoilFactory.from_spec(body_spec.airfoil) if body_spec.discretization == "thin_body": x_raw, y_raw = _camber_preview_coordinates(airfoil, body_spec) else: boundary = bwa.sample_airfoil_boundary( airfoil, num_points_per_surface=body_spec.sampling.num_points_per_surface, spacing=body_spec.sampling.spacing, order="lower_to_upper", warning_policy="ignore", ) x_raw = boundary.coordinates[:, 0] y_raw = boundary.coordinates[:, 1] scale = body_spec.placement.scale theta = body_spec.placement.rotation theta_rad = float(np.deg2rad(theta)) if spec.units.angle == "deg" else theta cos_t, sin_t = np.cos(theta_rad), np.sin(theta_rad) rp_x, rp_y = body_spec.placement.rotation_point tx, ty = body_spec.placement.translation x_scaled = x_raw * scale y_scaled = y_raw * scale pivot_x = scale * rp_x pivot_y = scale * rp_y x_centered = x_scaled - pivot_x y_centered = y_scaled - pivot_y x_rotated = pivot_x + cos_t * x_centered - sin_t * y_centered y_rotated = pivot_y + sin_t * x_centered + cos_t * y_centered x_final = tx + x_rotated y_final = ty + y_rotated return x_final, y_final
def _camber_preview_coordinates( airfoil: bwa.Airfoil, body_spec: AirfoilBody2DSpec, ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Return preview coordinates sampled from the airfoil camber curve.""" xi = _sampling_parameter_values( num_points=body_spec.sampling.num_points_per_surface, spacing=body_spec.sampling.spacing, ) camber = airfoil.camber_curve( num_points=body_spec.sampling.num_points_per_surface, spacing=body_spec.sampling.spacing, ) x_coord, y_coord = camber.curve.xy_from_u(xi) return np.asarray(x_coord, dtype=np.float64), np.asarray( y_coord, dtype=np.float64, ) def _sampling_parameter_values( *, num_points: int, spacing: str, ) -> npt.NDArray[np.float64]: """Return one-dimensional sample parameters for previews.""" if spacing == "uniform": return np.linspace(0.0, 1.0, num_points, dtype=np.float64) theta = np.linspace(0.0, np.pi, num_points, dtype=np.float64) return 0.5 * (1.0 - np.cos(theta))