Files
AR-Shipdesign/arshipdesign/parametric/wizard_sailing_mono.py
T
alro65 002c00aff3 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>
2026-05-27 07:52:46 -04:00

117 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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))