Modulo 1: editor interactivo de tabla de offsets (Task 10)

- offsets_editor.py: OffsetsEditor (QTableWidget editable con zoom de
  celdas modificadas en ambar, invalidas en rojo; Aplicar reconstruye Hull
  y emite hull_changed; importar/exportar CSV; info bar con dimensiones).

- main_window.py: OffsetsEditor inyectado como MOD_OFFSETS (F4);
  _load_hull_viewers recibe _skip_offsets_editor para evitar bucle;
  _on_hull_changed_from_editor propaga el Hull editado a todos los visores
  y al panel de hidrostáticos en vivo; ModuleArea.set_module_widget()
  para reemplazar placeholders en tiempo de setup.

86 tests pasan sin regresiones.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 08:30:30 -04:00
parent bdfd5ac4ca
commit 2137b0a228
2 changed files with 472 additions and 6 deletions
+31 -4
View File
@@ -633,6 +633,17 @@ class ModuleArea(QStackedWidget):
lo.addWidget(sub)
return w
def set_module_widget(self, idx: int, widget: QWidget) -> None:
"""Sustituye el placeholder de un modulo por el widget real.
Preserva el indice: los modulos con indice mayor no se desplazan.
"""
old = self.widget(idx)
if old is not None:
self.removeWidget(old)
old.deleteLater()
self.insertWidget(idx, widget)
def activate(self, module_index: int) -> None:
if 0 <= module_index < self.count():
self.setCurrentIndex(module_index)
@@ -849,6 +860,12 @@ class MainWindow(QMainWindow):
if _vp is not None:
_vp.set_canvas(_widget)
# Editor interactivo de offsets (sustituye el placeholder MOD_OFFSETS)
from arshipdesign.ui.widgets.offsets_editor import OffsetsEditor
self._offsets_editor = OffsetsEditor()
self._offsets_editor.hull_changed.connect(self._on_hull_changed_from_editor)
self._module_area.set_module_widget(ModuleArea.MOD_OFFSETS, self._offsets_editor)
# Dock izquierdo — capas
self._layers_panel = LayersPanel(self._strings)
self._dock_layers = QDockWidget("Capas", self)
@@ -1266,16 +1283,19 @@ class MainWindow(QMainWindow):
self._update_title()
self._layers_panel.set_project(self._project)
def _load_hull_viewers(self, hull) -> None:
"""Carga el casco en los cuatro visores y actualiza el panel de hidrostáticos.
def _load_hull_viewers(self, hull, *, _skip_offsets_editor: bool = False) -> None:
"""Carga el casco en todos los visores (2D, 3D, offsets) y actualiza hidrostáticos.
Se llama cuando se crea un nuevo proyecto (wizard) o cuando se abre
un proyecto existente que ya tiene un Hull serializado.
``_skip_offsets_editor=True`` evita el bucle de retroalimentacion cuando
la llamada proviene del propio editor de offsets.
"""
# ── Visores 2D ────────────────────────────────────────────
self._viewer_bodyplan.set_hull(hull)
self._viewer_profile.set_hull(hull)
self._viewer_plan.set_hull(hull)
# ── Editor de offsets ─────────────────────────────────────
if not _skip_offsets_editor:
self._offsets_editor.set_hull(hull)
# ── Visor 3D ──────────────────────────────────────────────
if self._viewer_3d is not None:
try:
@@ -1285,6 +1305,13 @@ class MainWindow(QMainWindow):
# ── Panel hidrostáticos ───────────────────────────────────
self._update_hydrostatics(hull)
def _on_hull_changed_from_editor(self, hull) -> None:
"""Slot: el editor de offsets reconstruyo el Hull — propagar a los demas visores."""
self._current_hull = hull
# _skip_offsets_editor=True para no re-poblar la tabla (ya esta actualizada)
self._load_hull_viewers(hull, _skip_offsets_editor=True)
self.statusBar().showMessage(f"Offsets actualizados — {hull.name}")
def _update_hydrostatics(self, hull) -> None:
"""Calcula hidrostáticos al calado de diseño y actualiza la barra inferior.