feat: AR-VMS-Seaman initial commit — Python FastAPI + PySide6 (runtime server + desktop studio client)

This commit is contained in:
2026-07-03 12:16:31 -04:00
parent 7390d5cd51
commit 2302e963b2
12 changed files with 1144 additions and 136 deletions
+37 -3
View File
@@ -66,6 +66,7 @@ class MimicEditor(QWidget):
# Almacén in-memory de mímicos por sistema:
# { system_id_value : [SymbolSpec, ...] }
self._mimics: dict[str, list[SymbolSpec]] = {}
self._empty_state_active: bool = True
outer = QVBoxLayout(self)
outer.setContentsMargins(12, 12, 12, 12)
@@ -227,15 +228,46 @@ class MimicEditor(QWidget):
self.mimicChanged.emit()
def _on_palette_double_click(self, item: QListWidgetItem) -> None:
"""Agrega un símbolo nuevo SIN reconstruir la escena.
Si ya hay símbolos arrastrados por el usuario, conservan posición.
El nuevo se coloca en el siguiente slot libre de una grilla 6 × N.
"""
sys = self._current_system()
if sys is None:
return
kind = item.data(Qt.ItemDataRole.UserRole)
new_spec = SymbolSpec(kind=kind, x=100, y=100, label="")
self._mimics.setdefault(sys, []).append(new_spec)
self._render_current()
existing = self._mimics.setdefault(sys, [])
x, y = self._next_free_position(existing)
new_spec = SymbolSpec(kind=kind, x=x, y=y, label="")
existing.append(new_spec)
if self._empty_state_active:
# En empty state hay un label central; render completo para limpiarlo
self._render_current()
else:
# Solo agrega el nuevo item — preserva posiciones arrastradas
new_item = make_symbol(new_spec)
self._scene.addItem(new_item)
new_item.setSelected(True)
self._symbol_count.setText(f"{len(existing)} símbolos")
self.mimicChanged.emit()
def _next_free_position(self, specs: list[SymbolSpec]) -> tuple[float, float]:
"""Devuelve (x, y) en una grilla 6 × N evitando solapamientos."""
col_w, row_h = 110, 90
cols = 6
used = set()
for s in specs:
col = int(round((s.x - 40) / col_w))
row = int(round((s.y - 40) / row_h))
used.add((col, row))
for row in range(30):
for col in range(cols):
if (col, row) not in used:
return 40.0 + col * col_w, 40.0 + row * row_h
return 40.0, 40.0
def _render_current(self) -> None:
sys = self._current_system()
self._scene.clear()
@@ -250,12 +282,14 @@ class MimicEditor(QWidget):
)
self._symbol_count.setText("0 símbolos")
return
self._empty_state_active = False
self._draw_grid()
for spec in specs:
self._scene.addItem(make_symbol(spec))
self._symbol_count.setText(f"{len(specs)} símbolos")
def _draw_empty_state(self, msg: str) -> None:
self._empty_state_active = True
self._draw_grid()
text = self._scene.addText(msg, ui_font(11))
text.setDefaultTextColor(QColor(C_FOG))
+17 -2
View File
@@ -73,22 +73,37 @@ class SymbolSpec:
class _BaseSymbol(QGraphicsItemGroup):
"""Símbolo base. Subclases dibujan en `_build()`."""
"""Símbolo base. Subclases dibujan en `_build()`.
Cuando el usuario arrastra el símbolo, `itemChange` propaga la nueva
posición al `SymbolSpec` para que sobreviva a re-renders del editor.
"""
KIND: ClassVar[str] = "base"
def __init__(self, spec: SymbolSpec) -> None:
super().__init__()
# NOTA: asignar self.spec ANTES de setPos para que itemChange tenga
# acceso al spec cuando Qt dispare ItemPositionHasChanged durante init.
self.spec = spec
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setPos(spec.x, spec.y)
self.setRotation(spec.rotation_deg)
self.spec = spec
self._build()
if spec.label:
self._add_label(spec.label)
def itemChange(self, change, value): # type: ignore[override]
"""Propaga la posición arrastrada al SymbolSpec en vivo."""
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
spec = getattr(self, "spec", None)
if spec is not None:
spec.x = float(self.pos().x())
spec.y = float(self.pos().y())
return super().itemChange(change, value)
def _build(self) -> None:
raise NotImplementedError