@huaqiu/dsh-kicad 0.4.13 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/lib/index.mjs +5 -0
  2. package/package.json +2 -2
  3. package/skills/hardware-design-brief/SKILL.md +65 -31
  4. package/skills/hardware-design-brief/agents/openai.yaml +3 -3
  5. package/skills/hardware-design-brief/references/design-brief-guide.md +84 -11
  6. package/skills/hardware-design-brief/references/examples/hardware-solution-example.md +442 -0
  7. package/skills/hardware-design-brief/references/hardware-prd-guide.md +53 -10
  8. package/skills/hardware-design-brief/references/mrd-guide.md +45 -10
  9. package/skills/hardware-design-brief/references/multi-board-guide.md +51 -0
  10. package/skills/hardware-design-brief/references/project-controls.md +45 -0
  11. package/skills/hardware-design-brief/references/solution-deliverable.md +55 -0
  12. package/skills/hardware-design-brief/references/stage-boundaries.md +32 -0
  13. package/skills/pcb-initial-placement/SKILL.md +343 -0
  14. package/skills/pcb-initial-placement/agents/openai.yaml +4 -0
  15. package/skills/pcb-initial-placement/references/algorithm.md +317 -0
  16. package/skills/pcb-initial-placement/scripts/apply_via_ipc.py +211 -0
  17. package/skills/pcb-initial-placement/scripts/board_geometry.py +368 -0
  18. package/skills/pcb-initial-placement/scripts/kipy_common.py +80 -0
  19. package/skills/pcb-initial-placement/scripts/pack.py +576 -0
  20. package/skills/pcb-initial-placement/scripts/placement_config.example.json +116 -0
  21. package/skills/pcb-initial-placement/scripts/placement_view.py +340 -0
  22. package/skills/pcb-initial-placement/scripts/pose_table.py +86 -0
  23. package/skills/pcb-initial-placement/scripts/projpaths.py +160 -0
  24. package/skills/pcb-initial-placement/scripts/sexpr.py +180 -0
  25. package/skills/pcb-initial-placement/scripts/suggest_modules.py +420 -0
  26. package/skills/pcb-initial-placement/scripts/verify_placement.py +226 -0
  27. package/skills/pcb-initial-placement/scripts/write_board_file.py +109 -0
  28. package/src/skills.ts +11 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """FALLBACK ONLY: write a placed board as a NEW .kicad_pcb file, offline.
3
+
4
+ python write_board_file.py --board board.kicad_pcb --plan placement_plan.json
5
+ # -> <board dir>/placement/board-placed-preview.kicad_pcb
6
+ # (override with --out / --outdir; the source board is never the target)
7
+
8
+ Use this only when KiCad IPC is genuinely unavailable AND the user has accepted an
9
+ offline file instead - prefer apply_via_ipc.py. Reasons this is the worse path:
10
+ the result is not in the editor's undo stack, KiCad will not reload it by itself,
11
+ and the file stops being the single source of truth the moment someone types in
12
+ the GUI.
13
+
14
+ Safety properties kept anyway:
15
+ * the SOURCE board is never modified - a new file is written
16
+ * only the depth-1 ``(at x y [rot])`` span of each footprint is replaced, so every
17
+ other byte (UUIDs, nets, pad stacks, 3D models, text effects) survives verbatim
18
+ * the rewritten text is re-parsed and every pose is read back before the file is
19
+ written; a mismatch aborts
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+
28
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
29
+ import projpaths # noqa: E402
30
+ import sexpr # noqa: E402
31
+
32
+
33
+ def num(v):
34
+ """Format like KiCad does: 4 decimals, trailing zeros stripped."""
35
+ s = "%.4f" % v
36
+ s = s.rstrip("0").rstrip(".")
37
+ return s if s not in ("", "-0") else "0"
38
+
39
+
40
+ def main() -> None:
41
+ ap = argparse.ArgumentParser(description=__doc__,
42
+ formatter_class=argparse.RawDescriptionHelpFormatter)
43
+ ap.add_argument("--board", required=True, help="source .kicad_pcb (never modified)")
44
+ ap.add_argument("--plan", required=True)
45
+ ap.add_argument("--out", help="new .kicad_pcb to write (default "
46
+ "<placement dir>/<board>-placed-preview.kicad_pcb)")
47
+ projpaths.add_outdir(ap)
48
+ args = ap.parse_args()
49
+
50
+ stem = os.path.splitext(os.path.basename(args.board))[0]
51
+ out_path = projpaths.resolve_out(
52
+ args.out, "%s-placed-preview.kicad_pcb" % stem,
53
+ board=args.board, refs=(args.plan,), outdir=args.outdir)
54
+
55
+ if os.path.abspath(args.board) == os.path.abspath(out_path):
56
+ raise SystemExit("refusing to overwrite the source board; choose another --out")
57
+
58
+ text = open(args.board, encoding="utf-8").read()
59
+ pos = json.load(open(args.plan, encoding="utf-8"))["positions"]
60
+ fps = list(sexpr.root(text).kids("footprint"))
61
+
62
+ edits, seen = [], set()
63
+ for fp in fps:
64
+ ref = sexpr.property_value(fp, "Reference")
65
+ if ref not in pos:
66
+ print("!! no planned position for", ref)
67
+ continue
68
+ seen.add(ref)
69
+ at = fp.first("at") # first *direct* child named "at"
70
+ assert at is not None, ref
71
+ p = pos[ref]
72
+ r = p["rot"] % 360
73
+ new = ("(at %s %s)" % (num(p["x"]), num(p["y"])) if r == 0
74
+ else "(at %s %s %s)" % (num(p["x"]), num(p["y"]), num(r)))
75
+ edits.append((at.start, at.end, new))
76
+
77
+ print("footprints in file :", len(fps))
78
+ print("poses applied :", len(edits))
79
+ print("planned but not found:", sorted(set(pos) - seen))
80
+
81
+ out = text
82
+ for start, end, new in sorted(edits, reverse=True): # back to front
83
+ out = out[:start] + new + out[end:]
84
+
85
+ chk = sexpr.root(out)
86
+ assert len(list(chk.kids("footprint"))) == len(fps), "footprint count changed"
87
+ bad = 0
88
+ for fp in chk.kids("footprint"):
89
+ ref = sexpr.property_value(fp, "Reference")
90
+ a = fp.first("at").nums()
91
+ p = pos[ref]
92
+ if abs(a[0] - p["x"]) > 1e-4 or abs(a[1] - p["y"]) > 1e-4:
93
+ print(" position mismatch", ref, a[:2], p["x"], p["y"])
94
+ bad += 1
95
+ got = round(a[2]) % 360 if len(a) > 2 else 0
96
+ if got != (p["rot"] % 360):
97
+ print(" rotation mismatch", ref, a, p["rot"])
98
+ bad += 1
99
+ print("read-back mismatches :", bad)
100
+ if bad:
101
+ raise SystemExit("not writing: the rewritten text does not read back as planned")
102
+
103
+ open(out_path, "w", encoding="utf-8", newline="").write(out)
104
+ print("wrote %s (%d bytes, source %d)" % (out_path, len(out), len(text)))
105
+ print("reload it in KiCad with File > Open; the original file is untouched")
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()
package/src/skills.ts CHANGED
@@ -76,6 +76,17 @@ export const KICAD_SKILLS: Readonly<Record<string, KicadBundledSkill>> = {
76
76
  'a new board project.',
77
77
  ownsScripts: false,
78
78
  },
79
+ 'pcb-initial-placement': {
80
+ id: 'pcb-initial-placement',
81
+ summary:
82
+ 'Produce a Phase-1 initial placement for a KiCad PCB that has a netlist ' +
83
+ 'but no layout yet: functional clustering, anchor placement, zone ' +
84
+ 'partitioning, overlap-free packing and a handover report; prefer applying ' +
85
+ 'the result to the open board through the KiCad IPC API, falling back to an ' +
86
+ 'offline board file only when IPC is unavailable. Suited to 20–200 ' +
87
+ 'components on a single, mostly single-sided board.',
88
+ ownsScripts: false,
89
+ },
79
90
  }
80
91
 
81
92
  /** The bundled skill ids, in registration order. */