""" icons.py — Iconos programáticos para el ribbon de AR-ShipDesign. Diseño: "flat icon" con relleno sólido de color + contorno oscuro. Visible tanto en fondos claros (ribbon blanco) como en fondos oscuros. Paleta por categoría: Geometría nueva : azul océano #2a7fc8 Edición NURBS : índigo #5548d0 Suavizado/Fairness: verde #20a860, púrpura #7040c8, gradiente rojo-verde Análisis hidro : teal #1898a8 Estabilidad : azul #2068c0 Resistencia : naranja #d07020 Tanques : cyan #18a0c0 Sistemas : amarillo #d0b818 (eléctrico), cyan (fluidos) Fabricación : violeta #8838b8 Autor: Álvaro Romero — AR-ShipDesign Sprint 1 """ from __future__ import annotations import math from typing import Callable from PySide6.QtCore import Qt, QPointF, QRectF from PySide6.QtGui import ( QColor, QIcon, QPainter, QPainterPath, QPen, QPixmap, QBrush, QFont, QLinearGradient, QRadialGradient, ) # ─── Constantes de dibujo ──────────────────────────────────────────────────── _SIZE = 24 _OUT = QColor("#1a2535") # contorno oscuro — visible sobre fondo blanco def _canvas(): px = QPixmap(_SIZE, _SIZE) px.fill(Qt.GlobalColor.transparent) p = QPainter(px) p.setRenderHint(QPainter.RenderHint.Antialiasing) return px, p def _pen(color=_OUT, w=1.6): pen = QPen(color, w) pen.setCapStyle(Qt.PenCapStyle.RoundCap) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) return pen def _fill(p: QPainter, path: QPainterPath, fill: QColor, stroke: QColor = _OUT, sw: float = 1.6): p.setPen(_pen(stroke, sw)) p.setBrush(QBrush(fill)) p.drawPath(path) p.setBrush(Qt.BrushStyle.NoBrush) def _finish(p, px): p.end() return QIcon(px) def _rect_path(x, y, w, h, rx=0): pa = QPainterPath() pa.addRoundedRect(QRectF(x, y, w, h), rx, rx) return pa # ─── Colores por categoría ─────────────────────────────────────────────────── _C_HULL = QColor("#2a7fc8") # geometría / casco _C_NURBS = QColor("#5548d0") # edición NURBS _C_SMOOTH = QColor("#20a860") # suavizado verde _C_COMB = QColor("#7040c8") # peines púrpura _C_FAIR_G = QColor("#10c870") # fairness verde _C_FAIR_R = QColor("#e03828") # fairness rojo _C_HYDRO = QColor("#1898a8") # hidrostática teal _C_GZ = QColor("#2068c0") # estabilidad azul _C_RES = QColor("#d07020") # resistencia naranja _C_TANK = QColor("#18a0c0") # tanques cyan _C_ELEC = QColor("#d0b818") # eléctrico amarillo _C_WATER = QColor("#3898d8") # agua cyan-azul _C_FIRE = QColor("#d83020") # incendio rojo _C_FUEL = QColor("#e07818") # combustible naranja _C_FAB = QColor("#8838b8") # fabricación violeta _C_STRUCT = QColor("#6888a8") # estructura gris-azul _C_WHITE = QColor("#f0f4ff") # blanco cálido (relleno claro) _C_MINT = QColor("#00e8a0") # mint verde (acento) # ═══════════════════════════════════════════════════════════════════════════════ # HOME — Vistas # ═══════════════════════════════════════════════════════════════════════════════ def _ico_lines_plan(): """Plano de líneas — secciones anidadas de casco.""" px, p = _canvas() # Fondo de panel bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#ddeeff"), _OUT, 1.4) # 3 secciones en U anidadas (cuadernas) for oy, w, col in [(3, 9, _C_HULL), (7, 7, QColor("#4a9fd8")), (11, 5, QColor("#6ab8e8"))]: path = QPainterPath() path.moveTo(12 - w, oy + 9) path.cubicTo(12 - w, oy, 12 + w, oy, 12 + w, oy + 9) p.setPen(_pen(col, 2.0)) p.drawPath(path) # Línea de agua p.setPen(_pen(_C_WATER, 1.6)) p.drawLine(QPointF(2, 20), QPointF(22, 20)) return _finish(p, px) def _ico_4views(): """4 Vistas — cuatro paneles de viewport.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 2) _fill(p, bg, QColor("#e8eef8"), _OUT, 1.2) # 3 paneles grises + 1 cyan (perspectiva 3D) for (x, y, w, h, col) in [ (2, 2, 9, 9, QColor("#c8d8e8")), (13, 2, 9, 9, QColor("#c8d8e8")), (2, 13, 9, 9, QColor("#c8d8e8")), (13, 13, 9, 9, _C_HULL), ]: r = _rect_path(x, y, w, h, 1) _fill(p, r, col, _OUT, 1.0) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # GEOMETRÍA — Nuevo # ═══════════════════════════════════════════════════════════════════════════════ def _ico_wizard(): """Asistente — varita mágica con estrella de destello.""" px, p = _canvas() # Varita (fondo oscuro → visible) p.setPen(_pen(_OUT, 2.4)) p.drawLine(QPointF(4, 20), QPointF(17, 7)) # Empuñadura p.setPen(_pen(QColor("#8060a0"), 4.0)) p.drawLine(QPointF(4, 20), QPointF(9, 15)) # Estrella de 4 puntas p.setPen(_pen(_C_ELEC, 2.0)) cx, cy = 18.5, 5.5 for a in [0, 90, 180, 270]: rad = math.radians(a) p.drawLine(QPointF(cx + 3*math.cos(rad), cy + 3*math.sin(rad)), QPointF(cx, cy)) for a in [45, 135, 225, 315]: rad = math.radians(a) p.drawLine(QPointF(cx + 1.8*math.cos(rad), cy + 1.8*math.sin(rad)), QPointF(cx, cy)) # Destellos pequeños p.setPen(_pen(_C_ELEC, 1.2)) for (x1,y1,x2,y2) in [(5,11,6,10),(8,14,10,13),(6,14,6,12)]: p.drawLine(QPointF(x1,y1), QPointF(x2,y2)) return _finish(p, px) def _ico_hull_nurbs(): """Casco NURBS — sección maestra rellena.""" px, p = _canvas() # Media cuaderna rellena path = QPainterPath() path.moveTo(12, 3) path.cubicTo(19, 3, 22, 13, 20, 20) path.lineTo(4, 20) path.cubicTo(2, 13, 5, 3, 12, 3) _fill(p, path, _C_HULL, _OUT, 1.6) # Línea de crujía p.setPen(QPen(_C_WHITE, 1.2, Qt.PenStyle.DashLine)) p.drawLine(QPointF(12, 3), QPointF(12, 20)) # Línea de base p.setPen(_pen(_C_WATER, 2.0)) p.drawLine(QPointF(2, 20), QPointF(22, 20)) return _finish(p, px) def _ico_appendage(): """Apéndice — casco + quilla/aleta.""" px, p = _canvas() # Casco (banda horizontal) hull = _rect_path(2, 8, 20, 5, 2) _fill(p, hull, _C_HULL, _OUT, 1.4) # Aleta (fin) — más oscura fin = QPainterPath() fin.moveTo(9, 13) fin.lineTo(7, 22) fin.lineTo(13, 22) fin.lineTo(15, 13) _fill(p, fin, QColor("#1a5a9a"), _OUT, 1.4) # Nodos de control en amarillo p.setPen(_pen(_C_ELEC, 1.0)) p.setBrush(QBrush(_C_ELEC)) for pt in [(9,13),(7,22),(13,22),(15,13)]: p.drawEllipse(QRectF(pt[0]-2, pt[1]-2, 4, 4)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # GEOMETRÍA — Edición NURBS # ═══════════════════════════════════════════════════════════════════════════════ def _ico_ctrl_pts(): """Puntos de Control — polígono de control visible.""" px, p = _canvas() pts = [(3, 20), (5, 9), (12, 4), (19, 9), (21, 18)] # Polígono de control gris p.setPen(QPen(QColor("#a0b0c8"), 1.4, Qt.PenStyle.DashLine)) for i in range(len(pts)-1): p.drawLine(QPointF(*pts[i]), QPointF(*pts[i+1])) # Curva NURBS en índigo p.setPen(_pen(_C_NURBS, 2.2)) path = QPainterPath() path.moveTo(*pts[0]) path.cubicTo(5,11,9,3,12,4) path.cubicTo(15,5,20,11,21,18) p.drawPath(path) # Nodos cuadrados amarillos p.setPen(_pen(_OUT, 1.2)) p.setBrush(QBrush(_C_ELEC)) for pt in pts: p.drawRect(QRectF(pt[0]-2.5, pt[1]-2.5, 5, 5)) return _finish(p, px) def _ico_extrude(): """Extruir — sección 2D con flecha 3D.""" px, p = _canvas() # Sección frontal rellena (índigo) face = _rect_path(2, 9, 10, 12, 1) _fill(p, face, _C_NURBS, _OUT, 1.4) # Profundidad / perspectiva (naranja) p.setPen(_pen(_C_RES, 1.8)) for (sx,sy,ex,ey) in [(12,9,19,4),(12,15,19,10),(12,21,19,16)]: p.drawLine(QPointF(sx,sy), QPointF(ex,ey)) p.drawLine(QPointF(19,4), QPointF(19,16)) # Punta de flecha p.setPen(_pen(_C_RES, 2.0)) p.drawLine(QPointF(16,2), QPointF(19,4)) p.drawLine(QPointF(21,3), QPointF(19,4)) return _finish(p, px) def _ico_mirror(): """Espejo — forma asimétrica + reflejo.""" px, p = _canvas() # Línea de crujía (cian punteado) p.setPen(QPen(_C_WATER, 1.6, Qt.PenStyle.DashLine)) p.drawLine(QPointF(12, 2), QPointF(12, 22)) # Forma original (sólida, azul) path1 = QPainterPath() path1.moveTo(12, 4) path1.cubicTo(4, 5, 2, 12, 4, 20) path1.lineTo(12, 20) _fill(p, path1, _C_HULL, _OUT, 1.6) # Reflejo (semi-transparente, más claro) path2 = QPainterPath() path2.moveTo(12, 4) path2.cubicTo(20, 5, 22, 12, 20, 20) path2.lineTo(12, 20) _fill(p, path2, QColor(42, 127, 200, 80), QColor("#4090c0"), 1.2) return _finish(p, px) def _ico_lackenby(): """Lackenby — curva que se deforma longitudinalmente.""" px, p = _canvas() # Curva original (gris) p.setPen(_pen(QColor("#a0b0c8"), 1.8)) path1 = QPainterPath() path1.moveTo(2, 20) path1.cubicTo(6, 4, 18, 4, 22, 20) p.drawPath(path1) # Curva transformada (naranja vivo) p.setPen(_pen(_C_RES, 2.4)) path2 = QPainterPath() path2.moveTo(2, 20) path2.cubicTo(4, 8, 13, 3, 22, 20) p.drawPath(path2) # Flecha de transformación (amarillo) p.setPen(_pen(_C_ELEC, 2.0)) p.drawLine(QPointF(8, 12), QPointF(14, 8)) p.drawLine(QPointF(14, 8), QPointF(12, 11)) p.drawLine(QPointF(14, 8), QPointF(11, 6)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # GEOMETRÍA — Importar # ═══════════════════════════════════════════════════════════════════════════════ def _ico_import_offsets(): """Importar Offsets — tabla + flecha verde de entrada.""" px, p = _canvas() tbl = _rect_path(6, 5, 15, 14, 1) _fill(p, tbl, _C_WHITE, _OUT, 1.3) for y in [9, 13]: p.setPen(_pen(_OUT, 0.9)) p.drawLine(QPointF(6, y), QPointF(21, y)) p.drawLine(QPointF(12, 5), QPointF(12, 19)) # Flecha verde entrante p.setPen(_pen(_C_SMOOTH, 2.2)) p.drawLine(QPointF(1, 12), QPointF(6, 12)) p.drawLine(QPointF(4, 10), QPointF(6, 12)) p.drawLine(QPointF(4, 14), QPointF(6, 12)) return _finish(p, px) def _ico_import_dxf(): """Importar DXF — carpeta naranja con letras.""" px, p = _canvas() folder = QPainterPath() folder.moveTo(2, 18) folder.lineTo(2, 11) folder.lineTo(7, 11) folder.lineTo(9, 8) folder.lineTo(21, 8) folder.lineTo(21, 18) folder.closeSubpath() _fill(p, folder, _C_RES, _OUT, 1.4) f = QFont("Arial", 5, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_WHITE, 1.0)) p.drawText(QRectF(5, 11, 14, 8), Qt.AlignmentFlag.AlignCenter, "DXF") return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # GEOMETRÍA — Exportar # ═══════════════════════════════════════════════════════════════════════════════ def _ico_export_iges(): """Exportar IGES — disco verde claro.""" px, p = _canvas() disk = _rect_path(2, 2, 20, 20, 3) _fill(p, disk, QColor("#20b880"), _OUT, 1.4) slot = _rect_path(6, 2, 8, 7, 1) _fill(p, slot, QColor("#e8f8f0"), _OUT, 1.0) label = _rect_path(3, 13, 18, 8, 2) _fill(p, label, QColor("#10905c"), _OUT, 1.0) f = QFont("Arial", 5, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_WHITE, 1.0)) p.drawText(QRectF(3, 13, 18, 8), Qt.AlignmentFlag.AlignCenter, "IGES") return _finish(p, px) def _ico_export_step(): """Exportar STEP — disco teal.""" px, p = _canvas() disk = _rect_path(2, 2, 20, 20, 3) _fill(p, disk, _C_HYDRO, _OUT, 1.4) slot = _rect_path(6, 2, 8, 7, 1) _fill(p, slot, QColor("#e0f4f8"), _OUT, 1.0) label = _rect_path(3, 13, 18, 8, 2) _fill(p, label, QColor("#107888"), _OUT, 1.0) f = QFont("Arial", 5, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_WHITE, 1.0)) p.drawText(QRectF(3, 13, 18, 8), Qt.AlignmentFlag.AlignCenter, "STEP") return _finish(p, px) def _ico_export_dxf(): """Exportar DXF — disco naranja.""" px, p = _canvas() disk = _rect_path(2, 2, 20, 20, 3) _fill(p, disk, _C_RES, _OUT, 1.4) slot = _rect_path(6, 2, 8, 7, 1) _fill(p, slot, QColor("#fce8d0"), _OUT, 1.0) label = _rect_path(3, 13, 18, 8, 2) _fill(p, label, QColor("#a05010"), _OUT, 1.0) f = QFont("Arial", 5, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_WHITE, 1.0)) p.drawText(QRectF(3, 13, 18, 8), Qt.AlignmentFlag.AlignCenter, "DXF") return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # GEOMETRÍA — Suavizado # ═══════════════════════════════════════════════════════════════════════════════ def _ico_smooth(): """Suavizar — zigzag rojo → curva verde suave.""" px, p = _canvas() # Zigzag rugoso (rojo) p.setPen(_pen(_C_FAIR_R, 2.0)) pts = [(2,15),(5,8),(7,16),(9,9),(11,14)] for i in range(len(pts)-1): p.drawLine(QPointF(*pts[i]), QPointF(*pts[i+1])) # Flecha de transformación p.setPen(_pen(_OUT, 1.8)) p.drawLine(QPointF(11, 11), QPointF(14, 11)) p.drawLine(QPointF(12, 9), QPointF(14, 11)) p.drawLine(QPointF(12, 13), QPointF(14, 11)) # Curva suave (verde vivo) p.setPen(_pen(_C_SMOOTH, 2.6)) path = QPainterPath() path.moveTo(14, 15) path.cubicTo(16, 7, 19, 7, 22, 13) p.drawPath(path) return _finish(p, px) def _ico_combs(): """Peines de curvatura — curva púrpura + pelos verdes densos.""" px, p = _canvas() # Spine (curva base) — negra/oscura, bien visible p.setPen(_pen(_OUT, 2.0)) path = QPainterPath() path.moveTo(2, 17) path.cubicTo(5, 8, 19, 8, 22, 17) p.drawPath(path) # Dibujamos la curva en púrpura encima p.setPen(_pen(_C_COMB, 2.4)) p.drawPath(path) # Pelos verdes densos (8 hairs) p.setPen(_pen(_C_SMOOTH, 1.6)) hairs = [ (3.0, 16.5, 3.0, 11.0), (5.5, 12.5, 5.5, 7.0), (8.0, 10.0, 8.0, 4.0), (11.0, 9.0, 11.0, 3.0), (14.0, 9.0, 14.0, 3.0), (17.0, 10.5, 17.0, 4.5), (19.5, 12.5, 19.5, 7.0), (21.5, 16.0, 21.5, 10.5), ] for x1,y1,x2,y2 in hairs: p.drawLine(QPointF(x1,y1), QPointF(x2,y2)) # Spine de las puntas (línea de curvatura) p.setPen(QPen(_C_MINT, 1.4, Qt.PenStyle.DashLine)) tips = QPainterPath() tips.moveTo(3.0, 11.0) tips.cubicTo(5.0, 5.0, 11.0, 2.5, 12.5, 2.5) tips.cubicTo(15.0, 2.5, 20.0, 5.5, 21.5, 10.5) p.drawPath(tips) return _finish(p, px) def _ico_fairness(): """Equidad — barra de gradiente verde→rojo sobre curva.""" px, p = _canvas() # Gradiente de fondo grad = QLinearGradient(2, 12, 22, 12) grad.setColorAt(0.0, _C_FAIR_G) grad.setColorAt(0.5, QColor("#c0d020")) grad.setColorAt(1.0, _C_FAIR_R) p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(grad)) p.drawRoundedRect(QRectF(2, 19, 20, 4), 2, 2) p.setBrush(Qt.BrushStyle.NoBrush) # Curva por encima (negra, contrastada) p.setPen(_pen(_OUT, 2.2)) curve = QPainterPath() curve.moveTo(2, 17) curve.cubicTo(6, 6, 18, 6, 22, 17) p.drawPath(curve) # Puntitos sobre la curva (valores de equidad) for x, y, col in [(5,10,_C_FAIR_G),(10,7,_C_FAIR_G),(14,7,_C_ELEC),(18,10,_C_FAIR_R)]: p.setPen(_pen(_OUT, 1.0)) p.setBrush(QBrush(col)) p.drawEllipse(QRectF(x-2,y-2,4,4)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # ANÁLISIS — Hidrostática # ═══════════════════════════════════════════════════════════════════════════════ def _ico_hydro_calc(): """Calcular hidrostáticos — Σ sobre fondo teal.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 4) _fill(p, bg, _C_HYDRO, _OUT, 1.4) f = QFont("serif", 16, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_WHITE, 1.0)) p.drawText(QRectF(1, 0, 22, 24), Qt.AlignmentFlag.AlignCenter, "Σ") return _finish(p, px) def _ico_hydro_curves(): """Curvas hidrostáticas — gráfico con 3 curvas de colores.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#e8f6f8"), _OUT, 1.2) # Ejes p.setPen(_pen(QColor("#4088a0"), 1.4)) p.drawLine(QPointF(3, 21), QPointF(3, 3)) p.drawLine(QPointF(3, 21), QPointF(22, 21)) # 3 curvas for pts, col in [ ([(3,20),(7,13),(12,10),(17,8),(22,7)], _C_HYDRO), ([(3,19),(7,16),(12,14),(17,12),(22,11)], _C_SMOOTH), ([(3,21),(7,18),(12,16),(17,15),(22,14)], _C_RES), ]: p.setPen(_pen(col, 1.8)) path = QPainterPath() path.moveTo(*pts[0]) for pt in pts[1:]: path.lineTo(*pt) p.drawPath(path) return _finish(p, px) def _ico_export_csv(): """Exportar CSV — tabla con flecha de exportación.""" px, p = _canvas() tbl = _rect_path(2, 4, 14, 16, 1) _fill(p, tbl, _C_WHITE, _OUT, 1.3) p.setPen(_pen(_OUT, 0.9)) for y in [8, 12, 16]: p.drawLine(QPointF(2, y), QPointF(16, y)) for x in [7, 12]: p.drawLine(QPointF(x, 4), QPointF(x, 20)) # Flecha verde saliente p.setPen(_pen(_C_SMOOTH, 2.4)) p.drawLine(QPointF(16, 12), QPointF(22, 12)) p.drawLine(QPointF(19.5, 9.5), QPointF(22, 12)) p.drawLine(QPointF(19.5, 14.5), QPointF(22, 12)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # ANÁLISIS — Estabilidad # ═══════════════════════════════════════════════════════════════════════════════ def _ico_gz_curve(): """Curva GZ — gráfico con curva verde + etiquetas.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#e8f0f8"), _OUT, 1.2) # Ejes p.setPen(_pen(_OUT, 1.4)) p.drawLine(QPointF(3, 20), QPointF(21, 20)) p.drawLine(QPointF(3, 20), QPointF(3, 3)) # Curva GZ (verde vivo) p.setPen(_pen(_C_SMOOTH, 2.4)) gz = QPainterPath() gz.moveTo(3, 20) gz.cubicTo(7, 8, 13, 5, 15, 10) gz.cubicTo(17, 15, 20, 20, 21, 20) p.drawPath(gz) # Etiquetas G, Z f = QFont("Arial", 5, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_RES, 1.0)) p.drawText(QRectF(4, 14, 5, 6), Qt.AlignmentFlag.AlignCenter, "G") p.setPen(_pen(_C_GZ, 1.0)) p.drawText(QRectF(10, 8, 5, 5), Qt.AlignmentFlag.AlignCenter, "Z") return _finish(p, px) def _ico_imo(): """IMO IS2008 — libro azul abierto con sello.""" px, p = _canvas() # Tapa izquierda left = QPainterPath() left.moveTo(3, 5); left.quadTo(7, 4, 12, 6) left.lineTo(12, 21); left.quadTo(7, 19, 3, 20) left.closeSubpath() _fill(p, left, _C_GZ, _OUT, 1.4) # Tapa derecha right = QPainterPath() right.moveTo(21, 5); right.quadTo(17, 4, 12, 6) right.lineTo(12, 21); right.quadTo(17, 19, 21, 20) right.closeSubpath() _fill(p, right, QColor("#3880d8"), _OUT, 1.4) # Lomo p.setPen(_pen(_OUT, 1.8)) p.drawLine(QPointF(12, 4), QPointF(12, 21)) # Texto IMO f = QFont("Arial", 4, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_ELEC, 1.0)) p.drawText(QRectF(13, 10, 8, 6), Qt.AlignmentFlag.AlignCenter, "IMO") return _finish(p, px) def _ico_damage(): """Avería — buque con brecha roja + agua.""" px, p = _canvas() # Casco hull = QPainterPath() hull.moveTo(2, 9); hull.lineTo(2, 18) hull.lineTo(22, 18); hull.lineTo(22, 9) hull.quadTo(12, 15, 2, 9) _fill(p, hull, _C_HULL, _OUT, 1.4) # Superestructura deck = _rect_path(7, 5, 9, 4, 1) _fill(p, deck, QColor("#3a70a8"), _OUT, 1.2) # Brecha / damage en rojo p.setPen(_pen(_C_FIRE, 2.4)) p.drawLine(QPointF(11, 16), QPointF(14, 19)) p.drawLine(QPointF(14, 16), QPointF(11, 19)) # Olas de agua p.setPen(_pen(_C_WATER, 1.6)) for dx in [-3, 0, 3]: wp = QPainterPath() wp.moveTo(12+dx, 20) wp.quadTo(13+dx, 18, 12+dx, 17) p.drawPath(wp) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # ANÁLISIS — Resistencia # ═══════════════════════════════════════════════════════════════════════════════ def _ico_holtrop(): """Holtrop & Mennen — buque naranja con olas.""" px, p = _canvas() # Casco naranja hull = QPainterPath() hull.moveTo(2, 14) hull.cubicTo(4, 10, 8, 9, 14, 9) hull.lineTo(20, 9); hull.cubicTo(22, 9, 22, 14, 20, 14) hull.closeSubpath() _fill(p, hull, _C_RES, _OUT, 1.4) # Superestructura deck = _rect_path(7, 5, 8, 4, 1) _fill(p, deck, QColor("#c08820"), _OUT, 1.2) # Olas cian p.setPen(_pen(_C_WATER, 1.8)) for ox in [0, 4, 8]: wp = QPainterPath() wp.moveTo(2+ox, 18) wp.cubicTo(3+ox, 15, 5+ox, 15, 6+ox, 18) p.drawPath(wp) return _finish(p, px) def _ico_savitsky(): """Savitsky — planeador a alta velocidad.""" px, p = _canvas() # Casco inclinado (naranja) hull = QPainterPath() hull.moveTo(2, 21); hull.lineTo(21, 9) hull.lineTo(22, 11); hull.lineTo(3, 23) hull.closeSubpath() _fill(p, hull, _C_RES, _OUT, 1.4) # Estela / spray (azul) p.setPen(_pen(_C_WATER, 1.4)) for i in range(5): p.drawLine(QPointF(3+i*2, 22-i*0.5), QPointF(3+i*2-3, 23+i*0.3)) # "V" de velocidad f = QFont("Arial", 6, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_C_ELEC, 1.0)) p.drawText(QRectF(13, 2, 9, 8), Qt.AlignmentFlag.AlignCenter, "V") return _finish(p, px) def _ico_vpp(): """VPP Velero — vela mayor + foque.""" px, p = _canvas() # Mástil p.setPen(_pen(_OUT, 2.0)) p.drawLine(QPointF(12, 22), QPointF(12, 2)) # Vela mayor (azul) main = QPainterPath() main.moveTo(12, 3); main.quadTo(20, 10, 12, 22) main.closeSubpath() _fill(p, main, _C_GZ, _OUT, 1.4) # Foque (teal) jib = QPainterPath() jib.moveTo(12, 5); jib.quadTo(4, 12, 12, 20) jib.closeSubpath() _fill(p, jib, _C_HYDRO, _OUT, 1.2) # Flotación p.setPen(_pen(_C_WATER, 1.6)) p.drawLine(QPointF(4, 22), QPointF(20, 22)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # ANÁLISIS — Seakeeping # ═══════════════════════════════════════════════════════════════════════════════ def _ico_stf(): """Strip Theory (STF) — buque con franjas transversales.""" px, p = _canvas() # Casco hull = QPainterPath() hull.moveTo(2, 14); hull.cubicTo(4, 10, 8, 9, 14, 9) hull.lineTo(21, 9); hull.lineTo(21, 14); hull.closeSubpath() _fill(p, hull, _C_GZ, _OUT, 1.4) # Franjas (strips) en cyan p.setPen(_pen(_C_MINT, 1.6)) for x in [6, 10, 14, 18]: p.drawLine(QPointF(x, 9), QPointF(x, 14)) # Olas base p.setPen(_pen(_C_WATER, 1.4)) wave = QPainterPath() wave.moveTo(2, 18) for i in range(5): wave.cubicTo(3+i*4, 15, 5+i*4, 21, 6+i*4, 18) p.drawPath(wave) return _finish(p, px) def _ico_spectrum(): """Espectro de respuesta — campana espectral sobre ejes.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#e8f0f8"), _OUT, 1.2) p.setPen(_pen(_OUT, 1.2)) p.drawLine(QPointF(3, 21), QPointF(21, 21)) p.drawLine(QPointF(3, 21), QPointF(3, 3)) # Campana rellena (teal) bell = QPainterPath() bell.moveTo(3, 21) bell.cubicTo(5, 21, 8, 4, 11, 4) bell.cubicTo(14, 4, 17, 21, 21, 21) bell.closeSubpath() _fill(p, bell, QColor(24, 152, 168, 100), _C_HYDRO, 1.6) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # ANÁLISIS — Estructura # ═══════════════════════════════════════════════════════════════════════════════ def _ico_iso12215(): """ISO 12215 — perfil T de acero estructural.""" px, p = _canvas() # Ala del perfil T (flange) flange = _rect_path(3, 7, 18, 4, 1) _fill(p, flange, _C_STRUCT, _OUT, 1.6) # Alma (web) web = _rect_path(9, 11, 6, 11, 1) _fill(p, web, _C_STRUCT, _OUT, 1.6) # Línea de cotas amarilla p.setPen(_pen(_C_ELEC, 1.2)) p.drawLine(QPointF(3, 4), QPointF(21, 4)) p.drawLine(QPointF(3, 3), QPointF(3, 5)) p.drawLine(QPointF(21, 3), QPointF(21, 5)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # TANQUES # ═══════════════════════════════════════════════════════════════════════════════ def _ico_new_tank(): """Nuevo Tanque — cilindro cyan con nivel de líquido.""" px, p = _canvas() # Cuerpo del cilindro body = _rect_path(5, 7, 14, 12, 1) _fill(p, body, QColor("#d0f0f8"), _OUT, 1.4) # Nivel de líquido (cyan relleno) liquid = _rect_path(5, 13, 14, 6, 1) _fill(p, liquid, _C_TANK, _OUT, 0.8) # Tapas elípticas p.setPen(_pen(_OUT, 1.4)) p.setBrush(QBrush(QColor("#e8f8fc"))) p.drawEllipse(QRectF(5, 4, 14, 6)) p.setBrush(QBrush(_C_TANK)) p.drawEllipse(QRectF(5, 16, 14, 6)) # Línea de nivel p.setPen(_pen(_C_WHITE, 1.6)) p.drawLine(QPointF(5, 13), QPointF(19, 13)) return _finish(p, px) def _ico_model_tank(): """Modelar Tanque — caja 3D isométrica.""" px, p = _canvas() # Cara frontal front = _rect_path(2, 11, 13, 11, 1) _fill(p, front, _C_TANK, _OUT, 1.4) # Techo (perspectiva) roof = QPainterPath() roof.moveTo(2, 11); roof.lineTo(7, 5) roof.lineTo(20, 5); roof.lineTo(15, 11) roof.closeSubpath() _fill(p, roof, QColor("#38b8d8"), _OUT, 1.4) # Lado derecho side = QPainterPath() side.moveTo(15, 11); side.lineTo(20, 5) side.lineTo(20, 16); side.lineTo(15, 22) side.closeSubpath() _fill(p, side, QColor("#1080a0"), _OUT, 1.4) # + verde (nuevo) p.setPen(_pen(_C_SMOOTH, 2.2)) p.drawLine(QPointF(6,16), QPointF(10,16)) p.drawLine(QPointF(8,14), QPointF(8,18)) return _finish(p, px) def _ico_load_case(): """Caso de Carga — balanza de platillos.""" px, p = _canvas() # Barra horizontal p.setPen(_pen(_OUT, 2.0)) p.drawLine(QPointF(4, 10), QPointF(20, 10)) # Mástil vertical p.drawLine(QPointF(12, 10), QPointF(12, 21)) p.drawLine(QPointF(9, 21), QPointF(15, 21)) # Platillo izquierdo lp = QPainterPath() lp.moveTo(3, 10); lp.quadTo(5, 17, 8, 17); lp.quadTo(11, 17, 9, 10) _fill(p, lp, QColor("#d0d8e8"), _OUT, 1.2) # Platillo derecho rp = QPainterPath() rp.moveTo(21, 10); rp.quadTo(19, 17, 16, 17); rp.quadTo(13, 17, 15, 10) _fill(p, rp, QColor("#d0d8e8"), _OUT, 1.2) # Pivote p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(_C_ELEC)) p.drawEllipse(QRectF(10, 8, 4, 4)) return _finish(p, px) def _ico_sounding(): """Sondeos — tubería de medición con nivel.""" px, p = _canvas() # Tubería tube = _rect_path(8, 2, 8, 20, 2) _fill(p, tube, QColor("#d8eef8"), _OUT, 1.4) # Líquido interior liquid = _rect_path(8, 13, 8, 9, 2) _fill(p, liquid, _C_TANK, _OUT, 0.8) # Marcas de escala for y, is_long in [(4,True),(6,False),(8,True),(10,False),(12,True),(14,False),(16,True),(18,False)]: l = 5 if is_long else 3 p.setPen(_pen(_OUT, 1.2 if is_long else 0.8)) p.drawLine(QPointF(8, y), QPointF(8+l, y)) # Menisco p.setPen(_pen(_C_WHITE, 1.6)) p.drawLine(QPointF(8, 13), QPointF(16, 13)) return _finish(p, px) def _ico_calc_kg(): """Calcular KG — buque azul + punto G amarillo.""" px, p = _canvas() # Casco hull = QPainterPath() hull.moveTo(2, 16); hull.cubicTo(4, 13, 8, 12, 14, 12) hull.lineTo(21, 12); hull.lineTo(21, 16); hull.closeSubpath() _fill(p, hull, _C_HULL, _OUT, 1.4) # Flotación p.setPen(_pen(_C_WATER, 1.6)) p.drawLine(QPointF(1, 17), QPointF(23, 17)) # Punto G (centro de gravedad) — círculo amarillo p.setPen(_pen(_OUT, 1.4)) p.setBrush(QBrush(_C_ELEC)) p.drawEllipse(QRectF(8, 7, 8, 8)) f = QFont("Arial", 6, QFont.Weight.Bold) p.setFont(f) p.setPen(_pen(_OUT, 1.0)) p.drawText(QRectF(8, 7, 8, 8), Qt.AlignmentFlag.AlignCenter, "G") # Flecha K→G p.setPen(_pen(_C_SMOOTH, 1.6)) p.drawLine(QPointF(12, 15), QPointF(12, 12)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # SISTEMAS — Eléctrico # ═══════════════════════════════════════════════════════════════════════════════ def _ico_epla(): """EPLA — rayo eléctrico amarillo sobre fondo oscuro.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 4) _fill(p, bg, QColor("#2a2010"), _OUT, 1.4) bolt = QPainterPath() bolt.moveTo(15, 2); bolt.lineTo(8, 13) bolt.lineTo(13, 13); bolt.lineTo(8, 22) bolt.lineTo(19, 10); bolt.lineTo(13, 10) bolt.closeSubpath() _fill(p, bolt, _C_ELEC, QColor("#c09010"), 1.4) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # SISTEMAS — Fluidos # ═══════════════════════════════════════════════════════════════════════════════ def _ico_fuel(): """Combustible — gota naranja con llama interior.""" px, p = _canvas() # Gota naranja drop = QPainterPath() drop.moveTo(12, 2) drop.cubicTo(19, 9, 19, 16, 12, 21) drop.cubicTo(5, 16, 5, 9, 12, 2) _fill(p, drop, _C_FUEL, _OUT, 1.6) # Llama roja interior flame = QPainterPath() flame.moveTo(12, 17) flame.cubicTo(10, 13, 11, 9, 13, 12) flame.cubicTo(14, 9, 16, 7, 12, 10) p.setPen(_pen(_C_FIRE, 1.6)) p.drawPath(flame) return _finish(p, px) def _ico_freshwater(): """Agua Dulce — grifo cyan con gota azul.""" px, p = _canvas() # Grifo (T) p.setPen(_pen(_OUT, 2.2)) p.drawLine(QPointF(7, 8), QPointF(17, 8)) p.drawLine(QPointF(12, 8), QPointF(12, 14)) valve = _rect_path(10, 13, 4, 3, 1) _fill(p, valve, _C_HYDRO, _OUT, 1.4) # Gota cayendo drop = QPainterPath() drop.moveTo(12, 17) drop.cubicTo(16, 19, 16, 23, 12, 23) drop.cubicTo(8, 23, 8, 19, 12, 17) _fill(p, drop, _C_WATER, _OUT, 1.4) return _finish(p, px) def _ico_bilge(): """Achique — bomba de achique (círculo + tuberías).""" px, p = _canvas() # Cuerpo bomba p.setPen(_pen(_OUT, 1.6)) p.setBrush(QBrush(_C_STRUCT)) p.drawEllipse(QRectF(6, 6, 12, 12)) p.setBrush(Qt.BrushStyle.NoBrush) # Tuberías p.setPen(_pen(_C_WATER, 2.4)) p.drawLine(QPointF(12, 6), QPointF(12, 2)) p.drawLine(QPointF(18, 12), QPointF(22, 12)) p.drawLine(QPointF(12, 18), QPointF(12, 22)) # Flechas de flujo p.setPen(_pen(_C_WATER, 1.6)) p.drawLine(QPointF(12, 2), QPointF(10, 4)) p.drawLine(QPointF(12, 2), QPointF(14, 4)) return _finish(p, px) def _ico_firefight(): """Contra Incendios — extintor rojo.""" px, p = _canvas() # Cuerpo extintor body = QPainterPath() body.addRoundedRect(QRectF(7, 9, 10, 13), 4, 4) _fill(p, body, _C_FIRE, _OUT, 1.6) # Cuello + boquilla p.setPen(_pen(_OUT, 1.8)) p.drawLine(QPointF(12, 9), QPointF(12, 6)) p.drawLine(QPointF(9, 6), QPointF(15, 6)) p.drawLine(QPointF(15, 6), QPointF(19, 4)) # Chorro (cyan) p.setPen(_pen(_C_WATER, 2.0)) p.drawLine(QPointF(19, 4), QPointF(22, 2)) p.drawLine(QPointF(19, 4), QPointF(21, 6)) # Cruz blanca en el cuerpo p.setPen(_pen(_C_WHITE, 1.8)) p.drawLine(QPointF(12, 12), QPointF(12, 19)) p.drawLine(QPointF(9, 15), QPointF(15, 15)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # SISTEMAS — Routing 3D # ═══════════════════════════════════════════════════════════════════════════════ def _ico_pipes(): """Tuberías 3D — tubería con codo 90°.""" px, p = _canvas() # Tubería horizontal (gris doble línea) p.setPen(_pen(_C_STRUCT, 4.0)) p.drawLine(QPointF(2, 13), QPointF(13, 13)) # Codo y tubería vertical arc = QPainterPath() arc.moveTo(13, 13); arc.quadTo(18, 13, 18, 8) p.setPen(_pen(_C_STRUCT, 4.0)) p.drawPath(arc) p.drawLine(QPointF(18, 8), QPointF(18, 2)) # Bridas (amarillo) p.setPen(_pen(_C_ELEC, 2.4)) p.drawLine(QPointF(2, 11), QPointF(2, 15)) p.drawLine(QPointF(18, 2), QPointF(18, 4)) return _finish(p, px) def _ico_cables(): """Cableados 3D — cable ondulante amarillo.""" px, p = _canvas() p.setPen(_pen(_C_ELEC, 2.8)) cable = QPainterPath() cable.moveTo(2, 12) cable.cubicTo(5, 5, 9, 19, 13, 12) cable.cubicTo(16, 6, 19, 15, 22, 10) p.drawPath(cable) # Terminales (negro) p.setPen(_pen(_OUT, 1.4)) p.setBrush(QBrush(_C_STRUCT)) p.drawEllipse(QRectF(0, 10, 4, 4)) p.drawEllipse(QRectF(20, 8, 4, 4)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # SISTEMAS — Clima / Control # ═══════════════════════════════════════════════════════════════════════════════ def _ico_hvac(): """HVAC — ventilador cyan con 4 aspas.""" px, p = _canvas() # Carcasa p.setPen(_pen(_OUT, 1.6)) p.setBrush(QBrush(QColor("#d8f4f8"))) p.drawEllipse(QRectF(2, 2, 20, 20)) p.setBrush(Qt.BrushStyle.NoBrush) # Aspas cx, cy = 12.0, 12.0 p.setPen(_pen(_C_HYDRO, 1.8)) for i in range(4): a = math.radians(i * 90) r1, r2 = 2.5, 7.5 blade = QPainterPath() blade.moveTo(cx + r1*math.cos(a), cy + r1*math.sin(a)) blade.quadTo( cx + r2*0.7*math.cos(a+0.4) + math.sin(a)*4, cy + r2*0.7*math.sin(a+0.4) - math.cos(a)*4, cx + r2*math.cos(a+0.7), cy + r2*math.sin(a+0.7) ) p.drawPath(blade) # Eje central p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(_C_HYDRO)) p.drawEllipse(QRectF(9, 9, 6, 6)) return _finish(p, px) def _ico_steering(): """Gobierno — rueda de gobierno de 6 radios.""" px, p = _canvas() # Aro exterior p.setPen(_pen(_OUT, 1.8)) p.setBrush(QBrush(QColor("#d0d8e8"))) p.drawEllipse(QRectF(2, 2, 20, 20)) p.setBrush(Qt.BrushStyle.NoBrush) # 6 radios cx, cy = 12.0, 12.0 p.setPen(_pen(_C_STRUCT, 2.0)) for i in range(6): a = math.radians(i * 60) p.drawLine( QPointF(cx + 3.5*math.cos(a), cy + 3.5*math.sin(a)), QPointF(cx + 9*math.cos(a), cy + 9*math.sin(a)) ) # Cubo central p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(_C_STRUCT)) p.drawEllipse(QRectF(8.5, 8.5, 7, 7)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # FABRICACIÓN — CNC # ═══════════════════════════════════════════════════════════════════════════════ def _ico_materials(): """Materiales — capas de material apiladas en colores.""" px, p = _canvas() layers = [ (3, 4, _C_HULL), (3, 8, _C_HYDRO), (3, 12, _C_SMOOTH), (3, 16, _C_RES), (3, 20, _C_FAB), ] for x, y, col in layers: layer = _rect_path(x, y, 18, 3, 1) _fill(p, layer, col, _OUT, 1.0) return _finish(p, px) def _ico_nesting(): """Nesting — piezas de colores encajadas en plancha.""" px, p = _canvas() board = _rect_path(1, 1, 22, 22, 2) _fill(p, board, QColor("#e8e8f0"), _OUT, 1.4) pieces = [ (2,2,10,6, _C_HULL), (13,2,9,7, _C_SMOOTH), (2,9,7,13, _C_RES), (10,9,13,13, _C_FAB), (2,17,20,5, _C_HYDRO), ] for x,y,w,h,col in pieces: r = _rect_path(x, y, w, h, 1) _fill(p, r, col, _OUT, 1.0) return _finish(p, px) def _ico_gcode(): """G-code — pantalla de terminal con instrucciones.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#0a1020"), _OUT, 1.4) f = QFont("Courier New", 5, QFont.Weight.Bold) p.setFont(f) for y, text, col in [(6,"G00 X10", _C_SMOOTH), (11,"G01 Z-5", _C_MINT), (16,"M03 S800", _C_ELEC), (21,"M30", QColor("#a0b0c8"))]: p.setPen(_pen(col, 1.0)) p.drawText(QRectF(2, y-4, 20, 6), Qt.AlignmentFlag.AlignLeft, text) return _finish(p, px) def _ico_postproc(): """Post-Procesador — rueda dentada gris.""" px, p = _canvas() cx, cy = 12.0, 12.0 n_teeth = 8 path = QPainterPath() for i in range(n_teeth * 2): a = math.radians(i * 180 / n_teeth) r = 9.0 if i % 2 == 0 else 6.5 x = cx + r * math.cos(a) y = cy + r * math.sin(a) if i == 0: path.moveTo(x, y) else: path.lineTo(x, y) path.closeSubpath() _fill(p, path, _C_STRUCT, _OUT, 1.4) # Hueco central p.setCompositionMode(QPainter.CompositionMode.CompositionMode_Clear) p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(Qt.GlobalColor.transparent)) p.drawEllipse(QRectF(8, 8, 8, 8)) p.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver) p.setPen(_pen(_OUT, 1.4)) p.setBrush(Qt.BrushStyle.NoBrush) p.drawEllipse(QRectF(8, 8, 8, 8)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # FABRICACIÓN — Moldes FRP # ═══════════════════════════════════════════════════════════════════════════════ def _ico_lofting(): """Lofting — cuadernas de molde con curvas longitudinales.""" px, p = _canvas() # Cuadernas (secciones) p.setPen(_pen(_C_FAB, 1.8)) for x, yt in [(4, 11), (9, 7), (15, 7), (20, 11)]: p.drawLine(QPointF(x, yt), QPointF(x, 21)) p.drawLine(QPointF(x-2, 21), QPointF(x+2, 21)) # Línea de cubierta (curva longitudinal) p.setPen(_pen(_C_RES, 2.2)) top = QPainterPath() top.moveTo(4, 11); top.cubicTo(7, 6, 17, 6, 20, 11) p.drawPath(top) # Línea de quilla p.setPen(_pen(_C_HYDRO, 2.0)) p.drawLine(QPointF(4, 21), QPointF(20, 21)) return _finish(p, px) def _ico_laminate(): """Laminado — capas de fibra en ángulos distintos.""" px, p = _canvas() layers = [(0, _C_FAB), (45, _C_HULL), (-45, _C_SMOOTH), (90, _C_ELEC)] for i, (angle, col) in enumerate(layers): y = 4 + i * 5 p.setPen(QPen(col, 3.5)) a = math.radians(angle) ca, sa = math.cos(a), math.sin(a) for x0 in range(4, 22, 4): p.drawLine( QPointF(x0 - 3*ca, y - 3*sa), QPointF(x0 + 3*ca, y + 3*sa) ) return _finish(p, px) def _ico_resin(): """Resina — cubo con gota cayendo.""" px, p = _canvas() # Cubo/contenedor bucket = _rect_path(4, 13, 16, 10, 2) _fill(p, bucket, _C_HYDRO, _OUT, 1.6) # Asa p.setPen(_pen(_OUT, 1.8)) arc = QPainterPath() arc.moveTo(6, 13); arc.quadTo(6, 7, 12, 7); arc.quadTo(18, 7, 18, 13) p.drawPath(arc) # Gota cayendo (cyan) drop = QPainterPath() drop.moveTo(12, 3) drop.cubicTo(15, 6, 15, 10, 12, 11) drop.cubicTo(9, 10, 9, 6, 12, 3) _fill(p, drop, _C_WATER, _OUT, 1.4) return _finish(p, px) def _ico_bom(): """BOM de materiales — lista estructurada de colores.""" px, p = _canvas() bg = _rect_path(1, 1, 22, 22, 3) _fill(p, bg, QColor("#f0f0f8"), _OUT, 1.2) rows = [(5, _C_HULL), (9, _C_HYDRO), (13, _C_SMOOTH), (17, _C_RES), (21, _C_FAB)] for y, col in rows: p.setPen(Qt.PenStyle.NoPen) p.setBrush(QBrush(col)) p.drawEllipse(QRectF(3, y-2.5, 5, 5)) p.setPen(_pen(_OUT, 1.4)) p.drawLine(QPointF(10, y), QPointF(21, y)) return _finish(p, px) # ═══════════════════════════════════════════════════════════════════════════════ # Registro y función pública # ═══════════════════════════════════════════════════════════════════════════════ _REGISTRY: dict[str, Callable[[], QIcon]] = { "lines_plan": _ico_lines_plan, "4views": _ico_4views, "wizard": _ico_wizard, "hull_nurbs": _ico_hull_nurbs, "appendage": _ico_appendage, "ctrl_pts": _ico_ctrl_pts, "extrude": _ico_extrude, "mirror": _ico_mirror, "lackenby": _ico_lackenby, "import_offsets": _ico_import_offsets, "import_dxf": _ico_import_dxf, "export_iges": _ico_export_iges, "export_step": _ico_export_step, "export_dxf": _ico_export_dxf, "smooth": _ico_smooth, "combs": _ico_combs, "fairness": _ico_fairness, "hydro_calc": _ico_hydro_calc, "hydro_curves": _ico_hydro_curves, "export_csv": _ico_export_csv, "gz_curve": _ico_gz_curve, "imo": _ico_imo, "damage": _ico_damage, "holtrop": _ico_holtrop, "savitsky": _ico_savitsky, "vpp": _ico_vpp, "stf": _ico_stf, "spectrum": _ico_spectrum, "iso12215": _ico_iso12215, "new_tank": _ico_new_tank, "model_tank": _ico_model_tank, "load_case": _ico_load_case, "sounding": _ico_sounding, "calc_kg": _ico_calc_kg, "epla": _ico_epla, "fuel": _ico_fuel, "freshwater": _ico_freshwater, "bilge": _ico_bilge, "firefight": _ico_firefight, "pipes": _ico_pipes, "cables": _ico_cables, "hvac": _ico_hvac, "steering": _ico_steering, "materials": _ico_materials, "nesting": _ico_nesting, "gcode": _ico_gcode, "postproc": _ico_postproc, "lofting": _ico_lofting, "laminate": _ico_laminate, "resin": _ico_resin, "bom": _ico_bom, } _CACHE: dict[str, QIcon] = {} def icon(name: str) -> QIcon: """Devuelve el QIcon para la clave *name* (con caché). Si la clave no existe devuelve QIcon() vacío en lugar de lanzar excepción. """ if name not in _CACHE: fn = _REGISTRY.get(name) _CACHE[name] = fn() if fn is not None else QIcon() return _CACHE[name]