002c00aff3
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>
117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
"""
|
||
Generador paramétrico — Velero Monocasco (fin keel).
|
||
|
||
Genera cascos para veleros de quilla de aleta:
|
||
- Cuerpo del casco: fino, secciones en V
|
||
- Quilla de aleta: apéndice separado con perfil NACA simplificado
|
||
- Proa fina (ángulo de entrada 10°–16°)
|
||
- Popa de espejo amplia
|
||
|
||
Parámetros típicos:
|
||
Cb (cuerpo): 0.35 – 0.44
|
||
L/B: 3.0 – 3.8
|
||
Froude de diseño: 0.30 – 0.45
|
||
|
||
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_sailing_hull(
|
||
name: str = "Velero Monocasco",
|
||
lpp: float = 10.0,
|
||
beam: float = 3.20,
|
||
draft: float = 0.55, # calado del cuerpo (sin quilla)
|
||
depth: float = 1.80,
|
||
draft_with_keel: float = 1.80, # calado total con quilla
|
||
cb: float = 0.40,
|
||
lcb_frac: float = 0.53, # ligeramente a popa del midship
|
||
cm: float = 0.75,
|
||
deadrise_mid: float = 18.0,
|
||
n_stations: int = 21,
|
||
n_waterlines: int = 9,
|
||
) -> Hull:
|
||
"""Genera un casco de velero de quilla de aleta.
|
||
|
||
El Hull resultante representa el cuerpo del casco sin la quilla.
|
||
La quilla de aleta se añade como apéndice en el módulo de geometría.
|
||
|
||
Parámetros
|
||
----------
|
||
draft : float
|
||
Calado del cuerpo del casco (sin quilla) [m].
|
||
draft_with_keel : float
|
||
Calado total incluyendo la quilla [m].
|
||
deadrise_mid : float
|
||
Ángulo de astilla muerta en cuaderna maestra [°].
|
||
"""
|
||
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
|
||
|
||
lcb_shift = 2.0 * (lcb_frac - 0.5)
|
||
|
||
# Plan form: fina en proa, moderadamente llena a popa
|
||
f_plan = _sailing_plan_form(xi, cb, lcb_shift)
|
||
|
||
# Exponente de sección
|
||
alpha_mid = max(0.30, 2.0 * (1.0 - cm)) # ≈ 0.50 para Cm=0.75
|
||
|
||
data = np.zeros((n_stations, n_waterlines))
|
||
dr_mid_rad = np.radians(deadrise_mid)
|
||
|
||
for i in range(n_stations):
|
||
y_wl = (beam / 2.0) * f_plan[i]
|
||
|
||
# Interpolar entre sección en V (extremos) y sección redonda (midship)
|
||
local_f = f_plan[i]
|
||
alpha = alpha_mid + (0.75 - alpha_mid) * (1.0 - local_f ** 0.7)
|
||
alpha = np.clip(alpha, alpha_mid, 0.80)
|
||
|
||
for j, z in enumerate(z_wl):
|
||
v = z / draft
|
||
y = y_wl * (v ** alpha)
|
||
data[i, j] = max(0.0, y)
|
||
|
||
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 _sailing_plan_form(
|
||
xi: np.ndarray, cb: float, lcb_shift: float
|
||
) -> np.ndarray:
|
||
"""Plan form para velero: fina en proa, moderada en popa."""
|
||
n = _sailing_plan_exponent(cb)
|
||
xi_s = np.clip(xi - lcb_shift, -1.0, 1.0)
|
||
|
||
f = np.zeros_like(xi)
|
||
for k, x in enumerate(xi_s):
|
||
if x <= 0:
|
||
# Run (AP): más llena que la entry
|
||
t = -x
|
||
f[k] = max(0.0, 1.0 - t ** (n * 0.85))
|
||
else:
|
||
# Entry (FP): fina, típico de velero
|
||
t = x
|
||
f[k] = max(0.0, 1.0 - t ** n)
|
||
return f
|
||
|
||
|
||
def _sailing_plan_exponent(cb: float) -> float:
|
||
cb_vals = [0.30, 0.36, 0.42, 0.48, 0.54]
|
||
n_vals = [1.0, 1.3, 1.6, 2.0, 2.4]
|
||
return float(np.interp(cb, cb_vals, n_vals))
|