Wizard: reduce default control mesh to 7x5 (DELFTship philosophy)

Prior defaults (21 stations x 11 waterlines = 231 nodes) made it
impossible to edit a hull fairly — too many degrees of freedom with
no locality.

DELFTship principle: start with the minimum viable mesh so each node
acts as a true Bezier handle with global influence, then refine only
where needed.

New defaults: 7 stations x 5 waterlines = 35 nodes
  - 7 stations: AP + 5 intermediate + FP (clear midship at index 3)
  - 5 waterlines: keel + 25% + 50% + 75% + design WL
  - 5 points per section = cubic B-spline ≈ one Bezier handle per quadrant

Range changes:
  - Stations: 7-81 -> 4-30 (step 1, was 2)
  - Waterlines: 5-31 -> 3-12 (step 1, was 2)

New UI elements:
  - Group renamed "Malla de control (puntos Bézier)"
  - Italic hint explaining the fewer-is-better philosophy
  - Live counter "Total nodos: N (manejable / moderado / difícil)"
    with color feedback: green ≤50, amber ≤120, red >120

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 21:08:07 -04:00
parent 36584f782c
commit 588735ea64
+61 -13
View File
@@ -468,36 +468,84 @@ class _StepRefine(QWidget):
lcb_lo.addWidget(self._lcb_val_lbl, 0, Qt.AlignmentFlag.AlignHCenter) lcb_lo.addWidget(self._lcb_val_lbl, 0, Qt.AlignmentFlag.AlignHCenter)
lo.addWidget(grp_lcb) lo.addWidget(grp_lcb)
# ── Discretización ───────────────────────────────────────────── # ── Malla de control ────────────────────────────────────────────
grp_disc = QGroupBox("Discretización") grp_disc = QGroupBox("Malla de control (puntos Bézier)")
grp_disc.setStyleSheet(grp_cb.styleSheet()) grp_disc.setStyleSheet(grp_cb.styleSheet())
disc_lo = QGridLayout(grp_disc) disc_lo = QGridLayout(grp_disc)
disc_lo.setSpacing(8)
disc_lo.addWidget(QLabel("Estaciones:"), 0, 0) # Nota filosófica: menos puntos = curvas más fáciles de afinar
hint = QLabel(
"Menos puntos → curvas naturalmente suaves y fáciles de editar.\n"
"Añade más sólo cuando necesites detalles locales."
)
hint.setWordWrap(True)
hint.setStyleSheet(f"color:{_MUTED}; font-size:10px; font-style:italic;")
disc_lo.addWidget(hint, 0, 0, 1, 2)
disc_lo.addWidget(QLabel("Estaciones (long.):"), 1, 0)
self._n_sta = QSpinBox() self._n_sta = QSpinBox()
self._n_sta.setRange(7, 81) self._n_sta.setRange(4, 30) # mínimo 4 para cúbica, máximo razonable
self._n_sta.setValue(21) self._n_sta.setValue(7) # 7 = como DELFTship en plantilla nueva
self._n_sta.setSingleStep(2) self._n_sta.setSingleStep(1)
self._n_sta.setToolTip(
"Número de secciones transversales del polígono de control.\n"
"7 es suficiente para la mayoría de cascos de eslora media."
)
self._n_sta.setStyleSheet( self._n_sta.setStyleSheet(
f"background:{_PANEL}; color:{_TEXT}; border:1px solid {_BORDER};" f"background:{_PANEL}; color:{_TEXT}; border:1px solid {_BORDER};"
f"border-radius:4px; padding:2px 6px;" f"border-radius:4px; padding:2px 6px;"
) )
disc_lo.addWidget(self._n_sta, 0, 1) disc_lo.addWidget(self._n_sta, 1, 1)
disc_lo.addWidget(QLabel("Líneas de agua:"), 1, 0) disc_lo.addWidget(QLabel("Líneas de agua (vert.):"), 2, 0)
self._n_wl = QSpinBox() self._n_wl = QSpinBox()
self._n_wl.setRange(5, 31) self._n_wl.setRange(3, 12) # 3 mínimo (quilla, media, flotación)
self._n_wl.setValue(11) self._n_wl.setValue(5) # 5 = quilla + 3 intermedias + flotación
self._n_wl.setSingleStep(2) self._n_wl.setSingleStep(1)
self._n_wl.setToolTip(
"Número de líneas de agua del polígono de control.\n"
"5 da una cúbica por sección con un punto a cada 25 % del calado."
)
self._n_wl.setStyleSheet(self._n_sta.styleSheet()) self._n_wl.setStyleSheet(self._n_sta.styleSheet())
disc_lo.addWidget(self._n_wl, 1, 1) disc_lo.addWidget(self._n_wl, 2, 1)
# Indicador en vivo del total de nodos
self._nodes_lbl = QLabel()
self._nodes_lbl.setStyleSheet(f"color:{_ACCENT}; font-size:11px; font-weight:600;")
disc_lo.addWidget(self._nodes_lbl, 3, 0, 1, 2)
self._n_sta.valueChanged.connect(self._update_nodes_lbl)
self._n_wl.valueChanged.connect(self._update_nodes_lbl)
self._update_nodes_lbl()
for lbl in grp_disc.findChildren(QLabel): for lbl in grp_disc.findChildren(QLabel):
lbl.setStyleSheet(f"color:{_TEXT}; font-size:11px;") if lbl is not hint and lbl is not self._nodes_lbl:
lbl.setStyleSheet(f"color:{_TEXT}; font-size:11px;")
lo.addWidget(grp_disc) lo.addWidget(grp_disc)
lo.addStretch() lo.addStretch()
def _update_nodes_lbl(self) -> None:
n = self._n_sta.value()
wl = self._n_wl.value()
total = n * wl
# Feedback visual: color según cantidad (verde = manejable, rojo = muchos)
if total <= 50:
color = _GREEN
advice = "✓ manejable"
elif total <= 120:
color = _GOLD
advice = "⚠ moderado"
else:
color = "#d04040"
advice = "✗ difícil de afinar"
self._nodes_lbl.setText(
f"Total nodos: {total} ({advice})"
)
self._nodes_lbl.setStyleSheet(
f"color:{color}; font-size:11px; font-weight:600;"
)
def _cb_changed(self, val: int) -> None: def _cb_changed(self, val: int) -> None:
cb = val / 100.0 cb = val / 100.0
self._cb_val_lbl.setText(f"Cb = {cb:.2f}") self._cb_val_lbl.setText(f"Cb = {cb:.2f}")