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.
- enginehall-2.0.0.dist-info/METADATA +511 -0
- enginehall-2.0.0.dist-info/RECORD +75 -0
- enginehall-2.0.0.dist-info/WHEEL +4 -0
- enginehall-2.0.0.dist-info/entry_points.txt +4 -0
- enginehall-2.0.0.dist-info/licenses/LICENSE +51 -0
- pcba/THIRD_PARTY_NOTICES.md +72 -0
- pcba/__init__.py +6 -0
- pcba/adapters/__init__.py +10 -0
- pcba/adapters/altium_bridge/MANIFEST.json +5 -0
- pcba/adapters/altium_bridge/__init__.py +1 -0
- pcba/adapters/altium_bridge/bridge.pas +3342 -0
- pcba/adapters/altium_drive.py +1160 -0
- pcba/adapters/altium_house.py +591 -0
- pcba/adapters/altium_ops.py +858 -0
- pcba/adapters/altium_outjob.py +247 -0
- pcba/adapters/altium_uia.py +304 -0
- pcba/adapters/altium_vcs.py +197 -0
- pcba/adapters/altium_win32.py +457 -0
- pcba/adapters/cubemx_drive.py +248 -0
- pcba/baseline.py +151 -0
- pcba/changes.py +81 -0
- pcba/cli.py +891 -0
- pcba/components.py +323 -0
- pcba/config.py +180 -0
- pcba/data/components/generic.json +61 -0
- pcba/discover.py +350 -0
- pcba/doctor.py +131 -0
- pcba/house.py +123 -0
- pcba/mcp/__init__.py +5 -0
- pcba/mcp/altium_server.py +1358 -0
- pcba/mcp/protocol.py +224 -0
- pcba/mcp/server.py +332 -0
- pcba/model/__init__.py +38 -0
- pcba/model/design.py +232 -0
- pcba/model/finding.py +148 -0
- pcba/model/pinmap.py +200 -0
- pcba/parsers/__init__.py +0 -0
- pcba/parsers/altium/__init__.py +37 -0
- pcba/parsers/altium/_sheet.py +293 -0
- pcba/parsers/altium/geometry.py +103 -0
- pcba/parsers/altium/netlist.py +302 -0
- pcba/parsers/altium/pinmap.py +336 -0
- pcba/parsers/altium/project.py +249 -0
- pcba/parsers/altium/records.py +227 -0
- pcba/parsers/firmware/__init__.py +5 -0
- pcba/parsers/firmware/index.py +250 -0
- pcba/parsers/ioc/__init__.py +49 -0
- pcba/parsers/ioc/mcu_db.py +180 -0
- pcba/parsers/ioc/reader.py +182 -0
- pcba/parsers/ioc/validate.py +192 -0
- pcba/parsers/kicad/__init__.py +103 -0
- pcba/parsers/kicad/board.py +269 -0
- pcba/parsers/kicad/kicad_cli.py +190 -0
- pcba/parsers/kicad/pinmap.py +90 -0
- pcba/parsers/kicad/schematic.py +517 -0
- pcba/parsers/kicad/sexpr.py +136 -0
- pcba/publish.py +269 -0
- pcba/report/__init__.py +7 -0
- pcba/report/json_report.py +39 -0
- pcba/report/sarif.py +128 -0
- pcba/report/text.py +304 -0
- pcba/requirements.py +320 -0
- pcba/review.py +177 -0
- pcba/rules/__init__.py +312 -0
- pcba/rules/cross_domain.py +496 -0
- pcba/rules/kicad_board.py +775 -0
- pcba/rules/kicad_common.py +167 -0
- pcba/rules/kicad_components.py +603 -0
- pcba/rules/kicad_schematic.py +621 -0
- pcba/rules/locate.py +22 -0
- pcba/rules/schematic.py +123 -0
- pcba/rules/signals.py +149 -0
- pcba/rules/spice.py +59 -0
- pcba/rules/stm32.py +155 -0
- 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;
|