feat(sprint1): motor NURBS, modelos de casco, visor 3D PyVista

Geometría:
  - BSplineCurve: interpolación scipy, arc_length, tangente, chord-length
  - LoftedSurface: lofting de secciones → RectBivariateSpline bivariate

Core (casco Wigley como caso de prueba):
  - Section: área, centroide_z, max_half_breadth, curva B-spline
  - OffsetsTable: from_wigley(), to_sections(), interpolación xy
  - Hull: volumen, Awp, LCB, VCB, Cb, Cm, Cp, desplazamiento, to_mesh()

UI:
  - Viewer3DWidget (pyvistaqt.QtInteractor): casco Wigley por defecto
    al arrancar, fondo navy, waterplane semi-transparente, fallback
    graceful si PyVista no disponible
  - MainWindow: Viewer3DWidget inyectado en viewport Perspectiva 3D

Tests: 39 nuevos tests, fórmulas analíticas Wigley verificadas (±1%)
  V = 4BLT/9, Cb = 4/9, Awp = 2BL/3 (derivación correcta)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 01:07:35 -04:00
parent 135f097079
commit 503e00bfc9
8 changed files with 1519 additions and 12 deletions
+10
View File
@@ -822,6 +822,16 @@ class MainWindow(QMainWindow):
self._module_area = ModuleArea()
self.setCentralWidget(self._module_area)
# Inyectar visor 3D en el viewport Perspectiva (diferido)
from arshipdesign.ui.widgets.viewer_3d import Viewer3DWidget, _PYVISTA_OK
if _PYVISTA_OK:
self._viewer_3d = Viewer3DWidget()
vp = self._module_area.four_viewport.viewport("perspective")
if vp is not None:
vp.set_canvas(self._viewer_3d)
else:
self._viewer_3d = None
# Dock izquierdo — capas
self._layers_panel = LayersPanel(self._strings)
self._dock_layers = QDockWidget("Capas", self)