logical_layers/backend/manager.py

216 lines
8.3 KiB
Python

# 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):
# 1. Record State (The "Wake Up Tape")
# Capture UUID before any refresh invalidates the item pointer or tool state.
target_uuid = get_item_uuid(item)
if not target_uuid: return
# 2. Brush Teeth (Start) - Sync with reality
# This ensures we are facing the actual playing field.
pcbnew.Refresh()
try:
if self.mgr.is_restoring: return
# 3. Re-acquire State (Watch the tape)
# The 'item' pointer passed by the event might be unsafe now.
# We look up the item freshly from the board using the recorded UUID.
board = pcbnew.GetBoard()
fresh_item = find_item_by_uuid(board, target_uuid)
if not fresh_item: return
# 4. Perform Action
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(fresh_item)
if trackable: layer.add_item(trackable)
except: pass
finally:
# 5. Brush Teeth (End) - Finalize
pcbnew.Refresh()
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):
pcbnew.Refresh() # Start
try:
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()
finally:
pcbnew.Refresh() # End
def recover_orphans(self):
pcbnew.Refresh() # Start
try:
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."
finally:
pcbnew.Refresh() # End
def hard_refresh(self):
pcbnew.Refresh() # Start
try:
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")
finally:
pcbnew.Refresh() # End
manager = LayerManager()