@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.
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/cordis.patch.yml +13 -0
- package/lib/index.d.mts +98 -0
- package/lib/index.mjs +159 -0
- package/package.json +40 -0
- package/skills/hqsch-scenario-b/AGENTS.md +209 -0
- package/skills/hqsch-scenario-b/CLAUDE.md +209 -0
- package/skills/hqsch-scenario-b/INSTALL.md +140 -0
- package/skills/hqsch-scenario-b/SKILL.md +107 -0
- package/skills/hqsch-scenario-b/SYSTEM-PROMPT.md +209 -0
- package/skills/hqsch-scenario-b/docs/circuit-pattern-layout.md +304 -0
- package/skills/hqsch-scenario-b/docs/editing-a-circuit.md +167 -0
- package/skills/hqsch-scenario-b/docs/part-search.md +244 -0
- package/skills/hqsch-scenario-b/docs/placement-conventions.md +714 -0
- package/skills/hqsch-scenario-b/docs/property-conventions.md +225 -0
- package/skills/hqsch-scenario-b/docs/quickstart.md +99 -0
- package/skills/hqsch-scenario-b/docs/reading-a-circuit.md +317 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-auto-connect-objects-by-id.md +98 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-delete-objects-by-ids.md +94 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-find-object-by-property.md +101 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-list-wire-segments.md +94 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-mirror-objects-by-ids.md +96 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-rotate-objects-by-ids.md +94 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-set-object-property.md +99 -0
- package/skills/hqsch-scenario-b/docs/rpc/canvas-zoom-all.md +92 -0
- package/skills/hqsch-scenario-b/docs/rpc/find-find-object.md +96 -0
- package/skills/hqsch-scenario-b/docs/rpc/kernel-get-snapshot.md +104 -0
- package/skills/hqsch-scenario-b/docs/rpc/netlist-get-active-page-net-list.md +87 -0
- package/skills/hqsch-scenario-b/docs/rpc/netlist-get-port-pairing.md +34 -0
- package/skills/hqsch-scenario-b/docs/rpc/obj-place-place-pin-stub-wire-and-net-alias.md +114 -0
- package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-apply-circuit-pattern.md +125 -0
- package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-get-page-occupancy.md +100 -0
- package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-list-circuit-patterns.md +97 -0
- package/skills/hqsch-scenario-b/docs/rpc/placement-list-part-libraries.md +94 -0
- package/skills/hqsch-scenario-b/docs/rpc/placement-list-symbol-libraries.md +94 -0
- package/skills/hqsch-scenario-b/docs/rpc/placement-place-kicad-symbol.md +105 -0
- package/skills/hqsch-scenario-b/docs/rpc/placement-place-part-from-library.md +106 -0
- package/skills/hqsch-scenario-b/docs/rpc/placement-place-symbol-from-library.md +108 -0
- package/skills/hqsch-scenario-b/docs/rpc/project-get-active-project.md +96 -0
- package/skills/hqsch-scenario-b/docs/rpc/project-list-open-projects.md +99 -0
- package/skills/hqsch-scenario-b/docs/rpc-availability.md +97 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/canvas.md +81 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/find.md +36 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/kernel.md +36 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/netlist.md +38 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/obj-place.md +76 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/pattern-layout.md +38 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/placement.md +48 -0
- package/skills/hqsch-scenario-b/docs/rpc-index/project.md +47 -0
- package/skills/hqsch-scenario-b/docs/serialization.md +60 -0
- package/skills/hqsch-scenario-b/docs/skill-dependencies.md +93 -0
- package/skills/hqsch-scenario-b/skill.json +13 -0
- package/skills/hqsch-scenario-b/template/package.json +16 -0
- package/skills/hqsch-scenario-b/template/scripts/edit-property-by-ref.ts +44 -0
- package/skills/hqsch-scenario-b/template/scripts/fix-designators.ts +52 -0
- package/skills/hqsch-scenario-b/template/scripts/hello.ts +46 -0
- package/skills/hqsch-scenario-b/template/scripts/read-circuit.ts +357 -0
- package/skills/hqsch-scenario-b/template/scripts/replace-part-by-ref.ts +44 -0
- package/skills/hqsch-scenario-b/template/scripts/rewire-pin-by-ref.ts +42 -0
- package/skills/hqsch-scenario-b/template/tsconfig.json +11 -0
- package/src/config.ts +25 -0
- package/src/index.ts +186 -0
- package/src/paths.ts +48 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Object Property Key Conventions (HQSCH)
|
|
2
|
+
|
|
3
|
+
## Objective
|
|
4
|
+
|
|
5
|
+
Map common schematic editing terms (designator, value, footprint) to the
|
|
6
|
+
`propKey` strings required by `CanvasOpsService` property RPCs. The protobuf
|
|
7
|
+
schema only declares `propKey` as a generic `string`; HQSCH uses
|
|
8
|
+
vendor-specific key names that differ between **find** and **read/write**
|
|
9
|
+
operations.
|
|
10
|
+
|
|
11
|
+
## Property Key Reference
|
|
12
|
+
|
|
13
|
+
| Business term | Read / write (`GetObjectProperty`, `SetObjectProperty`) | Find (`FindObjectByProperty`) | Notes |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| Designator (位号) | `Part Reference` | `Reference` | **Always use `Part Reference` when setting the designator.** `Reference` and `Designator` do not work for get/set. |
|
|
16
|
+
| Net label (电源/全局符号) | `Name` | `Name` | Power symbols (VCC, GND, VCC_5V) store the net label in `Name`. **Part Reference is often empty** — do not use `FindObjectByProperty("Reference", "VCC_5V")`. |
|
|
17
|
+
| Value (值) | `Value` | — | Component value (e.g. `10k`, `NCE30P50G`). |
|
|
18
|
+
| Footprint (封装) | `Footprint` or `pcbFootprint` | — | Prefer reading back with `GetObjectProperty` to confirm the active key on a given object. |
|
|
19
|
+
| Description | `Description` | — | Free-text description when present. |
|
|
20
|
+
| MPN | `MPN` | — | Manufacturer part number in symbol properties. |
|
|
21
|
+
|
|
22
|
+
## Find vs Set Asymmetry
|
|
23
|
+
|
|
24
|
+
HQSCH maps designator lookup and designator mutation to **different keys**:
|
|
25
|
+
|
|
26
|
+
1. **Locate** a part by designator → `FindObjectByProperty` with
|
|
27
|
+
`propKey = "Reference"`, `propValue = "Q31"`.
|
|
28
|
+
2. **Read or change** the designator → `GetObjectProperty` /
|
|
29
|
+
`SetObjectProperty` with `propKey = "Part Reference"`.
|
|
30
|
+
|
|
31
|
+
Do not assume the key used for find is valid for set.
|
|
32
|
+
|
|
33
|
+
## Response Shape Notes
|
|
34
|
+
|
|
35
|
+
| API | Field | Correct usage |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `FindObjectByProperty` | `objectIds` | Repeated field — use `objectIds[0]`, not `objectId`. |
|
|
38
|
+
| `SetObjectProperty` | `op` | Use `OBJ_PROP_SET` (value `1`) for replacement. |
|
|
39
|
+
| `SetObjectProperty` | `commitUndo` | Set `true` to record the edit on the editor undo stack. |
|
|
40
|
+
|
|
41
|
+
## Rename Designator Workflow
|
|
42
|
+
|
|
43
|
+
1. `project-get-active-project` → build `ProjectContext`.
|
|
44
|
+
2. `canvas-find-object-by-property` — `propKey: "Reference"`, `propValue: "<old>"`.
|
|
45
|
+
3. Take `objectIds[0]` from the response.
|
|
46
|
+
4. Optional: `canvas-get-object-property` — `propKey: "Part Reference"` to confirm current value.
|
|
47
|
+
5. `canvas-set-object-property` — `propKey: "Part Reference"`, `propValue: "<new>"`, `op: OBJ_PROP_SET`, `commitUndo: true`.
|
|
48
|
+
6. Verify with another `GetObjectProperty` or `FindObjectByProperty`.
|
|
49
|
+
|
|
50
|
+
## Pin Coordinates and Wiring
|
|
51
|
+
|
|
52
|
+
After placement, **always save `response.object_id`** from
|
|
53
|
+
`PlacePartFromLibrary` / `PlaceSymbolFromLibrary` / `PlaceKicadSymbol`.
|
|
54
|
+
Use that id for wiring — not designator strings alone.
|
|
55
|
+
|
|
56
|
+
### Wiring priority
|
|
57
|
+
|
|
58
|
+
| Priority | Skill | When to use |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| **1 (preferred)** | `canvas-auto-connect-objects-by-id` | You have both placement `object_id`s (works for C1 ↔ VCC/GND power symbols). |
|
|
61
|
+
| **2** | `obj-place-place-wire` | After `kernel-get-snapshot` pin lookup via `canvas_object_id`. |
|
|
62
|
+
| **3** | `canvas-auto-connect-by-part-reference` | Both endpoints are regular parts with stable `Reference` designators (R/C/U). |
|
|
63
|
+
| **Avoid** | Estimated coords (placement Y ± offset) | Never guess pin positions — wires will miss pins. |
|
|
64
|
+
|
|
65
|
+
For nets with **three or more nodes**, also read
|
|
66
|
+
[`placement-conventions.md` — Schematic wire routing (avoid overlap)](placement-conventions.md#schematic-wire-routing-avoid-overlap):
|
|
67
|
+
separate branch lanes at placement and chain-connect (do not hub from one anchor).
|
|
68
|
+
|
|
69
|
+
### Option 1 — AutoConnect by object id (recommended)
|
|
70
|
+
|
|
71
|
+
Uses the same `object_id` returned by placement RPCs directly — no snapshot
|
|
72
|
+
lookup required:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// After placing C1, VCC_5V, GND — save each object_id
|
|
76
|
+
await getSkill("canvas-auto-connect-objects-by-id")!.execute(ctx, {
|
|
77
|
+
context: projectCtx,
|
|
78
|
+
objectId1: c1ObjectId,
|
|
79
|
+
pinNum1: [1],
|
|
80
|
+
objectId2: vccObjectId,
|
|
81
|
+
pinNum2: [0], // power / GND / port / off-page symbol → always 0
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await getSkill("canvas-auto-connect-objects-by-id")!.execute(ctx, {
|
|
85
|
+
context: projectCtx,
|
|
86
|
+
objectId1: c1ObjectId,
|
|
87
|
+
pinNum1: [2],
|
|
88
|
+
objectId2: gndObjectId,
|
|
89
|
+
pinNum2: [0], // power / GND / port / off-page symbol → always 0
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Proto fields: `object_id_1`, `pin_num_1` (repeated int32), `object_id_2`,
|
|
94
|
+
`pin_num_2`.
|
|
95
|
+
|
|
96
|
+
**Symbol pin numbers:** when either endpoint is a **power / GND / hierarchical
|
|
97
|
+
port / off-page** symbol (`PlaceSymbolFromLibrary`), pass **`pinNum: [0]`** for
|
|
98
|
+
that side. Do not use `1` — the engine treats symbol connection points as pin `0`.
|
|
99
|
+
Regular parts use their printed pin numbers.
|
|
100
|
+
|
|
101
|
+
### Option 2 — PlaceWire after snapshot pin lookup
|
|
102
|
+
|
|
103
|
+
Pin positions come from **`kernel-get-snapshot`**, not property RPCs.
|
|
104
|
+
|
|
105
|
+
**Primary lookup:** `canvas_object_id` — same value as placement
|
|
106
|
+
`object_id`. Do not rely on `designator` or `Name` alone (power symbols have
|
|
107
|
+
empty Part Reference; duplicate names may exist on one page).
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const PAGE_TOP = 1400; // PAGE_SIZE_START_Y — page top canvas anchor
|
|
111
|
+
|
|
112
|
+
function canvasPinToExt(x: bigint, y: bigint) {
|
|
113
|
+
return { x: Number(x), y: PAGE_TOP - Number(y) };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getPinExtByObjectId(
|
|
117
|
+
snap: KernelSnapshot,
|
|
118
|
+
objectId: bigint,
|
|
119
|
+
pinNum: string,
|
|
120
|
+
): { x: number; y: number } | null {
|
|
121
|
+
const oid = objectId.toString();
|
|
122
|
+
const si = snap.symbolInstances?.find(
|
|
123
|
+
(s) => String(s.canvasObjectId) === oid,
|
|
124
|
+
);
|
|
125
|
+
if (!si) return null;
|
|
126
|
+
const defId = si.definitionId;
|
|
127
|
+
const symDef = snap.symbolDefinitions?.find((sd) => sd.id === defId);
|
|
128
|
+
const pinInst = snap.pinInstances?.find((p) => {
|
|
129
|
+
if (String(p.canvasObjectId) !== oid) return false;
|
|
130
|
+
const def = symDef?.pins?.find((pd) => pd.id === p.pinDefinitionId);
|
|
131
|
+
return def?.number === pinNum;
|
|
132
|
+
});
|
|
133
|
+
if (!pinInst?.position) return null;
|
|
134
|
+
return canvasPinToExt(pinInst.position.x!, pinInst.position.y!);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Workflow:
|
|
139
|
+
|
|
140
|
+
1. `PlacePartFromLibrary` / `PlaceSymbolFromLibrary` → save `response.object_id`.
|
|
141
|
+
2. `kernel-get-snapshot`.
|
|
142
|
+
3. `symbolInstances.find(s => s.canvasObjectId === object_id)`.
|
|
143
|
+
4. `pinInstances` filtered by `canvasObjectId === object_id` and pin number via
|
|
144
|
+
`symbolDefinitions[].pins[].number`.
|
|
145
|
+
5. Read `pinInstances[].position` (canvas coordinates, Y **up**), convert, then
|
|
146
|
+
`obj-place-place-wire`.
|
|
147
|
+
|
|
148
|
+
**Fallback (human-readable only):** match `symbolInstances[].designator` (e.g.
|
|
149
|
+
`Q31`) when `canvas_object_id` is unavailable — not reliable for power symbols.
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
extY = PAGE_SIZE_START_Y - canvasY // PAGE_SIZE_START_Y ≈ 1400 (page top anchor)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`obj-place-place-wire` (`PlaceWire`) uses the **same external display
|
|
156
|
+
coordinates** as placement RPCs — page top-left origin, Y **down**. The server
|
|
157
|
+
converts internally (`canvasY = PAGE_SIZE_START_Y - extY`).
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// geometry.start / geometry.end — external display coords
|
|
161
|
+
await client.objPlace.placeWire({
|
|
162
|
+
context: projectCtx,
|
|
163
|
+
geometry: {
|
|
164
|
+
start: { x: BigInt(extA.x), y: BigInt(extA.y) },
|
|
165
|
+
end: { x: BigInt(extB.x), y: BigInt(extB.y) },
|
|
166
|
+
},
|
|
167
|
+
snapToGrid: true,
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Use orthogonal segments (horizontal then vertical) when pins are not aligned.
|
|
172
|
+
|
|
173
|
+
**Do not use** `canvas-auto-connect-by-part-reference-single-pin` for power
|
|
174
|
+
symbols — their `Part Reference` is empty so `partRef2: "VCC_5V"` will not
|
|
175
|
+
resolve. Use `canvas-auto-connect-objects-by-id` instead.
|
|
176
|
+
|
|
177
|
+
**Common mistake:** passing raw `pinInstances[].position` (canvas, Y-up) directly
|
|
178
|
+
to `PlaceWire` — wire placement will fail or land off-grid. Always convert first.
|
|
179
|
+
|
|
180
|
+
**Common mistake:** locating power-symbol pins by `FindObjectByProperty("Reference",
|
|
181
|
+
"VCC_5V")` or by snapshot `designator` — use placement `object_id` →
|
|
182
|
+
`canvas_object_id` instead.
|
|
183
|
+
|
|
184
|
+
**Common mistake:** using placement center coordinates ± offset as pin endpoints.
|
|
185
|
+
|
|
186
|
+
## Kernel Snapshot Lag
|
|
187
|
+
|
|
188
|
+
`GetSnapshot` may lag the canvas immediately after placement — newly placed
|
|
189
|
+
objects may be missing from `symbolInstances` / `pinInstances` / `labels`, or
|
|
190
|
+
`canvas_object_id` may not yet be populated. Mitigations:
|
|
191
|
+
|
|
192
|
+
1. **Prefer `canvas-auto-connect-objects-by-id`** — does not require snapshot.
|
|
193
|
+
2. If using snapshot pin lookup, **retry** `kernel-get-snapshot` (e.g. 3–5 times,
|
|
194
|
+
200–500 ms apart) before falling back.
|
|
195
|
+
3. For designator state after `SetObjectProperty`, use `GetObjectProperty` /
|
|
196
|
+
`FindObjectByProperty` — not snapshot `designator`.
|
|
197
|
+
|
|
198
|
+
**Enumerating unknown port / NetAlias names:** use `GetSnapshot` —
|
|
199
|
+
`labels[].text` for net aliases, and `symbolInstances[].designator` for
|
|
200
|
+
hierarchical ports / off-page / power. Each also carries owning page via
|
|
201
|
+
`schematic_name` / `page_name` (top-level after SDK regen, or
|
|
202
|
+
`metadata.properties` keys `schematic_name` / `page_name` today).
|
|
203
|
+
**Do not** guess candidate names and poll `FindObjectByProperty(Name=…)`.
|
|
204
|
+
|
|
205
|
+
## Required Capabilities
|
|
206
|
+
|
|
207
|
+
| Capability | Purpose |
|
|
208
|
+
| --- | --- |
|
|
209
|
+
| `project-get-active-project` | Resolve active project / `ProjectContext`. |
|
|
210
|
+
| `canvas-find-object-by-property` | Locate objects by designator (`Reference`) or net label (`Name`). |
|
|
211
|
+
| `canvas-get-object-property` | Read property values (`Part Reference`, `Value`, …). |
|
|
212
|
+
| `canvas-set-object-property` | Write property values (`Part Reference`, `Value`, …). |
|
|
213
|
+
| `canvas-auto-connect-objects-by-id` | Wire two placed objects by `object_id` + pin numbers. |
|
|
214
|
+
| `kernel-get-snapshot` | Pin positions via `canvas_object_id`, net graph. |
|
|
215
|
+
| `obj-place-place-wire` | Place wire segments between converted pin coordinates. |
|
|
216
|
+
|
|
217
|
+
## Critical Guardrails
|
|
218
|
+
|
|
219
|
+
- Never use `Reference` as `propKey` in `SetObjectProperty` for designator changes.
|
|
220
|
+
- Never use `JSON.stringify()` on protobuf responses — use `toJsonString()` from `@huaqiu/hqeda`.
|
|
221
|
+
- When `FindObjectByProperty` returns multiple `objectIds`, disambiguate with
|
|
222
|
+
`objTypeFilter` or additional property constraints before mutating.
|
|
223
|
+
- Report designators exactly as shown on the schematic (e.g. `Q31`, `U1`).
|
|
224
|
+
- Never invent candidate port/NetAlias names to poll `FindObjectByProperty` —
|
|
225
|
+
enumerate via `GetSnapshot` (`labels` / `symbolInstances`) first.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# HQ EDA Skill Quickstart
|
|
2
|
+
|
|
3
|
+
End-to-end chain for running a generated skill against a live HQ EDA editor.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- HQ EDA desktop runtime running locally
|
|
8
|
+
- Node.js 18+
|
|
9
|
+
- Packages: `@huaqiu/hqeda` and `@huaqiu/huaqiu-client`
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @huaqiu/hqeda @huaqiu/huaqiu-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Step 1 — Discover running editors
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { listEditors } from "@huaqiu/huaqiu-client";
|
|
19
|
+
|
|
20
|
+
const editors = await listEditors();
|
|
21
|
+
console.log(editors);
|
|
22
|
+
// [{ instanceId: "...", pid: 12345, ... }, ...]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
When multiple editors are running, pass `instanceId` explicitly to `connect()`.
|
|
26
|
+
|
|
27
|
+
## Step 2 — Connect
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { connect } from "@huaqiu/huaqiu-client";
|
|
31
|
+
|
|
32
|
+
const client = await connect();
|
|
33
|
+
// Or target a specific instance:
|
|
34
|
+
// const client = await connect({ instanceId: editors[0].instanceId });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`connect()` returns an `EditorClient` with service accessors (`client.project`, `client.edaBom`, `client.canvasOps`, …).
|
|
38
|
+
|
|
39
|
+
## Step 3 — Build context (when required)
|
|
40
|
+
|
|
41
|
+
Many RPCs require a `context` field. Build it from the connected client:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Editor-level context (project listing, active project discovery)
|
|
45
|
+
const editorContext = client.createEditorContext();
|
|
46
|
+
|
|
47
|
+
// Project-scoped context (canvas, selection, BOM for one project)
|
|
48
|
+
const projectContext = client.createProjectContext("my-project");
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Obtain `projectId` via [`project-get-active-project`](../project/get-active-project/SKILL.md) or [`project-list-open-projects`](../project/list-open-projects/SKILL.md).
|
|
52
|
+
|
|
53
|
+
## Step 4 — Execute a skill
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { getSkill, toJsonString } from "@huaqiu/hqeda";
|
|
57
|
+
|
|
58
|
+
const skill = getSkill("bom-get-bom");
|
|
59
|
+
if (!skill) throw new Error("Skill not found: bom-get-bom");
|
|
60
|
+
|
|
61
|
+
const result = await skill.execute(
|
|
62
|
+
{ client },
|
|
63
|
+
{ projectId: "my-project" },
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log(toJsonString(result, { prettySpaces: 2 }));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Step 5 — Serialize responses
|
|
70
|
+
|
|
71
|
+
Always use `toJsonString()` or `toJson()` from `@huaqiu/hqeda` — never `JSON.stringify()` directly on protobuf messages. See [serialization.md](serialization.md).
|
|
72
|
+
|
|
73
|
+
## Minimal complete script
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { connect } from "@huaqiu/huaqiu-client";
|
|
77
|
+
import { getSkill, toJsonString } from "@huaqiu/hqeda";
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const client = await connect();
|
|
81
|
+
const active = await client.project.getActiveProject({
|
|
82
|
+
context: client.createEditorContext(),
|
|
83
|
+
});
|
|
84
|
+
const projectId = active.project?.projectId;
|
|
85
|
+
if (!projectId) throw new Error("No active project");
|
|
86
|
+
|
|
87
|
+
const skill = getSkill("bom-get-bom");
|
|
88
|
+
const bom = await skill!.execute({ client }, { projectId });
|
|
89
|
+
console.log(toJsonString(bom, { prettySpaces: 2 }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main().catch(console.error);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Install a single skill (Agent)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx skills add Huaqiu-Electronics/skills --path eda/bom/get-bom
|
|
99
|
+
```
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Reading a circuit (understanding an existing design)
|
|
2
|
+
|
|
3
|
+
Use this when the task is **read-only understanding**, not drawing: "what does
|
|
4
|
+
VR201 do?", "how is this powered?", "why is this net named X?".
|
|
5
|
+
|
|
6
|
+
The goal is to reconstruct the **electrical topology plus device semantics**
|
|
7
|
+
without hand-parsing wire geometry or guessing.
|
|
8
|
+
|
|
9
|
+
## The one call that does most of the work
|
|
10
|
+
|
|
11
|
+
`kernel.GetSnapshot({ context })` returns a **whole-project** snapshot in a
|
|
12
|
+
single unary RPC (~1.6 s for 160 parts / 602 wires / 181 nets). It is the
|
|
13
|
+
primary data source for circuit understanding.
|
|
14
|
+
|
|
15
|
+
Measured scope: snapshot nets (181) == `netList.GetProjectNetList` nets (181),
|
|
16
|
+
while the current page held only 132 nets. So **snapshot is project-wide, not
|
|
17
|
+
page-scoped** — you do not need to walk pages.
|
|
18
|
+
|
|
19
|
+
| Entity | Count here | What it gives you |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `symbolInstances` | 160 | designator, definitionId, position, rotation, mirrored, **metadata.properties** — includes parts, power globals, **hierarchical ports**, and **off-page connectors** (`designator` = Name) |
|
|
22
|
+
| `pinInstances` | 612 | `symbolInstanceId`, `pinDefinitionId`, **absolute position** |
|
|
23
|
+
| `nets` | 181 | `name`, `netClass`, `pinInstanceIds[]` |
|
|
24
|
+
| `symbolDefinitions` | 58 | `name`, **`pins[]` with `number` + `name`** (pin-number → pin-name table) |
|
|
25
|
+
| `wireSegments` | 602 | geometry (rarely needed for semantics) |
|
|
26
|
+
| `labels` | (when used) | NetAlias instances: `text` (= name), `position`, `netId`, `metadata.id` (= canvas dbId when present) |
|
|
27
|
+
| `junctions` | (when used) | wire junctions |
|
|
28
|
+
| `noConnects` | (when used) | no-connect markers |
|
|
29
|
+
|
|
30
|
+
### Enumerating ports / net labels (do not guess names)
|
|
31
|
+
|
|
32
|
+
**Never** invent candidate names and poll `FindObjectByProperty(Name=…)`. Use one snapshot:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const snap = await client.kernel.getSnapshot({ context: ctx });
|
|
36
|
+
const s = snap.snapshot!;
|
|
37
|
+
|
|
38
|
+
// Net aliases — text + owning schematic/page
|
|
39
|
+
const labels = (s.labels ?? []).map((l) => ({
|
|
40
|
+
name: l.text,
|
|
41
|
+
schematic: l.schematicName
|
|
42
|
+
?? l.metadata?.properties?.find((p) => p.key === "schematic_name")?.stringValue
|
|
43
|
+
?? "",
|
|
44
|
+
page: l.pageName
|
|
45
|
+
?? l.metadata?.properties?.find((p) => p.key === "page_name")?.stringValue
|
|
46
|
+
?? "",
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// Ports / off-page / power — designator + schematic/page (same property keys)
|
|
50
|
+
const ports = (s.symbolInstances ?? []).map((x) => ({
|
|
51
|
+
name: x.designator,
|
|
52
|
+
schematic: x.schematicName
|
|
53
|
+
?? x.metadata?.properties?.find((p) => p.key === "schematic_name")?.stringValue
|
|
54
|
+
?? "",
|
|
55
|
+
page: x.pageName
|
|
56
|
+
?? x.metadata?.properties?.find((p) => p.key === "page_name")?.stringValue
|
|
57
|
+
?? "",
|
|
58
|
+
}));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`Label` / `SymbolInstance` proto fields: `schematic_name`, `page_name`.
|
|
62
|
+
Until the published SDK regenerates those top-level fields, they are also present as
|
|
63
|
+
`metadata.properties` with keys `schematic_name` / `page_name`.
|
|
64
|
+
|
|
65
|
+
`FindObjectByProperty` is only for **known** names after this enumeration.
|
|
66
|
+
|
|
67
|
+
### Cross-page / cross-hierarchy port pairing (do not guess)
|
|
68
|
+
|
|
69
|
+
Different page ports can share one design net **even when names differ**
|
|
70
|
+
(e.g. `/TYPE-C` `USB_D+` ↔ `/ESP32-C3FH4` `GPIO18`). That comes from hierarchical
|
|
71
|
+
block-pin ↔ port merge on the parent sheet — **not** from GUID matching.
|
|
72
|
+
|
|
73
|
+
**Never** invent same-name rules for this case. Call:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const pairing = await client.netList.getPortPairing({});
|
|
77
|
+
for (const p of pairing.pairings ?? []) {
|
|
78
|
+
console.log(
|
|
79
|
+
`${p.portA?.occPath} ${p.portA?.pageName}/${p.portA?.name}` +
|
|
80
|
+
` ↔ ${p.portB?.occPath} ${p.portB?.pageName}/${p.portB?.name}` +
|
|
81
|
+
` on ${p.netName} rule=${p.rule}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| `rule` | Meaning |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `BY_NAME` | Same-name Off-page / Hierarchical Port within a schematic folder |
|
|
89
|
+
| `BY_HIERARCHY` | Parent hierarchical block pin joins child ports (may be **different names**) |
|
|
90
|
+
| `BY_GLOBAL` | Design-wide Power / Global name merge |
|
|
91
|
+
|
|
92
|
+
Recommended flow: `GetSnapshot` (enumerate ports) → `GetPortPairing` (cross-page edges)
|
|
93
|
+
→ `GetProjectNetList` only if you need pin membership of the merged net.
|
|
94
|
+
|
|
95
|
+
### Reconstructing pin → net (verified recipe)
|
|
96
|
+
|
|
97
|
+
Three joins give the full topology. This was verified end-to-end against VR201:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const snap = await client.kernel.getSnapshot({ context: ctx });
|
|
101
|
+
const s = snap.snapshot!;
|
|
102
|
+
|
|
103
|
+
// 1. designator → instance
|
|
104
|
+
const sym = s.symbolInstances.find((x) => x.designator === "VR201");
|
|
105
|
+
const symId = sym.metadata!.id; // e.g. "/-74618591"
|
|
106
|
+
|
|
107
|
+
// 2. pin-number → pin-name (from the symbol definition)
|
|
108
|
+
const def = s.symbolDefinitions.find((d) => d.name === sym.definitionId);
|
|
109
|
+
const pinName = new Map(def!.pins.map((p) => [p.number, p.name]));
|
|
110
|
+
// → 1=OUTPUT 2=SENSE 3=GND 4=SHDN# 5=VIN
|
|
111
|
+
|
|
112
|
+
// 3. pin instance → net
|
|
113
|
+
const netOf = new Map<string, string>();
|
|
114
|
+
for (const net of s.nets)
|
|
115
|
+
for (const pid of net.pinInstanceIds) netOf.set(pid, net.name);
|
|
116
|
+
|
|
117
|
+
// 4. join
|
|
118
|
+
for (const p of s.pinInstances.filter((x) => x.symbolInstanceId === symId)) {
|
|
119
|
+
const pn = p.metadata!.id; // "/-74618591-1"
|
|
120
|
+
const num = p.pinDefinitionId; // "1"
|
|
121
|
+
console.log(`pin${num} ${pinName.get(num)} → ${netOf.get(pn) ?? "(unconnected)"} @${p.position}`);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Output (matches the netlist exactly, and **additionally** shows the
|
|
126
|
+
unconnected pin and the physical coordinates):
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
pin1 OUTPUT → +3.3V @(1890,670)
|
|
130
|
+
pin2 SENSE → +3.3V @(1890,660)
|
|
131
|
+
pin3 GND → GND @(1890,650)
|
|
132
|
+
pin4 SHDN# → (unconnected) @(1770,670)
|
|
133
|
+
pin5 VIN → N0261718 @(1770,660)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Key advantages over `netList`:
|
|
137
|
+
|
|
138
|
+
- **Unconnected pins are visible.** `pin4 SHDN#` shows up here; the netlist
|
|
139
|
+
only lists pins that belong to a net, so floating pins disappear.
|
|
140
|
+
- **Absolute pin coordinates**, in kernel canvas coordinates (Y up).
|
|
141
|
+
- **`rotation` / `mirrored`** for each instance.
|
|
142
|
+
|
|
143
|
+
> Beware BigInt: `position.x/y` are `bigint`. `JSON.stringify` throws
|
|
144
|
+
> "Do not know how to serialize a BigInt" — pass a replacer:
|
|
145
|
+
> `JSON.stringify(v, (_k, x) => typeof x === "bigint" ? String(x) : x)`.
|
|
146
|
+
|
|
147
|
+
## Device semantics are already in the snapshot — do not fetch datasheets
|
|
148
|
+
|
|
149
|
+
`symbolInstance.metadata.properties` carries a rich electrical parameter set
|
|
150
|
+
harvested from the part database. **Read it before going online.**
|
|
151
|
+
|
|
152
|
+
Real dump for `VR201`:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Datasheet = http://file.elecfans.com/.../pYYBAGDuZ16ARrigAALCJORq5X4437.pdf
|
|
156
|
+
Description = Micropower Low Dropout Regulator with Shutdown,
|
|
157
|
+
4.15 to 30 V Vin, 5 V Vout, 5-pin FM (T-5), 0 to 125 degC
|
|
158
|
+
MPN = LT1129CT-5#06PBF
|
|
159
|
+
Manufacturer = Analog Devices
|
|
160
|
+
dropout_voltage = 450 mV
|
|
161
|
+
input_voltage = 30 V, 4.15 V
|
|
162
|
+
output_voltage = 5 V
|
|
163
|
+
output_current = 700 mA
|
|
164
|
+
supply_current = 50 μA
|
|
165
|
+
power_supply_rejection_ratio = 64 dB
|
|
166
|
+
topology = 正,固定式
|
|
167
|
+
features = Enable
|
|
168
|
+
operating_temperature = 0 °C, 125 °C
|
|
169
|
+
package = TO-220-5
|
|
170
|
+
huaqiu_pn = G7258042
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
For `U202` (MAX202ESE+T) the same mechanism yields `interface=RS-232`,
|
|
174
|
+
`data_rate=120 kb/s`, `driver_receiver=2/2`, `supply_voltage=5.5 V`, etc.
|
|
175
|
+
|
|
176
|
+
**Consequence:** an "is this part appropriate for its net?" check is fully
|
|
177
|
+
local. Comparing `output_voltage = 5 V` against the net name `+3.3V` surfaces
|
|
178
|
+
the mismatch with zero network access.
|
|
179
|
+
|
|
180
|
+
Property keys vary by part family (a regulator exposes `topology` /
|
|
181
|
+
`dropout_voltage`; a transceiver exposes `interface` / `data_rate`). Treat the
|
|
182
|
+
set as open — read the keys, do not assume a fixed schema.
|
|
183
|
+
|
|
184
|
+
## Which comprehension interfaces actually work
|
|
185
|
+
|
|
186
|
+
**Canonical map (works / ban / hollow):** [rpc-availability.md](./rpc-availability.md).
|
|
187
|
+
|
|
188
|
+
Summary: **Snapshot + `netList` is the whole toolbox for comprehension.** Do not
|
|
189
|
+
build on `GetConnectivity`, selection, ERC, export, runtime, or `FindNet`
|
|
190
|
+
(silent `success:false`). Advanced helpers (`GetObjectsJsonByIds`, `FindObject`,
|
|
191
|
+
`GetSelectionNetList`) are optional — not required for Flow B.
|
|
192
|
+
|
|
193
|
+
Timing baseline: `GetSnapshot` ~1.6–3.2 s for a mid-size project; do not treat
|
|
194
|
+
that as a hang.
|
|
195
|
+
|
|
196
|
+
## Project identification — do not assume the project
|
|
197
|
+
|
|
198
|
+
`getActiveProject` returns whichever schematic is focused. The project can
|
|
199
|
+
change between calls (the user may switch tabs, or the editor may restart).
|
|
200
|
+
Always read `projectId` from the response and print it, rather than reusing a
|
|
201
|
+
cached one.
|
|
202
|
+
|
|
203
|
+
Note the active project is identified by **file path** as the id, e.g.
|
|
204
|
+
`C:/Users/Administrator/Desktop/kit-dev-coldfire.HQSch`.
|
|
205
|
+
|
|
206
|
+
The editor instance can also restart (new `instanceId`, new pid), which clears
|
|
207
|
+
the live canvas. After a restart the canvas is empty while the saved project
|
|
208
|
+
file still holds data — that is expected, not a bug. Re-check
|
|
209
|
+
`GetPageOccupancy` before assuming a prior placement persists.
|
|
210
|
+
|
|
211
|
+
## Practical workflow
|
|
212
|
+
|
|
213
|
+
1. `project.GetActiveProject` → print `projectId`. Do not cache across tasks.
|
|
214
|
+
2. `kernel.GetSnapshot` once — it answers most questions.
|
|
215
|
+
3. Build the three maps (designator→instance, pinDef→name, pinInstance→net).
|
|
216
|
+
4. For semantics, read `metadata.properties` of the instance.
|
|
217
|
+
5. **Neighborhood expansion** — for each pin of the target REF, list other
|
|
218
|
+
members on the same net (grouped by pin). See below.
|
|
219
|
+
6. **Optional series traversal** — `TRACE=1` on `read-circuit.ts` to follow
|
|
220
|
+
C/R/L/FB two-terminal bridges to the far-side net. No extra RPC.
|
|
221
|
+
7. **Canvas edit ids** — print `metadata.id`, `canvasObjectId`, and
|
|
222
|
+
`FindObjectByProperty(Reference)` for Flow C. See [editing-a-circuit.md](./editing-a-circuit.md).
|
|
223
|
+
8. Only if a project is large enough to make the 1.6 s snapshot painful,
|
|
224
|
+
fall back to `netList.GetProjectNetList` (~27 ms) for pure net queries.
|
|
225
|
+
|
|
226
|
+
## Standard answer template (agent natural language)
|
|
227
|
+
|
|
228
|
+
When answering "what does REF do?", structure the reply as:
|
|
229
|
+
|
|
230
|
+
1. **Identity** — `Description` / `MPN` from `metadata.properties`; if empty,
|
|
231
|
+
state definition name only — **do not invent datasheet values**.
|
|
232
|
+
2. **Pin → net** — table from snapshot join.
|
|
233
|
+
3. **Neighborhood** — per pin, who else shares each net (see next section).
|
|
234
|
+
4. **Series links (optional)** — if user asks about coupling/series path, enable
|
|
235
|
+
TRACE or describe C/R/L/FB bridges manually from snapshot.
|
|
236
|
+
5. **Consistency flags** — e.g. `output_voltage` vs rail net name.
|
|
237
|
+
6. **Role summary** — one paragraph: what this part does **in this local context**.
|
|
238
|
+
|
|
239
|
+
Template script: `template/scripts/read-circuit.ts` (`REF=VR201`).
|
|
240
|
+
|
|
241
|
+
## Neighborhood expansion (same-net, 1 hop)
|
|
242
|
+
|
|
243
|
+
After pin→net for target `REF`:
|
|
244
|
+
|
|
245
|
+
1. Collect connected nets (exclude `(unconnected)`).
|
|
246
|
+
2. For each net, reverse-map `nets[].pinInstanceIds` → designator + pin number/name.
|
|
247
|
+
3. **Group by pin** (not flat per net) so "what's on VIN?" is obvious.
|
|
248
|
+
4. Remove the target REF's own pins from neighbor lists.
|
|
249
|
+
5. **Large net summary** (GND, +5V, …): if >8 pin instances, show netClass,
|
|
250
|
+
total part/pin counts, prefix stats (`C×4, R×2`), and **first 5** representative neighbors.
|
|
251
|
+
6. **Role hints** (rule-based, not online): `C*` → bypass/filter cap;
|
|
252
|
+
`R*` → pull/divider; `U*`/`VR*` → active IC; power symbols → rail entry.
|
|
253
|
+
7. Attach `Value=` from neighbor `metadata.properties` when present.
|
|
254
|
+
|
|
255
|
+
Default depth: **1 hop** (same net only). Do not recursively expand every
|
|
256
|
+
neighbor's other nets unless the user asks for a subsystem view.
|
|
257
|
+
|
|
258
|
+
## Series traversal (C / R / L / ferrite bead)
|
|
259
|
+
|
|
260
|
+
**No new engine RPC.** Build a client-side graph from one snapshot:
|
|
261
|
+
|
|
262
|
+
| Edge | Rule |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| Same net | All pins on net N are mutually reachable |
|
|
265
|
+
| Series passive | `C/R/L/FB*` with exactly **2 connected pins on 2 different nets** |
|
|
266
|
+
|
|
267
|
+
**Do not bridge:**
|
|
268
|
+
|
|
269
|
+
- Multi-pin ICs (`U*`, `VR*`, …) — stop at net boundary.
|
|
270
|
+
- **Shunt decoupling cap** — one leg on GND/VCC/POWER netClass, other on signal:
|
|
271
|
+
list as same-net neighbor only; **do not** traverse into the rail through the cap.
|
|
272
|
+
- Optional: stop penetration into huge GND/POWER nets after listing summary.
|
|
273
|
+
|
|
274
|
+
**BFS** from each pin of target REF (default `maxDepth=2`):
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
+3.3V —[C205 100nF]— NET_MCU_AVDD → U301(14 VDD)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Enable in template:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
REF=VR201 TRACE=1 npx tsx scripts/read-circuit.ts
|
|
284
|
+
REF=VR201 TRACE=1 TRACE_DEPTH=2 npx tsx scripts/read-circuit.ts
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Engine-side `GetConnectivity` with `edgeKind=series` would be more authoritative
|
|
288
|
+
(P2); skill uses this BFS until then — see [rpc-availability.md](./rpc-availability.md).
|
|
289
|
+
|
|
290
|
+
## Designator lookup and canvas id mapping
|
|
291
|
+
|
|
292
|
+
| Step | API / field |
|
|
293
|
+
| --- | --- |
|
|
294
|
+
| Exact REF | `symbolInstances.find(designator === REF)` |
|
|
295
|
+
| Fuzzy | prefix match / substring; print candidates — never guess silently |
|
|
296
|
+
| Snapshot join id | `symbolInstance.metadata.id` |
|
|
297
|
+
| Canvas mutate id | `canvasObjectId` **or** `FindObjectByProperty("Reference", REF).objectIds[0]` |
|
|
298
|
+
| Set designator | `SetObjectProperty` + **`Part Reference`** (not `Reference`) |
|
|
299
|
+
|
|
300
|
+
After Flow B, carry ids into Flow C — [editing-a-circuit.md](./editing-a-circuit.md).
|
|
301
|
+
|
|
302
|
+
## When properties are empty
|
|
303
|
+
|
|
304
|
+
Hand-drawn or non-CIS parts may have no `metadata.properties`:
|
|
305
|
+
|
|
306
|
+
- Use `symbolDefinitions` pin names + neighborhood topology.
|
|
307
|
+
- State uncertainty explicitly.
|
|
308
|
+
- **Forbidden:** fabricating voltage/current ratings or MPN from memory.
|
|
309
|
+
|
|
310
|
+
## Multi-part / subsystem questions
|
|
311
|
+
|
|
312
|
+
"How is this power block fed?" — expand by **net cluster**, not one REF:
|
|
313
|
+
|
|
314
|
+
1. Start from regulator VIN/EN nets (Flow B on REF).
|
|
315
|
+
2. Run `NET=<name>` mode on upstream nets.
|
|
316
|
+
3. Use `TRACE=1` for series elements between rails and loads.
|
|
317
|
+
4. Summarize chain: source → filter → regulator → load caps → loads.
|