feat(sprint2): wizard 'Nuevo Proyecto' + 5 generadores paramétricos de casco

Generadores paramétricos (arshipdesign/parametric/):
  - wizard_planing.py    → V-fondo, chine dura, deadrise variable AP→FP
  - wizard_cruiser.py    → Carena redonda, plan form Lackenby, Cm ajustable
  - wizard_workboat.py   → Sección cajón, pantoque duro, fondo plano
  - wizard_sailing_mono.py → Velero fin keel, sección fina, LCB a popa
  - series60.py          → Serie 60 / mercante full, Cm ~ 0.96
  - __init__.py          → API unificada generate_hull(family, lpp, beam, draft)
                           + HullFamily enum con labels, rangos Cb, descripciones

Wizard UI (arshipdesign/ui/dialogs/wizards.py):
  - NewShipWizard: QDialog 4 pasos con barra de progreso animada
  - _StepDimensions:  nombre, Lpp, B, puntal, calado + ratios L/B y B/T en vivo
  - _StepFamily:      6 FamilyCard con HullThumbnail QPainter (sección maestra)
  - _StepRefine:      sliders Cb y LCB, spinboxes discretización
  - _StepPreview:     tabla hidrostáticos completa (V, D, Cb, LCB, KB, Awp...)
  - Al aceptar → Hull cargado en visor 3D del viewport Perspectiva

MainWindow:
  - _on_new_project() abre NewShipWizard (antes creaba proyecto vacío)
  - Tras accept(): carga hull en Viewer3DWidget si disponible

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 07:52:46 -04:00
parent 503e00bfc9
commit 002c00aff3
8 changed files with 1685 additions and 15 deletions
+169 -1
View File
@@ -1 +1,169 @@
# arshipdesign/parametric
"""
arshipdesign.parametric — Generadores paramétricos de cascos.
Exporta la función unificada ``generate_hull`` y los constantes
de familia usados por el wizard de nuevo proyecto.
Uso rápido
----------
>>> from arshipdesign.parametric import generate_hull, HullFamily
>>> hull = generate_hull(
... family=HullFamily.DISPLACEMENT,
... lpp=15.0, beam=4.0, draft=1.60, depth=2.30,
... cb=0.55,
... )
"""
from __future__ import annotations
from enum import Enum, auto
from typing import Any
from arshipdesign.core.hull import Hull
class HullFamily(str, Enum):
"""Familia de carena disponible en el wizard."""
PLANING = "planing" # Planeador — V-fondo, chine dura
DISPLACEMENT = "displacement" # Desplazamiento — carena redonda
SEMI_DISP = "semi_disp" # Semi-desplazamiento
WORKBOAT = "workboat" # Workboat / Supply / Remolcador
SAILING = "sailing" # Velero monocasco fin keel
MERCHANT = "merchant" # Buque mercante / Serie 60
@property
def label_es(self) -> str:
return {
"planing": "Planeo",
"displacement":"Desplazamiento",
"semi_disp": "Semi-desplazamiento",
"workboat": "Workboat / Supply",
"sailing": "Velero",
"merchant": "Mercante / Supply full",
}[self.value]
@property
def cb_default(self) -> float:
return {
"planing": 0.43,
"displacement":0.55,
"semi_disp": 0.50,
"workboat": 0.67,
"sailing": 0.40,
"merchant": 0.70,
}[self.value]
@property
def cb_range(self) -> tuple[float, float]:
return {
"planing": (0.38, 0.48),
"displacement":(0.45, 0.65),
"semi_disp": (0.46, 0.58),
"workboat": (0.60, 0.75),
"sailing": (0.35, 0.46),
"merchant": (0.60, 0.82),
}[self.value]
@property
def description_es(self) -> str:
return {
"planing":
"Embarcación rápida con fondo en V y chine dura.\n"
"Fn > 0.50 — lanchas, patrulleras, RIBs.",
"displacement":
"Carena redondeada de velocidad moderada.\n"
"Fn 0.200.35 — cruceros, pesqueros, ferrys.",
"semi_disp":
"Compromiso entre planeo y desplazamiento.\n"
"Fn 0.350.55 — yates de motor, patrulleras.",
"workboat":
"Sección cajón con pantoque duro.\n"
"Fn < 0.22 — remolcadores, supply, barcazas.",
"sailing":
"Cuerpo fino con quilla de aleta.\n"
"Veleros de recreo y regata.",
"merchant":
"Formas llenas tipo Serie 60.\n"
"Fn < 0.20 — carga, RORO, buques de trabajo.",
}[self.value]
# ---------------------------------------------------------------------------
# Función unificada
# ---------------------------------------------------------------------------
def generate_hull(
family: HullFamily | str,
lpp: float,
beam: float,
draft: float,
depth: float | None = None,
name: str = "",
n_stations: int = 21,
n_waterlines: int = 11,
**kwargs: Any,
) -> Hull:
"""Genera un Hull paramétrico de la familia indicada.
Parámetros
----------
family : HullFamily
Tipo de carena (ver HullFamily).
lpp : float
Eslora entre perpendiculares [m].
beam : float
Manga máxima [m].
draft : float
Calado de diseño [m].
depth : float, optional
Puntal de trazado [m]. Si es None usa draft * 1.45.
name : str
Nombre del proyecto/casco.
n_stations : int
Número de estaciones transversales (≥ 7, default 21).
n_waterlines : int
Número de líneas de agua (≥ 5, default 11).
**kwargs
Parámetros adicionales específicos de cada familia
(p.ej. deadrise_mid para planing, cb para displacement, etc.)
Retorna
-------
Hull
"""
fam = HullFamily(family) if isinstance(family, str) else family
if depth is None:
depth = draft * 1.45
if not name:
name = f"{fam.label_es} {lpp:.0f}m"
common = dict(
name=name, lpp=lpp, beam=beam, draft=draft, depth=depth,
n_stations=n_stations, n_waterlines=n_waterlines,
)
common.update(kwargs)
if fam == HullFamily.PLANING:
from arshipdesign.parametric.wizard_planing import make_planing_hull
return make_planing_hull(**common)
elif fam in (HullFamily.DISPLACEMENT, HullFamily.SEMI_DISP):
from arshipdesign.parametric.wizard_cruiser import make_displacement_hull
if fam == HullFamily.SEMI_DISP and "cb" not in kwargs:
common.setdefault("cb", 0.50)
return make_displacement_hull(**common)
elif fam == HullFamily.WORKBOAT:
from arshipdesign.parametric.wizard_workboat import make_workboat_hull
return make_workboat_hull(**common)
elif fam == HullFamily.SAILING:
from arshipdesign.parametric.wizard_sailing_mono import make_sailing_hull
return make_sailing_hull(**common)
elif fam == HullFamily.MERCHANT:
from arshipdesign.parametric.series60 import make_merchant_hull
return make_merchant_hull(**common)
else:
raise ValueError(f"Familia de carena desconocida: {fam!r}")