5757import datetime
5858import json
5959import sys
60+ from dataclasses import dataclass
6061from pathlib import Path
61- from typing import Any
62+ from typing import TYPE_CHECKING , Any
6263
6364if __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
6668from 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.
6974REPO_ROOT : Path = Path (__file__ ).resolve ().parents [4 ]
7075
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+
97124def 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