"""Internal implementation of the wing geometry schema dataclasses."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal
from buffalo_wings.airfoil import AirfoilDefinitionSpec
type LengthUnit = Literal["m", "ft"]
type AngleUnit = Literal["deg", "rad"]
type SymmetryMode = Literal["mirror_y", "none"]
type ReferenceAxis = Literal["leading_edge", "quarter_chord", "elastic_axis"]
type JoinContinuity = Literal["C0", "G1"]
type PanelSide = Literal["u_min", "u_max", "v_min", "v_max"]
type BlendMethod = Literal["pointwise", "cst", "camber_thickness"]
type FitMethod = Literal["skin", "least_squares"]
type SpacingMethod = Literal["uniform", "cosine"]
[docs]
@dataclass(slots=True)
class UnitsSpec:
"""Unit declarations used by the serialized schema."""
#: Length unit used throughout the serialized schema.
length: LengthUnit = "m"
#: Angle unit used throughout the serialized schema.
angle: AngleUnit = "deg"
[docs]
@dataclass(slots=True)
class FrameSpec:
"""Human-readable axis labels for the wing coordinate frame."""
#: Label describing the positive ``x`` direction.
x: str = "forward"
#: Label describing the positive ``y`` direction.
y: str = "right"
#: Label describing the positive ``z`` direction.
z: str = "up"
[docs]
@dataclass(slots=True)
class PiecewiseLinearDistribution:
"""Piecewise-linear scalar distribution over ``eta``."""
#: Schema discriminator for a piecewise-linear distribution.
type: Literal["piecewise_linear"] = "piecewise_linear"
#: Ordered ``(eta, value)`` pairs defining the distribution.
data: list[tuple[float, float]] = field(default_factory=list)
type DistributionSpec = PiecewiseLinearDistribution
[docs]
@dataclass(slots=True)
class RefLineSpec:
"""Reference-axis position distributions used for sweep and dihedral."""
#: Spanwise distribution of the reference-line ``x`` position.
x_ref: DistributionSpec
#: Spanwise distribution of the reference-line ``z`` position.
z_ref: DistributionSpec
[docs]
@dataclass(slots=True)
class SingleAirfoilRef:
"""Reference to a single named airfoil."""
#: Schema discriminator for a single-airfoil reference.
type: Literal["single"] = "single"
#: Name of the referenced airfoil in the top-level ``airfoils`` map.
name: str = ""
[docs]
@dataclass(slots=True)
class BlendAirfoilRef:
"""Spanwise airfoil blend definition for a panel."""
#: Schema discriminator for a blended-airfoil reference.
type: Literal["blend"] = "blend"
#: Blend method used to transition between named stations.
method: BlendMethod = "cst"
#: Ordered list of ``(eta, airfoil_name)`` blend stations.
stations: list[tuple[float, str]] = field(default_factory=list)
type AirfoilRefSpec = SingleAirfoilRef | BlendAirfoilRef
[docs]
@dataclass(slots=True)
class DegreesSpec:
"""Requested NURBS polynomial degrees."""
#: Polynomial degree in the chordwise ``u`` direction.
u: int = 4
#: Polynomial degree in the spanwise ``v`` direction.
v: int = 3
[docs]
@dataclass(slots=True)
class ControlPointSpec:
"""Requested NURBS control-point counts."""
#: Control-point count in the chordwise ``u`` direction.
u: int = 16
#: Control-point count in the spanwise ``v`` direction.
v: int = 10
[docs]
@dataclass(slots=True)
class FitSpec:
"""Surface fitting method for internal NURBS construction."""
#: Surface fitting algorithm used during NURBS construction.
method: FitMethod = "skin"
[docs]
@dataclass(slots=True)
class NurbsSpec:
"""Panel-local NURBS construction controls."""
#: Requested polynomial degrees for the fitted surface.
degrees: DegreesSpec = field(default_factory=DegreesSpec)
#: Requested control-point counts for the fitted surface.
ctrlpts: ControlPointSpec = field(default_factory=ControlPointSpec)
#: Fitting method used to construct the panel surface.
fit: FitSpec = field(default_factory=FitSpec)
[docs]
@dataclass(slots=True)
class VlmMeshSpec:
"""VLM sampling controls."""
#: Number of spanwise VLM panels.
n_span: int
#: Number of chordwise VLM panels.
n_chord: int
#: Spanwise spacing rule for the VLM mesh.
span_spacing: SpacingMethod = "cosine"
#: Chordwise spacing rule for the VLM mesh.
chord_spacing: SpacingMethod = "cosine"
[docs]
@dataclass(slots=True)
class CfdMeshSpec:
"""CFD surface sampling controls."""
#: Number of spanwise CFD samples.
n_span: int
#: Number of airfoil-surface samples per section.
n_airfoil: int
[docs]
@dataclass(slots=True)
class MeshSpec:
"""Optional export sampling settings."""
#: Optional VLM mesh sampling settings.
vlm: VlmMeshSpec | None = None
#: Optional CFD surface sampling settings.
cfd: CfdMeshSpec | None = None
[docs]
@dataclass(slots=True)
class PanelSpec:
"""Parametric definition of a single spanwise wing panel."""
#: Unique panel identifier.
id: str
#: Closed spanwise ``eta`` interval covered by the panel.
eta_range: tuple[float, float]
#: Reference-line position distributions for the panel.
ref_line: RefLineSpec
#: Chord distribution over the panel span.
chord: DistributionSpec
#: Twist distribution over the panel span.
twist: DistributionSpec
#: Airfoil reference or blend assigned to the panel.
airfoil: AirfoilRefSpec
#: Optional NURBS construction controls.
nurbs: NurbsSpec | None = None
#: Optional mesh export controls.
mesh: MeshSpec | None = None
[docs]
@dataclass(slots=True)
class JoinSideSpec:
"""One side of a panel-to-panel join constraint."""
#: Panel identifier participating in the join.
panel: str
#: Boundary side of that panel used in the join.
side: PanelSide
[docs]
@dataclass(slots=True)
class JoinSpec:
"""Continuity constraint across a panel interface."""
#: Unique join identifier.
id: str
#: Left-hand side of the join relationship.
left: JoinSideSpec
#: Right-hand side of the join relationship.
right: JoinSideSpec
#: Requested continuity level across the join.
continuity: JoinContinuity
#: Optional enforcement weight for the join.
weight: float | None = None
#: Tangent scaling used for ``G1`` joins.
tangent_scale: float | Literal["auto"] = "auto"
[docs]
@dataclass(slots=True)
class WingDefinitionSpec:
"""The ``wing`` object from the documented schema."""
#: Symmetry mode for the wing definition.
symmetry: SymmetryMode
#: Half-span of the modeled wing.
half_span: float
#: Reference axis used to position sections along the span.
reference_axis: ReferenceAxis
#: Axis about which section twist is applied.
twist_axis: ReferenceAxis
#: Ordered list of spanwise wing panels.
panels: list[PanelSpec]
#: Optional human-readable wing name.
name: str | None = None
#: Optional human-readable frame labels.
frame: FrameSpec | None = None
#: Optional continuity constraints between adjacent panels.
joins: list[JoinSpec] = field(default_factory=list)
[docs]
@dataclass(slots=True)
class WingSpec:
"""Top-level wing geometry schema."""
#: Schema version number for the serialized wing definition.
schema_version: int
#: Units block used by the serialized schema.
units: UnitsSpec
#: Wing definition payload.
wing: WingDefinitionSpec
#: Named airfoils referenced by the wing definition.
airfoils: dict[str, AirfoilDefinitionSpec] = field(default_factory=dict)