dsh-eda-mcp 0.7.1 → 0.7.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/CHANGELOG.md +75 -0
- package/README.md +7 -0
- package/README.zh.md +7 -0
- package/docs/schematic-workflow.md +152 -0
- package/docs/tools.md +13 -9
- package/dsh-eda-bridge-extension-0.4.0.zip +0 -0
- package/lib/bridge.mjs +435 -128
- package/lib/client.js +17 -0
- package/lib/index.js +260 -5
- package/package.json +3 -3
- package/src/bridge/index.ts +430 -106
- package/src/bridge/workflow-guide.ts +45 -0
- package/src/client/EdaMcpSettingsCard.tsx +8 -0
- package/src/client/locales.ts +4 -0
- package/src/guidance.ts +4 -0
- package/src/index.ts +22 -0
- package/src/skills-content.ts +164 -0
- package/src/skills.ts +91 -0
- package/test/skills.test.ts +62 -0
package/lib/bridge.mjs
CHANGED
|
@@ -293,11 +293,50 @@ var BOARD_OUTLINE_WIDTH_MIL = 10;
|
|
|
293
293
|
var SILKSCREEN_HEIGHT_MIN_MM = 1;
|
|
294
294
|
var SILKSCREEN_WIDTH_MIN_MM = 0.15;
|
|
295
295
|
|
|
296
|
+
// src/bridge/workflow-guide.ts
|
|
297
|
+
var WORKFLOW_SUMMARY = [
|
|
298
|
+
"\u539F\u7406\u56FE\u5DE5\u4F5C\u89C4\u8303\uFF08\u9ED8\u8BA4\u5168\u90E8\u624B\u5DE5\uFF0C\u7981\u6B62\u56FE\u7701\u4E8B\u7528\u81EA\u52A8\u5E03\u5C40/\u81EA\u52A8\u5E03\u7EBF\uFF09\uFF1A",
|
|
299
|
+
"1) \u5E03\u5C40\uFF1A\u6309 10 \u5355\u4F4D\u6805\u683C\u5BF9\u9F50\uFF1B\u4FE1\u53F7\u4ECE\u5DE6\u5230\u53F3\u3001\u7535\u6E90\u5728\u4E0A\u3001\u5730\u5728\u4E0B\uFF1B\u540C\u7EA7\u5668\u4EF6\u7B49\u95F4\u8DDD\uFF1B\u4E0D\u538B\u5B57\u3001\u4E0D\u538B\u6807\u9898\u680F\uFF1B\u5148\u770B eda_get_page_info \u7684\u5B89\u5168\u533A\u518D\u6446\u4F4D\u3002",
|
|
300
|
+
"2) \u8FDE\u7EBF\uFF1A\u53EA\u7528 eda_draw_wire \u8D70\u76F4\u7EBF/45 \u5EA6\uFF1B**\u8DE8\u533A\u57DF\u540C\u540D\u7F51\u7EDC\u5FC5\u987B\u7528\u7F51\u7EDC\u7AEF\u53E3\uFF08eda_place_net_port\uFF09\u6216\u7F51\u7EDC\u6807\u7B7E**\uFF0C\u4E0D\u80FD\u9760\u5728\u4E24\u5904\u753B\u4E00\u6761\u53EB\u540C\u540D\u7684\u5BFC\u7EBF\u2014\u2014\u5BFC\u7EBF\u4E00\u65E6\u63A5\u89E6\u5F15\u811A\uFF0Cnet \u4F1A\u88AB\u8FDE\u901A\u6027\u91CD\u7B97\uFF0C\u6307\u5B9A\u540D\u4F1A\u88AB\u8986\u76D6\u3002",
|
|
301
|
+
"3) \u7F51\u7EDC\u6807\u7B7E\uFF1A\u5148\u8BD5 eda_place_net_label\uFF1B\u82E5\u8BE5\u6784\u5EFA\u7684 createNetLabel \u4E0D\u54CD\u5E94\uFF0C\u4F1A\u81EA\u52A8\u964D\u7EA7\u4E3A\u7EAF\u6587\u672C\u6807\u7B7E\uFF08\u65E0\u7535\u6C14\u8BED\u4E49\uFF09\uFF0C\u6B64\u65F6\u9760 stub \u5BFC\u7EBF\u627F\u8F7D\u7F51\u7EDC\uFF1B\u6279\u91CF\u7528 eda_place_net_labels \u4E00\u6B21\u8C03\u7528\uFF0C\u4E0D\u8981\u4E32\u884C\u4E0A\u767E\u6B21\u3002",
|
|
302
|
+
"4) \u6821\u9A8C\uFF1Aeda_export_netlist \u770B\u6BCF\u4E2A\u7F51\u7EDC\u7684\u7AEF\u70B9\u662F\u5426 \u22652\uFF1Beda_run_drc \u770B\u9010\u6761 DRC \u660E\u7EC6\uFF1B\u89C6\u89C9\u68C0\u67E5\u4F18\u5148\u5BFC\u51FA PNG \u81EA\u884C\u8BC6\u522B\uFF0C\u800C\u4E0D\u662F\u8BA9 EDA \u91CD\u6392\u3002",
|
|
303
|
+
"5) \u53EA\u6709\u7528\u6237\u660E\u786E\u8981\u6C42\u65F6\u624D eda_auto_layout / eda_auto_route\uFF08\u9700 force:true \u4E14\u8BBE\u7F6E\u91CC\u653E\u884C\uFF09\u3002",
|
|
304
|
+
"6) \u5F15\u811A\u4FE1\u606F\u7528 eda_get_pins \u4E00\u6B21\u62FF\u5168\uFF08\u542B pinName/pinNumber\uFF09\uFF0C\u4E0D\u8981\u9010\u811A\u8C03 raw API\u3002"
|
|
305
|
+
].join("\n");
|
|
306
|
+
var WORKFLOW_GUIDE = WORKFLOW_SUMMARY + "\n\n" + [
|
|
307
|
+
"## \u5E03\u5C40\u7EC6\u5219\u662F\u600E\u4E48\u843D\u5730\u7684",
|
|
308
|
+
"",
|
|
309
|
+
"- \u5750\u6807\u5355\u4F4D\u662F 0.01 \u82F1\u5BF8\uFF08MCP \u5355\u4F4D\uFF09\uFF1BA4 \u56FE\u6846 0..1170 / 0..825\uFF0C\u6807\u9898\u680F\u5360 right 460..1160 / bottom 10..190\uFF0C\u5B89\u5168\u533A\u89C1 eda_get_page_info\u3002",
|
|
310
|
+
"- \u4E00\u9875\u53EA\u653E\u4E00\u4E2A\u529F\u80FD\u5757\uFF1B\u5668\u4EF6\u6309\u4FE1\u53F7\u6D41\u4ECE\u5DE6\u5230\u53F3\u6392\uFF0C\u53CD\u9988\u5143\u4EF6\u653E\u5728\u653E\u5927\u5668\u4E0B\u65B9\uFF0C\u53BB\u8026\u7535\u5BB9\u8D34\u8FD1\u7535\u6E90\u811A\u3002",
|
|
311
|
+
"- \u5668\u4EF6\u4E4B\u95F4\u7559\u51FA\u81F3\u5C11 20 \u5355\u4F4D\u7528\u4E8E\u8D70\u7EBF\u548C\u6807\u7B7E\uFF1B\u7F51\u7EDC\u7AEF\u53E3\u653E\u5728\u6A21\u5757\u8FB9\u7F18\uFF0C\u5DE6\u53F3\u5404\u4E00\u5217\u3002",
|
|
312
|
+
"- \u6446\u597D\u4E00\u4E2A\u6A21\u5757\u518D\u6446\u4E0B\u4E00\u4E2A\uFF1B\u6BCF\u6B21\u6446\u5B8C\u7528 eda_get_components \u68C0\u67E5\u6CA1\u6709\u91CD\u53E0\uFF08\u5305\u56F4\u76D2\u76F8\u4EA4\uFF09\u3002",
|
|
313
|
+
"",
|
|
314
|
+
"## \u7F51\u7EDC\u4E0E\u6807\u7B7E",
|
|
315
|
+
"",
|
|
316
|
+
"- eda_place_net_port \u4EA7\u751F\u771F\u6B63\u7684\u7F51\u7EDC\u7AEF\u53E3\u56FE\u5143\uFF0C\u662F\u8DE8\u533A\u57DF\u8FDE\u63A5\u7684\u9996\u9009\u3002",
|
|
317
|
+
"- eda_place_net_label \u4F18\u5148\u8C03 sch_PrimitiveAttribute.createNetLabel\uFF1B\u8BE5 API \u5728 3.2.186 \u4E0A\u4E0D\u54CD\u5E94\uFF0C\u63A2\u6D4B\u7ED3\u8BBA\u6309\u4F1A\u8BDD\u7F13\u5B58\uFF0C\u4E4B\u540E\u76F4\u63A5\u964D\u7EA7\u4E3A\u6587\u672C\u6807\u7B7E\uFF08\u4E0D\u518D\u6BCF\u6B21\u4ED8 4 \u79D2\u8D85\u65F6\uFF09\u3002",
|
|
318
|
+
"- \u964D\u7EA7\u540E\u7684\u6587\u672C\u6807\u7B7E\u6CA1\u6709\u7535\u6C14\u8BED\u4E49\uFF1A\u8FDE\u901A\u6027\u7531 eda_place_net_label_at_pin \u540C\u65F6\u753B\u7684 stub \u5BFC\u7EBF\u627F\u62C5\u3002",
|
|
319
|
+
"- eda_get_net_labels \u4F1A\u540C\u65F6\u5217\u51FA\u5C5E\u6027\u56FE\u5143\uFF08netlabel/netport/netflag\uFF09\u548C\u6587\u672C\u6807\u7B7E\uFF0C\u5220\u9664\u7528 eda_delete_primitive \u7684\u5BF9\u5E94 kind\u3002",
|
|
320
|
+
"- \u6279\u91CF\u653E\u7F6E\u7528 eda_place_net_labels([{pinPrimitiveId, net}, ...])\uFF0C\u4E00\u6B21\u8C03\u7528\u5B8C\u6210\u5E76\u9010\u6761\u56DE\u62A5\u6210\u8D25\u3002",
|
|
321
|
+
"",
|
|
322
|
+
"## \u5BFC\u7EBF net \u88AB\u8986\u76D6\uFF08\u52A1\u5FC5\u77E5\u9053\uFF09",
|
|
323
|
+
"",
|
|
324
|
+
"eda_draw_wire \u4F20\u7684 net \u53EA\u5BF9**\u60AC\u7A7A\u5BFC\u7EBF**\u6709\u6548\uFF1B\u5BFC\u7EBF\u7AEF\u70B9\u4E00\u65E6\u843D\u5728\u5F15\u811A/\u7F51\u7EDC\u4E0A\uFF0CEDA \u4F1A\u6309\u8FDE\u901A\u6027\u91CD\u7B97\u7F51\u7EDC\u540D\uFF08\u5B9E\u6D4B TESTNET \u5E76\u5165\u4E86 LED_A\uFF09\u3002\u6240\u4EE5\u201C\u540C\u540D\u5BFC\u7EBF = \u540C\u540D\u7F51\u7EDC\u201D\u4E0D\u6210\u7ACB\uFF0C\u8DE8\u533A\u57DF\u540C\u540D\u5FC5\u987B\u7528\u7F51\u7EDC\u6807\u7B7E\u6216\u7F51\u7EDC\u7AEF\u53E3\u3002",
|
|
325
|
+
"",
|
|
326
|
+
"## \u6821\u9A8C\u987A\u5E8F",
|
|
327
|
+
"",
|
|
328
|
+
"1. eda_get_active_document \u786E\u8BA4\u5728\u539F\u7406\u56FE\u9875\uFF08\u4E0D\u662F HOME/PCB\uFF09\u3002",
|
|
329
|
+
"2. eda_export_netlist\uFF08\u5DF2\u89E3\u6790\u6210\u5BF9\u8C61\uFF09\u68C0\u67E5\u6BCF\u4E2A\u7F51\u7EDC \u22652 \u7AEF\u70B9\u3001\u65E0\u60AC\u7A7A\u3002",
|
|
330
|
+
"3. eda_run_drc \u770B\u9010\u6761\u660E\u7EC6\uFF08\u542B primitiveId \u4E0E\u5750\u6807\uFF09\u3002",
|
|
331
|
+
"4. \u9700\u8981\u4EBA\u770B\u6548\u679C\u5C31\u5BFC\u51FA\u56FE\u7247\u81EA\u884C\u8BC6\u522B\uFF0C\u4E0D\u8981\u7528\u81EA\u52A8\u5E03\u5C40\u201C\u4FEE\u201D\u7248\u9762\u3002"
|
|
332
|
+
].join("\n");
|
|
333
|
+
|
|
296
334
|
// src/bridge/index.ts
|
|
297
335
|
var VERSION = "0.4.0";
|
|
298
336
|
var WS_PORT = Number(process.env.DSH_EDA_MCP_WS_PORT ?? 39009);
|
|
299
337
|
var WS_HOST = "127.0.0.1";
|
|
300
338
|
var ALLOW_RAW_API = (process.env.DSH_EDA_MCP_ALLOW_RAW_API ?? "1") !== "0";
|
|
339
|
+
var ALLOW_AUTO_LAYOUT = (process.env.DSH_EDA_ALLOW_AUTO_LAYOUT ?? "0") === "1";
|
|
301
340
|
var TOOL_TIMEOUT_MS = Number(process.env.DSH_EDA_MCP_TOOL_TIMEOUT_MS ?? 6e4);
|
|
302
341
|
var MCP_UNITS_PER_INCH = 100;
|
|
303
342
|
var PAPER_SIZES = {
|
|
@@ -321,6 +360,13 @@ function safeAreas(paperName) {
|
|
|
321
360
|
lowerLeft: { left: 40, bottom: 40, right: paper.titleBlock.left - 20, top: paper.titleBlock.top - 10 }
|
|
322
361
|
};
|
|
323
362
|
}
|
|
363
|
+
function decodeTextPayload(value) {
|
|
364
|
+
const text = typeof value === "string" ? value : "";
|
|
365
|
+
if (text === "") return text;
|
|
366
|
+
const looksUtf16 = text.charCodeAt(0) === 65279 || text.length > 3 && text.charCodeAt(1) === 0 && text.charCodeAt(3) === 0;
|
|
367
|
+
if (!looksUtf16) return text;
|
|
368
|
+
return Buffer.from(text, "latin1").toString("utf16le").replace(String.fromCharCode(65279), "");
|
|
369
|
+
}
|
|
324
370
|
function checkPointInFrame(x, y, paperName, margin = 0) {
|
|
325
371
|
const paper = paperOf(paperName);
|
|
326
372
|
const f = paper.frame;
|
|
@@ -539,11 +585,11 @@ var TOOLS = [
|
|
|
539
585
|
}, ["libraryUuid", "uuid", "x", "y"]) },
|
|
540
586
|
{ name: "eda_place_net_flag", description: "Place a power/ground net flag (VCC/GND etc.) on the active schematic page.", inputSchema: objectSchema({ identification: { type: "string", enum: ["Power", "Ground", "AnalogGround", "ProtectGround"] }, net: { type: "string" }, x: { type: "number" }, y: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] }, mirror: { type: "boolean" }, paper: { type: "string" }, allowOutsideFrame: { type: "boolean" } }, ["identification", "net", "x", "y"]) },
|
|
541
587
|
{ name: "eda_place_net_port", description: "Place a net port on the active schematic page.", inputSchema: objectSchema({ direction: { type: "string", enum: ["IN", "OUT", "BI"] }, net: { type: "string" }, x: { type: "number" }, y: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] }, mirror: { type: "boolean" }, paper: { type: "string" }, allowOutsideFrame: { type: "boolean" } }, ["direction", "net", "x", "y"]) },
|
|
542
|
-
{ name: "eda_get_pins", description: "List pins of one schematic component. Use the returned primitiveId with eda_place_net_flag_at_pin, eda_place_net_label_at_pin, or eda_set_no_connect.", inputSchema: objectSchema({ componentPrimitiveId: { type: "string" } }, ["componentPrimitiveId"]) },
|
|
588
|
+
{ name: "eda_get_pins", description: "List pins of one schematic component in a single round trip, including pinName and pinNumber (no per-pin sch_PrimitivePin.get needed). Use the returned primitiveId with eda_place_net_flag_at_pin, eda_place_net_label_at_pin, or eda_set_no_connect.", inputSchema: objectSchema({ componentPrimitiveId: { type: "string" } }, ["componentPrimitiveId"]) },
|
|
543
589
|
{ name: "eda_place_net_flag_at_pin", description: "Place a power/ground net flag at a pin WITHOUT covering the symbol: the pin direction is read from the pin rotation, a stub wire of stubLength MCP units (default 20) is drawn outwards from the pin with the net name, and the flag is placed at its end. Pass offsetX/offsetY to place it manually (that disables the stub) or stubLength=0 for the old on-pin behaviour.", inputSchema: objectSchema({ pinPrimitiveId: { type: "string" }, identification: { type: "string", enum: ["Power", "Ground", "AnalogGround", "ProtectGround"] }, net: { type: "string" }, stubLength: { type: "number" }, offsetX: { type: "number" }, offsetY: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] }, mirror: { type: "boolean" }, color: { type: "string" }, lineWidth: { type: "integer" }, lineType: { type: "integer" }, paper: { type: "string" }, allowOutsideFrame: { type: "boolean" } }, ["pinPrimitiveId", "identification", "net"]) },
|
|
544
590
|
{ name: "eda_place_net_label_at_pin", description: "Place a REAL net label at a pin (sch_PrimitiveAttribute.createNetLabel; falls back to text and reports method) WITHOUT covering the symbol: a stub wire of stubLength MCP units (default 20) is drawn outwards along the pin direction and the label goes at its end. offsetX/offsetY place it manually (disables the stub); stubLength=0 keeps the old on-pin behaviour.", inputSchema: objectSchema({ pinPrimitiveId: { type: "string" }, net: { type: "string" }, stubLength: { type: "number" }, offsetX: { type: "number" }, offsetY: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] }, fontSize: { type: "number" }, color: { type: "string" }, lineWidth: { type: "integer" }, lineType: { type: "integer" }, paper: { type: "string" }, allowOutsideFrame: { type: "boolean" } }, ["pinPrimitiveId", "net"]) },
|
|
545
591
|
{ name: "eda_set_no_connect", description: "Set or clear the no-connect flag on one or more schematic pins via sch_PrimitivePin.modify.", inputSchema: objectSchema({ primitiveIds: { type: "array", items: { type: "string" } }, noConnected: { type: "boolean" } }, ["primitiveIds"]) },
|
|
546
|
-
{ name: "eda_draw_wire", description: "Draw a schematic wire (polyline). Coordinates are MCP units (0.01 inch).", inputSchema: objectSchema({ points: { type: "array", items: { type: "number" } }, net: { type: "string" }, color: { type: "string" }, lineWidth: { type: "integer", minimum: 1, maximum: 10 }, lineType: { type: "integer", enum: [0, 1, 2, 3] } }, ["points"]) },
|
|
592
|
+
{ name: "eda_draw_wire", description: "Draw a schematic wire (polyline). Coordinates are MCP units (0.01 inch). IMPORTANT: the net argument only sticks on a floating wire \u2014 as soon as an endpoint touches a pin, EDA recomputes the net from connectivity and the name you passed is overwritten (measured: TESTNET merged into LED_A). To join same-named nets across regions use eda_place_net_port or a net label; do not rely on two distantly drawn wires sharing a name.", inputSchema: objectSchema({ points: { type: "array", items: { type: "number" } }, net: { type: "string" }, color: { type: "string" }, lineWidth: { type: "integer", minimum: 1, maximum: 10 }, lineType: { type: "integer", enum: [0, 1, 2, 3] } }, ["points"]) },
|
|
547
593
|
{ name: "eda_draw_rectangle", description: "Draw a rectangle. topLeftY is the UPPER y coordinate (max y); height extends downward. Coordinates are MCP units.", inputSchema: objectSchema({ topLeftX: { type: "number" }, topLeftY: { type: "number" }, width: { type: "number" }, height: { type: "number" }, cornerRadius: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] }, color: { type: "string" }, fillColor: { type: "string" }, lineWidth: { type: "integer", minimum: 1, maximum: 10 }, lineType: { type: "integer", enum: [0, 1, 2, 3] }, fillStyle: { type: "string" } }, ["topLeftX", "topLeftY", "width", "height"]) },
|
|
548
594
|
{ name: "eda_draw_functional_box", description: "Draw a dashed functional-group box around one group of components, with an optional label. Margins and coordinates are MCP units; default margin 1 unit (about 0.254 mm).", inputSchema: objectSchema({ componentIds: { type: "array", items: { type: "string" } }, label: { type: "string" }, margin: { type: "number" }, color: { type: "string" }, lineWidth: { type: "integer", minimum: 1, maximum: 10 }, lineType: { type: "integer", enum: [0, 1, 2, 3] } }) },
|
|
549
595
|
{ name: "eda_draw_circle", description: "Draw a circle. Coordinates are MCP units.", inputSchema: objectSchema({ centerX: { type: "number" }, centerY: { type: "number" }, radius: { type: "number" }, color: { type: "string" }, fillColor: { type: "string" }, lineWidth: { type: "integer", minimum: 1, maximum: 10 }, lineType: { type: "integer", enum: [0, 1, 2, 3] }, fillStyle: { type: "string" } }, ["centerX", "centerY", "radius"]) },
|
|
@@ -556,14 +602,18 @@ var TOOLS = [
|
|
|
556
602
|
{ name: "eda_get_nets", description: "List nets of the current schematic project. Uses sch_Net.* first; when the schematic is not the active document those APIs return [] and this tool falls back to parsing sch_Netlist.getNetlist, reporting which source was used.", inputSchema: objectSchema({ name: { type: "string" }, parseNetlist: { type: "boolean" } }) },
|
|
557
603
|
{ name: "eda_get_active_document", description: "Return the document/tab that is currently active (documentType, uuid, tabId, parent project). Most tab-dependent APIs (sch_Net.*, netlist export, DRC) only work when the matching document is active - use eda_open_document to switch.", inputSchema: objectSchema({}) },
|
|
558
604
|
{ name: "eda_open_document", description: "Open/activate a document tab by uuid (dmt_EditorControl.openDocument). Works for a schematic page or a PCB, so tab-dependent APIs can run.", inputSchema: objectSchema({ documentUuid: { type: "string" }, splitScreenId: { type: "string" } }, ["documentUuid"]) },
|
|
559
|
-
{ name: "eda_get_net_labels", description: "List
|
|
560
|
-
{ name: "eda_place_net_label", description: "Place a
|
|
561
|
-
{ name: "
|
|
562
|
-
{ name: "
|
|
563
|
-
{ name: "
|
|
605
|
+
{ name: "eda_get_net_labels", description: "List every label-like primitive on the active schematic page: attribute primitives (netlabel/netport/netflag) PLUS plain texts left by the createNetLabel fallback, so they can be audited and deleted. Reports kind/primitiveId/net per entry.", inputSchema: objectSchema({ parentPrimitiveId: { type: "string" } }) },
|
|
606
|
+
{ name: "eda_place_net_label", description: "Place a net label at x/y. The real API (sch_PrimitiveAttribute.createNetLabel) is probed once per session; when the build never answers, a plain TEXT label is placed instead, which carries no electrical meaning and must be paired with a stub wire. Pass probe:true to re-check.", inputSchema: objectSchema({ x: { type: "number" }, y: { type: "number" }, net: { type: "string" }, fontSize: { type: "number" }, rotation: { type: "integer", enum: [0, 90, 180, 270] } }, ["x", "y", "net"]) },
|
|
607
|
+
{ name: "eda_place_net_labels", description: "Place many net labels in ONE call (per-pin or by coordinate) and report done/failed per item: a 133-label schematic used to exceed the caller timeout with no progress. items: [{ pinPrimitiveId, net, stubLength?, offsetX?, offsetY? }] or [{ x, y, net, fontSize? }].", inputSchema: objectSchema({ items: { type: "array", items: { type: "object", additionalProperties: true } }, probe: { type: "boolean" } }, ["items"]) },
|
|
608
|
+
{ name: "eda_run_drc", description: "Run schematic DRC with includeVerboseError and return one entry per problem (type, message, primitiveId, x/y plus the raw record) instead of bare counts.", inputSchema: objectSchema({ strict: { type: "boolean" } }) },
|
|
609
|
+
{ name: "eda_auto_layout", description: "Run schematic auto-layout (sch_Document.autoLayout). DISABLED by default: automated layout is not used unless the user asks for it. Pass force:true only then.", inputSchema: objectSchema({ uuids: { type: "array", items: { type: "string" } }, force: { type: "boolean" } }) },
|
|
610
|
+
{ name: "eda_auto_route", description: "Run schematic auto-routing (sch_Document.autoRouting). DISABLED by default: route by hand unless the user explicitly asks. Pass force:true only then.", inputSchema: objectSchema({ uuids: { type: "array", items: { type: "string" } }, force: { type: "boolean" } }) },
|
|
564
611
|
{ name: "eda_export_netlist", description: "Export the schematic netlist as text (default JLCEDA format). Returns null when no schematic is active - check eda_get_active_document / open the schematic page first.", inputSchema: objectSchema({ type: { type: "string" } }) },
|
|
565
|
-
{ name: "eda_export_bom", description: "Export the schematic BOM. csv
|
|
566
|
-
{ name: "
|
|
612
|
+
{ name: "eda_export_bom", description: "Export the schematic BOM. csv is decoded from UTF-16 to readable text; xlsx returns a base64 data URL.", inputSchema: objectSchema({ format: { type: "string", enum: ["csv", "xlsx"] } }) },
|
|
613
|
+
{ name: "eda_move_component", description: "Move/rotate/mirror a schematic component (sch_PrimitiveComponent.modify) without deleting and re-placing it, so the designator and value survive.", inputSchema: objectSchema({ primitiveId: { type: "string" }, x: { type: "number" }, y: { type: "number" }, rotation: { type: "number" }, mirror: { type: "boolean" } }, ["primitiveId"]) },
|
|
614
|
+
{ name: "eda_workflow_guide", description: "Return the schematic working standard: grid-aligned manual placement, signal flow, when net labels/ports are required (a wire net name is overridden by connectivity), net-label fallback behaviour, batch placement and the verification order. Read it before laying out a sheet.", inputSchema: objectSchema({}) },
|
|
615
|
+
{ name: "eda_list_api_methods", description: 'List every API method the connected extension accepts (its allow-list). Call it when eda_api_call answers "API method not allowed".', inputSchema: objectSchema({}) },
|
|
616
|
+
{ name: "eda_delete_primitive", description: 'Delete a schematic primitive by kind and primitive id. Net ports, net flags and net labels are attribute primitives and need kind "netport"/"netflag"/"netlabel"; the text fallback labels are kind "text".', inputSchema: objectSchema({ kind: { type: "string", enum: ["component", "wire", "rectangle", "circle", "arc", "polygon", "text", "netport", "netflag", "netlabel"] }, primitiveId: { type: "string" } }, ["kind", "primitiveId"]) },
|
|
567
617
|
{ name: "eda_create_board", description: "Create a board linking a schematic and a PCB (dmt_Board.createBoard).", inputSchema: objectSchema({ schematicUuid: { type: "string" }, pcbUuid: { type: "string" } }) },
|
|
568
618
|
{ name: "eda_api_call", description: "Call an allow-listed JLCEDA Pro extension API method directly.", inputSchema: objectSchema({ method: { type: "string" }, args: { type: "array" } }, ["method", "args"]) },
|
|
569
619
|
{ name: "pcb_get_components", description: "List PCB components (mil). includePins=true also reads pcb_PrimitiveComponent.getAllPinsByPrimitiveId per component, which is the accurate pad/net source; the fast summary pad list can be stale.", inputSchema: objectSchema({ layer: { type: ["string", "integer"] }, locked: { type: "boolean" }, includeBounds: { type: "boolean" }, includePins: { type: "boolean" } }) },
|
|
@@ -747,6 +797,8 @@ var WsConnection = class {
|
|
|
747
797
|
var clients = /* @__PURE__ */ new Set();
|
|
748
798
|
var pendingCalls = /* @__PURE__ */ new Map();
|
|
749
799
|
var pcbComponentBaseline = null;
|
|
800
|
+
var netLabelSupport = null;
|
|
801
|
+
var placedTextLabels = [];
|
|
750
802
|
function sleep(ms) {
|
|
751
803
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
752
804
|
}
|
|
@@ -1213,30 +1265,88 @@ async function parseNetlist(previous, timeoutMs) {
|
|
|
1213
1265
|
const nets = [...byNet.entries()].map(([net, pins]) => ({ net, pins, pinCount: pins.length }));
|
|
1214
1266
|
return { source: "sch_Netlist.getNetlist", count: nets.length, nets, netlistBytes: text.length };
|
|
1215
1267
|
}
|
|
1216
|
-
async function placeNetLabel(net, x, y, fontSize, timeoutMs) {
|
|
1268
|
+
async function placeNetLabel(net, x, y, fontSize, timeoutMs, options = {}) {
|
|
1217
1269
|
if (net === "" || net === "undefined") throw new Error("a net name is required");
|
|
1218
1270
|
assertNetName(String(net), "eda_place_net_label");
|
|
1219
1271
|
const size = fontSize === void 0 ? 12 : Number(fontSize);
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1272
|
+
if (options.probe === true) netLabelSupport = null;
|
|
1273
|
+
let failure = null;
|
|
1274
|
+
if (netLabelSupport !== false) {
|
|
1275
|
+
try {
|
|
1276
|
+
const raw2 = await callExtension("sch_PrimitiveAttribute.createNetLabel", [x, y, net], Math.min(timeoutMs, 4e3));
|
|
1277
|
+
if (raw2 === void 0 || raw2 === null) throw new Error("createNetLabel returned nothing");
|
|
1278
|
+
netLabelSupport = true;
|
|
1279
|
+
return { method: "netLabel", net, x, y, label: summarizePrimitive(raw2) };
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1282
|
+
if (!/not found|not available|not allowed|timed out|timeout|nothing|undefined/i.test(message)) throw error;
|
|
1283
|
+
failure = message;
|
|
1284
|
+
netLabelSupport = false;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
const raw = await callExtension("sch_PrimitiveText.create", [x, y, net, 0, null, null, size, false, false, false, 1], timeoutMs);
|
|
1288
|
+
const label = summarizePrimitive(raw);
|
|
1289
|
+
if (label.primitiveId !== void 0) {
|
|
1290
|
+
placedTextLabels.push({ net: String(net), x, y, primitiveId: label.primitiveId });
|
|
1291
|
+
if (placedTextLabels.length > 2e3) placedTextLabels.shift();
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
method: "text",
|
|
1295
|
+
net,
|
|
1296
|
+
x,
|
|
1297
|
+
y,
|
|
1298
|
+
created: label.primitiveId !== void 0,
|
|
1299
|
+
warning: "createNetLabel is unavailable on this EDA build (" + (failure ?? "probe already failed this session") + "), so a plain TEXT label was placed: it carries no electrical meaning. The stub wire drawn by the caller is what joins the net; eda_get_net_labels lists these texts by name and id.",
|
|
1300
|
+
...label.primitiveId === void 0 ? { hint: "No label primitive was created: the schematic page must be the ACTIVE document (sch_PrimitiveText.create is tab-dependent). Use eda_get_active_document / eda_open_document, then retry." } : {},
|
|
1301
|
+
label
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
async function placeAtPin(toolName, args, timeoutMs) {
|
|
1305
|
+
const pinPrimitiveId = String(args.pinPrimitiveId ?? "");
|
|
1306
|
+
if (pinPrimitiveId === "") throw new Error("pinPrimitiveId is required");
|
|
1307
|
+
const isFlag = toolName === "eda_place_net_flag_at_pin";
|
|
1308
|
+
assertNetName(String(args.net ?? ""), toolName);
|
|
1309
|
+
const pin = await callExtension("sch_PrimitivePin.get", [pinPrimitiveId], timeoutMs);
|
|
1310
|
+
const pinRecord = asRecord(pin);
|
|
1311
|
+
const pinX = Number(pickValue(pinRecord, "X", "x"));
|
|
1312
|
+
const pinY = Number(pickValue(pinRecord, "Y", "y"));
|
|
1313
|
+
if (!Number.isFinite(pinX) || !Number.isFinite(pinY)) throw new Error("could not read pin position for " + pinPrimitiveId);
|
|
1314
|
+
const pinRotation = Number(pickValue(pinRecord, "Rotation", "rotation") ?? 0);
|
|
1315
|
+
const direction = pinDirection(pinRotation);
|
|
1316
|
+
const explicitOffset = args.offsetX !== void 0 || args.offsetY !== void 0;
|
|
1317
|
+
const stubLength = args.stubLength === void 0 ? explicitOffset ? 0 : 20 : Math.max(0, Number(args.stubLength));
|
|
1318
|
+
const x = pinX + direction.dx * stubLength + Number(args.offsetX ?? 0);
|
|
1319
|
+
const y = pinY + direction.dy * stubLength + Number(args.offsetY ?? 0);
|
|
1320
|
+
if (args.allowOutsideFrame !== true) {
|
|
1321
|
+
const problem = checkPointInFrame(x, y, args.paper, 5) ?? checkPointAgainstTitleBlock(x, y, args.paper, 5);
|
|
1322
|
+
if (problem !== void 0) {
|
|
1323
|
+
throw new Error(toolName + " refused: " + problem + " - reduce stubLength or pass offsetX/offsetY to place it elsewhere.");
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
let stub;
|
|
1327
|
+
if (stubLength > 0 && typeof args.net === "string" && args.net !== "") {
|
|
1328
|
+
stub = summarizePrimitive(await callExtension("sch_PrimitiveWire.create", [
|
|
1329
|
+
[pinX, pinY, x, y],
|
|
1330
|
+
String(args.net),
|
|
1331
|
+
args.color === void 0 ? void 0 : String(args.color),
|
|
1332
|
+
args.lineWidth === void 0 ? void 0 : Number(args.lineWidth),
|
|
1333
|
+
args.lineType === void 0 ? void 0 : Number(args.lineType)
|
|
1334
|
+
], timeoutMs));
|
|
1335
|
+
}
|
|
1336
|
+
const placement = { pin: { primitiveId: pinPrimitiveId, x: pinX, y: pinY, rotation: pinRotation }, direction, stubLength, x, y, stub };
|
|
1337
|
+
if (isFlag) {
|
|
1338
|
+
const flag = await callExtension("sch_PrimitiveComponent.createNetFlag", [
|
|
1339
|
+
String(args.identification),
|
|
1340
|
+
String(args.net),
|
|
1232
1341
|
x,
|
|
1233
1342
|
y,
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
};
|
|
1343
|
+
args.rotation === void 0 ? pinRotation : Number(args.rotation),
|
|
1344
|
+
Boolean(args.mirror ?? false)
|
|
1345
|
+
], timeoutMs);
|
|
1346
|
+
return { ...placement, flag: summarizePrimitive(flag) };
|
|
1239
1347
|
}
|
|
1348
|
+
const label = await placeNetLabel(String(args.net), x, y, args.fontSize, timeoutMs);
|
|
1349
|
+
return { ...label, ...placement };
|
|
1240
1350
|
}
|
|
1241
1351
|
function isInternalHelperMissing(error) {
|
|
1242
1352
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1269,10 +1379,10 @@ async function resolveProjectUuid(args, timeoutMs) {
|
|
|
1269
1379
|
const teamUuid = String(pickValue(asRecord(team), "uuid", "Uuid") ?? "");
|
|
1270
1380
|
if (teamUuid === "") continue;
|
|
1271
1381
|
const owned = await callExtension("dmt_Project.getAllProjectsUuid", [teamUuid], toolTimeout(args, timeoutMs)).catch(() => []);
|
|
1272
|
-
for (const
|
|
1273
|
-
if (seen.has(
|
|
1274
|
-
seen.add(
|
|
1275
|
-
candidates.push(
|
|
1382
|
+
for (const uuid2 of Array.isArray(owned) ? owned.map(String) : []) {
|
|
1383
|
+
if (seen.has(uuid2)) continue;
|
|
1384
|
+
seen.add(uuid2);
|
|
1385
|
+
candidates.push(uuid2);
|
|
1276
1386
|
}
|
|
1277
1387
|
}
|
|
1278
1388
|
}
|
|
@@ -1291,6 +1401,40 @@ async function resolveProjectUuid(args, timeoutMs) {
|
|
|
1291
1401
|
}
|
|
1292
1402
|
return { uuid: matched.uuid, matched };
|
|
1293
1403
|
}
|
|
1404
|
+
async function waitForActiveProject(uuid2, timeoutMs = 4e3) {
|
|
1405
|
+
const deadline = Date.now() + timeoutMs;
|
|
1406
|
+
let lastActive = null;
|
|
1407
|
+
for (; ; ) {
|
|
1408
|
+
try {
|
|
1409
|
+
const info = asRecord(await callExtension("dmt_Project.getCurrentProjectInfo", [], 3e3));
|
|
1410
|
+
lastActive = String(pickValue(info, "uuid", "Uuid") ?? "") || null;
|
|
1411
|
+
if (lastActive === uuid2) return { active: true, project: info };
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1414
|
+
if (Date.now() > deadline) return { active: false, project: null, lastActive };
|
|
1415
|
+
await sleep(400);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function internalIdsOf(info) {
|
|
1419
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1420
|
+
const boards = Array.isArray(asRecord(info).data) ? asRecord(info).data : [];
|
|
1421
|
+
for (const board of boards) {
|
|
1422
|
+
const record = asRecord(board);
|
|
1423
|
+
const boardUuid = String(pickValue(record, "uuid", "Uuid") ?? "");
|
|
1424
|
+
if (boardUuid !== "") ids.add(boardUuid);
|
|
1425
|
+
for (const docKey of ["schematic", "pcb"]) {
|
|
1426
|
+
const doc = asRecord(record[docKey]);
|
|
1427
|
+
const docUuid = String(pickValue(doc, "uuid", "Uuid") ?? "");
|
|
1428
|
+
if (docUuid !== "") ids.add(docUuid);
|
|
1429
|
+
const pages = Array.isArray(doc.pages) ? doc.pages : [];
|
|
1430
|
+
for (const page of pages) {
|
|
1431
|
+
const pageUuid = String(pickValue(asRecord(page), "uuid", "Uuid") ?? "");
|
|
1432
|
+
if (pageUuid !== "") ids.add(pageUuid);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return ids;
|
|
1437
|
+
}
|
|
1294
1438
|
async function executeTool(name, rawArgs, timeoutMs) {
|
|
1295
1439
|
const args = asRecord(rawArgs);
|
|
1296
1440
|
const tool = PCB_TOOL_ALIASES[name] ?? name;
|
|
@@ -1361,24 +1505,45 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1361
1505
|
saveTo
|
|
1362
1506
|
], toolTimeout(args, timeoutMs));
|
|
1363
1507
|
const raw = imported?.result ?? imported;
|
|
1364
|
-
const
|
|
1508
|
+
const uuid2 = typeof raw === "string" ? raw : typeof raw?.uuid === "string" ? raw.uuid : void 0;
|
|
1365
1509
|
const landed = raw !== null && raw !== void 0;
|
|
1366
1510
|
let opened = false;
|
|
1367
|
-
if (args.open === true &&
|
|
1511
|
+
if (args.open === true && uuid2 !== void 0) {
|
|
1368
1512
|
try {
|
|
1369
|
-
|
|
1513
|
+
await callExtension("dmt_Project.openProject", [uuid2], timeoutMs);
|
|
1514
|
+
opened = (await waitForActiveProject(uuid2, 4e3)).active;
|
|
1370
1515
|
} catch {
|
|
1371
1516
|
opened = false;
|
|
1372
1517
|
}
|
|
1373
1518
|
}
|
|
1519
|
+
let collisions = null;
|
|
1520
|
+
if (landed && uuid2 !== void 0 && args.checkCollisions !== false) {
|
|
1521
|
+
try {
|
|
1522
|
+
const mine = await callExtension("dmt_Project.getProjectInfo", [uuid2], toolTimeout(args, timeoutMs));
|
|
1523
|
+
const myIds = internalIdsOf(mine);
|
|
1524
|
+
const others = await callExtension("dmt_Project.getAllProjectsUuid", [], toolTimeout(args, timeoutMs));
|
|
1525
|
+
const list = (Array.isArray(others) ? others.map(String) : []).filter((other) => other !== uuid2).slice(0, 20);
|
|
1526
|
+
collisions = [];
|
|
1527
|
+
for (const other of list) {
|
|
1528
|
+
const info = await callExtension("dmt_Project.getProjectInfo", [other], timeoutMs).catch(() => void 0);
|
|
1529
|
+
const shared = [...internalIdsOf(info)].filter((sharedId) => myIds.has(sharedId));
|
|
1530
|
+
if (shared.length > 0) {
|
|
1531
|
+
collisions.push({ projectUuid: other, friendlyName: pickValue(asRecord(info), "friendlyName", "FriendlyName") ?? null, sharedIds: shared });
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
} catch {
|
|
1535
|
+
collisions = null;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1374
1538
|
return {
|
|
1375
1539
|
ok: landed,
|
|
1376
1540
|
imported: landed,
|
|
1377
1541
|
name: basename(path),
|
|
1378
1542
|
size: bytes.length,
|
|
1379
1543
|
teamUuid,
|
|
1380
|
-
uuid:
|
|
1544
|
+
uuid: uuid2 ?? null,
|
|
1381
1545
|
opened,
|
|
1546
|
+
collisions,
|
|
1382
1547
|
result: raw ?? null,
|
|
1383
1548
|
hint: landed ? null : "The app returned nothing: the import did not create a project. That means this build ignores the saveTo target (same limitation as createProject), so import cannot be used to create projects here."
|
|
1384
1549
|
};
|
|
@@ -1441,10 +1606,10 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1441
1606
|
const teamUuid = String(pickValue(asRecord(team), "uuid", "Uuid") ?? "");
|
|
1442
1607
|
if (teamUuid === "") continue;
|
|
1443
1608
|
const owned = await callExtension("dmt_Project.getAllProjectsUuid", [teamUuid], toolTimeout(args, timeoutMs)).catch(() => []);
|
|
1444
|
-
for (const
|
|
1445
|
-
if (seen.has(
|
|
1446
|
-
seen.add(
|
|
1447
|
-
list.push(
|
|
1609
|
+
for (const uuid2 of Array.isArray(owned) ? owned.map(String) : []) {
|
|
1610
|
+
if (seen.has(uuid2)) continue;
|
|
1611
|
+
seen.add(uuid2);
|
|
1612
|
+
list.push(uuid2);
|
|
1448
1613
|
}
|
|
1449
1614
|
}
|
|
1450
1615
|
aggregated = true;
|
|
@@ -1452,11 +1617,11 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1452
1617
|
if (args.resolveNames === false) return { count: list.length, uuids: list, aggregated };
|
|
1453
1618
|
const limit = Number.isFinite(Number(args.limit)) ? Math.max(1, Number(args.limit)) : list.length;
|
|
1454
1619
|
const projects = [];
|
|
1455
|
-
for (const
|
|
1620
|
+
for (const uuid2 of list.slice(0, limit)) {
|
|
1456
1621
|
try {
|
|
1457
|
-
projects.push(await callExtension("dmt_Project.getProjectInfo", [
|
|
1622
|
+
projects.push(await callExtension("dmt_Project.getProjectInfo", [uuid2], toolTimeout(args, timeoutMs)));
|
|
1458
1623
|
} catch {
|
|
1459
|
-
projects.push({ uuid });
|
|
1624
|
+
projects.push({ uuid: uuid2 });
|
|
1460
1625
|
}
|
|
1461
1626
|
}
|
|
1462
1627
|
return { count: list.length, resolved: projects.length, aggregated, projects };
|
|
@@ -1467,10 +1632,17 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1467
1632
|
}
|
|
1468
1633
|
case "eda_open_project": {
|
|
1469
1634
|
const resolvedProject = await resolveProjectUuid(args, timeoutMs);
|
|
1470
|
-
const
|
|
1471
|
-
const
|
|
1472
|
-
const
|
|
1473
|
-
return {
|
|
1635
|
+
const uuid2 = resolvedProject.uuid;
|
|
1636
|
+
const returned = await callExtension("dmt_Project.openProject", [uuid2], toolTimeout(args, timeoutMs));
|
|
1637
|
+
const activation = await waitForActiveProject(uuid2, args.verifyMs === void 0 ? 4e3 : Number(args.verifyMs));
|
|
1638
|
+
return {
|
|
1639
|
+
opened: activation.active,
|
|
1640
|
+
openReturned: returned !== false,
|
|
1641
|
+
uuid: uuid2,
|
|
1642
|
+
matched: resolvedProject.matched,
|
|
1643
|
+
current: activation.active ? summarizePrimitive(activation.project) : null,
|
|
1644
|
+
...activation.active ? {} : { hint: "The project did not become active within the verification window (last active: " + (activation.lastActive ?? "unknown") + "); it may still be loading, or the app refused the switch." }
|
|
1645
|
+
};
|
|
1474
1646
|
}
|
|
1475
1647
|
case "eda_get_workspace": {
|
|
1476
1648
|
const workspace = await callExtension("dmt_Workspace.getCurrentWorkspaceInfo", [], timeoutMs).catch(() => void 0);
|
|
@@ -1545,13 +1717,26 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1545
1717
|
uuid: args.sourceUuid,
|
|
1546
1718
|
name: args.sourceName
|
|
1547
1719
|
}, timeoutMs);
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1720
|
+
let copyRaw;
|
|
1721
|
+
try {
|
|
1722
|
+
copyRaw = await callExtension("dmt_Project.copyProject", [
|
|
1723
|
+
source.uuid,
|
|
1724
|
+
args.targetTeamUuid === void 0 ? void 0 : String(args.targetTeamUuid),
|
|
1725
|
+
args.targetFolderUuid === void 0 ? void 0 : String(args.targetFolderUuid),
|
|
1726
|
+
args.newFriendlyName === void 0 ? void 0 : String(args.newFriendlyName),
|
|
1727
|
+
args.newName === void 0 ? void 0 : String(args.newName)
|
|
1728
|
+
], toolTimeout(args, timeoutMs));
|
|
1729
|
+
} catch (failure) {
|
|
1730
|
+
throw new Error("eda_copy_project failed: " + (failure instanceof Error ? failure.message : String(failure)));
|
|
1731
|
+
}
|
|
1732
|
+
const copyUuid = typeof copyRaw === "string" ? copyRaw : String(pickValue(asRecord(copyRaw), "uuid", "Uuid") ?? "");
|
|
1733
|
+
return {
|
|
1734
|
+
copied: copyUuid !== "",
|
|
1735
|
+
sourceUuid: source.uuid,
|
|
1736
|
+
uuid: copyUuid === "" ? null : copyUuid,
|
|
1737
|
+
raw: copyRaw ?? null,
|
|
1738
|
+
hint: copyUuid === "" ? "The app returned no uuid and created nothing: copyProject does not work on this build for local projects (same family as createProject/deleteProject). Duplicate it in the app, or export with eda_export_project and import it back with eda_import_project to get an independent copy." : null
|
|
1739
|
+
};
|
|
1555
1740
|
return { copied: typeof uuid === "string" && uuid !== "", sourceUuid: source.uuid, uuid: uuid ?? null };
|
|
1556
1741
|
}
|
|
1557
1742
|
case "eda_create_project": {
|
|
@@ -1568,25 +1753,26 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1568
1753
|
throw new Error("eda_create_project: no team is available (sign in to a JLC account in JLCEDA Pro, or pass teamUuid from eda_list_teams)");
|
|
1569
1754
|
}
|
|
1570
1755
|
const folderUuid = args.folderUuid === void 0 ? "" : String(args.folderUuid).trim();
|
|
1571
|
-
const
|
|
1756
|
+
const uuid2 = await callExtension("dmt_Project.createProject", [
|
|
1572
1757
|
friendlyName,
|
|
1573
1758
|
args.name === void 0 ? void 0 : String(args.name),
|
|
1574
1759
|
teamUuid,
|
|
1575
1760
|
folderUuid === "" ? void 0 : folderUuid,
|
|
1576
1761
|
args.description === void 0 ? void 0 : String(args.description)
|
|
1577
1762
|
], toolTimeout(args, timeoutMs));
|
|
1578
|
-
if (typeof
|
|
1763
|
+
if (typeof uuid2 !== "string" || uuid2 === "") {
|
|
1579
1764
|
throw new Error("eda_create_project: the app returned no project uuid (team " + teamUuid + " from " + teamSource + "; the app may have shown a dialog or rejected the name)");
|
|
1580
1765
|
}
|
|
1581
1766
|
let opened = false;
|
|
1582
1767
|
if (args.open === true) {
|
|
1583
1768
|
try {
|
|
1584
|
-
|
|
1769
|
+
await callExtension("dmt_Project.openProject", [uuid2], timeoutMs);
|
|
1770
|
+
opened = (await waitForActiveProject(uuid2, 4e3)).active;
|
|
1585
1771
|
} catch {
|
|
1586
1772
|
opened = false;
|
|
1587
1773
|
}
|
|
1588
1774
|
}
|
|
1589
|
-
return { created: true, uuid, friendlyName, teamUuid, teamSource, folderUuid: folderUuid === "" ? null : folderUuid, opened };
|
|
1775
|
+
return { created: true, uuid: uuid2, friendlyName, teamUuid, teamSource, folderUuid: folderUuid === "" ? null : folderUuid, opened };
|
|
1590
1776
|
}
|
|
1591
1777
|
case "eda_launch": {
|
|
1592
1778
|
const probe = launchProbe();
|
|
@@ -1768,68 +1954,90 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1768
1954
|
const componentPrimitiveId = String(args.componentPrimitiveId ?? "");
|
|
1769
1955
|
if (componentPrimitiveId === "") throw new Error("componentPrimitiveId is required");
|
|
1770
1956
|
const raw = await callExtension("sch_PrimitiveComponent.getAllPinsByPrimitiveId", [componentPrimitiveId], timeoutMs);
|
|
1771
|
-
if (Array.isArray(raw)) return raw
|
|
1772
|
-
return raw
|
|
1957
|
+
if (!Array.isArray(raw)) return raw;
|
|
1958
|
+
return raw.map((entry) => {
|
|
1959
|
+
const record = asRecord(entry);
|
|
1960
|
+
return {
|
|
1961
|
+
primitiveId: pickValue(record, "PrimitiveId", "primitiveId"),
|
|
1962
|
+
pinNumber: pickValue(record, "PinNumber", "pinNumber") ?? null,
|
|
1963
|
+
pinName: pickValue(record, "PinName", "pinName") ?? null,
|
|
1964
|
+
x: pickValue(record, "X", "x"),
|
|
1965
|
+
y: pickValue(record, "Y", "y"),
|
|
1966
|
+
rotation: pickValue(record, "Rotation", "rotation") ?? 0,
|
|
1967
|
+
pinLength: pickValue(record, "PinLength", "pinLength") ?? null,
|
|
1968
|
+
noConnected: pickValue(record, "NoConnected", "noConnected") ?? null
|
|
1969
|
+
};
|
|
1970
|
+
});
|
|
1773
1971
|
}
|
|
1774
1972
|
case "eda_place_net_flag_at_pin":
|
|
1775
|
-
case "eda_place_net_label_at_pin":
|
|
1776
|
-
|
|
1777
|
-
if (pinPrimitiveId === "") throw new Error("pinPrimitiveId is required");
|
|
1778
|
-
assertNetName(String(args.net ?? ""), name === "eda_place_net_flag_at_pin" ? "eda_place_net_flag_at_pin" : "eda_place_net_label_at_pin");
|
|
1779
|
-
const pin = await callExtension("sch_PrimitivePin.get", [pinPrimitiveId], timeoutMs);
|
|
1780
|
-
const pinRecord = asRecord(pin);
|
|
1781
|
-
const pinX = Number(pickValue(pinRecord, "X", "x"));
|
|
1782
|
-
const pinY = Number(pickValue(pinRecord, "Y", "y"));
|
|
1783
|
-
if (!Number.isFinite(pinX) || !Number.isFinite(pinY)) throw new Error("could not read pin position for " + pinPrimitiveId);
|
|
1784
|
-
const pinRotation = Number(pickValue(pinRecord, "Rotation", "rotation") ?? 0);
|
|
1785
|
-
const direction = pinDirection(pinRotation);
|
|
1786
|
-
const explicitOffset = args.offsetX !== void 0 || args.offsetY !== void 0;
|
|
1787
|
-
const stubLength = args.stubLength === void 0 ? explicitOffset ? 0 : 20 : Math.max(0, Number(args.stubLength));
|
|
1788
|
-
const x = pinX + direction.dx * stubLength + Number(args.offsetX ?? 0);
|
|
1789
|
-
const y = pinY + direction.dy * stubLength + Number(args.offsetY ?? 0);
|
|
1790
|
-
if (args.allowOutsideFrame !== true) {
|
|
1791
|
-
const problem = checkPointInFrame(x, y, args.paper, 5) ?? checkPointAgainstTitleBlock(x, y, args.paper, 5);
|
|
1792
|
-
if (problem !== void 0) {
|
|
1793
|
-
throw new Error((name === "eda_place_net_flag_at_pin" ? "eda_place_net_flag_at_pin" : "eda_place_net_label_at_pin") + " refused: " + problem + " - reduce stubLength or pass offsetX/offsetY to place it elsewhere.");
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
let stub;
|
|
1797
|
-
if (stubLength > 0 && typeof args.net === "string" && args.net !== "") {
|
|
1798
|
-
stub = summarizePrimitive(await callExtension("sch_PrimitiveWire.create", [
|
|
1799
|
-
[pinX, pinY, x, y],
|
|
1800
|
-
String(args.net),
|
|
1801
|
-
args.color === void 0 ? void 0 : String(args.color),
|
|
1802
|
-
args.lineWidth === void 0 ? void 0 : Number(args.lineWidth),
|
|
1803
|
-
args.lineType === void 0 ? void 0 : Number(args.lineType)
|
|
1804
|
-
], timeoutMs));
|
|
1805
|
-
}
|
|
1806
|
-
const placement = { pin: { primitiveId: pinPrimitiveId, x: pinX, y: pinY, rotation: pinRotation }, direction, stubLength, x, y, stub };
|
|
1807
|
-
if (name === "eda_place_net_flag_at_pin") {
|
|
1808
|
-
const flag = await callExtension("sch_PrimitiveComponent.createNetFlag", [
|
|
1809
|
-
String(args.identification),
|
|
1810
|
-
String(args.net),
|
|
1811
|
-
x,
|
|
1812
|
-
y,
|
|
1813
|
-
args.rotation === void 0 ? pinRotation : Number(args.rotation),
|
|
1814
|
-
Boolean(args.mirror ?? false)
|
|
1815
|
-
], timeoutMs);
|
|
1816
|
-
return { ...placement, flag: summarizePrimitive(flag) };
|
|
1817
|
-
}
|
|
1818
|
-
const label = await placeNetLabel(String(args.net), x, y, args.fontSize, timeoutMs);
|
|
1819
|
-
return { ...label, ...placement };
|
|
1820
|
-
}
|
|
1973
|
+
case "eda_place_net_label_at_pin":
|
|
1974
|
+
return await placeAtPin(name, args, timeoutMs);
|
|
1821
1975
|
case "eda_place_net_label": {
|
|
1822
1976
|
const x = Number(args.x);
|
|
1823
1977
|
const y = Number(args.y);
|
|
1824
1978
|
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error("eda_place_net_label requires numeric x/y");
|
|
1825
|
-
return await placeNetLabel(String(args.net), x, y, args.fontSize, timeoutMs);
|
|
1979
|
+
return await placeNetLabel(String(args.net), x, y, args.fontSize, timeoutMs, { probe: args.probe === true });
|
|
1980
|
+
}
|
|
1981
|
+
case "eda_place_net_labels": {
|
|
1982
|
+
const items = Array.isArray(args.items) ? args.items.map(asRecord) : [];
|
|
1983
|
+
if (items.length === 0) throw new Error("eda_place_net_labels requires items: [{ pinPrimitiveId, net }] or [{ x, y, net }]");
|
|
1984
|
+
const results = [];
|
|
1985
|
+
for (const item of items) {
|
|
1986
|
+
const net = item.net === void 0 ? "" : String(item.net);
|
|
1987
|
+
try {
|
|
1988
|
+
const placed = item.pinPrimitiveId !== void 0 ? await placeAtPin("eda_place_net_label_at_pin", { ...item, net }, timeoutMs) : await placeNetLabel(net, Number(item.x), Number(item.y), item.fontSize, timeoutMs, { probe: args.probe === true });
|
|
1989
|
+
results.push({ ok: true, net, ...placed });
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
results.push({ ok: false, net, error: error instanceof Error ? error.message : String(error) });
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const done = results.filter((entry) => entry.ok).length;
|
|
1995
|
+
return {
|
|
1996
|
+
total: items.length,
|
|
1997
|
+
done,
|
|
1998
|
+
failed: items.length - done,
|
|
1999
|
+
method: netLabelSupport === true ? "netLabel" : "text",
|
|
2000
|
+
results
|
|
2001
|
+
};
|
|
1826
2002
|
}
|
|
1827
2003
|
case "eda_get_net_labels": {
|
|
1828
|
-
const
|
|
2004
|
+
const attributes = await callExtension("sch_PrimitiveAttribute.getAll", [
|
|
1829
2005
|
args.parentPrimitiveId === void 0 ? void 0 : String(args.parentPrimitiveId)
|
|
1830
|
-
], timeoutMs);
|
|
1831
|
-
|
|
1832
|
-
|
|
2006
|
+
], timeoutMs).catch(() => []);
|
|
2007
|
+
const texts = args.parentPrimitiveId === void 0 ? await callExtension("sch_PrimitiveText.getAll", [], timeoutMs).catch(() => []) : [];
|
|
2008
|
+
const entries = [];
|
|
2009
|
+
for (const entry of Array.isArray(attributes) ? attributes : []) {
|
|
2010
|
+
const record = asRecord(entry);
|
|
2011
|
+
entries.push({
|
|
2012
|
+
primitiveId: pickValue(record, "PrimitiveId", "primitiveId"),
|
|
2013
|
+
kind: String(pickValue(record, "PrimitiveType", "primitiveType", "Type", "type") ?? "attribute"),
|
|
2014
|
+
net: pickValue(record, "Net", "net") ?? null,
|
|
2015
|
+
userValue: pickValue(record, "UserValue", "userValue", "Value", "value") ?? null,
|
|
2016
|
+
x: pickValue(record, "X", "x"),
|
|
2017
|
+
y: pickValue(record, "Y", "y")
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
for (const entry of Array.isArray(texts) ? texts : []) {
|
|
2021
|
+
const record = asRecord(entry);
|
|
2022
|
+
const content = String(pickValue(record, "Content", "content", "Text", "text") ?? "");
|
|
2023
|
+
if (content === "") continue;
|
|
2024
|
+
entries.push({
|
|
2025
|
+
primitiveId: pickValue(record, "PrimitiveId", "primitiveId"),
|
|
2026
|
+
kind: "text",
|
|
2027
|
+
net: content,
|
|
2028
|
+
userValue: content,
|
|
2029
|
+
x: pickValue(record, "X", "x"),
|
|
2030
|
+
y: pickValue(record, "Y", "y")
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
return {
|
|
2034
|
+
count: entries.length,
|
|
2035
|
+
attributeCount: Array.isArray(attributes) ? attributes.length : 0,
|
|
2036
|
+
textCount: Array.isArray(texts) ? texts.length : 0,
|
|
2037
|
+
sessionTextLabels: placedTextLabels.length,
|
|
2038
|
+
note: 'kind "text" entries are plain texts (no electrical meaning). Delete any entry with eda_delete_primitive(kind: "netlabel"|"netport"|"netflag"|"text", primitiveId).',
|
|
2039
|
+
labels: entries
|
|
2040
|
+
};
|
|
1833
2041
|
}
|
|
1834
2042
|
case "eda_get_active_document": {
|
|
1835
2043
|
const info = await callExtension("dmt_SelectControl.getCurrentDocumentInfo", [], timeoutMs);
|
|
@@ -1955,8 +2163,33 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1955
2163
|
], timeoutMs);
|
|
1956
2164
|
case "eda_save_document":
|
|
1957
2165
|
return await callExtension("sch_Document.save", [], timeoutMs);
|
|
1958
|
-
case "eda_zoom_to_fit":
|
|
1959
|
-
|
|
2166
|
+
case "eda_zoom_to_fit": {
|
|
2167
|
+
const raw = await callExtension("dmt_EditorControl.zoomToAllPrimitives", [args.tabId], timeoutMs);
|
|
2168
|
+
const record = asRecord(raw);
|
|
2169
|
+
const left = Number(pickValue(record, "left", "Left"));
|
|
2170
|
+
const right = Number(pickValue(record, "right", "Right"));
|
|
2171
|
+
const bottom = Number(pickValue(record, "bottom", "Bottom"));
|
|
2172
|
+
const top = Number(pickValue(record, "top", "Top"));
|
|
2173
|
+
if (![left, right, bottom, top].every((value) => Number.isFinite(value))) return raw;
|
|
2174
|
+
const frame = paperOf(args.paper).frame;
|
|
2175
|
+
const clamped = {
|
|
2176
|
+
left: Math.max(frame.left, left),
|
|
2177
|
+
right: Math.min(frame.right, right),
|
|
2178
|
+
bottom: Math.max(frame.bottom, bottom),
|
|
2179
|
+
top: Math.min(frame.top, top)
|
|
2180
|
+
};
|
|
2181
|
+
if (clamped.left >= clamped.right || clamped.bottom >= clamped.top) {
|
|
2182
|
+
return { ...record, requested: { left, right, bottom, top }, clamped: null, note: "the primitive bounds do not overlap the page frame" };
|
|
2183
|
+
}
|
|
2184
|
+
await callExtension("dmt_EditorControl.zoomToRegion", [
|
|
2185
|
+
clamped.left,
|
|
2186
|
+
clamped.right,
|
|
2187
|
+
clamped.top,
|
|
2188
|
+
clamped.bottom,
|
|
2189
|
+
args.tabId
|
|
2190
|
+
], timeoutMs);
|
|
2191
|
+
return { ...record, requested: { left, right, bottom, top }, clamped };
|
|
2192
|
+
}
|
|
1960
2193
|
case "eda_zoom_to_region":
|
|
1961
2194
|
return await callExtension("dmt_EditorControl.zoomToRegion", [
|
|
1962
2195
|
Number(args.left),
|
|
@@ -2000,24 +2233,98 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
2000
2233
|
const parsed = await parseNetlist(Array.isArray(all) ? all : project, timeoutMs);
|
|
2001
2234
|
return parsed;
|
|
2002
2235
|
}
|
|
2003
|
-
case "eda_run_drc":
|
|
2004
|
-
|
|
2236
|
+
case "eda_run_drc": {
|
|
2237
|
+
const verbose = await callExtension("sch_Drc.check", [
|
|
2238
|
+
args.strict === void 0 ? true : Boolean(args.strict),
|
|
2239
|
+
false,
|
|
2240
|
+
true
|
|
2241
|
+
], toolTimeout(args, timeoutMs));
|
|
2242
|
+
if (Array.isArray(verbose)) {
|
|
2243
|
+
return {
|
|
2244
|
+
ok: verbose.length === 0,
|
|
2245
|
+
count: verbose.length,
|
|
2246
|
+
entries: verbose.map((entry) => {
|
|
2247
|
+
const record = asRecord(entry);
|
|
2248
|
+
return {
|
|
2249
|
+
type: pickValue(record, "Type", "type", "Level", "level") ?? null,
|
|
2250
|
+
message: pickValue(record, "Message", "message", "Error", "error", "Description", "description") ?? null,
|
|
2251
|
+
primitiveId: pickValue(record, "PrimitiveId", "primitiveId") ?? null,
|
|
2252
|
+
x: pickValue(record, "X", "x") ?? null,
|
|
2253
|
+
y: pickValue(record, "Y", "y") ?? null,
|
|
2254
|
+
raw: entry
|
|
2255
|
+
};
|
|
2256
|
+
}),
|
|
2257
|
+
hint: 'Field names vary by EDA build; "raw" keeps the untouched record.'
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
return { ok: Boolean(verbose), count: null, entries: [], raw: verbose };
|
|
2261
|
+
}
|
|
2005
2262
|
case "eda_auto_layout":
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2263
|
+
case "eda_auto_route": {
|
|
2264
|
+
if (ALLOW_AUTO_LAYOUT !== true && args.force !== true) {
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
name + " is disabled by policy (settings allowAutoLayoutRouting=false, env DSH_EDA_ALLOW_AUTO_LAYOUT). Do NOT retry with force:true unless the user explicitly asked for automatic layout/routing.",
|
|
2267
|
+
"",
|
|
2268
|
+
"Do this instead, in order:",
|
|
2269
|
+
"1. eda_get_active_document \u2014 make sure a schematic page is active (eda_open_document if not).",
|
|
2270
|
+
"2. eda_get_page_info \u2014 read the A4 frame and the safe areas before placing anything.",
|
|
2271
|
+
"3. eda_get_components \u2014 see what is already placed and where (x/y + bounding boxes) to avoid overlap.",
|
|
2272
|
+
"4. Place or move parts yourself on a 10-unit grid: eda_place_component for new ones, eda_move_component (keeps designator/value) to re-grid existing ones. Signal flow left to right, power rails on top, ground at the bottom, >=20 units between symbols for wires and labels.",
|
|
2273
|
+
"5. Connect with eda_draw_wire for local nets; for anything that spans the sheet use eda_place_net_port or a net label (a wire net name is overridden by connectivity once an endpoint touches a pin).",
|
|
2274
|
+
"6. Verify: eda_export_netlist (every net should have >=2 endpoints) and eda_run_drc (per-problem entries). For a visual check export the canvas to an image and look at it, rather than letting EDA rearrange the sheet.",
|
|
2275
|
+
"7. Call eda_workflow_guide for the full standard."
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
const payload = Array.isArray(args.uuids) ? { uuids: args.uuids.map(String) } : void 0;
|
|
2279
|
+
return await callExtension(name === "eda_auto_layout" ? "sch_Document.autoLayout" : "sch_Document.autoRouting", [payload], toolTimeout(args, timeoutMs));
|
|
2280
|
+
}
|
|
2281
|
+
case "eda_export_netlist": {
|
|
2282
|
+
const payload = await callExtension("sch_ManufactureData.getNetlistFile", [
|
|
2283
|
+
void 0,
|
|
2284
|
+
typeof args.type === "string" && args.type !== "" ? args.type : void 0
|
|
2285
|
+
], toolTimeout(args, timeoutMs));
|
|
2286
|
+
const text = typeof payload === "string" ? payload : payload !== null && typeof payload === "object" && typeof payload.text === "string" ? payload.text : void 0;
|
|
2287
|
+
if (text === void 0) return { parsed: false, netlist: payload ?? null };
|
|
2288
|
+
if (args.format === "text") return { parsed: false, netlistText: text, bytes: text.length };
|
|
2289
|
+
try {
|
|
2290
|
+
return { parsed: true, bytes: text.length, netlist: JSON.parse(text) };
|
|
2291
|
+
} catch {
|
|
2292
|
+
return { parsed: false, bytes: text.length, netlistText: text };
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
case "eda_export_bom": {
|
|
2296
|
+
const format = args.format === "xlsx" ? "xlsx" : "csv";
|
|
2297
|
+
const payload = await callExtension("sch_ManufactureData.getBomFile", [void 0, format], toolTimeout(args, timeoutMs));
|
|
2298
|
+
if (format === "xlsx") return payload;
|
|
2299
|
+
const raw = typeof payload === "string" ? payload : payload !== null && typeof payload === "object" && typeof payload.text === "string" ? payload.text : "";
|
|
2300
|
+
return { format, bom: decodeTextPayload(raw) };
|
|
2301
|
+
}
|
|
2302
|
+
case "eda_delete_primitive": {
|
|
2303
|
+
const kind = String(args.kind);
|
|
2304
|
+
const className = kind === "netport" || kind === "netflag" || kind === "netlabel" ? "sch_PrimitiveAttribute" : primitiveKindToClass(kind);
|
|
2305
|
+
return await callExtension(className + ".delete", [String(args.primitiveId)], timeoutMs);
|
|
2306
|
+
}
|
|
2307
|
+
case "eda_move_component":
|
|
2308
|
+
return await callExtension("sch_PrimitiveComponent.modify", [String(args.primitiveId), {
|
|
2309
|
+
x: args.x === void 0 ? void 0 : Number(args.x),
|
|
2310
|
+
y: args.y === void 0 ? void 0 : Number(args.y),
|
|
2311
|
+
rotation: args.rotation === void 0 ? void 0 : Number(args.rotation),
|
|
2312
|
+
mirror: args.mirror === void 0 ? void 0 : Boolean(args.mirror)
|
|
2313
|
+
}], timeoutMs);
|
|
2314
|
+
case "eda_list_api_methods":
|
|
2315
|
+
return { count: ALLOWED_API.size, methods: [...ALLOWED_API].sort() };
|
|
2316
|
+
case "eda_workflow_guide":
|
|
2317
|
+
return { summary: WORKFLOW_SUMMARY, guide: WORKFLOW_GUIDE, autoLayoutAllowed: ALLOW_AUTO_LAYOUT };
|
|
2318
|
+
case "eda_workflow_guide":
|
|
2319
|
+
return { summary: WORKFLOW_SUMMARY, guide: WORKFLOW_GUIDE, autoLayoutAllowed: ALLOW_AUTO_LAYOUT };
|
|
2015
2320
|
case "eda_create_board":
|
|
2016
2321
|
return await callExtension("dmt_Board.createBoard", [args.schematicUuid, args.pcbUuid], timeoutMs);
|
|
2017
2322
|
case "eda_api_call": {
|
|
2018
2323
|
if (!ALLOW_RAW_API) throw new Error("eda_api_call is disabled by settings (allowRawApi=false)");
|
|
2019
2324
|
const method = String(args.method ?? "");
|
|
2020
|
-
if (!ALLOWED_API.has(method))
|
|
2325
|
+
if (!ALLOWED_API.has(method)) {
|
|
2326
|
+
throw new Error("API method not allowed: " + method + ". The extension only accepts its allow-list (" + ALLOWED_API.size + " methods): call eda_list_api_methods to get it. Common schematic helpers such as sch_PrimitiveComponent.getAllPinsByPrimitiveId and sch_PrimitivePin.get are already wrapped by eda_get_pins.");
|
|
2327
|
+
}
|
|
2021
2328
|
return await callExtension(method, Array.isArray(args.args) ? args.args : [], timeoutMs);
|
|
2022
2329
|
}
|
|
2023
2330
|
case "pcb_get_components": {
|
|
@@ -2196,17 +2503,17 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
2196
2503
|
case "pcb_save_document":
|
|
2197
2504
|
return await callExtension("pcb_Document.save", [], timeoutMs);
|
|
2198
2505
|
case "pcb_import_changes": {
|
|
2199
|
-
const
|
|
2506
|
+
const uuid2 = args.schematicUuid === void 0 || args.schematicUuid === null ? null : String(args.schematicUuid);
|
|
2200
2507
|
if (args.wait === true) {
|
|
2201
|
-
return await callExtension("pcb_Document.importChanges", [
|
|
2508
|
+
return await callExtension("pcb_Document.importChanges", [uuid2 === null ? void 0 : uuid2], toolTimeout(args, timeoutMs));
|
|
2202
2509
|
}
|
|
2203
2510
|
pcbComponentBaseline = await pcbComponentIds(timeoutMs);
|
|
2204
2511
|
let started;
|
|
2205
2512
|
try {
|
|
2206
|
-
started = asRecord(await callExtension("dsh.internal.jobStart", ["importChanges",
|
|
2513
|
+
started = asRecord(await callExtension("dsh.internal.jobStart", ["importChanges", uuid2], timeoutMs));
|
|
2207
2514
|
} catch (error) {
|
|
2208
2515
|
if (!isInternalHelperMissing(error)) throw error;
|
|
2209
|
-
const result2 = await callExtension("pcb_Document.importChanges", [
|
|
2516
|
+
const result2 = await callExtension("pcb_Document.importChanges", [uuid2 === null ? void 0 : uuid2], toolTimeout(args, timeoutMs));
|
|
2210
2517
|
const after = await pcbComponentIds(timeoutMs);
|
|
2211
2518
|
return {
|
|
2212
2519
|
triggered: true,
|