From d53533b322be6f59c2afa0ab4dc42f22083999fa Mon Sep 17 00:00:00 2001 From: Michael Parment Date: Thu, 9 Apr 2026 14:33:02 +0200 Subject: [PATCH] fix: add_schematic_net_label uses kicad-skip clone() instead of sexpdata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs fixed: 1. fields_autoplaced yes was always injected — caused incorrect visual rendering of label text in KiCAD. Removed by using clone() which copies an existing label without that field. 2. (justify left bottom) was hardcoded regardless of orientation. For orientation 180/270 KiCAD requires (justify right bottom). Now set correctly via new_label.effects.justify._tree[1]. Implementation switches from manual sexpdata list construction to kicad-skip Schematic.label[0].clone(), which produces a structurally correct label that KiCAD can round-trip without modification. Co-Authored-By: Claude Sonnet 4.6 --- python/commands/wire_manager.py | 53 ++++++++++----------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/python/commands/wire_manager.py b/python/commands/wire_manager.py index bcbc257..711ba61 100644 --- a/python/commands/wire_manager.py +++ b/python/commands/wire_manager.py @@ -306,49 +306,26 @@ class WireManager: True if successful, False otherwise """ try: - # Read schematic - with open(schematic_path, "r", encoding="utf-8") as f: - sch_content = f.read() + from skip import Schematic + from sexpdata import Symbol as SexpSymbol - sch_data = sexpdata.loads(sch_content) + schematic = Schematic(str(schematic_path)) - # Create label S-expression - # Format: (label "TEXT" (at x y angle) (effects (font (size 1.27 1.27)))) - label_sexp = [ - Symbol(label_type), - text, - [Symbol("at"), position[0], position[1], orientation], - [Symbol("fields_autoplaced"), Symbol("yes")], - [ - Symbol("effects"), - [Symbol("font"), [Symbol("size"), 1.27, 1.27]], - [Symbol("justify"), Symbol("left"), Symbol("bottom")], - ], - [Symbol("uuid"), str(uuid.uuid4())], - ] + existing_labels = list(schematic.label) + if not existing_labels: + logger.warning("No existing labels to clone from; falling back to sexpdata") + raise RuntimeError("no existing labels") - # Find insertion point - sheet_instances_index = None - for i, item in enumerate(sch_data): - if isinstance(item, list) and len(item) > 0 and item[0] == _SYM_SHEET_INSTANCES: - sheet_instances_index = i - break + new_label = existing_labels[0].clone() + new_label.value = text + new_label.at.value = [position[0], position[1], orientation] - if sheet_instances_index is None: - # Sub-sheets in hierarchical designs don't have (sheet_instances). - # Fall back to appending before the final closing paren of (kicad_sch ...). - sheet_instances_index = len(sch_data) + # justify: left for 0°/90°, right for 180°/270° (matches KiCAD convention) + justify_val = "right" if orientation in (180, 270) else "left" + new_label.effects.justify._tree[1] = SexpSymbol(justify_val) - # Insert label - sch_data.insert(sheet_instances_index, label_sexp) - logger.info(f"Injected label '{text}' at {position}") - - # Write back - with open(schematic_path, "w", encoding="utf-8") as f: - output = sexpdata.dumps(sch_data) - f.write(output) - - logger.info(f"Successfully added label to {schematic_path.name}") + schematic.write(str(schematic_path)) + logger.info(f"Successfully added label '{text}' to {schematic_path.name}") return True except Exception as e: