21 KiB
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, losx_offsetsse 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_yzento_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
NodeInfoPanelmuestra 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
#00FFB0con 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_draggingdurante el drag,offsets_editedal 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 (
QtInteractorembebido). - Degradación sin PyVista: muestra
QLabelen lugar de crashear (permite que CI pase). - Carga diferida:
QtInteractorse crea 500 ms después del arranque (evita conflicto OpenGL). - Tema oscuro: fondo
#1a1d30, casco#3a6080, aristas#4da8ff, plano de flotación#4da8ffal 15%. - Toggle mallas (botón
⬡ Mallasen barra superior del visor): apagado por defecto. LlamaGetProperty().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_dictno llamasnap_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
QPaintersobre unQPixmap(24×24)transparente. - Paleta coherente con el tema oscuro:
#c8d8e8trazo principal#4da8ffcyan / agua#00ffb0verde mint (selección / OK)#ffd060amarillo / dorado (energía, controles)#ff5555rojo (daño, alerta)
- Función pública:
icon("clave") → QIconcon caché_CACHEdict. - Importado en
main_window.pycomofrom 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.pyes segura antes de que existaQApplication.
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_combantes 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_gz → compute_gz_wall_sided →
compute_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.