Files
AR-Shipdesign/arshipdesign/ui/icons.py
T

1287 lines
48 KiB
Python

"""
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]