Files
AR-Shipdesign/arshipdesign/parametric/wizard_workboat.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

113 lines
3.7 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 — Workboat / Supply / Remolcador (sección cajón).
Genera cascos de trabajo con:
- Sección transversal rectangular-redondeada
- Fondo casi plano (quilla de barra)
- Radio de pantoque duro
- Cuadernas muy llenas (Cm 0.920.97)
- Proa de bulbo cilíndrico o proa plana
Parámetros típicos:
Cb: 0.60 0.75
Velocidad/Froude: Fn 0.12 0.22
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_workboat_hull(
name: str = "Workboat / Supply",
lpp: float = 15.0,
beam: float = 5.0,
draft: float = 1.80,
depth: float = 2.60,
cb: float = 0.67,
lcb_frac: float = 0.50,
bilge_radius_frac: float = 0.12, # radio de pantoque / calado
flat_bottom_frac: float = 0.85, # ancho fondo plano / manga
n_stations: int = 21,
n_waterlines: int = 11,
) -> Hull:
"""Genera un casco tipo workboat con sección cajón.
Parámetros
----------
bilge_radius_frac : float
Radio del pantoque como fracción del calado (0.080.18).
flat_bottom_frac : float
Anchura del fondo plano como fracción de la manga (0.800.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
lcb_shift = 2.0 * (lcb_frac - 0.5)
# Plan form: muy llena, extremos redondeados pero no agudos
f_plan = _workboat_plan_form(xi, cb, lcb_shift)
# Geometría del pantoque
r_bilge = draft * bilge_radius_frac # radio en metros
y_flat = (beam / 2.0) * flat_bottom_frac # semi-manga del fondo plano
data = np.zeros((n_stations, n_waterlines))
for i in range(n_stations):
y_wl = (beam / 2.0) * f_plan[i]
# Escalar fondo plano y radio de pantoque con el ancho de la estación
scale = f_plan[i]
y_flat_i = y_flat * scale
r_bilge_i = r_bilge * max(scale, 0.3)
for j, z in enumerate(z_wl):
if z <= r_bilge_i:
# ── Zona de pantoque redondeado ──────────────────────
# Arco de círculo: y² + (z - r)² = r² → y = sqrt(r² - (r-z)²)
dz = r_bilge_i - z
inner = max(0.0, r_bilge_i**2 - dz**2)
y = y_flat_i - r_bilge_i + np.sqrt(inner)
else:
# ── Zona de costado (cuasi-vertical) ─────────────────
# Ligero flare hacia afuera
flare = 0.04
y = y_flat_i + (y_wl - y_flat_i) * (z - r_bilge_i) / (draft - r_bilge_i + 1e-9)
y = min(y, y_wl)
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 _workboat_plan_form(
xi: np.ndarray, cb: float, lcb_shift: float
) -> np.ndarray:
"""Plan form para workboat: extremos redondeados pero llenos."""
n = _workboat_plan_exponent(cb)
xi_s = np.clip(xi - lcb_shift, -1.0, 1.0)
f = np.maximum(0.0, 1.0 - np.abs(xi_s) ** n)
return f
def _workboat_plan_exponent(cb: float) -> float:
"""Cb → exponente plan form (workboat más llena que displacement)."""
cb_vals = [0.55, 0.62, 0.68, 0.73, 0.78]
n_vals = [2.5, 3.0, 3.8, 4.5, 5.5]
return float(np.interp(cb, cb_vals, n_vals))