216 lines
8.3 KiB
Python
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() |