Skip to content

Commit a19387c

Browse files
authored
Merge pull request #32 from knnmelprop/feature/standardize-analysis-setup
refactor: standardize all analysis setup() on the vehicle contract (issue #28)
2 parents 2de7844 + f743885 commit a19387c

20 files changed

Lines changed: 1720 additions & 329 deletions

File tree

‎YAADO_Core/Foundation/analysis_base.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
from abc import ABC, abstractmethod
1313
from dataclasses import dataclass, field
1414
from enum import IntEnum
15-
from typing import Any
15+
from typing import TYPE_CHECKING, Any
16+
17+
if TYPE_CHECKING:
18+
from YAADO_Core.Foundation.vehicle_base import BaseVehicleConfig
1619

1720

1821
class FidelityLevel(IntEnum):
@@ -75,8 +78,22 @@ def __init__(self, name: str) -> None:
7578
self._is_setup = False
7679

7780
@abstractmethod
78-
def setup(self, *args: Any, **kwargs: Any) -> None:
79-
"""Bind the analysis to a vehicle configuration and prepare inputs."""
81+
def setup(
82+
self,
83+
vehicle: BaseVehicleConfig,
84+
operating_state: dict | None = None,
85+
) -> None:
86+
"""Bind the analysis to a vehicle configuration and prepare inputs.
87+
88+
Extracts the necessary geometry, component parameters, and operating
89+
conditions to initialize the underlying solver for execution.
90+
91+
Args:
92+
vehicle: The centralized vehicle configuration to analyze.
93+
operating_state: Optional dictionary of operating conditions in SI units
94+
(e.g., ``mach``, ``altitude_m``, ``alpha_deg``). If ``None``,
95+
the analysis will use its documented defaults.
96+
"""
8097

8198
@abstractmethod
8299
def execute(self) -> AnalysisResults:

‎YAADO_Core/modules/airframe/generator_methods/openvsp.py‎

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,19 @@
5757
import datetime
5858
import json
5959
import sys
60+
from dataclasses import dataclass
6061
from pathlib import Path
61-
from typing import Any
62+
from typing import TYPE_CHECKING, Any
6263

6364
if __name__ in ("__main__",) and __package__ in (None, ""):
6465
sys.path.insert(0, str(Path(__file__).resolve().parents[4]))
6566

67+
from YAADO_Core.ComponentStore import AxisymmetricBody, Fins # noqa: E402
6668
from YAADO_Core.Foundation.analysis_base import AnalysisResults, BaseAnalysis, FidelityLevel # noqa: E402
6769

70+
if TYPE_CHECKING:
71+
from YAADO_Core.Foundation.vehicle_base import BaseVehicleConfig
72+
6873
#: Repository root, resolved from this file's location.
6974
REPO_ROOT: Path = Path(__file__).resolve().parents[4]
7075

@@ -94,6 +99,28 @@
9499
)
95100

96101

102+
@dataclass
103+
class _VehicleGeometryAdapter:
104+
"""Adapts a generic ``BaseVehicleConfig`` to this exporter's body/fins pair.
105+
106+
:func:`build_vspscript` and :func:`build_manifest` were written against
107+
a bespoke rocket schema exposing single ``body``/``fins`` attributes;
108+
this adapter lets :meth:`OpenVSPExporter.setup` bind the generic,
109+
dict-based :class:`~YAADO_Core.Foundation.vehicle_base.BaseVehicleConfig`
110+
(``vehicle.bodies`` / ``vehicle.aero_surfaces``) without touching that
111+
downstream rendering logic.
112+
113+
Attributes:
114+
name: Vehicle name, forwarded from the source ``BaseVehicleConfig``.
115+
body: The vehicle's axisymmetric body geometry.
116+
fins: The vehicle's fin-set geometry.
117+
"""
118+
119+
name: str
120+
body: AxisymmetricBody
121+
fins: Fins
122+
123+
97124
def load_rocket_config(path: Path | str = VEHICLE_CONFIG_PATH) -> Any:
98125
"""Load and validate the ramjet-rocket vehicle configuration.
99126
@@ -325,7 +352,7 @@ class OpenVSPExporter(BaseAnalysis):
325352
326353
Example:
327354
>>> analysis = OpenVSPExporter()
328-
>>> analysis.setup(rocket_config)
355+
>>> analysis.setup(vehicle)
329356
>>> results = analysis.execute()
330357
>>> results.metadata["script_path"] # doctest: +SKIP
331358
"""
@@ -334,32 +361,60 @@ class OpenVSPExporter(BaseAnalysis):
334361

335362
def __init__(self, name: str = "openvsp_vehicle_export") -> None:
336363
super().__init__(name)
337-
self._config: Any | None = None
364+
self._config: _VehicleGeometryAdapter | None = None
338365
self._output_dir: Path = RUNS_OUTPUT_DIR
339366

340367
def setup(
341368
self,
342-
vehicle_config: Any,
343-
output_dir: Path | str = RUNS_OUTPUT_DIR,
369+
vehicle: "BaseVehicleConfig",
370+
operating_state: dict | None = None,
344371
) -> None:
345-
"""Bind the analysis to a rocket config and output directory.
372+
"""Bind the analysis to a validated vehicle configuration.
373+
374+
Geometry is read exclusively from ``vehicle``: the first entry of
375+
``vehicle.bodies`` (an
376+
:class:`~YAADO_Core.ComponentStore.body.AxisymmetricBody`) and the
377+
first :class:`~YAADO_Core.ComponentStore.aero_surfaces.Fins`
378+
aero surface found in ``vehicle.aero_surfaces``.
346379
347380
Args:
348-
vehicle_config: Validated rocket configuration with body/fins.
349-
output_dir: Directory the ``.vspscript`` and manifest are
350-
written into (created if missing). Injectable so tests
351-
can point it at ``tmp_path`` instead of the repo-root
352-
``runs/openvsp/`` default.
381+
vehicle: Validated, vehicle-agnostic configuration providing
382+
the body and fin-set geometry this exporter needs.
383+
operating_state: Optional SI-unit operating conditions. This
384+
geometry-only exporter has no operating-state dependence,
385+
except that an ``"output_dir"`` key (``str`` or
386+
:class:`~pathlib.Path`) overrides the directory the
387+
``.vspscript`` and manifest are written into (default:
388+
the constructor's :data:`RUNS_OUTPUT_DIR`, injectable so
389+
tests can point it at ``tmp_path``).
353390
354391
Raises:
355-
ValueError: If the config has no body/fins definition.
392+
ValueError: If ``vehicle`` defines no body, or no ``Fins``
393+
aero surface.
356394
"""
357-
if getattr(vehicle_config, "body", None) is None:
358-
raise ValueError("vehicle_config must define body")
359-
if getattr(vehicle_config, "fins", None) is None:
360-
raise ValueError("vehicle_config must define fins")
361-
self._config = vehicle_config
362-
self._output_dir = Path(output_dir)
395+
body = next(iter(vehicle.bodies.values()), None)
396+
if body is None:
397+
raise ValueError("vehicle must define at least one body (vehicle.bodies)")
398+
399+
fins = next(
400+
(
401+
surface
402+
for surface in vehicle.aero_surfaces.values()
403+
if isinstance(surface, Fins)
404+
),
405+
None,
406+
)
407+
if fins is None:
408+
raise ValueError(
409+
"vehicle must define at least one Fins aero surface "
410+
"(vehicle.aero_surfaces)"
411+
)
412+
413+
self._config = _VehicleGeometryAdapter(name=vehicle.name, body=body, fins=fins)
414+
415+
if operating_state is not None and "output_dir" in operating_state:
416+
self._output_dir = Path(operating_state["output_dir"])
417+
363418
self._is_setup = True
364419

365420
def execute(self) -> AnalysisResults:

0 commit comments

Comments
 (0)