@fateforge/xpedition-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.agent/AGENT.md +59 -0
  2. package/.agent/AGENT_zh.md +59 -0
  3. package/.agent/CLI-SPEC.md +1073 -0
  4. package/.agent/CLI-SPEC_zh.md +891 -0
  5. package/.agent/SEC-SPEC.md +158 -0
  6. package/.agent/SEC-SPEC_zh.md +132 -0
  7. package/.agent/SKILL-SPEC.md +266 -0
  8. package/.agent/SKILL-SPEC_zh.md +221 -0
  9. package/.agent/SPEC_VERSION +1 -0
  10. package/AGENTS.md +34 -0
  11. package/AGENTS_zh.md +33 -0
  12. package/CHANGELOG.md +795 -0
  13. package/CODE_OF_CONDUCT.md +35 -0
  14. package/CODE_OF_CONDUCT_zh.md +35 -0
  15. package/CONTRIBUTING.md +50 -0
  16. package/CONTRIBUTING_zh.md +42 -0
  17. package/LICENSE +21 -0
  18. package/NOTICE.md +16 -0
  19. package/NOTICE_zh.md +13 -0
  20. package/README.md +200 -0
  21. package/README_zh.md +178 -0
  22. package/SECURITY.md +108 -0
  23. package/SECURITY_zh.md +83 -0
  24. package/docs/AGENT_HARDENING_EVIDENCE.md +102 -0
  25. package/docs/AGENT_READS.md +74 -0
  26. package/docs/AGENT_READS_METRICS.json +216 -0
  27. package/docs/AGENT_READS_VALIDATION.json +13 -0
  28. package/docs/API_INVENTORY_BINDING_VALIDATION.json +16 -0
  29. package/docs/API_INVENTORY_DESIGN.md +90 -0
  30. package/docs/API_INVENTORY_REVIEW.md +59 -0
  31. package/docs/API_INVENTORY_VALIDATION.json +29 -0
  32. package/docs/API_INVENTORY_WINDOWS_VALIDATION.json +29 -0
  33. package/docs/COMPATIBILITY.md +499 -0
  34. package/docs/CONFIRMATION_CONCURRENCY_VALIDATION.json +33 -0
  35. package/docs/DIAGNOSTIC_BOUNDARIES.md +33 -0
  36. package/docs/DIAGNOSTIC_BOUNDARIES_VALIDATION.json +12 -0
  37. package/docs/E2E.md +445 -0
  38. package/docs/EVALS.md +134 -0
  39. package/docs/MCP.md +20 -0
  40. package/docs/NATIVE_ADAPTER.md +141 -0
  41. package/docs/OPEN_SOURCE_CHECKLIST.md +61 -0
  42. package/docs/OPEN_SOURCE_CHECKLIST_zh.md +61 -0
  43. package/docs/PIN_WORKFLOW_VALIDATION.json +28 -0
  44. package/docs/PLACEMENT_TASKS.md +99 -0
  45. package/docs/PLACEMENT_TASKS_VALIDATION.json +36 -0
  46. package/docs/REFERENCE_ADOPTION.md +67 -0
  47. package/package.json +48 -0
  48. package/scripts/run.js +46 -0
  49. package/skills/xpedition-cli/SKILL.md +300 -0
  50. package/skills/xpedition-cli/reference/agent-hardening.md +58 -0
  51. package/skills/xpedition-cli/reference/api-inventory.md +58 -0
  52. package/skills/xpedition-cli/reference/confirmation-safety.md +55 -0
  53. package/skills/xpedition-cli/test-prompts.json +62 -0
  54. package/skills/xpedition-pcb/SKILL.md +244 -0
  55. package/skills/xpedition-pcb/reference/fabrication.md +26 -0
  56. package/skills/xpedition-pcb/reference/hand-routing.md +33 -0
  57. package/skills/xpedition-pcb/reference/pcb-conventions.md +162 -0
  58. package/skills/xpedition-pcb/reference/placement-tasks.md +28 -0
  59. package/skills/xpedition-pcb/test-prompts.json +62 -0
  60. package/skills/xpedition-schematic/SKILL.md +244 -0
  61. package/skills/xpedition-schematic/reference/pin-assignment.md +61 -0
  62. package/skills/xpedition-schematic/reference/schematic-conventions.md +306 -0
  63. package/skills/xpedition-schematic/reference/schematic-design-format.md +219 -0
  64. package/skills/xpedition-schematic/test-prompts.json +52 -0
@@ -0,0 +1,499 @@
1
+ # Compatibility matrix
2
+
3
+ This document records the backends that have actually been verified. A
4
+ capability is not advertised as native support until a licensed Xpedition
5
+ environment passes the smoke loop described in [`E2E.md`](E2E.md).
6
+
7
+ | Backend | Version / environment | Status | Notes |
8
+ |---|---|---|---|
9
+ | MockBackend | xpedition-cli 1.0.x, Python 3.10–3.12 | verified | Offline JSON project model, schematic/PCB/constraint/analysis/manufacturing/library reads, ChangeSet validation/preview/apply, snapshot, BOM and review. |
10
+ | NativeBackend — Designer | Xpedition Standard XPED2604 on Windows 11, `Viewdraw.Application`, pywin32 bridge | reads and writes verified | Attach, schematic snapshot, `AddPartInstance` placement, net creation, labels and coordinate read-back all confirmed against a running Designer session. The R1/C1 smoke loop of [`E2E.md`](E2E.md) is recorded against a hand-built minimal library, not the stock one — see below. |
11
+ | NativeBackend — Layout | Xpedition Standard XPED2604, `MGCPCB.ExpeditionPCBApplication` | reads and writes verified | Board creation, forward annotation, `Components`/`Nets` reads, placement, routing by the router and by hand, pours, Batch DRC and fabrication outputs, all through the adapter; recorded in [`E2E.md`](E2E.md) from 2026-09-14 on boards built from a template, with cells generated or converted by the CLI rather than a production library. Bottom-side placement has not been exercised. |
12
+ | ExchangeBackend | JSON / CSV / BOM / IPC-2581 XML | verified | Import and normalization use the normalized project model with dry-run/confirm writes. |
13
+ | ExchangeBackend | PDF / EDN / ODB++ | planned | Format-specific parsers are not enabled yet. |
14
+
15
+ ## A stock installation ships no component library
16
+
17
+ Not the CLI, and not COM: **the installation ships no component library**.
18
+ `SDD_HOME/standard/templates/dxdesigner/TemplateLibrary` is a deliberately empty
19
+ skeleton — every category directory (`Resistors`, `Capacitors`, …) exists, but
20
+ every `*.pdb` is a 3296-byte stub, every `*.cel` a 19492-byte stub, and the
21
+ device symbol directories hold zero files. Only borders, builtin symbols,
22
+ `Globals` (power/ground) and `Drawing.cel` carry content. Requesting a resistor
23
+ is answered by Designer itself:
24
+
25
+ ```
26
+ 6055 Symbol Resistors:R.1 not found, empty or a block.
27
+ ```
28
+
29
+ The first smoke loop in `E2E.md` therefore ran against a hand-built library, and
30
+ the CLI now generates the parts a design needs (`library build`, with cells from
31
+ `library kicad-import`). Placement against a symbol that *does* exist
32
+ (`builtin:espl1`) succeeds and reads back with correct coordinates, so the write
33
+ path is not what is missing.
34
+
35
+ ## Driving the applications by command
36
+
37
+ The COM classes expose only a narrow slice of each product. The full command set
38
+ of every application is catalogued on disk:
39
+
40
+ ```
41
+ SDD_HOME/standard/automation/Commands_XpeditionLayout.csv 1041 commands
42
+ SDD_HOME/standard/automation/Commands_Xpedition_Designer.csv
43
+ SDD_HOME/standard/automation/Commands_{Library_Manager,Symbol_Editor,CellEditor}.csv
44
+ ```
45
+
46
+ Each row carries an id, an internal name, a display name and a description.
47
+ Designer runs them through `ExecuteCommandByID(id)`, `ExecuteCommandByName(name)`
48
+ or `ExecuteCommand(string)`; Layout has the equivalent on `Gui.ProcessCommand` /
49
+ `Gui.ProcessKeyin`. This is how a board gets created — Layout's own COM interface
50
+ has no such entry point, only `OpenDocument` and `OpenReference`.
51
+
52
+ Not every command is automation-friendly. `Package Design for Layout` (35085)
53
+ launches `packagerui.exe`, a separate GUI process that waits for a human, and the
54
+ dialog-suppression switches do not reach it.
55
+
56
+ ## Forward annotation without a GUI
57
+
58
+ `package.exe` does the same work as the packager dialog and is a console program,
59
+ so the adapter's `package` method runs it headless and returns structured errors
60
+ read back from `<project>/Integration/PartPkg.log`.
61
+
62
+ It must be started through `common/win64/bin/package.exe`. Launching the real
63
+ binary under `wg/win64/bin` directly skips the release environment and the
64
+ program cannot initialise its Qt platform plugin — it puts up "no Qt platform
65
+ plugin could be initialized" and never runs. Same launcher rule as below.
66
+
67
+ ## COM activation needs the launcher
68
+
69
+ Every Xpedition `LocalServer32` registration points at the real binary under
70
+ `SDD_HOME/<product>/win64/bin`, but those binaries depend on the release
71
+ environment that the small launcher in `SDD_HOME/common/win64/bin` sets up.
72
+ Direct COM activation skips the launcher, so `CoCreateInstance` fails with
73
+ `CO_E_SERVER_EXEC_FAILURE` (0x80080005). Start the product through its launcher,
74
+ then attach with `GetActiveObject`. This applies to `LibraryManager` and
75
+ `ExpeditionPCB` alike; the adapter reports the condition as
76
+ `E_BACKEND_UNAVAILABLE` with a launcher hint rather than a bare COM error.
77
+
78
+ ## Library tooling present on a stock installation
79
+
80
+ Useful when a library has to be built rather than imported:
81
+
82
+ | Tool | Path under `SDD_HOME` | Automatable? |
83
+ |---|---|---|
84
+ | Library Manager | `common/win64/bin/LibraryManager.exe` | COM `LibraryManager.Application`, **read-only** — the object model exposes no Add/Create for symbols, cells or parts |
85
+ | Cell / Padstack editors | reached via `ActiveLibrary.CellEditor` / `.PadstackEditor` | yes — `OpenDatabase`, `NewPartition`, `NewCell(eCellType)`, `SaveActiveDatabase`, `SuppressTrivialDialogs` |
86
+ | HKP converters | `common/win64/bin/HKP2{PadstackDB,CellDB,PartsDB,LMCDB}.exe` and the `*DB2HKP` reverse | yes — GUI-subsystem binaries with a command line (`-i <hkp> -o <db> -c <lmc> -m -l <log>`; a wrong argument is a message box, not an exit code). `library build` and `library kicad-import` run them; `CellDB2HKP -a` / `PadstackDB2HKP -a` exports are the grammar reference (see "KiCad footprints as cells" below) |
87
+ | PCB Footprint Expert 26 (Siemens) | `lm_fpe/` | IPC-7351B generator with populated `.fpx` libraries for SM/TH discretes, semiconductors, connectors and BGA |
88
+
89
+ Schematic symbols are plain ASCII (`SymbolLibs/<partition>/sym/<name>.<version>`),
90
+ so they can be generated as text; cells and padstacks go through the editor COM
91
+ objects above.
92
+
93
+ ## Sheet units and symbol file versions
94
+
95
+ Measured by placing stock symbols through the adapter and reading the pin
96
+ coordinates back:
97
+
98
+ | Object | In the file | Read back on the sheet |
99
+ |---|---|---|
100
+ | `builtin:nc` connection point | `P 16 -254000 0 …`, header `V 54` | 10 units left of the origin |
101
+ | `builtin:Arrow_Blue` pin | `889000`, header `V 54` | 35 units |
102
+ | `builtin:Arrow_Green` pin | `35`, header `V 53` | 35 units |
103
+ | `asheet` border | `D 330200 0 27609800 21590000`, header `V 54` | 8.5 in tall, so 10 nm per file unit |
104
+
105
+ One sheet unit is therefore 10 mil (0.254 mm) and the 100 mil grid is 10 units.
106
+ `V 53` symbol files use sheet units directly; `V 54` files — every stock symbol
107
+ on XPED2604 — are in 10 nm, 254000 per grid step. The `Y` record is the
108
+ symbol type, not a scale: 1 part, 3 annotation, 4 power or ground, 5 border.
109
+ Hand-generated symbols use `V 53`. The xpedition-schematic Skill's
110
+ `reference/schematic-conventions.md` carries the grid, size and sheet-extent
111
+ rules that follow from this.
112
+
113
+ ## Drawing a whole schematic
114
+
115
+ `schematic draw --design FILE` plans a readable schematic with
116
+ `xpedition_cli.schematic_layout` (IC blocks with a treatment per pin, vertical
117
+ ladders and horizontal chains of two-terminal parts between power, ground and
118
+ labelled nodes, titles, notes, overview boxes) and executes the plan through the
119
+ adapter method `draw`: `open_sheet`, `wipe_sheet`, `set_sheet`, `place_part`,
120
+ `place_symbol`, `wire`, `box`, `text`, `save`. Symbol files are written into the
121
+ project's central-library partition first, named by content. Afterwards the
122
+ project is reopened and every net is read back and compared with the plan;
123
+ unlabelled junctions are matched through Designer's own net `UID` (`$2N121`).
124
+ The recorded run is in [`E2E.md`](E2E.md).
125
+
126
+ `schematic show --sheet N` (adapter method `show`) activates a sheet, runs
127
+ `Fit All` (32775) and raises Designer's window: `SetForegroundWindow` is refused
128
+ to a background process, but making the window topmost and releasing it works.
129
+ `--output` captures the window through GDI into a PNG encoded in-process.
130
+
131
+ `project init --backend native_xpedition --template TPL.prj --project NEW.prj`
132
+ (adapter method `clone_project`) creates a project the only way Designer allows
133
+ without a GUI: copy a known-good project folder without `Templates`, `Work`,
134
+ `LogFiles`, `ProjectBackup`, `Thumbnail` and `*.bak`, rename the `.prj`, rewrite
135
+ the absolute `CentralLibrary` and `DBCFile` keys that pointed into the template
136
+ folder, and open the copy. The template is closed first if Designer has it
137
+ open, because the iCDB server holds its files. The destination must be an
138
+ ASCII path (see the facts below).
139
+
140
+ ## Rendering a schematic to PDF
141
+
142
+ Designer's `Generate PDF` (command 34622) is a dialog, but the installation
143
+ ships `common/win64/bin/sch2pdf.exe`, a console program that renders a project
144
+ straight from its database and works while Designer still has the project
145
+ open. `schematic export --backend native_xpedition --project X.prj --output
146
+ X.pdf` runs it through the adapter method `export_pdf`. Facts that matter:
147
+
148
+ - It prints the area inside each sheet's border and nothing else; objects
149
+ placed outside the border simply do not appear.
150
+ - `-c` selects colour: 0 black on white without black text, 1 colour on white
151
+ (the default), 2 colour on black, 3 black on white, 4 colour on white with
152
+ coloured text. `-schematic NAME` limits the run to one schematic.
153
+ - Every rendered sheet is reported as `Printed <schematic> Sheet <n>`; the
154
+ adapter returns those as `sheets`.
155
+
156
+ ## Designer drawing facts verified on XPED2604
157
+
158
+ | Fact | Detail |
159
+ |---|---|
160
+ | `AddPartInstance(partition, part, symbol, x, y)` | the second argument is the part (shown as the instance's Part Number), the third the symbol name |
161
+ | `AddSymbolInstance(partition, symbol, x, y)` | places a refdes-less symbol — stock `Globals:gnd`, `builtin:No_Connect`, generated power symbols — with no refdes step and therefore no orphan |
162
+ | Power and ground nets | a wire ending on the symbol's origin joins the net named by its `NETNAME` attribute, provided the symbol file is type 4 (`Y 4`) like every stock Globals symbol; a type-1 (part) symbol with a `NETNAME` attribute names nothing |
163
+ | Symbol cache | Designer keeps the definition of any symbol it has placed in the open project; a regenerated file with the same name is ignored. Give a changed symbol a new name |
164
+ | `Orientation` on an instance | 0 / 1 / 2 / 3 = 0° / 90° / 180° / 270° counter-clockwise, 4–7 the mirrored set; pin coordinates read back rotated, so read them instead of predicting them |
165
+ | Wiping a sheet | `ExecuteCommandByID(57642)` (Select All) then `Block.DeleteSelected(False)` removes every object and keeps the border; there is no per-object delete |
166
+ | Text | `Block.AddText(text, x, y)`, then `.Size = n` in sheet units |
167
+ | Attributes on an instance | `instance.AddAttribute("VALUE=10k", x, y, 3)` adds a visible attribute; the stock `pwr_bar`'s `NETNAME` cannot be changed this way, its attribute collection is empty |
168
+ | Sheet size | `Block.SheetSize` is the `VDSHEET_*` enum (0 A, 1 B, 2 C, 3 D, 4 E, 5–9 A4–A0, 10 custom); `SchematicSheetDocuments.Add()` and `InsertSheet` add sheets |
169
+ | Drawn wires | free `AddNet` segments that meet at an endpoint, and a segment that starts on the middle of another, all join one net (verified: an L-shaped run plus a T drop read back as one three-pin net); labels go on any segment, once per net (`Net already labeled`, 6035) |
170
+ | More sheets | `ExecuteCommandByID(34165)` (New Sheet) adds and activates a sheet; `SchematicSheetDocuments.Open("Schematic1", "2")` switches, `DeleteSheet("Schematic1", 2)` removes. The COM `Add`/`InsertSheet` calls fail with error 670 |
171
+ | After sheet operations | `GetActiveDesign` reports the schematic (`Schematic1`) instead of the block (`Board1`) and `DesignComponents` fails with a type mismatch until the project is closed and reopened |
172
+ | Off-page connectors | `builtin:OFFPAGE_INPUT` places like any symbol; the net still takes its name from a label on the wire |
173
+ | Pin text on a symbol | pin attributes take absolute symbol coordinates: `A x y size 0 3 3 #=1` shows the pin number, `A x y size 0 3 3 NAME=VIN` the pin name inside the body. An `L` record after a `P` record is not displayed on a part symbol, which is why generated boxes showed a black blob at the origin: every pin number was drawn at the same point |
174
+ | Sheet size and border | `Block.SheetSize = 5` (VDSHEET_A4_SIZE) changes the page; `Block.ChangeBorder("a4sheet")` swaps the border symbol; set the size first, the border second. Either change can leave another sheet's window active, so re-activate the target sheet before drawing on |
175
+ | Sheets open vs sheets existing | `SchematicSheetDocuments` lists the sheet windows that are open; `GetAvailableSheets(schematic)` (an `IStringList`: `GetCount()`, 1-based `GetItem(i)`) lists the sheets that exist. `Open(schematic, n)` activates a sheet only the first time; afterwards call `document.Activate()` on the open document |
176
+ | Attribute text on an instance | `attribute.Orientation = 0` turns a rotated value or refdes horizontal and `attribute.SetLocation(x, y)` moves it; `X`/`Y` are read-only |
177
+ | Chinese text | `AddText` accepts Unicode and Designer draws Chinese titles and notes correctly on screen. The project stores them in the system code page (GBK here), and `sch2pdf` writes those bytes as Latin-1 glyphs, so a PDF shows mojibake; the text is recoverable by re-encoding Latin-1 to GBK, which is what a review pipeline has to do when it extracts notes |
178
+ | Verification | `RunDesignIntegrityChecks` is a database integrity test (22 layer/object tests). The schematic ERC is the `Full Verification` command (34155; `Quick Verification` 46461) with the rules of the project's `Verify.ini`; it writes `LogFiles/vdrc.log` (`SEVERITY` / `GROUP` headers, then `<rule> - [flatnet : X, component: R1($2I5), pin: $1P10] message`) and `LogFiles/grc.log` (graphical checks). The adapter method `verify` runs it and parses both |
179
+ | Reading a design back | `net.LogicalNetName` gives the net name, `net.UID` the `$<sheet>N<id>` id of an unlabelled net; a component's `UID` `$<sheet>I<id>` tells its sheet; `component.Attributes` holds the instance attributes (Part Number, VALUE …) but not the symbol's DEVICE/PART_NAME; pin names and types are not exposed on the connection's pin object; the symbol name of an instance is not exposed at all — a no-connect mark is recognised by geometry, a refdes-less symbol sitting on the pin end |
180
+ | Non-ASCII paths | a project whose folder path contains Chinese characters opens, reads back and shows its sheets, but `AddPartInstance` reports every symbol file written after the copy as `not found, empty or a block`, before and after a reopen; the same project on an ASCII path finds them at once. Keep projects and their central library on ASCII paths; `schematic draw` and `project init --template` refuse others with `E_VALIDATION` |
181
+ | Pins on wire corners | a symbol pin placed where a stub and a bar meet end-to-end does not connect: Designer merges the two segments into one polyline and the pin sits on a vertex. A pin on a free wire end or on a T junction connects. The planner therefore runs a shared ground bar one stub past the last pin and puts the ground symbol on the free end |
182
+ | Creating a project | there is no automation call; copy a project folder as described above. The iCDB `database` folder copies cleanly while the project is closed, and the copy opens under a new `.prj` name |
183
+ | Pin types and ERC | `drc-BI-POWER` / `drc-BI-GROUND` fire for every pin typed `BI` on a net that also carries a `POWER` or `GROUND` typed pin, except on resistors and capacitors, which the verification exempts. Pins typed `ANALOG` pass: the generated inductor, diode, LED, switch, battery, thermistor, MOSFET drain/source and test-point pins are `ANALOG`, and the demo design verifies with no warning |
184
+ | Symbols without pins | a `V 53` part symbol with shapes and no `P` record (a mounting hole) places through `AddPartInstance`, gets a refdes and appears in the BOM |
185
+
186
+ ## Creating a board and forward-annotating it (verified on XPED2604)
187
+
188
+ Layout's `File > New` is the Job Management Wizard, a separate program
189
+ (`common/win64/bin/JobWizard.exe`) with a documented command line:
190
+
191
+ ```
192
+ JobWizard -createnew [-f] -prj <project.prj> -newpcb <PCB/Name.pcb> -template "<Layout Template Name>" -lib <library.lmc> -l <log>
193
+ ```
194
+
195
+ `-f` ignores create warnings, `-l` names the log (otherwise
196
+ `%LOCALAPPDATA%\Temp\JobWizard.log`). It copies `Templates/Layout/<name>` out of
197
+ the central library (58 files for the stock 4-layer template), writes
198
+ `PCBDesignPath` and `LayoutTemplate` into the `.prj`, and needs no Designer
199
+ session. `pcb create` runs it. Facts that cost time:
200
+
201
+ - The command line takes the **first** entry of the `.prj`'s `LIST Designs` and
202
+ stops with "未能从通用数据库中获取设计信息" (failed to get design information
203
+ from the common database) when that is a schematic node (`ConfigType
204
+ "Board1"`) rather than the board design (`ConfigType "PCB"`); the GUI wizard
205
+ picks the board design itself. `pcb create` lists the board design first.
206
+ - Template names are the folders under `<library>/Templates/Layout`. A project
207
+ cloned without that folder (what `project init --template` does, 24 MB per
208
+ template) offers none; `pcb create` copies the requested one from
209
+ `SDD_HOME/standard/templates/dxdesigner/TemplateLibrary/Templates/Layout`.
210
+ - `-l` is the log file; it is not a mode flag. Wrong options show the usage in
211
+ a message box titled `messageBox`.
212
+ - `ExpeditionPCB.exe -create -prj X.prj -design <template.pcb>` opens the
213
+ template itself and asks for a library; `pcbui.exe -p X.prj Board1` is the
214
+ old PADS-style "PCB Interface" netlister, not a board creator.
215
+
216
+ Forward annotation is `Document.ProjectIntegration`, an object without type
217
+ information (`GetTypeInfo` fails, so `EnsureDispatch` cannot wrap it); its
218
+ interface is in `common/win64/lib/ProjectIntegration.dll`: `ForwardAnnotate`,
219
+ `BackAnnotate`, `LoadCES`, `SynchCES`, `IsForwardAnnotationAllowed`,
220
+ `IsBackAnnotationAllowed`, `IsLoadCESAllowed`, `IsSynchCESAllowed`,
221
+ `ProjectFile`, `ForwardAnnotationStatus`, `BackAnnotationStatus`,
222
+ `SynchCESStatus`, `LoadCESStatus` (1 required, 2 in synch, 3 no CES). Through a
223
+ late-bound object every member runs on attribute access and returns a bool.
224
+ `pcb annotate` runs it. Facts:
225
+
226
+ - The board needs the cell partitions in the `.prj`: `LIST 2dCellLibraries`
227
+ with `VALUE "CellDBLibs\<partition>.cel"` entries in the design section, next
228
+ to `LIST PDBs` and `LIST Symbols`. Without it Database Load stops with "No cell
229
+ library search paths found from X.prj — Unable to initialize CellDBUpdate".
230
+ `library build` and `pcb create` register a cell partition for every parts
231
+ partition listed.
232
+ - A cell must have exactly the part's pin count; a 4-pin SOIC placeholder on a
233
+ 3-pin regulator is "Cell CLI_SOIC4 has 4 unique Alphanumeric Pin Numbers
234
+ while Part Number XC6206-3.3 has 3" and Database Load terminates.
235
+ - Asking for forward annotation when the status is already 2 (in synch) raises
236
+ `正向标注失败` (1001) without writing the log; the adapter reports `in_synch`
237
+ instead of running.
238
+ - A successful run writes `PCB/LogFiles/ForwardAnnotation.txt` with "N nets were
239
+ found containing M pins", "N components were found" and "Forward-Annotation on
240
+ the Layout Design has been successfully completed"; the run takes about 30 s
241
+ and shows progress boxes (`Packager`, `Database Load`) that need no answer.
242
+ - After opening a board whose front end is newer, Layout asks "新更改已经可以进行
243
+ 正向标注。是否要立即进行正向标注?" (是/否); the adapter answers 否 so that the
244
+ annotation stays an explicit command.
245
+
246
+ Opening a board and attaching to Layout:
247
+
248
+ - `OpenDocument` wants a `.pcb`; a `.prj` is "Cannot open document file due to
249
+ invalid file extension" (10211). The board of a project is the design
250
+ section's `PCBDesignPath`, relative to the `.prj`.
251
+ - A Layout that a killed session left behind asks "应用程序已尝试确定设计状态超过
252
+ 15 秒了…" (Open/Cancel) and then "数据库恢复" (load the autosave or the
253
+ user-saved database, 确定/取消). Both are Qt windows, not `#32770` dialogs, so
254
+ they are read and answered through UI Automation (`pywinauto`), from a helper
255
+ thread, because the `OpenDocument` call does not return until they are
256
+ answered.
257
+ - An instance started by COM activation (`Dispatch` of the progid) opens the
258
+ board in a hidden window and never registers in the running object table:
259
+ `GetActiveObject` keeps failing and nothing can attach to it. An instance
260
+ started through the launcher registers after start-up (about 50 s on this
261
+ machine). The adapter starts Layout through the launcher and waits.
262
+ - `GetActiveObject` returns a late-bound object without the type library's
263
+ enumerations; `gencache.EnsureDispatch` generates the library (`epcbSelectAll`
264
+ and friends appear in `win32com.client.constants`) but its generated classes
265
+ expose `Document.Components` and `Document.Nets` as parameterised properties
266
+ that reject arguments (`__call__() takes from 1 to 2 positional arguments`),
267
+ and the late-bound object rejects them too ("无效的参数数目"). Read both bare:
268
+ every parameter is optional and defaults to everything.
269
+ - Components arrive unplaced (`Placed` false, position 0/0); `Pins.Count` on the
270
+ document reads 0 even with 95 pins on the board.
271
+ - Designer has a question of the same kind: `OpenProject` for another project
272
+ while sheets are open asks "更改当前项目需要关闭所有打开的文档。是否希望关闭它们
273
+ 并继续打开选定的项目?" (Yes/No) and waits. The adapter answers Yes from the
274
+ same helper thread.
275
+
276
+ Placing components (verified on XPED2604, `pcb arrange`):
277
+
278
+ - `Component.Place(dX, dY, dOrientation, bTop, eFixType, eUnit, eAngleUnit)`:
279
+ the fourth argument is "top side" (True), the fifth the fix type (0 none), the
280
+ sixth the unit of the coordinates (`EPcbUnit`: 0 current, 2 mils, 3 inch, 4 mm,
281
+ 5 µm), the seventh the angle unit (0 degrees). `Component.Move(dX, dY, eUnit)`,
282
+ `Component.UnPlace()`. `GetPositionX(eUnit)` / `GetPositionY(eUnit)` read the
283
+ position in any unit; the bare `PositionX` is in the document's current unit,
284
+ which is mils on the stock templates (`Document.CurrentUnit` 2).
285
+ - `Component.Side` is 1 on the top and 512 on the bottom (`epcbSideTop`,
286
+ `epcbSideBottom`); `Layer` is 1 for a top-side part as well, so a layer test
287
+ cannot tell the sides apart.
288
+ - `Component.Extrema` (a property) is the footprint's bounding box:
289
+ `GetMinX(eUnit)` … `GetMaxY(eUnit)`. An unplaced component has no extents and
290
+ reading them fails, so a part is measured by placing it, reading and
291
+ unplacing it again. The cell object itself (`Component.Cell`) has no size.
292
+ - `Document.BoardOutline.Geometry` gives the outline: `GetRectMinX(eUnit)` …
293
+ `GetRectMaxY(eUnit)` for the bounding rectangle and `GetPointsArray(eUnit)`
294
+ for the vertices; the stock 4-layer template is a 50.8 mm (2000 mil) square
295
+ with its origin at the bottom-left corner.
296
+ - An unplaced component is not drawn on the board at all; after forward
297
+ annotation the board looks empty although the component navigator counts
298
+ the parts. Placing 39 parts through `Place` and saving takes about 5 s.
299
+ - `Net.Pins` lists only the pins of placed components once a part has been
300
+ unplaced (right after forward annotation the unplaced parts still appear);
301
+ a placement planner that reads connectivity must have every part on the
302
+ board first. `Net.IsPower` is a method and is False for `+3V3` on this board,
303
+ so a supply is better recognised by its member count.
304
+ - Layout's online DRC rejects `Place` with "DRC 违规" (10203) when the part
305
+ would land on another part or on its own old footprint, and when two
306
+ identical footprints share one spot; a part placed outside the board outline
307
+ is accepted. `Document.RespectComponentPlacementDRC` reads False here and
308
+ does not switch that check off. Lift the parts first, then place them.
309
+ - `PutBoardOutline(nPnts, points, width, eUnit)` replaces the outline; the
310
+ route border (`PutRouteBorder`) and manufacturing outline
311
+ (`PutManufacturingOutline`) are separate objects that keep their old size
312
+ until replaced too. Points go as three rows (x, y, radius) with the first
313
+ point repeated to close the polygon.
314
+ - `PutPlaneShape(layer, nPnts, points, net, routeObstruct, hatch, hatchWidth,
315
+ hatchDistance, component, eUnit)` and `PutFabricationLayerText(text, x, y,
316
+ epcbFabSilkscreen=2, epcbSideTop=1, height, rotation, penWidth, font, attr,
317
+ horiz, vert, component, eUnit, angleUnit)` take an optional component object;
318
+ pass `None`, because win32com turns the typelib's default `0` into "The
319
+ Python instance can not be converted to a COM object". Text is anchored at
320
+ its centre: a 1.2 mm string measures about 1.18 mm per character.
321
+ - Routing rules are read-only through Layout's own automation:
322
+ `NetClass.MinTraceWidth(layer, scheme, unit)`, `TypicalTraceWidth`,
323
+ `ExpansionTraceWidth` and `Document.GetClearanceRule(a, b, layerA, layerB, unit)`
324
+ read them (0.254 mm everywhere on the stock 4-layer template, via padstack
325
+ `026VIA`) and nothing there sets them. Constraint Manager's automation does set
326
+ trace widths (`pcb rules`; see "Constraint Manager automation" below); this tool
327
+ does not set clearances. Placeholder cells therefore need pads at least 0.254 mm
328
+ apart: a 0.65 mm pitch with 0.4 mm pads (0.25 mm gap) leaves its nets open.
329
+
330
+ Autorouting (verified on XPED2604, `pcb route`):
331
+
332
+ - `Document.NewRoutePass()` returns a pass object: `PassType(ePassType,
333
+ effortStart, effortEnd, viaGrid, routeGrid)` with the `epcbAR…Pass` values
334
+ (Expand 1, Fanout 2, NoVia 6, RemoveHangers 7, Route 8, Smooth 10, Spread 11,
335
+ ViaMin 14), `Items(epcbARAllNetsItem=0, None)`, `Order(epcbARAutoOrder=0, 0)`,
336
+ `Config(routeDuringFanout, allowCleanup)`, then `Go()`, which returns when the
337
+ pass is done (under a second for 18 nets on this board). `LayerSelect(1, 0)`
338
+ is "参数无效"; without it the pass uses every layer enabled for routing
339
+ (`Document.RouteLayerEnabled(n)`).
340
+ - `Net.IsRouted` and `Net.NumberOfOpens` (both methods) give the state per net;
341
+ `Document.Traces` and `Document.Vias` count the result. A pass on a board
342
+ whose parts are unplaced reports every net routed with no opens.
343
+ - Route passes leave the fine-pitch pads alone and never report why; the
344
+ clearance rule above is the reason.
345
+
346
+ Batch DRC and hazards (verified on XPED2604, `pcb drc`):
347
+
348
+ - No automation call runs Batch DRC. Its engine, `common/win64/bin/DrcDriver.exe`
349
+ (`-p PcbFilename [-q]`), refuses to start from a command line ("无法从命令行运行
350
+ 可执行文件"). The menu command does run: `Gui.CommandBars` lists `Document
351
+ Menu Bar > 分析 > 批量 DRC...` with id 32769, and `Gui.ProcessCommand(32769)`
352
+ opens the `批量 DRC` dialog (scheme combo, 确定/取消); `ProcessCommand` takes
353
+ such MFC command ids, not toolbar names. The command-bar control objects have
354
+ no `Execute`. After 确定 a `Processing...` window shows until the checks are
355
+ done (about 10 s here).
356
+ - `Document.GetHazards(eType)` returns the hazards: 0 all, 1 online, 2 batch.
357
+ Each has `Type` (an `epcbHazardType…` value: 67 Proximity, 74 PartialNets,
358
+ 75 Dangling, 77 ViasUnderParts, 24 PlacementGrid, 31/34/46/47 length and
359
+ delay summaries…), `Description` (a few lines of Chinese text), `Objects`,
360
+ `GetPositionX/Y(unit)` and, for clearance hazards, `GetRequiredClearance` /
361
+ `GetActualClearance`; the other kinds raise "Property or method not valid
362
+ for selected hazard type" on those.
363
+ - A plane shape made with `PutPlaneShape` is in the Draft state and connects
364
+ nothing (`GeneratedPlanes` 0): every via to it is dangling and its net partial.
365
+ `PlaneAssignment.PlaneDataState = 1` (Dynamic) generates the plane at once;
366
+ vias the router adds afterwards only tie in after the state is toggled to
367
+ Draft (3) and back to Dynamic. `Document.PlaneAssignments` has one entry per
368
+ assigned net and layer.
369
+ - The first Batch DRC on this board reported 18 pad-to-pad proximity
370
+ violations (required 0.254 mm, actual 0) on every dual-row placeholder: the
371
+ pads' long side had been laid along the pitch. Placeholder geometry needs a
372
+ DRC run as much as a real one.
373
+
374
+ Forward annotation after a library change (verified the hard way):
375
+
376
+ - Forward annotation never changes the cell of a component that already exists
377
+ on the board — placed, unplaced, with `ResetCell`, `ResetCellEx`, the `.prj`
378
+ `FwdAnnoRebuildFlag`, or after `Component.Delete` (which is refused silently
379
+ for a schematic part). The local parts cache and the central partition both
380
+ carry the new cell; the board keeps the old one. `ReplaceCell` wants a cell
381
+ object from `Document.Cells`, the board's local library, which only holds
382
+ cells a part has used. The way through is to create the board again
383
+ (`pcb create --replace`); JobWizard's own `-deletePCB` fails with "failed to
384
+ get design information" on these projects.
385
+ - `ForwardAnnotate` failed "in its packaging phase" ("正向标注的封装阶段出现错误",
386
+ while the packager's own log said it finished) on every run from the adapter
387
+ and succeeded from a plain script in the same state. The difference was the
388
+ adapter's prompt-answering thread, which also pressed the default button of
389
+ every `#32770` dialog of the process once a second: that ends the packager's
390
+ progress box and the annotation with it. Only rule-based answers are pressed
391
+ now, and the annotation succeeds at the first attempt on a fresh board. Two
392
+ earlier observations were symptoms of the same thing, not rules: "the second
393
+ call succeeds" and "it works with Designer's project closed". What is real:
394
+ closing Designer's project while Layout is opening the board breaks Layout's
395
+ database session ("iCDB error getting UID manager"), so the adapter reopens
396
+ the board first and only then lets Designer go of the project for the run.
397
+ - With traces on the board, a part whose pins changed needs traces broken
398
+ back, which Layout refuses in its preventive DRC mode ("不能在预防模式下打断导
399
+ 线"); `pcb annotate --unroute` deletes traces and vias first.
400
+ - Placed parts can still be invisible: the stock templates open under the
401
+ `Loc: Assembly Bottom` display scheme, which shows nothing of a top-side
402
+ part. `Document.DisplaySchemes` is a tuple of the scheme names (`Loc: …` from
403
+ the design's `Config/*.dcs`, `Sys: …` from the installation) and
404
+ `Document.ActiveView` is the `View` object (`Name` is the view name). The scheme
405
+ setter is `ActiveView.DisplayControl.LoadScheme("Loc: Top View")` (returns True;
406
+ `DisplayControl.Name` reads the current scheme back; `SaveScheme`,
407
+ `LoadUserScheme` beside it), and Fit Board is `ActiveView.SetExtentsToBoard()`
408
+ (`SetExtentsToAll`, `SetExtents(...)` too). `Application.Gui.ProcessCommand(name)`
409
+ runs any command of `SDD_HOME/standard/automation/Commands_XpeditionLayout.csv`
410
+ by its internal name (`VIEW_FITBOARD` → True; a name with arguments is "未找到命令").
411
+ `pcb show` uses these; driving the toolbar combo `CMD_DISPLAY_SCHEMES` through UI
412
+ Automation is only its fallback — on a Layout started by `pcb annotate`,
413
+ pywinauto's `Desktop().window(handle=…).descendants()` came back empty while
414
+ `Application(backend="uia").connect(process=pid).top_window()` saw the tree.
415
+ The scheme picked last is kept in `Config/<user>/Graphics Settings.hkp`
416
+ (`"[Virtual].iDC.SchemeSelectorName"`), which is what the board opens with.
417
+ `Loc: All On` shows outlines, pads, reference designators and the ratsnest.
418
+
419
+ ## Layout facts from the placement and routing round
420
+
421
+ | Fact | Detail |
422
+ |---|---|
423
+ | `Component.Extrema` | the placement outline only; the reference designator text above it is not included, so a planner that packs parts by their extents puts the labels of neighbours on top of each other |
424
+ | `RoutePass.LayerSelect(n, bool)` | accepts the **inner** layers only (2 and 3 on a four-layer board); 1 and 4 are "invalid parameters" — the outer layers are always the router's. `pcb route --layers 1,4` therefore disables 2 and 3. A handful of traces still landed on layer 3 in the first run; a second run after `--unroute` is the check |
425
+ | `TraceSegment` | has `Point1X/Y`, `Point2X/Y`, `Geometry`, no `Width`; a `Trace`'s width is `Geometry.LineWidth` (thousandths of an inch). Trace widths cannot be set through Layout's automation (`NetClass.MinTraceWidth` and friends are read-only): see the Constraint Manager section |
426
+ | `PutPlaneShape(..., bRouteObstruct, ...)` | with `bRouteObstruct` true (the earlier default) the shape blocks the autorouter on its layer — a board with pours on the outer layers routed nothing — and laid over traces already routed the call fails with "DRC 违反"; false lets traces through and the plane data flows around them. `PlaneShape.RouteObstructed` can be cleared afterwards too |
427
+ | Outer pours and the router | with the pours in place the router counts a plane net as routed while the regenerated copper leaves pins cut off (5 GND pins here) and a Fanout pass does not add vias for them; pour the outer layers after routing instead |
428
+ | `PointsArray` | three rows (x, y, r) in thousandths of an inch; a row with `r ≠ 0` is the centre of an arc from the previous row to the next, **positive r clockwise, negative counter-clockwise** (a pad's rounded corners come back positive on a clockwise outline; the board outline's corners negative on a counter-clockwise one). `Geometry.IsCircle()` first: a circle has `CircleX/Y/R` and `PointsArray` raises "the geometry for this call is incorrect" |
429
+ | Geometry for a picture | `Pin.Pads` / `Via.Pads` (one per layer: `Layer`, `Name`, `ShapeType`, `Geometries`), `Pin.Holes` / `Via.Holes` / `MountingHole.Holes` (`GetDrillSize(unit)`, `Plated`), `Trace.Geometry`, `PlaneShape.GeneratedPlanes[].Geometry` with `Cutouts`, `FabricationLayerGfxs` (`Type` 1 assembly, 2 silkscreen; `Side`, `Geometry.LineWidth`), `FabricationLayerTexts` (`TextString`, `Format.Height/Orientation/Mirrored`; `StrokeText()` for the vector fonts only), `Component.PlacementOutlines`, `Pin.GetPositionX/Y(unit)` |
430
+ | Window capture | `pcb show --output` captures a black PNG while the desktop is locked (the result says `blank` with that hint); UI Automation still works. `pcb render` draws the board from its geometry instead |
431
+ | Rounded outline | `PutBoardOutline` points array with arcs: per corner the rows `(start, 0)`, `(centre, −r)`, `(end, 0)`; a positive radius on the centre row draws the 270° arc the other way, a radius on the start or end row is an "invalid point array". `Geometry.GetRectMinX` etc. then fail with "the geometry for this call is incorrect": they only serve rectangles; read `Extrema` |
432
+ | Save prompt | while the outline was in that state the adapter's open logic tried to open the board again and Layout asked "your design and local library have changed… save all / library only / don't save / cancel" (four buttons, so the generic answerer leaves it); pressing `取消(C)` through UI Automation recovers |
433
+ | `PutMountingHoleEx(x, y, padstackName, bFromCentralLib, nDepth, bMirrored, pNet, pComponent, eFixed, eUnit)` | places a mounting hole by padstack name; `None` for the two object arguments, `True` to take the padstack from the central library. `MountingHoles` lists them with `GetPositionX/Y(unit)` |
434
+ | Display schemes | `Loc: Placement` shows pads, bodies and designators on a dark background without traces or planes — the picture to judge a placement by; `Loc: All On` shows everything, **but draws plane copper as outlines only** (`"Option.Planes.Data.Fill" "0"` in the scheme) and every layer and text layer at once. `pcb show --scheme` picks either; `--top-view` writes and picks `Loc: Top View` |
435
+ | Scheme files | `PCB/Config/<name>.dcs`, HKP-style text (`.Global_Constants`, `..File_Type Graphics_Scheme`). The `..Trace_Layer_On ( T T … )` arrays and `..Assy_Ref_Des_On True` toggles are the legacy section and this Layout ignores them; what counts is the key-value section: `"LayerControl.N" "d:1" "e:1" …` per layer, item entries such as `"Fabrication.Assembly.Part.Text.RefDes.Top"`, `"Fabrication.Silkscreen.Part.Text.RefDes.Top"`, `"Place.Part.Text.RefDes.Top"` (the designator drawn at the cell, a third copy of the name), `"Part.Cell.Origin.Top"` (the origin marker), `"Part.PlaceOutline.Top"`, `"Fabrication.DrillDrawingThrough"` — an item is drawn only with **both** `d:1` and `e:1` — and `"Option.*"` values (`Planes.Data.Fill`, `Pin.Number.Top`, `Pin.Type.Top`, `Pin.NetName.Top`, `Fabrication.AssemblyItems.Top`). A file added there appears in `Document.DisplaySchemes` at once but in the toolbar combo only after the board reopens |
436
+ | Three names per part | every part carries its designator three times on screen: the silkscreen text (printed on the board), the assembly-layer text (the assembly drawing; 1 mm on KiCad test points, 0.4 mm on the others) and Layout's placement-level designator at the cell. Normal; only the silkscreen one matters for the board. `Loc: Top View` shows that one alone |
437
+
438
+ ## Hand routing through the automation (verified 2026-09-15)
439
+
440
+ | Fact | Detail |
441
+ |---|---|
442
+ | `PutTrace(nLayer, pNet, dWidth, nPnts, points, pComponent=None, eFixType=0, eUnit)` | adds a trace; the points array is the three-row `(x, y, r)` form, width and points in the unit given (`UNIT_MM`); returns the trace (`Geometry.LineWidth` in th). A trace that violates a clearance rule fails with "DRC 违反" — Layout's online DRC judges every call |
443
+ | `PutVia(dX, dY, pPadstack, pNet, pComponent=None, eFixed=0, eUnit)` | places a via; `pPadstack` is an object, not a name. `Document.Padstacks` lists only the padstacks in use, so a board whose vias were all deleted has no via padstack: `PutPadstack(1, nLayers, "026VIA", False, True)` pulls one from the central library and returns it; `GetPadstackNames(2, -1, "*", True)` lists the library's via padstacks (`026VIA`, `VC…`). `DefaultViaPadstack` takes other arguments ("无效的参数数目") |
444
+ | Via next to a pad | a via pad within the clearance of a surface-mount pad is refused **even for the same net** (SCL via 0.07 mm from U303.4's pad, VBAT via 0.1 mm from U301.1's); keep 0.254 mm to any pad the via does not sit inside |
445
+ | Test points | `TestPoint_Pad_D1.0mm` cells are surface-mount pads (`SMD-RND1`), so an inner-layer trace ending under one connects nothing and is a "Hangers" hazard; a via beside the pad with a top stub is the connection |
446
+ | `Net.NumberOfOpens`, `Pin.IsConnectedToPathOrAreaOnLayer(n)` | the open count per net and, per pin, whether copper on layer n touches it — enough to tell "pin not reached" from "two islands" |
447
+ | Unconnected pins | Layout shows them as net `(Net0)`; there is no such net in `Nets` (`PutTrace` for it is "the board has no such net") |
448
+ | `RoutePass` type 7 (remove hangers) | `PassType(7, 1, 3, False, False)` is "参数无效"; deleting the hanging trace (`pcb unroute --at`) and drawing it to the via instead is the fix |
449
+ | `Component.FabricationLayerTexts` | the cell's texts; `Type` 2 silkscreen, `TextType` 1 the designator; `Move(x, y, eUnit)` moves one, `Format.Orientation` turns with the part (a designator of a part at 90° stands upright), `Extrema` is its box |
450
+ | `RespectComponentPlacementDRC` | true makes `Component.Place` refuse a spot that touches another part — what `pcb move` wants; `pcb arrange` turns it off while it lifts and re-places everything |
451
+ | Trace-width hazards | the stock `(Default)` class allows exactly one width (min = typical = expansion = 10 th); a 0.3 mm stub is a `TraceWidths` hazard until the expansion width is raised (`pcb rules --class "(Default)" --width 0.254 --min 0.254 --expansion 0.5`; `--width` is required, and `--min` keeps the stock minimum) |
452
+
453
+ ## Constraint Manager automation (net classes and trace widths)
454
+
455
+ | Fact | Detail |
456
+ |---|---|
457
+ | Server | `ConstraintsAuto` (late-bound `Dispatch`; `gencache.EnsureDispatch` fails with "can not automate the makepy process"); needs `SDD_HOME` and Layout's `PATH` set first, else "Cannot locate Mentor environment" |
458
+ | Loading | `auto.Design.CreateDesignParams()` → `ProjectFile`, `Board` (the design name, `Board1`), `DesignContext = 1` (layout; 0 answers "设计上下文丢失"), then `design.Load(params)` / `UnLoad()` |
459
+ | Objects | `design.NetClasses` (`Add(name)`, `Item(name)`, `NetClass.AssignNet(net)`), `design.Nets` and `design.PowerNets` (GND lives in the second; each net has `NetClass.Name`), `design.PhysicalRules.Schemes.Item(1)` = `(Master)` → `.NetClasses.Item(name).Layers` (`SIGNAL_1…4`) → `GetConstraints(7)` → constraints with `Name`, `Value`, `ConstraintType` (283 trace width minimum, 439 typical, 183 expansion). Values are thousandths of an inch (0.5 mm = 19.685) and settable; `nc.Constraints` on a net class answers "对象不可约束" |
460
+ | Layout sees it only after | `doc.ProjectIntegration.SynchCES` (returns true); `LoadCES` and reopening the board did not; afterwards `NetClass.TypicalTraceWidth(1, "(Master)", unit)` in Layout reports the new width and the router uses it |
461
+
462
+ ## Manufacturing outputs (ODB++, Gerber, NC drill)
463
+
464
+ | Fact | Detail |
465
+ |---|---|
466
+ | No automation call | `IMGCPCBDocument` has `ExportAscii`, `ExportComponentData`, `GenerateEDMOutputs` and nothing for Gerber/ODB++/drill; the Output menu commands are `OUTPUT_ODBPP` 33017, `OUTPUT_GERBER` 33016, `OUTPUT_NCDRILL` 33018, `OUTPUT_SILKSCREEN` 33019 (Silkscreen Generator), `OUTPUT_NEUTRALFILE` 33773, `FILE_EXPORT_IPCD356B` 32938; `Gui.ProcessCommand` opens their dialogs (`ODB++ 设计输出`, `Gerber 输出`, `NC 钻孔生成`), OK is `确定(O)` / `确定` |
467
+ | Settings files | `Config/ODBSetup.ocf` (+ `.eocf`), `Config/PlotSetup.gpf` (+ `GerberPlot.egpf`), `Config/GerberMachineFile1.gmf`, NC drill scheme `Sys: DrillEnglish.dff`: HKP-style text. **The dialogs hold their settings in memory while the board is open and rewrite the files on OK**, so a file edited while the board is open is overwritten; edit it with the board closed and reopen |
468
+ | ODB++ | the stock setup leaves the drill span out (`..NAME "d_1_4"` / `...INCLUDE NO`) and the outline off (`.BOARD_OUTLINE NO`); with both on, the job has a `d_1_4` `TYPE=DRILL` layer with tools and hits and a `profile`. Job folder `Output/ODBpp/<OutputJobName lower-cased>/`, silkscreen `sst` complete, units inch |
469
+ | Gerber silkscreen | `..BoardItem SilkscreenTop` is accepted and writes a header-only file; the installation's sample setups draw the cells: `..CellType <each type>`, `..CellItemsSide Top`, `...CellItemsLayer 1` (bottom: `0`, which the dialog rewrites as the last layer number), `...CellItem SilkscreenOutline`, `...CellItem SilkscreenReferenceDesignator`. `GeneratedSilkscreen*` files need the Silkscreen Generator, whose per-package-group layer checklist is not exposed to UI Automation (clicks toggle blindly) |
470
+ | Gerber output | RS-274X, `Output/Gerber/*.gdo`, coordinates modal (a line may carry only X or Y); the stock 4-layer setup writes the outer copper twice (`EtchLayer1Top` and `EtchLayerTop`) and negative plane files for the inner layers |
471
+ | NC drill | `Output/NCDrill/ThruHolePlated.ncd` and `ThruHoleNonPlated.ncd`, Excellon 2.4 inch trailing-zero, modal coordinates |
472
+ | Files held after the run | NC drill leaves `PCB/LogFiles/DrillPrefs.txt` open in the Layout process after the document closes; `Application.Quit` puts Layout on its start page (`[首页]`) with the file still held, so `pcb create --replace` ends a document-less Layout by its process (`tasklist` / `taskkill`) when the folder will not go |
473
+ | Setups written back | a dialog that was opened earlier in the Layout session writes its in-memory settings over the patched file on OK even after the board was closed and reopened (the ODB++ job came out without `d_1_4` once); `pcb export` reads the setups again after the run and repeats the patch and the dialogs once (`rounds`) |
474
+ | 3D | `WINDOW_3D_VIEW` 33155 opens a `[3D View : <board>]` tab; view commands 53330–53338; `EXP3D_EXPORT` 53325 exports STEP/PDF/PNG; cells without a 3D model are drawn as their placement outline at the cell's height (0 for converted KiCad cells, so flat); `CONDUCTORVIEWRMB_VIEW_PHOTOREALISTIC` 53361 exists |
475
+
476
+ ## KiCad footprints as cells
477
+
478
+ KiCad ships its footprint library as text (`share/kicad/footprints/<library>.pretty/*.kicad_mod`,
479
+ 155 libraries and 15 450 footprints in KiCad 9 here) under CC-BY-SA 4.0 with the KiCad library
480
+ exception. `xpedition_cli.kicad_footprints` turns each `.pretty` folder into one cell partition
481
+ and `library kicad-import` (the adapter's `kicad_import`) feeds them through
482
+ `HKP2PadstackDB` / `HKP2CellDB`; a design then names a footprint as its package
483
+ (`"packages": {"RES": "kicad:Resistor_SMD:R_0603_1608Metric"}`) and `library build` writes
484
+ parts that reference the cell and registers its partition in `LIST 2dCellLibraries`. The whole
485
+ library takes about a quarter of an hour. Facts that cost time:
486
+
487
+ | Fact | Detail |
488
+ |---|---|
489
+ | Grammar reference | `CellDB2HKP -i X.cel -o X.hkp -a` and `PadstackDB2HKP -i PadstackDB.psk -o X.hkp -a` export what the importers read; the stock `Drawing` and `Starpoints and Tiebars` partitions show text, arcs, circles, rectangles and filled shapes |
490
+ | Pad shapes | `..ROUND ...DIAMETER`, `..SQUARE ...WIDTH`, `..RECTANGLE`/`..OBLONG ...WIDTH ...HEIGHT`, `..RADIUS_CORNER_RECTANGLE ...WIDTH ...HEIGHT ...RADIUS`; holes `..ROUND ...DIAMETER` or `..SLOT ...WIDTH ...HEIGHT`. KiCad `roundrect` maps to the radius-corner rectangle, `oval` to oblong, `custom` to the rectangle around its primitives |
491
+ | Graphics | one path per outline block: `..SILKSCREEN_OUTLINE`/`..ASSEMBLY_OUTLINE`/`..PLACEMENT_OUTLINE` with `...SIDE MNT_SIDE` and `...POLYLINE_PATH` (`....WIDTH`, `....XY (x, y) …`), `...RECT_PATH` (two corners), `...CIRCLE_PATH` (`....XY`, `....RADIUS`) or `...POLYLINE_SHAPE` + `....SHAPE_OPTIONS FILLED`. Any number of silkscreen blocks survive; **only one assembly outline is kept per cell**, so the converter emits the largest closed loop of the fabrication drawing; the placement outline must be one closed shape |
492
+ | Reference designator | `..TEXT "Ref Des"` / `...TEXT_TYPE REF_DES` / `...DISPLAY_ATTR` with `....XY`, `....TEXT_LYR SILKSCREEN_MNT_LYR`, `....HEIGHT`, `....WIDTH`, `....STROKE_WIDTH`, `....ROTATION`, `....FONT "vf_std"` places the refdes at the cell's own size; a cell without it gets Layout's default text, which is what made the placeholder boards look like a sea of "C"s |
493
+ | Holes in package cells | `..MOUNTING_HOLE ...PADSTACK ...XY ...ROTATION` is accepted inside `.PACKAGE_CELL` (a `MOUNTING_HOLE` padstack: clearance pad, mask, hole, no pad); KiCad `np_thru_hole` pads and unnumbered plated pads become these |
494
+ | Mount type | `..MOUNT_TYPE MIXED` is accepted with any package group; the converter derives it from the pads that became pins |
495
+ | Cell names | at most **64 characters**: `HKP2CellDB` logs `无法添加单元 "…"。正在跳到下一个单元。` for longer ones and then **saves nothing** for the whole file (`遇到 N 个错误。将不会保存单元数据库文件。`, exit code 1). 685 KiCad names are longer; they are cut to 56 characters plus `~` and seven hex digits of a SHA-1 of the full name (`kicad_footprints.cell_name`), and `kicad_import` retries a partition once without any cell the log refused |
496
+ | Log noise | every converter log contains `正在检查文件格式错误...` and `未找到文件格式错误。` ("checking for file format errors… none found"); a log check that matches the word "error" alone reports success as failure. `_tool_log` matches a leading `错误`/`error`, `错误:`/`error:`, `无法添加`, `遇到 N 个错误` |
497
+ | Coordinates | KiCad's Y points down, Xpedition's up: every Y is negated; rotations are counter-clockwise on screen in both, so angles stay |
498
+ | Same pad number twice | every copper land of a number stays, as a pad of that pin (a MOSFET's drain leads and paddle), and forward annotation puts them all on its net; only a pad lying wholly inside a larger one of its number (a thermal pad's via or a copper paste window) is dropped, reported as `inside_same_number`; paste-only, back-side and `connect` pads are dropped too |
499
+ | Merge | `HKP2PadstackDB … -m` and `HKP2CellDB … -m` add to what exists, replacing same-named entries and registering the partition in the `.lmc`; Layout and Designer may stay open with the project (Designer's project is closed and reopened by the adapter as for `library build`) |
@@ -0,0 +1,33 @@
1
+ {
2
+ "baseline": "d42b226a5b1812b2937ded096bf618b8692c4f9a",
3
+ "run_id": "35248354040",
4
+ "python": "3.12.14 (main, Aug 13 2026, 02:47:42) [GCC 13.3.0]",
5
+ "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39",
6
+ "baseline_selected": {
7
+ "tests": 6,
8
+ "failures": 6,
9
+ "errors": 0,
10
+ "skipped": 0
11
+ },
12
+ "full_suite": {
13
+ "tests": 240,
14
+ "failures": 0,
15
+ "errors": 0,
16
+ "skipped": 0
17
+ },
18
+ "ruff": "passed",
19
+ "version_sync": "passed",
20
+ "contract_local_only": "passed",
21
+ "native_xpedition_executed": false,
22
+ "limits": [
23
+ "healthy local store; cooperating versions",
24
+ "storage degradation remains permitted",
25
+ "not a project write lock or exactly-once native execution"
26
+ ],
27
+ "source_sha256": {
28
+ "xpedition_cli/confirm.py": "67cc748364aa7cc5801b33ba6dacfa24f7cb0fce5c8ff9567a6c193eead967d7",
29
+ "xpedition_cli/confirmation_store.py": "e991a66a8a8d9f97cfa07b59dbff357c90207ba6d70fbb5f88dc9d965fbaa38a",
30
+ "tests/test_confirm_concurrency.py": "e919a74330dac1380bd1517e094aff45a8464d3d24fe46a5094c922f3cb18d7d",
31
+ "tests/test_confirm_cli.py": "629421ef7eb224adb233f51f5e600d45e758ef03476495a79982e59a9a78eec0"
32
+ }
33
+ }
@@ -0,0 +1,33 @@
1
+ # Diagnostic backend boundaries
2
+
3
+ *Historical record: written for pull request #6 before it was merged on 2026-09-18;
4
+ kept as the design and evidence record. The current contract is
5
+ `xpedition-cli reference`.*
6
+
7
+ Two narrow fixes based on `main@d42b226`. This PR is independent of PRs #4 and #5;
8
+ neither existing branch is changed or merged.
9
+
10
+ `agent capabilities` now returns capability metadata before any project load or
11
+ backend requirement check, matching the existing streaming capability method.
12
+ This does not claim the configured native adapter is healthy or licensed. The
13
+ registry may still inspect local capability configuration. Use the appropriate
14
+ runtime diagnostics before operating on a real project.
15
+
16
+ `analysis run` is declared MockBackend-only in the existing reference. Previously,
17
+ selecting NativeBackend loaded a native snapshot and then executed the Mock
18
+ analysis function (the result did label its engine `mock`). The explicit native
19
+ combination now fails with non-retryable `E_BACKEND_UNAVAILABLE` before reading a
20
+ project. This is an intentional compatibility tightening, not a new native engine.
21
+
22
+ Native stored analysis reads (`analysis results/erc/drc/dfm`) are not disabled,
23
+ but the native snapshot never fills `analysis`, so on NativeBackend they return an
24
+ empty list. The native `pcb drc` and `review run` entry points remain separate
25
+ capabilities; they are not interchangeable with every analysis kind, and this patch
26
+ does not claim a native DFM runner exists.
27
+
28
+ The regression tests exercise the public CLI boundary, fail if unsupported
29
+ analysis touches either backend access or the mock analysis function, and ensure
30
+ capability discovery ignores invalid project contents. Stored native reads use
31
+ a fake adapter to protect their existing dispatch path. No licensed Xpedition
32
+ installation or production project is used. The branch workflow records full-suite
33
+ results in `DIAGNOSTIC_BOUNDARIES_VALIDATION.json` only after all checks pass.
@@ -0,0 +1,12 @@
1
+ {
2
+ "baseline": "d42b226a5b1812b2937ded096bf618b8692c4f9a",
3
+ "run_id": "35233583013",
4
+ "tests": 219,
5
+ "failures": 0,
6
+ "errors": 0,
7
+ "skipped": 0,
8
+ "ruff": "passed",
9
+ "version_sync": "passed",
10
+ "contract_local_only": "passed",
11
+ "native_xpedition_executed": false
12
+ }