550 lines
21 KiB
Markdown
550 lines
21 KiB
Markdown
# 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`)
|
||
|
||
```python
|
||
_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 | Conectada en `__init__` |
|
||
| 2026-05-29 | `QPushButton` no importado | Faltaba en bloque de imports | Añadido |
|
||
|
||
---
|
||
|
||
## 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_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
|
||
|
||
```bash
|
||
# 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.*
|