Módulo 1 fixes + Módulo 2 motor hidrostático (Tasks 13–13b)

Fixes Module 1 UI:
- wizard_cruiser/sailing/planing: perfiles sin^n calibrados por Cm, V-bottom
  con ángulo de astilla, corrección zona sobre chine planeador
- viewer_3d: buffer hull pendiente para eliminar race condition 500ms
- viewer_lines: reescritura completa — waterlines visibles, control points
  interactivos (drag DelftShip-style), señal offsets_edited
- main_window: conecta offsets_edited → slot _on_offsets_edited_from_viewer
  que propaga cambios a todos los visores, editor, 3D y barra hidrostática

Módulo 2 — motor HydrostaticCurves (Task 13):
- integrator.py: integrate() (Simpson+trapz), waterplane_strips(), section_areas()
- upright.py: UprightHydrostatics (19 campos), compute_upright() single-pass
- curves_of_form.py: HydrostaticCurves.compute(), at_draft(), to_csv_lines(), to_dict()
- tests/test_module2_hydrostatics.py: 83 tests — Wigley V&V, monotonicidad,
  CSV export, IACS Rec.34 §4.3–4.5; todos los 224 tests pasan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 09:11:58 -04:00
parent 274b3b3f53
commit 98ff57ed08
17 changed files with 1687 additions and 199 deletions
+12 -3
View File
@@ -54,6 +54,7 @@ class Viewer3DWidget(QWidget):
super().__init__(parent)
self._plotter: Optional["QtInteractor"] = None
self._ready = False
self._pending_hull = None # hull recibido antes de que el plotter esté listo
self._build_ui()
# ------------------------------------------------------------------
@@ -105,8 +106,13 @@ class Viewer3DWidget(QWidget):
# Configurar tema dark para que combine con la UI
self._plotter.set_background("#1a1d30") # viewportCanvas color
# Cargar casco Wigley como geometría de bienvenida
self._load_default_wigley()
# Cargar casco pendiente (recibido antes del init) o Wigley por defecto
if self._pending_hull is not None:
mesh = self._pending_hull.to_mesh()
self._render_hull_mesh(mesh)
self._pending_hull = None
else:
self._load_default_wigley()
self._ready = True
logger.info("Viewer3DWidget: QtInteractor iniciado correctamente")
@@ -136,12 +142,15 @@ class Viewer3DWidget(QWidget):
def load_hull(self, hull) -> None:
"""Carga un objeto Hull en el visor.
Si el plotter aún no ha terminado de inicializarse (race condition de 500 ms),
guarda el hull como pendiente — se cargará al final de _init_plotter().
Parámetros
----------
hull : arshipdesign.core.hull.Hull
"""
if not self._ready or self._plotter is None:
logger.warning("Viewer3DWidget no listo — hull no cargado")
self._pending_hull = hull # se cargará cuando _init_plotter termine
return
try:
mesh = hull.to_mesh()