enginehall 2.0.0__py3-none-any.whl

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 (75) hide show
  1. enginehall-2.0.0.dist-info/METADATA +511 -0
  2. enginehall-2.0.0.dist-info/RECORD +75 -0
  3. enginehall-2.0.0.dist-info/WHEEL +4 -0
  4. enginehall-2.0.0.dist-info/entry_points.txt +4 -0
  5. enginehall-2.0.0.dist-info/licenses/LICENSE +51 -0
  6. pcba/THIRD_PARTY_NOTICES.md +72 -0
  7. pcba/__init__.py +6 -0
  8. pcba/adapters/__init__.py +10 -0
  9. pcba/adapters/altium_bridge/MANIFEST.json +5 -0
  10. pcba/adapters/altium_bridge/__init__.py +1 -0
  11. pcba/adapters/altium_bridge/bridge.pas +3342 -0
  12. pcba/adapters/altium_drive.py +1160 -0
  13. pcba/adapters/altium_house.py +591 -0
  14. pcba/adapters/altium_ops.py +858 -0
  15. pcba/adapters/altium_outjob.py +247 -0
  16. pcba/adapters/altium_uia.py +304 -0
  17. pcba/adapters/altium_vcs.py +197 -0
  18. pcba/adapters/altium_win32.py +457 -0
  19. pcba/adapters/cubemx_drive.py +248 -0
  20. pcba/baseline.py +151 -0
  21. pcba/changes.py +81 -0
  22. pcba/cli.py +891 -0
  23. pcba/components.py +323 -0
  24. pcba/config.py +180 -0
  25. pcba/data/components/generic.json +61 -0
  26. pcba/discover.py +350 -0
  27. pcba/doctor.py +131 -0
  28. pcba/house.py +123 -0
  29. pcba/mcp/__init__.py +5 -0
  30. pcba/mcp/altium_server.py +1358 -0
  31. pcba/mcp/protocol.py +224 -0
  32. pcba/mcp/server.py +332 -0
  33. pcba/model/__init__.py +38 -0
  34. pcba/model/design.py +232 -0
  35. pcba/model/finding.py +148 -0
  36. pcba/model/pinmap.py +200 -0
  37. pcba/parsers/__init__.py +0 -0
  38. pcba/parsers/altium/__init__.py +37 -0
  39. pcba/parsers/altium/_sheet.py +293 -0
  40. pcba/parsers/altium/geometry.py +103 -0
  41. pcba/parsers/altium/netlist.py +302 -0
  42. pcba/parsers/altium/pinmap.py +336 -0
  43. pcba/parsers/altium/project.py +249 -0
  44. pcba/parsers/altium/records.py +227 -0
  45. pcba/parsers/firmware/__init__.py +5 -0
  46. pcba/parsers/firmware/index.py +250 -0
  47. pcba/parsers/ioc/__init__.py +49 -0
  48. pcba/parsers/ioc/mcu_db.py +180 -0
  49. pcba/parsers/ioc/reader.py +182 -0
  50. pcba/parsers/ioc/validate.py +192 -0
  51. pcba/parsers/kicad/__init__.py +103 -0
  52. pcba/parsers/kicad/board.py +269 -0
  53. pcba/parsers/kicad/kicad_cli.py +190 -0
  54. pcba/parsers/kicad/pinmap.py +90 -0
  55. pcba/parsers/kicad/schematic.py +517 -0
  56. pcba/parsers/kicad/sexpr.py +136 -0
  57. pcba/publish.py +269 -0
  58. pcba/report/__init__.py +7 -0
  59. pcba/report/json_report.py +39 -0
  60. pcba/report/sarif.py +128 -0
  61. pcba/report/text.py +304 -0
  62. pcba/requirements.py +320 -0
  63. pcba/review.py +177 -0
  64. pcba/rules/__init__.py +312 -0
  65. pcba/rules/cross_domain.py +496 -0
  66. pcba/rules/kicad_board.py +775 -0
  67. pcba/rules/kicad_common.py +167 -0
  68. pcba/rules/kicad_components.py +603 -0
  69. pcba/rules/kicad_schematic.py +621 -0
  70. pcba/rules/locate.py +22 -0
  71. pcba/rules/schematic.py +123 -0
  72. pcba/rules/signals.py +149 -0
  73. pcba/rules/spice.py +59 -0
  74. pcba/rules/stm32.py +155 -0
  75. pcba/simulation.py +143 -0
@@ -0,0 +1,3342 @@
1
+ { PCBA Studio - Altium Designer bridge library. }
2
+ { }
3
+ { The adapter (pcba/adapters/altium_drive.py) verifies this file's SHA-256, then }
4
+ { appends a generated `Body` that calls only the procedures below with literal }
5
+ { arguments, and a `Run` entry point, and runs the result with }
6
+ { ScriptingSystem:RunScriptFile. Nothing here parses a request: arguments arrive }
7
+ { as Pascal literals the adapter has validated and escaped. }
8
+ { }
9
+ { Rules this code follows, all measured on Altium Designer Develop 26.10: }
10
+ { - integers only: German Windows formats floats with a comma; }
11
+ { - no `raise`: DelphiScript rejects it at parse time; ops call Fail() instead, }
12
+ { and every op returns at once when an earlier op failed; }
13
+ { - the response goes to <exchange>\<id>.json via a temp file, so a late reply }
14
+ { can never be read as the answer to another request; }
15
+ { - a new document joins the focused project, whichever that is: it is unbound }
16
+ { from every other project and bound to its own. }
17
+ { }
18
+ { Portions follow patterns published in altium-mcp, Copyright (c) 2025 Siddharth }
19
+ { Ahuja, MIT licence; see THIRD_PARTY_NOTICES.md. }
20
+
21
+ const
22
+ BRIDGE_VERSION = '1.6.0';
23
+ RA = 1;
24
+
25
+ var
26
+ G_ID : String;
27
+ G_DONE : Boolean;
28
+ G_EX : String;
29
+ FailS : String;
30
+ ItemsS : String;
31
+ CurOpName : String;
32
+ CurOp : Integer;
33
+ Ok : Boolean;
34
+ LogSL : TStringList;
35
+ CurSch : ISch_Document;
36
+ CurSchDoc : IServerDocument;
37
+ CurLib : ISch_Lib;
38
+ CurLibDoc : IServerDocument;
39
+ CurComp : ISch_Component;
40
+ CurPcbLib : IPCB_Library;
41
+ CurPcbLibDoc : IServerDocument;
42
+ CurFp : IPCB_LibComponent;
43
+ CurBoard : IPCB_Board;
44
+ CurBoardDoc : IServerDocument;
45
+ CurSheetSym : ISch_SheetSymbol;
46
+
47
+ { ---------------------------------------------------------------- plumbing --- }
48
+
49
+ procedure Log(S: String);
50
+ begin
51
+ LogSL.Add(FormatDateTime('hh:nn:ss.zzz', Now) + ' ' + S);
52
+ LogSL.SaveToFile(G_EX + G_ID + '.log');
53
+ end;
54
+
55
+ function J(S: String): String;
56
+ var
57
+ I, C : Integer;
58
+ R : String;
59
+ begin
60
+ R := '';
61
+ for I := 1 to Length(S) do
62
+ begin
63
+ C := Ord(S[I]);
64
+ if C = 34 then R := R + '\"'
65
+ else if C = 92 then R := R + '\\'
66
+ else if C = 10 then R := R + '\n'
67
+ else if C = 13 then R := R + '\r'
68
+ else if C = 9 then R := R + '\t'
69
+ else if (C < 32) or (C > 126) then R := R + '\u' + IntToHex(C, 4)
70
+ else R := R + S[I];
71
+ end;
72
+ Result := R;
73
+ end;
74
+
75
+ function F(K, V: String): String;
76
+ begin
77
+ Result := ',"' + K + '":"' + J(V) + '"';
78
+ end;
79
+
80
+ function FI(K: String; V: Integer): String;
81
+ begin
82
+ Result := ',"' + K + '":' + IntToStr(V);
83
+ end;
84
+
85
+ function FB(K: String; V: Boolean): String;
86
+ begin
87
+ if V then Result := ',"' + K + '":true' else Result := ',"' + K + '":false';
88
+ end;
89
+
90
+ procedure Fail(Msg: String);
91
+ begin
92
+ if Ok then
93
+ begin
94
+ Ok := False;
95
+ FailS := Msg;
96
+ end;
97
+ Log('FAIL ' + Msg);
98
+ end;
99
+
100
+ procedure OpBegin(I: Integer; Name: String);
101
+ begin
102
+ if not Ok then Exit;
103
+ CurOp := I;
104
+ CurOpName := Name;
105
+ Log('op ' + IntToStr(I) + ' ' + Name);
106
+ end;
107
+
108
+ procedure Emit(Fields: String);
109
+ begin
110
+ if ItemsS <> '' then ItemsS := ItemsS + ',';
111
+ ItemsS := ItemsS + '{"op":' + IntToStr(CurOp) + ',"name":"' + CurOpName + '"' + Fields + '}';
112
+ end;
113
+
114
+ procedure Init(Id, Ex: String);
115
+ begin
116
+ G_ID := Id;
117
+ G_EX := Ex;
118
+ Ok := True;
119
+ FailS := '';
120
+ ItemsS := '';
121
+ CurOp := -1;
122
+ CurOpName := '';
123
+ G_DONE := False;
124
+ LogSL := TStringList.Create;
125
+ Log('start bridge ' + BRIDGE_VERSION);
126
+ end;
127
+
128
+ procedure Finish(Err: String);
129
+ var
130
+ SL : TStringList;
131
+ Body : String;
132
+ begin
133
+ if (Err <> '') then Fail(Err);
134
+ Body := '{"id":"' + G_ID + '","bridge":"' + BRIDGE_VERSION + '"' + FB('ok', Ok);
135
+ if not Ok then Body := Body + FI('failed_op', CurOp) + F('failed_name', CurOpName) + F('error', FailS);
136
+ Body := Body + ',"items":[' + ItemsS + ']}';
137
+ SL := TStringList.Create;
138
+ SL.Text := Body;
139
+ DeleteFile(G_EX + G_ID + '.tmp');
140
+ SL.SaveToFile(G_EX + G_ID + '.tmp');
141
+ SL.Free;
142
+ DeleteFile(G_EX + G_ID + '.json');
143
+ RenameFile(G_EX + G_ID + '.tmp', G_EX + G_ID + '.json');
144
+ Log('end ok=' + BoolToStr(Ok, True));
145
+ end;
146
+
147
+ procedure Done;
148
+ begin
149
+ G_DONE := True;
150
+ end;
151
+
152
+ procedure Complete;
153
+ begin
154
+ { Body ends with Done. Reaching here without it means an operation stopped the script
155
+ without raising anything: never report that as success. }
156
+ if G_DONE then Finish('')
157
+ else Finish('the script stopped inside this operation without an error message');
158
+ end;
159
+
160
+ function IntList(Csv: String): TStringList;
161
+ begin
162
+ Result := TStringList.Create;
163
+ Result.Delimiter := ',';
164
+ Result.StrictDelimiter := True;
165
+ Result.DelimitedText := Csv;
166
+ end;
167
+
168
+ { ------------------------------------------------------- workspace / documents --- }
169
+
170
+ function FindProject(Path: String): IProject;
171
+ var
172
+ WS : IWorkspace;
173
+ I : Integer;
174
+ begin
175
+ Result := nil;
176
+ WS := GetWorkspace;
177
+ for I := 0 to WS.DM_ProjectCount - 1 do
178
+ if SameText(WS.DM_Projects(I).DM_ProjectFullPath, Path) then Result := WS.DM_Projects(I);
179
+ end;
180
+
181
+ procedure CloseDocPath(Path: String; Save: Boolean);
182
+ var
183
+ SD : IServerDocument;
184
+ begin
185
+ { Closing a modified document raises Altium's "Unsaved Changes" dialog, which blocks the
186
+ script. Save or discard explicitly first so it can never appear. }
187
+ SD := Client.GetDocumentByPath(Path);
188
+ if (SD <> nil) and SD.Modified then
189
+ begin
190
+ if Save then SD.DoFileSave('') else SD.SetModified(False);
191
+ end;
192
+ ResetParameters;
193
+ AddStringParameter('ObjectKind', 'Document');
194
+ AddStringParameter('FileName', Path);
195
+ RunProcess('WorkspaceManager:CloseObject');
196
+ end;
197
+
198
+ function ShowDoc(Kind, Path: String): IServerDocument;
199
+ begin
200
+ Result := Client.OpenDocument(Kind, Path);
201
+ if Result <> nil then Client.ShowDocument(Result);
202
+ end;
203
+
204
+ procedure ProjectOpen(Path: String);
205
+ var
206
+ Prj : IProject;
207
+ begin
208
+ if not Ok then Exit;
209
+ if not FileExists(Path) then begin Fail('project file not found: ' + Path); Exit; end;
210
+ Prj := FindProject(Path);
211
+ if Prj = nil then
212
+ begin
213
+ ResetParameters;
214
+ AddStringParameter('ObjectKind', 'Project');
215
+ AddStringParameter('FileName', Path);
216
+ RunProcess('WorkspaceManager:OpenObject');
217
+ Prj := FindProject(Path);
218
+ end;
219
+ if Prj = nil then begin Fail('Altium did not open the project: ' + Path); Exit; end;
220
+ Prj.DM_SetAsCurrentProject;
221
+ Emit(F('project', Path) + FI('documents', Prj.DM_LogicalDocumentCount));
222
+ end;
223
+
224
+ function InOtherProject(DocPath, PrjPath: String): Boolean;
225
+ var
226
+ WS : IWorkspace;
227
+ Prj : IProject;
228
+ I, K: Integer;
229
+ begin
230
+ Result := False;
231
+ WS := GetWorkspace;
232
+ for K := 0 to WS.DM_ProjectCount - 1 do
233
+ begin
234
+ Prj := WS.DM_Projects(K);
235
+ if not SameText(Prj.DM_ProjectFullPath, PrjPath) then
236
+ for I := 0 to Prj.DM_LogicalDocumentCount - 1 do
237
+ if SameText(Prj.DM_LogicalDocuments(I).DM_FullPath, DocPath) then Result := True;
238
+ end;
239
+ end;
240
+
241
+ procedure ProjectClose(Path: String; SaveAll: Boolean);
242
+ var
243
+ SD : IServerDocument;
244
+ Prj : IProject;
245
+ I : Integer;
246
+ Loaded : TStringList;
247
+ Shared : TStringList;
248
+ Left : String;
249
+ begin
250
+ if not Ok then Exit;
251
+ Prj := FindProject(Path);
252
+ if Prj = nil then begin Emit(FB('closed', True)); Exit; end;
253
+ { CloseObject acts on the focused project, whatever FileName says: focus this one first.
254
+ SaveAll saves the project and every loaded document first, so no "Unsaved Changes" prompt
255
+ can appear (a PCB's Modified flag can read False after an ECO, so loaded documents are
256
+ saved regardless). Without SaveAll nothing is saved: Altium asks about each modified
257
+ document ("Unsaved Changes", or "Confirm Save to Server" in an Altium 365 working copy)
258
+ and the drive answers by the request's answer policy, or cancels, which fails below. }
259
+ Prj.DM_SetAsCurrentProject;
260
+ Loaded := TStringList.Create;
261
+ Shared := TStringList.Create;
262
+ for I := 0 to Prj.DM_LogicalDocumentCount - 1 do
263
+ begin
264
+ SD := Client.GetDocumentByPath(Prj.DM_LogicalDocuments(I).DM_FullPath);
265
+ if SD <> nil then
266
+ begin
267
+ Loaded.Add(Prj.DM_LogicalDocuments(I).DM_FullPath);
268
+ { A library another open project also lists stays open legitimately. }
269
+ if InOtherProject(Prj.DM_LogicalDocuments(I).DM_FullPath, Path) then
270
+ Shared.Add(Prj.DM_LogicalDocuments(I).DM_FullPath);
271
+ if SaveAll then
272
+ begin
273
+ SD.SetModified(True);
274
+ SD.DoFileSave('');
275
+ end;
276
+ end;
277
+ end;
278
+ SD := Client.GetDocumentByPath(Path);
279
+ if (SD <> nil) and SaveAll then
280
+ begin
281
+ { Modified can read False while Altium still holds unsaved project state. }
282
+ SD.SetModified(True);
283
+ SD.DoFileSave('');
284
+ end;
285
+ ResetParameters;
286
+ AddStringParameter('ObjectKind', 'Project');
287
+ AddStringParameter('FileName', Path);
288
+ RunProcess('WorkspaceManager:CloseObject');
289
+ { A document that refuses to close does not stay with the closed project: Altium hands it
290
+ to another open project or leaves it free, and it later breaks an ECO. Only documents
291
+ another project listed before the close may stay open; fail on any other. }
292
+ Left := '';
293
+ for I := 0 to Loaded.Count - 1 do
294
+ if (Client.GetDocumentByPath(Loaded[I]) <> nil) and (Shared.IndexOf(Loaded[I]) < 0) then
295
+ Left := Left + Loaded[I] + '; ';
296
+ Loaded.Free;
297
+ Shared.Free;
298
+ if FindProject(Path) <> nil then
299
+ begin
300
+ Fail('project stayed open (a prompt was cancelled or refused?): ' + Path);
301
+ Exit;
302
+ end;
303
+ if Left <> '' then begin Fail('documents stayed open after closing the project: ' + Left); Exit; end;
304
+ Emit(FB('closed', True) + FB('saved', SaveAll));
305
+ end;
306
+
307
+ procedure ProjectState(Path: String);
308
+ var
309
+ WS : IWorkspace;
310
+ Prj : IProject;
311
+ D : IDocument;
312
+ SD : IServerDocument;
313
+ I : Integer;
314
+ Folder : String;
315
+ Loaded : Boolean;
316
+ Modified : Boolean;
317
+ begin
318
+ { Path '' lists the open projects; a project path lists its documents with what Altium holds
319
+ in memory (loaded, modified) and what is on disk (exists, inside the project folder). }
320
+ if not Ok then Exit;
321
+ WS := GetWorkspace;
322
+ if Path = '' then
323
+ begin
324
+ Folder := '';
325
+ if WS.DM_FocusedProject <> nil then Folder := WS.DM_FocusedProject.DM_ProjectFullPath;
326
+ for I := 0 to WS.DM_ProjectCount - 1 do
327
+ begin
328
+ Prj := WS.DM_Projects(I);
329
+ Emit(F('kind', 'project') + F('project', Prj.DM_ProjectFullPath)
330
+ + FI('documents', Prj.DM_LogicalDocumentCount)
331
+ + FB('current', SameText(Prj.DM_ProjectFullPath, Folder)));
332
+ end;
333
+ Exit;
334
+ end;
335
+ Prj := FindProject(Path);
336
+ if Prj = nil then begin Emit(F('kind', 'project') + F('project', Path) + FB('open', False)); Exit; end;
337
+ Folder := ExtractFilePath(Path);
338
+ for I := 0 to Prj.DM_LogicalDocumentCount - 1 do
339
+ begin
340
+ D := Prj.DM_LogicalDocuments(I);
341
+ SD := Client.GetDocumentByPath(D.DM_FullPath);
342
+ Loaded := SD <> nil;
343
+ Modified := False;
344
+ if Loaded then Modified := SD.Modified;
345
+ Emit(F('kind', 'document') + F('path', D.DM_FullPath) + F('doc_kind', D.DM_DocumentKind)
346
+ + FB('loaded', Loaded) + FB('modified', Modified) + FB('exists', FileExists(D.DM_FullPath))
347
+ + FB('inside', SameText(Copy(D.DM_FullPath, 1, Length(Folder)), Folder)));
348
+ end;
349
+ SD := Client.GetDocumentByPath(Path);
350
+ Modified := False;
351
+ if SD <> nil then Modified := SD.Modified;
352
+ Emit(F('kind', 'project') + F('project', Path) + FB('open', True) + FB('modified', Modified)
353
+ + FI('documents', Prj.DM_LogicalDocumentCount));
354
+ end;
355
+
356
+ procedure ProjectSave(Path: String);
357
+ var
358
+ SD : IServerDocument;
359
+ begin
360
+ if not Ok then Exit;
361
+ if FindProject(Path) = nil then begin Fail('project not open: ' + Path); Exit; end;
362
+ SD := Client.GetDocumentByPath(Path);
363
+ if SD = nil then begin Fail('Altium has no server document for ' + Path); Exit; end;
364
+ SD.SetModified(True);
365
+ Emit(F('project', Path) + FB('saved', SD.DoFileSave('')));
366
+ end;
367
+
368
+ procedure BindToProject(DocPath, PrjPath: String);
369
+ var
370
+ Prj, Free : IProject;
371
+ I : Integer;
372
+ Found : Boolean;
373
+ begin
374
+ Prj := FindProject(PrjPath);
375
+ if Prj = nil then begin Fail('project not open: ' + PrjPath); Exit; end;
376
+ Free := GetWorkspace.DM_FreeDocumentsProject;
377
+ if Free <> nil then
378
+ for I := Free.DM_LogicalDocumentCount - 1 downto 0 do
379
+ if SameText(Free.DM_LogicalDocuments(I).DM_FullPath, DocPath) then
380
+ begin
381
+ { A free document must be saved and closed before a project can own it. }
382
+ CloseDocPath(DocPath, True);
383
+ Free.DM_RemoveSourceDocument(DocPath);
384
+ end;
385
+ Found := False;
386
+ for I := 0 to Prj.DM_LogicalDocumentCount - 1 do
387
+ if SameText(Prj.DM_LogicalDocuments(I).DM_FullPath, DocPath) then Found := True;
388
+ if not Found then Prj.DM_AddSourceDocument(DocPath);
389
+ end;
390
+
391
+ function UnbindElsewhere(DocPath, PrjPath: String): Integer;
392
+ var
393
+ WS : IWorkspace;
394
+ Prj : IProject;
395
+ I, K : Integer;
396
+ FreePath : String;
397
+ begin
398
+ { DM_CreateNewDocument adds the new document to the focused project, which is the project of
399
+ the document in front (another project's library left open by an earlier request), not
400
+ the current one. The new document belongs to PrjPath alone (or to none): remove it from
401
+ every other project before any of them is saved with it. }
402
+ Result := 0;
403
+ WS := GetWorkspace;
404
+ FreePath := '';
405
+ if WS.DM_FreeDocumentsProject <> nil then FreePath := WS.DM_FreeDocumentsProject.DM_ProjectFullPath;
406
+ for K := 0 to WS.DM_ProjectCount - 1 do
407
+ begin
408
+ Prj := WS.DM_Projects(K);
409
+ if not SameText(Prj.DM_ProjectFullPath, FreePath) and not SameText(Prj.DM_ProjectFullPath, PrjPath) then
410
+ for I := Prj.DM_LogicalDocumentCount - 1 downto 0 do
411
+ if SameText(Prj.DM_LogicalDocuments(I).DM_FullPath, DocPath) then
412
+ begin
413
+ Prj.DM_RemoveSourceDocument(DocPath);
414
+ Log('unbound ' + DocPath + ' from ' + Prj.DM_ProjectFullPath);
415
+ Result := Result + 1;
416
+ end;
417
+ end;
418
+ end;
419
+
420
+ procedure DocCreate(Kind, Path, PrjPath: String);
421
+ var
422
+ SD : IServerDocument;
423
+ Prj : IProject;
424
+ Unbound : Integer;
425
+ begin
426
+ if not Ok then Exit;
427
+ if FileExists(Path) then
428
+ begin
429
+ { Existing document: only bind it. Closing an open document can raise a deferred
430
+ "Unsaved Changes" prompt after the script has returned. }
431
+ if PrjPath <> '' then BindToProject(Path, PrjPath);
432
+ Emit(F('path', Path) + FB('created', False));
433
+ Exit;
434
+ end;
435
+ if PrjPath <> '' then
436
+ begin
437
+ Prj := FindProject(PrjPath);
438
+ if Prj = nil then begin Fail('project not open: ' + PrjPath); Exit; end;
439
+ Prj.DM_SetAsCurrentProject;
440
+ end;
441
+ GetWorkSpace.DM_CreateNewDocument(Kind);
442
+ SD := Client.CurrentView.OwnerDocument;
443
+ if SD = nil then begin Fail('Altium created no ' + Kind + ' document'); Exit; end;
444
+ SD.SetFileName(Path);
445
+ SD.DoFileSave('');
446
+ CloseDocPath(Path, True);
447
+ if not FileExists(Path) then begin Fail('new document was not written: ' + Path); Exit; end;
448
+ if PrjPath <> '' then BindToProject(Path, PrjPath);
449
+ if not Ok then Exit;
450
+ Unbound := UnbindElsewhere(Path, PrjPath);
451
+ Emit(F('path', Path) + FB('created', True) + FI('unbound', Unbound));
452
+ end;
453
+
454
+ procedure DocSave(Path: String);
455
+ var
456
+ SD : IServerDocument;
457
+ begin
458
+ if not Ok then Exit;
459
+ SD := Client.GetDocumentByPath(Path);
460
+ if SD = nil then begin Fail('document not open: ' + Path); Exit; end;
461
+ SD.SetModified(True);
462
+ Emit(F('path', Path) + FB('saved', SD.DoFileSave('')));
463
+ end;
464
+
465
+ procedure DocClose(Path: String; Save: Boolean);
466
+ begin
467
+ if not Ok then Exit;
468
+ CloseDocPath(Path, Save);
469
+ Emit(F('path', Path) + FB('closed', Client.GetDocumentByPath(Path) = nil));
470
+ end;
471
+
472
+ procedure DocFocus(Kind, Path: String; ZoomProcess: String);
473
+ var
474
+ SD : IServerDocument;
475
+ begin
476
+ if not Ok then Exit;
477
+ SD := ShowDoc(Kind, Path);
478
+ if SD = nil then begin Fail('cannot open ' + Path); Exit; end;
479
+ { A PCB zooms to the board shape: Action=All would include parts parked off the board. }
480
+ if ZoomProcess = 'PCB:Zoom' then
481
+ Client.SendMessage(ZoomProcess, 'Action=Board', 255, Client.CurrentView)
482
+ else if ZoomProcess <> '' then
483
+ Client.SendMessage(ZoomProcess, 'Action=All', 255, Client.CurrentView);
484
+ Emit(F('path', Path));
485
+ end;
486
+
487
+ procedure EmitViolation(V: IViolation);
488
+ var
489
+ K : Integer;
490
+ O : IDMObject;
491
+ S : String;
492
+ begin
493
+ { The descriptor alone names the kind of message ("Net has no driving source") but not
494
+ where. The related objects carry the net, pin, port or part and the sheet it is on,
495
+ which is what someone fixing the warning needs. At most 8 per violation. }
496
+ S := '';
497
+ for K := 0 to V.DM_RelatedObjectCount - 1 do
498
+ if K < 8 then
499
+ begin
500
+ O := V.DM_RelatedObjects(K);
501
+ if S <> '' then S := S + ',';
502
+ S := S + '{"kind":"' + J(O.DM_ObjectKindString) + '","text":"' + J(O.DM_LongDescriptorString)
503
+ + '","document":"' + J(O.DM_OwnerDocumentName) + '"}';
504
+ end;
505
+ Emit(F('kind', 'violation') + F('text', V.DM_LongDescriptorString) + FI('level', V.DM_ErrorLevel)
506
+ + ',"objects":[' + S + ']');
507
+ end;
508
+
509
+ procedure ProjectNetlist(PrjPath: String; Reopen: Boolean);
510
+ var
511
+ Prj : IProject;
512
+ Flat : IDocument;
513
+ Net : INet;
514
+ Pin : IPin;
515
+ Comp : IComponent;
516
+ I, K : Integer;
517
+ S : String;
518
+ HasSheet : Boolean;
519
+ begin
520
+ if not Ok then Exit;
521
+ Prj := FindProject(PrjPath);
522
+ if Prj = nil then begin Fail('project not open: ' + PrjPath); Exit; end;
523
+ if Reopen then
524
+ begin
525
+ { Scripted sheet edits do not refresh the compiler's model: once a project has compiled
526
+ components, later compiles in the session keep returning that first state (measured:
527
+ sheets 2 and 3 of a project drawn sheet by sheet never reached the netlist). Only
528
+ closing and reopening the project makes Altium compile the saved sheets. The close
529
+ saves every document first; only an approved apply passes Reopen. }
530
+ Log('netlist: closing and reopening the project for a fresh compile');
531
+ ProjectClose(PrjPath, True);
532
+ ProjectOpen(PrjPath);
533
+ if not Ok then Exit;
534
+ Prj := FindProject(PrjPath);
535
+ if Prj = nil then begin Fail('project did not reopen: ' + PrjPath); Exit; end;
536
+ end;
537
+ { Measured: compiling a just-created project while another one held the focus returned no
538
+ nets and no messages. Focus the project being compiled first. }
539
+ Prj.DM_SetAsCurrentProject;
540
+ Prj.DM_Compile;
541
+ Flat := Prj.DM_DocumentFlattened;
542
+ if (Flat = nil) or (Flat.DM_NetCount = 0) then
543
+ begin
544
+ { The first compile after documents were bound to a new project can come back empty. }
545
+ Prj.DM_Compile;
546
+ Flat := Prj.DM_DocumentFlattened;
547
+ end;
548
+ HasSheet := False;
549
+ for I := 0 to Prj.DM_LogicalDocumentCount - 1 do
550
+ if SameText(Prj.DM_LogicalDocuments(I).DM_DocumentKind, 'SCH') then HasSheet := True;
551
+ if HasSheet and ((Flat = nil) or (Flat.DM_ComponentCount = 0)) and not Reopen then
552
+ { A read never reopens: closing saves every document of the project. }
553
+ Emit(F('kind', 'warning') + F('text', 'no components compiled from a project with sheets: empty sheets, '
554
+ + 'or the project is stale in memory; altium_netlist or altium_verify with fresh: true compiles '
555
+ + 'the saved sheets'));
556
+ if Flat = nil then begin Fail('Altium produced no flattened netlist'); Exit; end;
557
+ for I := 0 to Flat.DM_NetCount - 1 do
558
+ begin
559
+ Net := Flat.DM_Nets(I);
560
+ S := '';
561
+ for K := 0 to Net.DM_PinCount - 1 do
562
+ begin
563
+ Pin := Net.DM_Pins(K);
564
+ if S <> '' then S := S + ',';
565
+ S := S + '["' + J(Pin.DM_LogicalPartDesignator) + '","' + J(Pin.DM_PinNumber) + '"]';
566
+ end;
567
+ Emit(F('kind', 'net') + F('net', Net.DM_NetName) + ',"pins":[' + S + ']');
568
+ end;
569
+ for I := 0 to Flat.DM_ComponentCount - 1 do
570
+ begin
571
+ Comp := Flat.DM_Components(I);
572
+ Emit(F('kind', 'component') + F('designator', Comp.DM_LogicalDesignator)
573
+ + F('lib_ref', Comp.DM_LibraryReference) + F('comment', Comp.DM_Comment)
574
+ + F('footprint', Comp.DM_Footprint));
575
+ end;
576
+ for I := 0 to Prj.DM_ViolationCount - 1 do
577
+ if I < 300 then EmitViolation(Prj.DM_Violations(I));
578
+ Emit(F('kind', 'summary') + FI('violations', Prj.DM_ViolationCount) + FI('nets', Flat.DM_NetCount)
579
+ + FI('components', Flat.DM_ComponentCount));
580
+ end;
581
+
582
+ { ------------------------------------------------------------ symbol library --- }
583
+
584
+ procedure SymBegin(Lib, Name, Desc, Prefix, Comment, Footprint: String; PartCount: Integer);
585
+ var
586
+ It : ISch_Iterator;
587
+ C : ISch_Component;
588
+ Impl : ISch_Implementation;
589
+ begin
590
+ if not Ok then Exit;
591
+ CurLibDoc := ShowDoc('SCHLIB', Lib);
592
+ if CurLibDoc = nil then begin Fail('cannot open symbol library ' + Lib); Exit; end;
593
+ CurLib := SchServer.GetSchDocumentByPath(Lib);
594
+ if (CurLib = nil) or (CurLib.ObjectID <> eSchLib) then begin Fail('not a symbol library: ' + Lib); Exit; end;
595
+ It := CurLib.SchLibIterator_Create;
596
+ It.AddFilter_ObjectSet(MkSet(eSchComponent));
597
+ C := It.FirstSchObject;
598
+ while C <> nil do
599
+ begin
600
+ if SameText(C.LibReference, Name) then
601
+ begin
602
+ CurLib.RemoveSchComponent(C);
603
+ Log('replaced existing symbol ' + Name);
604
+ C := nil;
605
+ end
606
+ else C := It.NextSchObject;
607
+ end;
608
+ CurLib.SchIterator_Destroy(It);
609
+ CurComp := SchServer.SchObjectFactory(eSchComponent, eCreate_Default);
610
+ CurComp.CurrentPartID := 1;
611
+ CurComp.DisplayMode := 0;
612
+ CurComp.PartCount := PartCount;
613
+ CurComp.LibReference := Name;
614
+ CurComp.ComponentDescription := Desc;
615
+ CurComp.Designator.Text := Prefix + '?';
616
+ CurComp.Comment.Text := Comment;
617
+ if Footprint <> '' then
618
+ begin
619
+ Impl := CurComp.AddSchImplementation;
620
+ Impl.ModelName := Footprint;
621
+ Impl.ModelType := 'PCBLIB';
622
+ Impl.IsCurrent := True;
623
+ end;
624
+ end;
625
+
626
+ procedure SymPin(Num, Name: String; Elec, X, Y, Orient, Len, Part: Integer; ShowName, Hidden: Boolean);
627
+ var
628
+ P : ISch_Pin;
629
+ begin
630
+ if not Ok then Exit;
631
+ P := SchServer.SchObjectFactory(ePin, eCreate_Default);
632
+ P.Designator := Num;
633
+ P.Name := Name;
634
+ P.Electrical := Elec;
635
+ P.Orientation := Orient;
636
+ P.Location := Point(MilsToCoord(X), MilsToCoord(Y));
637
+ P.PinLength := MilsToCoord(Len);
638
+ P.ShowName := ShowName;
639
+ P.IsHidden := Hidden;
640
+ P.OwnerPartId := Part;
641
+ P.OwnerPartDisplayMode := 0;
642
+ CurComp.AddSchObject(P);
643
+ end;
644
+
645
+ procedure SymRect(X1, Y1, X2, Y2, Part: Integer);
646
+ var
647
+ R : ISch_Rectangle;
648
+ begin
649
+ if not Ok then Exit;
650
+ R := SchServer.SchObjectFactory(eRectangle, eCreate_Default);
651
+ R.Location := Point(MilsToCoord(X1), MilsToCoord(Y1));
652
+ R.Corner := Point(MilsToCoord(X2), MilsToCoord(Y2));
653
+ R.LineWidth := eSmall;
654
+ R.IsSolid := True;
655
+ R.AreaColor := $00B0FFFF;
656
+ R.Color := $00800000;
657
+ R.OwnerPartId := Part;
658
+ R.OwnerPartDisplayMode := 0;
659
+ CurComp.AddSchObject(R);
660
+ end;
661
+
662
+ procedure SymLine(X1, Y1, X2, Y2, Part: Integer);
663
+ var
664
+ L : ISch_Line;
665
+ begin
666
+ if not Ok then Exit;
667
+ L := SchServer.SchObjectFactory(eLine, eCreate_Default);
668
+ L.Location := Point(MilsToCoord(X1), MilsToCoord(Y1));
669
+ L.Corner := Point(MilsToCoord(X2), MilsToCoord(Y2));
670
+ L.LineWidth := eSmall;
671
+ L.Color := $00800000;
672
+ L.OwnerPartId := Part;
673
+ L.OwnerPartDisplayMode := 0;
674
+ CurComp.AddSchObject(L);
675
+ end;
676
+
677
+ procedure SymText(Text: String; X, Y, Part: Integer);
678
+ var
679
+ L : ISch_Label;
680
+ begin
681
+ if not Ok then Exit;
682
+ L := SchServer.SchObjectFactory(eLabel, eCreate_Default);
683
+ L.Location := Point(MilsToCoord(X), MilsToCoord(Y));
684
+ L.Text := Text;
685
+ L.OwnerPartId := Part;
686
+ L.OwnerPartDisplayMode := 0;
687
+ CurComp.AddSchObject(L);
688
+ end;
689
+
690
+ procedure SymParam(Name, Value: String; Visible: Boolean);
691
+ var
692
+ P : ISch_Parameter;
693
+ begin
694
+ if not Ok then Exit;
695
+ P := SchServer.SchObjectFactory(eParameter, eCreate_Default);
696
+ P.Name := Name;
697
+ P.Text := Value;
698
+ P.IsHidden := not Visible;
699
+ CurComp.AddSchObject(P);
700
+ end;
701
+
702
+ procedure SymEnd;
703
+ var
704
+ Pins : Integer;
705
+ It : ISch_Iterator;
706
+ O : ISch_GraphicalObject;
707
+ begin
708
+ if not Ok then Exit;
709
+ CurLib.AddSchComponent(CurComp);
710
+ SchServer.RobotManager.SendMessage(nil, c_BroadCast, SCHM_PrimitiveRegistration, CurComp.I_ObjectAddress);
711
+ CurLib.CurrentSchComponent := CurComp;
712
+ CurLib.GraphicallyInvalidate;
713
+ Pins := 0;
714
+ It := CurComp.SchIterator_Create;
715
+ It.AddFilter_ObjectSet(MkSet(ePin));
716
+ O := It.FirstSchObject;
717
+ while O <> nil do begin Pins := Pins + 1; O := It.NextSchObject; end;
718
+ CurComp.SchIterator_Destroy(It);
719
+ CurLibDoc.SetModified(True);
720
+ CurLibDoc.DoFileSave('');
721
+ Emit(F('symbol', CurComp.LibReference) + FI('pins', Pins));
722
+ end;
723
+
724
+ { --------------------------------------------------------- footprint library --- }
725
+
726
+ procedure FpBegin(Lib, Name, Desc: String; Height: Integer);
727
+ var
728
+ Old : IPCB_LibComponent;
729
+ begin
730
+ if not Ok then Exit;
731
+ CurPcbLibDoc := ShowDoc('PCBLIB', Lib);
732
+ if CurPcbLibDoc = nil then begin Fail('cannot open footprint library ' + Lib); Exit; end;
733
+ CurPcbLib := PCBServer.GetCurrentPCBLibrary;
734
+ if CurPcbLib = nil then begin Fail('not a footprint library: ' + Lib); Exit; end;
735
+ if not SameText(CurPcbLib.Board.FileName, Lib) then begin Fail('Altium focused another library: ' + CurPcbLib.Board.FileName); Exit; end;
736
+ Old := CurPcbLib.GetComponentByName(Name);
737
+ if Old <> nil then
738
+ begin
739
+ CurPcbLib.RemoveComponent(Old);
740
+ Log('replaced existing footprint ' + Name);
741
+ end;
742
+ CurFp := PCBServer.CreatePCBLibComp;
743
+ CurFp.Name := Name;
744
+ CurFp.Description := Desc;
745
+ CurFp.Height := Height;
746
+ CurPcbLib.RegisterComponent(CurFp);
747
+ PCBServer.PreProcess;
748
+ end;
749
+
750
+ procedure FpPad(Name: String; X, Y, Shape, W, H, Rot, Layer, Hole: Integer; Plated: Boolean; CornerPct: Integer;
751
+ MaskManual: Boolean; MaskExp: Integer; Paste: Boolean);
752
+ var
753
+ P : IPCB_Pad;
754
+ C : TPadCache;
755
+ begin
756
+ if not Ok then Exit;
757
+ P := PCBServer.PCBObjectFactory(ePadObject, eNoDimension, eCreate_Default);
758
+ P.Name := Name;
759
+ P.Mode := ePadMode_Simple;
760
+ P.x := X;
761
+ P.y := Y;
762
+ P.Layer := Layer;
763
+ P.HoleSize := Hole;
764
+ P.Plated := Plated;
765
+ P.TopShape := Shape;
766
+ P.TopXSize := W;
767
+ P.TopYSize := H;
768
+ P.MidShape := Shape;
769
+ P.MidXSize := W;
770
+ P.MidYSize := H;
771
+ P.BotShape := Shape;
772
+ P.BotXSize := W;
773
+ P.BotYSize := H;
774
+ if Shape = eRoundedRectangular then P.SetState_StackCRPctOnLayer(eTopLayer, CornerPct);
775
+ P.Rotation := Rot;
776
+ CurFp.AddPCBObject(P);
777
+ PCBServer.SendMessageToRobots(P.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, c_NoEventData);
778
+ { Manual mask and paste expansions go through the pad cache: writing P.SolderMaskExpansion
779
+ directly is an access violation inside Altium's scripting system. No paste is a manual paste
780
+ expansion that shrinks the aperture to nothing. }
781
+ if MaskManual or (not Paste) then
782
+ begin
783
+ PCBServer.SendMessageToRobots(P.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
784
+ C := P.GetState_Cache;
785
+ if MaskManual then
786
+ begin
787
+ C.SolderMaskExpansion := MaskExp;
788
+ C.SolderMaskExpansionValid := eCacheManual;
789
+ end;
790
+ if not Paste then
791
+ begin
792
+ C.PasteMaskExpansion := -(W + H);
793
+ C.PasteMaskExpansionValid := eCacheManual;
794
+ end;
795
+ P.SetState_Cache := C;
796
+ PCBServer.SendMessageToRobots(P.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
797
+ end;
798
+ end;
799
+
800
+ procedure FpTrack(Layer, X1, Y1, X2, Y2, W: Integer);
801
+ var
802
+ T : IPCB_Track;
803
+ begin
804
+ if not Ok then Exit;
805
+ T := PCBServer.PCBObjectFactory(eTrackObject, eNoDimension, eCreate_Default);
806
+ T.x1 := X1; T.y1 := Y1; T.x2 := X2; T.y2 := Y2;
807
+ T.Layer := Layer;
808
+ T.Width := W;
809
+ CurFp.AddPCBObject(T);
810
+ end;
811
+
812
+ procedure FpArc(Layer, XC, YC, R, A1, A2, W: Integer);
813
+ var
814
+ A : IPCB_Arc;
815
+ begin
816
+ if not Ok then Exit;
817
+ A := PCBServer.PCBObjectFactory(eArcObject, eNoDimension, eCreate_Default);
818
+ A.XCenter := XC; A.YCenter := YC; A.Radius := R;
819
+ A.StartAngle := A1; A.EndAngle := A2;
820
+ A.Layer := Layer;
821
+ A.LineWidth := W;
822
+ CurFp.AddPCBObject(A);
823
+ end;
824
+
825
+ procedure FpBody(Csv: String; Standoff, Height, Layer: Integer);
826
+ var
827
+ B : IPCB_ComponentBody;
828
+ C : IPCB_Contour;
829
+ M : IPCB_Model;
830
+ SL : TStringList;
831
+ I : Integer;
832
+ begin
833
+ if not Ok then Exit;
834
+ SL := IntList(Csv);
835
+ B := PCBServer.PCBObjectFactory(eComponentBodyObject, eNoDimension, eCreate_Default);
836
+ C := PCBServer.PCBContourFactory;
837
+ for I := 0 to (SL.Count div 2) - 1 do
838
+ C.AddPoint(StrToInt(SL[2 * I]), StrToInt(SL[2 * I + 1]));
839
+ B.SetOutlineContour(C);
840
+ B.Layer := Layer;
841
+ B.BodyProjection := eBoardSide_Top;
842
+ M := B.ModelFactory_CreateExtruded(Standoff, Height, $00808080);
843
+ B.SetModel(M);
844
+ CurFp.AddPCBObject(B);
845
+ SL.Free;
846
+ end;
847
+
848
+ procedure FpModel(Path: String; RX, RY, RZ, DX, DY, DZ, Layer: Integer);
849
+ var
850
+ B : IPCB_ComponentBody;
851
+ M : IPCB_Model;
852
+ begin
853
+ { A STEP model, embedded so the library carries it. Altium rotates it about its own origin
854
+ (X, Y, then Z), lifts it by DZ, and the body is then moved by DX, DY. }
855
+ if not Ok then Exit;
856
+ if not FileExists(Path) then begin Fail('STEP model not found: ' + Path); Exit; end;
857
+ B := PCBServer.PCBObjectFactory(eComponentBodyObject, eNoDimension, eCreate_Default);
858
+ M := B.ModelFactory_FromFilename(Path, True);
859
+ if M = nil then begin Fail('Altium could not read the STEP model ' + Path); Exit; end;
860
+ M.SetState(RX, RY, RZ, DZ);
861
+ B.SetState_FromModel;
862
+ B.SetModel(M);
863
+ B.Layer := Layer;
864
+ B.BodyProjection := eBoardSide_Top;
865
+ B.MoveByXY(DX, DY);
866
+ CurFp.AddPCBObject(B);
867
+ Emit(F('model', Path) + FI('overall', B.OverallHeight) + FI('standoff', B.StandoffHeight));
868
+ end;
869
+
870
+ procedure FpEnd;
871
+ var
872
+ It : IPCB_GroupIterator;
873
+ O : IPCB_Primitive;
874
+ N : Integer;
875
+ begin
876
+ if not Ok then Exit;
877
+ PCBServer.PostProcess;
878
+ PCBServer.SendMessageToRobots(CurPcbLib.Board.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, CurFp.I_ObjectAddress);
879
+ CurPcbLib.CurrentComponent := CurFp;
880
+ CurPcbLib.Board.ViewManager_FullUpdate;
881
+ N := 0;
882
+ It := CurFp.GroupIterator_Create;
883
+ It.AddFilter_ObjectSet(MkSet(ePadObject));
884
+ O := It.FirstPCBObject;
885
+ while O <> nil do begin N := N + 1; O := It.NextPCBObject; end;
886
+ CurFp.GroupIterator_Destroy(It);
887
+ CurPcbLibDoc.SetModified(True);
888
+ CurPcbLibDoc.DoFileSave('');
889
+ Emit(F('footprint', CurFp.Name) + FI('pads', N));
890
+ end;
891
+
892
+ { ---------------------------------------------------------------- schematic --- }
893
+
894
+ procedure SchOpen(Path: String);
895
+ begin
896
+ if not Ok then Exit;
897
+ CurSchDoc := ShowDoc('SCH', Path);
898
+ if CurSchDoc = nil then begin Fail('cannot open schematic ' + Path); Exit; end;
899
+ CurSch := SchServer.GetSchDocumentByPath(Path);
900
+ if (CurSch = nil) or (CurSch.ObjectID <> eSheet) then begin Fail('not a schematic: ' + Path); Exit; end;
901
+ Emit(F('sheet', Path));
902
+ end;
903
+
904
+ function FindSchComp(Des: String): ISch_Component;
905
+ var
906
+ It : ISch_Iterator;
907
+ C : ISch_Component;
908
+ begin
909
+ Result := nil;
910
+ It := CurSch.SchIterator_Create;
911
+ It.AddFilter_ObjectSet(MkSet(eSchComponent));
912
+ C := It.FirstSchObject;
913
+ while C <> nil do
914
+ begin
915
+ if SameText(C.Designator.Text, Des) then Result := C;
916
+ C := It.NextSchObject;
917
+ end;
918
+ CurSch.SchIterator_Destroy(It);
919
+ end;
920
+
921
+ function SchParts(Des: String; Part: Integer): TInterfaceList;
922
+ var
923
+ It : ISch_Iterator;
924
+ C : ISch_Component;
925
+ begin
926
+ { Every placed part of a component on the current sheet. The parts of a multi-part
927
+ component are separate objects sharing the designator, told apart by CurrentPartID
928
+ (each carries every pin; the part id selects which ones are active). Part 0: all. }
929
+ Result := TInterfaceList.Create;
930
+ It := CurSch.SchIterator_Create;
931
+ It.AddFilter_ObjectSet(MkSet(eSchComponent));
932
+ C := It.FirstSchObject;
933
+ while C <> nil do
934
+ begin
935
+ if SameText(C.Designator.Text, Des) and ((Part = 0) or (C.CurrentPartID = Part)) then Result.Add(C);
936
+ C := It.NextSchObject;
937
+ end;
938
+ CurSch.SchIterator_Destroy(It);
939
+ end;
940
+
941
+ function OnePart(Des: String; Part: Integer): ISch_Component;
942
+ var
943
+ L : TInterfaceList;
944
+ begin
945
+ { The one part an operation acts on: Part 0 is accepted only when the designator has a
946
+ single part on the sheet, so an edit never lands on an arbitrary part. }
947
+ Result := nil;
948
+ L := SchParts(Des, Part);
949
+ if L.Count = 0 then
950
+ begin
951
+ if Part = 0 then Fail('no component ' + Des + ' on the sheet')
952
+ else Fail('no part ' + IntToStr(Part) + ' of ' + Des + ' on the sheet');
953
+ end
954
+ else if L.Count > 1 then
955
+ begin
956
+ if Part = 0 then Fail(Des + ' has ' + IntToStr(L.Count) + ' parts on this sheet: give part')
957
+ else Fail(Des + ' part ' + IntToStr(Part) + ' is placed ' + IntToStr(L.Count) + ' times on this sheet');
958
+ end
959
+ else Result := L.Items[0];
960
+ L.Free;
961
+ end;
962
+
963
+ procedure SchClear;
964
+ var
965
+ It : ISch_Iterator;
966
+ O : ISch_GraphicalObject;
967
+ L : TInterfaceList;
968
+ I : Integer;
969
+ begin
970
+ if not Ok then Exit;
971
+ L := TInterfaceList.Create;
972
+ It := CurSch.SchIterator_Create;
973
+ It.SetState_IterationDepth(eIterateFirstLevel);
974
+ It.AddFilter_ObjectSet(MkSet(eSchComponent, eWire, eNetLabel, ePowerObject, eNoERC, eJunction,
975
+ eLabel, eBus, eBusEntry, ePort, eLine, eRectangle, eNote, eTextFrame, eSheetSymbol));
976
+ O := It.FirstSchObject;
977
+ while O <> nil do begin L.Add(O); O := It.NextSchObject; end;
978
+ CurSch.SchIterator_Destroy(It);
979
+ SchServer.ProcessControl.PreProcess(CurSch, '');
980
+ for I := 0 to L.Count - 1 do CurSch.RemoveSchObject(L.Items[I]);
981
+ SchServer.ProcessControl.PostProcess(CurSch, '');
982
+ CurSch.GraphicallyInvalidate;
983
+ Emit(FI('removed', L.Count));
984
+ end;
985
+
986
+ procedure SchClearWiring;
987
+ var
988
+ It : ISch_Iterator;
989
+ O : ISch_GraphicalObject;
990
+ L : TInterfaceList;
991
+ I : Integer;
992
+ begin
993
+ { Like SchClear, but the components stay: their UniqueIDs, parameters and links to the PCB
994
+ survive, so a sheet can be redrawn without the board seeing new parts. }
995
+ if not Ok then Exit;
996
+ L := TInterfaceList.Create;
997
+ It := CurSch.SchIterator_Create;
998
+ It.SetState_IterationDepth(eIterateFirstLevel);
999
+ It.AddFilter_ObjectSet(MkSet(eWire, eNetLabel, ePowerObject, eNoERC, eJunction, eLabel, eBus, eBusEntry,
1000
+ ePort, eLine));
1001
+ O := It.FirstSchObject;
1002
+ while O <> nil do begin L.Add(O); O := It.NextSchObject; end;
1003
+ CurSch.SchIterator_Destroy(It);
1004
+ SchServer.ProcessControl.PreProcess(CurSch, '');
1005
+ for I := 0 to L.Count - 1 do CurSch.RemoveSchObject(L.Items[I]);
1006
+ SchServer.ProcessControl.PostProcess(CurSch, '');
1007
+ CurSch.GraphicallyInvalidate;
1008
+ Emit(FI('removed', L.Count));
1009
+ end;
1010
+
1011
+ procedure SchMove(Des: String; X, Y, Orient: Integer; Mirror: Boolean; Part: Integer);
1012
+ var
1013
+ C : ISch_Component;
1014
+ begin
1015
+ { Moves and turns a placed component without replacing it, so it keeps its UniqueID and
1016
+ its link to the PCB (SchPlace replaces the part and the board would see a new one). }
1017
+ if not Ok then Exit;
1018
+ C := OnePart(Des, Part);
1019
+ if C = nil then Exit;
1020
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1021
+ C.MoveToXY(MilsToCoord(X), MilsToCoord(Y));
1022
+ C.Orientation := Orient;
1023
+ C.IsMirrored := Mirror;
1024
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1025
+ C.GraphicallyInvalidate;
1026
+ Emit(F('designator', Des) + FI('part', C.CurrentPartID) + FI('x', X) + FI('y', Y) + FB('mirrored', Mirror));
1027
+ end;
1028
+
1029
+ procedure PlaceText(P: ISch_Parameter; X, Y: Integer);
1030
+ begin
1031
+ SchServer.RobotManager.SendMessage(P.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1032
+ P.Orientation := eRotate0;
1033
+ P.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1034
+ SchServer.RobotManager.SendMessage(P.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1035
+ end;
1036
+
1037
+ procedure SchPartTexts(Des: String; DX, DY, X, Y, Step: Integer);
1038
+ var
1039
+ C : ISch_Component;
1040
+ It : ISch_Iterator;
1041
+ P : ISch_Parameter;
1042
+ Row : Integer;
1043
+ begin
1044
+ { Puts a part's visible texts where the drawing has room for them, upright: the designator at
1045
+ DX, DY, then the comment and every other visible parameter one under the other from X, Y
1046
+ down in steps of Step (all in mils, text base line, left aligned). }
1047
+ if not Ok then Exit;
1048
+ C := FindSchComp(Des);
1049
+ if C = nil then begin Fail('no component ' + Des + ' on the sheet'); Exit; end;
1050
+ PlaceText(C.Designator, DX, DY);
1051
+ Row := 0;
1052
+ if not C.Comment.IsHidden then begin PlaceText(C.Comment, X, Y); Row := 1; end;
1053
+ It := C.SchIterator_Create;
1054
+ It.AddFilter_ObjectSet(MkSet(eParameter));
1055
+ P := It.FirstSchObject;
1056
+ while P <> nil do
1057
+ begin
1058
+ if (not P.IsHidden) and (not SameText(P.Name, 'Comment')) then
1059
+ begin
1060
+ PlaceText(P, X, Y - Row * Step);
1061
+ Row := Row + 1;
1062
+ end;
1063
+ P := It.NextSchObject;
1064
+ end;
1065
+ C.SchIterator_Destroy(It);
1066
+ C.GraphicallyInvalidate;
1067
+ Emit(F('designator', Des) + FI('rows', Row));
1068
+ end;
1069
+
1070
+ function StdSheetMargin(Style: Integer): Integer;
1071
+ begin
1072
+ { The border width of each standard sheet in mils (measured on Altium Designer 26.10). Like
1073
+ SheetSizeX, SheetMarginWidth keeps the old style's value for the rest of the script. }
1074
+ case Style of
1075
+ eSheetA4, eSheetA3, eSheetA, eSheetB: Result := 200;
1076
+ eSheetA2, eSheetA1, eSheetC, eSheetD: Result := 300;
1077
+ eSheetA0, eSheetE: Result := 400;
1078
+ else
1079
+ Result := 0;
1080
+ end;
1081
+ end;
1082
+
1083
+ function StdSheetWidth(Style: Integer): Integer;
1084
+ begin
1085
+ { Altium's standard sheet widths in mils (its own sizes, not ISO; measured on 26.10).
1086
+ SheetSizeX keeps the previous style's width for the rest of a script that changes
1087
+ SheetStyle (#38), so the title block is placed from this table instead; 0 = unknown style,
1088
+ use what the sheet reports. }
1089
+ case Style of
1090
+ eSheetA4: Result := 11500;
1091
+ eSheetA3: Result := 15500;
1092
+ eSheetA2: Result := 22300;
1093
+ eSheetA1: Result := 31500;
1094
+ eSheetA0: Result := 44600;
1095
+ eSheetA: Result := 9500;
1096
+ eSheetB: Result := 15000;
1097
+ eSheetC: Result := 20000;
1098
+ eSheetD: Result := 32000;
1099
+ eSheetE: Result := 42000;
1100
+ else
1101
+ Result := 0;
1102
+ end;
1103
+ end;
1104
+
1105
+ procedure TitleField(Text: String; SheetX, Margin, X, Y: Integer);
1106
+ var
1107
+ L : ISch_Label;
1108
+ begin
1109
+ { One special string in Altium's standard title block, 3500 x 800 mil in the bottom-right
1110
+ corner inside the sheet border; SheetX and Margin are the sheet width and border
1111
+ (coordinates), X, Y are mils from the block's lower-left corner. }
1112
+ L := SchServer.SchObjectFactory(eLabel, eCreate_Default);
1113
+ L.Text := Text;
1114
+ L.Location := Point(SheetX - Margin - MilsToCoord(3500 - X), Margin + MilsToCoord(Y));
1115
+ CurSch.RegisterSchObjectInContainer(L);
1116
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, L.I_ObjectAddress);
1117
+ end;
1118
+
1119
+ procedure SheetParam(Name, Value: String);
1120
+ var
1121
+ It : ISch_Iterator;
1122
+ P : ISch_Parameter;
1123
+ Found : Boolean;
1124
+ begin
1125
+ Found := False;
1126
+ It := CurSch.SchIterator_Create;
1127
+ It.SetState_IterationDepth(eIterateFirstLevel);
1128
+ It.AddFilter_ObjectSet(MkSet(eParameter));
1129
+ P := It.FirstSchObject;
1130
+ while P <> nil do
1131
+ begin
1132
+ if SameText(P.Name, Name) then begin P.Text := Value; Found := True; end;
1133
+ P := It.NextSchObject;
1134
+ end;
1135
+ CurSch.SchIterator_Destroy(It);
1136
+ if not Found then
1137
+ begin
1138
+ P := SchServer.SchObjectFactory(eParameter, eCreate_Default);
1139
+ P.Name := Name;
1140
+ P.Text := Value;
1141
+ P.IsHidden := True;
1142
+ CurSch.AddSchObject(P);
1143
+ end;
1144
+ end;
1145
+
1146
+ procedure SchSheet(Style: Integer; Title, Revision, DocNumber, SheetNo, SheetTotal: String);
1147
+ var
1148
+ It : ISch_Iterator;
1149
+ O : ISch_Label;
1150
+ L : TInterfaceList;
1151
+ I : Integer;
1152
+ SheetX, Reported, Margin : Integer;
1153
+ begin
1154
+ if not Ok then Exit;
1155
+ { Without the modify notifications the new size shows on screen but is never saved, and
1156
+ the compiler keeps checking objects against the old sheet boundary. }
1157
+ SchServer.ProcessControl.PreProcess(CurSch, '');
1158
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1159
+ CurSch.SheetStyle := Style;
1160
+ CurSch.UseCustomSheet := False;
1161
+ CurSch.TitleBlockOn := True;
1162
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1163
+ SchServer.ProcessControl.PostProcess(CurSch, '');
1164
+ { A new sheet has none of these parameters, and the standard title block shows only its
1165
+ labels: create the parameters and put their special strings into the block's fields. }
1166
+ SheetParam('Title', Title);
1167
+ SheetParam('Revision', Revision);
1168
+ SheetParam('DocumentNumber', DocNumber);
1169
+ SheetParam('SheetNumber', SheetNo);
1170
+ SheetParam('SheetTotal', SheetTotal);
1171
+ L := TInterfaceList.Create;
1172
+ It := CurSch.SchIterator_Create;
1173
+ It.SetState_IterationDepth(eIterateFirstLevel);
1174
+ It.AddFilter_ObjectSet(MkSet(eLabel));
1175
+ O := It.FirstSchObject;
1176
+ while O <> nil do
1177
+ begin
1178
+ if (O.Text = '=Title') or (O.Text = '=Revision') or (O.Text = '=DocumentNumber')
1179
+ or (O.Text = '=SheetNumber') or (O.Text = '=SheetTotal') then L.Add(O);
1180
+ O := It.NextSchObject;
1181
+ end;
1182
+ CurSch.SchIterator_Destroy(It);
1183
+ for I := 0 to L.Count - 1 do CurSch.RemoveSchObject(L.Items[I]);
1184
+ Reported := CurSch.SheetSizeX;
1185
+ SheetX := MilsToCoord(StdSheetWidth(Style));
1186
+ if SheetX = 0 then SheetX := Reported;
1187
+ Margin := MilsToCoord(StdSheetMargin(Style));
1188
+ if Margin = 0 then Margin := CurSch.SheetMarginWidth;
1189
+ TitleField('=Title', SheetX, Margin, 100, 560);
1190
+ TitleField('=DocumentNumber', SheetX, Margin, 600, 270);
1191
+ TitleField('=Revision', SheetX, Margin, 3000, 270);
1192
+ TitleField('=SheetNumber', SheetX, Margin, 2262, 100);
1193
+ TitleField('=SheetTotal', SheetX, Margin, 2410, 100);
1194
+ CurSch.GraphicallyInvalidate;
1195
+ Emit(F('title', Title) + FI('sheet_x_mils', CoordToMils(SheetX)) + FI('margin_mils', CoordToMils(Margin))
1196
+ + FI('reported_x_mils', CoordToMils(Reported)) + FI('reported_margin_mils', CoordToMils(CurSch.SheetMarginWidth)));
1197
+ end;
1198
+
1199
+ procedure SchAddSheetParam(Name, Value: String);
1200
+ var
1201
+ P : ISch_Parameter;
1202
+ begin
1203
+ if not Ok then Exit;
1204
+ P := SchServer.SchObjectFactory(eParameter, eCreate_Default);
1205
+ P.Name := Name;
1206
+ P.Text := Value;
1207
+ P.IsHidden := True;
1208
+ CurSch.AddSchObject(P);
1209
+ end;
1210
+
1211
+ procedure SchPlace(Lib, LibRef, Des: String; X, Y, Orient: Integer; Comment: String; Part: Integer);
1212
+ var
1213
+ H : Integer;
1214
+ It : ISch_Iterator;
1215
+ C, New : ISch_Component;
1216
+ Old : TInterfaceList;
1217
+ I : Integer;
1218
+ begin
1219
+ if not Ok then Exit;
1220
+ if not FileExists(Lib) then begin Fail('symbol library not found: ' + Lib); Exit; end;
1221
+ { Placing a part replaces that part of the designator only: the other parts of a
1222
+ multi-part component stay where they are. }
1223
+ Old := SchParts(Des, Part);
1224
+ for I := 0 to Old.Count - 1 do
1225
+ begin
1226
+ CurSch.RemoveSchObject(Old.Items[I]);
1227
+ Log('replaced existing ' + Des + ' part ' + IntToStr(Part));
1228
+ end;
1229
+ Old.Free;
1230
+ H := 0;
1231
+ CurSch.PlaceSchComponent(Lib, LibRef, H);
1232
+ New := nil;
1233
+ It := CurSch.SchIterator_Create;
1234
+ It.AddFilter_ObjectSet(MkSet(eSchComponent));
1235
+ C := It.FirstSchObject;
1236
+ while C <> nil do
1237
+ begin
1238
+ if SameText(C.LibReference, LibRef) and (Pos('?', C.Designator.Text) > 0) then New := C;
1239
+ C := It.NextSchObject;
1240
+ end;
1241
+ CurSch.SchIterator_Destroy(It);
1242
+ if New = nil then begin Fail('Altium placed no ' + LibRef + ' from ' + Lib); Exit; end;
1243
+ if Part > New.PartCount then
1244
+ begin
1245
+ CurSch.RemoveSchObject(New);
1246
+ Fail(LibRef + ' has ' + IntToStr(New.PartCount) + ' parts; part ' + IntToStr(Part) + ' does not exist');
1247
+ Exit;
1248
+ end;
1249
+ SchServer.RobotManager.SendMessage(New.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1250
+ New.CurrentPartID := Part;
1251
+ New.Designator.Text := Des;
1252
+ New.MoveToXY(MilsToCoord(X), MilsToCoord(Y));
1253
+ New.Orientation := Orient;
1254
+ if Comment <> '' then New.Comment.Text := Comment;
1255
+ SchServer.RobotManager.SendMessage(New.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1256
+ New.GraphicallyInvalidate;
1257
+ Emit(F('designator', Des) + F('lib_ref', LibRef) + FI('part', Part) + FI('parts', New.PartCount));
1258
+ end;
1259
+
1260
+ procedure CompParam(C: ISch_Component; Name, Value: String; Visible: Boolean);
1261
+ var
1262
+ It : ISch_Iterator;
1263
+ P : ISch_Parameter;
1264
+ Found : Boolean;
1265
+ begin
1266
+ Found := False;
1267
+ It := C.SchIterator_Create;
1268
+ It.AddFilter_ObjectSet(MkSet(eParameter));
1269
+ P := It.FirstSchObject;
1270
+ while P <> nil do
1271
+ begin
1272
+ if SameText(P.Name, Name) then begin P.Text := Value; Found := True; end;
1273
+ P := It.NextSchObject;
1274
+ end;
1275
+ C.SchIterator_Destroy(It);
1276
+ if not Found then
1277
+ begin
1278
+ P := SchServer.SchObjectFactory(eParameter, eCreate_Default);
1279
+ P.Name := Name;
1280
+ P.Text := Value;
1281
+ P.IsHidden := not Visible;
1282
+ P.Location := C.Location;
1283
+ C.AddSchObject(P);
1284
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, P.I_ObjectAddress);
1285
+ end;
1286
+ end;
1287
+
1288
+ procedure SchParam(Des, Name, Value: String; Visible: Boolean);
1289
+ var
1290
+ L : TInterfaceList;
1291
+ C : ISch_Component;
1292
+ I : Integer;
1293
+ begin
1294
+ { Every part of a multi-part component carries its own parameters: set them on all. }
1295
+ if not Ok then Exit;
1296
+ L := SchParts(Des, 0);
1297
+ if L.Count = 0 then begin L.Free; Fail('no component ' + Des + ' on the sheet'); Exit; end;
1298
+ for I := 0 to L.Count - 1 do
1299
+ begin
1300
+ C := L.Items[I];
1301
+ CompParam(C, Name, Value, Visible);
1302
+ end;
1303
+ Emit(F('designator', Des) + F('parameter', Name) + FI('parts', L.Count));
1304
+ L.Free;
1305
+ end;
1306
+
1307
+ procedure SchParamRemove(Des, Name: String);
1308
+ var
1309
+ C : ISch_Component;
1310
+ It : ISch_Iterator;
1311
+ P : ISch_Parameter;
1312
+ L : TInterfaceList;
1313
+ Parts : TInterfaceList;
1314
+ I, K : Integer;
1315
+ N : Integer;
1316
+ begin
1317
+ if not Ok then Exit;
1318
+ Parts := SchParts(Des, 0);
1319
+ if Parts.Count = 0 then begin Parts.Free; Fail('no component ' + Des + ' on the sheet'); Exit; end;
1320
+ N := 0;
1321
+ for K := 0 to Parts.Count - 1 do
1322
+ begin
1323
+ C := Parts.Items[K];
1324
+ L := TInterfaceList.Create;
1325
+ It := C.SchIterator_Create;
1326
+ It.AddFilter_ObjectSet(MkSet(eParameter));
1327
+ P := It.FirstSchObject;
1328
+ while P <> nil do
1329
+ begin
1330
+ if SameText(P.Name, Name) then L.Add(P);
1331
+ P := It.NextSchObject;
1332
+ end;
1333
+ C.SchIterator_Destroy(It);
1334
+ for I := 0 to L.Count - 1 do C.RemoveSchObject(L.Items[I]);
1335
+ N := N + L.Count;
1336
+ L.Free;
1337
+ C.GraphicallyInvalidate;
1338
+ end;
1339
+ Parts.Free;
1340
+ Emit(F('designator', Des) + F('parameter', Name) + FI('removed', N));
1341
+ end;
1342
+
1343
+ procedure SchComponentKind(Des: String; NoBom: Boolean);
1344
+ var
1345
+ C : ISch_Component;
1346
+ L : TInterfaceList;
1347
+ I : Integer;
1348
+ begin
1349
+ { Standard, or Standard (No BOM) for parts that are PCB features (fiducials): they stay on
1350
+ the board and in pick-and-place, but leave the BOM and ActiveBOM. Every part alike. }
1351
+ if not Ok then Exit;
1352
+ L := SchParts(Des, 0);
1353
+ if L.Count = 0 then begin L.Free; Fail('no component ' + Des + ' on the sheet'); Exit; end;
1354
+ for I := 0 to L.Count - 1 do
1355
+ begin
1356
+ C := L.Items[I];
1357
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1358
+ if NoBom then C.ComponentKind := eComponentKind_Standard_NoBOM
1359
+ else C.ComponentKind := eComponentKind_Standard;
1360
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1361
+ end;
1362
+ L.Free;
1363
+ Emit(F('designator', Des) + FB('no_bom', NoBom));
1364
+ end;
1365
+
1366
+ procedure SchDelete(Des: String; Part: Integer);
1367
+ var
1368
+ L : TInterfaceList;
1369
+ I : Integer;
1370
+ begin
1371
+ { Removes a component from the current sheet (it moves to another sheet with its UniqueID):
1372
+ every part of it with Part 0, else that part only. }
1373
+ if not Ok then Exit;
1374
+ L := SchParts(Des, Part);
1375
+ if L.Count = 0 then
1376
+ begin
1377
+ L.Free;
1378
+ if Part = 0 then Fail('no component ' + Des + ' on the sheet')
1379
+ else Fail('no part ' + IntToStr(Part) + ' of ' + Des + ' on the sheet');
1380
+ Exit;
1381
+ end;
1382
+ for I := 0 to L.Count - 1 do CurSch.RemoveSchObject(L.Items[I]);
1383
+ CurSch.GraphicallyInvalidate;
1384
+ Emit(F('designator', Des) + FI('removed', L.Count));
1385
+ L.Free;
1386
+ end;
1387
+
1388
+ procedure SchUniqueId(Des, Uid: String);
1389
+ var
1390
+ C : ISch_Component;
1391
+ begin
1392
+ { A part placed anew on another sheet takes the UniqueID it had, so the board keeps its link:
1393
+ in a flat project the link is the UniqueID alone. }
1394
+ if not Ok then Exit;
1395
+ C := FindSchComp(Des);
1396
+ if C = nil then begin Fail('no component ' + Des + ' on the sheet'); Exit; end;
1397
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_BeginModify, c_NoEventData);
1398
+ C.UniqueId := Uid;
1399
+ SchServer.RobotManager.SendMessage(C.I_ObjectAddress, c_BroadCast, SCHM_EndModify, c_NoEventData);
1400
+ Emit(F('designator', Des) + F('unique_id', C.UniqueId));
1401
+ end;
1402
+
1403
+ procedure RegObj(O: ISch_GraphicalObject);
1404
+ begin
1405
+ CurSch.RegisterSchObjectInContainer(O);
1406
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, O.I_ObjectAddress);
1407
+ end;
1408
+
1409
+ procedure SchWire(Csv: String);
1410
+ var
1411
+ W : ISch_Wire;
1412
+ SL : TStringList;
1413
+ I, N : Integer;
1414
+ begin
1415
+ if not Ok then Exit;
1416
+ SL := IntList(Csv);
1417
+ N := SL.Count div 2;
1418
+ if N < 2 then begin Fail('a wire needs two points'); Exit; end;
1419
+ W := SchServer.SchObjectFactory(eWire, eCreate_Default);
1420
+ W.Location := Point(MilsToCoord(StrToInt(SL[0])), MilsToCoord(StrToInt(SL[1])));
1421
+ for I := 1 to N do
1422
+ begin
1423
+ W.InsertVertex := I;
1424
+ W.SetState_Vertex(I, Point(MilsToCoord(StrToInt(SL[2 * I - 2])), MilsToCoord(StrToInt(SL[2 * I - 1]))));
1425
+ end;
1426
+ RegObj(W);
1427
+ SL.Free;
1428
+ end;
1429
+
1430
+ procedure SchLabel(Text: String; X, Y, Orient: Integer);
1431
+ var
1432
+ N : ISch_NetLabel;
1433
+ begin
1434
+ if not Ok then Exit;
1435
+ N := SchServer.SchObjectFactory(eNetLabel, eCreate_Default);
1436
+ N.Text := Text;
1437
+ N.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1438
+ N.Orientation := Orient;
1439
+ RegObj(N);
1440
+ end;
1441
+
1442
+ procedure SchPower(Text: String; X, Y, Style, Orient: Integer);
1443
+ var
1444
+ P : ISch_PowerObject;
1445
+ begin
1446
+ if not Ok then Exit;
1447
+ P := SchServer.SchObjectFactory(ePowerObject, eCreate_Default);
1448
+ P.Text := Text;
1449
+ P.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1450
+ P.Style := Style;
1451
+ P.Orientation := Orient;
1452
+ P.ShowNetName := True;
1453
+ RegObj(P);
1454
+ end;
1455
+
1456
+ procedure SchNoErc(X, Y: Integer);
1457
+ var
1458
+ N : ISch_NoERC;
1459
+ begin
1460
+ if not Ok then Exit;
1461
+ N := SchServer.SchObjectFactory(eNoERC, eCreate_Default);
1462
+ N.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1463
+ RegObj(N);
1464
+ end;
1465
+
1466
+ procedure SchJunction(X, Y: Integer);
1467
+ var
1468
+ N : ISch_Junction;
1469
+ begin
1470
+ if not Ok then Exit;
1471
+ N := SchServer.SchObjectFactory(eJunction, eCreate_Default);
1472
+ N.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1473
+ RegObj(N);
1474
+ end;
1475
+
1476
+ procedure SchText(Text: String; X, Y, Orient: Integer);
1477
+ var
1478
+ L : ISch_Label;
1479
+ begin
1480
+ if not Ok then Exit;
1481
+ L := SchServer.SchObjectFactory(eLabel, eCreate_Default);
1482
+ L.Text := Text;
1483
+ L.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1484
+ L.Orientation := Orient;
1485
+ RegObj(L);
1486
+ end;
1487
+
1488
+ procedure SchSheetSymBegin(Name, FileName: String; X, Y, W, H: Integer);
1489
+ var
1490
+ It : ISch_Iterator;
1491
+ O : ISch_SheetSymbol;
1492
+ L : TInterfaceList;
1493
+ I : Integer;
1494
+ begin
1495
+ { A sheet symbol: X, Y is its top-left corner, W x H its size. FileName names the child
1496
+ sheet as the project lists it (relative to the project folder). A sheet symbol of the
1497
+ same name on this sheet is replaced. }
1498
+ if not Ok then Exit;
1499
+ CurSheetSym := nil;
1500
+ L := TInterfaceList.Create;
1501
+ It := CurSch.SchIterator_Create;
1502
+ It.SetState_IterationDepth(eIterateFirstLevel);
1503
+ It.AddFilter_ObjectSet(MkSet(eSheetSymbol));
1504
+ O := It.FirstSchObject;
1505
+ while O <> nil do
1506
+ begin
1507
+ if SameText(O.SheetName.Text, Name) then L.Add(O);
1508
+ O := It.NextSchObject;
1509
+ end;
1510
+ CurSch.SchIterator_Destroy(It);
1511
+ for I := 0 to L.Count - 1 do CurSch.RemoveSchObject(L.Items[I]);
1512
+ if L.Count > 0 then Log('replaced sheet symbol ' + Name);
1513
+ L.Free;
1514
+ CurSheetSym := SchServer.SchObjectFactory(eSheetSymbol, eCreate_Default);
1515
+ if CurSheetSym = nil then begin Fail('Altium created no sheet symbol'); Exit; end;
1516
+ CurSheetSym.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1517
+ CurSheetSym.XSize := MilsToCoord(W);
1518
+ CurSheetSym.YSize := MilsToCoord(H);
1519
+ CurSheetSym.SheetName.Text := Name;
1520
+ CurSheetSym.SheetFileName.Text := FileName;
1521
+ { Left at the factory defaults, neither text showed on the sheet. Place them as Altium's
1522
+ own placement does: the name just above the top-left corner, the file name below the
1523
+ bottom-left corner, and show both. }
1524
+ CurSheetSym.SheetName.IsHidden := False;
1525
+ CurSheetSym.SheetFileName.IsHidden := False;
1526
+ CurSheetSym.SheetName.Location := Point(MilsToCoord(X), MilsToCoord(Y + 20));
1527
+ CurSheetSym.SheetFileName.Location := Point(MilsToCoord(X), MilsToCoord(Y - H - 100));
1528
+ CurSch.AddSchObject(CurSheetSym);
1529
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration,
1530
+ CurSheetSym.I_ObjectAddress);
1531
+ Emit(F('kind', 'sheet_symbol') + F('designator', Name) + F('file', FileName) + FI('x', X) + FI('y', Y)
1532
+ + FI('width', W) + FI('height', H)
1533
+ + FB('name_shown', not CurSheetSym.SheetName.IsHidden)
1534
+ + FB('file_shown', not CurSheetSym.SheetFileName.IsHidden)
1535
+ + FI('name_y', CoordToMils(CurSheetSym.SheetName.Location.Y))
1536
+ + FI('file_y', CoordToMils(CurSheetSym.SheetFileName.Location.Y)));
1537
+ end;
1538
+
1539
+ procedure SchSheetEntry(Name: String; Right: Boolean; Offset, IO: Integer);
1540
+ var
1541
+ E : ISch_SheetEntry;
1542
+ X, Y : Integer;
1543
+ begin
1544
+ { An entry on the left or right edge, Offset mils below the symbol's top. Its electrical
1545
+ point is on that edge; a child sheet port of the same name connects to it. }
1546
+ if not Ok then Exit;
1547
+ if CurSheetSym = nil then begin Fail('sheet entry outside a sheet symbol'); Exit; end;
1548
+ E := SchServer.SchObjectFactory(eSheetEntry, eCreate_Default);
1549
+ if Right then E.Side := eRightSide else E.Side := eLeftSide;
1550
+ E.DistanceFromTop := MilsToCoord(Offset);
1551
+ E.Name := Name;
1552
+ E.IOType := IO;
1553
+ CurSheetSym.AddSchObject(E);
1554
+ SchServer.RobotManager.SendMessage(CurSheetSym.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration,
1555
+ E.I_ObjectAddress);
1556
+ X := CoordToMils(CurSheetSym.Location.X);
1557
+ if Right then X := X + CoordToMils(CurSheetSym.XSize);
1558
+ Y := CoordToMils(CurSheetSym.Location.Y) - Offset;
1559
+ Emit(F('kind', 'sheet_entry') + F('entry', Name) + FI('x', X) + FI('y', Y));
1560
+ end;
1561
+
1562
+ procedure SchSheetSymEnd;
1563
+ begin
1564
+ if not Ok then Exit;
1565
+ CurSheetSym.GraphicallyInvalidate;
1566
+ CurSheetSym := nil;
1567
+ end;
1568
+
1569
+ procedure SchPort(Name: String; X, Y, W, Style, IO: Integer);
1570
+ var
1571
+ P : ISch_Port;
1572
+ begin
1573
+ { A horizontal port from X, Y to X + W, Y; both ends are electrical. A port connects to the
1574
+ sheet entry of the same name on the parent sheet's symbol for this sheet. }
1575
+ if not Ok then Exit;
1576
+ P := SchServer.SchObjectFactory(ePort, eCreate_Default);
1577
+ if P = nil then begin Fail('Altium created no port'); Exit; end;
1578
+ P.Location := Point(MilsToCoord(X), MilsToCoord(Y));
1579
+ P.Width := MilsToCoord(W);
1580
+ P.Name := Name;
1581
+ P.Style := Style;
1582
+ P.IOType := IO;
1583
+ CurSch.AddSchObject(P);
1584
+ SchServer.RobotManager.SendMessage(CurSch.I_ObjectAddress, c_BroadCast, SCHM_PrimitiveRegistration, P.I_ObjectAddress);
1585
+ Emit(F('port', Name) + FI('x', X) + FI('y', Y) + FI('x2', X + W) + FI('y2', Y));
1586
+ end;
1587
+
1588
+ procedure SchSave;
1589
+ begin
1590
+ if not Ok then Exit;
1591
+ CurSch.GraphicallyInvalidate;
1592
+ CurSchDoc.SetModified(True);
1593
+ Emit(FB('saved', CurSchDoc.DoFileSave('')));
1594
+ end;
1595
+
1596
+ procedure PartPins(C: ISch_Component; Des: String);
1597
+ var
1598
+ It : ISch_Iterator;
1599
+ P : ISch_Pin;
1600
+ X, Y, L : Integer;
1601
+ begin
1602
+ It := C.SchIterator_Create;
1603
+ It.AddFilter_ObjectSet(MkSet(ePin));
1604
+ P := It.FirstSchObject;
1605
+ while P <> nil do
1606
+ begin
1607
+ if (P.OwnerPartId = C.CurrentPartID) or (P.OwnerPartId = 0) then
1608
+ begin
1609
+ X := CoordToMils(P.Location.X);
1610
+ Y := CoordToMils(P.Location.Y);
1611
+ L := CoordToMils(P.PinLength);
1612
+ if P.Orientation = eRotate0 then X := X + L
1613
+ else if P.Orientation = eRotate90 then Y := Y + L
1614
+ else if P.Orientation = eRotate180 then X := X - L
1615
+ else Y := Y - L;
1616
+ Emit(F('designator', Des) + FI('part', C.CurrentPartID) + F('pin', P.Designator) + F('pin_name', P.Name)
1617
+ + FI('x', X) + FI('y', Y) + FI('orientation', P.Orientation));
1618
+ end;
1619
+ P := It.NextSchObject;
1620
+ end;
1621
+ C.SchIterator_Destroy(It);
1622
+ end;
1623
+
1624
+ procedure SchPins(Des: String; Part: Integer);
1625
+ var
1626
+ L : TInterfaceList;
1627
+ C : ISch_Component;
1628
+ I : Integer;
1629
+ begin
1630
+ { The active pins of each placed part (Part 0: every part on the sheet). }
1631
+ if not Ok then Exit;
1632
+ L := SchParts(Des, Part);
1633
+ if L.Count = 0 then
1634
+ begin
1635
+ L.Free;
1636
+ if Part = 0 then Fail('no component ' + Des + ' on the sheet')
1637
+ else Fail('no part ' + IntToStr(Part) + ' of ' + Des + ' on the sheet');
1638
+ Exit;
1639
+ end;
1640
+ for I := 0 to L.Count - 1 do
1641
+ begin
1642
+ C := L.Items[I];
1643
+ PartPins(C, Des);
1644
+ end;
1645
+ L.Free;
1646
+ end;
1647
+
1648
+ procedure SchComponents;
1649
+ var
1650
+ It : ISch_Iterator;
1651
+ C : ISch_Component;
1652
+ begin
1653
+ if not Ok then Exit;
1654
+ It := CurSch.SchIterator_Create;
1655
+ It.AddFilter_ObjectSet(MkSet(eSchComponent));
1656
+ C := It.FirstSchObject;
1657
+ while C <> nil do
1658
+ begin
1659
+ Emit(F('designator', C.Designator.Text) + F('lib_ref', C.LibReference) + F('comment', C.Comment.Text)
1660
+ + FI('x', CoordToMils(C.Location.X)) + FI('y', CoordToMils(C.Location.Y)) + FI('orientation', C.Orientation)
1661
+ + F('unique_id', C.UniqueId) + FI('part', C.CurrentPartID) + FI('parts', C.PartCount));
1662
+ C := It.NextSchObject;
1663
+ end;
1664
+ CurSch.SchIterator_Destroy(It);
1665
+ end;
1666
+
1667
+ { ---------------------------------------------------------------------- PCB --- }
1668
+
1669
+ procedure PcbOpen(Path: String);
1670
+ begin
1671
+ if not Ok then Exit;
1672
+ CurBoardDoc := ShowDoc('PCB', Path);
1673
+ if CurBoardDoc = nil then begin Fail('cannot open board ' + Path); Exit; end;
1674
+ CurBoard := PCBServer.GetPCBBoardByPath(Path);
1675
+ if CurBoard = nil then begin Fail('not a board: ' + Path); Exit; end;
1676
+ if not SameText(CurBoard.FileName, Path) then begin Fail('Altium returned another board: ' + CurBoard.FileName); Exit; end;
1677
+ Emit(F('board', Path) + FI('x_origin', CurBoard.XOrigin) + FI('y_origin', CurBoard.YOrigin));
1678
+ end;
1679
+
1680
+ procedure PcbOrigin(X, Y: Integer);
1681
+ begin
1682
+ { X, Y are absolute: every other PCB coordinate is relative to this origin, and Altium's
1683
+ workspace has no negative absolute coordinates. }
1684
+ if not Ok then Exit;
1685
+ if (X < 0) or (Y < 0) then begin Fail('the origin must have non-negative absolute coordinates'); Exit; end;
1686
+ CurBoard.XOrigin := X;
1687
+ CurBoard.YOrigin := Y;
1688
+ CurBoard.ViewManager_FullUpdate;
1689
+ Emit(FI('x_origin', CurBoard.XOrigin) + FI('y_origin', CurBoard.YOrigin));
1690
+ end;
1691
+
1692
+ procedure SetBoardRegion(O: IPCB_BoardOutline);
1693
+ var
1694
+ It : IPCB_BoardIterator;
1695
+ R : IPCB_Region;
1696
+ L : TInterfaceList;
1697
+ GP : IPCB_GeometricPolygon;
1698
+ C : IPCB_Contour;
1699
+ Seg : TPolySegment;
1700
+ I : Integer;
1701
+ begin
1702
+ { Altium keeps the layer stack in a board region (kind 3) that is drawn separately from the
1703
+ outline; a new board's region is the default 6 x 4 inch board. Left alone, the part of the
1704
+ new outline outside it has no stack: 3D shows it without mask, and ODB++ without layers.
1705
+ A board with exactly one region gets the outline as its region. }
1706
+ L := TInterfaceList.Create;
1707
+ It := CurBoard.BoardIterator_Create;
1708
+ It.AddFilter_ObjectSet(MkSet(eRegionObject));
1709
+ It.AddFilter_LayerSet(AllLayers);
1710
+ It.AddFilter_Method(eProcessAll);
1711
+ R := It.FirstPCBObject;
1712
+ while R <> nil do
1713
+ begin
1714
+ if (R.Kind = 3) and not R.InComponent then L.Add(R);
1715
+ R := It.NextPCBObject;
1716
+ end;
1717
+ CurBoard.BoardIterator_Destroy(It);
1718
+ if L.Count = 1 then
1719
+ begin
1720
+ R := L.Items[0];
1721
+ GP := PCBServer.PCBGeometricPolygonFactory;
1722
+ C := GP.AddEmptyContour;
1723
+ for I := 0 to O.PointCount - 1 do
1724
+ begin
1725
+ Seg := O.Segments[I]; { a record: read it into a variable, never field by index }
1726
+ C.AddPoint(Seg.vx, Seg.vy);
1727
+ end;
1728
+ PCBServer.SendMessageToRobots(R.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
1729
+ R.SetOutlineContour(C);
1730
+ PCBServer.SendMessageToRobots(R.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
1731
+ end;
1732
+ Emit(FI('board_regions', L.Count) + FB('region_set', L.Count = 1));
1733
+ L.Free;
1734
+ end;
1735
+
1736
+ procedure PcbOutline(Csv: String);
1737
+ var
1738
+ O : IPCB_BoardOutline;
1739
+ Seg : TPolySegment;
1740
+ SL : TStringList;
1741
+ I, N : Integer;
1742
+ begin
1743
+ if not Ok then Exit;
1744
+ SL := IntList(Csv);
1745
+ N := SL.Count div 2;
1746
+ if N < 3 then begin Fail('an outline needs three points'); Exit; end;
1747
+ O := CurBoard.BoardOutline;
1748
+ PCBServer.PreProcess;
1749
+ O.BeginModify;
1750
+ O.PointCount := N;
1751
+ for I := 0 to N - 1 do
1752
+ begin
1753
+ Seg := TPolySegment;
1754
+ Seg.Kind := ePolySegmentLine;
1755
+ Seg.vx := CurBoard.XOrigin + StrToInt(SL[2 * I]);
1756
+ Seg.vy := CurBoard.YOrigin + StrToInt(SL[2 * I + 1]);
1757
+ O.Segments[I] := Seg;
1758
+ end;
1759
+ O.EndModify;
1760
+ O.Invalidate;
1761
+ O.Rebuild;
1762
+ O.Validate;
1763
+ SetBoardRegion(O);
1764
+ PCBServer.PostProcess;
1765
+ CurBoard.ViewManager_FullUpdate;
1766
+ Emit(FI('points', O.PointCount)
1767
+ + FI('width', O.BoundingRectangle.Right - O.BoundingRectangle.Left)
1768
+ + FI('height', O.BoundingRectangle.Top - O.BoundingRectangle.Bottom));
1769
+ SL.Free;
1770
+ end;
1771
+
1772
+ function FindPcbComp(Des: String): IPCB_Component;
1773
+ var
1774
+ It : IPCB_BoardIterator;
1775
+ C : IPCB_Component;
1776
+ begin
1777
+ Result := nil;
1778
+ It := CurBoard.BoardIterator_Create;
1779
+ It.AddFilter_ObjectSet(MkSet(eComponentObject));
1780
+ It.AddFilter_LayerSet(AllLayers);
1781
+ It.AddFilter_Method(eProcessAll);
1782
+ C := It.FirstPCBObject;
1783
+ while C <> nil do
1784
+ begin
1785
+ if SameText(C.Name.Text, Des) then Result := C;
1786
+ C := It.NextPCBObject;
1787
+ end;
1788
+ CurBoard.BoardIterator_Destroy(It);
1789
+ end;
1790
+
1791
+ function FindNet(Name: String): IPCB_Net;
1792
+ var
1793
+ It : IPCB_BoardIterator;
1794
+ N : IPCB_Net;
1795
+ begin
1796
+ Result := nil;
1797
+ if Name = '' then Exit;
1798
+ It := CurBoard.BoardIterator_Create;
1799
+ It.AddFilter_ObjectSet(MkSet(eNetObject));
1800
+ It.AddFilter_LayerSet(AllLayers);
1801
+ It.AddFilter_Method(eProcessAll);
1802
+ N := It.FirstPCBObject;
1803
+ while N <> nil do
1804
+ begin
1805
+ if SameText(N.Name, Name) then Result := N;
1806
+ N := It.NextPCBObject;
1807
+ end;
1808
+ CurBoard.BoardIterator_Destroy(It);
1809
+ end;
1810
+
1811
+ procedure PcbPlace(Des: String; X, Y, Rot: Integer; Bottom: Boolean);
1812
+ var
1813
+ C : IPCB_Component;
1814
+ begin
1815
+ if not Ok then Exit;
1816
+ C := FindPcbComp(Des);
1817
+ if C = nil then begin Fail('no component ' + Des + ' on the board (run Update PCB first)'); Exit; end;
1818
+ PCBServer.PreProcess;
1819
+ PCBServer.SendMessageToRobots(C.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
1820
+ if Bottom and (C.Layer <> eBottomLayer) then C.Layer := eBottomLayer;
1821
+ if (not Bottom) and (C.Layer <> eTopLayer) then C.Layer := eTopLayer;
1822
+ C.Rotation := Rot;
1823
+ C.MoveToXY(CurBoard.XOrigin + X, CurBoard.YOrigin + Y);
1824
+ PCBServer.SendMessageToRobots(C.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
1825
+ PCBServer.PostProcess;
1826
+ Emit(F('designator', Des) + FI('x', C.X - CurBoard.XOrigin) + FI('y', C.Y - CurBoard.YOrigin)
1827
+ + FB('bottom', C.Layer = eBottomLayer));
1828
+ end;
1829
+
1830
+ procedure PcbTrack(Net: String; Layer, X1, Y1, X2, Y2, W: Integer; Locked: Boolean);
1831
+ var
1832
+ T : IPCB_Track;
1833
+ N : IPCB_Net;
1834
+ begin
1835
+ if not Ok then Exit;
1836
+ N := FindNet(Net);
1837
+ if (Net <> '') and (N = nil) then begin Fail('no net ' + Net + ' on the board'); Exit; end;
1838
+ PCBServer.PreProcess;
1839
+ T := PCBServer.PCBObjectFactory(eTrackObject, eNoDimension, eCreate_Default);
1840
+ T.x1 := CurBoard.XOrigin + X1; T.y1 := CurBoard.YOrigin + Y1;
1841
+ T.x2 := CurBoard.XOrigin + X2; T.y2 := CurBoard.YOrigin + Y2;
1842
+ T.Layer := Layer;
1843
+ T.Width := W;
1844
+ if N <> nil then T.Net := N;
1845
+ { Locked primitives are left alone by the autorouter and by interactive rip-up. }
1846
+ T.Moveable := not Locked;
1847
+ CurBoard.AddPCBObject(T);
1848
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, T.I_ObjectAddress);
1849
+ PCBServer.PostProcess;
1850
+ end;
1851
+
1852
+ procedure PcbArc(Net: String; Layer, XC, YC, R, A1, A2, W: Integer);
1853
+ var
1854
+ A : IPCB_Arc;
1855
+ N : IPCB_Net;
1856
+ begin
1857
+ if not Ok then Exit;
1858
+ N := FindNet(Net);
1859
+ if (Net <> '') and (N = nil) then begin Fail('no net ' + Net + ' on the board'); Exit; end;
1860
+ PCBServer.PreProcess;
1861
+ A := PCBServer.PCBObjectFactory(eArcObject, eNoDimension, eCreate_Default);
1862
+ A.XCenter := CurBoard.XOrigin + XC; A.YCenter := CurBoard.YOrigin + YC; A.Radius := R;
1863
+ A.StartAngle := A1; A.EndAngle := A2;
1864
+ A.Layer := Layer;
1865
+ A.LineWidth := W;
1866
+ if N <> nil then A.Net := N;
1867
+ CurBoard.AddPCBObject(A);
1868
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, A.I_ObjectAddress);
1869
+ PCBServer.PostProcess;
1870
+ end;
1871
+
1872
+ procedure PcbVia(Net: String; X, Y, Hole, Size: Integer; Locked: Boolean);
1873
+ var
1874
+ V : IPCB_Via;
1875
+ N : IPCB_Net;
1876
+ begin
1877
+ if not Ok then Exit;
1878
+ N := FindNet(Net);
1879
+ if (Net <> '') and (N = nil) then begin Fail('no net ' + Net + ' on the board'); Exit; end;
1880
+ PCBServer.PreProcess;
1881
+ V := PCBServer.PCBObjectFactory(eViaObject, eNoDimension, eCreate_Default);
1882
+ V.x := CurBoard.XOrigin + X;
1883
+ V.y := CurBoard.YOrigin + Y;
1884
+ V.HoleSize := Hole;
1885
+ V.Size := Size;
1886
+ V.LowLayer := eTopLayer;
1887
+ V.HighLayer := eBottomLayer;
1888
+ if N <> nil then V.Net := N;
1889
+ V.Moveable := not Locked;
1890
+ CurBoard.AddPCBObject(V);
1891
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, V.I_ObjectAddress);
1892
+ PCBServer.PostProcess;
1893
+ end;
1894
+
1895
+ function DropPolygon(Name: String): Integer;
1896
+ var
1897
+ It : IPCB_BoardIterator;
1898
+ P : IPCB_Polygon;
1899
+ L : TInterfaceList;
1900
+ I : Integer;
1901
+ begin
1902
+ { A named polygon is replaced, never duplicated, so a layout script can be re-run. }
1903
+ L := TInterfaceList.Create;
1904
+ It := CurBoard.BoardIterator_Create;
1905
+ It.AddFilter_ObjectSet(MkSet(ePolyObject));
1906
+ It.AddFilter_LayerSet(AllLayers);
1907
+ It.AddFilter_Method(eProcessAll);
1908
+ P := It.FirstPCBObject;
1909
+ while P <> nil do
1910
+ begin
1911
+ if SameText(P.Name, Name) then L.Add(P);
1912
+ P := It.NextPCBObject;
1913
+ end;
1914
+ CurBoard.BoardIterator_Destroy(It);
1915
+ PCBServer.PreProcess;
1916
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
1917
+ PCBServer.PostProcess;
1918
+ Result := L.Count;
1919
+ L.Free;
1920
+ end;
1921
+
1922
+ procedure PcbDeletePolygon(Name: String);
1923
+ begin
1924
+ { Remove a named polygon, e.g. an inner ground area the autorouter should not repour around
1925
+ on every move; pcb_polygon puts it back afterwards. }
1926
+ if not Ok then Exit;
1927
+ Emit(F('polygon', Name) + FI('removed', DropPolygon(Name)));
1928
+ end;
1929
+
1930
+ procedure PcbPolygon(Net: String; Layer: Integer; Csv, Name: String; Clearance: Integer);
1931
+ var
1932
+ P : IPCB_Polygon;
1933
+ N : IPCB_Net;
1934
+ Seg : TPolySegment;
1935
+ SL : TStringList;
1936
+ I, K : Integer;
1937
+ begin
1938
+ if not Ok then Exit;
1939
+ N := FindNet(Net);
1940
+ if (Net <> '') and (N = nil) then begin Fail('no net ' + Net + ' on the board'); Exit; end;
1941
+ if Name <> '' then DropPolygon(Name);
1942
+ SL := IntList(Csv);
1943
+ K := SL.Count div 2;
1944
+ PCBServer.PreProcess;
1945
+ P := PCBServer.PCBObjectFactory(ePolyObject, eNoDimension, eCreate_Default);
1946
+ P.Layer := Layer;
1947
+ P.PolyHatchStyle := ePolySolid;
1948
+ P.RemoveDead := True;
1949
+ P.PourOver := ePolygonPourOver_SameNet;
1950
+ P.Name := Name;
1951
+ if N <> nil then P.Net := N;
1952
+ P.PointCount := K;
1953
+ for I := 0 to K - 1 do
1954
+ begin
1955
+ Seg := TPolySegment;
1956
+ Seg.Kind := ePolySegmentLine;
1957
+ Seg.vx := CurBoard.XOrigin + StrToInt(SL[2 * I]);
1958
+ Seg.vy := CurBoard.YOrigin + StrToInt(SL[2 * I + 1]);
1959
+ P.Segments[I] := Seg;
1960
+ end;
1961
+ CurBoard.AddPCBObject(P);
1962
+ P.Rebuild;
1963
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, P.I_ObjectAddress);
1964
+ PCBServer.PostProcess;
1965
+ SL.Free;
1966
+ end;
1967
+
1968
+ procedure PcbRegion(Net: String; Layer: Integer; Csv: String);
1969
+ var
1970
+ R : IPCB_Region;
1971
+ N : IPCB_Net;
1972
+ GP : IPCB_GeometricPolygon;
1973
+ C : IPCB_Contour;
1974
+ SL : TStringList;
1975
+ I, K : Integer;
1976
+ begin
1977
+ { A filled region with a straight-edged outline: silkscreen artwork (a logo) on an overlay
1978
+ layer, or a copper area on a signal layer (with a net). }
1979
+ if not Ok then Exit;
1980
+ N := nil;
1981
+ if Net <> '' then
1982
+ begin
1983
+ N := FindNet(Net);
1984
+ if N = nil then begin Fail('no net ' + Net + ' on the board'); Exit; end;
1985
+ end;
1986
+ SL := IntList(Csv);
1987
+ K := SL.Count div 2;
1988
+ GP := PCBServer.PCBGeometricPolygonFactory;
1989
+ C := GP.AddEmptyContour;
1990
+ for I := 0 to K - 1 do
1991
+ C.AddPoint(CurBoard.XOrigin + StrToInt(SL[2 * I]), CurBoard.YOrigin + StrToInt(SL[2 * I + 1]));
1992
+ PCBServer.PreProcess;
1993
+ R := PCBServer.PCBObjectFactory(eRegionObject, eNoDimension, eCreate_Default);
1994
+ R.Layer := Layer;
1995
+ R.SetOutlineContour(C);
1996
+ if N <> nil then R.Net := N;
1997
+ CurBoard.AddPCBObject(R);
1998
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, R.I_ObjectAddress);
1999
+ PCBServer.PostProcess;
2000
+ SL.Free;
2001
+ Emit(FI('layer', Layer) + FI('points', K));
2002
+ end;
2003
+
2004
+ procedure PcbText(Layer, X, Y, Height, Rot: Integer; Text: String; Mirror: Boolean);
2005
+ var
2006
+ T : IPCB_Text;
2007
+ begin
2008
+ if not Ok then Exit;
2009
+ PCBServer.PreProcess;
2010
+ T := PCBServer.PCBObjectFactory(eTextObject, eNoDimension, eCreate_Default);
2011
+ T.XLocation := CurBoard.XOrigin + X;
2012
+ T.YLocation := CurBoard.YOrigin + Y;
2013
+ T.Layer := Layer;
2014
+ T.Text := Text;
2015
+ T.Size := Height;
2016
+ T.Width := Height div 8;
2017
+ T.Rotation := Rot;
2018
+ T.MirrorFlag := Mirror;
2019
+ CurBoard.AddPCBObject(T);
2020
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, T.I_ObjectAddress);
2021
+ PCBServer.PostProcess;
2022
+ end;
2023
+
2024
+ procedure PcbTextReplace(Old, New: String);
2025
+ var
2026
+ It : IPCB_BoardIterator;
2027
+ T : IPCB_Text;
2028
+ N : IPCB_Text;
2029
+ L : TInterfaceList;
2030
+ I : Integer;
2031
+ begin
2032
+ { Free strings whose whole text is Old get New, in place (layer, position and size stay), e.g.
2033
+ a revision mark. Finding none is an error, so a stale Old never passes silently. }
2034
+ if not Ok then Exit;
2035
+ L := TInterfaceList.Create;
2036
+ It := CurBoard.BoardIterator_Create;
2037
+ It.AddFilter_ObjectSet(MkSet(eTextObject));
2038
+ It.AddFilter_LayerSet(AllLayers);
2039
+ It.AddFilter_Method(eProcessAll);
2040
+ T := It.FirstPCBObject;
2041
+ while T <> nil do
2042
+ begin
2043
+ { Component-owned texts are left alone: their replica would be added as free text and no
2044
+ longer move with the part. }
2045
+ if (not T.IsDesignator) and (not T.IsComment) and (not T.InComponent) and (T.Text = Old) then L.Add(T);
2046
+ T := It.NextPCBObject;
2047
+ end;
2048
+ CurBoard.BoardIterator_Destroy(It);
2049
+ if L.Count = 0 then begin L.Free; Fail('no free text "' + Old + '" on the board'); Exit; end;
2050
+ { A replica replaces each text: Altium computes the extent only when a text is added (see
2051
+ PcbTextSet), and a longer text must not keep the shorter one's box. }
2052
+ PCBServer.PreProcess;
2053
+ for I := 0 to L.Count - 1 do
2054
+ begin
2055
+ T := L.Items[I];
2056
+ N := T.Replicate;
2057
+ N.Text := New;
2058
+ CurBoard.AddPCBObject(N);
2059
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, N.I_ObjectAddress);
2060
+ CurBoard.RemovePCBObject(T);
2061
+ end;
2062
+ PCBServer.PostProcess;
2063
+ Emit(F('old', Old) + F('new', New) + FI('replaced', L.Count));
2064
+ L.Free;
2065
+ end;
2066
+
2067
+ { Rules: a rule added to the board takes priority 1 (the highest) and pushes the others down,
2068
+ and Altium refuses a scripted Priority change. Add general rules before specific ones. }
2069
+ function DropRule(Name: String): Boolean;
2070
+ var
2071
+ It : IPCB_BoardIterator;
2072
+ R : IPCB_Rule;
2073
+ L : TInterfaceList;
2074
+ I : Integer;
2075
+ begin
2076
+ { Rule ops are idempotent: a rule of the same name is replaced, never duplicated. }
2077
+ L := TInterfaceList.Create;
2078
+ It := CurBoard.BoardIterator_Create;
2079
+ It.AddFilter_ObjectSet(MkSet(eRuleObject));
2080
+ It.AddFilter_LayerSet(AllLayers);
2081
+ It.AddFilter_Method(eProcessAll);
2082
+ R := It.FirstPCBObject;
2083
+ while R <> nil do
2084
+ begin
2085
+ if SameText(R.Name, Name) then L.Add(R);
2086
+ R := It.NextPCBObject;
2087
+ end;
2088
+ CurBoard.BoardIterator_Destroy(It);
2089
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
2090
+ Result := L.Count > 0;
2091
+ L.Free;
2092
+ end;
2093
+
2094
+ procedure PcbRuleClearance(Name, Scope1, Scope2: String; Gap: Integer);
2095
+ var
2096
+ R : IPCB_ClearanceConstraint;
2097
+ begin
2098
+ if not Ok then Exit;
2099
+ DropRule(Name);
2100
+ R := PCBServer.PCBRuleFactory(eRule_Clearance);
2101
+ R.Name := Name;
2102
+ R.Scope1Expression := Scope1;
2103
+ R.Scope2Expression := Scope2;
2104
+ R.Gap := Gap;
2105
+ CurBoard.AddPCBObject(R);
2106
+ Emit(F('rule', Name));
2107
+ end;
2108
+
2109
+ procedure PcbRuleWidth(Name, Scope: String; MinW, PrefW, MaxW: Integer);
2110
+ var
2111
+ R : IPCB_MaxMinWidthConstraint;
2112
+ L : Integer;
2113
+ begin
2114
+ if not Ok then Exit;
2115
+ DropRule(Name);
2116
+ R := PCBServer.PCBRuleFactory(eRule_MaxMinWidth);
2117
+ R.Name := Name;
2118
+ R.Scope1Expression := Scope;
2119
+ for L := eTopLayer to eBottomLayer do
2120
+ begin
2121
+ R.MinWidth[L] := MinW;
2122
+ R.MaxWidth[L] := MaxW;
2123
+ R.FavoredWidth[L] := PrefW;
2124
+ end;
2125
+ CurBoard.AddPCBObject(R);
2126
+ Emit(F('rule', Name));
2127
+ end;
2128
+
2129
+ procedure PcbNetClass(Name, Csv: String);
2130
+ var
2131
+ NC : IPCB_ObjectClass;
2132
+ SL : TStringList;
2133
+ I : Integer;
2134
+ begin
2135
+ if not Ok then Exit;
2136
+ SL := TStringList.Create;
2137
+ SL.Delimiter := ',';
2138
+ SL.StrictDelimiter := True;
2139
+ SL.DelimitedText := Csv;
2140
+ NC := PCBServer.PCBClassFactoryByClassMember(eClassMemberKind_Net);
2141
+ NC.SuperClass := False;
2142
+ NC.Name := Name;
2143
+ for I := 0 to SL.Count - 1 do NC.AddMemberByName(SL[I]);
2144
+ CurBoard.AddPCBObject(NC);
2145
+ Emit(F('class', Name) + FI('members', SL.Count));
2146
+ SL.Free;
2147
+ end;
2148
+
2149
+ function PolygonPieces(P: IPCB_Polygon): Integer;
2150
+ var
2151
+ GI : IPCB_GroupIterator;
2152
+ O : IPCB_Primitive;
2153
+ begin
2154
+ { What a pour produced: its regions, tracks and arcs. An unpoured or shelved polygon has none. }
2155
+ Result := 0;
2156
+ GI := P.GroupIterator_Create;
2157
+ GI.AddFilter_ObjectSet(MkSet(eRegionObject, eTrackObject, eArcObject));
2158
+ O := GI.FirstPCBObject;
2159
+ while O <> nil do
2160
+ begin
2161
+ Result := Result + 1;
2162
+ O := GI.NextPCBObject;
2163
+ end;
2164
+ P.GroupIterator_Destroy(GI);
2165
+ end;
2166
+
2167
+ function NetName(O: IPCB_Primitive): String;
2168
+ begin
2169
+ Result := '';
2170
+ if O.Net <> nil then Result := O.Net.Name;
2171
+ end;
2172
+
2173
+ procedure PcbRepour;
2174
+ var
2175
+ It : IPCB_BoardIterator;
2176
+ P : IPCB_Polygon;
2177
+ L : TInterfaceList;
2178
+ I, Empty : Integer;
2179
+ begin
2180
+ { Repour the polygons of this board (CurBoard), each through its own Rebuild. The process
2181
+ route (PCB:ChangeObject Action=RepourAllPolygons) acts on the focused view, so it repoured
2182
+ another open board, and after a project reopen it silently did nothing. Collect first:
2183
+ changing a primitive re-registers it with a running iterator. }
2184
+ if not Ok then Exit;
2185
+ L := TInterfaceList.Create;
2186
+ It := CurBoard.BoardIterator_Create;
2187
+ It.AddFilter_ObjectSet(MkSet(ePolyObject));
2188
+ It.AddFilter_LayerSet(AllLayers);
2189
+ It.AddFilter_Method(eProcessAll);
2190
+ P := It.FirstPCBObject;
2191
+ while P <> nil do
2192
+ begin
2193
+ L.Add(P);
2194
+ P := It.NextPCBObject;
2195
+ end;
2196
+ CurBoard.BoardIterator_Destroy(It);
2197
+ PCBServer.PreProcess;
2198
+ for I := 0 to L.Count - 1 do
2199
+ begin
2200
+ P := L.Items[I];
2201
+ P.Rebuild;
2202
+ end;
2203
+ PCBServer.PostProcess;
2204
+ ResetParameters;
2205
+ AddStringParameter('Action', 'RebuildConnectivity');
2206
+ RunProcess('PCB:UpdateConnectivity');
2207
+ CurBoard.ViewManager_FullUpdate;
2208
+ Empty := 0;
2209
+ for I := 0 to L.Count - 1 do
2210
+ begin
2211
+ P := L.Items[I];
2212
+ if PolygonPieces(P) = 0 then Empty := Empty + 1;
2213
+ Emit(F('polygon', P.Name) + F('net', NetName(P)) + FI('layer', P.Layer) + FI('pieces', PolygonPieces(P)));
2214
+ end;
2215
+ Emit(F('kind', 'summary') + FI('polygons', L.Count) + FI('empty', Empty));
2216
+ L.Free;
2217
+ end;
2218
+
2219
+ procedure PcbPolygons;
2220
+ var
2221
+ It : IPCB_BoardIterator;
2222
+ P : IPCB_Polygon;
2223
+ R : TCoordRect;
2224
+ begin
2225
+ if not Ok then Exit;
2226
+ It := CurBoard.BoardIterator_Create;
2227
+ It.AddFilter_ObjectSet(MkSet(ePolyObject));
2228
+ It.AddFilter_LayerSet(AllLayers);
2229
+ It.AddFilter_Method(eProcessAll);
2230
+ P := It.FirstPCBObject;
2231
+ while P <> nil do
2232
+ begin
2233
+ R := P.BoundingRectangle;
2234
+ Emit(F('polygon', P.Name) + F('net', NetName(P)) + FI('layer', P.Layer) + F('layer_label', Layer2String(P.Layer))
2235
+ + FI('pieces', PolygonPieces(P)) + FB('locked', not P.Moveable)
2236
+ + FI('box_left', R.Left - CurBoard.XOrigin) + FI('box_bottom', R.Bottom - CurBoard.YOrigin)
2237
+ + FI('box_right', R.Right - CurBoard.XOrigin) + FI('box_top', R.Top - CurBoard.YOrigin));
2238
+ P := It.NextPCBObject;
2239
+ end;
2240
+ CurBoard.BoardIterator_Destroy(It);
2241
+ end;
2242
+
2243
+ function LayerWanted(Layer: Integer; Bars: String): Boolean;
2244
+ begin
2245
+ Result := (Bars = '') or (Pos('|' + IntToStr(Layer) + '|', Bars) > 0);
2246
+ end;
2247
+
2248
+ function TextHidden(T: IPCB_Text): Boolean;
2249
+ begin
2250
+ { A component's designator and comment are texts that print only when the component shows
2251
+ them (NameOn, CommentOn); a free text always prints. }
2252
+ Result := False;
2253
+ if T.InComponent and (T.Component <> nil) then
2254
+ begin
2255
+ if T.IsDesignator then Result := not T.Component.NameOn
2256
+ else if T.IsComment then Result := not T.Component.CommentOn;
2257
+ end;
2258
+ end;
2259
+
2260
+ function PrimitiveFields(Kind: String; O: IPCB_Primitive): String;
2261
+ var
2262
+ R : TCoordRect;
2263
+ XO, YO : Integer;
2264
+ begin
2265
+ XO := CurBoard.XOrigin;
2266
+ YO := CurBoard.YOrigin;
2267
+ R := O.BoundingRectangle;
2268
+ Result := F('kind', Kind) + FI('layer', O.Layer) + F('layer_label', Layer2String(O.Layer)) + F('net', NetName(O))
2269
+ + FB('locked', not O.Moveable) + FB('in_component', O.InComponent) + FB('in_polygon', O.InPolygon)
2270
+ + FI('box_left', R.Left - XO) + FI('box_bottom', R.Bottom - YO)
2271
+ + FI('box_right', R.Right - XO) + FI('box_top', R.Top - YO);
2272
+ if Kind = 'track' then
2273
+ Result := Result + FI('x1', O.x1 - XO) + FI('y1', O.y1 - YO) + FI('x2', O.x2 - XO) + FI('y2', O.y2 - YO)
2274
+ + FI('width', O.Width)
2275
+ else if Kind = 'arc' then
2276
+ Result := Result + FI('xc', O.XCenter - XO) + FI('yc', O.YCenter - YO) + FI('r', O.Radius)
2277
+ + FI('start', Round(O.StartAngle)) + FI('end', Round(O.EndAngle)) + FI('width', O.LineWidth)
2278
+ else if Kind = 'via' then
2279
+ Result := Result + FI('x', O.x - XO) + FI('y', O.y - YO) + FI('hole', O.HoleSize) + FI('diameter', O.Size)
2280
+ + FI('low_layer', O.LowLayer) + FI('high_layer', O.HighLayer)
2281
+ else if Kind = 'fill' then
2282
+ Result := Result + FI('x1', O.X1Location - XO) + FI('y1', O.Y1Location - YO)
2283
+ + FI('x2', O.X2Location - XO) + FI('y2', O.Y2Location - YO) + FI('rotation', Round(O.Rotation))
2284
+ else if Kind = 'region' then
2285
+ Result := Result + FI('region_kind', O.Kind)
2286
+ else if Kind = 'text' then
2287
+ Result := Result + F('text', O.Text) + FI('x', O.XLocation - XO) + FI('y', O.YLocation - YO)
2288
+ + FI('height', O.Size) + FI('stroke', O.Width) + FI('rotation', Round(O.Rotation))
2289
+ + FB('mirror', O.MirrorFlag) + FB('designator', O.IsDesignator) + FB('comment', O.IsComment)
2290
+ + FB('hidden', TextHidden(O));
2291
+ end;
2292
+
2293
+ procedure PcbPrimitives(Kinds, Bars: String; Offset, Limit: Integer);
2294
+ var
2295
+ It : IPCB_BoardIterator;
2296
+ O : IPCB_Primitive;
2297
+ KL : TStringList;
2298
+ K, N, Shown : Integer;
2299
+ Kind : String;
2300
+ begin
2301
+ { Read the board's primitives: Kinds is a comma list (track, arc, via, fill, region, text);
2302
+ Bars a |layer|layer| filter ('' for every layer). Paged with Offset and Limit; the summary
2303
+ item says how many matched in total, so a caller can tell a short list from a cut one. }
2304
+ if not Ok then Exit;
2305
+ KL := IntList(Kinds);
2306
+ N := 0;
2307
+ Shown := 0;
2308
+ for K := 0 to KL.Count - 1 do
2309
+ begin
2310
+ Kind := KL[K];
2311
+ It := CurBoard.BoardIterator_Create;
2312
+ if Kind = 'track' then It.AddFilter_ObjectSet(MkSet(eTrackObject))
2313
+ else if Kind = 'arc' then It.AddFilter_ObjectSet(MkSet(eArcObject))
2314
+ else if Kind = 'via' then It.AddFilter_ObjectSet(MkSet(eViaObject))
2315
+ else if Kind = 'fill' then It.AddFilter_ObjectSet(MkSet(eFillObject))
2316
+ else if Kind = 'region' then It.AddFilter_ObjectSet(MkSet(eRegionObject))
2317
+ else It.AddFilter_ObjectSet(MkSet(eTextObject));
2318
+ It.AddFilter_LayerSet(AllLayers);
2319
+ It.AddFilter_Method(eProcessAll);
2320
+ O := It.FirstPCBObject;
2321
+ while O <> nil do
2322
+ begin
2323
+ if LayerWanted(O.Layer, Bars) then
2324
+ begin
2325
+ if (N >= Offset) and (Shown < Limit) then
2326
+ begin
2327
+ Emit(PrimitiveFields(Kind, O));
2328
+ Shown := Shown + 1;
2329
+ end;
2330
+ N := N + 1;
2331
+ end;
2332
+ O := It.NextPCBObject;
2333
+ end;
2334
+ CurBoard.BoardIterator_Destroy(It);
2335
+ end;
2336
+ KL.Free;
2337
+ Emit(F('kind', 'summary') + FI('total', N) + FI('shown', Shown) + FI('offset', Offset));
2338
+ end;
2339
+
2340
+ procedure PcbTextSet(Match: String; Layer: Integer; Fields: String; X, Y, Height, Rot: Integer;
2341
+ Mirror: Boolean; NewText: String);
2342
+ var
2343
+ It : IPCB_BoardIterator;
2344
+ T : IPCB_Text;
2345
+ N : IPCB_Text;
2346
+ L : TInterfaceList;
2347
+ Before : String;
2348
+ begin
2349
+ { Change one free text found by its whole text (and layer, unless Layer is -1). Fields says
2350
+ what to set: p = position, h = height, r = rotation, m = mirror, t = text. Exactly one
2351
+ text must match: none or several is an error, never a guess. }
2352
+ if not Ok then Exit;
2353
+ L := TInterfaceList.Create;
2354
+ It := CurBoard.BoardIterator_Create;
2355
+ It.AddFilter_ObjectSet(MkSet(eTextObject));
2356
+ It.AddFilter_LayerSet(AllLayers);
2357
+ It.AddFilter_Method(eProcessAll);
2358
+ T := It.FirstPCBObject;
2359
+ while T <> nil do
2360
+ begin
2361
+ if (not T.IsDesignator) and (not T.IsComment) and (not T.InComponent) and (T.Text = Match)
2362
+ and ((Layer < 0) or (T.Layer = Layer)) then L.Add(T);
2363
+ T := It.NextPCBObject;
2364
+ end;
2365
+ CurBoard.BoardIterator_Destroy(It);
2366
+ if L.Count <> 1 then
2367
+ begin
2368
+ Fail(IntToStr(L.Count) + ' free texts match "' + Match + '"; exactly one must');
2369
+ L.Free;
2370
+ Exit;
2371
+ end;
2372
+ T := L.Items[0];
2373
+ L.Free;
2374
+ Before := FI('x_before', T.XLocation - CurBoard.XOrigin) + FI('y_before', T.YLocation - CurBoard.YOrigin)
2375
+ + FI('height_before', T.Size);
2376
+ { Altium computes a text's bounding rectangle when the text is added to a board; changing a
2377
+ text in place leaves the old extent (measured: a text grown to 1.0 mm kept its 0.8 mm box
2378
+ through redraws and a full update), so a silkscreen check would read the old size. The
2379
+ change goes to a replica that replaces the original. }
2380
+ PCBServer.PreProcess;
2381
+ N := T.Replicate;
2382
+ if Pos('p', Fields) > 0 then
2383
+ begin
2384
+ N.XLocation := CurBoard.XOrigin + X;
2385
+ N.YLocation := CurBoard.YOrigin + Y;
2386
+ end;
2387
+ if Pos('h', Fields) > 0 then
2388
+ begin
2389
+ N.Size := Height;
2390
+ N.Width := Height div 8;
2391
+ end;
2392
+ if Pos('r', Fields) > 0 then N.Rotation := Rot;
2393
+ if Pos('m', Fields) > 0 then N.MirrorFlag := Mirror;
2394
+ if Pos('t', Fields) > 0 then N.Text := NewText;
2395
+ CurBoard.AddPCBObject(N);
2396
+ PCBServer.SendMessageToRobots(CurBoard.I_ObjectAddress, c_Broadcast, PCBM_BoardRegisteration, N.I_ObjectAddress);
2397
+ CurBoard.RemovePCBObject(T);
2398
+ PCBServer.PostProcess;
2399
+ Emit(PrimitiveFields('text', N) + Before);
2400
+ end;
2401
+
2402
+ procedure PcbSave;
2403
+ begin
2404
+ if not Ok then Exit;
2405
+ CurBoardDoc.SetModified(True);
2406
+ Emit(FB('saved', CurBoardDoc.DoFileSave('')));
2407
+ end;
2408
+
2409
+ procedure PcbComponents;
2410
+ var
2411
+ It : IPCB_BoardIterator;
2412
+ C : IPCB_Component;
2413
+ begin
2414
+ if not Ok then Exit;
2415
+ It := CurBoard.BoardIterator_Create;
2416
+ It.AddFilter_ObjectSet(MkSet(eComponentObject));
2417
+ It.AddFilter_LayerSet(AllLayers);
2418
+ It.AddFilter_Method(eProcessAll);
2419
+ C := It.FirstPCBObject;
2420
+ while C <> nil do
2421
+ begin
2422
+ Emit(F('designator', C.Name.Text) + F('footprint', C.Pattern)
2423
+ + FI('x', C.X - CurBoard.XOrigin) + FI('y', C.Y - CurBoard.YOrigin)
2424
+ + FI('rotation', Round(C.Rotation)) + FB('bottom', C.Layer = eBottomLayer));
2425
+ C := It.NextPCBObject;
2426
+ end;
2427
+ CurBoard.BoardIterator_Destroy(It);
2428
+ end;
2429
+
2430
+ function ShapeName(Shape: Integer): String;
2431
+ begin
2432
+ if Shape = eRounded then Result := 'round'
2433
+ else if Shape = eRectangular then Result := 'rect'
2434
+ else if Shape = eOctagonal then Result := 'octagonal'
2435
+ else if Shape = eRoundedRectangular then Result := 'roundrect'
2436
+ else Result := 'other';
2437
+ end;
2438
+
2439
+ procedure PcbPads;
2440
+ var
2441
+ It : IPCB_BoardIterator;
2442
+ P : IPCB_Pad;
2443
+ Net, Owner, Stack : String;
2444
+ begin
2445
+ if not Ok then Exit;
2446
+ It := CurBoard.BoardIterator_Create;
2447
+ It.AddFilter_ObjectSet(MkSet(ePadObject));
2448
+ It.AddFilter_LayerSet(AllLayers);
2449
+ It.AddFilter_Method(eProcessAll);
2450
+ P := It.FirstPCBObject;
2451
+ while P <> nil do
2452
+ begin
2453
+ Net := '';
2454
+ if P.Net <> nil then Net := P.Net.Name;
2455
+ Owner := '';
2456
+ if P.Component <> nil then Owner := P.Component.Name.Text;
2457
+ { w, h and shape are the top layer's. A pad with a stack (mode 1 top-middle-bottom, 2 full
2458
+ stack) can differ below: its middle and bottom geometry is reported too. }
2459
+ Stack := FI('stack_mode', P.Mode);
2460
+ if P.Mode <> 0 then
2461
+ Stack := Stack + FI('mid_w', P.MidXSize) + FI('mid_h', P.MidYSize) + F('mid_shape', ShapeName(P.MidShape))
2462
+ + FI('bottom_w', P.BotXSize) + FI('bottom_h', P.BotYSize) + F('bottom_shape', ShapeName(P.BotShape));
2463
+ Emit(F('designator', Owner) + F('pad', P.Name) + F('net', Net)
2464
+ + FI('x', P.X - CurBoard.XOrigin) + FI('y', P.Y - CurBoard.YOrigin)
2465
+ + FI('layer', P.Layer) + FI('hole', P.HoleSize)
2466
+ + FI('w', P.TopXSize) + FI('h', P.TopYSize) + F('shape', ShapeName(P.TopShape))
2467
+ + FI('rotation', Round(P.Rotation))
2468
+ + FI('mask_mode', P.SolderMaskExpansionMode) + FI('mask', P.SolderMaskExpansion)
2469
+ + FI('paste_mode', P.PasteMaskExpansionMode) + FI('paste', P.PasteMaskExpansion) + Stack);
2470
+ P := It.NextPCBObject;
2471
+ end;
2472
+ CurBoard.BoardIterator_Destroy(It);
2473
+ end;
2474
+
2475
+ procedure PcbRules;
2476
+ var
2477
+ It : IPCB_BoardIterator;
2478
+ R : IPCB_Rule;
2479
+ begin
2480
+ if not Ok then Exit;
2481
+ It := CurBoard.BoardIterator_Create;
2482
+ It.AddFilter_ObjectSet(MkSet(eRuleObject));
2483
+ It.AddFilter_LayerSet(AllLayers);
2484
+ It.AddFilter_Method(eProcessAll);
2485
+ R := It.FirstPCBObject;
2486
+ while R <> nil do
2487
+ begin
2488
+ Emit(F('rule', R.Name) + FI('kind', R.RuleKind) + FI('priority', R.Priority) + FB('enabled', R.Enabled)
2489
+ + F('comment', R.Comment) + F('descriptor', R.Descriptor));
2490
+ R := It.NextPCBObject;
2491
+ end;
2492
+ CurBoard.BoardIterator_Destroy(It);
2493
+ end;
2494
+
2495
+ procedure PcbBoardOutline;
2496
+ var
2497
+ O : IPCB_BoardOutline;
2498
+ Seg : TPolySegment;
2499
+ I : Integer;
2500
+ begin
2501
+ if not Ok then Exit;
2502
+ O := CurBoard.BoardOutline;
2503
+ for I := 0 to O.PointCount - 1 do
2504
+ begin
2505
+ { Indexed record fields read inline can come back 0: copy the segment first. }
2506
+ Seg := O.Segments[I];
2507
+ Emit(FI('x', Seg.vx - CurBoard.XOrigin) + FI('y', Seg.vy - CurBoard.YOrigin) + FI('kind', Seg.Kind));
2508
+ end;
2509
+ end;
2510
+
2511
+ procedure PcbBodies;
2512
+ var
2513
+ It : IPCB_BoardIterator;
2514
+ GI : IPCB_GroupIterator;
2515
+ C : IPCB_Component;
2516
+ O : IPCB_Primitive;
2517
+ B : IPCB_ComponentBody;
2518
+ S : String;
2519
+ begin
2520
+ { One item per 3D body of every placed component: the STEP model's file name, or 'extruded'
2521
+ for a body drawn in the footprint, so a footprint update can be checked part by part. }
2522
+ if not Ok then Exit;
2523
+ It := CurBoard.BoardIterator_Create;
2524
+ It.AddFilter_ObjectSet(MkSet(eComponentObject));
2525
+ It.AddFilter_LayerSet(AllLayers);
2526
+ It.AddFilter_Method(eProcessAll);
2527
+ C := It.FirstPCBObject;
2528
+ while C <> nil do
2529
+ begin
2530
+ GI := C.GroupIterator_Create;
2531
+ GI.AddFilter_ObjectSet(MkSet(eComponentBodyObject));
2532
+ O := GI.FirstPCBObject;
2533
+ while O <> nil do
2534
+ begin
2535
+ B := O;
2536
+ S := '';
2537
+ if B.Model <> nil then S := ExtractFileName(B.Model.FileName);
2538
+ if S = '' then S := 'extruded';
2539
+ Emit(F('designator', C.Name.Text) + F('footprint', C.Pattern) + F('model', S)
2540
+ + FI('overall', B.OverallHeight) + FI('standoff', B.StandoffHeight));
2541
+ O := GI.NextPCBObject;
2542
+ end;
2543
+ C.GroupIterator_Destroy(GI);
2544
+ C := It.NextPCBObject;
2545
+ end;
2546
+ CurBoard.BoardIterator_Destroy(It);
2547
+ end;
2548
+
2549
+ procedure PcbNets;
2550
+ var
2551
+ It : IPCB_BoardIterator;
2552
+ N : IPCB_Net;
2553
+ begin
2554
+ if not Ok then Exit;
2555
+ It := CurBoard.BoardIterator_Create;
2556
+ It.AddFilter_ObjectSet(MkSet(eNetObject));
2557
+ It.AddFilter_LayerSet(AllLayers);
2558
+ It.AddFilter_Method(eProcessAll);
2559
+ N := It.FirstPCBObject;
2560
+ while N <> nil do
2561
+ begin
2562
+ Emit(F('net', N.Name));
2563
+ N := It.NextPCBObject;
2564
+ end;
2565
+ CurBoard.BoardIterator_Destroy(It);
2566
+ end;
2567
+
2568
+ procedure PcbRuleVia(Name, Scope: String; HoleMin, HolePref, HoleMax, DiaMin, DiaPref, DiaMax: Integer);
2569
+ var
2570
+ R : IPCB_RoutingViaStyleRule;
2571
+ begin
2572
+ if not Ok then Exit;
2573
+ DropRule(Name);
2574
+ R := PCBServer.PCBRuleFactory(eRule_RoutingViaStyle);
2575
+ R.Name := Name;
2576
+ R.Scope1Expression := Scope;
2577
+ R.MinHoleWidth := HoleMin;
2578
+ R.PreferedHoleWidth := HolePref;
2579
+ R.MaxHoleWidth := HoleMax;
2580
+ R.MinWidth := DiaMin;
2581
+ R.PreferedWidth := DiaPref;
2582
+ R.MaxWidth := DiaMax;
2583
+ CurBoard.AddPCBObject(R);
2584
+ Emit(F('rule', Name));
2585
+ end;
2586
+
2587
+ procedure PcbRuleValue(Name: String; Kind: Integer; Scope1, Scope2: String; Value, Value2: Integer);
2588
+ var
2589
+ R : IPCB_Rule;
2590
+ begin
2591
+ { Rule kinds whose value is one length: 12 PlaneClearance, 13 SolderMaskExpansion,
2592
+ 14 PasteMaskExpansion, 24 ComponentClearance (Value2 = vertical gap), 52 HoleToHoleClearance. }
2593
+ if not Ok then Exit;
2594
+ DropRule(Name);
2595
+ R := PCBServer.PCBRuleFactory(Kind);
2596
+ R.Name := Name;
2597
+ R.Scope1Expression := Scope1;
2598
+ R.Scope2Expression := Scope2;
2599
+ case Kind of
2600
+ 12: R.Clearance := Value;
2601
+ 13, 14: R.Expansion := Value;
2602
+ 24: begin R.Gap := Value; R.VerticalGap := Value2; end;
2603
+ 52: R.Gap := Value;
2604
+ else
2605
+ begin Fail('rule kind ' + IntToStr(Kind) + ' is not a single-value rule'); Exit; end;
2606
+ end;
2607
+ CurBoard.AddPCBObject(R);
2608
+ Emit(F('rule', Name));
2609
+ end;
2610
+
2611
+ procedure PcbRuleDelete(Name: String);
2612
+ begin
2613
+ if not Ok then Exit;
2614
+ Emit(F('rule', Name) + FB('deleted', DropRule(Name)));
2615
+ end;
2616
+
2617
+ procedure PcbPlaneNet(Layer: Integer; Net: String; PullBack: Integer);
2618
+ var
2619
+ LO : IPCB_LayerObject_V7;
2620
+ begin
2621
+ if not Ok then Exit;
2622
+ if FindNet(Net) = nil then begin Fail('no net ' + Net + ' on the board'); Exit; end;
2623
+ LO := CurBoard.LayerStack_V7.LayerObject_V7[Layer];
2624
+ if LO = nil then begin Fail('layer ' + IntToStr(Layer) + ' is not in the stack (pcb_add_layer first)'); Exit; end;
2625
+ LO.NetName := Net;
2626
+ LO.PullBackDistance := PullBack;
2627
+ CurBoard.ViewManager_FullUpdate;
2628
+ Emit(FI('layer', Layer) + F('net', LO.NetName));
2629
+ end;
2630
+
2631
+ procedure PcbDielectric(Layer, Height, Copper: Integer);
2632
+ var
2633
+ LO : IPCB_LayerObject_V7;
2634
+ begin
2635
+ { The copper thickness of one stack layer and the height of the dielectric below it. }
2636
+ if not Ok then Exit;
2637
+ LO := CurBoard.LayerStack_V7.LayerObject_V7[Layer];
2638
+ if LO = nil then begin Fail('layer ' + IntToStr(Layer) + ' is not in the stack (pcb_add_layer first)'); Exit; end;
2639
+ if LO.Dielectric = nil then begin Fail('layer ' + IntToStr(Layer) + ' has no dielectric below it'); Exit; end;
2640
+ LO.Dielectric.DielectricHeight := Height;
2641
+ LO.CopperThickness := Copper;
2642
+ CurBoard.ViewManager_FullUpdate;
2643
+ Emit(FI('layer', Layer) + FI('height', LO.Dielectric.DielectricHeight) + FI('copper', LO.CopperThickness));
2644
+ end;
2645
+
2646
+ procedure PcbCopper(Layer, Copper: Integer);
2647
+ var
2648
+ LO : IPCB_LayerObject_V7;
2649
+ begin
2650
+ { The copper thickness of one stack layer alone (pcb_dielectric needs a dielectric below, which
2651
+ the bottom layer has not). }
2652
+ if not Ok then Exit;
2653
+ LO := CurBoard.LayerStack_V7.LayerObject_V7[Layer];
2654
+ if LO = nil then begin Fail('layer ' + IntToStr(Layer) + ' is not in the stack (pcb_add_layer first)'); Exit; end;
2655
+ LO.CopperThickness := Copper;
2656
+ CurBoard.ViewManager_FullUpdate;
2657
+ Emit(FI('layer', Layer) + FI('copper', LO.CopperThickness));
2658
+ end;
2659
+
2660
+ procedure PcbDeleteTrack(Layer, X1, Y1, X2, Y2: Integer);
2661
+ var
2662
+ It : IPCB_BoardIterator;
2663
+ T : IPCB_Track;
2664
+ L : TInterfaceList;
2665
+ I : Integer;
2666
+ Ax, Ay, Bx, By, Tol : Integer;
2667
+ begin
2668
+ { One track, named by its end points (either direction, 0.01 mm tolerance). Fails when no
2669
+ track matches, so a stale coordinate never passes silently. }
2670
+ if not Ok then Exit;
2671
+ Tol := 3937;
2672
+ Ax := CurBoard.XOrigin + X1; Ay := CurBoard.YOrigin + Y1;
2673
+ Bx := CurBoard.XOrigin + X2; By := CurBoard.YOrigin + Y2;
2674
+ L := TInterfaceList.Create;
2675
+ It := CurBoard.BoardIterator_Create;
2676
+ It.AddFilter_ObjectSet(MkSet(eTrackObject));
2677
+ It.AddFilter_LayerSet(MkSet(Layer));
2678
+ It.AddFilter_Method(eProcessAll);
2679
+ T := It.FirstPCBObject;
2680
+ while T <> nil do
2681
+ begin
2682
+ if (T.Layer = Layer) and (
2683
+ ((Abs(T.x1 - Ax) <= Tol) and (Abs(T.y1 - Ay) <= Tol) and (Abs(T.x2 - Bx) <= Tol) and (Abs(T.y2 - By) <= Tol)) or
2684
+ ((Abs(T.x1 - Bx) <= Tol) and (Abs(T.y1 - By) <= Tol) and (Abs(T.x2 - Ax) <= Tol) and (Abs(T.y2 - Ay) <= Tol))) then
2685
+ L.Add(T);
2686
+ T := It.NextPCBObject;
2687
+ end;
2688
+ CurBoard.BoardIterator_Destroy(It);
2689
+ if L.Count = 0 then begin L.Free; Fail('no track with those end points on that layer'); Exit; end;
2690
+ PCBServer.PreProcess;
2691
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
2692
+ PCBServer.PostProcess;
2693
+ Emit(FI('removed', L.Count));
2694
+ L.Free;
2695
+ end;
2696
+
2697
+ procedure PcbDeleteVia(X, Y: Integer; Net: String);
2698
+ var
2699
+ It : IPCB_BoardIterator;
2700
+ V : IPCB_Via;
2701
+ L : TInterfaceList;
2702
+ I, Ax, Ay, Tol : Integer;
2703
+ begin
2704
+ { The vias at one point (0.01 mm tolerance), optionally only of one net. Fails when none
2705
+ matches, so a stale coordinate never passes silently. }
2706
+ if not Ok then Exit;
2707
+ Tol := 3937;
2708
+ Ax := CurBoard.XOrigin + X; Ay := CurBoard.YOrigin + Y;
2709
+ L := TInterfaceList.Create;
2710
+ It := CurBoard.BoardIterator_Create;
2711
+ It.AddFilter_ObjectSet(MkSet(eViaObject));
2712
+ It.AddFilter_LayerSet(AllLayers);
2713
+ It.AddFilter_Method(eProcessAll);
2714
+ V := It.FirstPCBObject;
2715
+ while V <> nil do
2716
+ begin
2717
+ if (Abs(V.x - Ax) <= Tol) and (Abs(V.y - Ay) <= Tol) and ((Net = '') or SameText(NetName(V), Net)) then
2718
+ L.Add(V);
2719
+ V := It.NextPCBObject;
2720
+ end;
2721
+ CurBoard.BoardIterator_Destroy(It);
2722
+ if L.Count = 0 then begin L.Free; Fail('no via at that point'); Exit; end;
2723
+ PCBServer.PreProcess;
2724
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
2725
+ PCBServer.PostProcess;
2726
+ Emit(FI('removed', L.Count));
2727
+ L.Free;
2728
+ end;
2729
+
2730
+ function NearPoint(X, Y, Px, Py, Tol: Integer): Boolean;
2731
+ begin
2732
+ Result := (Abs(X - Px) <= Tol) and (Abs(Y - Py) <= Tol);
2733
+ end;
2734
+
2735
+ procedure PcbTrackEnd(Net: String; Layer, X, Y, ToX, ToY: Integer);
2736
+ var
2737
+ It : IPCB_BoardIterator;
2738
+ T : IPCB_Track;
2739
+ L1, L2 : TInterfaceList;
2740
+ I, Ax, Ay, Bx, By, Tol, Skipped : Integer;
2741
+ begin
2742
+ { Move the track ends of Net on Layer that lie within 0.01 mm of (X, Y), and not on
2743
+ (ToX, ToY) already, to (ToX, ToY): joins an autorouter left a fraction of a micron apart
2744
+ (reported as net antennae) become one point. A track whose other end is the target is left
2745
+ alone (moving it would leave no track). Width, net and lock stay. Fails when no end moves. }
2746
+ if not Ok then Exit;
2747
+ Tol := 3937;
2748
+ Skipped := 0;
2749
+ Ax := CurBoard.XOrigin + X; Ay := CurBoard.YOrigin + Y;
2750
+ Bx := CurBoard.XOrigin + ToX; By := CurBoard.YOrigin + ToY;
2751
+ L1 := TInterfaceList.Create;
2752
+ L2 := TInterfaceList.Create;
2753
+ It := CurBoard.BoardIterator_Create;
2754
+ It.AddFilter_ObjectSet(MkSet(eTrackObject));
2755
+ It.AddFilter_LayerSet(MkSet(Layer));
2756
+ It.AddFilter_Method(eProcessAll);
2757
+ T := It.FirstPCBObject;
2758
+ while T <> nil do
2759
+ begin
2760
+ if (T.Layer = Layer) and SameText(NetName(T), Net) then
2761
+ begin
2762
+ if NearPoint(T.x1, T.y1, Ax, Ay, Tol) and not ((T.x1 = Bx) and (T.y1 = By)) then
2763
+ begin
2764
+ if NearPoint(T.x2, T.y2, Bx, By, 0) then Skipped := Skipped + 1 else L1.Add(T);
2765
+ end;
2766
+ if NearPoint(T.x2, T.y2, Ax, Ay, Tol) and not ((T.x2 = Bx) and (T.y2 = By)) then
2767
+ begin
2768
+ if NearPoint(T.x1, T.y1, Bx, By, 0) then Skipped := Skipped + 1 else L2.Add(T);
2769
+ end;
2770
+ end;
2771
+ T := It.NextPCBObject;
2772
+ end;
2773
+ CurBoard.BoardIterator_Destroy(It);
2774
+ if L1.Count + L2.Count = 0 then
2775
+ begin
2776
+ L1.Free; L2.Free;
2777
+ Fail('no track end of that net to move at that point on that layer (' + IntToStr(Skipped) + ' would collapse)');
2778
+ Exit;
2779
+ end;
2780
+ PCBServer.PreProcess;
2781
+ for I := 0 to L1.Count - 1 do
2782
+ begin
2783
+ T := L1.Items[I];
2784
+ PCBServer.SendMessageToRobots(T.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
2785
+ T.x1 := Bx; T.y1 := By;
2786
+ PCBServer.SendMessageToRobots(T.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
2787
+ end;
2788
+ for I := 0 to L2.Count - 1 do
2789
+ begin
2790
+ T := L2.Items[I];
2791
+ PCBServer.SendMessageToRobots(T.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
2792
+ T.x2 := Bx; T.y2 := By;
2793
+ PCBServer.SendMessageToRobots(T.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
2794
+ end;
2795
+ PCBServer.PostProcess;
2796
+ Emit(FI('moved', L1.Count + L2.Count) + FI('skipped', Skipped));
2797
+ L1.Free;
2798
+ L2.Free;
2799
+ end;
2800
+
2801
+ procedure PcbUnroute(Csv: String; IncludeLocked: Boolean);
2802
+ var
2803
+ It : IPCB_BoardIterator;
2804
+ O : IPCB_Primitive;
2805
+ L : TInterfaceList;
2806
+ SL : TStringList;
2807
+ I, K : Integer;
2808
+ begin
2809
+ if not Ok then Exit;
2810
+ SL := TStringList.Create;
2811
+ SL.Delimiter := ',';
2812
+ SL.StrictDelimiter := True;
2813
+ SL.DelimitedText := Csv;
2814
+ L := TInterfaceList.Create;
2815
+ It := CurBoard.BoardIterator_Create;
2816
+ It.AddFilter_ObjectSet(MkSet(eTrackObject, eArcObject, eViaObject));
2817
+ It.AddFilter_LayerSet(AllLayers);
2818
+ It.AddFilter_Method(eProcessAll);
2819
+ O := It.FirstPCBObject;
2820
+ while O <> nil do
2821
+ begin
2822
+ if (O.Net <> nil) and (SL.IndexOf(O.Net.Name) >= 0) and (IncludeLocked or O.Moveable) then L.Add(O);
2823
+ O := It.NextPCBObject;
2824
+ end;
2825
+ CurBoard.BoardIterator_Destroy(It);
2826
+ PCBServer.PreProcess;
2827
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
2828
+ PCBServer.PostProcess;
2829
+ K := L.Count;
2830
+ L.Free;
2831
+ SL.Free;
2832
+ CurBoard.ViewManager_FullUpdate;
2833
+ Emit(FI('removed', K));
2834
+ end;
2835
+
2836
+ procedure PcbRuleHoleSize(Name: String; MinH, MaxH: Integer);
2837
+ var
2838
+ R : IPCB_Rule;
2839
+ begin
2840
+ if not Ok then Exit;
2841
+ DropRule(Name);
2842
+ R := PCBServer.PCBRuleFactory(42); { eRule_MaxMinHoleSize }
2843
+ R.Name := Name;
2844
+ R.Scope1Expression := 'All';
2845
+ R.MinLimit := MinH;
2846
+ R.MaxLimit := MaxH;
2847
+ CurBoard.AddPCBObject(R);
2848
+ Emit(F('rule', Name));
2849
+ end;
2850
+
2851
+ procedure PcbRuleRoutingLayers(Name, Scope, Layers: String);
2852
+ var
2853
+ R : IPCB_Rule;
2854
+ LO : IPCB_LayerObject_V7;
2855
+ S : String;
2856
+ begin
2857
+ { Which signal layers the router may use; Layers lists layer numbers between bars ("|1|32|").
2858
+ Every other signal layer in the stack is switched off for this rule. }
2859
+ if not Ok then Exit;
2860
+ DropRule(Name);
2861
+ R := PCBServer.PCBRuleFactory(eRule_RoutingLayers);
2862
+ R.Name := Name;
2863
+ R.Scope1Expression := Scope;
2864
+ S := '';
2865
+ LO := CurBoard.LayerStack_V7.FirstLayer;
2866
+ while LO <> nil do
2867
+ begin
2868
+ if (LO.LayerID >= eTopLayer) and (LO.LayerID <= eBottomLayer) then
2869
+ begin
2870
+ R.RoutingLayers[LO.LayerID] := Pos('|' + IntToStr(LO.LayerID) + '|', Layers) > 0;
2871
+ if R.RoutingLayers[LO.LayerID] then S := S + LO.Name + ';';
2872
+ end;
2873
+ LO := CurBoard.LayerStack_V7.NextLayer(LO);
2874
+ end;
2875
+ CurBoard.AddPCBObject(R);
2876
+ Emit(F('rule', Name) + F('layers', S));
2877
+ end;
2878
+
2879
+ procedure PcbRuleCorners(Name, Scope: String; Style, MinSetBack, MaxSetBack: Integer);
2880
+ var
2881
+ R : IPCB_Rule;
2882
+ begin
2883
+ { Routing Corners: the corner style Situs and interactive routing use. Style 0 = 90 degrees,
2884
+ 1 = 45 degrees, 2 = rounded; the setbacks size the 45-degree chamfer or the arc. }
2885
+ if not Ok then Exit;
2886
+ DropRule(Name);
2887
+ R := PCBServer.PCBRuleFactory(10); { eRule_RoutingCorners }
2888
+ R.Name := Name;
2889
+ R.Scope1Expression := Scope;
2890
+ R.Style := Style;
2891
+ R.MinSetBack := MinSetBack;
2892
+ R.MaxSetBack := MaxSetBack;
2893
+ CurBoard.AddPCBObject(R);
2894
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString));
2895
+ end;
2896
+
2897
+ procedure PcbRulePriority(Name, Scope: String; Priority: Integer);
2898
+ var
2899
+ R : IPCB_Rule;
2900
+ begin
2901
+ { Routing Priority: Situs routes nets with a higher priority (0-100) first. }
2902
+ if not Ok then Exit;
2903
+ DropRule(Name);
2904
+ R := PCBServer.PCBRuleFactory(8); { eRule_RoutingPriority }
2905
+ R.Name := Name;
2906
+ R.Scope1Expression := Scope;
2907
+ R.RoutingPriority := Priority;
2908
+ CurBoard.AddPCBObject(R);
2909
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString));
2910
+ end;
2911
+
2912
+ procedure PcbRuleTopology(Name, Scope: String; Topology: Integer);
2913
+ var
2914
+ R : IPCB_Rule;
2915
+ begin
2916
+ { Routing Topology: the order in which Situs joins a net's pins (0 = shortest). }
2917
+ if not Ok then Exit;
2918
+ DropRule(Name);
2919
+ R := PCBServer.PCBRuleFactory(7); { eRule_RoutingTopology }
2920
+ R.Name := Name;
2921
+ R.Scope1Expression := Scope;
2922
+ R.Topology := Topology;
2923
+ CurBoard.AddPCBObject(R);
2924
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString));
2925
+ end;
2926
+
2927
+ function DropDiffPair(Name: String): Integer;
2928
+ var
2929
+ It : IPCB_BoardIterator;
2930
+ D : IPCB_DifferentialPair;
2931
+ L : TInterfaceList;
2932
+ I : Integer;
2933
+ begin
2934
+ L := TInterfaceList.Create;
2935
+ It := CurBoard.BoardIterator_Create;
2936
+ It.AddFilter_ObjectSet(MkSet(eDifferentialPairObject));
2937
+ It.AddFilter_LayerSet(AllLayers);
2938
+ It.AddFilter_Method(eProcessAll);
2939
+ D := It.FirstPCBObject;
2940
+ while D <> nil do
2941
+ begin
2942
+ if SameText(D.Name, Name) then L.Add(D);
2943
+ D := It.NextPCBObject;
2944
+ end;
2945
+ CurBoard.BoardIterator_Destroy(It);
2946
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
2947
+ Result := L.Count;
2948
+ L.Free;
2949
+ end;
2950
+
2951
+ procedure PcbDiffPairs;
2952
+ var
2953
+ It : IPCB_BoardIterator;
2954
+ D : IPCB_DifferentialPair;
2955
+ P, N : String;
2956
+ begin
2957
+ if not Ok then Exit;
2958
+ It := CurBoard.BoardIterator_Create;
2959
+ It.AddFilter_ObjectSet(MkSet(eDifferentialPairObject));
2960
+ It.AddFilter_LayerSet(AllLayers);
2961
+ It.AddFilter_Method(eProcessAll);
2962
+ D := It.FirstPCBObject;
2963
+ while D <> nil do
2964
+ begin
2965
+ P := ''; N := '';
2966
+ if D.PositiveNet <> nil then P := D.PositiveNet.Name;
2967
+ if D.NegativeNet <> nil then N := D.NegativeNet.Name;
2968
+ Emit(F('pair', D.Name) + F('positive', P) + F('negative', N));
2969
+ D := It.NextPCBObject;
2970
+ end;
2971
+ CurBoard.BoardIterator_Destroy(It);
2972
+ end;
2973
+
2974
+ procedure PcbDiffPair(Name, PosNet, NegNet: String);
2975
+ var
2976
+ D : IPCB_DifferentialPair;
2977
+ P, N : IPCB_Net;
2978
+ Replaced : Integer;
2979
+ begin
2980
+ { A differential pair object: the rule scope IsDifferentialPair / InDifferentialPair('Name')
2981
+ and the interactive pair router act on these, not on net names. Same name = replaced. }
2982
+ if not Ok then Exit;
2983
+ P := FindNet(PosNet);
2984
+ N := FindNet(NegNet);
2985
+ if P = nil then begin Fail('no net ' + PosNet + ' on the board'); Exit; end;
2986
+ if N = nil then begin Fail('no net ' + NegNet + ' on the board'); Exit; end;
2987
+ Replaced := DropDiffPair(Name);
2988
+ PCBServer.PreProcess;
2989
+ D := PCBServer.PCBObjectFactory(eDifferentialPairObject, eNoDimension, eCreate_Default);
2990
+ D.Name := Name;
2991
+ D.PositiveNet := P;
2992
+ D.NegativeNet := N;
2993
+ CurBoard.AddPCBObject(D);
2994
+ PCBServer.PostProcess;
2995
+ Emit(F('pair', Name) + F('positive', P.Name) + F('negative', N.Name) + FI('replaced', Replaced));
2996
+ end;
2997
+
2998
+ procedure PcbRuleDiffPair(Name, Scope: String; MinW, PrefW, MaxW, MinG, PrefG, MaxG, Uncoupled: Integer);
2999
+ var
3000
+ R : IPCB_Rule;
3001
+ L : Integer;
3002
+ begin
3003
+ { Differential Pairs Routing: width and gap per signal layer, and the longest stretch the pair
3004
+ may run uncoupled (at connectors and pads). }
3005
+ if not Ok then Exit;
3006
+ DropRule(Name);
3007
+ R := PCBServer.PCBRuleFactory(51); { eRule_DifferentialPairsRouting, between fan-out height 50 and hole-to-hole 52 }
3008
+ R.Name := Name;
3009
+ R.Scope1Expression := Scope;
3010
+ for L := eTopLayer to eBottomLayer do
3011
+ begin
3012
+ R.MinWidth[L] := MinW;
3013
+ R.PreferedWidth[L] := PrefW;
3014
+ R.MaxWidth[L] := MaxW;
3015
+ R.MinGap[L] := MinG;
3016
+ R.PreferedGap[L] := PrefG;
3017
+ R.MaxGap[L] := MaxG;
3018
+ end;
3019
+ R.MaxUncoupledLength := Uncoupled;
3020
+ CurBoard.AddPCBObject(R);
3021
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString) + FI('uncoupled_max', R.MaxUncoupledLength));
3022
+ end;
3023
+
3024
+ procedure PcbRulePlaneConnect(Name, Scope: String; Style, Conductor, Entries, AirGap, Expansion: Integer);
3025
+ var
3026
+ R : IPCB_Rule;
3027
+ begin
3028
+ { Power Plane Connect Style: how pads and vias join an internal plane. Style 0 = relief,
3029
+ 1 = direct, 2 = no connect (TPlaneConnectStyle). }
3030
+ if not Ok then Exit;
3031
+ DropRule(Name);
3032
+ R := PCBServer.PCBRuleFactory(6); { eRule_PowerPlaneConnectStyle }
3033
+ R.Name := Name;
3034
+ R.Scope1Expression := Scope;
3035
+ R.PlaneConnectStyle := Style;
3036
+ R.ReliefConductorWidth := Conductor;
3037
+ R.ReliefEntries := Entries;
3038
+ R.ReliefAirGap := AirGap;
3039
+ R.ReliefExpansion := Expansion;
3040
+ CurBoard.AddPCBObject(R);
3041
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString));
3042
+ end;
3043
+
3044
+ procedure PcbRulePolygonConnect(Name, Scope: String; Style, Conductor, Entries, Angle, AirGap: Integer);
3045
+ var
3046
+ R : IPCB_Rule;
3047
+ begin
3048
+ { Polygon Connect Style: how pads and vias join a polygon pour. Style as for planes; Angle 45
3049
+ or 90 degrees for the relief spokes. }
3050
+ if not Ok then Exit;
3051
+ DropRule(Name);
3052
+ R := PCBServer.PCBRuleFactory(20); { eRule_PolygonConnectStyle }
3053
+ R.Name := Name;
3054
+ R.Scope1Expression := Scope;
3055
+ R.ConnectStyle := Style;
3056
+ R.ReliefConductorWidth := Conductor;
3057
+ R.ReliefEntries := Entries;
3058
+ R.PolygonReliefAngle := Angle;
3059
+ R.ReliefAirGap := AirGap;
3060
+ CurBoard.AddPCBObject(R);
3061
+ Emit(F('rule', Name) + F('summary', R.GetState_DataSummaryString) + FI('relief_angle', R.PolygonReliefAngle));
3062
+ end;
3063
+
3064
+ procedure PcbRuleSilkToSilk(Name: String; Gap: Integer);
3065
+ var
3066
+ R : IPCB_Rule;
3067
+ begin
3068
+ if not Ok then Exit;
3069
+ DropRule(Name);
3070
+ R := PCBServer.PCBRuleFactory(55); { eRule_SilkToSilkClearance }
3071
+ R.Name := Name;
3072
+ R.Scope1Expression := 'All';
3073
+ R.Scope2Expression := 'All';
3074
+ R.SilkToSilkClearance := Gap;
3075
+ CurBoard.AddPCBObject(R);
3076
+ Emit(F('rule', Name));
3077
+ end;
3078
+
3079
+ procedure PcbRuleMaskSliver(Name: String; Gap: Integer);
3080
+ var
3081
+ R : IPCB_Rule;
3082
+ begin
3083
+ if not Ok then Exit;
3084
+ DropRule(Name);
3085
+ R := PCBServer.PCBRuleFactory(53); { eRule_MinimumSolderMaskSliver }
3086
+ R.Name := Name;
3087
+ R.Scope1Expression := 'All';
3088
+ R.Scope2Expression := 'All';
3089
+ R.MinSolderMaskSliver := Gap;
3090
+ CurBoard.AddPCBObject(R);
3091
+ Emit(F('rule', Name));
3092
+ end;
3093
+
3094
+ procedure PcbTentVias(Top, Bottom: Boolean);
3095
+ var
3096
+ It : IPCB_BoardIterator;
3097
+ V : IPCB_Via;
3098
+ L : TInterfaceList;
3099
+ I : Integer;
3100
+ begin
3101
+ if not Ok then Exit;
3102
+ { Collect first: a primitive modified during iteration is re-indexed and visited again,
3103
+ which never ends. }
3104
+ L := TInterfaceList.Create;
3105
+ It := CurBoard.BoardIterator_Create;
3106
+ It.AddFilter_ObjectSet(MkSet(eViaObject));
3107
+ It.AddFilter_LayerSet(AllLayers);
3108
+ It.AddFilter_Method(eProcessAll);
3109
+ V := It.FirstPCBObject;
3110
+ while V <> nil do begin L.Add(V); V := It.NextPCBObject; end;
3111
+ CurBoard.BoardIterator_Destroy(It);
3112
+ PCBServer.PreProcess;
3113
+ for I := 0 to L.Count - 1 do
3114
+ begin
3115
+ V := L.Items[I];
3116
+ PCBServer.SendMessageToRobots(V.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
3117
+ { Tenting flags are ignored while the via follows the mask expansion rule (mode 1). }
3118
+ V.SolderMaskExpansionMode := 2;
3119
+ V.IsTenting_Top := Top;
3120
+ V.IsTenting_Bottom := Bottom;
3121
+ PCBServer.SendMessageToRobots(V.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
3122
+ end;
3123
+ PCBServer.PostProcess;
3124
+ Emit(FI('vias', L.Count));
3125
+ L.Free;
3126
+ end;
3127
+
3128
+ procedure PcbClearTexts(Layer: Integer);
3129
+ var
3130
+ It : IPCB_BoardIterator;
3131
+ T : IPCB_Text;
3132
+ L : TInterfaceList;
3133
+ I : Integer;
3134
+ begin
3135
+ { Free texts only: designators and comments belong to their components. Lets a script
3136
+ that writes silk text be re-run without stacking duplicates. }
3137
+ if not Ok then Exit;
3138
+ L := TInterfaceList.Create;
3139
+ It := CurBoard.BoardIterator_Create;
3140
+ It.AddFilter_ObjectSet(MkSet(eTextObject));
3141
+ It.AddFilter_LayerSet(MkSet(Layer));
3142
+ It.AddFilter_Method(eProcessAll);
3143
+ T := It.FirstPCBObject;
3144
+ while T <> nil do
3145
+ begin
3146
+ if (not T.InComponent) and (T.Layer = Layer) then L.Add(T);
3147
+ T := It.NextPCBObject;
3148
+ end;
3149
+ CurBoard.BoardIterator_Destroy(It);
3150
+ PCBServer.PreProcess;
3151
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
3152
+ PCBServer.PostProcess;
3153
+ Emit(FI('layer', Layer) + FI('removed', L.Count));
3154
+ L.Free;
3155
+ end;
3156
+
3157
+ procedure PcbClearLayer(Layer: Integer);
3158
+ var
3159
+ It : IPCB_BoardIterator;
3160
+ O : IPCB_Primitive;
3161
+ L : TInterfaceList;
3162
+ I : Integer;
3163
+ Pass : Integer;
3164
+ begin
3165
+ { Free tracks, arcs and regions on one layer (no net, not part of a component or polygon),
3166
+ e.g. an outline drawn on a mechanical layer or a logo on an overlay, so a script can redraw
3167
+ it without stacking copies. Board regions (Kind 3, the shape that carries the layer stack)
3168
+ are never drawings: regions are read in their own pass, where Kind is defined. }
3169
+ if not Ok then Exit;
3170
+ L := TInterfaceList.Create;
3171
+ for Pass := 0 to 1 do
3172
+ begin
3173
+ It := CurBoard.BoardIterator_Create;
3174
+ if Pass = 0 then It.AddFilter_ObjectSet(MkSet(eTrackObject, eArcObject))
3175
+ else It.AddFilter_ObjectSet(MkSet(eRegionObject));
3176
+ It.AddFilter_LayerSet(MkSet(Layer));
3177
+ It.AddFilter_Method(eProcessAll);
3178
+ O := It.FirstPCBObject;
3179
+ while O <> nil do
3180
+ begin
3181
+ if (O.Layer = Layer) and (O.Net = nil) and (not O.InComponent) and (not O.InPolygon) then
3182
+ begin
3183
+ if Pass = 0 then L.Add(O)
3184
+ else if O.Kind <> 3 then L.Add(O);
3185
+ end;
3186
+ O := It.NextPCBObject;
3187
+ end;
3188
+ CurBoard.BoardIterator_Destroy(It);
3189
+ end;
3190
+ PCBServer.PreProcess;
3191
+ for I := 0 to L.Count - 1 do CurBoard.RemovePCBObject(L.Items[I]);
3192
+ PCBServer.PostProcess;
3193
+ Emit(FI('layer', Layer) + FI('removed', L.Count));
3194
+ L.Free;
3195
+ end;
3196
+
3197
+ procedure PcbRemoveComponents(Csv: String);
3198
+ var
3199
+ SL : TStringList;
3200
+ C : IPCB_Component;
3201
+ I, K : Integer;
3202
+ begin
3203
+ if not Ok then Exit;
3204
+ SL := TStringList.Create;
3205
+ SL.Delimiter := ',';
3206
+ SL.StrictDelimiter := True;
3207
+ SL.DelimitedText := Csv;
3208
+ K := 0;
3209
+ PCBServer.PreProcess;
3210
+ for I := 0 to SL.Count - 1 do
3211
+ begin
3212
+ C := FindPcbComp(SL[I]);
3213
+ if C <> nil then begin CurBoard.RemovePCBObject(C); K := K + 1; end;
3214
+ end;
3215
+ PCBServer.PostProcess;
3216
+ CurBoard.ViewManager_FullUpdate;
3217
+ Emit(FI('removed', K));
3218
+ SL.Free;
3219
+ end;
3220
+
3221
+ procedure PcbDesignator(Des: String; Show: Boolean; X, Y, Height, Rot: Integer);
3222
+ var
3223
+ C : IPCB_Component;
3224
+ begin
3225
+ if not Ok then Exit;
3226
+ C := FindPcbComp(Des);
3227
+ if C = nil then begin Fail('no component ' + Des + ' on the board'); Exit; end;
3228
+ PCBServer.PreProcess;
3229
+ PCBServer.SendMessageToRobots(C.I_ObjectAddress, c_Broadcast, PCBM_BeginModify, c_NoEventData);
3230
+ C.NameOn := Show;
3231
+ if Show then
3232
+ begin
3233
+ C.NameAutoPosition := eAutoPos_Manual;
3234
+ C.Name.Size := Height;
3235
+ C.Name.Width := Height div 7;
3236
+ C.Name.Rotation := Rot;
3237
+ C.Name.MoveToXY(CurBoard.XOrigin + X, CurBoard.YOrigin + Y);
3238
+ end;
3239
+ PCBServer.SendMessageToRobots(C.I_ObjectAddress, c_Broadcast, PCBM_EndModify, c_NoEventData);
3240
+ PCBServer.PostProcess;
3241
+ Emit(F('designator', Des) + FB('shown', C.NameOn));
3242
+ end;
3243
+
3244
+ procedure PcbAddLayer(Layer: Integer);
3245
+ var
3246
+ LS : IPCB_LayerStack_V7;
3247
+ LO : IPCB_LayerObject_V7;
3248
+ Found : Boolean;
3249
+ begin
3250
+ if not Ok then Exit;
3251
+ LS := CurBoard.LayerStack_V7;
3252
+ Found := False;
3253
+ LO := LS.FirstLayer;
3254
+ while LO <> nil do
3255
+ begin
3256
+ if LO.LayerID = Layer then Found := True;
3257
+ LO := LS.NextLayer(LO);
3258
+ end;
3259
+ if not Found then LS.InsertLayer(Layer);
3260
+ CurBoard.ViewManager_FullUpdate;
3261
+ Emit(FI('layer', Layer) + FB('added', not Found));
3262
+ end;
3263
+
3264
+ procedure PcbLayers;
3265
+ var
3266
+ LS : IPCB_LayerStack_V7;
3267
+ LO : IPCB_LayerObject_V7;
3268
+ Next : IPCB_LayerObject_V7;
3269
+ begin
3270
+ if not Ok then Exit;
3271
+ LS := CurBoard.LayerStack_V7;
3272
+ LO := LS.FirstLayer;
3273
+ while LO <> nil do
3274
+ begin
3275
+ { The dielectric below a layer counts only between copper layers: Altium reports one under
3276
+ the bottom layer too, but leaves it out of the board thickness. }
3277
+ Next := LS.NextLayer(LO);
3278
+ if (Next <> nil) and (LO.Dielectric <> nil) then
3279
+ Emit(F('layer', LO.Name) + FI('id', LO.LayerID) + FI('copper', LO.CopperThickness)
3280
+ + FI('dielectric', LO.Dielectric.DielectricHeight))
3281
+ else
3282
+ Emit(F('layer', LO.Name) + FI('id', LO.LayerID) + FI('copper', LO.CopperThickness));
3283
+ LO := Next;
3284
+ end;
3285
+ end;
3286
+
3287
+ { ------------------------------------------------------------------ outputs --- }
3288
+
3289
+ procedure SaveProjectFile(PrjPath: String);
3290
+ var
3291
+ SD : IServerDocument;
3292
+ begin
3293
+ { Binding an OutJob changes the project file while its Modified flag may read False: save that
3294
+ file. The design documents are not saved. Saving every loaded document after a run rewrote
3295
+ the PCB, schematics, libraries, drawings and BOM of an Altium 365 working copy with new GUIDs,
3296
+ timestamps and prices, and the same design. The "Unsaved Changes" prompt a run can leave is
3297
+ answered "Don't Save" by the drive, which refuses a run while the project holds unsaved edits. }
3298
+ SD := Client.GetDocumentByPath(PrjPath);
3299
+ if SD <> nil then begin SD.SetModified(True); SD.DoFileSave(''); end;
3300
+ end;
3301
+
3302
+ procedure OutJobRun(OutJob, PrjPath, Medium: String);
3303
+ var
3304
+ SD : IServerDocument;
3305
+ begin
3306
+ if not Ok then Exit;
3307
+ { Adding the OutJob modifies the project; save it before anything closes, or Altium asks. }
3308
+ if PrjPath <> '' then
3309
+ begin
3310
+ BindToProject(OutJob, PrjPath);
3311
+ if not Ok then Exit; { the project is not open: generate nothing }
3312
+ SaveProjectFile(PrjPath);
3313
+ end;
3314
+ { The OutJob file on disk is the drive's: close an open copy without saving, so Altium loads the
3315
+ file as written. Altium's own save rewrote it with defaults, absolute paths and another section
3316
+ order on every run, a working-copy change each time. }
3317
+ CloseDocPath(OutJob, False);
3318
+ SD := ShowDoc('OUTPUTJOB', OutJob);
3319
+ if SD = nil then begin Fail('cannot open OutJob ' + OutJob); Exit; end;
3320
+ ResetParameters;
3321
+ if Copy(Medium, 1, 3) = 'PDF' then
3322
+ begin
3323
+ { A Publish medium (named PDF...) prints rather than generates; no dialog, no overwrite prompt. }
3324
+ AddStringParameter('Action', 'PublishToPDF');
3325
+ AddStringParameter('OutputMedium', Medium);
3326
+ AddStringParameter('ObjectKind', 'OutputBatch');
3327
+ AddStringParameter('DisableDialog', 'True');
3328
+ AddStringParameter('PromptOverwrite', 'FALSE');
3329
+ RunProcess('WorkspaceManager:Print');
3330
+ end
3331
+ else
3332
+ begin
3333
+ AddStringParameter('Action', 'Run');
3334
+ AddStringParameter('OutputMedium', Medium);
3335
+ AddStringParameter('ObjectKind', 'OutputBatch');
3336
+ RunProcess('WorkspaceManager:GenerateReport');
3337
+ end;
3338
+ { Leave no OutJob editor in front: a later screenshot of a schematic or PCB would catch it. }
3339
+ if PrjPath <> '' then SaveProjectFile(PrjPath);
3340
+ CloseDocPath(OutJob, False);
3341
+ Emit(F('outjob', OutJob) + F('medium', Medium));
3342
+ end;