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:
@@ -1,2 +1,130 @@
|
||||
"""Wizard crucero. Stub — Sprint 11."""
|
||||
raise NotImplementedError("wizard_cruiser — Sprint 11")
|
||||
"""
|
||||
Generador paramétrico — Casco de Desplazamiento / Crucero (carena redonda).
|
||||
|
||||
Genera una tabla de offsets para embarcaciones de desplazamiento:
|
||||
- Carena redondeada (round bilge)
|
||||
- Sección maestra llena (Cm 0.82–0.90)
|
||||
- Proa fina, popa de espejo o crucero
|
||||
- LCB ajustable
|
||||
|
||||
Parámetros típicos:
|
||||
Cb: 0.45 – 0.65
|
||||
Velocidad/Froude: Fn 0.20 – 0.35
|
||||
|
||||
Autor: Álvaro Romero | Sprint 2A — AR-ShipDesign
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from arshipdesign.core.hull import Hull
|
||||
from arshipdesign.core.offsets import OffsetsTable
|
||||
|
||||
|
||||
def make_displacement_hull(
|
||||
name: str = "Crucero de Desplazamiento",
|
||||
lpp: float = 12.0,
|
||||
beam: float = 3.80,
|
||||
draft: float = 1.40,
|
||||
depth: float = 2.20,
|
||||
cb: float = 0.55,
|
||||
lcb_frac: float = 0.52, # fracción de Lpp desde AP
|
||||
cm: float = 0.86, # coeficiente de cuaderna maestra
|
||||
n_stations: int = 21,
|
||||
n_waterlines: int = 11,
|
||||
) -> Hull:
|
||||
"""Genera un casco de desplazamiento de carena redonda.
|
||||
|
||||
Parámetros
|
||||
----------
|
||||
cb : float
|
||||
Coeficiente de bloque objetivo (0.45–0.65).
|
||||
lcb_frac : float
|
||||
Posición del LCB como fracción de Lpp desde AP (0.50–0.55).
|
||||
cm : float
|
||||
Coeficiente de cuaderna maestra (0.82–0.92).
|
||||
"""
|
||||
x_sta = np.linspace(0.0, lpp, n_stations)
|
||||
z_wl = np.linspace(0.0, draft, n_waterlines)
|
||||
xi = (x_sta / lpp - 0.5) * 2.0 # ∈ [−1, 1], 0=midship
|
||||
|
||||
# ── Plan form (semi-manga en flotación) ────────────────────────────
|
||||
# LCB desplazado del midship
|
||||
lcb_shift = 2.0 * (lcb_frac - 0.5) # ∈ [−1, 1]
|
||||
f_plan = _displacement_plan_form(xi, cb, lcb_shift)
|
||||
|
||||
# ── Exponente de forma de sección ──────────────────────────────────
|
||||
# alpha controla la plenitud de la sección transversal
|
||||
# alpha pequeño → sección llena (Cm alto)
|
||||
# alpha = 2*(1 - Cm) según aproximación de Munro-Smith
|
||||
alpha_mid = max(0.25, 2.0 * (1.0 - cm)) # ≈ 0.28 para Cm=0.86
|
||||
|
||||
data = np.zeros((n_stations, n_waterlines))
|
||||
|
||||
for i in range(n_stations):
|
||||
y_wl = (beam / 2.0) * f_plan[i]
|
||||
|
||||
# El exponente de sección varía: más fino en proa/popa
|
||||
local_fullness = f_plan[i]
|
||||
# En extremos (local_fullness→0) el exponente sube → sección más en V
|
||||
alpha = alpha_mid + (1.0 - alpha_mid) * (1.0 - local_fullness ** 0.5)
|
||||
alpha = np.clip(alpha, alpha_mid, 0.80)
|
||||
|
||||
for j, z in enumerate(z_wl):
|
||||
v = z / draft # ∈ [0, 1]
|
||||
data[i, j] = y_wl * (v ** alpha)
|
||||
|
||||
data = np.clip(data, 0.0, None)
|
||||
|
||||
offsets = OffsetsTable(
|
||||
x_stations=x_sta,
|
||||
z_waterlines=z_wl,
|
||||
data=data,
|
||||
station_labels=[f"S{i}" for i in range(n_stations)],
|
||||
lpp=lpp, beam=beam, draft=draft,
|
||||
)
|
||||
return Hull(
|
||||
name=name, lpp=lpp, beam=beam, depth=depth, draft=draft, offsets=offsets
|
||||
)
|
||||
|
||||
|
||||
def _displacement_plan_form(
|
||||
xi: np.ndarray, cb: float, lcb_shift: float
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Plan form normalizada para carena de desplazamiento.
|
||||
|
||||
Usa una distribución tipo seno modificado con el apex desplazado
|
||||
según la posición del LCB.
|
||||
|
||||
xi ∈ [−1, 1], −1=AP, +1=FP.
|
||||
Retorna f ∈ [0, 1].
|
||||
"""
|
||||
# Exponent de la plan form: más alto = más llena = Cb mayor
|
||||
n = _plan_exponent(cb)
|
||||
|
||||
xi_shifted = xi - lcb_shift
|
||||
xi_shifted = np.clip(xi_shifted, -1.0, 1.0)
|
||||
|
||||
# Plan form base: bell curve asimétrica
|
||||
# Parte de entrada (FP): más suave
|
||||
# Parte de salida (AP): ligeramente más llena
|
||||
f = np.zeros_like(xi)
|
||||
for k, x in enumerate(xi_shifted):
|
||||
if x <= 0:
|
||||
# Run (desde midship hacia AP): ligeramente más llena
|
||||
t = -x
|
||||
f[k] = max(0.0, 1.0 - (t ** (n * 0.90)))
|
||||
else:
|
||||
# Entry (desde midship hacia FP): estándar
|
||||
t = x
|
||||
f[k] = max(0.0, 1.0 - (t ** n))
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def _plan_exponent(cb: float) -> float:
|
||||
"""Mapa Cb → exponente de plan form."""
|
||||
cb_vals = [0.35, 0.45, 0.55, 0.65, 0.75]
|
||||
n_vals = [1.2, 1.6, 2.0, 2.7, 3.5]
|
||||
return float(np.interp(cb, cb_vals, n_vals))
|
||||
|
||||
Reference in New Issue
Block a user