@huaqiu/dsh-hqsch 0.4.8

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/cordis.patch.yml +13 -0
  4. package/lib/index.d.mts +98 -0
  5. package/lib/index.mjs +159 -0
  6. package/package.json +40 -0
  7. package/skills/hqsch-scenario-b/AGENTS.md +209 -0
  8. package/skills/hqsch-scenario-b/CLAUDE.md +209 -0
  9. package/skills/hqsch-scenario-b/INSTALL.md +140 -0
  10. package/skills/hqsch-scenario-b/SKILL.md +107 -0
  11. package/skills/hqsch-scenario-b/SYSTEM-PROMPT.md +209 -0
  12. package/skills/hqsch-scenario-b/docs/circuit-pattern-layout.md +304 -0
  13. package/skills/hqsch-scenario-b/docs/editing-a-circuit.md +167 -0
  14. package/skills/hqsch-scenario-b/docs/part-search.md +244 -0
  15. package/skills/hqsch-scenario-b/docs/placement-conventions.md +714 -0
  16. package/skills/hqsch-scenario-b/docs/property-conventions.md +225 -0
  17. package/skills/hqsch-scenario-b/docs/quickstart.md +99 -0
  18. package/skills/hqsch-scenario-b/docs/reading-a-circuit.md +317 -0
  19. package/skills/hqsch-scenario-b/docs/rpc/canvas-auto-connect-objects-by-id.md +98 -0
  20. package/skills/hqsch-scenario-b/docs/rpc/canvas-delete-objects-by-ids.md +94 -0
  21. package/skills/hqsch-scenario-b/docs/rpc/canvas-find-object-by-property.md +101 -0
  22. package/skills/hqsch-scenario-b/docs/rpc/canvas-list-wire-segments.md +94 -0
  23. package/skills/hqsch-scenario-b/docs/rpc/canvas-mirror-objects-by-ids.md +96 -0
  24. package/skills/hqsch-scenario-b/docs/rpc/canvas-rotate-objects-by-ids.md +94 -0
  25. package/skills/hqsch-scenario-b/docs/rpc/canvas-set-object-property.md +99 -0
  26. package/skills/hqsch-scenario-b/docs/rpc/canvas-zoom-all.md +92 -0
  27. package/skills/hqsch-scenario-b/docs/rpc/find-find-object.md +96 -0
  28. package/skills/hqsch-scenario-b/docs/rpc/kernel-get-snapshot.md +104 -0
  29. package/skills/hqsch-scenario-b/docs/rpc/netlist-get-active-page-net-list.md +87 -0
  30. package/skills/hqsch-scenario-b/docs/rpc/netlist-get-port-pairing.md +34 -0
  31. package/skills/hqsch-scenario-b/docs/rpc/obj-place-place-pin-stub-wire-and-net-alias.md +114 -0
  32. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-apply-circuit-pattern.md +125 -0
  33. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-get-page-occupancy.md +100 -0
  34. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-list-circuit-patterns.md +97 -0
  35. package/skills/hqsch-scenario-b/docs/rpc/placement-list-part-libraries.md +94 -0
  36. package/skills/hqsch-scenario-b/docs/rpc/placement-list-symbol-libraries.md +94 -0
  37. package/skills/hqsch-scenario-b/docs/rpc/placement-place-kicad-symbol.md +105 -0
  38. package/skills/hqsch-scenario-b/docs/rpc/placement-place-part-from-library.md +106 -0
  39. package/skills/hqsch-scenario-b/docs/rpc/placement-place-symbol-from-library.md +108 -0
  40. package/skills/hqsch-scenario-b/docs/rpc/project-get-active-project.md +96 -0
  41. package/skills/hqsch-scenario-b/docs/rpc/project-list-open-projects.md +99 -0
  42. package/skills/hqsch-scenario-b/docs/rpc-availability.md +97 -0
  43. package/skills/hqsch-scenario-b/docs/rpc-index/canvas.md +81 -0
  44. package/skills/hqsch-scenario-b/docs/rpc-index/find.md +36 -0
  45. package/skills/hqsch-scenario-b/docs/rpc-index/kernel.md +36 -0
  46. package/skills/hqsch-scenario-b/docs/rpc-index/netlist.md +38 -0
  47. package/skills/hqsch-scenario-b/docs/rpc-index/obj-place.md +76 -0
  48. package/skills/hqsch-scenario-b/docs/rpc-index/pattern-layout.md +38 -0
  49. package/skills/hqsch-scenario-b/docs/rpc-index/placement.md +48 -0
  50. package/skills/hqsch-scenario-b/docs/rpc-index/project.md +47 -0
  51. package/skills/hqsch-scenario-b/docs/serialization.md +60 -0
  52. package/skills/hqsch-scenario-b/docs/skill-dependencies.md +93 -0
  53. package/skills/hqsch-scenario-b/skill.json +13 -0
  54. package/skills/hqsch-scenario-b/template/package.json +16 -0
  55. package/skills/hqsch-scenario-b/template/scripts/edit-property-by-ref.ts +44 -0
  56. package/skills/hqsch-scenario-b/template/scripts/fix-designators.ts +52 -0
  57. package/skills/hqsch-scenario-b/template/scripts/hello.ts +46 -0
  58. package/skills/hqsch-scenario-b/template/scripts/read-circuit.ts +357 -0
  59. package/skills/hqsch-scenario-b/template/scripts/replace-part-by-ref.ts +44 -0
  60. package/skills/hqsch-scenario-b/template/scripts/rewire-pin-by-ref.ts +42 -0
  61. package/skills/hqsch-scenario-b/template/tsconfig.json +11 -0
  62. package/src/config.ts +25 -0
  63. package/src/index.ts +186 -0
  64. package/src/paths.ts +48 -0
@@ -0,0 +1,714 @@
1
+ # Placement Conventions (HQSCH)
2
+
3
+ ## Coordinate Convention
4
+
5
+ All three library-placement RPCs share the same **external display coordinates**
6
+ for `local_pos`:
7
+
8
+ | Space | Origin | Y direction | Range |
9
+ | --- | --- | --- | --- |
10
+ | **User / Agent** (`local_pos`) | Page top-left | Downward ↑ Y | **0 ~ page display height** (varies by A4/Letter/custom size) |
11
+ | **Internal canvas** | Bottom-left | Upward | Page top at `PAGE_SIZE_START_Y` (typically **1400**) |
12
+
13
+ **Important:** `1400` is the **canvas Y anchor of the page top edge**, not the
14
+ maximum user `extY`. User `extY` counts down from the page top and is bounded
15
+ by the active page's display height (e.g. A4 ≠ Letter ≠ custom).
16
+
17
+ Conversion (inside `SDK_PlacePartByExtCoord` / `SDK_PlaceSymbolByExtCoord`):
18
+
19
+ ```
20
+ canvasY = PAGE_SIZE_START_Y - extY // PAGE_SIZE_START_Y ≈ 1400 (page top)
21
+ ```
22
+
23
+ Example (default page): user places GND at `(50, 100)` → canvas Y = `1400 - 100 = 1300`.
24
+
25
+ Reverse (from `kernel-get-snapshot` pin positions):
26
+
27
+ ```
28
+ extY = PAGE_SIZE_START_Y - canvasY
29
+ ```
30
+
31
+ Applies to:
32
+
33
+ - `PlaceKicadSymbol` → `SCH_PlaceKicadSymbolFromResource`
34
+ - `PlacePartFromLibrary` → `SCH_PlacePartFromLibrary`
35
+ - `PlaceSymbolFromLibrary` → `SCH_PlaceSymbolFromLibrary`
36
+ - `PlaceWire` → wire segment endpoints (same external convention)
37
+
38
+ See also [`property-conventions.md`](property-conventions.md) for pin/wiring coords.
39
+
40
+ ## Orientation: Mirror vs Rotate
41
+
42
+ | Goal | API | Notes |
43
+ | --- | --- | --- |
44
+ | **Fixed facing** (e.g. LDO VIN pin left) | `RotateObjectsByIds` | Each call = 90° clockwise; predictable, call 1–3 times as needed |
45
+ | **Flip symbol** | `MirrorObjectsByIds` | **`mode` is required** — see table below |
46
+
47
+ ### MirrorObjectsByIds — `mode` is mandatory
48
+
49
+ Protobuf `int32 mode` defaults to **0** (`MIRROR_NONE`), which performs **no transform**.
50
+ The RPC now returns `success: false` when `mode` is 0 or missing.
51
+
52
+ | `mode` | Effect |
53
+ | --- | --- |
54
+ | **1** | Horizontal mirror (left ↔ right) |
55
+ | **2** | Vertical mirror (top ↔ bottom) |
56
+ | **3** | Both axes |
57
+ | **0** | **Invalid** — no-op, `success: false` |
58
+
59
+ ```typescript
60
+ // Correct — flip U1 horizontally
61
+ await canvasOps.mirrorObjectsByIds({
62
+ context: projectCtx,
63
+ objectIds: [u1ObjectId],
64
+ mode: 1,
65
+ });
66
+
67
+ // Wrong — mode omitted or 0 looks like "success" in old builds but changes nothing
68
+ await canvasOps.mirrorObjectsByIds({ context: projectCtx, objectIds: [u1ObjectId] }); // BAD
69
+
70
+ // Prefer rotate when layout goal is "pin N faces direction X"
71
+ await canvasOps.rotateObjectsByIds({ context: projectCtx, objectIds: [u1ObjectId] });
72
+ ```
73
+
74
+ ## Objective
75
+
76
+ Guide agents to place schematic objects through the **correct library surface**.
77
+ Do **not** hand-craft engine-internal JSON for `PlacePart` / `PlaceSymbol`.
78
+
79
+ ## Part Placement (R, C, Q, U, …) — Online First
80
+
81
+ | Priority | Step | RPC / API | Notes |
82
+ | --- | --- | --- | --- |
83
+ | **1 (preferred)** | Search | `client.parts.searchParts` / DSH `search_hqsch_parts` | Pick candidate with `hasSymbol` + `hasFootprint`. |
84
+ | **2** | Models | `client.parts.getEdaModels` / DSH `get_hqsch_part_models` | Get symbol / footprint download URLs. |
85
+ | **3** | Place | `PlaceKicadSymbol` | Maps to SDK `SCH_PlaceKicadSymbolFromResource` — engine downloads `.kicad_sym`, converts, places. |
86
+ | Fallback | List | `ListPartLibraries` | Only when online search has no suitable part. |
87
+ | Fallback | Place | `PlacePartFromLibrary` | Local/builtin `.HQSchLib` part. |
88
+
89
+ **Do not use** `PlacePart` with hand-crafted `json_part` / `json_part_inst`.
90
+
91
+ See [`../eda/part-search/SKILL.md`](../eda/part-search/SKILL.md) for search strategy.
92
+
93
+ ### TypeScript Example (0805 capacitor)
94
+
95
+ ```typescript
96
+ import { connect } from "@huaqiu/huaqiu-client";
97
+
98
+ const client = await connect({ instanceId: "..." });
99
+ const projectCtx = client.createProjectContext(projectId);
100
+
101
+ // 1. Online search
102
+ const page = await client.parts.searchParts({
103
+ query: "0805 0.1uF capacitor",
104
+ requirements: { symbol: true, footprint: true },
105
+ pageSize: 10,
106
+ });
107
+ const hit = page.items.find((i) => i.hasSymbol && i.hasFootprint && i.manufacturer.id);
108
+ if (!hit) throw new Error("no online part candidate");
109
+
110
+ // 2. EDA model URLs
111
+ const models = await client.parts.getEdaModels({
112
+ manufacturerId: hit.manufacturer.id!,
113
+ mpn: hit.mpn,
114
+ });
115
+ if (!models.symbol?.url) throw new Error("no symbol URL");
116
+
117
+ // 3. Place via SCH_PlaceKicadSymbolFromResource (gRPC: PlaceKicadSymbol)
118
+ // localPos Y: 0 = page top, increases downward (NOT capped at 1400)
119
+ const res = await client.componentPlace.placeKicadSymbol({
120
+ context: projectCtx,
121
+ localPos: { x: 50n, y: 75n },
122
+ component: {
123
+ componentName: "C1_cap",
124
+ symbolResource: { uri: models.symbol.url },
125
+ footprintResources: models.footprint?.url
126
+ ? [{ uri: models.footprint.url, models: models.model3d?.url ? [{ uri: models.model3d.url }] : [] }]
127
+ : [],
128
+ attributes: [
129
+ { name: "Reference", value: "C1" },
130
+ { name: "Value", value: "0.1 μF" },
131
+ { name: "Footprint", value: models.footprint?.fileName ?? hit.package ?? "0805" },
132
+ { name: "MPN", value: hit.mpn },
133
+ ],
134
+ },
135
+ });
136
+ if (!res.success || !res.objectId) throw new Error("PlaceKicadSymbol failed");
137
+ ```
138
+
139
+ After placement, confirm designator/value with `SetObjectProperty` (`propKey: "Part Reference"`, `"Value"`, `"Footprint"`) — see [`property-conventions.md`](property-conventions.md).
140
+
141
+ ## Symbol Placement (GND, VCC, port, off-page)
142
+
143
+ Global/power symbols, hierarchical ports, and off-page connectors use the **symbol library** surface (not part-search):
144
+
145
+ | Step | RPC | Notes |
146
+ | --- | --- | --- |
147
+ | 1 | `ListSymbolLibraries` | Read `library_name`, `symbol_name`, `kind`. |
148
+ | 2 | `PlaceSymbolFromLibrary` | Maps to SDK library load + place; do not hand-craft JSON. |
149
+
150
+ **Do not use** `PlaceSymbol` with `json_symbol` / `json_symbol_inst`.
151
+
152
+ ### Approximate `symbol_name` matching
153
+
154
+ `symbol_name` does not have to be an exact library entry. The engine scores every
155
+ symbol of the **resolved library only** and takes the best hit — it never searches
156
+ other libraries, so step 1 (`ListSymbolLibraries`) is still required to pick the
157
+ right `library_name` / `library_file_path`.
158
+
159
+ Ranking, from strongest to weakest: exact → case-insensitive → normalized
160
+ (`_ - . space` stripped) → separator-boundary prefix → separator-boundary token →
161
+ normalized prefix → plain substring. Ties prefer the shorter candidate name.
162
+
163
+ Because separator-boundary hits outrank plain substrings, `GND` never resolves to
164
+ `VCC_5V`. If nothing scores, the call fails with `SymbolNotFound`.
165
+
166
+ `net_name` is written to the `"Name"` user property of that one placed instance;
167
+ the shared library / design-cache definition keeps its original name. Leaving
168
+ `net_name` empty means "same as `symbol_name`", which is usually what you want:
169
+
170
+ ```typescript
171
+ import { HqServicesV1ComponentPlaceService } from "@hqedge/connect";
172
+
173
+ const { LibrarySymbolKind } = HqServicesV1ComponentPlaceService;
174
+
175
+ // Step 1 — list libraries (required: symbol_name is matched within one library only)
176
+ const symLibs = await client.componentPlace.listSymbolLibraries({ context: projectCtx });
177
+
178
+ // Step 2 — find the library entry whose symbol_name matches (e.g. "VCC_5V" or "GND")
179
+ let placedId: bigint | undefined;
180
+ for (const lib of symLibs.libraries) {
181
+ const sym = lib.symbols.find((s) => s.symbolName === "VCC_5V");
182
+ if (!sym) continue;
183
+
184
+ // Step 3 — place at external display coordinates (Y down from page top)
185
+ const res = await client.componentPlace.placeSymbolFromLibrary({
186
+ context: projectCtx,
187
+ localPos: { x: 50n, y: 50n }, // user view: (50, 50) from top-left
188
+ libraryName: lib.libraryName, // from ListSymbolLibraries
189
+ symbolName: sym.symbolName, // e.g. "VCC_5V" (approx match → library "VCC")
190
+ libraryFilePath: lib.libraryFilePath, // from ListSymbolLibraries
191
+ kind: sym.kind ?? LibrarySymbolKind.LIBRARY_SYMBOL_KIND_GLOBAL,
192
+ netName: "VCC_5V", // → "Name" user property on this instance
193
+ });
194
+ if (res.success && res.objectId) {
195
+ placedId = res.objectId;
196
+ break;
197
+ }
198
+ }
199
+ if (!placedId) throw new Error("PlaceSymbolFromLibrary failed for VCC_5V");
200
+
201
+ // Save object_id for pin lookup — use kernel-get-snapshot canvas_object_id, not designator.
202
+ // See property-conventions.md "Pin Coordinates and Wiring".
203
+
204
+ // Step 4 — set designator (find uses "Reference"; set uses "Part Reference")
205
+ await client.canvasOps.setObjectProperty({
206
+ context: projectCtx,
207
+ objectId: placedId,
208
+ propKey: "Part Reference",
209
+ propValue: "VCC_5V",
210
+ op: 1, // OBJ_PROP_SET
211
+ commitUndo: true,
212
+ });
213
+ ```
214
+
215
+ ### `PlaceSymbolFromLibrary` request fields (VCC_5V example)
216
+
217
+ | Field | Example | Source |
218
+ | --- | --- | --- |
219
+ | `localPos` | `{ x: 50, y: 50 }` | Agent/user coordinates — **external**, Y down |
220
+ | `libraryName` | `"JupSym"` | `ListSymbolLibraries` → `libraries[].library_name` |
221
+ | `symbolName` | `"VCC_5V"` | Desired net label; engine fuzzy-matches within the resolved library |
222
+ | `libraryFilePath` | `"…/JupSym.HQSchLib"` | `ListSymbolLibraries` → `libraries[].library_file_path` |
223
+ | `kind` | `LIBRARY_SYMBOL_KIND_GLOBAL` | `ListSymbolLibraries` → `symbols[].kind` |
224
+ | `netName` | `"VCC_5V"` | Written to instance `"Name"` property; empty = same as `symbolName` |
225
+
226
+ The response does not report which symbol was actually matched. Read the placed
227
+ object's `"Name"` back with `GetObjectProperty` when you need to confirm.
228
+
229
+ **Do not** use `PlaceSymbol` with `json_symbol: { lib_id: "power:VCC_5V" }` on
230
+ the agent path — that is an internal fallback only when `ListSymbolLibraries`
231
+ returns no match.
232
+
233
+ ## Wiring After Placement
234
+
235
+ After any placement RPC, **save `response.object_id`** for every component.
236
+ Wire using the saved ids — do not look up power symbols by designator string.
237
+
238
+ ### Routing mode gate (mandatory before every net)
239
+
240
+ **Agents forget to measure** — they either `autoConnect` everything (long ugly
241
+ wires) or stub+alias everything (missing real wires when pins are adjacent).
242
+ Run this gate **per net**, after patterns, **before** picking a mode.
243
+
244
+ ```
245
+ For each net to wire (pinA on partA ↔ pinB on partB):
246
+ │
247
+ ├─ Already routed inside a pattern (connections[] routed=true)?
248
+ │ → STOP — do not hand-wire
249
+ │
250
+ ├─ Measure: Manhattan ext, L-route length, wire crossing count
251
+ │ (cross-part is OK if pins are close — same gate as same-part)
252
+ │
253
+ ├─ Short hop? aligned (same X or Y), Manhattan ≤ 300 ext,
254
+ │ L-route ≤ 500 ext, crossings < 3, not a ≥4-node hub net
255
+ │ → autoConnect (preferred — real wire, clean sheet)
256
+ │ e.g. cap→GND beside host, EN→C_EN, nearby LDO.VOUT→MCU.VDD
257
+ │
258
+ └─ Long / crowded / off-page / ≥4 nodes on net
259
+ → dual-end placePinStubWireAndNetAlias (same netName)
260
+ NO autoConnect between these two pins afterward
261
+ ```
262
+
263
+ | Situation | Mode | Example |
264
+ | --- | --- | --- |
265
+ | Two pins close (gate passes short-hop checks) | **autoConnect** | Decap→GND, EN→reset cap, LDO→MCU when placed adjacent |
266
+ | Two pins far apart or L-route > 500 / ≥3 crossings | **stub+alias** | Type-C ↔ LDO ↔ ESP when blocks are spaced out |
267
+ | Pattern already wired one end | **stub+alias on the other end only** | GPIO9 after `pull_resistor` → SW |
268
+ | Replacing a whole pull/reset/decoupling cluster | **pattern** — not stub instead of pattern | CC→R→GND, NRST reset |
269
+
270
+ **Forbidden (common script bugs):**
271
+
272
+ - `autoConnect` **without measuring** — the usual mistake on cross-block power nets
273
+ - `autoConnect` between far pins, then alias on **one** side only — long wire + duplicate labels
274
+ - Skipping `pull_resistor` / `reset_circuit` and using stub+alias for the **whole** R→rail chain
275
+ - `autoConnect` as fallback when stub fails — fix pin_name / pinNumber instead
276
+ - Hub: one pin `autoConnect` to many targets on the same net
277
+
278
+ **Script helper:** implement `routeNet(a, pinA, b, pinB, netName)` that **measures
279
+ first**, then calls `autoConnectObjectsById` or dual-end stub+alias (`tieFarNet`).
280
+ Reserve `tieFarNet()` for nets the gate classifies as long — do not call it blindly
281
+ for every cross-part net when placement already put the pins next to each other.
282
+
283
+ ### Circuit pattern first — decision checklist
284
+
285
+ Before **any** hand placement coordinates, `autoConnect`, or `PlaceWire`, run
286
+ this checklist. It applies to the whole sheet and to **each local subnet**
287
+ independently (see mixed-circuit decomposition in
288
+ [`circuit-pattern-layout.md`](circuit-pattern-layout.md)).
289
+
290
+ #### Step 0 — Refresh catalog
291
+
292
+ - [ ] Call `pattern-layout-list-circuit-patterns` (or read the latest catalog).
293
+ - [ ] New pattern ids may have been added since the last session — do not rely on
294
+ memory or old scripts alone.
295
+
296
+ #### Step 1 — Decompose the design
297
+
298
+ - [ ] List every local cluster (power pin decoupling, single-pin pull-up/down,
299
+ bus interface, crystal, pin shorts, termination R, net aliases, …).
300
+ - [ ] Mark each cluster **pattern** or **hand** — independently. A sheet may be
301
+ both.
302
+
303
+ #### Step 2 — Match clusters to catalog (current shortcuts)
304
+
305
+ | Local circuit | Pattern id | Skill |
306
+ | --- | --- | --- |
307
+ | Bypass / decoupling caps on a power pin | `CIRCUIT_PATTERN_DECOUPLING_CAP` | `pattern-layout-apply-circuit-pattern` |
308
+ | Pull-up / pull-down resistors (signal → R → rail) | `CIRCUIT_PATTERN_PULL_RESISTOR` | `pattern-layout-apply-circuit-pattern` |
309
+ | MCU reset (pull-up + cap + optional button) | `CIRCUIT_PATTERN_RESET_CIRCUIT` | `pattern-layout-apply-circuit-pattern` |
310
+ | I2C master + slaves + SDA/SCL pull-ups | `CIRCUIT_PATTERN_I2C_BUS` | `pattern-layout-apply-circuit-pattern` |
311
+ | SPI master + slaves | `CIRCUIT_PATTERN_SPI_BUS` | `pattern-layout-apply-circuit-pattern` |
312
+ | Crystal + load caps (+ feedback R) | `CIRCUIT_PATTERN_CRYSTAL` | `pattern-layout-apply-circuit-pattern` |
313
+
314
+ *When new patterns ship, extend this table from `list-circuit-patterns` — the RPC
315
+ is the source of truth.*
316
+
317
+ | Usually **hand** (no catalog entry yet) | Approach |
318
+ | --- | --- |
319
+ | Cross-module / different parts on one net | Run [Routing mode gate](#routing-mode-gate-mandatory-before-every-net): **autoConnect if short**, stub+NetAlias if long |
320
+ | Termination / series R between two signal pins | Same gate — short → autoConnect; far → stub+NetAlias |
321
+ | Two pins shorted on one IC | `autoConnect` (same part, short) |
322
+ | Off-page / port labels | stub + NetAlias after patterns |
323
+
324
+ #### Step 3 — Apply patterns (per matching cluster)
325
+
326
+ - [ ] `pattern-layout-get-page-occupancy` — pick anchors outside reserved boxes.
327
+ - [ ] Place parts (rough position is OK for movable roles).
328
+ - [ ] `apply-circuit-pattern(plan_only=true)` until anchor is free.
329
+ - [ ] `apply-circuit-pattern(plan_only=false)` — book-keep `occupied_box`.
330
+ - [ ] Repeat for **each** matching subnet (multiple applies per sheet is normal).
331
+
332
+ **Same IC, multiple patterns:** allowed when the IC is `host` / `signal_host`
333
+ / `master` (`movable=false`). For **same-polarity pulls to one rail**, prefer
334
+ **one** `pull_resistor` apply with multiple `slotIndex` pairs + bound `rail`.
335
+ **Not allowed:**
336
+ the same **movable** part (one resistor, one cap) bound in two applies. Details:
337
+ [`circuit-pattern-layout.md` — Pull resistor: multiple pins](circuit-pattern-layout.md#pull-resistor-multiple-pins).
338
+
339
+ #### Step 4 — Hand-layout leftovers only
340
+
341
+ - [ ] Run [Routing mode gate](#routing-mode-gate-mandatory-before-every-net) **for each net**.
342
+ - [ ] Do **not** re-wire nets already listed in `response.connections` with `routed=true`.
343
+ - [ ] **Short hop (gate pass):** `autoConnectObjectsById` — even between different parts if pins are close.
344
+ - [ ] **Long hop (gate fail):** dual-end `placePinStubWireAndNetAlias` (same `netName`).
345
+ - [ ] Power symbols: `autoConnect` pin **0** when the rail tap is adjacent; stub+alias when modules are far apart.
346
+
347
+ If **no** subnet matches any catalog entry, skip Step 3 and hand-layout the
348
+ whole local circuit.
349
+
350
+ Full role / terminal / option catalog and plan-then-apply rules:
351
+ [`circuit-pattern-layout.md`](circuit-pattern-layout.md).
352
+
353
+ **Symbol pin numbers:** for **power / GND / hierarchical port / off-page**
354
+ symbols placed via `PlaceSymbolFromLibrary`, the connection pin number in
355
+ `AutoConnectObjectsById` is **always `0`** — not `1`. Regular parts (R, C, L,
356
+ U, …) use the symbol's printed pin numbers (`1`, `2`, …).
357
+
358
+ ### Decoupling cap example (VCC_5V — C1 — GND)
359
+
360
+ ```typescript
361
+ // 1. Place symbols/parts — save object_id from each response
362
+ const vccRes = await client.componentPlace.placeSymbolFromLibrary({ /* … */ localPos: { x: 50n, y: 50n }, netName: "VCC_5V" });
363
+ const gndRes = await client.componentPlace.placeSymbolFromLibrary({ /* … */ localPos: { x: 50n, y: 100n }, netName: "GND" });
364
+ const c1Res = await client.componentPlace.placePartFromLibrary({ /* … */ localPos: { x: 50n, y: 75n } });
365
+
366
+ const vccId = vccRes.objectId!;
367
+ const gndId = gndRes.objectId!;
368
+ const c1Id = c1Res.objectId!;
369
+
370
+ // 2. Set properties (optional but recommended)
371
+ await client.canvasOps.setObjectProperty({ objectId: c1Id, propKey: "Part Reference", propValue: "C1", op: 1, commitUndo: true });
372
+ await client.canvasOps.setObjectProperty({ objectId: c1Id, propKey: "Value", propValue: "0.1 μF", op: 1, commitUndo: true });
373
+
374
+ // 3. Wire by object_id (preferred — no snapshot needed)
375
+ // Symbol pins (VCC, GND, port, off-page): pin number is ALWAYS 0.
376
+ await client.canvasOps.autoConnectObjectsById({
377
+ context: projectCtx,
378
+ objectId1: c1Id, pinNum1: [1],
379
+ objectId2: vccId, pinNum2: [0],
380
+ });
381
+ await client.canvasOps.autoConnectObjectsById({
382
+ context: projectCtx,
383
+ objectId1: c1Id, pinNum1: [2],
384
+ objectId2: gndId, pinNum2: [0],
385
+ });
386
+ ```
387
+
388
+ ### Schematic wire routing (avoid overlap)
389
+
390
+ `AutoConnectObjectsById` routes **each call independently**. If many endpoints
391
+ on the same net all connect to one anchor (hub / star topology), the router
392
+ reuses the same corridor and wire segments stack visually.
393
+
394
+ Apply these rules for **any multi-node net** — power, GND, buses, control
395
+ signals, or groups of parallel passives — not only decoupling caps.
396
+
397
+ | Rule | Requirement |
398
+ | --- | --- |
399
+ | **Separate branch lanes at placement time** | Before wiring, offset each branch along the axis **perpendicular** to the intended bus so taps do not share one column (or row). On a horizontal bus, give each branch a **unique X**; on a vertical bus, a **unique Y**. Spacing can be small but must not be identical. |
400
+ | **Chain nodes on the same net** | Connect **two adjacent nodes per call** in sequence: `anchor → n₁ → n₂ → … → nₖ`. This builds a bus with independent vertical (or horizontal) drops instead of re-routing from the anchor every time. |
401
+ | **Follow a logical order** | Order the chain by signal or power flow (source → load, input → output, left → right). Keep bus direction consistent across related nets on the same sheet when practical. |
402
+ | **Layout is part of routing** | If segments still overlap, **move components** and re-wire — do not stack multiple hub connections from one symbol. |
403
+
404
+ **Avoid (hub — segments overlap):**
405
+
406
+ ```
407
+ NET ──┬── node₁
408
+ ├── node₂
409
+ └── node₃
410
+ ```
411
+
412
+ **Prefer (chain — each tap uses its own lane):**
413
+
414
+ ```
415
+ NET ── node₁ ── node₂ ── node₃
416
+ │ │ │
417
+ (unique X or Y per branch when dropping off the bus)
418
+ ```
419
+
420
+ Minimal pattern:
421
+
422
+ ```typescript
423
+ // Same net, sequential links — NOT anchor → every node
424
+ for (const [id1, pin1, id2, pin2] of [
425
+ [anchorId, 1, n1Id, 1],
426
+ [n1Id, 1, n2Id, 1],
427
+ [n2Id, 1, n3Id, 1],
428
+ ] as const) {
429
+ await getSkill("canvas-auto-connect-objects-by-id")!.execute(ctx, {
430
+ context: projectCtx,
431
+ objectId1: id1, pinNum1: [pin1],
432
+ objectId2: id2, pinNum2: [pin2],
433
+ });
434
+ }
435
+ ```
436
+
437
+ Place the anchor (power symbol, port, or upstream driver) at one end of the
438
+ chain; distribute loads/passives along the bus axis with **non-colliding**
439
+ coordinates before calling `AutoConnectObjectsById`.
440
+
441
+ ### Stub + Net Alias (long distance / many crossings)
442
+
443
+ > Use when the [routing mode gate](#routing-mode-gate-mandatory-before-every-net)
444
+ > **fails** the short-hop checks. **Short nets still use `autoConnect`** — stub+alias
445
+ > is not the default for every cross-part net, only for nets that would draw a long
446
+ > or crowded wire.
447
+
448
+ When two pins are far apart or a direct orthogonal route would cross many
449
+ existing wires, **do not** call `AutoConnectObjectsById` between them.
450
+ Instead, place **matching `NetAlias` labels** on the same net name at each pin
451
+ end — either on a **new short stub** (**30** ext units) or **directly on an
452
+ existing wire** already connected to that pin.
453
+
454
+ ```
455
+ Pin A ── stub ── NetAlias NET_X NetAlias NET_X ── stub ── Pin B
456
+ (30) (30)
457
+
458
+ Pin A ── existing wire ── NetAlias NET_X (no new stub if wire already on pin)
459
+ ```
460
+
461
+ Electrical connectivity comes from two aliases sharing the same `netName` on
462
+ the active page — not from a long wire between the pins.
463
+
464
+ #### When to use (agent heuristics)
465
+
466
+ **Do not use pin-to-pin Manhattan alone** — routed wire length often exceeds straight
467
+ distance when the path wraps around symbol bounding boxes (typical multi-pin IC layouts).
468
+
469
+ | Condition | Action |
470
+ | --- | --- |
471
+ | Manhattan distance between pins (ext coords) **> 400** (cross-part / off-page) | Use stub + alias (both pin ends, same `netName`) |
472
+ | Estimated **L-shaped** route length (H→V or V→H, shorter variant, including bbox detour) **> 500** | Use stub + alias |
473
+ | Proposed L-route crosses **≥ 3** existing `wireSegments` on the active page | Use stub + alias |
474
+ | Same net has **≥ 4 nodes** to interconnect | Stub + alias at each pin; **no** hub/star `AutoConnectObjectsById` |
475
+ | **Different parts**, pins **aligned**, Manhattan **≤ 300**, L-route **≤ 500**, crossings **< 3** | **`AutoConnectObjectsById`** — cross-part is fine when close |
476
+ | **Different parts**, Manhattan **> 400** or L-route **> 500** or crossings **≥ 3** | Dual-end stub + alias |
477
+ | Pins **aligned** (same X or same Y), Manhattan **≤ 300**, crossings **< 3** (same or different parts) | `AutoConnectObjectsById` or a single `PlaceWire` |
478
+ | **Several signal pins on one IC edge** (see [Dense same-edge pins](#dense-same-edge-pins)) | Prefer `AutoConnectObjectsById` per net to the remote pin; reserve stub + alias for long hops or single-pin parts |
479
+
480
+ **Stub + alias is symmetric:** call `placePinStubWireAndNetAlias` at **each** pin end
481
+ with the **same** `netName`. Do **not** `autoConnect` between the pins and add an alias
482
+ on only one side — that leaves long physical wires on the page.
483
+
484
+ **One routing mode per net:** for a given `(netName, pinA, pinB)` pair, choose **either**
485
+ `AutoConnectObjectsById` + alias on the resulting wires **or** dual-end stub + alias —
486
+ **not both**. Mixing modes leaves orphan stubs, duplicate NetAlias labels, and ERC noise.
487
+
488
+ **Never** call `PlaceWire` manually before `placePinStubWireAndNetAlias` to “ensure a
489
+ stub exists” — stub creation is the RPC’s job. Manual stubs often duplicate what
490
+ `autoConnect` already placed and are not removed automatically.
491
+
492
+ **List existing wires** before routing: `canvas-list-wire-segments` (active page,
493
+ real-time) or `kernel-get-snapshot` → `wireSegments[]`. Endpoints are in
494
+ **canvas coordinates (Y up)** — convert to ext before comparing with pin ext
495
+ coords, or convert pins to canvas for crossing checks.
496
+
497
+ **Crossing check (agent reasoning):**
498
+
499
+ 1. Each `wireSegment` is a straight segment `(x1,y1)–(x2,y2)`.
500
+ 2. Candidate direct route = orthogonal L-path: pinA → corner → pinB (two segments).
501
+ 3. Segments that share an endpoint do **not** count as crossings; collinear overlap does not add extra crossings.
502
+ 4. Evaluate both H→V and V→H corners; use the variant with **fewer** crossings. If the minimum is still **≥ 3**, use stub + alias.
503
+
504
+ #### Pin already has a wire
505
+
506
+ Before placing a stub at a pin, check whether that pin **already has a wire
507
+ segment connected** (from prior `AutoConnectObjectsById`, `PlaceWire`, or manual
508
+ editing):
509
+
510
+ | Pin state | Action |
511
+ | --- | --- |
512
+ | **No wire** at pin | `obj-place-place-pin-stub-wire-and-net-alias` creates stub + alias (default **30** ext; C++ extends **+10** when stub end hits a **perpendicular** wire; parallel stubs on the same edge are ignored) |
513
+ | **Wire already connected** at pin | Same RPC — **no** new stub; alias on existing wire (**30** ext from pin) |
514
+
515
+ **Detecting an existing connection (intended rule):** `placePinStubWireAndNetAlias`
516
+ should set `usedExistingWire=true` **only** when a wire segment **endpoint** matches
517
+ **this pin's** connection point (`objectId` + `pinNum`/`pinName` → exact `ex/ey`,
518
+ tolerance ≈ `PIN_CONNECT_OFFSET`, **not** one full grid step). A wire merely
519
+ passing near an adjacent pin, or attached to a **neighbor pin on the same symbol**,
520
+ should **not** count — the engine should create a **new stub** (`wireObjectId > 0`).
521
+
522
+ Agents mirroring this check: from `canvas-list-wire-segments` or `wireSegments[]`,
523
+ require **start or end** coords equal to **that pin's** position (within ~3 ext),
524
+ not “any wire on the same row / same escape direction”.
525
+
526
+ **Observed limitation — pin-level wire detection:** On symbols with **multiple
527
+ adjacent signal pins on one edge** (same escape direction, typical **10 ext** grid
528
+ spacing), sequential `placePinStubWireAndNetAlias` calls can still attach a NetAlias
529
+ to the **wrong physical wire** even when RPC returns `success=true`:
530
+
531
+ | What you see | Meaning |
532
+ | --- | --- |
533
+ | `stub=30`, `wireObjectId > 0` on every call | RPC succeeded; stubs were created |
534
+ | `stub=0`, `usedExistingWire=true` on later pins | Alias placed on an **existing** segment — verify it belongs to **this** `pinNum` |
535
+ | `listWireSegments` near pin *N* lists **other** net names | Physical segment is shared or mis-attached — logical NetAlias may still differ |
536
+ | All adjacent pins show the **same** wire `netName` | Nets merged on one stub column — **do not** trust wire names alone |
537
+
538
+ Reproduction pattern: place distinct `netName` values on **consecutive pin numbers**
539
+ on one part (e.g. pins 10–17 → `net1`…`net8` on the active page). Compare NetAlias
540
+ labels and `netlist-get-active-page-net-list` to wire `netName` at each pin.
541
+
542
+ **Root cause (agent diagnosis):** “Detect existing wire” inside
543
+ `PlacePinStubWireAndNetAlias` is not always scoped to **(objectId + pinNum + pin
544
+ connection point)**; stubs escaping the same way on one edge can overlap or be treated
545
+ as shared, so labels land on the wrong segment.
546
+
547
+ **Engine fix request:** Restrict “wire at pin” to segments whose start **or** end
548
+ equals **this pin’s** connection geometry — never any wire on the symbol body or escape row.
549
+
550
+ **Alias placement on existing wire:**
551
+
552
+ - Set `localPos` to a point **on the wire**, typically **30 ext units along
553
+ the wire away from the pin** (same spacing as a stub would use), not on the
554
+ symbol body.
555
+ - Prefer `attachObjectId = <wire segment canvasObjectId>` so the alias snaps to
556
+ the wire; `attachObjectId = 0` (auto attach) is acceptable when the position
557
+ lies on the segment.
558
+ - If the existing wire is very short (< 30 ext units), place the alias at the
559
+ far end of that segment or slightly beyond along the same axis — still **no
560
+ duplicate stub**.
561
+
562
+ Apply this check **independently per pin** — pin A may need a new stub while pin
563
+ B already has a wire and only needs an alias.
564
+
565
+ #### Stub length and direction
566
+
567
+ | Item | Rule |
568
+ | --- | --- |
569
+ | **Length** | **30** ext units initial; C++ extends **+10** ext when stub end hits a **perpendicular** wire (horizontal stub ↔ vertical wire); parallel wires ignored (up to `maxStubLength`, default 300) |
570
+ | **Direction** | Extend **away from the symbol body** along the pin escape axis: horizontal pin → ±X; vertical pin → ±Y (ext Y **down**, +Y = downward) |
571
+ | **Grid** | Always `snapToGrid: true`; 30 is a multiple of the schematic grid (`GRIDSPACE = 10`) |
572
+ | **Avoid** | Do not route stubs through the symbol bounding box — flip direction if blocked |
573
+
574
+ Pin positions: snapshot `pinInstances[].position` (canvas, Y up) →
575
+ `extY = PAGE_SIZE_START_Y - canvasY` (see [`property-conventions.md`](property-conventions.md)).
576
+
577
+ #### Procedure (one net name, two pins)
578
+
579
+ For net `NET_X` between pinA and pinB:
580
+
581
+ 1. Resolve pin positions in **external display coordinates**.
582
+ 2. **Per pin**, list connected wire segments (see [Pin already has a wire](#pin-already-has-a-wire)).
583
+ 3. **Pin A side** — `obj-place-place-pin-stub-wire-and-net-alias` with `objectId`, `pinNum`/`pinName`, `netName = "NET_X"`, `snapToGrid: true`. The RPC detects existing wire, places stub + alias when needed, and merges wire + alias into **one undo step**.
584
+ 4. **Pin B side** — same RPC; `netName` **must match** pin A.
585
+ 5. **Do not** call `AutoConnectObjectsById` between A and B afterward (that defeats the pattern).
586
+ 6. **Verify** — `kernel-get-snapshot` or `netlist-get-active-page-net-list`: both pins on the same net; run ERC if needed.
587
+
588
+ ```typescript
589
+ function manhattanExt(a: { x: number; y: number }, b: { x: number; y: number }) {
590
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
591
+ }
592
+
593
+ /** Measure first — short → wire, long → alias both ends. */
594
+ async function routeNet(
595
+ partA: bigint, pinA: { pinNum?: number; pinName?: string; x: number; y: number },
596
+ partB: bigint, pinB: { pinNum?: number; pinName?: string; x: number; y: number },
597
+ netName: string,
598
+ ) {
599
+ if (manhattanExt(pinA, pinB) <= 300) {
600
+ await client.canvasOps.autoConnectObjectsById({
601
+ context: projectCtx,
602
+ objectId1: partA, pinNum1: pinA.pinNum != null ? [pinA.pinNum] : undefined,
603
+ pinName1: pinA.pinName ? [pinA.pinName] : undefined,
604
+ objectId2: partB, pinNum2: pinB.pinNum != null ? [pinB.pinNum] : undefined,
605
+ pinName2: pinB.pinName ? [pinB.pinName] : undefined,
606
+ });
607
+ return;
608
+ }
609
+ for (const [objectId, pin] of [[partA, pinA], [partB, pinB]] as const) {
610
+ await client.objPlace.placePinStubWireAndNetAlias({
611
+ context: projectCtx, objectId, pinNum: pin.pinNum, pinName: pin.pinName,
612
+ netName, snapToGrid: true,
613
+ });
614
+ }
615
+ }
616
+ ```
617
+
618
+ #### Relation to other wiring rules
619
+
620
+ - **Bus / chain nets**: still connect **adjacent** pairs with `AutoConnectObjectsById` when distance and crossings are low; apply stub + alias only on **edges** that fail the heuristics above (signal name = `netName`, e.g. `SCL`, `KEY_IN`).
621
+ - **Power / GND**: short taps keep `AutoConnectObjectsById` with symbol pin **0**; long power trees may use stub + alias with `VCC_3V3`, `GND`, etc.
622
+ - **Single-point labels**: placing one alias at a junction (e.g. `KEY_IN` at one node) is for **annotation**; stub + alias is for **connecting two distant pins** — one alias per pin end (stub or existing wire), same `netName`.
623
+
624
+ #### Dense same-edge pins
625
+
626
+ When **several signal pins on one symbol edge** each need connectivity (same escape
627
+ direction, **~10 ext** pin spacing — common on QFP/QFN left/right columns):
628
+
629
+ **Prefer**
630
+
631
+ - **`AutoConnectObjectsById`** from each pin to its **remote** counterpart (one net
632
+ at a time), then **one** `placePinStubWireAndNetAlias` per pin to name the wire
633
+ already on **that** pin (`usedExistingWire=true` is OK when the wire came from
634
+ `autoConnect` on the same `pinNum`).
635
+ - **Dual-end stub + alias** only when the **other end** is far away **and** this part
636
+ exposes **one pin** for that net on this edge (e.g. a sensor’s single SDA pin).
637
+
638
+ **Avoid**
639
+
640
+ - Sequential stub + alias on **every** pin of a dense column without physical routes —
641
+ see [Observed limitation — pin-level wire detection](#pin-already-has-a-wire).
642
+ - Pre-drawing a shared bus with `PlaceWire` before labeling — creates orphan segments.
643
+ - `autoConnect` between two pins, then alias on **one** side only — long wires + duplicate labels.
644
+ - Re-running stub/alias or `autoConnect` on a pin already labeled for that `netName`
645
+ (track `(objectId, pinNum, netName)` and skip duplicates).
646
+
647
+ **Multi-tap / shared nets (one signal, ≥ 3 parts):**
648
+
649
+ - Wire **one new segment per tap** (A↔B, then B↔C) or stub + alias at **each** endpoint once.
650
+ - After A↔B is done, adding C: connect **only the new segment** (B↔C or A↔C); **do not**
651
+ re-connect A↔B or re-alias pins already done for that `netName`.
652
+ - **Wrong pattern:** hub/star `autoConnect` from one pin to many slaves **and** stub +
653
+ alias on the same net — duplicates wires and NetAlias on shared pins.
654
+
655
+ #### Verifying stub + alias
656
+
657
+ - **Do not** treat `wireSegments[].netName` as proof of success — physical wire
658
+ net names may differ from the **NetAlias label text** until net refresh completes.
659
+ - **Do** confirm `netAliasObjectId > 0` in the RPC response and, after a brief
660
+ retry, that `netlist-get-active-page-net-list` shows both pins on the same logical net.
661
+ - **Do** inspect NetAlias label objects (not wire `netName` alone) when debugging
662
+ missing or wrong labels.
663
+
664
+ #### Known limitations
665
+
666
+ - **`PlacePinStubWireAndNetAlias` on dense same-edge pins** — see
667
+ [Observed limitation — pin-level wire detection](#pin-already-has-a-wire) and
668
+ [Dense same-edge pins](#dense-same-edge-pins).
669
+ - **`listWireSegments` / `wireSegments[].netName` ≠ NetAlias text** — stub + alias
670
+ logical nets may show **zero** wire segments with the alias name; verify with
671
+ NetAlias objects and `netlist-get-active-page-net-list` / ERC.
672
+ - `PlaceNetAliasAt` has returned **REFUSED** in some API audits — if alias placement fails, fall back to direct `PlaceWire` between pins and report the failure. See [hqsch-api-slow-and-broken-rpcs.md](../../../docs/issues/hqsch-api-slow-and-broken-rpcs.md).
673
+ - Snapshot may lag immediately after placement — retry `kernel-get-snapshot` briefly before verifying net membership.
674
+
675
+ ### Fallback — PlaceWire via snapshot pin lookup
676
+
677
+ If `AutoConnectObjectsById` fails, call `kernel-get-snapshot`, locate pins via
678
+ `pinInstances[].canvas_object_id` (equal to placement `object_id`), convert
679
+ canvas Y-up positions to external Y-down coords, then `PlaceWire`.
680
+ See [`property-conventions.md`](property-conventions.md) for conversion and retry.
681
+
682
+ ### Enumerating existing wires
683
+
684
+ | Use case | Preferred skill | Notes |
685
+ | --- | --- | --- |
686
+ | List wires on the **active page** (real-time) | `canvas-list-wire-segments` | O(page objects); returns `objectId`, canvas-local endpoints, optional `netName`. Use for **pin endpoint** matching and crossing checks — **not** as sole proof that a NetAlias label exists (`netName` on wire ≠ alias text). |
687
+ | Batch topology / ERC / BOM pull model | `kernel-get-snapshot` → `wireSegments[]` | Each segment includes `canvasObjectId` + geometry; same id as `canvas-list-wire-segments` when the page is unchanged. |
688
+
689
+ Do **not** brute-force `canvas-get-objects-json-by-ids` over id ranges to find wires.
690
+
691
+ ## Capability Skill IDs
692
+
693
+ | Task | Preferred skill |
694
+ | --- | --- |
695
+ | Search online parts | `eda-part-search` / `search_hqsch_parts` |
696
+ | Place R/C/Q/U (online) | `placement-place-kicad-symbol` |
697
+ | Place GND/VCC/port/off-page | `placement-place-symbol-from-library` |
698
+ | List symbol libraries | `placement-list-symbol-libraries` |
699
+ | Known local circuit (I2C / SPI / crystal / decoupling / pull) | `pattern-layout-list-circuit-patterns` → `pattern-layout-apply-circuit-pattern` — see [circuit-pattern-layout.md](circuit-pattern-layout.md) |
700
+ | Leftover nets — short hop (gate pass, ≤300 ext, low crossings) | `canvas-auto-connect-objects-by-id` |
701
+ | Leftover nets — long hop (gate fail) | `obj-place-place-pin-stub-wire-and-net-alias` at **both** pin ends — see [Routing mode gate](#routing-mode-gate-mandatory-before-every-net) |
702
+ | List existing wires (active page) | `canvas-list-wire-segments` |
703
+ | Wire via pin coords (fallback) | `kernel-get-snapshot` → `obj-place-place-wire` |
704
+ | Wire topology from snapshot | `kernel-get-snapshot` → `wireSegments[].canvasObjectId` |
705
+ | Fallback: local part lib | `placement-list-part-libraries` → `placement-place-part-from-library` |
706
+ | **Avoid for agents** | `placement-place-part`, `placement-place-symbol` |
707
+
708
+ ## Backend Mapping
709
+
710
+ | gRPC | SDK |
711
+ | --- | --- |
712
+ | `PlaceKicadSymbol` | `SCH_PlaceKicadSymbolFromResource` |
713
+ | `PlaceSymbolFromLibrary` | `SCH_PlaceSymbolFromLibrary` |
714
+ | `PlacePartFromLibrary` | `SCH_PlacePartFromLibrary` |