Add observable AppConfig (grid/snap/arc-on-crossing/animation/theme), pure-geometry orthogonal router with grid-snapped bends and perpendicular crossing detection for arc hops, and a mock flow-distribution solver that pushes supply to demand along shortest paths and records signed per-edge flow and direction. Covered by routing and flow-solver tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
"""Application configuration (grid, snapping, routing, animation, theming)."""
|
|
from __future__ import annotations
|
|
|
|
from PyQt6.QtCore import QObject, pyqtSignal
|
|
|
|
|
|
class AppConfig(QObject):
|
|
"""Live, observable editor configuration.
|
|
|
|
Emitting :attr:`changed` lets the canvas and items refresh when the user
|
|
tweaks a setting in the configuration dialog.
|
|
"""
|
|
|
|
changed = pyqtSignal()
|
|
|
|
def __init__(self, parent: QObject | None = None) -> None:
|
|
super().__init__(parent)
|
|
self.grid_size = 20
|
|
self.show_grid = True
|
|
self.snap_to_grid = True
|
|
# Draw a small arc "hop" where an edge crosses another edge.
|
|
self.arc_on_crossing = True
|
|
self.arc_radius = 5.0
|
|
# Routing stub length from a port before turning.
|
|
self.route_stub = 20
|
|
# Flow animation.
|
|
self.animation_speed = 1.0 # multiplier
|
|
self.color_scheme = "default"
|
|
# Canvas theme.
|
|
self.background = "#fafafa"
|
|
self.grid_color = "#e0e0e0"
|
|
|
|
def snap(self, value: float) -> float:
|
|
if not self.snap_to_grid or self.grid_size <= 0:
|
|
return value
|
|
g = self.grid_size
|
|
return round(value / g) * g
|
|
|
|
def snap_point(self, x: float, y: float) -> tuple[float, float]:
|
|
return self.snap(x), self.snap(y)
|
|
|
|
def emit_changed(self) -> None:
|
|
self.changed.emit()
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"grid_size": self.grid_size,
|
|
"show_grid": self.show_grid,
|
|
"snap_to_grid": self.snap_to_grid,
|
|
"arc_on_crossing": self.arc_on_crossing,
|
|
"arc_radius": self.arc_radius,
|
|
"route_stub": self.route_stub,
|
|
"animation_speed": self.animation_speed,
|
|
"color_scheme": self.color_scheme,
|
|
"background": self.background,
|
|
"grid_color": self.grid_color,
|
|
}
|
|
|
|
def update_from(self, data: dict) -> None:
|
|
for k, v in data.items():
|
|
if hasattr(self, k):
|
|
setattr(self, k, v)
|
|
self.changed.emit()
|