diff --git a/plugins/layout_replicator/__init__.py b/plugins/layout_replicator/__init__.py index 9240046..a248160 100644 --- a/plugins/layout_replicator/__init__.py +++ b/plugins/layout_replicator/__init__.py @@ -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] + if parts: + return '/' + '/'.join(parts[:-1]) + '/' + return None + + +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() - parts = [p for p in path.split('/') if p] - return parts[-1] if parts else '' + return path[len(sheet_prefix):] -def build_uuid_map(footprints): - return {local_uuid(fp): fp for fp in footprints} +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): - source_fps = get_sheet_fps(board, source_sheet) - target_fps = get_sheet_fps(board, target_sheet) +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) - target_map = build_uuid_map(target_fps) - ref_uuid = local_uuid(ref_fp) + if src_prefix is None or tgt_prefix is None: + raise ValueError('Could not determine sheet path prefix') - target_ref = target_map.get(ref_uuid) + 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_key_map(target_fps, tgt_prefix) + ref_key = local_key(ref_fp, src_prefix) + + # 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())