Fix two regressions from PR #88 (rotate/mirror)
1. add_schematic_net_label failed on schematics with no existing labels. The clone-based path required a pre-existing label to copy from; the documented "fallback to sexpdata" was a misleading log line — the RuntimeError was caught and the call silently returned False. Restore hand-built sexpdata construction (without the buggy fields_autoplaced token, with orientation-aware justify). 2. get_pin_angle returned the wrong angle for every mirrored symbol (off by exactly 180°, all rotations, both mirror axes). The mirror_x and mirror_y formulas were swapped relative to the pin_world_xy convention — pin_world_xy mirrors a position by flipping its local axis component, so the matching angle transform is (180 - θ) for mirror_x and -θ for mirror_y. Add regression tests: - test_add_label_empty_schematic.py — first label on empty schematic, orientation-aware justify. - test_get_pin_angle.py — full 24-case matrix (4 rotations × 3 mirror states × 2 pins).
This commit is contained in:
@@ -280,12 +280,12 @@ class PinLocator:
|
||||
pin_def_angle = pins[pin_number].get("angle", 0)
|
||||
|
||||
# Mirror flips the angle before applying symbol rotation.
|
||||
# mirror_x negates the Y component → reflects angle across X axis → negate angle.
|
||||
# mirror_y negates the X component → reflects angle across Y axis → 180 - angle.
|
||||
# mirror_x flips the X component of local vectors → reflects across Y axis → 180 - angle.
|
||||
# mirror_y flips the Y component of local vectors → reflects across X axis → negate angle.
|
||||
if mirror_x:
|
||||
pin_def_angle = (-pin_def_angle) % 360
|
||||
if mirror_y:
|
||||
pin_def_angle = (180 - pin_def_angle) % 360
|
||||
if mirror_y:
|
||||
pin_def_angle = (-pin_def_angle) % 360
|
||||
|
||||
absolute_angle = (pin_def_angle + symbol_rotation) % 360
|
||||
return absolute_angle
|
||||
@@ -374,9 +374,13 @@ class PinLocator:
|
||||
from commands.wire_dragger import WireDragger
|
||||
|
||||
abs_x, abs_y = WireDragger.pin_world_xy(
|
||||
pin_data["x"], pin_data["y"],
|
||||
symbol_x, symbol_y,
|
||||
symbol_rotation, mirror_x, mirror_y,
|
||||
pin_data["x"],
|
||||
pin_data["y"],
|
||||
symbol_x,
|
||||
symbol_y,
|
||||
symbol_rotation,
|
||||
mirror_x,
|
||||
mirror_y,
|
||||
)
|
||||
|
||||
logger.info(f"Pin {symbol_reference}/{pin_number} located at ({abs_x}, {abs_y})")
|
||||
|
||||
@@ -306,25 +306,41 @@ class WireManager:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
from skip import Schematic
|
||||
from sexpdata import Symbol as SexpSymbol
|
||||
with open(schematic_path, "r", encoding="utf-8") as f:
|
||||
sch_content = f.read()
|
||||
|
||||
schematic = Schematic(str(schematic_path))
|
||||
sch_data = sexpdata.loads(sch_content)
|
||||
|
||||
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")
|
||||
# Orientation-aware justify: KiCAD flips horizontal alignment for 180°/270°
|
||||
justify_h = Symbol("right") if orientation in (180, 270) else Symbol("left")
|
||||
|
||||
new_label = existing_labels[0].clone()
|
||||
new_label.value = text
|
||||
new_label.at.value = [position[0], position[1], orientation]
|
||||
label_sexp = [
|
||||
Symbol(label_type),
|
||||
text,
|
||||
[Symbol("at"), position[0], position[1], orientation],
|
||||
[
|
||||
Symbol("effects"),
|
||||
[Symbol("font"), [Symbol("size"), 1.27, 1.27]],
|
||||
[Symbol("justify"), justify_h, Symbol("bottom")],
|
||||
],
|
||||
[Symbol("uuid"), str(uuid.uuid4())],
|
||||
]
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
if sheet_instances_index is None:
|
||||
# Sub-sheets in hierarchical designs don't have (sheet_instances).
|
||||
sheet_instances_index = len(sch_data)
|
||||
|
||||
sch_data.insert(sheet_instances_index, label_sexp)
|
||||
|
||||
with open(schematic_path, "w", encoding="utf-8") as f:
|
||||
f.write(sexpdata.dumps(sch_data))
|
||||
|
||||
schematic.write(str(schematic_path))
|
||||
logger.info(f"Successfully added label '{text}' to {schematic_path.name}")
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user