feat(stability): Módulo 3 — Curva GZ + criterios IMO IS Code 2008
- gz_integrator.py: GZCurve, GZPoint, compute_gz_wall_sided (fórmula pared lateral), compute_gz_direct (integración Sutherland-Hodgman) - imo_is2008.py: IMOCriterion, IMOResult, check_imo_is2008 — 6 criterios A.2.1.1–A.2.1.6 del IS Code 2008 Cap.2 - gz_curve_widget.py: GZCurveWidget QPainter — curva cian, áreas sombreadas, líneas IMO, marcador AVS, tabla PASS/FAIL integrada - main_window.py: GZCurveWidget en MOD_STABILITY, _compute_and_show_gz, _on_show_stability conectado al ribbon - dark.qss: estilos GZCurveWidget - test_module3_stability.py: 33 tests S-01..S-28 (315 total, todos pasan) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,8 @@ from PySide6.QtWidgets import (
|
||||
from arshipdesign import __version__
|
||||
from arshipdesign.core.project import Project
|
||||
from arshipdesign.utils.logger import get_logger
|
||||
from arshipdesign.stability import compute_gz_wall_sided, GZCurve, check_imo_is2008
|
||||
from arshipdesign.ui.widgets.gz_curve_widget import GZCurveWidget
|
||||
from arshipdesign.utils.settings import (
|
||||
add_recent_file,
|
||||
get_language,
|
||||
@@ -813,6 +815,7 @@ class MainWindow(QMainWindow):
|
||||
super().__init__()
|
||||
self._project: Optional[Project] = None
|
||||
self._current_hull = None # Hull activo en todos los visores
|
||||
self._gz_widget: Optional[GZCurveWidget] = None
|
||||
self._lang = get_language()
|
||||
self._strings = _load_i18n(self._lang)
|
||||
self._setup_ui()
|
||||
@@ -878,6 +881,10 @@ class MainWindow(QMainWindow):
|
||||
self._hydro_chart = HydrostaticsChartWidget()
|
||||
self._module_area.set_module_widget(ModuleArea.MOD_CURVES, self._hydro_chart)
|
||||
|
||||
# Módulo de estabilidad GZ (sustituye el placeholder MOD_STABILITY)
|
||||
self._gz_widget = GZCurveWidget()
|
||||
self._module_area.set_module_widget(ModuleArea.MOD_STABILITY, self._gz_widget)
|
||||
|
||||
# Dock izquierdo — capas
|
||||
self._layers_panel = LayersPanel(self._strings)
|
||||
self._dock_layers = QDockWidget("Capas", self)
|
||||
@@ -981,7 +988,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
g = self._ribbon.new_group(RibbonBar.TAB_ANALYSIS, "Estabilidad")
|
||||
g.add_button(_spi(sp.SP_FileDialogDetailedView), "Curva GZ", "Curva GZ estática",
|
||||
lambda: self._module_area.activate(M.MOD_STABILITY), False)
|
||||
self._on_show_stability)
|
||||
g.add_button(_spi(sp.SP_FileDialogDetailedView), "IMO IS2008", "Criterios IMO IS Code 2008", enabled=False)
|
||||
g.add_button(_spi(sp.SP_FileDialogDetailedView), "Avería", "Estabilidad en avería", enabled=False)
|
||||
|
||||
@@ -1114,7 +1121,7 @@ class MainWindow(QMainWindow):
|
||||
slot=self._on_export_hydrostatics_csv)
|
||||
|
||||
sm = m.addMenu("Estabilidad")
|
||||
self._add_action(sm, "Curva GZ — Estabilidad estática", slot=lambda: self._module_area.activate(M.MOD_STABILITY), enabled=False)
|
||||
self._add_action(sm, "Curva GZ — Estabilidad estática", slot=self._on_show_stability)
|
||||
self._add_action(sm, "Criterios IMO IS Code 2008", enabled=False)
|
||||
self._add_action(sm, "Criterio de viento A.749(18)", enabled=False)
|
||||
self._add_action(sm, "Estabilidad en avería (SOLAS 2009)", enabled=False)
|
||||
@@ -1330,6 +1337,8 @@ class MainWindow(QMainWindow):
|
||||
logger.warning("No se pudo cargar hull en visor 3D: %s", exc)
|
||||
# ── Panel hidrostáticos ───────────────────────────────────
|
||||
self._update_hydrostatics(hull)
|
||||
# ── Curva GZ (si el módulo está activo o precalcular) ─────
|
||||
self._compute_and_show_gz()
|
||||
|
||||
def _on_offsets_dragging(self, offsets_table) -> None:
|
||||
"""Slot ligero — actualiza vistas 2D durante drag sin resetear zoom ni actualizar 3D."""
|
||||
@@ -1474,6 +1483,44 @@ class MainWindow(QMainWindow):
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
QMessageBox.critical(self, "Error al exportar", str(exc))
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# CURVA GZ — ESTABILIDAD
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
||||
def _compute_and_show_gz(self) -> None:
|
||||
"""Calcula la curva GZ wall-sided y actualiza el widget de estabilidad."""
|
||||
if self._current_hull is None:
|
||||
return
|
||||
if self._gz_widget is None:
|
||||
return
|
||||
try:
|
||||
hull = self._current_hull
|
||||
kg = hull.depth * 0.55
|
||||
self.statusBar().showMessage("Calculando curva GZ…")
|
||||
QApplication.processEvents()
|
||||
gz_curve = compute_gz_wall_sided(hull, hull.draft, kg=kg)
|
||||
imo_result = check_imo_is2008(gz_curve)
|
||||
self._gz_widget.set_curve(gz_curve, imo_result)
|
||||
# Actualizar indicador IMO en la barra de hidrostáticos
|
||||
self._hydro.set_imo_status(
|
||||
imo_result.overall_passed,
|
||||
"" if imo_result.overall_passed else "GZ",
|
||||
)
|
||||
self.statusBar().showMessage(
|
||||
f"Curva GZ calculada — {hull.name} "
|
||||
f"GM={gz_curve.gm:.3f}m GZmax={gz_curve.gz_max:.3f}m "
|
||||
f"AVS={gz_curve.avs:.0f}° "
|
||||
f"IMO={'CUMPLE' if imo_result.overall_passed else 'FALLA'}"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("Error al calcular curva GZ: %s", exc)
|
||||
|
||||
def _on_show_stability(self) -> None:
|
||||
"""Muestra el módulo de estabilidad GZ (calcula si hay casco disponible)."""
|
||||
if self._current_hull is not None:
|
||||
self._compute_and_show_gz()
|
||||
self._module_area.activate(ModuleArea.MOD_STABILITY)
|
||||
|
||||
def _ask_save(self) -> bool:
|
||||
reply = QMessageBox.question(
|
||||
self, "Cambios sin guardar",
|
||||
|
||||
Reference in New Issue
Block a user