"""Port relations (media/domain a port carries) and their color schemes. A relation like ``water`` or ``power`` defines both a palette and the set of relations it may connect to. Ports may only be wired together when their relations are mutually compatible. """ from __future__ import annotations from typing import Iterable, Iterator, Optional class PortRelation: """A connectable domain, e.g. water / power / gas. Attributes: key: stable identifier. label: display name. color: base ``#rrggbb`` color used for ports and edges of this relation. compatible: set of relation keys this one may connect to. A relation is always compatible with itself; ``compatible`` lists *additional* keys. """ __slots__ = ("key", "label", "color", "compatible") def __init__( self, key: str, label: str, color: str, compatible: Optional[Iterable[str]] = None, ) -> None: self.key = key self.label = label self.color = color self.compatible = set(compatible or []) def can_connect(self, other: "PortRelation") -> bool: if other.key == self.key: return True return other.key in self.compatible or self.key in other.compatible def to_dict(self) -> dict: return { "key": self.key, "label": self.label, "color": self.color, "compatible": sorted(self.compatible), } @classmethod def from_dict(cls, data: dict) -> "PortRelation": return cls(data["key"], data.get("label", data["key"]), data.get("color", "#888888"), data.get("compatible")) class RelationRegistry: """Registry of relations plus named color schemes. A *color scheme* remaps relation base colors, letting the whole diagram be re-themed (e.g. light vs. dark, or a high-contrast palette) without touching per-item data. """ def __init__(self, relations: Optional[Iterable[PortRelation]] = None) -> None: self._relations: dict[str, PortRelation] = {} for r in relations or []: self.register(r) # scheme name -> {relation_key: color} self._schemes: dict[str, dict[str, str]] = {} self.active_scheme = "default" def register(self, relation: PortRelation) -> PortRelation: self._relations[relation.key] = relation return relation def get(self, key: str) -> Optional[PortRelation]: return self._relations.get(key) def __iter__(self) -> Iterator[PortRelation]: return iter(self._relations.values()) def can_connect(self, key_a: str, key_b: str) -> bool: a, b = self._relations.get(key_a), self._relations.get(key_b) if a is None or b is None: return False return a.can_connect(b) # -- color schemes ---------------------------------------------------- def add_scheme(self, name: str, colors: dict[str, str]) -> None: self._schemes[name] = dict(colors) def scheme_names(self) -> list[str]: return ["default", *[n for n in self._schemes if n != "default"]] def set_active_scheme(self, name: str) -> None: self.active_scheme = name def color(self, relation_key: str) -> str: """Resolve the color for a relation under the active scheme.""" scheme = self._schemes.get(self.active_scheme, {}) if relation_key in scheme: return scheme[relation_key] rel = self._relations.get(relation_key) return rel.color if rel else "#888888" def default_registry() -> RelationRegistry: """Starter relations covering common utility networks.""" reg = RelationRegistry([ PortRelation("water", "Water", "#1e88e5"), PortRelation("hot_water", "Hot Water", "#e53935", compatible=["water"]), PortRelation("gas", "Gas", "#fdd835"), PortRelation("power", "Power", "#43a047"), PortRelation("signal", "Signal", "#8e24aa"), PortRelation("sewage", "Sewage", "#6d4c41"), ]) # A high-contrast alternative scheme. reg.add_scheme("high_contrast", { "water": "#0000ff", "hot_water": "#ff0000", "gas": "#ffcc00", "power": "#00cc00", "signal": "#cc00cc", "sewage": "#663300", }) # A muted / dark-friendly scheme. reg.add_scheme("muted", { "water": "#6fa8dc", "hot_water": "#e06666", "gas": "#ffd966", "power": "#93c47d", "signal": "#b4a7d6", "sewage": "#a67c52", }) return reg