fix(ui): 3D stale cache + node/waterline visual hierarchy
hull.py
- add invalidate() — clears _surface NURBS cache on in-place
offsets edit; fixes 3D viewer showing old geometry after drag
main_window.py
- call hull.invalidate() before load_hull() in
_on_offsets_edited_from_viewer so PyVista always rebuilds mesh
from the updated offsets
viewer_lines.py
- 4-layer drawing order: grid → control-net → hull-curves → nodes
- nodes changed from 4px white-blue circles to 6px orange squares
(_NODE_NORMAL #FF8000) — unambiguous visual language vs blue/green
hull curves
- _draw_cnet_bodyplan / _draw_cnet_planview helpers: thin muted
control-net mesh (transverse + longitudinal edges) drawn between
grid and bold hull curves, matching Maxsurf/DelftShip visual style
- waterline reference lines made more muted (_GRID_WL dotted)
- all old _GRID / _CPT_* references replaced with new palette
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,16 @@ class Hull:
|
|||||||
self._surface = self._build_surface()
|
self._surface = self._build_surface()
|
||||||
return self._surface
|
return self._surface
|
||||||
|
|
||||||
|
def invalidate(self) -> None:
|
||||||
|
"""Invalida la caché de la superficie NURBS.
|
||||||
|
|
||||||
|
Llamar siempre que se modifiquen los offsets in-place
|
||||||
|
(p.ej. arrastre interactivo en los visores 2D) para que la
|
||||||
|
próxima llamada a ``surface`` o ``to_mesh`` reconstruya la
|
||||||
|
geometría desde los datos actualizados.
|
||||||
|
"""
|
||||||
|
self._surface = None
|
||||||
|
|
||||||
def _build_surface(self) -> LoftedSurface:
|
def _build_surface(self) -> LoftedSurface:
|
||||||
sections_data = []
|
sections_data = []
|
||||||
u_arr = self.offsets.x_stations / self.lpp # normalizar a [0,1]
|
u_arr = self.offsets.x_stations / self.lpp # normalizar a [0,1]
|
||||||
|
|||||||
@@ -1366,7 +1366,10 @@ class MainWindow(QMainWindow):
|
|||||||
hull = self._current_hull
|
hull = self._current_hull
|
||||||
if hull is None:
|
if hull is None:
|
||||||
return
|
return
|
||||||
# hull.offsets ya fue modificado in-place durante el drag
|
# hull.offsets ya fue modificado in-place durante el drag.
|
||||||
|
# Invalidar caché NURBS para que to_mesh() reconstruya desde los
|
||||||
|
# offsets editados y no devuelva la geometría anterior.
|
||||||
|
hull.invalidate()
|
||||||
if self._project is not None:
|
if self._project is not None:
|
||||||
self._project.set_hull(hull)
|
self._project.set_hull(hull)
|
||||||
# Actualizar vistas 2D SIN resetear zoom/pan
|
# Actualizar vistas 2D SIN resetear zoom/pan
|
||||||
|
|||||||
@@ -37,24 +37,37 @@ from arshipdesign.core.hull import Hull
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Paleta del tema
|
# Paleta del tema
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
_BG = QColor("#1a1d30")
|
_BG = QColor("#131722")
|
||||||
_GRID = QColor("#2a3060") # Estaciones (muy tenue)
|
|
||||||
_WATERLINE = QColor("#4da8ff") # Líneas de agua
|
|
||||||
_WL_DESIGN = QColor("#00d4ff") # Flotación de diseño (más gruesa)
|
|
||||||
_SECTION = QColor("#48a858") # Secciones de proa (verde)
|
|
||||||
_SECTION_AFT= QColor("#4da8ff") # Secciones de popa (azul)
|
|
||||||
_MIDSHIP = QColor("#e8a020") # Cuaderna maestra (dorado)
|
|
||||||
_DECK = QColor("#8868c8") # Línea de cubierta (púrpura)
|
|
||||||
_KEEL = QColor("#e06060") # Quilla (rojo suave)
|
|
||||||
_TEXT = QColor("#7a8ba8")
|
|
||||||
_AXIS = QColor("#3e4255")
|
|
||||||
|
|
||||||
# Puntos de control (malla editable)
|
# ── Referencia / grilla (muy tenue, no compite con nada) ────────────────
|
||||||
_CPT_NORMAL = QColor("#c8d8f0") # blanco-azulado
|
_GRID_STA = QColor(38, 55, 88, 80) # líneas de estación
|
||||||
_CPT_HOVER = QColor("#ffd700") # oro
|
_GRID_WL = QColor(40, 60, 95, 70) # líneas de agua (referencia)
|
||||||
_CPT_DRAG = QColor("#ff5555") # rojo activo
|
_AXIS = QColor("#3e4255")
|
||||||
_CPT_RADIUS = 4.0 # px en reposo
|
|
||||||
_CPT_HIT = 14.0 # px umbral de captura
|
# ── 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)
|
||||||
|
|
||||||
|
# ── 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
|
||||||
|
_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
|
||||||
|
_CPT_HIT = 16.0 # px umbral de captura (alias legacy)
|
||||||
|
_CPT_RADIUS = _NODE_R # alias legacy
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Clase base
|
# Clase base
|
||||||
@@ -266,19 +279,127 @@ class _BaseViewer(QWidget):
|
|||||||
screen_pt: QPointF,
|
screen_pt: QPointF,
|
||||||
idx: tuple[int, int],
|
idx: tuple[int, int],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Dibuja un punto de control con color según estado."""
|
"""Dibuja un nodo de control como cuadrado naranja sobre las curvas.
|
||||||
|
|
||||||
|
El naranja distingue inequívocamente los nodos de cualquier línea del
|
||||||
|
casco (azul/verde/dorado). La forma cuadrada evoca el vocabulario de
|
||||||
|
las herramientas CAD (Maxsurf, DelftShip).
|
||||||
|
"""
|
||||||
if idx == self._drag_idx:
|
if idx == self._drag_idx:
|
||||||
color = _CPT_DRAG
|
color = _NODE_DRAG
|
||||||
r = _CPT_RADIUS * 1.8
|
r = _NODE_R * 1.8
|
||||||
elif idx == self._hover_idx:
|
elif idx == self._hover_idx:
|
||||||
color = _CPT_HOVER
|
color = _NODE_HOVER
|
||||||
r = _CPT_RADIUS * 1.5
|
r = _NODE_R * 1.4
|
||||||
else:
|
else:
|
||||||
color = _CPT_NORMAL
|
color = _NODE_NORMAL
|
||||||
r = _CPT_RADIUS
|
r = _NODE_R
|
||||||
p.setPen(QPen(color.darker(130), 1))
|
from PySide6.QtCore import QRectF
|
||||||
|
p.setPen(QPen(color.darker(180), 1))
|
||||||
p.setBrush(QBrush(color))
|
p.setBrush(QBrush(color))
|
||||||
p.drawEllipse(screen_pt, r, r)
|
p.drawRect(QRectF(screen_pt.x() - r, screen_pt.y() - r, r * 2, r * 2))
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Helpers: malla de control (control net)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _draw_cnet_bodyplan(p: QPainter, ot, w2s_fn) -> None:
|
||||||
|
"""Dibuja la malla de control en el Body Plan.
|
||||||
|
|
||||||
|
Capa visual entre la grilla de referencia y las curvas del casco:
|
||||||
|
• Aristas transversales — polilínea de control de cada sección
|
||||||
|
(equal to the section control polyline, muted, drawn BEFORE the
|
||||||
|
actual hull-curve so the colored curve reads on top of it).
|
||||||
|
• Aristas longitudinales — segmentos horizontales a la altura de cada
|
||||||
|
línea de agua, conectando todos los nodos de esa LdA en ambas bandas.
|
||||||
|
Permiten ver cómo varía la manga de proa a popa en cada calado.
|
||||||
|
"""
|
||||||
|
n_sta = ot.n_stations
|
||||||
|
n_wl = ot.n_waterlines
|
||||||
|
|
||||||
|
# ── Aristas transversales (a lo largo de cada sección) ────────────
|
||||||
|
pen_t = QPen(_CNET_TRAN, 0.8, Qt.PenStyle.SolidLine)
|
||||||
|
p.setPen(pen_t)
|
||||||
|
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
|
for i in range(n_sta):
|
||||||
|
sign = 1.0 if i >= n_sta // 2 else -1.0
|
||||||
|
path = QPainterPath()
|
||||||
|
for k in range(n_wl):
|
||||||
|
pt = w2s_fn(sign * ot.data[i, k], ot.z_waterlines[k])
|
||||||
|
if k == 0:
|
||||||
|
path.moveTo(pt)
|
||||||
|
else:
|
||||||
|
path.lineTo(pt)
|
||||||
|
# Cerrar al eje de crujía en la quilla
|
||||||
|
path.lineTo(w2s_fn(0.0, 0.0))
|
||||||
|
p.drawPath(path)
|
||||||
|
|
||||||
|
# ── Aristas longitudinales (a lo largo de cada LdA) ───────────────
|
||||||
|
# Para cada LdA j: una polilínea a través de todas las estaciones, en
|
||||||
|
# cada banda por separado (proa=+y, popa=−y). Se ve como un arco a
|
||||||
|
# la altura z[j], mostrando la variación de manga longitudinalmente.
|
||||||
|
pen_l = QPen(_CNET_LONG, 0.7, Qt.PenStyle.SolidLine)
|
||||||
|
p.setPen(pen_l)
|
||||||
|
for j in range(n_wl):
|
||||||
|
z = ot.z_waterlines[j]
|
||||||
|
# Banda de proa (estribor, sign=+1)
|
||||||
|
path_fwd = QPainterPath()
|
||||||
|
path_aft = QPainterPath()
|
||||||
|
for i in range(n_sta):
|
||||||
|
sign = 1.0 if i >= n_sta // 2 else -1.0
|
||||||
|
pt = w2s_fn(sign * ot.data[i, j], z)
|
||||||
|
if i == 0:
|
||||||
|
path_aft.moveTo(pt)
|
||||||
|
elif i == n_sta // 2:
|
||||||
|
path_fwd.moveTo(pt)
|
||||||
|
if i < n_sta // 2:
|
||||||
|
path_aft.lineTo(pt)
|
||||||
|
else:
|
||||||
|
path_fwd.lineTo(pt)
|
||||||
|
p.drawPath(path_fwd)
|
||||||
|
p.drawPath(path_aft)
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_cnet_planview(p: QPainter, ot, w2s_fn) -> None:
|
||||||
|
"""Dibuja la malla de control en la Vista de Planta.
|
||||||
|
|
||||||
|
• Aristas longitudinales — waterlines (conectan todas las estaciones
|
||||||
|
en una LdA = las curvas de contorno, dibujadas muted ANTES de las
|
||||||
|
curvas reales).
|
||||||
|
• Aristas transversales — polilínea vertical por estación,
|
||||||
|
conectando los nodos de esa estación a lo largo de todas las LdA.
|
||||||
|
Muestra cómo cambia la manga con el calado para cada estación.
|
||||||
|
"""
|
||||||
|
n_sta = ot.n_stations
|
||||||
|
n_wl = ot.n_waterlines
|
||||||
|
|
||||||
|
# ── Aristas longitudinales (contornos de LdA) ─────────────────────
|
||||||
|
pen_l = QPen(_CNET_LONG, 0.7, Qt.PenStyle.SolidLine)
|
||||||
|
p.setPen(pen_l)
|
||||||
|
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
|
for j in range(n_wl):
|
||||||
|
path = QPainterPath()
|
||||||
|
for i in range(n_sta):
|
||||||
|
pt = w2s_fn(ot.x_stations[i], ot.data[i, j])
|
||||||
|
if i == 0:
|
||||||
|
path.moveTo(pt)
|
||||||
|
else:
|
||||||
|
path.lineTo(pt)
|
||||||
|
p.drawPath(path)
|
||||||
|
|
||||||
|
# ── Aristas transversales (polilínea de sección en planta) ─────────
|
||||||
|
pen_t = QPen(_CNET_TRAN, 0.7, Qt.PenStyle.SolidLine)
|
||||||
|
p.setPen(pen_t)
|
||||||
|
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])
|
||||||
|
if j == 0:
|
||||||
|
path.moveTo(pt)
|
||||||
|
else:
|
||||||
|
path.lineTo(pt)
|
||||||
|
p.drawPath(path)
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -352,33 +473,41 @@ class BodyPlanViewer(_BaseViewer):
|
|||||||
T = self._hull.draft
|
T = self._hull.draft
|
||||||
n = ot.n_stations
|
n = ot.n_stations
|
||||||
|
|
||||||
# ── Líneas de agua — grilla horizontal ────────────────────────
|
|
||||||
x_max = ot.max_half_breadth * 1.15
|
x_max = ot.max_half_breadth * 1.15
|
||||||
|
|
||||||
|
# ══ CAPA 1: Grilla de referencia (tenue, sin competir) ════════
|
||||||
|
# Líneas de agua horizontales — referencia de altura
|
||||||
for j, z in enumerate(ot.z_waterlines):
|
for j, z in enumerate(ot.z_waterlines):
|
||||||
is_design = abs(z - T) < 1e-6
|
is_design = abs(z - T) < 1e-6
|
||||||
if is_design:
|
if is_design:
|
||||||
p.setPen(QPen(_WL_DESIGN, 1.2, Qt.PenStyle.DashLine))
|
p.setPen(QPen(_WL_DESIGN.darker(200), 0.8, Qt.PenStyle.DashLine))
|
||||||
else:
|
else:
|
||||||
p.setPen(QPen(_WATERLINE.darker(160), 0.6, Qt.PenStyle.DotLine))
|
p.setPen(QPen(_GRID_WL, 0.5, Qt.PenStyle.DotLine))
|
||||||
p.drawLine(self._w2s(-x_max, z), self._w2s(x_max, z))
|
p.drawLine(self._w2s(-x_max, z), self._w2s(x_max, z))
|
||||||
|
|
||||||
# Línea de flotación de diseño (más visible)
|
# Ejes
|
||||||
p.setPen(QPen(_WL_DESIGN, 1.5, Qt.PenStyle.DashLine))
|
p.setPen(QPen(_AXIS, 1.0))
|
||||||
p.drawLine(self._w2s(-x_max, T), self._w2s(x_max, T))
|
p.drawLine(self._w2s(-x_max, 0), self._w2s(x_max, 0))
|
||||||
|
p.setPen(QPen(_AXIS, 0.7, Qt.PenStyle.DashLine))
|
||||||
|
p.drawLine(self._w2s(0, 0), self._w2s(0, T * 1.18))
|
||||||
|
|
||||||
# ── Secciones ─────────────────────────────────────────────────
|
# ══ CAPA 2: Malla de control (control net — thin, muted) ══════
|
||||||
|
_draw_cnet_bodyplan(p, ot, self._w2s)
|
||||||
|
|
||||||
|
# ══ CAPA 3: Curvas del casco (bold, saturated) ════════════════
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
is_fwd = i >= n // 2
|
is_fwd = i >= n // 2
|
||||||
is_mid = i == n // 2
|
is_mid = i == n // 2
|
||||||
|
|
||||||
if is_mid:
|
if is_mid:
|
||||||
pen = QPen(_MIDSHIP, 2.5)
|
pen = QPen(_MIDSHIP, 2.2)
|
||||||
elif is_fwd:
|
elif is_fwd:
|
||||||
pen = QPen(_SECTION, 1.4)
|
pen = QPen(_SECTION, 1.5)
|
||||||
else:
|
else:
|
||||||
pen = QPen(_SECTION_AFT, 1.4)
|
pen = QPen(_SECTION_AFT, 1.5)
|
||||||
|
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
|
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
y_arr = ot.data[i, :]
|
y_arr = ot.data[i, :]
|
||||||
z_arr = ot.z_waterlines
|
z_arr = ot.z_waterlines
|
||||||
sign = 1.0 if is_fwd else -1.0
|
sign = 1.0 if is_fwd else -1.0
|
||||||
@@ -390,18 +519,14 @@ class BodyPlanViewer(_BaseViewer):
|
|||||||
path.moveTo(pt)
|
path.moveTo(pt)
|
||||||
else:
|
else:
|
||||||
path.lineTo(pt)
|
path.lineTo(pt)
|
||||||
# Cerrar en quilla
|
|
||||||
path.lineTo(self._w2s(0.0, 0.0))
|
path.lineTo(self._w2s(0.0, 0.0))
|
||||||
p.drawPath(path)
|
p.drawPath(path)
|
||||||
|
|
||||||
# ── Ejes ──────────────────────────────────────────────────────
|
# Flotación de diseño (encima de todo lo anterior)
|
||||||
p.setPen(QPen(_AXIS, 1))
|
p.setPen(QPen(_WL_DESIGN, 1.8, Qt.PenStyle.DashLine))
|
||||||
p.drawLine(self._w2s(-x_max, 0), self._w2s(x_max, 0)) # quilla
|
p.drawLine(self._w2s(-x_max, T), self._w2s(x_max, T))
|
||||||
p.setPen(QPen(_AXIS, 0.8, Qt.PenStyle.DashLine))
|
|
||||||
p.drawLine(self._w2s(0, 0), self._w2s(0, T * 1.15)) # eje crujía
|
|
||||||
|
|
||||||
# ── Puntos de control ─────────────────────────────────────────
|
# ══ CAPA 4: Nodos (cuadrados naranjas — siempre encima) ═══════
|
||||||
p.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
for j in range(ot.n_waterlines):
|
for j in range(ot.n_waterlines):
|
||||||
self._draw_control_point(p, self._screen_pt(i, j), (i, j))
|
self._draw_control_point(p, self._screen_pt(i, j), (i, j))
|
||||||
@@ -464,7 +589,7 @@ class ProfileViewer(_BaseViewer):
|
|||||||
Lpp = self._hull.lpp
|
Lpp = self._hull.lpp
|
||||||
|
|
||||||
# ── Grilla de estaciones ───────────────────────────────────────
|
# ── Grilla de estaciones ───────────────────────────────────────
|
||||||
p.setPen(QPen(_GRID, 0.5, Qt.PenStyle.DotLine))
|
p.setPen(QPen(_GRID_STA, 0.5, Qt.PenStyle.DotLine))
|
||||||
for x in ot.x_stations:
|
for x in ot.x_stations:
|
||||||
p.drawLine(self._w2s(x, -T * 0.1), self._w2s(x, T * 1.2))
|
p.drawLine(self._w2s(x, -T * 0.1), self._w2s(x, T * 1.2))
|
||||||
|
|
||||||
@@ -574,48 +699,50 @@ class PlanViewer(_BaseViewer):
|
|||||||
p.end()
|
p.end()
|
||||||
return
|
return
|
||||||
|
|
||||||
ot = self._hull.offsets
|
ot = self._hull.offsets
|
||||||
T = self._hull.draft
|
T = self._hull.draft
|
||||||
n_wl = ot.n_waterlines
|
n_wl = ot.n_waterlines
|
||||||
|
y_max = ot.max_half_breadth
|
||||||
|
|
||||||
# ── Líneas de agua como contornos ─────────────────────────────
|
# ══ 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))
|
||||||
|
p.drawLine(self._w2s(0, 0), self._w2s(self._hull.lpp, 0))
|
||||||
|
|
||||||
|
# ══ CAPA 2: Malla de control ══════════════════════════════════
|
||||||
|
_draw_cnet_planview(p, ot, self._w2s)
|
||||||
|
|
||||||
|
# ══ CAPA 3: Curvas del casco (waterlines como contornos) ══════
|
||||||
for j in range(n_wl):
|
for j in range(n_wl):
|
||||||
z = ot.z_waterlines[j]
|
z = ot.z_waterlines[j]
|
||||||
is_design = abs(z - T) < 1e-6
|
|
||||||
frac = j / max(n_wl - 1, 1)
|
frac = j / max(n_wl - 1, 1)
|
||||||
|
is_design = abs(z - T) < 1e-6
|
||||||
|
|
||||||
if is_design:
|
if is_design:
|
||||||
color = QColor(_WL_DESIGN)
|
color = QColor(_WL_DESIGN)
|
||||||
color.setAlphaF(1.0)
|
width = 2.2
|
||||||
width = 2.0
|
|
||||||
else:
|
else:
|
||||||
color = QColor(_WATERLINE)
|
color = QColor(_WATERLINE)
|
||||||
color.setAlphaF(0.30 + 0.55 * frac)
|
color.setAlphaF(0.40 + 0.50 * frac)
|
||||||
width = 0.9
|
width = 1.1
|
||||||
|
|
||||||
p.setPen(QPen(color, width))
|
p.setPen(QPen(color, width))
|
||||||
|
p.setBrush(Qt.BrushStyle.NoBrush)
|
||||||
path = QPainterPath()
|
path = QPainterPath()
|
||||||
x_arr = ot.x_stations
|
for i, (x, y) in enumerate(zip(ot.x_stations, ot.data[:, j])):
|
||||||
y_arr = ot.data[:, j]
|
|
||||||
for k, (x, y) in enumerate(zip(x_arr, y_arr)):
|
|
||||||
pt = self._w2s(x, y)
|
pt = self._w2s(x, y)
|
||||||
if k == 0:
|
if i == 0:
|
||||||
path.moveTo(pt)
|
path.moveTo(pt)
|
||||||
else:
|
else:
|
||||||
path.lineTo(pt)
|
path.lineTo(pt)
|
||||||
p.drawPath(path)
|
p.drawPath(path)
|
||||||
|
|
||||||
# ── Eje de crujía ─────────────────────────────────────────────
|
# ══ CAPA 4: Nodos (cuadrados naranjas) ════════════════════════
|
||||||
p.setPen(QPen(_AXIS, 0.8, Qt.PenStyle.DashLine))
|
|
||||||
p.drawLine(self._w2s(0, 0), self._w2s(self._hull.lpp, 0))
|
|
||||||
|
|
||||||
# ── Estaciones ────────────────────────────────────────────────
|
|
||||||
p.setPen(QPen(_GRID, 0.4, Qt.PenStyle.DotLine))
|
|
||||||
y_max = ot.max_half_breadth
|
|
||||||
for x in ot.x_stations:
|
|
||||||
p.drawLine(self._w2s(x, 0), self._w2s(x, y_max * 1.15))
|
|
||||||
|
|
||||||
# ── Puntos de control ─────────────────────────────────────────
|
|
||||||
for i in range(ot.n_stations):
|
for i in range(ot.n_stations):
|
||||||
for j in range(n_wl):
|
for j in range(n_wl):
|
||||||
self._draw_control_point(p, self._screen_pt(i, j), (i, j))
|
self._draw_control_point(p, self._screen_pt(i, j), (i, j))
|
||||||
|
|||||||
Reference in New Issue
Block a user