Add hierarchical mode to layout replicator
A checkbox enables hierarchical operation: when checked, components in sub-sheets are included in the replication. Matching uses the full local path (relative to the sheet prefix) instead of just the last UUID, so sub-sheet components match correctly across instances. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,32 +19,64 @@ def get_sheets(board):
|
||||
|
||||
|
||||
def get_sheet_fps(board, sheet_name):
|
||||
"""Direct children of sheet_name only."""
|
||||
return [fp for fp in board.GetFootprints() if fp.GetSheetname() == sheet_name]
|
||||
|
||||
|
||||
def local_uuid(fp):
|
||||
"""Last segment of the hierarchical path — same across all instances of a sheet."""
|
||||
def get_sheet_prefix(board, sheet_name):
|
||||
"""Return the path prefix for sheet_name, e.g. '/uuid_sheet/'.
|
||||
Determined from any direct child component of that sheet."""
|
||||
for fp in board.GetFootprints():
|
||||
if fp.GetSheetname() == sheet_name:
|
||||
path = fp.GetPath().AsString()
|
||||
parts = [p for p in path.split('/') if p]
|
||||
return parts[-1] if parts else ''
|
||||
if parts:
|
||||
return '/' + '/'.join(parts[:-1]) + '/'
|
||||
return None
|
||||
|
||||
|
||||
def build_uuid_map(footprints):
|
||||
return {local_uuid(fp): fp for fp in footprints}
|
||||
def get_sheet_fps_hierarchical(board, sheet_prefix):
|
||||
"""All components whose path starts with sheet_prefix (includes sub-sheets)."""
|
||||
return [fp for fp in board.GetFootprints()
|
||||
if fp.GetPath().AsString().startswith(sheet_prefix)]
|
||||
|
||||
|
||||
def local_key(fp, sheet_prefix):
|
||||
"""Path relative to the sheet prefix — used to match components across instances.
|
||||
For direct children: 'uuid_sym'.
|
||||
For sub-sheet children: 'uuid_subsheet/uuid_sym'."""
|
||||
path = fp.GetPath().AsString()
|
||||
return path[len(sheet_prefix):]
|
||||
|
||||
|
||||
def build_key_map(footprints, sheet_prefix):
|
||||
return {local_key(fp, sheet_prefix): fp for fp in footprints}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Transform
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def apply_replication(board, source_sheet, target_sheet, ref_fp):
|
||||
def apply_replication(board, source_sheet, target_sheet, ref_fp, hierarchical=False):
|
||||
src_prefix = get_sheet_prefix(board, source_sheet)
|
||||
tgt_prefix = get_sheet_prefix(board, target_sheet)
|
||||
|
||||
if src_prefix is None or tgt_prefix is None:
|
||||
raise ValueError('Could not determine sheet path prefix')
|
||||
|
||||
if hierarchical:
|
||||
source_fps = get_sheet_fps_hierarchical(board, src_prefix)
|
||||
target_fps = get_sheet_fps_hierarchical(board, tgt_prefix)
|
||||
else:
|
||||
source_fps = get_sheet_fps(board, source_sheet)
|
||||
target_fps = get_sheet_fps(board, target_sheet)
|
||||
|
||||
target_map = build_uuid_map(target_fps)
|
||||
ref_uuid = local_uuid(ref_fp)
|
||||
target_map = build_key_map(target_fps, tgt_prefix)
|
||||
ref_key = local_key(ref_fp, src_prefix)
|
||||
|
||||
target_ref = target_map.get(ref_uuid)
|
||||
# ref_fp is from the source sheet, so its key uses src_prefix.
|
||||
# Find the matching component in target using tgt_prefix.
|
||||
target_ref = target_map.get(ref_key)
|
||||
if target_ref is None:
|
||||
raise ValueError('Could not find matching reference component in target sheet')
|
||||
|
||||
@@ -58,10 +90,10 @@ def apply_replication(board, source_sheet, target_sheet, ref_fp):
|
||||
|
||||
count = 0
|
||||
for src_fp in source_fps:
|
||||
uuid = local_uuid(src_fp)
|
||||
if uuid == ref_uuid:
|
||||
key = local_key(src_fp, src_prefix)
|
||||
if key == ref_key:
|
||||
continue
|
||||
tgt_fp = target_map.get(uuid)
|
||||
tgt_fp = target_map.get(key)
|
||||
if tgt_fp is None:
|
||||
continue
|
||||
|
||||
@@ -115,6 +147,9 @@ class Layout_Replicator_Dialog(wx.Dialog):
|
||||
|
||||
sizer.Add(grid, 0, wx.EXPAND | wx.ALL, 8)
|
||||
|
||||
self.hierarchical_cb = wx.CheckBox(self, label='Include sub-sheets (hierarchical)')
|
||||
sizer.Add(self.hierarchical_cb, 0, wx.LEFT | wx.BOTTOM, 8)
|
||||
|
||||
# Reference component list
|
||||
sizer.Add(
|
||||
wx.StaticText(self, label='Reference component (from source sheet):'),
|
||||
@@ -186,9 +221,10 @@ class Layout_Replicator_Dialog(wx.Dialog):
|
||||
return
|
||||
|
||||
ref_fp = self._ref_fps[ref_idx]
|
||||
hierarchical = self.hierarchical_cb.IsChecked()
|
||||
|
||||
try:
|
||||
count = apply_replication(self.board, source_sheet, target_sheet, ref_fp)
|
||||
count = apply_replication(self.board, source_sheet, target_sheet, ref_fp, hierarchical)
|
||||
self.status.SetLabel(f'Done — {count} component(s) placed.')
|
||||
except Exception:
|
||||
self._show_error(traceback.format_exc())
|
||||
|
||||
Reference in New Issue
Block a user