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
+34 -2
View File
@@ -860,6 +860,10 @@ class MainWindow(QMainWindow):
if _vp is not None:
_vp.set_canvas(_widget)
# Conectar edición interactiva de control points → propagar a todos los visores
self._viewer_bodyplan.offsets_edited.connect(self._on_offsets_edited_from_viewer)
self._viewer_plan.offsets_edited.connect(self._on_offsets_edited_from_viewer)
# Editor interactivo de offsets (sustituye el placeholder MOD_OFFSETS)
from arshipdesign.ui.widgets.offsets_editor import OffsetsEditor
self._offsets_editor = OffsetsEditor()
@@ -1316,11 +1320,39 @@ class MainWindow(QMainWindow):
"""Slot: el editor de offsets reconstruyo el Hull — propagar a visores y proyecto."""
self._current_hull = hull
if self._project is not None:
self._project.set_hull(hull) # mantener proyecto sincronizado
# _skip_offsets_editor=True para no re-poblar la tabla (ya esta actualizada)
self._project.set_hull(hull)
self._load_hull_viewers(hull, _skip_offsets_editor=True)
self.statusBar().showMessage(f"Offsets actualizados — {hull.name}")
def _on_offsets_edited_from_viewer(self, offsets_table) -> None:
"""Slot: un visor 2D editó un punto de control — sincronizar todos los visores.
La OffsetsTable ya fue modificada in-place por el visor (durante el drag).
Aquí propagamos el cambio al visor 3D, al panel de hidrostáticos y al
editor de offsets, e informamos al proyecto del estado nuevo.
"""
hull = self._current_hull
if hull is None:
return
# hull.offsets ya contiene los cambios (modificación in-place del visor)
if self._project is not None:
self._project.set_hull(hull)
# Refrescar la vista cruzada (edición body plan actualiza planta y viceversa)
self._viewer_bodyplan.set_hull(hull)
self._viewer_profile.set_hull(hull)
self._viewer_plan.set_hull(hull)
# Sincronizar editor de tabla de offsets
self._offsets_editor.set_hull(hull)
# Actualizar visor 3D con la geometría nueva
if self._viewer_3d is not None:
try:
self._viewer_3d.load_hull(hull)
except Exception as exc:
logger.warning("Error al actualizar visor 3D: %s", exc)
# Actualizar barra de hidrostáticos
self._update_hydrostatics(hull)
self.statusBar().showMessage(f"Geometría editada — {hull.name}")
def _update_hydrostatics(self, hull) -> None:
"""Calcula hidrostáticos al calado de diseño y actualiza la barra inferior.