Lines plan visual overhaul: correct colors, full plan view symmetry
Color palette: - Control net (nodes + connecting lines): neutral grey (#A8B8D0 / 110,120,140) — matches DELFTship convention for control polygon handles - Forward stations (proa): bright green #22CC58 - Aft stations (popa): amber #C8A010 - Midship: orange #FF7020 - Node size reduced 4.5→3.0 px so hull curves dominate visually Plan view (Vista de Planta): - World bbox now symmetric: y ∈ [−B/2·1.22, +B/2·1.22] shows BOTH halves - Waterlines drawn as closed contours: CL-AP → starboard curve → CL-FP → port curve (mirrored) → close at CL-AP Every waterline terminates at the centerline at bow and stern - Control net grid: both directions (station-arm + waterline-arm) drawn on port AND starboard — same visual language as DELFTship control polygon - Station reference lines span full beam (both sides) - Centerline (eje de crujía) drawn as solid line dividing the two halves - Edit nodes remain on starboard only; port updates symmetrically Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,28 +44,30 @@ _GRID_STA = QColor(38, 55, 88, 80) # líneas de estación
|
||||
_GRID_WL = QColor(40, 60, 95, 70) # líneas de agua (referencia)
|
||||
_AXIS = QColor("#3e4255")
|
||||
|
||||
# ── Malla de control (control net) — thin, muted ───────────────────────
|
||||
# Capa intermedia entre grilla y curvas del casco.
|
||||
# Conecta los nodos formando el poliedro de control.
|
||||
_CNET_TRAN = QColor(50, 80, 130, 140) # aristas transversales (a lo largo de estación)
|
||||
_CNET_LONG = QColor(35, 90, 80, 110) # aristas longitudinales (a lo largo de LdA)
|
||||
# ── Malla de control (poliedro de control) — gris neutro, muy tenue ────
|
||||
# Los nodos SON los vértices del polígono de control; la curva del casco
|
||||
# pasa CERCA de ellos (interpolante aquí, aproximante en NURBS clásico).
|
||||
# El gris neutro evita confusión con las curvas de casco (verde/ámbar/azul).
|
||||
_CNET_TRAN = QColor(110, 120, 140, 90) # aristas (dirección estación)
|
||||
_CNET_LONG = QColor(100, 112, 132, 75) # aristas (dirección LdA)
|
||||
|
||||
# ── Curvas del casco (sobre la malla) ──────────────────────────────────
|
||||
_WATERLINE = QColor("#2a82c0") # líneas de agua
|
||||
_WL_DESIGN = QColor("#00ccff") # flotación de diseño
|
||||
_SECTION = QColor("#3a9e52") # secciones de proa
|
||||
_SECTION_AFT = QColor("#2a78c0") # secciones de popa
|
||||
_MIDSHIP = QColor("#d89020") # cuaderna maestra
|
||||
_WATERLINE = QColor("#2878C8") # líneas de agua — azul
|
||||
_WL_DESIGN = QColor("#00D0FF") # flotación de diseño — cian
|
||||
_SECTION = QColor("#22CC58") # estaciones de proa — VERDE
|
||||
_SECTION_AFT = QColor("#C8A010") # estaciones de popa — ÁMBAR
|
||||
_MIDSHIP = QColor("#FF7020") # cuaderna maestra — naranja
|
||||
_DECK = QColor("#7058b8") # cubierta
|
||||
_KEEL = QColor("#c85858") # quilla
|
||||
_TEXT = QColor("#7a8ba8")
|
||||
|
||||
# ── Nodos (handles) — encima de todo, color único: NARANJA ─────────────
|
||||
# El naranja no existe en ninguna curva del casco → cero ambigüedad.
|
||||
_NODE_NORMAL = QColor("#FF8000") # naranja: estado de reposo
|
||||
_NODE_HOVER = QColor("#FFD700") # oro: hover
|
||||
_NODE_DRAG = QColor("#FF2020") # rojo vivo: arrastrando
|
||||
_NODE_R = 4.5 # px semi-lado del cuadrado
|
||||
# ── Nodos (vértices del polígono de control) — gris-azulado ────────────
|
||||
# Gris claro = convenio DELFTship/Maxsurf para puntos de control.
|
||||
# Pequeño (3 px) para no tapar las curvas del casco.
|
||||
_NODE_NORMAL = QColor("#A8B8D0") # gris-azulado: reposo
|
||||
_NODE_HOVER = QColor("#E0EAFF") # casi blanco: hover
|
||||
_NODE_DRAG = QColor("#FF3838") # rojo: arrastrando
|
||||
_NODE_R = 3.0 # px semi-lado (era 4.5)
|
||||
_CPT_HIT = 16.0 # px umbral de captura (alias legacy)
|
||||
_CPT_RADIUS = _NODE_R # alias legacy
|
||||
|
||||
@@ -336,34 +338,47 @@ def _draw_cnet_bodyplan(p: QPainter, ot, w2s_fn) -> None:
|
||||
|
||||
|
||||
def _draw_cnet_planview(p: QPainter, ot, w2s_fn) -> None:
|
||||
"""Dibuja la malla de control en la Vista de Planta.
|
||||
"""Dibuja el poliedro de control completo en la Vista de Planta.
|
||||
|
||||
Solo aristas TRANSVERSALES: por cada estación i, una polilínea
|
||||
vertical que conecta sus nodos a lo largo de todas las LdA
|
||||
(x constante, y varía de 0 a manga máxima en esa estación).
|
||||
Esto muestra claramente «este nodo pertenece a esta estación» y
|
||||
distingue los nodos longitudinales (en la waterline) de los
|
||||
transversales (en la estación).
|
||||
Se dibujan DOS direcciones (igual que DELFTship):
|
||||
• Dirección estación (aristas verticales en planta): misma estación,
|
||||
distintas LdA → muestra cómo varía la manga con la altura.
|
||||
• Dirección LdA (aristas horizontales en planta): misma LdA, distintas
|
||||
estaciones → el polígono de control de la línea de agua.
|
||||
|
||||
Las aristas longitudinales (waterlines) se omiten aquí porque la
|
||||
Capa 3 ya las dibuja como las propias curvas del casco, más bold.
|
||||
Ambas direcciones se dibujan en BABOR y ESTRIBOR (simetría).
|
||||
La Capa 3 superpone las curvas del casco en colores saturados encima,
|
||||
lo que hace visualmente evidente la diferencia poliedro ↔ curva suave.
|
||||
"""
|
||||
n_sta = ot.n_stations
|
||||
n_wl = ot.n_waterlines
|
||||
|
||||
pen_t = QPen(_CNET_TRAN, 0.7, Qt.PenStyle.SolidLine)
|
||||
p.setPen(pen_t)
|
||||
pen = QPen(_CNET_TRAN, 0.7, Qt.PenStyle.SolidLine)
|
||||
p.setPen(pen)
|
||||
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||
|
||||
for sign in (1.0, -1.0): # estribor (+) y babor (−)
|
||||
# ── Dirección estación: nodos de la misma estación a lo largo de LdA
|
||||
for i in range(n_sta):
|
||||
path = QPainterPath()
|
||||
for j in range(n_wl):
|
||||
pt = w2s_fn(ot.x_stations[i], ot.data[i, j])
|
||||
pt = w2s_fn(ot.x_stations[i], sign * ot.data[i, j])
|
||||
if j == 0:
|
||||
path.moveTo(pt)
|
||||
else:
|
||||
path.lineTo(pt)
|
||||
p.drawPath(path)
|
||||
|
||||
# ── Dirección LdA: nodos de la misma LdA a lo largo de estaciones
|
||||
for j in range(n_wl):
|
||||
path = QPainterPath()
|
||||
for i in range(n_sta):
|
||||
pt = w2s_fn(ot.x_stations[i], sign * ot.data[i, j])
|
||||
if i == 0:
|
||||
path.moveTo(pt)
|
||||
else:
|
||||
path.lineTo(pt)
|
||||
p.drawPath(path)
|
||||
|
||||
|
||||
def _compute_buttock_pts(
|
||||
ot, y_b: float
|
||||
@@ -792,11 +807,12 @@ class PlanViewer(_BaseViewer):
|
||||
if self._hull is None:
|
||||
return None
|
||||
y_max = self._hull.offsets.max_half_breadth
|
||||
# Mostrar AMBOS semiplanos (estribor + babor) simétricamente
|
||||
return (
|
||||
-self._hull.lpp * 0.05,
|
||||
-y_max * 0.15,
|
||||
-y_max * 1.22,
|
||||
self._hull.lpp * 1.05,
|
||||
y_max * 1.25,
|
||||
y_max * 1.22,
|
||||
)
|
||||
|
||||
# ── Edición ───────────────────────────────────────────────────────────────
|
||||
@@ -842,19 +858,24 @@ class PlanViewer(_BaseViewer):
|
||||
y_max = ot.max_half_breadth
|
||||
|
||||
# ══ CAPA 1: Grilla de referencia ══════════════════════════════
|
||||
# Estaciones — líneas verticales tenues
|
||||
p.setPen(QPen(_GRID_STA, 0.5, Qt.PenStyle.DotLine))
|
||||
for x in ot.x_stations:
|
||||
p.drawLine(self._w2s(x, 0), self._w2s(x, y_max * 1.15))
|
||||
|
||||
# Eje de crujía
|
||||
p.setPen(QPen(_AXIS, 0.8, Qt.PenStyle.DashLine))
|
||||
# Eje de crujía — línea continua que divide babor y estribor
|
||||
p.setPen(QPen(_AXIS, 1.2))
|
||||
p.drawLine(self._w2s(0, 0), self._w2s(self._hull.lpp, 0))
|
||||
|
||||
# ══ CAPA 2: Malla de control ══════════════════════════════════
|
||||
# Estaciones — líneas verticales en AMBOS semiplanos
|
||||
p.setPen(QPen(_GRID_STA, 0.5, Qt.PenStyle.DotLine))
|
||||
for x in ot.x_stations:
|
||||
p.drawLine(self._w2s(x, -y_max * 1.10), self._w2s(x, y_max * 1.10))
|
||||
|
||||
# ══ CAPA 2: Poliedro de control (ambas mitades) ════════════════
|
||||
_draw_cnet_planview(p, ot, self._w2s)
|
||||
|
||||
# ══ CAPA 3: Curvas del casco (waterlines como contornos) ══════
|
||||
# ══ CAPA 3: Líneas de agua (ambos semiplanos) ══════════════════
|
||||
# Cada línea de agua se dibuja como contorno cerrado:
|
||||
# eje de crujía (AP) → semi-manga estribor → eje crujía (FP)
|
||||
# → semi-manga babor → eje crujía (AP)
|
||||
# Las curvas se cierran en el eje de crujía porque el casco es
|
||||
# simétrico y la línea de agua termina en y=0 en AP y FP.
|
||||
for j in range(n_wl):
|
||||
z = ot.z_waterlines[j]
|
||||
frac = j / max(n_wl - 1, 1)
|
||||
@@ -871,20 +892,28 @@ class PlanViewer(_BaseViewer):
|
||||
p.setPen(QPen(color, width))
|
||||
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||
|
||||
# Smooth B-spline through the waterline data points
|
||||
raw = np.column_stack([ot.x_stations, ot.data[:, j]])
|
||||
smooth = _smooth_pts(raw, n=80)
|
||||
n_smo = len(smooth)
|
||||
|
||||
# Coordenadas del eje de crujía en AP y FP (donde la LdA termina)
|
||||
ap_x = float(smooth[0, 0])
|
||||
fp_x = float(smooth[-1, 0])
|
||||
|
||||
# Semiplano estribor (y > 0) + cierre → semiplano babor (y < 0)
|
||||
path = QPainterPath()
|
||||
for k_pt in range(len(smooth)):
|
||||
pt = self._w2s(smooth[k_pt, 0], smooth[k_pt, 1])
|
||||
if k_pt == 0:
|
||||
path.moveTo(pt)
|
||||
else:
|
||||
path.lineTo(pt)
|
||||
path.moveTo(self._w2s(ap_x, 0.0)) # inicio en CL-AP
|
||||
for k in range(n_smo): # estribor: AP→FP
|
||||
path.lineTo(self._w2s(float(smooth[k, 0]), float(smooth[k, 1])))
|
||||
path.lineTo(self._w2s(fp_x, 0.0)) # cierre CL-FP
|
||||
for k in range(n_smo - 1, -1, -1): # babor: FP→AP
|
||||
path.lineTo(self._w2s(float(smooth[k, 0]), -float(smooth[k, 1])))
|
||||
path.closeSubpath() # cierre CL-AP
|
||||
p.drawPath(path)
|
||||
|
||||
# ══ CAPA 4: Nodos (cuadrados naranjas) ════════════════════════
|
||||
# ══ CAPA 4: Nodos (estribor — lado editable) ══════════════════
|
||||
# Solo se muestran los nodos del semiplano estribor (positivo).
|
||||
# Babor es simétrico → editar un nodo actualiza ambos lados.
|
||||
for i in range(ot.n_stations):
|
||||
for j in range(n_wl):
|
||||
self._draw_control_point(p, self._screen_pt(i, j), (i, j))
|
||||
|
||||
Reference in New Issue
Block a user