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):
|
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]
|
return [fp for fp in board.GetFootprints() if fp.GetSheetname() == sheet_name]
|
||||||
|
|
||||||
|
|
||||||
def local_uuid(fp):
|
def get_sheet_prefix(board, sheet_name):
|
||||||
"""Last segment of the hierarchical path — same across all instances of a sheet."""
|
"""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()
|
path = fp.GetPath().AsString()
|
||||||
parts = [p for p in path.split('/') if p]
|
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):
|
def get_sheet_fps_hierarchical(board, sheet_prefix):
|
||||||
return {local_uuid(fp): fp for fp in footprints}
|
"""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
|
# 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)
|
source_fps = get_sheet_fps(board, source_sheet)
|
||||||
target_fps = get_sheet_fps(board, target_sheet)
|
target_fps = get_sheet_fps(board, target_sheet)
|
||||||
|
|
||||||
target_map = build_uuid_map(target_fps)
|
target_map = build_key_map(target_fps, tgt_prefix)
|
||||||
ref_uuid = local_uuid(ref_fp)
|
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:
|
if target_ref is None:
|
||||||
raise ValueError('Could not find matching reference component in target sheet')
|
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
|
count = 0
|
||||||
for src_fp in source_fps:
|
for src_fp in source_fps:
|
||||||
uuid = local_uuid(src_fp)
|
key = local_key(src_fp, src_prefix)
|
||||||
if uuid == ref_uuid:
|
if key == ref_key:
|
||||||
continue
|
continue
|
||||||
tgt_fp = target_map.get(uuid)
|
tgt_fp = target_map.get(key)
|
||||||
if tgt_fp is None:
|
if tgt_fp is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -115,6 +147,9 @@ class Layout_Replicator_Dialog(wx.Dialog):
|
|||||||
|
|
||||||
sizer.Add(grid, 0, wx.EXPAND | wx.ALL, 8)
|
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
|
# Reference component list
|
||||||
sizer.Add(
|
sizer.Add(
|
||||||
wx.StaticText(self, label='Reference component (from source sheet):'),
|
wx.StaticText(self, label='Reference component (from source sheet):'),
|
||||||
@@ -186,9 +221,10 @@ class Layout_Replicator_Dialog(wx.Dialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
ref_fp = self._ref_fps[ref_idx]
|
ref_fp = self._ref_fps[ref_idx]
|
||||||
|
hierarchical = self.hierarchical_cb.IsChecked()
|
||||||
|
|
||||||
try:
|
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.')
|
self.status.SetLabel(f'Done — {count} component(s) placed.')
|
||||||
except Exception:
|
except Exception:
|
||||||
self._show_error(traceback.format_exc())
|
self._show_error(traceback.format_exc())
|
||||||
|
|||||||
Reference in New Issue
Block a user