@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.
- package/lib/index.mjs +5 -0
- package/package.json +2 -2
- package/skills/hardware-design-brief/SKILL.md +65 -31
- package/skills/hardware-design-brief/agents/openai.yaml +3 -3
- package/skills/hardware-design-brief/references/design-brief-guide.md +84 -11
- package/skills/hardware-design-brief/references/examples/hardware-solution-example.md +442 -0
- package/skills/hardware-design-brief/references/hardware-prd-guide.md +53 -10
- package/skills/hardware-design-brief/references/mrd-guide.md +45 -10
- package/skills/hardware-design-brief/references/multi-board-guide.md +51 -0
- package/skills/hardware-design-brief/references/project-controls.md +45 -0
- package/skills/hardware-design-brief/references/solution-deliverable.md +55 -0
- package/skills/hardware-design-brief/references/stage-boundaries.md +32 -0
- package/skills/pcb-initial-placement/SKILL.md +343 -0
- package/skills/pcb-initial-placement/agents/openai.yaml +4 -0
- package/skills/pcb-initial-placement/references/algorithm.md +317 -0
- package/skills/pcb-initial-placement/scripts/apply_via_ipc.py +211 -0
- package/skills/pcb-initial-placement/scripts/board_geometry.py +368 -0
- package/skills/pcb-initial-placement/scripts/kipy_common.py +80 -0
- package/skills/pcb-initial-placement/scripts/pack.py +576 -0
- package/skills/pcb-initial-placement/scripts/placement_config.example.json +116 -0
- package/skills/pcb-initial-placement/scripts/placement_view.py +340 -0
- package/skills/pcb-initial-placement/scripts/pose_table.py +86 -0
- package/skills/pcb-initial-placement/scripts/projpaths.py +160 -0
- package/skills/pcb-initial-placement/scripts/sexpr.py +180 -0
- package/skills/pcb-initial-placement/scripts/suggest_modules.py +420 -0
- package/skills/pcb-initial-placement/scripts/verify_placement.py +226 -0
- package/skills/pcb-initial-placement/scripts/write_board_file.py +109 -0
- 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. */
|