Files
AR-Shipdesign/arshipdesign/parametric/wizard_planing.py
T

176 lines
6.4 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 — Casco Planeador (V-fondo, chine dura).
Genera una tabla de offsets realista para embarcaciones de planeo:
- Fondo en V con ángulo de astilla muerta variable
- Chine dura recorriendo toda la eslora
- Popa de espejo plana
- Proa aguda con alto ángulo de astilla muerta
- Sección transversal: dos regiones (bajo/sobre chine)
Parámetros típicos:
Cb: 0.40 0.48
Deadrise midship: 12° 22°
Deadrise bow: 35° 55°
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.03, aft_rise_frac: float = 0.015,
) -> 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),
)
# ---------------------------------------------------------------------------
# API pública
# ---------------------------------------------------------------------------
def make_planing_hull(
name: str = "Planeador",
lpp: float = 10.0,
beam: float = 3.0,
draft: float = 0.70,
depth: float = 1.20,
deadrise_mid: float = 18.0, # grados en cuaderna maestra
deadrise_bow: float = 45.0, # grados en proa
deadrise_stern: float = 8.0, # grados en popa
chine_frac: float = 0.55, # z_chine / draft
flare: float = 0.10, # ensanchamiento por encima del chine [fracción]
n_stations: int = 21,
n_waterlines: int = 11,
) -> Hull:
"""Genera un casco planeador con forma V y chine dura.
Parámetros
----------
deadrise_mid : float
Ángulo de astilla muerta [°] en la cuaderna maestra (midship).
Valores típicos: 12°–22°.
deadrise_bow : float
Ángulo de astilla muerta [°] en proa.
Valores típicos: 35°–55°.
chine_frac : float
Altura del chine como fracción del calado (01).
flare : float
Fracción de ensanchamiento por encima del chine (0 = sin ensanche).
"""
x_sta = np.linspace(0.0, lpp, n_stations)
sheer_z = _standard_sheer_z(x_sta, lpp, depth, fwd_rise_frac=0.03, aft_rise_frac=0.015)
z_wl = np.linspace(0.0, float(sheer_z.max()), n_waterlines)
xi = (x_sta / lpp - 0.5) * 2.0 # normalizado ∈ [1, 1], 0=midship
# ── Plan form: ancho en línea de agua por estación ─────────────────
# El planeador tiene popa muy ancha y proa más estrecha
# f_stern > f_bow para que la popa sea la sección más llena
f_plan = _planing_plan_form(xi) # ∈ [0, 1]
# ── Ángulo de astilla muerta varía a lo largo de la eslora ─────────
dr_arr = _deadrise_distribution(xi, deadrise_mid, deadrise_bow, deadrise_stern)
data = np.zeros((n_stations, n_waterlines))
for i in range(n_stations):
y_max = (beam / 2.0) * f_plan[i]
z_c = draft * chine_frac # altura del chine
dr = np.radians(dr_arr[i]) # ángulo de astilla en esta estación
for j, z in enumerate(z_wl):
if z <= z_c:
# ── Zona V-fondo (bajo el chine) ──────────────────────
# y_chine se calcula desde el ángulo de astilla muerta
# tan(dr) = z_c / y_chine → y_chine = z_c / tan(dr)
tan_dr = max(np.tan(dr), 0.01)
y_chine_dr = z_c / tan_dr
y_chine = min(y_chine_dr, y_max)
y = y_chine * (z / z_c)
else:
# ── Zona sobre el chine (costado) ─────────────────────
tan_dr = max(np.tan(dr), 0.01)
y_chine_dr = z_c / tan_dr
y_chine = min(y_chine_dr, y_max)
# Costado recto (hard-chine) desde y_chine hasta y_max en cubierta.
# El parámetro flare añade ensanche adicional sobre y_max.
t_side = (z - z_c) / (draft - z_c + 1e-9)
y = y_chine + (y_max * (1.0 + flare) - y_chine) * t_side
y = min(y, y_max * (1.0 + flare))
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,
)
# ---------------------------------------------------------------------------
# Helpers internos
# ---------------------------------------------------------------------------
def _planing_plan_form(xi: np.ndarray) -> np.ndarray:
"""
Plan form del planeador: sección de popa llena, proa estrecha.
xi ∈ [1, 1], xi=1 AP (popa), xi=+1 FP (proa).
El planeador tiene la sección maestra desplazada hacia popa (~35% Lpp desde FP).
"""
# Apex en xi ≈ 0.3 (30% desde popa) → máximo ancho allí
apex = -0.30
# Forma asimétrica: run suave hacia popa, proa afilada
f = np.zeros_like(xi)
for k, x in enumerate(xi):
if x >= apex:
# Desde apex hacia proa: caída rápida (proa aguda)
t = (x - apex) / (1.0 - apex)
f[k] = (1.0 - t ** 1.6)
else:
# Desde apex hacia popa: espejo plano, caída suave
t = (apex - x) / (1.0 + apex)
f[k] = (1.0 - t ** 2.8)
return np.maximum(f, 0.0)
def _deadrise_distribution(
xi: np.ndarray,
dr_mid: float,
dr_bow: float,
dr_stern: float,
) -> np.ndarray:
"""Distribución del ángulo de astilla muerta a lo largo de la eslora.
Varía suavemente de dr_stern (popa) → dr_mid (midship) → dr_bow (proa).
xi ∈ [1, 1], 1=AP, +1=FP.
"""
dr = np.zeros_like(xi)
for k, x in enumerate(xi):
if x >= 0.0:
# midship → proa
t = x
dr[k] = dr_mid + (dr_bow - dr_mid) * t ** 1.5
else:
# midship → popa
t = -x
dr[k] = dr_mid + (dr_stern - dr_mid) * t ** 2.0
return dr