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.
Files changed (199) hide show
  1. ecadlib/__init__.py +3 -0
  2. ecadlib/__main__.py +5 -0
  3. ecadlib/a365_sync.py +637 -0
  4. ecadlib/agent.py +1407 -0
  5. ecadlib/agent_queue.py +780 -0
  6. ecadlib/agent_watch.py +732 -0
  7. ecadlib/altium365.py +385 -0
  8. ecadlib/altium_dblib_sync.py +530 -0
  9. ecadlib/altium_export.py +209 -0
  10. ecadlib/altium_forms.py +768 -0
  11. ecadlib/altium_lib.py +1089 -0
  12. ecadlib/altium_menu.py +1751 -0
  13. ecadlib/altium_pkg.py +396 -0
  14. ecadlib/altium_project.py +1095 -0
  15. ecadlib/app.py +1458 -0
  16. ecadlib/approvals.py +395 -0
  17. ecadlib/assets/demo_bom.csv +8 -0
  18. ecadlib/assets/kicad_action_plugin.py +607 -0
  19. ecadlib/assets/kicad_reread.py +60 -0
  20. ecadlib/assets/pcblib_template.json.gz +0 -0
  21. ecadlib/bom_inputs.py +339 -0
  22. ecadlib/bom_triage.py +519 -0
  23. ecadlib/cad_model.py +528 -0
  24. ecadlib/cfb.py +171 -0
  25. ecadlib/cli.py +185 -0
  26. ecadlib/clihelp.py +149 -0
  27. ecadlib/cliutil.py +119 -0
  28. ecadlib/completion.py +173 -0
  29. ecadlib/costs.py +276 -0
  30. ecadlib/crosscheck.py +412 -0
  31. ecadlib/datasheet.py +2168 -0
  32. ecadlib/dedupe.py +572 -0
  33. ecadlib/detect.py +170 -0
  34. ecadlib/doctor.py +450 -0
  35. ecadlib/ds_route.py +541 -0
  36. ecadlib/eagle_export.py +1409 -0
  37. ecadlib/easy_setup.py +338 -0
  38. ecadlib/easyeda_convert.py +386 -0
  39. ecadlib/easyeda_pro_export.py +972 -0
  40. ecadlib/eval/__init__.py +0 -0
  41. ecadlib/eval/baselines/kicad-v1.json +404 -0
  42. ecadlib/eval/build_set.py +299 -0
  43. ecadlib/eval/cli.py +119 -0
  44. ecadlib/eval/gate.py +358 -0
  45. ecadlib/eval/golden/demo.txt +379 -0
  46. ecadlib/eval/golden/specs.txt +3120 -0
  47. ecadlib/eval/golden/testpack.txt +463 -0
  48. ecadlib/eval/golden.py +446 -0
  49. ecadlib/eval/replay/kicad-v1.json.gz +0 -0
  50. ecadlib/eval/runner.py +456 -0
  51. ecadlib/eval/score.py +179 -0
  52. ecadlib/eval/sets/kicad-v1.json +630 -0
  53. ecadlib/firstrun.py +400 -0
  54. ecadlib/footprint_adv.py +1006 -0
  55. ecadlib/footprint_bench.py +218 -0
  56. ecadlib/footprint_check.py +562 -0
  57. ecadlib/footprint_gen.py +582 -0
  58. ecadlib/footprint_rules.py +1092 -0
  59. ecadlib/forge.py +267 -0
  60. ecadlib/fp_share.py +95 -0
  61. ecadlib/fp_tidy.py +389 -0
  62. ecadlib/fsutil.py +62 -0
  63. ecadlib/house_style.py +1051 -0
  64. ecadlib/httpcache.py +124 -0
  65. ecadlib/hub.py +1490 -0
  66. ecadlib/hub_copy.py +161 -0
  67. ecadlib/hub_tools.py +79 -0
  68. ecadlib/kicad_dblib.py +257 -0
  69. ecadlib/kicad_export.py +837 -0
  70. ecadlib/kicad_import.py +913 -0
  71. ecadlib/kicad_index.py +160 -0
  72. ecadlib/kicad_menu.py +193 -0
  73. ecadlib/kicad_pcm.py +475 -0
  74. ecadlib/launcher.py +156 -0
  75. ecadlib/librarian.py +963 -0
  76. ecadlib/lifecycle.py +691 -0
  77. ecadlib/llm/__init__.py +276 -0
  78. ecadlib/llm/cli.py +70 -0
  79. ecadlib/llm/openai_compat.py +214 -0
  80. ecadlib/llm/profiles.py +245 -0
  81. ecadlib/llm/rails.py +570 -0
  82. ecadlib/llm/run.py +126 -0
  83. ecadlib/llm/sampling.py +49 -0
  84. ecadlib/mcp_server.py +723 -0
  85. ecadlib/messages.py +419 -0
  86. ecadlib/migrate.py +407 -0
  87. ecadlib/notify.py +329 -0
  88. ecadlib/package_schema.json +1238 -0
  89. ecadlib/package_specs/CP_EIA-3216-18_Kemet-A.json +27 -0
  90. ecadlib/package_specs/C_0402_1005Metric.json +22 -0
  91. ecadlib/package_specs/C_0805_2012Metric.json +22 -0
  92. ecadlib/package_specs/DFN-8-1EP_3x3mm_P0.5mm_EP1.66x2.38mm.json +40 -0
  93. ecadlib/package_specs/D_SMA.json +27 -0
  94. ecadlib/package_specs/D_SMB.json +27 -0
  95. ecadlib/package_specs/D_SOD-123.json +43 -0
  96. ecadlib/package_specs/D_SOD-323.json +43 -0
  97. ecadlib/package_specs/LED_0603_1608Metric.json +23 -0
  98. ecadlib/package_specs/LED_0805_2012Metric.json +23 -0
  99. ecadlib/package_specs/LQFP-48_7x7mm_P0.5mm.json +31 -0
  100. ecadlib/package_specs/L_0805_2012Metric.json +22 -0
  101. ecadlib/package_specs/MSOP-8_3x3mm_P0.65mm.json +31 -0
  102. ecadlib/package_specs/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm.json +39 -0
  103. ecadlib/package_specs/QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm.json +37 -0
  104. ecadlib/package_specs/R_0402_1005Metric.json +22 -0
  105. ecadlib/package_specs/R_0603_1608Metric.json +22 -0
  106. ecadlib/package_specs/R_0805_2012Metric.json +22 -0
  107. ecadlib/package_specs/R_1206_3216Metric.json +22 -0
  108. ecadlib/package_specs/SOIC-16_3.9x9.9mm_P1.27mm.json +32 -0
  109. ecadlib/package_specs/SOIC-8_3.9x4.9mm_P1.27mm.json +32 -0
  110. ecadlib/package_specs/SOT-223-3_TabPin2.json +56 -0
  111. ecadlib/package_specs/SOT-23-5.json +57 -0
  112. ecadlib/package_specs/SOT-23-6.json +62 -0
  113. ecadlib/package_specs/SOT-23.json +48 -0
  114. ecadlib/package_specs/TSSOP-16_4.4x5mm_P0.65mm.json +31 -0
  115. ecadlib/package_specs/VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm.json +39 -0
  116. ecadlib/package_specs/advanced/BGA-100_11.0x11.0mm_Layout10x10_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  117. ecadlib/package_specs/advanced/BGA-121_9.0x9.0mm_Layout11x11_P0.8mm_Ball0.4mm_Pad0.35mm_NSMD.json +26 -0
  118. ecadlib/package_specs/advanced/BGA-144_7.0x7.0mm_Layout13x13_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +29 -0
  119. ecadlib/package_specs/advanced/BGA-169_11.0x11.0mm_Layout13x13_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +23 -0
  120. ecadlib/package_specs/advanced/BGA-256_11.0x11.0mm_Layout20x20_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +26 -0
  121. ecadlib/package_specs/advanced/BGA-256_17.0x17.0mm_Layout16x16_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  122. ecadlib/package_specs/advanced/BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  123. ecadlib/package_specs/advanced/BGA-36_3.396x3.466mm_Layout6x6_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD.json +24 -0
  124. ecadlib/package_specs/advanced/BGA-672_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD.json +32 -0
  125. ecadlib/package_specs/advanced/BGA-68_5.0x5.0mm_Layout9x9_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +30 -0
  126. ecadlib/package_specs/advanced/BGA-90_8.0x13.0mm_Layout2x3x15_P0.8mm.json +26 -0
  127. ecadlib/package_specs/advanced/DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias.json +60 -0
  128. ecadlib/package_specs/advanced/Example_Custom_LPad_Slot.json +24 -0
  129. ecadlib/package_specs/advanced/JST_GH_SM04B-GHS-TB_1x04-1MP_P1.25mm_Horizontal.json +49 -0
  130. ecadlib/package_specs/advanced/JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical.json +33 -0
  131. ecadlib/package_specs/advanced/JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal.json +55 -0
  132. ecadlib/package_specs/advanced/JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal.json +49 -0
  133. ecadlib/package_specs/advanced/JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical.json +34 -0
  134. ecadlib/package_specs/advanced/JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical.json +34 -0
  135. ecadlib/package_specs/advanced/JST_XH_B6B-XH-A_1x06_P2.50mm_Vertical.json +33 -0
  136. ecadlib/package_specs/advanced/LFBGA-144_10x10mm_Layout12x12_P0.8mm.json +23 -0
  137. ecadlib/package_specs/advanced/Molex_KK-254_AE-6410-04A_1x04_P2.54mm_Vertical.json +34 -0
  138. ecadlib/package_specs/advanced/Molex_PicoBlade_53398-0471_1x04-1MP_P1.25mm_Vertical.json +54 -0
  139. ecadlib/package_specs/advanced/PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Left.json +14 -0
  140. ecadlib/package_specs/advanced/PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Left.json +14 -0
  141. ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical.json +12 -0
  142. ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Left.json +14 -0
  143. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical.json +12 -0
  144. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Left.json +25 -0
  145. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Right.json +14 -0
  146. ecadlib/package_specs/advanced/PinHeader_1x10_P1.00mm_Vertical.json +15 -0
  147. ecadlib/package_specs/advanced/PinHeader_2x05_P1.00mm_Vertical_SMD.json +13 -0
  148. ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical.json +18 -0
  149. ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical_SMD.json +24 -0
  150. ecadlib/package_specs/advanced/PinHeader_2x05_P2.00mm_Vertical_SMD.json +13 -0
  151. ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical.json +12 -0
  152. ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical_SMD.json +24 -0
  153. ecadlib/package_specs/advanced/PinHeader_2x20_P2.54mm_Vertical.json +12 -0
  154. ecadlib/package_specs/advanced/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias.json +87 -0
  155. ecadlib/package_specs/advanced/UFBGA-132_7x7mm_Layout12x12_P0.5mm.json +43 -0
  156. ecadlib/package_specs/advanced/USB_C_Receptacle_HRO_TYPE-C-31-M-12.json +218 -0
  157. ecadlib/package_specs/standard.json.gz +0 -0
  158. ecadlib/parity.py +1204 -0
  159. ecadlib/parts_export.py +154 -0
  160. ecadlib/partview.py +215 -0
  161. ecadlib/plugins.py +231 -0
  162. ecadlib/providers.py +226 -0
  163. ecadlib/rebuild.py +471 -0
  164. ecadlib/release_check.py +437 -0
  165. ecadlib/reports.py +274 -0
  166. ecadlib/resolve.py +556 -0
  167. ecadlib/review_cli.py +292 -0
  168. ecadlib/rules.py +979 -0
  169. ecadlib/rules_infer.py +1205 -0
  170. ecadlib/sexpr.py +149 -0
  171. ecadlib/sheetsafe.py +65 -0
  172. ecadlib/sim_models.py +1087 -0
  173. ecadlib/sourcing.py +1161 -0
  174. ecadlib/stdparts.py +755 -0
  175. ecadlib/step_gen.py +929 -0
  176. ecadlib/symbol_layout.py +798 -0
  177. ecadlib/team.py +558 -0
  178. ecadlib/team_setup.py +288 -0
  179. ecadlib/term.py +420 -0
  180. ecadlib/term_views.py +64 -0
  181. ecadlib/testpack.py +334 -0
  182. ecadlib/ui/__init__.py +1 -0
  183. ecadlib/ui/__main__.py +3 -0
  184. ecadlib/ui/server.py +1029 -0
  185. ecadlib/ui/static/hairline.js +2 -0
  186. ecadlib/ui/static/index.html +2098 -0
  187. ecadlib/ui/static/setup.html +294 -0
  188. ecadlib/upkeep.py +717 -0
  189. ecadlib/upstream.py +573 -0
  190. ecadlib/userconfig.py +257 -0
  191. ecadlib/visual_check.py +1603 -0
  192. ecadlib/ws_status.py +203 -0
  193. ecadlib-0.3.0.dist-info/METADATA +2245 -0
  194. ecadlib-0.3.0.dist-info/RECORD +199 -0
  195. ecadlib-0.3.0.dist-info/WHEEL +5 -0
  196. ecadlib-0.3.0.dist-info/entry_points.txt +2 -0
  197. ecadlib-0.3.0.dist-info/licenses/LICENSE +21 -0
  198. ecadlib-0.3.0.dist-info/licenses/NOTICE +53 -0
  199. ecadlib-0.3.0.dist-info/top_level.txt +1 -0
@@ -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)