Files

21 KiB
Raw Permalink Blame History

BITÁCORA DE DESARROLLO — AR-ShipDesign

Propósito: Registro técnico vivo de cada módulo funcional de la app. A diferencia de CHANGELOG.md (que registra versiones), esta bitácora documenta el estado interno de cada módulo: qué funciona, qué se corrigió, por qué se tomaron ciertas decisiones y qué queda pendiente.

Actualizar al final de cada sesión de trabajo o al completar un feature.


Convenciones de estado

Símbolo Significado
Implementado y verificado (tests + visual)
🔧 Implementado, pendiente verificación visual en la app
🐛 Bug conocido, no resuelto aún
📋 Planificado, no iniciado
Descartado o revertido (con explicación)
⚠️ Restricción crítica — no romper

Reglas Inquebrantables (leer SIEMPRE antes de editar los visores)

⚠️ REGLA DE EJES — NUNCA VIOLAR

Vista Perfil  (ProfileViewer)   →  nodos en EJE X (longitudinal)  + EJE Z (vertical)
Vista Planta  (PlanViewer)      →  nodos en EJE X (longitudinal)  + EJE Y (transversal)
Vista Frontal (BodyPlanViewer)  →  nodos en EJE Y (transversal)   + EJE Z (vertical)

Nunca bloquear nodos en ninguna vista. Nunca añadir restricciones _hit_test a nodos normales. Si un cambio rompe el movimiento en alguno de estos ejes → REVERTIR INMEDIATAMENTE.

⚠️ REGLA DE SNAP

El snap de nodos de contorno (snap_boundary_nodes_to_contours) solo se ejecuta en _on_new_project (wizard de creación). Nunca en _on_offsets_edited_from_viewer ni en Hull.from_dict(). Los x_offsets son datos del usuario y se restauran tal cual.


Sentinels de nodo especial (viewer_lines.py)

_KEEL_IDX   = -1   # nodo de quilla  (keel_z[i] por estación)
_SHEER_IDX  = -2   # nodo de cubierta (sheer_z[i] por estación)
_STEM_IDX   = -10  # punto de control de roda
_TRANS_IDX  = -20  # punto de control de espejo de popa

El índice j en (i, j) siendo negativo indica nodo especial, no columna de data[i,j].


Módulo 1 — Geometría del Casco

Archivo clave: arshipdesign/core/hull.py, arshipdesign/core/offsets.py

Estructura de datos

Hull
├── offsets: OffsetsTable
│   ├── x_stations[n_sta]         — posición X de cada estación [m]
│   ├── data[n_sta, n_wl]         — semi-manga Y por (estación, LdA) [m]
│   ├── keel_z[n_sta]             — Z de la quilla por estación [m]
│   ├── z_waterlines[n_wl]        — Z absoluta de cada LdA [m]
│   ├── z_offsets[n_sta, n_wl]    — ajuste Z local por nodo [m]
│   └── x_offsets[n_sta, n_wl]   — ajuste X visual del nodo en los visores 2D [m]
├── sheer_z[n_sta]                — Z de la cubierta (arrufo) por estación [m]
├── stem_ctrl[k, 2]               — polígono de control de la roda (B-spline)
├── transom_ctrl[k, 2]            — polígono de control del espejo de popa
└── corner_nodes: list[[i,j]]    — nodos marcados como esquina (rompen suavidad)

Estado

  • Serialización (to_dict / from_dict): guarda todos los arrays sin recalcular. Al cargar, los x_offsets se restauran exactamente como el usuario los dejó.
  • Inserción de estaciones (insert_station): interpola Y, keel_z, sheer_z y offsets.
  • Inserción de líneas de agua (insert_waterline): interpola semi-mangas.
  • B-Spline de sección (_section_yz en to_mesh): muestrea el perfil Y-Z desde quilla → LdA de control → cubierta con grado mín(3, n-1).
  • Malla 3D (to_mesh): grilla estructurada n_u × n_v interpolada entre estaciones, triangulada para PyVista. Genera ambas bandas (estribor + babor).
  • Lazy cache (station_planes, get_sheer_z): no recalcula si los datos no cambian.

Bug conocido 🐛

"Tabla en quilla" — Si se mueve keel_z[i] de una sola estación muy lejos de las vecinas, la malla 3D muestra una depresión abrupta (tabla/aleta) porque:

  • Las LdA permanecen en sus Z fijos absolutas.
  • La interpolación entre estaciones crea una concavidad estrecha en esa estación.
  • Workaround: mover la quilla en varias estaciones sucesivas para distribuir el cambio.
  • Fix definitivo: wizard de redistribución de LdA + más puntos de control de quilla.

Módulo 2 — Visores 2D Interactivos

Archivo clave: arshipdesign/ui/widgets/viewer_lines.py

Clases principales

_BaseViewer          — zoom, paneo, drag de nodos, hit-test, HUD, fairness, selección de curva
├── BodyPlanViewer   — secciones transversales Y-Z (cuadernas)
├── ProfileViewer    — vista lateral X-Z (quilla, cubierta, roda, espejo)
└── PlanViewer       — vista de planta X-Y (líneas de agua desde arriba)

Estado 🔧

  • Drag de nodos: todos los nodos arrastrables, sin restricciones (respeta Regla de Ejes).
  • Selección de nodo (clic): nodo se vuelve dorado; panel NodeInfoPanel muestra X/Y/Z y checkbox de esquina. Enter aplica el valor editado manualmente.
  • Selección de curva (Shift+clic): detecta arista de la malla NURBS más cercana. La curva completa se resalta en verde menta #00FFB0 con 2.5 px.
    • Body Plan: Shift+clic → sección completa keel→LdA→sheer (estación i)
    • Perfil: Shift+clic → quilla o cubierta (curva longitudinal)
    • Planta: Shift+clic → línea de agua j completa
  • Peines de curvatura [C]: pelos perpendiculares a la curva. Normalizados por max|κ| → siempre visibles aunque la curva sea casi recta. Solo en la curva seleccionada (Shift+clic) o en todas si no hay selección. Pelo invertido al lado opuesto = inflexión (cambio de signo de curvatura).
  • Coloreo de equidad [F]: nodos coloreados verde→amarillo→rojo por |d²Y/dX²|.
  • Suavizado local [S]: Laplaciano 1 paso en el nodo seleccionado.
  • Zoom: rueda del ratón. Doble clic: fit-to-view.
  • Paneo: botón medio o derecho + arrastrar.
  • HUD (esquina inferior derecha): estado de [C]/[F]/[S] y nombre de la curva activa.
  • Sincronización entre vistas (en vivo): offsets_dragging durante el drag, offsets_edited al soltar.
  • Menú contextual (clic derecho): insertar LdA, estación, roda, espejo, esquina.

Historial de correcciones

Fecha Problema Causa raíz Fix aplicado
2026-05-28 Nodos de borde no arrastrables en X _hit_test de ProfileViewer excluía i=0 e i=n-1 para LdA normales Revertido: loop incluye todos los nodos sin excepción
2026-05-28 Peines de curvatura invisibles scale = beam × 0.20 → κ≈0.02 → pelo de 2cm, invisible a escala normal Normalizado: todos los κ ÷ max|κ| antes de escalar
2026-05-28 self._selected no existe Nombre incorrecto del atributo Corregido a self._selected_idx

Pendiente 📋

  • Peines de curvatura en keel/sheer desde el ProfileViewer (actualmente solo en quilla/cubierta como curvas, no como Z).
  • Suavizado 2D (Laplaciano transversal dentro de la cuaderna).
  • Tests automatizados para fairness coloring y suavizado.

Módulo 3 — Visor 3D

Archivo clave: arshipdesign/ui/widgets/viewer_3d.py

Estado 🔧

  • Motor: PyVista + pyvistaqt (QtInteractor embebido).
  • Degradación sin PyVista: muestra QLabel en lugar de crashear (permite que CI pase).
  • Carga diferida: QtInteractor se crea 500 ms después del arranque (evita conflicto OpenGL).
  • Tema oscuro: fondo #1a1d30, casco #3a6080, aristas #4da8ff, plano de flotación #4da8ff al 15%.
  • Toggle mallas (botón ⬡ Mallas en barra superior del visor): apagado por defecto. Llama GetProperty().EdgeVisibilityOn/Off() sobre el actor VTK → sin re-render.

Historial de correcciones

Fecha Problema Fix
2026-05-29 Mallas siempre visibles, sin forma de apagarlas Añadido botón toggle + _show_edges=False por defecto

Pendiente 📋

  • Caras invertidas: detectar y colorear diferente (rojo/azul), comando flip.
  • Capas de visualización: buttocks, waterlines, sections como actores independientes.
  • Cierre de malla en AP para transom stern.

Módulo 4 — Guardado y Cargado de Proyectos

Archivos: arshipdesign/core/project.py, arshipdesign/core/hull.py

Formato .arsd

Archivo ZIP que contiene hull.json con formato hull_v1. Incluye todos los arrays de offsets, control curves, y metadatos del buque.

Estado

  • Persistencia exacta: todos los arrays se guardan y restauran fielmente.
  • Sin snap en carga: from_dict no llama snap_boundary_nodes_to_contours.

Historial de correcciones

Fecha Problema Causa Fix
2026-05-28 Forma diferente al recargar snap_boundary_nodes_to_contours en from_dict recalculaba x_offsets Eliminado de from_dict
2026-05-28 Nodos saltaban al soltar snap en _on_offsets_edited_from_viewer sobreescribía la posición del usuario Eliminado del handler

Módulo 5 — Hidrostáticos

Archivos: arshipdesign/core/hydrostatics.py

Estado

  • Cálculo en tiempo real al modificar cualquier nodo.
  • Métricas: Δ, LCB, TCB, KB, BM, GM, Cb, Cm, Cp, Cw, AWP.
  • Validado contra casco analítico Wigley (IACS Rec.34 §4). Tests: 315/315

Módulo 6 — Estabilidad

Archivo: arshipdesign/core/stability.py

Estado

  • Curva GZ por planos de inclinación.
  • Criterios IMO IS Code 2008 verificados.

Módulo 7 — Generadores Paramétricos

Archivos: arshipdesign/parametric/wizard_*.py

Familias disponibles

Familia Archivo Estado
Workboat (buque de trabajo) wizard_workboat.py
Velero wizard_sailing.py
Lancha rápida wizard_fast.py
Remolcador wizard_tug.py
Ferry / pasaje wizard_ferry.py
  • Arrufo parabólico: sheer_z[i] = sheer_base + camber × (1 (2x/L 1)²)
  • Snap de nodos de contorno se aplica una sola vez al crear el proyecto.

Pendiente 📋

  • Opción transom stern en el wizard (has_transom: bool, transom_angle: float).
  • Wizard de estaciones/LdA/buttocks: definir manualmente posiciones antes de generar la malla.

Módulo 8 — UI / Layout / Ribbon

Archivos: arshipdesign/ui/main_window.py, widgets varios

Estado 🔧

  • Layout 4 viewports: QSplitters anidados. Arriba: 3D+Perfil. Abajo: FrontalI+Planta.
  • Maximizar viewport (botón / o doble clic en barra de título): oculta viewport compañero y fila opuesta. Restaurar vuelve a 50/50.
  • Ribbon: tabs Geometría, Hidrostáticos, Estabilidad, Estructural. Grupo "Suavizado" con botones Curvatura, Equidad, Suavizar.
  • NodeInfoPanel: flotante, coordenadas X/Y/Z editables + checkbox esquina.

Historial de correcciones

Fecha Problema Fix
2026-05-28 Enter en NodeInfoPanel no aplicaba cambio Señal coord_edited no conectada
2026-05-29 QPushButton no importado Faltaba en bloque de imports

Módulo 9 — Herramientas de Fairness (Equidad)

Funciones en viewer_lines.py: _fairness_color, _smooth_selected_node, _draw_curvature_comb, _curvature_comb_data, _dist_to_segment

Peines de curvatura

κᵢ = 2 × cross(t₁, t₂) / (l₁ + l₂)       — curvatura discreta firmada
κ_normalizada = κᵢ / max|κ|                 — rango [-1, 1]
pelo_longitud = κ_normalizada × scale       — en unidades de mundo
  • Pelo al lado contrario de la curva = curvatura positiva (convexa).
  • Pelo al mismo lado = curvatura negativa (cóncava / inflexión).
  • Spine = línea que une las puntas → revela continuidad de curvatura.

Coloreo de equidad

roughness = |Y[i+1] - 2·Y[i] + Y[i-1]| / (Δx²)
  • Verde #22cc66: roughness < 0.005 m⁻¹
  • Rojo #e03030: roughness > 0.150 m⁻¹

Suavizado Laplaciano 1-paso

Y_new[i] = (Y[i-1] + Y[i] + Y[i+1]) / 3

Solo nodos interiores. Aplica a Y breadths, keel_z y sheer_z.


Módulo 10 — Deshacer / Rehacer (Ctrl+Z / Ctrl+Y)

Archivo: arshipdesign/ui/main_window.py

Estado 🔧

  • Mecanismo: stack de snapshots hull.to_dict() — cada estado es una copia completa del casco serializado (arreglos numpy → listas, muy pequeño en memoria).
  • Capacidad: 50 pasos de deshacer (_MAX_UNDO = 50).
  • Ctrl+Z (Editar → Deshacer): restaura el estado anterior al último drag/edición.
  • Ctrl+Y (Editar → Rehacer): rehace el cambio deshecho.
  • Cada nueva edición limpia el stack de redo (rama nueva invalida el futuro).
  • Al crear o abrir un proyecto, ambos stacks se limpian (_reset_undo_history).
  • Las acciones del menú se habilitan/deshabilitan según haya pasos disponibles.

Cómo funciona internamente

_last_hull_state  = snapshot del hull ANTES del último edit
_undo_stack       = [estado_0, estado_1, ..., estado_n]  ← el más reciente al final
_redo_stack       = estados deshechados disponibles

Al recibir offsets_edited:
  1. push _last_hull_state → _undo_stack
  2. clear _redo_stack
  3. _last_hull_state = hull.to_dict()  (nuevo estado actual)

Al hacer Ctrl+Z:
  1. push hull.to_dict() → _redo_stack
  2. hull = Hull.from_dict(_undo_stack.pop())
  3. _load_hull_viewers(hull)  — refresca todos los visores + hidrostáticos

Qué operaciones son deshaciibles

Operación ¿Deshacible?
Arrastrar nodo
Suavizar con [S] (si emite offsets_edited)
Editar coordenada en panel
Insertar estación/LdA desde menú contextual
Crear nuevo proyecto (limpia el historial)
Abrir proyecto (limpia el historial)

Módulo 11 — Iconos de Ribbon (arshipdesign/ui/icons.py)

Estado: 🔧 Implementado — pendiente verificación visual

Qué hace

Nuevo módulo arshipdesign/ui/icons.py con 50 iconos programáticos únicos, uno por cada botón del ribbon. Antes todos compartían el mismo icono genérico del sistema (SP_FileDialogDetailedView).

Diseño técnico

  • Cada icono se dibuja con QPainter sobre un QPixmap(24×24) transparente.
  • Paleta coherente con el tema oscuro:
    • #c8d8e8 trazo principal
    • #4da8ff cyan / agua
    • #00ffb0 verde mint (selección / OK)
    • #ffd060 amarillo / dorado (energía, controles)
    • #ff5555 rojo (daño, alerta)
  • Función pública: icon("clave") → QIcon con caché _CACHE dict.
  • Importado en main_window.py como from arshipdesign.ui.icons import icon as _ico.

Iconos implementados por grupo

Grupo Claves
HOME / Vistas 4views, lines_plan
Geometría / Nuevo wizard, hull_nurbs, appendage
Geometría / Edición NURBS ctrl_pts, extrude, mirror, lackenby
Geometría / Importar import_offsets, import_dxf
Geometría / Exportar export_iges, export_step, export_dxf
Geometría / Suavizado smooth, combs, fairness
Análisis / Hidrostática hydro_calc, hydro_curves, export_csv
Análisis / Estabilidad gz_curve, imo, damage
Análisis / Resistencia holtrop, savitsky, vpp
Análisis / Seakeeping stf, spectrum
Análisis / Estructura iso12215
Tanques new_tank, model_tank, load_case, sounding, calc_kg
Sistemas / Eléctrico epla
Sistemas / Fluidos fuel, freshwater, bilge, firefight
Sistemas / Routing 3D pipes, cables
Sistemas / Clima hvac, steering
Fabricación / CNC materials, nesting, gcode, postproc
Fabricación / Moldes FRP lofting, laminate, resin, bom

Decisiones

  • Se mantienen los iconos estándar del sistema para: Nuevo, Abrir, Guardar (Archivo), Deshacer/Rehacer (flechas del sistema), Offsets (vista de lista).
  • El módulo NO importa Qt en el nivel de módulo — los QIcon solo se crean cuando se llaman, así la importación de icons.py es segura antes de que exista QApplication.

Corrección — Rediseño v2 (2026-05-30 sesión 2)

Problema detectado: La primera versión usaba trazos claros (#c8d8e8) sobre fondo transparente. El ribbon de PySide6 tiene fondo blanco → los iconos eran prácticamente invisibles (se veía solo el borde del botón).

Solución: Rediseño completo con estilo "flat icon":

  • Relleno sólido de color por categoría + contorno oscuro #1a2535
  • Visible en fondos claros Y oscuros
  • Colores por categoría:
Categoría Color
Geometría / Casco Azul océano #2a7fc8
Edición NURBS Índigo #5548d0
Suavizado Verde vivo #20a860
Peines Púrpura #7040c8 + verde mint
Fairness Gradiente rojo→verde
Análisis hidro Teal #1898a8
Estabilidad Azul #2068c0
Resistencia Naranja #d07020
Tanques Cyan #18a0c0
Sistemas eléctrico Amarillo sobre negro
Fabricación Violeta #8838b8

Estado tras rediseño: 🔧 Pendiente verificar visualmente (requiere python main.py)


Módulo 12 — Peines de Curvatura Mejorados

Estado: 🔧 Implementado — pendiente verificación visual

Cambios en viewer_lines.py

Problema: Los peines se dibujaban usando los ~10-20 puntos crudos de la tabla de offsets. Resultado: pelos escasos, ángulos bruscos, spine anguloso.

Solución: Nueva función _resample_curve_smooth(xs, ys, n=80):

  • Parametriza la curva por longitud de arco acumulada
  • Remuestrea a 80 puntos equidistantes usando scipy.interpolate.CubicSpline
  • Fallback a np.interp (lineal) si scipy no está disponible
  • Llamada al inicio de _draw_curvature_comb antes de calcular κ

Resultado esperado: 80 pelos por curva en lugar de ~10-20, spine suave.

Regla

No aumentar más de 80 muestras sin medir impacto en FPS — la función se llama en cada paintEvent (puede ser frecuente al arrastrar nodos).


Módulo 13 — Visor 3D Colores Sólidos

Estado: 🔧 Implementado — pendiente verificación visual

Cambios en viewer_3d.py_render_hull_mesh

Parámetro Antes Ahora Por qué
smooth_shading True False Facetas planas = aspecto sólido, sin blur
opacity 0.92 1.0 Totalmente opaco = color pleno
ambient (default ~0.2) 0.40 Reduce sombras duras, color más uniforme
diffuse (default ~0.8) 0.60 Equilibrio iluminación
specular (default ~0.1) 0.05 Sin brillos que difuminen
color #3a6080 #4a8ab0 Tono más vivo y legible
line_width 0.3 0.6 Aristas más visibles al activar mallas

Nota

Si en el futuro se quiere smooth shading selectivo (solo en alta resolución), usar mesh.compute_normals() primero y luego smooth_shading=True.


Módulo 14 — Fix Freeze Curva GZ (QThread)

Estado: 🔧 Implementado — pendiente verificación

Problema

_on_show_stability_compute_and_show_gzcompute_gz_wall_sidedcompute_upright (integración hidrostática pesada) → bloqueaba el UI thread de Qt indefinidamente ("se trabó el programa").

Solución

Nueva clase _GZWorker(QObject) con señales finished / error. El cálculo se mueve a un QThread:

[UI thread]  botón →  _compute_and_show_gz()
                          ↓ lanza QThread
[Hilo GZ]               _GZWorker.run()
                          ↓ emite finished(gz_curve, imo_result)
[UI thread]             _on_gz_done()  → actualiza widget + statusBar

Guarda doble

Si el usuario hace clic dos veces seguidas, el segundo clic se ignora mientras el hilo anterior sigue corriendo (if self._gz_thread.isRunning(): return).

Archivos modificados

Archivo Cambio
main_window.py Import QThread, QObject; clase _GZWorker; _compute_and_show_gz refactorizado; nuevo slot _on_gz_done

Roadmap Global

Prioridad Feature Módulo Estado
🔴 Alta Wizard de estaciones / LdA / buttocks Geometría 📋
🔴 Alta Transom stern (popa espejo) Geometría + 3D 📋
🟡 Media Verificar iconos ribbon visualmente UI 🔧
🟡 Media Verificar peines densidad visual Fairness 🔧
🟡 Media Verificar colores sólidos 3D visual Visor 3D 🔧
🟡 Media Caras invertidas 3D + flip Visor 3D 📋
🟡 Media Peines de curvatura en keel/sheer (Z) Fairness 📋
🟡 Media Suavizado 2D (Laplaciano transversal) Fairness 📋
🟢 Baja Tests de fairness automatizados Tests 📋
🟢 Baja Exportar DXF / offsets CSV Exportación 📋
🟢 Baja Importar offsets desde tabla manual Importación 📋

Tests y Entorno

# Ejecutar suite completa
cd "D:\Proyectos Software\AR-Shipdesign"
python -m pytest tests/ -x -q

# Lanzar la aplicación
python main.py

Estado de tests: 315/315 — Última verificación: 2026-05-30 ⚠️ Tests no actualizados para GZWorker (QThread) — agregar en próxima sesión.


Última actualización: 2026-05-30 (sesión 2) Mantener este archivo actualizado al final de cada sesión de trabajo.