126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
"""
|
||
Generador paramétrico — Buque mercante / Supply full (inspirado en Serie 60).
|
||
|
||
Genera cascos para buques de carga, supply vessels y embarcaciones de trabajo
|
||
con coeficientes de bloque altos:
|
||
- Fondo casi plano en la zona central
|
||
- Cuadernas rectangulares con pantoque redondeado
|
||
- Proa de bulbo (simplificada) o proa recta
|
||
- Popa transom o popa de crucero
|
||
|
||
Parámetros típicos:
|
||
Cb: 0.60 – 0.80
|
||
Froude: 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 _standard_sheer_z(
|
||
x_sta: np.ndarray, lpp: float, depth: float,
|
||
fwd_rise_frac: float = 0.04, aft_rise_frac: float = 0.020,
|
||
) -> np.ndarray:
|
||
"""Línea de cubierta parabólica: mínimo en cuaderna maestra, sube hacia proa/popa."""
|
||
xi = x_sta / lpp # 0=AP, 1=FP, 0.5=midship
|
||
return np.where(
|
||
xi >= 0.5,
|
||
depth * (1.0 + fwd_rise_frac * ((xi - 0.5) / 0.5) ** 2),
|
||
depth * (1.0 + aft_rise_frac * ((0.5 - xi) / 0.5) ** 2),
|
||
)
|
||
|
||
|
||
def make_merchant_hull(
|
||
name: str = "Buque Mercante / Supply",
|
||
lpp: float = 20.0,
|
||
beam: float = 6.0,
|
||
draft: float = 2.40,
|
||
depth: float = 3.20,
|
||
cb: float = 0.70,
|
||
lcb_frac: float = 0.50,
|
||
cm: float = 0.96, # sección maestra casi rectangular
|
||
bilge_radius_frac: float = 0.08,
|
||
flat_bottom_frac: float = 0.90,
|
||
n_stations: int = 21,
|
||
n_waterlines: int = 11,
|
||
) -> Hull:
|
||
"""Genera un casco tipo Serie 60 / buque mercante.
|
||
|
||
Parámetros
|
||
----------
|
||
cb : float
|
||
Coeficiente de bloque objetivo (0.60–0.80).
|
||
cm : float
|
||
Coeficiente de cuaderna maestra (0.93–0.98).
|
||
bilge_radius_frac : float
|
||
Radio del pantoque / calado (0.06–0.14).
|
||
flat_bottom_frac : float
|
||
Ancho del fondo plano / manga (0.85–0.94).
|
||
"""
|
||
x_sta = np.linspace(0.0, lpp, n_stations)
|
||
sheer_z = _standard_sheer_z(x_sta, lpp, depth, fwd_rise_frac=0.04, aft_rise_frac=0.020)
|
||
z_wl = np.linspace(0.0, float(sheer_z.max()), n_waterlines)
|
||
xi = (x_sta / lpp - 0.5) * 2.0
|
||
|
||
lcb_shift = 2.0 * (lcb_frac - 0.5)
|
||
f_plan = _merchant_plan_form(xi, cb, lcb_shift)
|
||
|
||
r_bilge = draft * bilge_radius_frac
|
||
y_flat_b = (beam / 2.0) * flat_bottom_frac
|
||
|
||
data = np.zeros((n_stations, n_waterlines))
|
||
|
||
for i in range(n_stations):
|
||
y_wl = (beam / 2.0) * f_plan[i]
|
||
scale = f_plan[i]
|
||
|
||
y_flat_i = y_flat_b * scale
|
||
r_bilge_i = r_bilge * max(scale, 0.25)
|
||
|
||
for j, z in enumerate(z_wl):
|
||
if z <= r_bilge_i:
|
||
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:
|
||
# Costados casi verticales (Cm alto)
|
||
t = (z - r_bilge_i) / max(draft - r_bilge_i, 1e-6)
|
||
# Cm ajusta plenitud de la zona de costados
|
||
flare_side = (1.0 - cm) * 0.15
|
||
y = y_flat_i + (y_wl - y_flat_i) * (t + flare_side * t * (1 - t))
|
||
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, sheer_z=sheer_z,
|
||
)
|
||
|
||
|
||
def _merchant_plan_form(
|
||
xi: np.ndarray, cb: float, lcb_shift: float
|
||
) -> np.ndarray:
|
||
"""Plan form mercante: central muy llena, extremos redondeados."""
|
||
n = _merchant_plan_exponent(cb)
|
||
xi_s = np.clip(xi - lcb_shift, -1.0, 1.0)
|
||
return np.maximum(0.0, 1.0 - np.abs(xi_s) ** n)
|
||
|
||
|
||
def _merchant_plan_exponent(cb: float) -> float:
|
||
cb_vals = [0.58, 0.65, 0.70, 0.75, 0.82]
|
||
n_vals = [3.0, 4.0, 5.0, 6.0, 8.0]
|
||
return float(np.interp(cb, cb_vals, n_vals))
|