fix: locate placed symbols when (lib_name) precedes (lib_id)
KiCad serialises rescued or locally-customised library entries with an
extra (lib_name "...") child before (lib_id "..."):
(symbol
(lib_name "RESISTOR_0603_4")
(lib_id "MF_Passives:RESISTOR_0603")
(at 132.08 44.45 90)
...)
The block-matching regex in _handle_get_schematic_component,
_handle_edit_schematic_component, and _handle_delete_schematic_component
required (lib_id IMMEDIATELY after (symbol, so any placed component
using this form was silently invisible to lookup. The user-visible
symptom is "Component '<ref>' not found in schematic" even though the
component is plainly present (and reachable through list / IPC paths).
This bug also affected set/remove_schematic_component_property and the
existing footprint/value/reference rewriting paths in edit, since they
all share the same lookup code.
The parent-position lookup used a similarly-strict regex
((symbol (lib_id "...") (at ...))), which silently fell back to (0,0)
on (lib_name)-first symbols and caused new properties added through
the custom-properties path to anchor at the schematic origin instead
of the parent symbol.
Fix: relax the symbol-block opening pattern to (symbol\s+\( — matching
any opening paren after (symbol — and read the symbol's origin from
the first (at ...) inside the block. Library-definition entries inside
(lib_symbols ...) are still excluded by the existing range check
(they use the (symbol "name" ...) form with a quoted string, not a
paren).
Adds 7 regression tests in TestLibNameBeforeLibIdOrdering using a
real-world (lib_name)-first resistor block, covering get / edit /
set-property / remove-property / delete and verifying that newly
added properties anchor to the symbol origin instead of (0, 0).
This commit is contained in:
@@ -15,11 +15,11 @@ import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from annotations import AnnotationLoader
|
||||
from resources.resource_definitions import RESOURCE_DEFINITIONS, handle_resource_read
|
||||
|
||||
# Import tool schemas, resource definitions, and IPC API annotations
|
||||
from schemas.tool_schemas import TOOL_SCHEMAS
|
||||
from annotations import AnnotationLoader
|
||||
|
||||
_annotation_loader = AnnotationLoader()
|
||||
|
||||
@@ -806,7 +806,15 @@ class KiCADInterface:
|
||||
# line-by-line regex would never match.
|
||||
blocks_to_delete = [] # list of (char_start, char_end) into content
|
||||
search_start = 0
|
||||
pattern = re.compile(r'\(symbol\s+\(lib_id\s+"')
|
||||
# Match the opening of any placed-symbol block. KiCAD may emit the
|
||||
# children of (symbol ...) in any order — most commonly
|
||||
# `(symbol (lib_id "..."))`, but symbols whose library entry has been
|
||||
# rescued / customised carry an additional `(lib_name "...")` first:
|
||||
# `(symbol (lib_name "...") (lib_id "...") ...)`. Matching just
|
||||
# `(symbol\s+(` covers both, and the lib_symbols range check below
|
||||
# still excludes library-definition symbols (which use the
|
||||
# `(symbol "name" ...)` form with a quoted string, not a paren).
|
||||
pattern = re.compile(r"\(symbol\s+\(")
|
||||
while True:
|
||||
m = pattern.search(content, search_start)
|
||||
if not m:
|
||||
@@ -1140,11 +1148,17 @@ class KiCADInterface:
|
||||
self._find_matching_paren(content, lib_sym_pos) if lib_sym_pos >= 0 else -1
|
||||
)
|
||||
|
||||
# Find placed symbol blocks that match the reference
|
||||
# Search for (symbol (lib_id "...") ... (property "Reference" "<ref>" ...) ...)
|
||||
# Find placed symbol blocks that match the reference. KiCAD may
|
||||
# serialise the children of (symbol ...) in different orders —
|
||||
# `(symbol (lib_id "..."))` is the common case but rescued or
|
||||
# locally-customised symbols carry an extra `(lib_name "...")`
|
||||
# before the lib_id: `(symbol (lib_name "...") (lib_id "..."))`.
|
||||
# Match any opening paren after `(symbol`; the lib_symbols range
|
||||
# check below excludes library-definition symbols, which use the
|
||||
# `(symbol "name" ...)` form (quoted string, not paren).
|
||||
block_start = block_end = None
|
||||
search_start = 0
|
||||
pattern = re.compile(r'\(symbol\s+\(lib_id\s+"')
|
||||
pattern = re.compile(r"\(symbol\s+\(")
|
||||
while True:
|
||||
m = pattern.search(content, search_start)
|
||||
if not m:
|
||||
@@ -1178,8 +1192,12 @@ class KiCADInterface:
|
||||
|
||||
# Determine the parent symbol position so that newly-added properties
|
||||
# default to a sensible location (anchored near the component).
|
||||
# KiCAD always emits the symbol's own (at x y angle) before any
|
||||
# (property ...) child blocks, so the FIRST (at ...) inside the
|
||||
# symbol block is the symbol origin regardless of whether
|
||||
# (lib_name ...) precedes (lib_id ...).
|
||||
comp_at = re.search(
|
||||
r'\(symbol\s+\(lib_id\s+"[^"]*"\s*\)\s+\(at\s+([\d\.\-]+)\s+([\d\.\-]+)',
|
||||
r"\(at\s+([\d\.\-]+)\s+([\d\.\-]+)",
|
||||
block_text,
|
||||
)
|
||||
comp_origin: Tuple[float, float] = (
|
||||
@@ -1379,10 +1397,17 @@ class KiCADInterface:
|
||||
lib_sym_pos = content.find("(lib_symbols")
|
||||
lib_sym_end = find_matching_paren(content, lib_sym_pos) if lib_sym_pos >= 0 else -1
|
||||
|
||||
# Find the placed symbol block for this reference
|
||||
# Find the placed symbol block for this reference. KiCAD may emit
|
||||
# the children of (symbol ...) in different orders — most commonly
|
||||
# `(symbol (lib_id "..."))`, but symbols whose library entry has
|
||||
# been rescued / customised carry an extra `(lib_name "...")` first
|
||||
# (`(symbol (lib_name "...") (lib_id "..."))`). Match `(symbol\s+(`
|
||||
# — any opening paren — to handle both. The lib_symbols range check
|
||||
# below excludes library-definition symbols, which use the
|
||||
# `(symbol "name" ...)` form (quoted string, not paren).
|
||||
block_start = block_end = None
|
||||
search_start = 0
|
||||
pattern = re.compile(r'\(symbol\s+\(lib_id\s+"')
|
||||
pattern = re.compile(r"\(symbol\s+\(")
|
||||
while True:
|
||||
m = pattern.search(content, search_start)
|
||||
if not m:
|
||||
@@ -1412,9 +1437,12 @@ class KiCADInterface:
|
||||
|
||||
block_text = content[block_start : block_end + 1]
|
||||
|
||||
# Extract component position: first (at x y angle) in the symbol header line
|
||||
# Extract component position: the first (at x y angle) inside the
|
||||
# symbol block. KiCAD always writes the symbol's own (at) before
|
||||
# any (property ...) child blocks, so the first match is the
|
||||
# symbol origin regardless of the (lib_name)/(lib_id) ordering.
|
||||
comp_at = re.search(
|
||||
r'\(symbol\s+\(lib_id\s+"[^"]*"\s*\)\s+\(at\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s*\)',
|
||||
r"\(at\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s*\)",
|
||||
block_text,
|
||||
)
|
||||
if comp_at:
|
||||
|
||||
Reference in New Issue
Block a user