# backend/manager.py import pcbnew import os import re from .storage import StorageManager from .layer import Layer from .resolvers import find_item_by_uuid, get_item_uuid, resolve_to_trackable_item from .serializer import Serializer class LayerListener(pcbnew.BOARD_LISTENER): def __init__(self, mgr): super().__init__() self.mgr = mgr def OnBoardItemAdded(self, item): if self.mgr.is_restoring: return if self.mgr.active_layer_index is not None: try: layer = self.mgr.layers[self.mgr.active_layer_index] if layer.visible: trackable = resolve_to_trackable_item(item) if trackable: layer.add_item(trackable) except: pass class LayerManager: def __init__(self): self.layers = [] self.active_layer_index = None self.listener = None self.last_save_status = "Init" self.last_save_path = "Session Dir" self.last_error = "None" self.settings = {"delete_mode": "ask"} self.is_restoring = False self.debug_log = [] self.storage = StorageManager() def log(self, msg): self.debug_log.append(str(msg)) print(f"[LayerManager] {msg}") def get_log_text(self): return "\n".join(self.debug_log) def clear_log(self): self.debug_log = [] def activate_listener(self): if not self.listener: self.listener = LayerListener(self) pcbnew.GetBoard().AddListener(self.listener) def set_active_layer(self, index): self.active_layer_index = index if index is not None: self.activate_listener() def get_file_path(self): return self.storage.master_file def save(self): if not self.storage.setup_paths(): self.last_error = "Failed to setup storage paths" return for l in self.layers: self.storage.save_layer(l.name, l.to_dict()) self.storage.update_master(self.layers) self.last_save_status = "Saved" self.last_save_path = self.storage.session_dir def load(self): if not self.storage.setup_paths(): self.last_save_status = "No Board" return master = self.storage.load_master() if master and "layers" in master: self.layers = [] for l_meta in master["layers"]: l_data = self.storage.load_layer(l_meta["name"]) if l_data: self.layers.append(Layer.from_dict(l_data, self)) self.last_save_status = "Loaded" else: self.last_save_status = "New Session" def create_layer(self, name): l = Layer(name, self) self.layers.append(l) self.save() return l def delete_layer(self, index, delete_items): if 0 <= index < len(self.layers): l = self.layers[index] l.delete_content(True) if delete_items else l.delete_content(False) self.layers.pop(index) self.save() def move_layer_up(self, index): if index > 0 and index < len(self.layers): self.layers[index], self.layers[index-1] = self.layers[index-1], self.layers[index] if self.active_layer_index == index: self.active_layer_index = index - 1 elif self.active_layer_index == index - 1: self.active_layer_index = index self.save() return True return False def move_layer_down(self, index): if index < len(self.layers) - 1 and index >= 0: self.layers[index], self.layers[index+1] = self.layers[index+1], self.layers[index] if self.active_layer_index == index: self.active_layer_index = index + 1 elif self.active_layer_index == index + 1: self.active_layer_index = index self.save() return True return False def analyze_clipboard_text(self, text): self.log(f"Analyze: Input text length: {len(text)}") uuids = set(re.findall(r'\(uuid\s+"([^"]+)"\)', text)) if not uuids: return self.get_log_text() board = pcbnew.GetBoard() matched = 0 def check(item): try: if get_item_uuid(item) in uuids: return 1 except: pass return 0 try: for lst in [board.GetTracks(), board.GetFootprints(), board.GetDrawings(), board.Zones()]: for item in lst: matched += check(item) except: pass self.log(f"Analyze: Total Matches on Board: {matched}") return self.get_log_text() def recover_orphans(self): if not self.storage.setup_paths(): return 0, "Storage not ready" tracked_uuids = set() stored_files = set() board = pcbnew.GetBoard() for l in self.layers: for f in l.stored_items: stored_files.add(f) for entry in l.on_board_items: tracked_uuids.add(entry['uuid']) orphans = [] try: for f in os.listdir(self.storage.session_dir): if f.endswith(".json") and f != "$Master$.json" and f not in stored_files: data = self.storage.load_item(f) if not data or 'uuid' not in data: continue uuid = data['uuid'] if uuid in tracked_uuids or find_item_by_uuid(board, uuid): continue orphans.append(f) except Exception as e: return 0, f"Error scanning dir: {e}" if not orphans: return 0, "No orphans found" rec_layer = self.create_layer("Recovered") rec_layer.stored_items = orphans rec_layer.visible = False self.save() return len(orphans), f"Recovered {len(orphans)} items." def hard_refresh(self): self.log("Hard Refresh: Starting...") board = pcbnew.GetBoard() for layer in self.layers: if layer.visible: valid_on_board = [] for entry in layer.on_board_items: uuid = entry['uuid'] item = find_item_by_uuid(board, uuid) if item: sexpr, err = Serializer.serialize(item) if sexpr: valid_on_board.append(entry) else: self.log(f"Refresh: Item {uuid} missing from visible layer {layer.name}") layer.on_board_items = valid_on_board else: for filename in layer.stored_items: data = self.storage.load_item(filename) if data and 'uuid' in data: uuid = data['uuid'] item = find_item_by_uuid(board, uuid) if item: self.log(f"Refresh: Found ghost {uuid} from hidden layer {layer.name}. Removing.") try: board.Remove(item) except: pass self.save() self.log("Hard Refresh: Complete") manager = LayerManager()