Ilya 58f3c891ee feat(calc,routing): config, orthogonal routing, and mock flow solver
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>
2026-07-02 23:15:35 +02:00

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()