ecadlib 0.3.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.
- ecadlib/__init__.py +3 -0
- ecadlib/__main__.py +5 -0
- ecadlib/a365_sync.py +637 -0
- ecadlib/agent.py +1407 -0
- ecadlib/agent_queue.py +780 -0
- ecadlib/agent_watch.py +732 -0
- ecadlib/altium365.py +385 -0
- ecadlib/altium_dblib_sync.py +530 -0
- ecadlib/altium_export.py +209 -0
- ecadlib/altium_forms.py +768 -0
- ecadlib/altium_lib.py +1089 -0
- ecadlib/altium_menu.py +1751 -0
- ecadlib/altium_pkg.py +396 -0
- ecadlib/altium_project.py +1095 -0
- ecadlib/app.py +1458 -0
- ecadlib/approvals.py +395 -0
- ecadlib/assets/demo_bom.csv +8 -0
- ecadlib/assets/kicad_action_plugin.py +607 -0
- ecadlib/assets/kicad_reread.py +60 -0
- ecadlib/assets/pcblib_template.json.gz +0 -0
- ecadlib/bom_inputs.py +339 -0
- ecadlib/bom_triage.py +519 -0
- ecadlib/cad_model.py +528 -0
- ecadlib/cfb.py +171 -0
- ecadlib/cli.py +185 -0
- ecadlib/clihelp.py +149 -0
- ecadlib/cliutil.py +119 -0
- ecadlib/completion.py +173 -0
- ecadlib/costs.py +276 -0
- ecadlib/crosscheck.py +412 -0
- ecadlib/datasheet.py +2168 -0
- ecadlib/dedupe.py +572 -0
- ecadlib/detect.py +170 -0
- ecadlib/doctor.py +450 -0
- ecadlib/ds_route.py +541 -0
- ecadlib/eagle_export.py +1409 -0
- ecadlib/easy_setup.py +338 -0
- ecadlib/easyeda_convert.py +386 -0
- ecadlib/easyeda_pro_export.py +972 -0
- ecadlib/eval/__init__.py +0 -0
- ecadlib/eval/baselines/kicad-v1.json +404 -0
- ecadlib/eval/build_set.py +299 -0
- ecadlib/eval/cli.py +119 -0
- ecadlib/eval/gate.py +358 -0
- ecadlib/eval/golden/demo.txt +379 -0
- ecadlib/eval/golden/specs.txt +3120 -0
- ecadlib/eval/golden/testpack.txt +463 -0
- ecadlib/eval/golden.py +446 -0
- ecadlib/eval/replay/kicad-v1.json.gz +0 -0
- ecadlib/eval/runner.py +456 -0
- ecadlib/eval/score.py +179 -0
- ecadlib/eval/sets/kicad-v1.json +630 -0
- ecadlib/firstrun.py +400 -0
- ecadlib/footprint_adv.py +1006 -0
- ecadlib/footprint_bench.py +218 -0
- ecadlib/footprint_check.py +562 -0
- ecadlib/footprint_gen.py +582 -0
- ecadlib/footprint_rules.py +1092 -0
- ecadlib/forge.py +267 -0
- ecadlib/fp_share.py +95 -0
- ecadlib/fp_tidy.py +389 -0
- ecadlib/fsutil.py +62 -0
- ecadlib/house_style.py +1051 -0
- ecadlib/httpcache.py +124 -0
- ecadlib/hub.py +1490 -0
- ecadlib/hub_copy.py +161 -0
- ecadlib/hub_tools.py +79 -0
- ecadlib/kicad_dblib.py +257 -0
- ecadlib/kicad_export.py +837 -0
- ecadlib/kicad_import.py +913 -0
- ecadlib/kicad_index.py +160 -0
- ecadlib/kicad_menu.py +193 -0
- ecadlib/kicad_pcm.py +475 -0
- ecadlib/launcher.py +156 -0
- ecadlib/librarian.py +963 -0
- ecadlib/lifecycle.py +691 -0
- ecadlib/llm/__init__.py +276 -0
- ecadlib/llm/cli.py +70 -0
- ecadlib/llm/openai_compat.py +214 -0
- ecadlib/llm/profiles.py +245 -0
- ecadlib/llm/rails.py +570 -0
- ecadlib/llm/run.py +126 -0
- ecadlib/llm/sampling.py +49 -0
- ecadlib/mcp_server.py +723 -0
- ecadlib/messages.py +419 -0
- ecadlib/migrate.py +407 -0
- ecadlib/notify.py +329 -0
- ecadlib/package_schema.json +1238 -0
- ecadlib/package_specs/CP_EIA-3216-18_Kemet-A.json +27 -0
- ecadlib/package_specs/C_0402_1005Metric.json +22 -0
- ecadlib/package_specs/C_0805_2012Metric.json +22 -0
- ecadlib/package_specs/DFN-8-1EP_3x3mm_P0.5mm_EP1.66x2.38mm.json +40 -0
- ecadlib/package_specs/D_SMA.json +27 -0
- ecadlib/package_specs/D_SMB.json +27 -0
- ecadlib/package_specs/D_SOD-123.json +43 -0
- ecadlib/package_specs/D_SOD-323.json +43 -0
- ecadlib/package_specs/LED_0603_1608Metric.json +23 -0
- ecadlib/package_specs/LED_0805_2012Metric.json +23 -0
- ecadlib/package_specs/LQFP-48_7x7mm_P0.5mm.json +31 -0
- ecadlib/package_specs/L_0805_2012Metric.json +22 -0
- ecadlib/package_specs/MSOP-8_3x3mm_P0.65mm.json +31 -0
- ecadlib/package_specs/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm.json +39 -0
- ecadlib/package_specs/QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm.json +37 -0
- ecadlib/package_specs/R_0402_1005Metric.json +22 -0
- ecadlib/package_specs/R_0603_1608Metric.json +22 -0
- ecadlib/package_specs/R_0805_2012Metric.json +22 -0
- ecadlib/package_specs/R_1206_3216Metric.json +22 -0
- ecadlib/package_specs/SOIC-16_3.9x9.9mm_P1.27mm.json +32 -0
- ecadlib/package_specs/SOIC-8_3.9x4.9mm_P1.27mm.json +32 -0
- ecadlib/package_specs/SOT-223-3_TabPin2.json +56 -0
- ecadlib/package_specs/SOT-23-5.json +57 -0
- ecadlib/package_specs/SOT-23-6.json +62 -0
- ecadlib/package_specs/SOT-23.json +48 -0
- ecadlib/package_specs/TSSOP-16_4.4x5mm_P0.65mm.json +31 -0
- ecadlib/package_specs/VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm.json +39 -0
- ecadlib/package_specs/advanced/BGA-100_11.0x11.0mm_Layout10x10_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-121_9.0x9.0mm_Layout11x11_P0.8mm_Ball0.4mm_Pad0.35mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-144_7.0x7.0mm_Layout13x13_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +29 -0
- ecadlib/package_specs/advanced/BGA-169_11.0x11.0mm_Layout13x13_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +23 -0
- ecadlib/package_specs/advanced/BGA-256_11.0x11.0mm_Layout20x20_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-256_17.0x17.0mm_Layout16x16_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-36_3.396x3.466mm_Layout6x6_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD.json +24 -0
- ecadlib/package_specs/advanced/BGA-672_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD.json +32 -0
- ecadlib/package_specs/advanced/BGA-68_5.0x5.0mm_Layout9x9_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +30 -0
- ecadlib/package_specs/advanced/BGA-90_8.0x13.0mm_Layout2x3x15_P0.8mm.json +26 -0
- ecadlib/package_specs/advanced/DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias.json +60 -0
- ecadlib/package_specs/advanced/Example_Custom_LPad_Slot.json +24 -0
- ecadlib/package_specs/advanced/JST_GH_SM04B-GHS-TB_1x04-1MP_P1.25mm_Horizontal.json +49 -0
- ecadlib/package_specs/advanced/JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical.json +33 -0
- ecadlib/package_specs/advanced/JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal.json +55 -0
- ecadlib/package_specs/advanced/JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal.json +49 -0
- ecadlib/package_specs/advanced/JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/JST_XH_B6B-XH-A_1x06_P2.50mm_Vertical.json +33 -0
- ecadlib/package_specs/advanced/LFBGA-144_10x10mm_Layout12x12_P0.8mm.json +23 -0
- ecadlib/package_specs/advanced/Molex_KK-254_AE-6410-04A_1x04_P2.54mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/Molex_PicoBlade_53398-0471_1x04-1MP_P1.25mm_Vertical.json +54 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Left.json +25 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Right.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x10_P1.00mm_Vertical.json +15 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.00mm_Vertical_SMD.json +13 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical.json +18 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical_SMD.json +24 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.00mm_Vertical_SMD.json +13 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical_SMD.json +24 -0
- ecadlib/package_specs/advanced/PinHeader_2x20_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias.json +87 -0
- ecadlib/package_specs/advanced/UFBGA-132_7x7mm_Layout12x12_P0.5mm.json +43 -0
- ecadlib/package_specs/advanced/USB_C_Receptacle_HRO_TYPE-C-31-M-12.json +218 -0
- ecadlib/package_specs/standard.json.gz +0 -0
- ecadlib/parity.py +1204 -0
- ecadlib/parts_export.py +154 -0
- ecadlib/partview.py +215 -0
- ecadlib/plugins.py +231 -0
- ecadlib/providers.py +226 -0
- ecadlib/rebuild.py +471 -0
- ecadlib/release_check.py +437 -0
- ecadlib/reports.py +274 -0
- ecadlib/resolve.py +556 -0
- ecadlib/review_cli.py +292 -0
- ecadlib/rules.py +979 -0
- ecadlib/rules_infer.py +1205 -0
- ecadlib/sexpr.py +149 -0
- ecadlib/sheetsafe.py +65 -0
- ecadlib/sim_models.py +1087 -0
- ecadlib/sourcing.py +1161 -0
- ecadlib/stdparts.py +755 -0
- ecadlib/step_gen.py +929 -0
- ecadlib/symbol_layout.py +798 -0
- ecadlib/team.py +558 -0
- ecadlib/team_setup.py +288 -0
- ecadlib/term.py +420 -0
- ecadlib/term_views.py +64 -0
- ecadlib/testpack.py +334 -0
- ecadlib/ui/__init__.py +1 -0
- ecadlib/ui/__main__.py +3 -0
- ecadlib/ui/server.py +1029 -0
- ecadlib/ui/static/hairline.js +2 -0
- ecadlib/ui/static/index.html +2098 -0
- ecadlib/ui/static/setup.html +294 -0
- ecadlib/upkeep.py +717 -0
- ecadlib/upstream.py +573 -0
- ecadlib/userconfig.py +257 -0
- ecadlib/visual_check.py +1603 -0
- ecadlib/ws_status.py +203 -0
- ecadlib-0.3.0.dist-info/METADATA +2245 -0
- ecadlib-0.3.0.dist-info/RECORD +199 -0
- ecadlib-0.3.0.dist-info/WHEEL +5 -0
- ecadlib-0.3.0.dist-info/entry_points.txt +2 -0
- ecadlib-0.3.0.dist-info/licenses/LICENSE +21 -0
- ecadlib-0.3.0.dist-info/licenses/NOTICE +53 -0
- ecadlib-0.3.0.dist-info/top_level.txt +1 -0
ecadlib/altium_forms.py
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
"""The ecadlib dialog inside Altium Designer: a DelphiScript form (.pas + .dfm) the menu's entries open.
|
|
2
|
+
|
|
3
|
+
It looks like Altium's own dark dialogs and does the work in place, as the landing page's workflow shows it:
|
|
4
|
+
|
|
5
|
+
ecadlib . Check project BOM a big count ("24 lines"), one row per kind (already in your library, a source can
|
|
6
|
+
supply, needs a footprint), a progress line, then Later / Fill the library now
|
|
7
|
+
ecadlib . Add part from MPN MPN + manufacturer, then the lookup live (where ecadlib looked, what it took, the
|
|
8
|
+
release check), then Done; the Components panel shows the part after the reinstall
|
|
9
|
+
ecadlib . Fill the library the librarian's progress part by part, then ready / held counts
|
|
10
|
+
|
|
11
|
+
The dialog never waits in a loop: a TTimer (every 250 ms) reads <job>/progress.ini while ecadlib runs and
|
|
12
|
+
<job>/result.ini when exit.txt appears, so Altium keeps painting. Only a step that needs a browser opens one: the
|
|
13
|
+
report of a BOM check (on request) and the review queue for a held part. Everything the dialog needs from the menu
|
|
14
|
+
unit (StartJob, Ask, Answer, CommandLine, Reinstall, Setting...) is called across units, as Altium script projects
|
|
15
|
+
allow (Altium-Designer-addons OpenLib calls its libUtils unit the same way).
|
|
16
|
+
|
|
17
|
+
Event handlers are named in the .dfm (OnClick = PrimaryClick): DelphiScript cannot assign one to a control in code
|
|
18
|
+
(Altium-Designer-addons ReturnViaCheck.pas: "CheckBox.OnClick := ... throws error")."""
|
|
19
|
+
from . import __version__
|
|
20
|
+
|
|
21
|
+
DIALOG_UNIT, DIALOG_FORM = "ecadlib_dialog.pas", "ecadlib_dialog.dfm"
|
|
22
|
+
ROWS = 7
|
|
23
|
+
WIDTH = 420
|
|
24
|
+
LABEL_W = 190 # the rows' left column; the value column gets the rest (Consolas: about 7 px a character)
|
|
25
|
+
|
|
26
|
+
# Altium's dark UI, as Delphi TColor values ($00BBGGRR)
|
|
27
|
+
BG, HEAD, TEXT, DIM, RULE = "$002E2A2A", "$00242121", "$00E8E8E8", "$009C9C9C", "$00433D3D"
|
|
28
|
+
BLUE, BLUE_OFF, GREEN, RED, BTN, FIELD = "$00D9772F", "$00705A4A", "$005AB85C", "$006060E0", "$00443E3E", "$00201D1D"
|
|
29
|
+
|
|
30
|
+
# Every identifier the dialog's .pas uses besides its own routines, the menu unit's and the .dfm's component names:
|
|
31
|
+
# VCL properties, methods and constants of the script form components (Altium "Forms & Components":
|
|
32
|
+
# altium.com/documentation/altium-designer/scripting/delphiscript/forms-components; TR0120 p10-11).
|
|
33
|
+
FORM_API = {
|
|
34
|
+
"Caption": "TLabel / TPanel / TForm caption", "Visible": "TControl.Visible", "Top": "TControl.Top",
|
|
35
|
+
"Left": "TControl.Left", "Width": "TControl.Width", "Height": "TControl.Height", "ClientHeight": "TForm.ClientHeight",
|
|
36
|
+
"Font": "TControl.Font", "Color": "TControl.Color / TFont.Color", "Text": "TEdit.Text", "Enabled": "TTimer.Enabled",
|
|
37
|
+
"Interval": "TTimer.Interval", "ModalResult": "TForm.ModalResult (Forms & Components: ShowModal example)",
|
|
38
|
+
"ShowModal": "TForm.ShowModal (Forms & Components)", "mrOk": "TModalResult", "mrCancel": "TModalResult",
|
|
39
|
+
"SetFocus": "TWinControl.SetFocus", "SelectAll": "TEdit.SelectAll",
|
|
40
|
+
"EcadlibDialog": "the form instance a script form unit gets (named after the .dfm's object)",
|
|
41
|
+
"Sender": "event handler parameter", "TObject": "event handler parameter type", "Style": "TFont.Style",
|
|
42
|
+
"ActiveControl": "TForm.ActiveControl", "AutoSize": "TLabel.AutoSize", "TEdit": "Forms & Components: TEdit",
|
|
43
|
+
"PasteFromClipboard": "TCustomEdit.PasteFromClipboard", "CopyToClipboard": "TCustomEdit.CopyToClipboard",
|
|
44
|
+
"CutToClipboard": "TCustomEdit.CutToClipboard", "Word": "OnKeyDown Key type", "TShiftState": "OnKeyDown Shift type",
|
|
45
|
+
"Key": "OnKeyDown parameter", "TMouseButton": "OnMouseDown / OnMouseUp Button type", "InSet": "DelphiScript set test (InSet(ssCtrl, Shift))", "ssCtrl": "TShiftState", "ssShift": "TShiftState",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dlg_pas():
|
|
50
|
+
rows_set = "\n".join(f""" If i = {i} Then
|
|
51
|
+
Begin
|
|
52
|
+
EcadlibDialog.Row{i}A.Caption := A;
|
|
53
|
+
EcadlibDialog.Row{i}B.Caption := B;
|
|
54
|
+
EcadlibDialog.Row{i}B.Font.Color := C;
|
|
55
|
+
EcadlibDialog.Row{i}.Visible := V;
|
|
56
|
+
End;""" for i in range(1, ROWS + 1))
|
|
57
|
+
rows_top = "\n".join(f""" If EcadlibDialog.Row{i}.Visible Then
|
|
58
|
+
Begin
|
|
59
|
+
EcadlibDialog.Row{i}.Top := Y;
|
|
60
|
+
Y := Y + 26;
|
|
61
|
+
End;""" for i in range(1, ROWS + 1))
|
|
62
|
+
return r"""{ ecadlib dialog for Altium Designer (written by `ecadlib altium-menu build`, ecadlib @VERSION@).
|
|
63
|
+
|
|
64
|
+
One dark dialog for every menu entry, shown with OpenDialog(Mode, Project, MPN):
|
|
65
|
+
Mode 'add' ask for an MPN (unless one is given), build it, show where it came from, reinstall the library
|
|
66
|
+
Mode 'bom' check the project's BOM, show the counts, offer to fill the library, fill it
|
|
67
|
+
Mode 'fill' fill the library from the project's BOM straight away
|
|
68
|
+
A timer polls the job folder (progress.ini while ecadlib runs, result.ini at the end), so Altium never freezes. }
|
|
69
|
+
|
|
70
|
+
Var
|
|
71
|
+
Mode : String;
|
|
72
|
+
Phase : String;
|
|
73
|
+
Job : String;
|
|
74
|
+
JobAction : String;
|
|
75
|
+
Project : String;
|
|
76
|
+
Title : String;
|
|
77
|
+
Second : String;
|
|
78
|
+
Waited : Integer;
|
|
79
|
+
Shown : Integer;
|
|
80
|
+
Total : Integer;
|
|
81
|
+
Swing : Integer;
|
|
82
|
+
Dragging : Boolean;
|
|
83
|
+
DragX : Integer;
|
|
84
|
+
DragY : Integer;
|
|
85
|
+
|
|
86
|
+
Procedure SetRow(i : Integer; Spec : String);
|
|
87
|
+
{ Spec is "label|value|ok" (ok = 1: the value in green); '' hides the row. }
|
|
88
|
+
Var
|
|
89
|
+
A : String;
|
|
90
|
+
B : String;
|
|
91
|
+
C : Integer;
|
|
92
|
+
V : Boolean;
|
|
93
|
+
k : Integer;
|
|
94
|
+
Begin
|
|
95
|
+
V := Spec <> '';
|
|
96
|
+
A := Spec;
|
|
97
|
+
B := '';
|
|
98
|
+
C := @TEXT@;
|
|
99
|
+
k := Pos('|', A);
|
|
100
|
+
If k > 0 Then
|
|
101
|
+
Begin
|
|
102
|
+
B := Copy(A, k + 1, Length(A));
|
|
103
|
+
A := Copy(A, 1, k - 1);
|
|
104
|
+
k := Pos('|', B);
|
|
105
|
+
If k > 0 Then
|
|
106
|
+
Begin
|
|
107
|
+
If Copy(B, k + 1, Length(B)) = '1' Then
|
|
108
|
+
C := @GREEN@;
|
|
109
|
+
B := Copy(B, 1, k - 1);
|
|
110
|
+
End;
|
|
111
|
+
End;
|
|
112
|
+
@ROWS_SET@
|
|
113
|
+
End;
|
|
114
|
+
|
|
115
|
+
Procedure ClearRows;
|
|
116
|
+
Var
|
|
117
|
+
i : Integer;
|
|
118
|
+
Begin
|
|
119
|
+
For i := 1 To @ROWS@ Do
|
|
120
|
+
SetRow(i, '');
|
|
121
|
+
End;
|
|
122
|
+
|
|
123
|
+
Function ButtonWidth(S : String) : Integer;
|
|
124
|
+
Begin
|
|
125
|
+
Result := Length(S) * 7 + 30;
|
|
126
|
+
End;
|
|
127
|
+
|
|
128
|
+
Procedure Buttons(Primary : String; Secondary : String; Busy : Boolean);
|
|
129
|
+
Begin
|
|
130
|
+
EcadlibDialog.Primary.Caption := Primary;
|
|
131
|
+
EcadlibDialog.Primary.Width := ButtonWidth(Primary);
|
|
132
|
+
EcadlibDialog.Primary.Left := @WIDTH@ - 16 - EcadlibDialog.Primary.Width;
|
|
133
|
+
If Busy Then
|
|
134
|
+
EcadlibDialog.Primary.Color := @BLUE_OFF@
|
|
135
|
+
Else
|
|
136
|
+
EcadlibDialog.Primary.Color := @BLUE@;
|
|
137
|
+
EcadlibDialog.Secondary.Caption := Secondary;
|
|
138
|
+
EcadlibDialog.Secondary.Visible := Secondary <> '';
|
|
139
|
+
EcadlibDialog.Secondary.Width := ButtonWidth(Secondary);
|
|
140
|
+
EcadlibDialog.Secondary.Left := EcadlibDialog.Primary.Left - 8 - EcadlibDialog.Secondary.Width;
|
|
141
|
+
End;
|
|
142
|
+
|
|
143
|
+
Procedure PasteInto(Box : TEdit);
|
|
144
|
+
{ Altium takes Ctrl+V / Shift+Insert as its own paste, so the boxes paste here. }
|
|
145
|
+
Begin
|
|
146
|
+
Box.PasteFromClipboard;
|
|
147
|
+
End;
|
|
148
|
+
|
|
149
|
+
Function LooksLikeMpn(S : String) : Boolean;
|
|
150
|
+
{ One word of 3 to 40 characters with a digit in it: a part number, not a sentence or a path someone copied. }
|
|
151
|
+
Var
|
|
152
|
+
i : Integer;
|
|
153
|
+
Begin
|
|
154
|
+
Result := False;
|
|
155
|
+
If (Length(S) < 3) Or (Length(S) > 40) Then Exit;
|
|
156
|
+
If (Pos(' ', S) > 0) Or (Pos(#13, S) > 0) Or (Pos(#10, S) > 0) Or (Pos(#9, S) > 0) Or (Pos('\', S) > 0) Then Exit;
|
|
157
|
+
For i := 1 To Length(S) Do
|
|
158
|
+
If (S[i] >= '0') And (S[i] <= '9') Then
|
|
159
|
+
Result := True;
|
|
160
|
+
End;
|
|
161
|
+
|
|
162
|
+
Procedure ClipboardMpn;
|
|
163
|
+
{ A part number on the clipboard goes into the MPN box, selected, so typing replaces it. }
|
|
164
|
+
Begin
|
|
165
|
+
PasteInto(EcadlibDialog.EditMpn);
|
|
166
|
+
EcadlibDialog.EditMpn.Text := Trim(EcadlibDialog.EditMpn.Text);
|
|
167
|
+
If LooksLikeMpn(EcadlibDialog.EditMpn.Text) Then
|
|
168
|
+
EcadlibDialog.EditMpn.SelectAll
|
|
169
|
+
Else
|
|
170
|
+
EcadlibDialog.EditMpn.Text := '';
|
|
171
|
+
End;
|
|
172
|
+
|
|
173
|
+
Function NoteLines(S : String) : Integer;
|
|
174
|
+
{ How many lines the note wraps to (a word-wrapped TLabel with AutoSize would shrink to its first word). }
|
|
175
|
+
Var
|
|
176
|
+
k : Integer;
|
|
177
|
+
Begin
|
|
178
|
+
Result := 1;
|
|
179
|
+
k := Pos(#10, S);
|
|
180
|
+
While k > 0 Do
|
|
181
|
+
Begin
|
|
182
|
+
Result := Result + 1 + ((k * 7) Div @NOTE_W@);
|
|
183
|
+
S := Copy(S, k + 1, Length(S));
|
|
184
|
+
k := Pos(#10, S);
|
|
185
|
+
End;
|
|
186
|
+
Result := Result + ((Length(S) * 7) Div @NOTE_W@);
|
|
187
|
+
End;
|
|
188
|
+
|
|
189
|
+
Procedure Layout;
|
|
190
|
+
Var
|
|
191
|
+
Y : Integer;
|
|
192
|
+
Begin
|
|
193
|
+
Y := 46;
|
|
194
|
+
If EcadlibDialog.AskBox.Visible Then
|
|
195
|
+
Begin
|
|
196
|
+
EcadlibDialog.AskBox.Top := Y;
|
|
197
|
+
Y := Y + EcadlibDialog.AskBox.Height + 4;
|
|
198
|
+
End;
|
|
199
|
+
If EcadlibDialog.Big.Visible Then
|
|
200
|
+
Begin
|
|
201
|
+
EcadlibDialog.Big.Top := Y;
|
|
202
|
+
Y := Y + 40;
|
|
203
|
+
End;
|
|
204
|
+
@ROWS_TOP@
|
|
205
|
+
If EcadlibDialog.Status.Visible Then
|
|
206
|
+
Begin
|
|
207
|
+
EcadlibDialog.Status.Top := Y + 4;
|
|
208
|
+
Y := Y + 24;
|
|
209
|
+
End;
|
|
210
|
+
If EcadlibDialog.MeterTrack.Visible Then
|
|
211
|
+
Begin
|
|
212
|
+
EcadlibDialog.MeterTrack.Top := Y + 4;
|
|
213
|
+
Y := Y + 12;
|
|
214
|
+
End;
|
|
215
|
+
If EcadlibDialog.Note.Visible Then
|
|
216
|
+
Begin
|
|
217
|
+
EcadlibDialog.Note.AutoSize := False; { a word-wrapped label left to size itself shrinks to a column }
|
|
218
|
+
EcadlibDialog.Note.Width := @NOTE_W@;
|
|
219
|
+
EcadlibDialog.Note.Height := NoteLines(EcadlibDialog.Note.Caption) * 17;
|
|
220
|
+
EcadlibDialog.Note.Top := Y + 6;
|
|
221
|
+
Y := Y + EcadlibDialog.Note.Height + 10;
|
|
222
|
+
End;
|
|
223
|
+
If EcadlibDialog.Report.Visible Then
|
|
224
|
+
Begin
|
|
225
|
+
EcadlibDialog.Report.Top := Y + 2;
|
|
226
|
+
Y := Y + 22;
|
|
227
|
+
End;
|
|
228
|
+
EcadlibDialog.Primary.Top := Y + 10;
|
|
229
|
+
EcadlibDialog.Secondary.Top := Y + 10;
|
|
230
|
+
EcadlibDialog.ClientHeight := Y + 10 + 30 + 16;
|
|
231
|
+
End;
|
|
232
|
+
|
|
233
|
+
Procedure Meter(Done : Integer; Count : Integer);
|
|
234
|
+
{ Count = 0: ecadlib does not know how far it is; a short bar swings along the track. }
|
|
235
|
+
Begin
|
|
236
|
+
EcadlibDialog.MeterTrack.Visible := True;
|
|
237
|
+
If Count > 0 Then
|
|
238
|
+
Begin
|
|
239
|
+
EcadlibDialog.MeterFill.Left := 0;
|
|
240
|
+
EcadlibDialog.MeterFill.Width := (EcadlibDialog.MeterTrack.Width * Done) Div Count;
|
|
241
|
+
End
|
|
242
|
+
Else
|
|
243
|
+
Begin
|
|
244
|
+
Swing := Swing + 16;
|
|
245
|
+
If Swing > EcadlibDialog.MeterTrack.Width Then
|
|
246
|
+
Swing := -60;
|
|
247
|
+
EcadlibDialog.MeterFill.Left := Swing;
|
|
248
|
+
EcadlibDialog.MeterFill.Width := 60;
|
|
249
|
+
End;
|
|
250
|
+
End;
|
|
251
|
+
|
|
252
|
+
Procedure SetTitle(S : String);
|
|
253
|
+
Begin
|
|
254
|
+
Title := S;
|
|
255
|
+
EcadlibDialog.HeadTitle.Caption := 'ecadlib ' + #183 + ' ' + S;
|
|
256
|
+
End;
|
|
257
|
+
|
|
258
|
+
Procedure Fail(Problem : String);
|
|
259
|
+
Begin
|
|
260
|
+
Phase := 'done';
|
|
261
|
+
Second := 'log';
|
|
262
|
+
EcadlibDialog.Big.Caption := 'Not done';
|
|
263
|
+
EcadlibDialog.Status.Visible := False;
|
|
264
|
+
EcadlibDialog.MeterTrack.Visible := False;
|
|
265
|
+
EcadlibDialog.Note.Caption := Problem;
|
|
266
|
+
EcadlibDialog.Note.Font.Color := @RED@;
|
|
267
|
+
EcadlibDialog.Note.Visible := True;
|
|
268
|
+
If Job = '' Then
|
|
269
|
+
Buttons('Close', '', False)
|
|
270
|
+
Else
|
|
271
|
+
Buttons('Close', 'Open log', False);
|
|
272
|
+
Layout;
|
|
273
|
+
End;
|
|
274
|
+
|
|
275
|
+
Procedure Run(Act : String);
|
|
276
|
+
{ Starts the job Job (request.ini already written) and switches the dialog to its live view. }
|
|
277
|
+
Var
|
|
278
|
+
Code : Integer;
|
|
279
|
+
Begin
|
|
280
|
+
JobAction := Act;
|
|
281
|
+
Phase := 'run';
|
|
282
|
+
Waited := 0;
|
|
283
|
+
Shown := 0;
|
|
284
|
+
Total := 0;
|
|
285
|
+
Swing := -60;
|
|
286
|
+
ClearRows;
|
|
287
|
+
EcadlibDialog.AskBox.Visible := False;
|
|
288
|
+
EcadlibDialog.Report.Visible := False;
|
|
289
|
+
EcadlibDialog.Note.Visible := False;
|
|
290
|
+
EcadlibDialog.Note.Font.Color := @DIM@;
|
|
291
|
+
EcadlibDialog.Big.Visible := True;
|
|
292
|
+
EcadlibDialog.Status.Visible := True;
|
|
293
|
+
EcadlibDialog.Status.Caption := 'starting ecadlib';
|
|
294
|
+
Meter(0, 0);
|
|
295
|
+
Buttons('Working...', '', True);
|
|
296
|
+
Layout;
|
|
297
|
+
Code := RunApplication(CommandLine(Job));
|
|
298
|
+
If Code <> 0 Then
|
|
299
|
+
Fail('Windows could not start ecadlib (error ' + IntToStr(Code) + ').' + #13#10 + 'Next: set hidden=0 in ' + Here + 'ecadlib_menu.ini and try again.')
|
|
300
|
+
Else
|
|
301
|
+
EcadlibDialog.Tick.Enabled := True;
|
|
302
|
+
End;
|
|
303
|
+
|
|
304
|
+
Procedure StartAdd;
|
|
305
|
+
Var
|
|
306
|
+
MPN : String;
|
|
307
|
+
Maker : String;
|
|
308
|
+
Begin
|
|
309
|
+
MPN := Trim(EcadlibDialog.EditMpn.Text);
|
|
310
|
+
Maker := Trim(EcadlibDialog.EditMaker.Text);
|
|
311
|
+
If MPN = '' Then
|
|
312
|
+
Begin
|
|
313
|
+
EcadlibDialog.EditMpn.SetFocus;
|
|
314
|
+
Exit;
|
|
315
|
+
End;
|
|
316
|
+
SetTitle(MPN);
|
|
317
|
+
EcadlibDialog.Big.Caption := MPN;
|
|
318
|
+
Job := StartJob('add-part');
|
|
319
|
+
Ask(Job, 'dialog', '1');
|
|
320
|
+
Ask(Job, 'mpn', MPN);
|
|
321
|
+
Ask(Job, 'manufacturer', Maker);
|
|
322
|
+
Run('add-part');
|
|
323
|
+
End;
|
|
324
|
+
|
|
325
|
+
Procedure StartBom;
|
|
326
|
+
Begin
|
|
327
|
+
SetTitle('Check project BOM');
|
|
328
|
+
EcadlibDialog.Big.Caption := 'Checking...';
|
|
329
|
+
Job := StartJob('check-bom');
|
|
330
|
+
Ask(Job, 'dialog', '1');
|
|
331
|
+
Ask(Job, 'project', Project);
|
|
332
|
+
Run('check-bom');
|
|
333
|
+
End;
|
|
334
|
+
|
|
335
|
+
Procedure StartFill;
|
|
336
|
+
Begin
|
|
337
|
+
SetTitle('Fill the library');
|
|
338
|
+
EcadlibDialog.Big.Caption := 'Filling...';
|
|
339
|
+
Job := StartJob('fill');
|
|
340
|
+
Ask(Job, 'dialog', '1');
|
|
341
|
+
Ask(Job, 'project', Project);
|
|
342
|
+
Run('fill');
|
|
343
|
+
End;
|
|
344
|
+
|
|
345
|
+
Procedure Poll;
|
|
346
|
+
Var
|
|
347
|
+
Ini : TIniFile;
|
|
348
|
+
Done : Integer;
|
|
349
|
+
Count : Integer;
|
|
350
|
+
N : Integer;
|
|
351
|
+
i : Integer;
|
|
352
|
+
Begin
|
|
353
|
+
Waited := Waited + EcadlibDialog.Tick.Interval;
|
|
354
|
+
Ini := TIniFile.Create(Job + '\progress.ini');
|
|
355
|
+
Done := Ini.ReadInteger('progress', 'done', 0);
|
|
356
|
+
Count := Ini.ReadInteger('progress', 'total', 0);
|
|
357
|
+
EcadlibDialog.Status.Caption := Ini.ReadString('progress', 'text', EcadlibDialog.Status.Caption);
|
|
358
|
+
N := Ini.ReadInteger('progress', 'rows', 0);
|
|
359
|
+
If N > @ROWS@ Then
|
|
360
|
+
N := @ROWS@;
|
|
361
|
+
For i := 1 To N Do
|
|
362
|
+
SetRow(i, Ini.ReadString('progress', 'row' + IntToStr(i), ''));
|
|
363
|
+
Ini.Free;
|
|
364
|
+
Meter(Done, Count);
|
|
365
|
+
Layout;
|
|
366
|
+
If Waited >= SettingNumber('timeout_s', 1800) * 1000 Then
|
|
367
|
+
Begin
|
|
368
|
+
Waited := 0;
|
|
369
|
+
If Not ConfirmNoYes('ecadlib: ' + Title + ' is still working. Keep waiting?') Then
|
|
370
|
+
Fail('Stopped waiting. ecadlib may still finish in the background.' + #13#10 + 'Next: look at its log, ' + Job + '\log.txt');
|
|
371
|
+
End;
|
|
372
|
+
End;
|
|
373
|
+
|
|
374
|
+
Procedure Finish;
|
|
375
|
+
Var
|
|
376
|
+
St : String;
|
|
377
|
+
Headline : String;
|
|
378
|
+
Extra : String;
|
|
379
|
+
Ini : TIniFile;
|
|
380
|
+
Begin
|
|
381
|
+
If Not FileExists(Job + '\result.ini') Then
|
|
382
|
+
Begin
|
|
383
|
+
If FileExists(Job + '\notfound.txt') Then
|
|
384
|
+
Fail('ecadlib is not installed on this PC (not on PATH, and no Python can import it).' + #13#10 + 'Next: pip install ecadlib, or set command= in ' + Here + 'ecadlib_menu.ini')
|
|
385
|
+
Else
|
|
386
|
+
Fail('ecadlib stopped without a result.' + #13#10 + 'Next: look at its log, ' + Job + '\log.txt');
|
|
387
|
+
Exit;
|
|
388
|
+
End;
|
|
389
|
+
St := Answer(Job, 'status');
|
|
390
|
+
If St = 'error' Then
|
|
391
|
+
Begin
|
|
392
|
+
Fail(MessageText(Job, ''));
|
|
393
|
+
Exit;
|
|
394
|
+
End;
|
|
395
|
+
Extra := '';
|
|
396
|
+
If Answer(Job, 'install') <> '' Then
|
|
397
|
+
Begin
|
|
398
|
+
EcadlibDialog.Status.Caption := 'installing the library again';
|
|
399
|
+
Application.ProcessMessages;
|
|
400
|
+
Extra := Reinstall(Job);
|
|
401
|
+
End;
|
|
402
|
+
Headline := Answer(Job, 'big');
|
|
403
|
+
If Headline = '' Then
|
|
404
|
+
Headline := 'Done';
|
|
405
|
+
EcadlibDialog.Big.Caption := Headline;
|
|
406
|
+
ClearRows;
|
|
407
|
+
Ini := TIniFile.Create(Job + '\result.ini');
|
|
408
|
+
Total := Ini.ReadInteger('result', 'rows', 0);
|
|
409
|
+
Ini.Free;
|
|
410
|
+
If Total > @ROWS@ Then
|
|
411
|
+
Total := @ROWS@;
|
|
412
|
+
Shown := 0;
|
|
413
|
+
EcadlibDialog.Status.Visible := False;
|
|
414
|
+
EcadlibDialog.MeterFill.Left := 0;
|
|
415
|
+
EcadlibDialog.MeterFill.Width := EcadlibDialog.MeterTrack.Width;
|
|
416
|
+
EcadlibDialog.Note.Font.Color := @DIM@;
|
|
417
|
+
EcadlibDialog.Note.Caption := '';
|
|
418
|
+
Second := '';
|
|
419
|
+
Phase := 'done';
|
|
420
|
+
If JobAction = 'check-bom' Then
|
|
421
|
+
Begin
|
|
422
|
+
EcadlibDialog.Report.Visible := Answer(Job, 'report') <> '';
|
|
423
|
+
If Answer(Job, 'warnings') <> '' Then
|
|
424
|
+
EcadlibDialog.Note.Caption := Answer(Job, 'warnings');
|
|
425
|
+
If Answer(Job, 'offer_fill') = '1' Then
|
|
426
|
+
Begin
|
|
427
|
+
Phase := 'offer';
|
|
428
|
+
Buttons('Fill the library now', 'Later', False);
|
|
429
|
+
End
|
|
430
|
+
Else
|
|
431
|
+
Buttons('Done', '', False);
|
|
432
|
+
End
|
|
433
|
+
Else
|
|
434
|
+
Begin
|
|
435
|
+
If (Answer(Job, 'fix') <> '') And (St <> 'ready') Then
|
|
436
|
+
EcadlibDialog.Note.Caption := 'Next: ' + Answer(Job, 'fix');
|
|
437
|
+
If (St = 'held') Or (St = 'awaiting_approval') Or (Answer(Job, 'offer_queue') = '1') Then
|
|
438
|
+
Begin
|
|
439
|
+
Second := 'queue';
|
|
440
|
+
Buttons('Done', 'Open review queue', False);
|
|
441
|
+
End
|
|
442
|
+
Else
|
|
443
|
+
Buttons('Done', '', False);
|
|
444
|
+
End;
|
|
445
|
+
If Pos('Missing:', Extra) > 0 Then
|
|
446
|
+
EcadlibDialog.Note.Caption := EcadlibDialog.Note.Caption + Extra;
|
|
447
|
+
EcadlibDialog.Note.Visible := EcadlibDialog.Note.Caption <> '';
|
|
448
|
+
Layout;
|
|
449
|
+
End;
|
|
450
|
+
|
|
451
|
+
Procedure Reveal;
|
|
452
|
+
{ The result rows come in one by one, like the progress log on ecadlib's page. }
|
|
453
|
+
Var
|
|
454
|
+
Ini : TIniFile;
|
|
455
|
+
Begin
|
|
456
|
+
Shown := Shown + 1;
|
|
457
|
+
Ini := TIniFile.Create(Job + '\result.ini');
|
|
458
|
+
SetRow(Shown, Ini.ReadString('result', 'row' + IntToStr(Shown), ''));
|
|
459
|
+
Ini.Free;
|
|
460
|
+
Layout;
|
|
461
|
+
End;
|
|
462
|
+
|
|
463
|
+
Procedure Fire(Act : String; Key : String; Value : String);
|
|
464
|
+
{ A job the dialog does not wait for: open the review queue or a report in the browser. }
|
|
465
|
+
Var
|
|
466
|
+
J : String;
|
|
467
|
+
Begin
|
|
468
|
+
J := StartJob(Act);
|
|
469
|
+
If Key <> '' Then
|
|
470
|
+
Ask(J, Key, Value);
|
|
471
|
+
RunApplication(CommandLine(J));
|
|
472
|
+
End;
|
|
473
|
+
|
|
474
|
+
Procedure OpenDialog(AMode : String; AProject : String; AMpn : String);
|
|
475
|
+
Begin
|
|
476
|
+
Mode := AMode;
|
|
477
|
+
Project := AProject;
|
|
478
|
+
Job := '';
|
|
479
|
+
JobAction := '';
|
|
480
|
+
Second := '';
|
|
481
|
+
ClearRows;
|
|
482
|
+
EcadlibDialog.Tick.Enabled := False;
|
|
483
|
+
EcadlibDialog.Note.Visible := False;
|
|
484
|
+
EcadlibDialog.Report.Visible := False;
|
|
485
|
+
EcadlibDialog.Status.Visible := False;
|
|
486
|
+
EcadlibDialog.MeterTrack.Visible := False;
|
|
487
|
+
EcadlibDialog.Big.Visible := True;
|
|
488
|
+
If AMode = 'add' Then
|
|
489
|
+
Begin
|
|
490
|
+
SetTitle('Add part from MPN');
|
|
491
|
+
EcadlibDialog.EditMpn.Text := AMpn;
|
|
492
|
+
EcadlibDialog.EditMaker.Text := '';
|
|
493
|
+
If AMpn <> '' Then
|
|
494
|
+
StartAdd
|
|
495
|
+
Else
|
|
496
|
+
Begin
|
|
497
|
+
Phase := 'ask';
|
|
498
|
+
ClipboardMpn;
|
|
499
|
+
EcadlibDialog.AskBox.Visible := True;
|
|
500
|
+
EcadlibDialog.Big.Visible := False;
|
|
501
|
+
Buttons('OK', 'Cancel', False);
|
|
502
|
+
Layout;
|
|
503
|
+
End;
|
|
504
|
+
End
|
|
505
|
+
Else If AMode = 'fill' Then
|
|
506
|
+
StartFill
|
|
507
|
+
Else
|
|
508
|
+
StartBom;
|
|
509
|
+
EcadlibDialog.ShowModal;
|
|
510
|
+
EcadlibDialog.Tick.Enabled := False;
|
|
511
|
+
End;
|
|
512
|
+
|
|
513
|
+
Procedure TEcadlibDialog.TickTimer(Sender : TObject);
|
|
514
|
+
Begin
|
|
515
|
+
If Phase = 'run' Then
|
|
516
|
+
Begin
|
|
517
|
+
If FileExists(Job + '\exit.txt') Then
|
|
518
|
+
Begin
|
|
519
|
+
Tick.Enabled := False;
|
|
520
|
+
Finish;
|
|
521
|
+
Tick.Enabled := True;
|
|
522
|
+
End
|
|
523
|
+
Else
|
|
524
|
+
Poll;
|
|
525
|
+
End
|
|
526
|
+
Else If Shown < Total Then
|
|
527
|
+
Reveal
|
|
528
|
+
Else
|
|
529
|
+
Tick.Enabled := False;
|
|
530
|
+
End;
|
|
531
|
+
|
|
532
|
+
Procedure TEcadlibDialog.PrimaryClick(Sender : TObject);
|
|
533
|
+
Begin
|
|
534
|
+
If Phase = 'ask' Then
|
|
535
|
+
StartAdd
|
|
536
|
+
Else If Phase = 'offer' Then
|
|
537
|
+
StartFill
|
|
538
|
+
Else If Phase = 'done' Then
|
|
539
|
+
ModalResult := mrOk;
|
|
540
|
+
End;
|
|
541
|
+
|
|
542
|
+
Procedure TEcadlibDialog.SecondaryClick(Sender : TObject);
|
|
543
|
+
Begin
|
|
544
|
+
If Second = 'queue' Then
|
|
545
|
+
Begin
|
|
546
|
+
Fire('queue', '', '');
|
|
547
|
+
ModalResult := mrOk;
|
|
548
|
+
End
|
|
549
|
+
Else If Second = 'log' Then
|
|
550
|
+
RunApplication('notepad.exe "' + Job + '\log.txt"')
|
|
551
|
+
Else If Phase <> 'run' Then
|
|
552
|
+
ModalResult := mrCancel;
|
|
553
|
+
End;
|
|
554
|
+
|
|
555
|
+
Procedure TEcadlibDialog.ReportClick(Sender : TObject);
|
|
556
|
+
Begin
|
|
557
|
+
Fire('open', 'path', Answer(Job, 'report'));
|
|
558
|
+
End;
|
|
559
|
+
|
|
560
|
+
Procedure TEcadlibDialog.HeadMouseDown(Sender : TObject; Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
|
|
561
|
+
{ The window has no Windows title bar (bsNone): its own header moves it. }
|
|
562
|
+
Begin
|
|
563
|
+
Dragging := True;
|
|
564
|
+
DragX := X;
|
|
565
|
+
DragY := Y;
|
|
566
|
+
End;
|
|
567
|
+
|
|
568
|
+
Procedure TEcadlibDialog.HeadMouseMove(Sender : TObject; Shift : TShiftState; X, Y : Integer);
|
|
569
|
+
Begin
|
|
570
|
+
If Dragging Then
|
|
571
|
+
Begin
|
|
572
|
+
Left := Left + X - DragX;
|
|
573
|
+
Top := Top + Y - DragY;
|
|
574
|
+
End;
|
|
575
|
+
End;
|
|
576
|
+
|
|
577
|
+
Procedure TEcadlibDialog.HeadMouseUp(Sender : TObject; Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
|
|
578
|
+
Begin
|
|
579
|
+
Dragging := False;
|
|
580
|
+
End;
|
|
581
|
+
|
|
582
|
+
Procedure TEcadlibDialog.EcadlibDialogKeyDown(Sender : TObject; Var Key : Word; Shift : TShiftState);
|
|
583
|
+
{ Ctrl+V/C/X/A and Shift+Insert in the MPN and manufacturer boxes (KeyPreview: the form sees keys first). }
|
|
584
|
+
Var
|
|
585
|
+
Box : TEdit;
|
|
586
|
+
Begin
|
|
587
|
+
If ActiveControl = EditMpn Then
|
|
588
|
+
Box := EditMpn
|
|
589
|
+
Else If ActiveControl = EditMaker Then
|
|
590
|
+
Box := EditMaker
|
|
591
|
+
Else
|
|
592
|
+
Exit;
|
|
593
|
+
If InSet(ssCtrl, Shift) And (Key = 86) Then PasteInto(Box)
|
|
594
|
+
Else If InSet(ssShift, Shift) And (Key = 45) Then PasteInto(Box)
|
|
595
|
+
Else If InSet(ssCtrl, Shift) And (Key = 67) Then Box.CopyToClipboard
|
|
596
|
+
Else If InSet(ssCtrl, Shift) And (Key = 88) Then Box.CutToClipboard
|
|
597
|
+
Else If InSet(ssCtrl, Shift) And (Key = 65) Then Box.SelectAll
|
|
598
|
+
Else Exit;
|
|
599
|
+
Key := 0;
|
|
600
|
+
End;
|
|
601
|
+
|
|
602
|
+
Procedure TEcadlibDialog.PasteMpnClick(Sender : TObject);
|
|
603
|
+
Begin
|
|
604
|
+
EditMpn.SelectAll;
|
|
605
|
+
PasteInto(EditMpn);
|
|
606
|
+
EditMpn.SetFocus;
|
|
607
|
+
End;
|
|
608
|
+
|
|
609
|
+
Procedure TEcadlibDialog.EcadlibDialogClose(Sender : TObject; Var CloseAction : TCloseAction);
|
|
610
|
+
{ A closed form's enabled timer fires again whenever this script project runs (AD 26.10): stop it. }
|
|
611
|
+
Begin
|
|
612
|
+
Tick.Enabled := False;
|
|
613
|
+
End;
|
|
614
|
+
|
|
615
|
+
Procedure TEcadlibDialog.CloseClick(Sender : TObject);
|
|
616
|
+
Begin
|
|
617
|
+
If Phase = 'run' Then
|
|
618
|
+
If Not ConfirmNoYes('ecadlib is still working on ' + Title + '. Stop waiting?' + #13#10 + 'It finishes in the background; run the menu entry again to put the result into the library.') Then
|
|
619
|
+
Exit;
|
|
620
|
+
ModalResult := mrCancel;
|
|
621
|
+
End;
|
|
622
|
+
""".replace("@ROWS_SET@", rows_set).replace("@ROWS_TOP@", rows_top).replace("@ROWS@", str(ROWS)) \
|
|
623
|
+
.replace("@WIDTH@", str(WIDTH)).replace("@TEXT@", TEXT).replace("@GREEN@", GREEN).replace("@RED@", RED) \
|
|
624
|
+
.replace("@DIM@", DIM).replace("@NOTE_W@", str(WIDTH - 40)).replace("@BLUE@", BLUE).replace("@BLUE_OFF@", BLUE_OFF).replace("@VERSION@", __version__)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _font(color=TEXT, size=-12, face="Segoe UI", bold=False, ind=" "):
|
|
628
|
+
return (f"{ind}Font.Charset = DEFAULT_CHARSET\n{ind}Font.Color = {color}\n{ind}Font.Height = {size}\n"
|
|
629
|
+
f"{ind}Font.Name = '{face}'\n{ind}Font.Style = [{'fsBold' if bold else ''}]\n{ind}ParentFont = False\n")
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _dfm_str(s):
|
|
633
|
+
"""A .dfm string: printable ASCII in quotes, anything else as #code (183 = middle dot, 215 = multiplication sign)."""
|
|
634
|
+
out, quoted = "", False
|
|
635
|
+
for ch in s:
|
|
636
|
+
if 32 <= ord(ch) < 127 and ch != "'":
|
|
637
|
+
out += ch if quoted else "'" + ch
|
|
638
|
+
quoted = True
|
|
639
|
+
else:
|
|
640
|
+
out += ("'" if quoted else "") + f"#{ord(ch)}"
|
|
641
|
+
quoted = False
|
|
642
|
+
return (out + "'") if quoted else (out or "''")
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
DRAG = ("Cursor = crSizeAll", "OnMouseDown = HeadMouseDown", "OnMouseMove = HeadMouseMove", "OnMouseUp = HeadMouseUp")
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _drag(ind):
|
|
649
|
+
return "".join(f"{ind}{x}\n" for x in DRAG)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _label(name, left, top, width, height, caption, ind=" ", align="", wrap=False, drag=False, **font):
|
|
653
|
+
i2 = ind + " "
|
|
654
|
+
extra = (f"{i2}Alignment = taRightJustify\n" if align == "r" else "") + (f"{i2}WordWrap = True\n" if wrap else "") \
|
|
655
|
+
+ (_drag(i2) if drag else "")
|
|
656
|
+
return (f"{ind}object {name}: TLabel\n{i2}Left = {left}\n{i2}Top = {top}\n{i2}Width = {width}\n{i2}Height = {height}\n"
|
|
657
|
+
f"{i2}AutoSize = False\n{i2}Caption = {_dfm_str(caption)}\n{_font(ind=i2, **font)}"
|
|
658
|
+
f"{i2}Transparent = True\n{extra}{ind}end\n")
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _panel(name, left, top, width, height, color, body="", caption="", ind=" ", onclick="", font=None, visible=True,
|
|
662
|
+
drag=False):
|
|
663
|
+
i2 = ind + " "
|
|
664
|
+
return (f"{ind}object {name}: TPanel\n{i2}Left = {left}\n{i2}Top = {top}\n{i2}Width = {width}\n{i2}Height = {height}\n"
|
|
665
|
+
f"{i2}BevelOuter = bvNone\n{i2}Caption = {_dfm_str(caption)}\n{i2}Color = {color}\n{i2}ParentBackground = False\n"
|
|
666
|
+
+ (_font(ind=i2, **font) if font else "") + (f"{i2}Cursor = crHandPoint\n{i2}OnClick = {onclick}\n" if onclick else "")
|
|
667
|
+
+ ("" if visible else f"{i2}Visible = False\n") + (_drag(i2) if drag else "") + body + f"{ind}end\n")
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _edit(name, left, top, width, ind=" "):
|
|
671
|
+
i2 = ind + " "
|
|
672
|
+
return (f"{ind}object {name}: TEdit\n{i2}Left = {left}\n{i2}Top = {top}\n{i2}Width = {width}\n{i2}Height = 24\n"
|
|
673
|
+
f"{i2}BorderStyle = bsSingle\n{i2}Color = {FIELD}\n{_font(ind=i2)}{i2}Text = ''\n{ind}end\n")
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _paste_link(w, ind=" "):
|
|
677
|
+
"""'Paste' at the right of the MPN label: pastes the clipboard into the MPN box (and so does Ctrl+V)."""
|
|
678
|
+
i2 = ind + " "
|
|
679
|
+
return (f"{ind}object PasteMpn: TLabel\n{i2}Left = {w - 60}\n{i2}Top = 0\n{i2}Width = 60\n{i2}Height = 16\n"
|
|
680
|
+
f"{i2}Alignment = taRightJustify\n{i2}AutoSize = False\n{i2}Caption = 'Paste'\n{i2}Cursor = crHandPoint\n"
|
|
681
|
+
f"{_font(color=BLUE, ind=i2)}{i2}Transparent = True\n{i2}OnClick = PasteMpnClick\n{ind}end\n")
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def dialog_dfm_text():
|
|
685
|
+
"""The form: header with title and close mark, the MPN box, the big line, ROWS rows, status, meter, note, report
|
|
686
|
+
link and two buttons. Positions are set again by Layout at run time; these are the first frame's."""
|
|
687
|
+
w = WIDTH - 32
|
|
688
|
+
rows = "".join(_panel(f"Row{i}", 16, 90 + 26 * i, w, 26, BG, visible=False, body=(
|
|
689
|
+
_label(f"Row{i}A", 0, 4, LABEL_W, 18, "", face="Consolas") +
|
|
690
|
+
_label(f"Row{i}B", LABEL_W, 4, w - LABEL_W, 18, "", align="r", face="Consolas") +
|
|
691
|
+
_panel(f"Row{i}Rule", 0, 25, w, 1, RULE, ind=" "))) for i in range(1, ROWS + 1))
|
|
692
|
+
ask = _panel("AskBox", 16, 46, w, 104, BG, visible=False, body=(
|
|
693
|
+
_label("AskMpn", 0, 0, w, 16, "MPN", color=DIM) + _paste_link(w) + _edit("EditMpn", 0, 18, w) +
|
|
694
|
+
_label("AskMaker", 0, 52, w, 16, "Manufacturer (optional)", color=DIM) + _edit("EditMaker", 0, 70, w)))
|
|
695
|
+
meter = _panel("MeterTrack", 16, 300, w, 2, RULE, visible=False,
|
|
696
|
+
body=_panel("MeterFill", 0, 0, 0, 2, BLUE, ind=" "))
|
|
697
|
+
button_font = {"color": "$00FFFFFF"}
|
|
698
|
+
head = _panel("Head", 0, 0, WIDTH, 34, HEAD, drag=True, body=(
|
|
699
|
+
_label("HeadTitle", 14, 8, WIDTH - 60, 18, "ecadlib · Check project BOM", drag=True) +
|
|
700
|
+
_panel("HeadClose", WIDTH - 34, 2, 30, 30, HEAD, caption="×", ind=" ", onclick="CloseClick",
|
|
701
|
+
font={"color": DIM, "size": -16})))
|
|
702
|
+
return f"""object EcadlibDialog: TEcadlibDialog
|
|
703
|
+
Left = 0
|
|
704
|
+
Top = 0
|
|
705
|
+
BorderStyle = bsNone
|
|
706
|
+
Caption = 'ecadlib'
|
|
707
|
+
ClientHeight = 300
|
|
708
|
+
ClientWidth = {WIDTH}
|
|
709
|
+
Color = {BG}
|
|
710
|
+
{_font(ind=" ")} FormKind = fkModal
|
|
711
|
+
KeyPreview = True
|
|
712
|
+
OldCreateOrder = False
|
|
713
|
+
OnClose = EcadlibDialogClose
|
|
714
|
+
OnKeyDown = EcadlibDialogKeyDown
|
|
715
|
+
Position = poMainFormCenter
|
|
716
|
+
PixelsPerInch = 96
|
|
717
|
+
TextHeight = 15
|
|
718
|
+
{head}{ask}{_label("Big", 16, 46, w, 34, "", size=-24)}{rows}{_label("Status", 16, 250, w, 18, "", color=DIM)}{meter}{_label("Note", 16, 310, w, 18, "", wrap=True, color=DIM)} object Report: TLabel
|
|
719
|
+
Left = 16
|
|
720
|
+
Top = 330
|
|
721
|
+
Width = 160
|
|
722
|
+
Height = 18
|
|
723
|
+
AutoSize = False
|
|
724
|
+
Caption = 'Open the full report'
|
|
725
|
+
Cursor = crHandPoint
|
|
726
|
+
{_font(color=BLUE, ind=" ")} Transparent = True
|
|
727
|
+
Visible = False
|
|
728
|
+
OnClick = ReportClick
|
|
729
|
+
end
|
|
730
|
+
{_panel("Secondary", 120, 350, 80, 30, BTN, caption="Later", onclick="SecondaryClick", font=button_font)}{_panel("Primary", 208, 350, 136, 30, BLUE, caption="OK", onclick="PrimaryClick", font=button_font)} object EnterKey: TButton
|
|
731
|
+
Left = -200
|
|
732
|
+
Top = 0
|
|
733
|
+
Width = 10
|
|
734
|
+
Height = 10
|
|
735
|
+
Caption = 'OK'
|
|
736
|
+
Default = True
|
|
737
|
+
TabOrder = 9
|
|
738
|
+
OnClick = PrimaryClick
|
|
739
|
+
end
|
|
740
|
+
object EscKey: TButton
|
|
741
|
+
Left = -200
|
|
742
|
+
Top = 20
|
|
743
|
+
Width = 10
|
|
744
|
+
Height = 10
|
|
745
|
+
Cancel = True
|
|
746
|
+
Caption = 'Cancel'
|
|
747
|
+
TabOrder = 10
|
|
748
|
+
OnClick = CloseClick
|
|
749
|
+
end
|
|
750
|
+
object Tick: TTimer
|
|
751
|
+
Enabled = False
|
|
752
|
+
Interval = 250
|
|
753
|
+
OnTimer = TickTimer
|
|
754
|
+
Left = 4
|
|
755
|
+
Top = 4
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
""".replace("\n", "\r\n")
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def dialog_pas_text():
|
|
762
|
+
return _dlg_pas().replace("\n", "\r\n")
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def dfm_names(dfm):
|
|
766
|
+
"""Component names declared in a .dfm (object Name: TClass), the form's included."""
|
|
767
|
+
import re
|
|
768
|
+
return re.findall(r"(?m)^\s*object (\w+): \w+", dfm)
|