158 lines
6.3 KiB
Python
158 lines
6.3 KiB
Python
"""Ventana principal de Plano3D: abrir un plano y mostrar el resultado del motor."""
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
from PySide6.QtCore import QMimeData, Qt
|
||
from PySide6.QtGui import QAction, QDragEnterEvent, QDropEvent, QKeySequence
|
||
from PySide6.QtWidgets import (
|
||
QFileDialog,
|
||
QLabel,
|
||
QListWidget,
|
||
QListWidgetItem,
|
||
QMainWindow,
|
||
QMessageBox,
|
||
QStackedWidget,
|
||
QStyle,
|
||
QVBoxLayout,
|
||
QWidget,
|
||
)
|
||
|
||
from app.procesado import HiloProcesado
|
||
from motor.api import Estado, Gravedad, Resultado
|
||
|
||
EXTENSIONES = {".dxf"} # se ampliará con .dwg y .pdf cuando el motor los admita
|
||
|
||
TEXTO_ESTADO = {
|
||
Estado.OK: "Pieza reconstruida",
|
||
Estado.CON_AVISOS: "Pieza reconstruida con avisos",
|
||
Estado.IMPOSIBLE: "No se ha podido reconstruir la pieza",
|
||
}
|
||
|
||
ICONO_GRAVEDAD = {
|
||
Gravedad.INFO: QStyle.StandardPixmap.SP_MessageBoxInformation,
|
||
Gravedad.AVISO: QStyle.StandardPixmap.SP_MessageBoxWarning,
|
||
Gravedad.BLOQUEANTE: QStyle.StandardPixmap.SP_MessageBoxCritical,
|
||
}
|
||
|
||
|
||
class VentanaPrincipal(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle("Plano3D")
|
||
self.resize(1000, 700)
|
||
self.setAcceptDrops(True)
|
||
|
||
self.resultado: Resultado | None = None
|
||
self.ruta_actual: Path | None = None
|
||
self._hilo: HiloProcesado | None = None
|
||
|
||
self._crear_menu()
|
||
self._crear_contenido()
|
||
self.statusBar().showMessage("Listo")
|
||
|
||
# ------------------------------------------------------------------ construcción
|
||
def _crear_menu(self) -> None:
|
||
self.accion_abrir = QAction("&Abrir plano…", self)
|
||
self.accion_abrir.setShortcut(QKeySequence.StandardKey.Open)
|
||
self.accion_abrir.triggered.connect(self.elegir_plano)
|
||
|
||
accion_salir = QAction("&Salir", self)
|
||
accion_salir.setShortcut(QKeySequence.StandardKey.Quit)
|
||
accion_salir.triggered.connect(self.close)
|
||
|
||
menu_archivo = self.menuBar().addMenu("&Archivo")
|
||
menu_archivo.addAction(self.accion_abrir)
|
||
menu_archivo.addSeparator()
|
||
menu_archivo.addAction(accion_salir)
|
||
|
||
def _crear_contenido(self) -> None:
|
||
# Página 0: indicación inicial. Página 1: resultado del plano.
|
||
self.paginas = QStackedWidget()
|
||
|
||
self.etiqueta_inicio = QLabel("Arrastra un plano DXF a esta ventana\no usa Archivo › Abrir plano (Ctrl+O)")
|
||
self.etiqueta_inicio.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
self.paginas.addWidget(self.etiqueta_inicio)
|
||
|
||
pagina_resultado = QWidget()
|
||
disposicion = QVBoxLayout(pagina_resultado)
|
||
self.etiqueta_archivo = QLabel()
|
||
self.etiqueta_estado = QLabel()
|
||
fuente = self.etiqueta_estado.font()
|
||
fuente.setPointSize(fuente.pointSize() + 2)
|
||
fuente.setBold(True)
|
||
self.etiqueta_estado.setFont(fuente)
|
||
self.lista_avisos = QListWidget()
|
||
self.lista_avisos.setWordWrap(True) # los avisos largos se parten en varias líneas
|
||
self.lista_avisos.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||
disposicion.addWidget(self.etiqueta_archivo)
|
||
disposicion.addWidget(self.etiqueta_estado)
|
||
disposicion.addWidget(QLabel("Informe del plano:"))
|
||
disposicion.addWidget(self.lista_avisos)
|
||
self.paginas.addWidget(pagina_resultado)
|
||
|
||
self.setCentralWidget(self.paginas)
|
||
|
||
# ------------------------------------------------------------------ abrir planos
|
||
def elegir_plano(self) -> None:
|
||
ruta, _ = QFileDialog.getOpenFileName(self, "Abrir plano", "", "Planos DXF (*.dxf)")
|
||
if ruta:
|
||
self.cargar_plano(Path(ruta))
|
||
|
||
def cargar_plano(self, ruta: Path) -> None:
|
||
"""Lanza el procesado de un plano. El resultado llega por _mostrar_resultado."""
|
||
if self._hilo is not None and self._hilo.isRunning():
|
||
return # ya hay un plano en proceso
|
||
if ruta.suffix.lower() not in EXTENSIONES:
|
||
QMessageBox.warning(self, "Formato no admitido",
|
||
f"No se puede abrir «{ruta.name}»: de momento solo se admiten planos DXF.")
|
||
return
|
||
|
||
self.ruta_actual = ruta
|
||
self.accion_abrir.setEnabled(False)
|
||
self.statusBar().showMessage(f"Procesando {ruta.name}…")
|
||
self._hilo = HiloProcesado(ruta, parent=self)
|
||
self._hilo.terminado.connect(self._mostrar_resultado)
|
||
self._hilo.fallido.connect(self._mostrar_error)
|
||
self._hilo.finished.connect(lambda: self.accion_abrir.setEnabled(True))
|
||
self._hilo.start()
|
||
|
||
# Arrastrar y soltar: se acepta solo si se arrastra un único archivo con extensión admitida.
|
||
def dragEnterEvent(self, evento: QDragEnterEvent) -> None:
|
||
if self._ruta_arrastrada(evento.mimeData()) is not None:
|
||
evento.acceptProposedAction()
|
||
|
||
def dropEvent(self, evento: QDropEvent) -> None:
|
||
ruta = self._ruta_arrastrada(evento.mimeData())
|
||
if ruta is not None:
|
||
self.cargar_plano(ruta)
|
||
|
||
@staticmethod
|
||
def _ruta_arrastrada(datos: QMimeData) -> Path | None:
|
||
urls = datos.urls()
|
||
if len(urls) != 1 or not urls[0].isLocalFile():
|
||
return None
|
||
ruta = Path(urls[0].toLocalFile())
|
||
return ruta if ruta.suffix.lower() in EXTENSIONES else None
|
||
|
||
# ------------------------------------------------------------------ resultado
|
||
def _mostrar_resultado(self, resultado: Resultado) -> None:
|
||
self.resultado = resultado
|
||
self.etiqueta_archivo.setText(f"Plano: {self.ruta_actual.name} · Escala aplicada: ×{resultado.escala:g}")
|
||
self.etiqueta_estado.setText(TEXTO_ESTADO[resultado.estado])
|
||
|
||
self.lista_avisos.clear()
|
||
for aviso in resultado.avisos:
|
||
elemento = QListWidgetItem(self.style().standardIcon(ICONO_GRAVEDAD[aviso.gravedad]), aviso.mensaje)
|
||
elemento.setToolTip(aviso.codigo)
|
||
self.lista_avisos.addItem(elemento)
|
||
if not resultado.avisos:
|
||
self.lista_avisos.addItem("Sin observaciones.")
|
||
|
||
self.paginas.setCurrentIndex(1)
|
||
self.statusBar().showMessage("Procesado terminado", 5000)
|
||
|
||
def _mostrar_error(self, mensaje: str) -> None:
|
||
self.statusBar().showMessage("Error al procesar el plano", 5000)
|
||
QMessageBox.critical(self, "Error al procesar el plano", mensaje)
|