@yumiai/chat-widget 0.1.2 → 0.2.1

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 (107) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +119 -22
  3. package/dist/ExcelCore-DJOIVQMI.js +11 -0
  4. package/dist/ExcelCore-DJOIVQMI.js.map +1 -0
  5. package/dist/ExcelViewer-3YLLYYIQ.js +65 -0
  6. package/dist/ExcelViewer-3YLLYYIQ.js.map +1 -0
  7. package/dist/GerberViewerA2UI-7CNT7HX4.css +693 -0
  8. package/dist/GerberViewerA2UI-7CNT7HX4.css.map +1 -0
  9. package/dist/GerberViewerA2UI-X5FWAD5M.js +57 -0
  10. package/dist/GerberViewerA2UI-X5FWAD5M.js.map +1 -0
  11. package/dist/GraphStatsLegend-D5bPeXB_.d.cts +607 -0
  12. package/dist/GraphStatsLegend-D5bPeXB_.d.ts +607 -0
  13. package/dist/JsonRenderStandalone-EIZM62JU.js +18 -0
  14. package/dist/JsonRenderStandalone-EIZM62JU.js.map +1 -0
  15. package/dist/JsonRenderStandalone-POB4Q3N3.css +2384 -0
  16. package/dist/JsonRenderStandalone-POB4Q3N3.css.map +1 -0
  17. package/dist/JsonRenderStandalone-UsTcST4G.d.cts +23 -0
  18. package/dist/JsonRenderStandalone-UsTcST4G.d.ts +23 -0
  19. package/dist/KicadViewer-GV6ZC4AQ.js +124 -0
  20. package/dist/KicadViewer-GV6ZC4AQ.js.map +1 -0
  21. package/dist/KicadViewerCore-U7BWZHKJ.js +11 -0
  22. package/dist/KicadViewerCore-U7BWZHKJ.js.map +1 -0
  23. package/dist/PdfViewer-CHPDRK46.js +51 -0
  24. package/dist/PdfViewer-CHPDRK46.js.map +1 -0
  25. package/dist/PdfViewer-LPYGQETK.css +1899 -0
  26. package/dist/PdfViewer-LPYGQETK.css.map +1 -0
  27. package/dist/PdfViewerCore-HJPEHSRA.js +364 -0
  28. package/dist/PdfViewerCore-HJPEHSRA.js.map +1 -0
  29. package/dist/PowerPointCore-FPDR2BL4.js +11 -0
  30. package/dist/PowerPointCore-FPDR2BL4.js.map +1 -0
  31. package/dist/PowerPointViewer-LQTO6UCU.js +61 -0
  32. package/dist/PowerPointViewer-LQTO6UCU.js.map +1 -0
  33. package/dist/StepViewerCore-7W3L3R4E.js +285 -0
  34. package/dist/StepViewerCore-7W3L3R4E.js.map +1 -0
  35. package/dist/ThreeViewerCore-N3QJD5QI.js +161 -0
  36. package/dist/ThreeViewerCore-N3QJD5QI.js.map +1 -0
  37. package/dist/WordCore-JKSXK2XD.js +11 -0
  38. package/dist/WordCore-JKSXK2XD.js.map +1 -0
  39. package/dist/WordViewer-ZHCQMHOH.js +61 -0
  40. package/dist/WordViewer-ZHCQMHOH.js.map +1 -0
  41. package/dist/chunk-2SKA3F5U.js +88 -0
  42. package/dist/chunk-2SKA3F5U.js.map +1 -0
  43. package/dist/chunk-2UC7YLVX.js +318 -0
  44. package/dist/chunk-2UC7YLVX.js.map +1 -0
  45. package/dist/chunk-3R6T3LBR.js +24 -0
  46. package/dist/chunk-3R6T3LBR.js.map +1 -0
  47. package/dist/chunk-56WRZM3R.js +398 -0
  48. package/dist/chunk-56WRZM3R.js.map +1 -0
  49. package/dist/chunk-7A4FY6FK.js +10226 -0
  50. package/dist/chunk-7A4FY6FK.js.map +1 -0
  51. package/dist/chunk-7D4SUZUM.js +38 -0
  52. package/dist/chunk-7D4SUZUM.js.map +1 -0
  53. package/dist/chunk-7S67DOHQ.js +436 -0
  54. package/dist/chunk-7S67DOHQ.js.map +1 -0
  55. package/dist/chunk-CFKGNAJM.js +14013 -0
  56. package/dist/chunk-CFKGNAJM.js.map +1 -0
  57. package/dist/chunk-GAMA3VA7.js +99 -0
  58. package/dist/chunk-GAMA3VA7.js.map +1 -0
  59. package/dist/chunk-GYXTSY22.js +639 -0
  60. package/dist/chunk-GYXTSY22.js.map +1 -0
  61. package/dist/chunk-K4KGNVL5.js +77 -0
  62. package/dist/chunk-K4KGNVL5.js.map +1 -0
  63. package/dist/chunk-KQV7IKET.js +1621 -0
  64. package/dist/chunk-KQV7IKET.js.map +1 -0
  65. package/dist/chunk-O3NXUM6C.js +1871 -0
  66. package/dist/chunk-O3NXUM6C.js.map +1 -0
  67. package/dist/chunk-PZXSASDY.js +83 -0
  68. package/dist/chunk-PZXSASDY.js.map +1 -0
  69. package/dist/chunk-QLVPIM6R.js +595 -0
  70. package/dist/chunk-QLVPIM6R.js.map +1 -0
  71. package/dist/chunk-VXJWGLZ7.js +21 -0
  72. package/dist/chunk-VXJWGLZ7.js.map +1 -0
  73. package/dist/chunk-XQ562W7I.js +116 -0
  74. package/dist/chunk-XQ562W7I.js.map +1 -0
  75. package/dist/components/JsonRender/standalone.cjs +39368 -0
  76. package/dist/components/JsonRender/standalone.cjs.map +1 -0
  77. package/dist/components/JsonRender/standalone.css +2384 -0
  78. package/dist/components/JsonRender/standalone.css.map +1 -0
  79. package/dist/components/JsonRender/standalone.d.cts +16 -0
  80. package/dist/components/JsonRender/standalone.d.ts +16 -0
  81. package/dist/components/JsonRender/standalone.js +38 -0
  82. package/dist/components/JsonRender/standalone.js.map +1 -0
  83. package/dist/gerber-2d-entry-OQ4SQRBY.js +3950 -0
  84. package/dist/gerber-2d-entry-OQ4SQRBY.js.map +1 -0
  85. package/dist/gerber-3d-entry-DEHDBOO2.js +3679 -0
  86. package/dist/gerber-3d-entry-DEHDBOO2.js.map +1 -0
  87. package/dist/gerber-simulation-entry-EBDX72XE.js +1801 -0
  88. package/dist/gerber-simulation-entry-EBDX72XE.js.map +1 -0
  89. package/dist/index.cjs +60113 -2970
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.css +11342 -1708
  92. package/dist/index.css.map +1 -1
  93. package/dist/index.d.cts +3275 -77
  94. package/dist/index.d.ts +3275 -77
  95. package/dist/index.js +18078 -2540
  96. package/dist/index.js.map +1 -1
  97. package/dist/provenance/index.cjs +2248 -0
  98. package/dist/provenance/index.cjs.map +1 -0
  99. package/dist/provenance/index.css +52 -0
  100. package/dist/provenance/index.css.map +1 -0
  101. package/dist/provenance/index.d.cts +12 -0
  102. package/dist/provenance/index.d.ts +12 -0
  103. package/dist/provenance/index.js +27 -0
  104. package/dist/provenance/index.js.map +1 -0
  105. package/dist/resolveToArrayBuffer-AQIDZHSQ.js +23 -0
  106. package/dist/resolveToArrayBuffer-AQIDZHSQ.js.map +1 -0
  107. package/package.json +98 -17
@@ -0,0 +1,1801 @@
1
+ import {
2
+ GerberParser
3
+ } from "./chunk-O3NXUM6C.js";
4
+ import {
5
+ extractRar,
6
+ fetchFileById,
7
+ fetchFileByUrl,
8
+ parseUrlFileUrl,
9
+ parseUrlId,
10
+ require_lib
11
+ } from "./chunk-7A4FY6FK.js";
12
+ import {
13
+ __toESM
14
+ } from "./chunk-7D4SUZUM.js";
15
+
16
+ // src/components/jetPaveGerberViewer/src/viewer-src/simulation/js/main.js
17
+ var import_jszip = __toESM(require_lib(), 1);
18
+ var FINISH_COLORS = {
19
+ "ENIG": "#D4AF37",
20
+ // 沉金 - 金色
21
+ "HASL": "#C0C0C0",
22
+ // 喷锡 - 银色
23
+ "OSP": "#B87333",
24
+ // OSP - 铜色
25
+ "SILVER": "#E8E8E8",
26
+ // 镀银 - 亮银色
27
+ "ENEPIG": "#E6C87A"
28
+ // 化镍钯金 - 浅金色
29
+ };
30
+ var layers = {};
31
+ var boardBounds = null;
32
+ var refBounds = null;
33
+ var currentView = "top";
34
+ var zoomLevel = 1;
35
+ var panOffset = { x: 0, y: 0 };
36
+ var isDragging = false;
37
+ var lastMousePos = { x: 0, y: 0 };
38
+ var canvas;
39
+ var ctx;
40
+ var loadingOverlay;
41
+ var loadingText;
42
+ var statusLeft;
43
+ var statusRight;
44
+ var config = {
45
+ maskColor: "#1C590B",
46
+ silkColor: "#ffffff",
47
+ surfaceFinish: "ENIG",
48
+ showMask: true,
49
+ showSilk: true,
50
+ showDrill: true
51
+ };
52
+ var unitScale = 1;
53
+ var DEBUG = true;
54
+ var layerCache = {
55
+ top: null,
56
+ bottom: null,
57
+ dirty: true,
58
+ // 是否需要重新渲染
59
+ lastWidth: 0,
60
+ lastHeight: 0
61
+ };
62
+ var renderRAF = null;
63
+ if (DEBUG) console.log("[simulation] main.js \u5DF2\u52A0\u8F7D");
64
+ function initSimulation() {
65
+ if (DEBUG) console.log("[simulation] \u5F00\u59CB\u521D\u59CB\u5316");
66
+ initUI();
67
+ initEventListeners();
68
+ initUrlHandler();
69
+ }
70
+ if (document.readyState === "loading") {
71
+ document.addEventListener("DOMContentLoaded", initSimulation);
72
+ } else {
73
+ initSimulation();
74
+ }
75
+ function initUI() {
76
+ canvas = document.getElementById("simulationCanvas");
77
+ ctx = canvas.getContext("2d");
78
+ loadingOverlay = document.getElementById("loadingOverlay");
79
+ loadingText = document.getElementById("loadingText");
80
+ statusLeft = document.getElementById("statusLeft");
81
+ statusRight = document.getElementById("statusRight");
82
+ render();
83
+ }
84
+ function initEventListeners() {
85
+ const fileInput = document.getElementById("fileInput");
86
+ fileInput.addEventListener("change", async (e) => {
87
+ const files = Array.from(e.target.files);
88
+ if (files.length > 0) {
89
+ await handleFiles(files);
90
+ }
91
+ });
92
+ document.getElementById("btnTopView").addEventListener("click", () => setView("top"));
93
+ document.getElementById("btnBottomView").addEventListener("click", () => setView("bottom"));
94
+ document.getElementById("maskColorSelect").addEventListener("change", (e) => {
95
+ config.maskColor = e.target.value;
96
+ document.getElementById("maskColorPreview").style.background = e.target.value;
97
+ invalidateCache();
98
+ render();
99
+ });
100
+ document.getElementById("silkColorSelect").addEventListener("change", (e) => {
101
+ config.silkColor = e.target.value;
102
+ document.getElementById("silkColorPreview").style.background = e.target.value;
103
+ invalidateCache();
104
+ render();
105
+ });
106
+ document.getElementById("finishSelect").addEventListener("change", (e) => {
107
+ config.surfaceFinish = e.target.value;
108
+ document.getElementById("finishColorPreview").style.background = FINISH_COLORS[e.target.value];
109
+ invalidateCache();
110
+ render();
111
+ });
112
+ document.getElementById("showMask").addEventListener("change", (e) => {
113
+ config.showMask = e.target.checked;
114
+ invalidateCache();
115
+ render();
116
+ });
117
+ document.getElementById("showSilk").addEventListener("change", (e) => {
118
+ config.showSilk = e.target.checked;
119
+ invalidateCache();
120
+ render();
121
+ });
122
+ document.getElementById("showDrill").addEventListener("change", (e) => {
123
+ config.showDrill = e.target.checked;
124
+ invalidateCache();
125
+ render();
126
+ });
127
+ document.getElementById("btnExport").addEventListener("click", exportImage);
128
+ document.getElementById("btnClear").addEventListener("click", clearAll);
129
+ const canvasContainer = document.getElementById("canvasContainer");
130
+ canvasContainer.addEventListener("wheel", (e) => {
131
+ e.preventDefault();
132
+ if (!boardBounds) return;
133
+ const delta = e.deltaY > 0 ? 0.9 : 1.1;
134
+ zoomLevel *= delta;
135
+ zoomLevel = Math.max(0.1, Math.min(10, zoomLevel));
136
+ render();
137
+ });
138
+ canvas.addEventListener("mousedown", (e) => {
139
+ if (e.button === 0) {
140
+ isDragging = true;
141
+ lastMousePos = { x: e.clientX, y: e.clientY };
142
+ canvas.style.cursor = "grabbing";
143
+ }
144
+ });
145
+ window.addEventListener("mousemove", (e) => {
146
+ if (isDragging) {
147
+ const dx = e.clientX - lastMousePos.x;
148
+ const dy = e.clientY - lastMousePos.y;
149
+ panOffset.x += dx;
150
+ panOffset.y += dy;
151
+ lastMousePos = { x: e.clientX, y: e.clientY };
152
+ render();
153
+ }
154
+ });
155
+ window.addEventListener("mouseup", () => {
156
+ if (isDragging) {
157
+ isDragging = false;
158
+ canvas.style.cursor = "grab";
159
+ }
160
+ });
161
+ }
162
+ function initUrlHandler() {
163
+ if (window.isRenderPage) {
164
+ if (DEBUG) console.log("[simulation] \u5728 render \u9875\u9762\u4E2D\u8FD0\u884C\uFF0C\u8DF3\u8FC7 URL \u5904\u7406");
165
+ return;
166
+ }
167
+ if (DEBUG) console.log("[simulation] initUrlHandler \u88AB\u8C03\u7528, URL:", window.location.href);
168
+ const id = parseUrlId();
169
+ const url = parseUrlFileUrl();
170
+ if (DEBUG) console.log("[simulation] \u89E3\u6790\u5230\u7684 ID:", id, "URL:", url);
171
+ if (id) {
172
+ if (DEBUG) console.log("[simulation] \u5F00\u59CB\u4ECE\u63A5\u53E3\u52A0\u8F7D\u6587\u4EF6, ID:", id);
173
+ loadFromId(id);
174
+ return;
175
+ }
176
+ if (url) {
177
+ if (DEBUG) console.log("[simulation] \u5F00\u59CB\u4ECE\u94FE\u63A5\u52A0\u8F7D\u6587\u4EF6:", url);
178
+ loadFromUrl(url);
179
+ }
180
+ }
181
+ async function handleFiles(files) {
182
+ console.log("[\u6027\u80FD] handleFiles \u5F00\u59CB");
183
+ const startTimeTotal = performance.now();
184
+ showLoading("\u6B63\u5728\u89E3\u6790\u6587\u4EF6...");
185
+ try {
186
+ console.log("[\u6027\u80FD] \u5F00\u59CB\u89E3\u538B\u6587\u4EF6...");
187
+ const startTimeExtract = performance.now();
188
+ let gerberFiles = [];
189
+ for (const file of files) {
190
+ const ext = file.name.split(".").pop().toLowerCase();
191
+ if (ext === "zip") {
192
+ console.log(`[\u6027\u80FD] \u89E3\u538B ZIP: ${file.name}`);
193
+ const startTimeZip = performance.now();
194
+ const extracted = await extractZip(file);
195
+ const endTimeZip = performance.now();
196
+ console.log(`[\u6027\u80FD] ZIP \u89E3\u538B\u5B8C\u6210\uFF0C\u8017\u65F6: ${(endTimeZip - startTimeZip).toFixed(2)}ms, \u6587\u4EF6\u6570: ${extracted.length}`);
197
+ gerberFiles.push(...extracted);
198
+ } else if (ext === "rar") {
199
+ console.log(`[\u6027\u80FD] \u89E3\u538B RAR: ${file.name}`);
200
+ const startTimeRar = performance.now();
201
+ try {
202
+ const extracted = await extractRar(file);
203
+ const endTimeRar = performance.now();
204
+ console.log(`[\u6027\u80FD] RAR \u89E3\u538B\u5B8C\u6210\uFF0C\u8017\u65F6: ${(endTimeRar - startTimeRar).toFixed(2)}ms, \u6587\u4EF6\u6570: ${extracted.length}`);
205
+ gerberFiles.push(...extracted);
206
+ } catch (rarError) {
207
+ if (rarError.isActualZip) {
208
+ const extracted = await extractZip(file);
209
+ const endTimeRar = performance.now();
210
+ console.log(`[\u6027\u80FD] RAR(\u5B9E\u4E3AZIP) \u89E3\u538B\u5B8C\u6210\uFF0C\u8017\u65F6: ${(endTimeRar - startTimeRar).toFixed(2)}ms, \u6587\u4EF6\u6570: ${extracted.length}`);
211
+ gerberFiles.push(...extracted);
212
+ } else {
213
+ throw rarError;
214
+ }
215
+ }
216
+ } else {
217
+ gerberFiles.push(file);
218
+ }
219
+ }
220
+ const endTimeExtract = performance.now();
221
+ console.log(`[\u6027\u80FD] \u6587\u4EF6\u89E3\u538B\u603B\u8017\u65F6: ${(endTimeExtract - startTimeExtract).toFixed(2)}ms, \u89E3\u538B\u540E\u6587\u4EF6\u6570: ${gerberFiles.length}`);
222
+ if (gerberFiles.length === 0) {
223
+ throw new Error("\u672A\u627E\u5230\u6709\u6548\u7684 Gerber \u6587\u4EF6");
224
+ }
225
+ console.log("[\u6027\u80FD] \u5F00\u59CB\u89E3\u6790 Gerber \u6587\u4EF6...");
226
+ const startTimeParse = performance.now();
227
+ await parseGerberFiles(gerberFiles);
228
+ const endTimeParse = performance.now();
229
+ console.log(`[\u6027\u80FD] Gerber \u6587\u4EF6\u89E3\u6790\u5B8C\u6210\uFF0C\u8017\u65F6: ${((endTimeParse - startTimeParse) / 1e3).toFixed(2)}\u79D2`);
230
+ console.log("[\u6027\u80FD] \u5F00\u59CB\u66F4\u65B0 UI \u548C\u6E32\u67D3...");
231
+ const startTimeRender = performance.now();
232
+ invalidateCache();
233
+ updateLayerList();
234
+ fitToView();
235
+ render();
236
+ const endTimeRender = performance.now();
237
+ console.log(`[\u6027\u80FD] UI \u66F4\u65B0\u548C\u6E32\u67D3\u5B8C\u6210\uFF0C\u8017\u65F6: ${(endTimeRender - startTimeRender).toFixed(2)}ms`);
238
+ const endTimeTotal = performance.now();
239
+ const totalTime = (endTimeTotal - startTimeTotal) / 1e3;
240
+ console.log(`[\u6027\u80FD] ========== handleFiles \u5B8C\u6210\uFF0C\u603B\u8017\u65F6: ${totalTime.toFixed(2)}\u79D2 ==========`);
241
+ console.log(`[\u6027\u80FD] \u8BE6\u7EC6\u8017\u65F6\uFF1A\u89E3\u538B=${((endTimeExtract - startTimeExtract) / 1e3).toFixed(2)}\u79D2, \u89E3\u6790=${((endTimeParse - startTimeParse) / 1e3).toFixed(2)}\u79D2, \u6E32\u67D3=${((endTimeRender - startTimeRender) / 1e3).toFixed(2)}\u79D2`);
242
+ hideLoading();
243
+ updateStatus(`\u5DF2\u52A0\u8F7D ${Object.keys(layers).length} \u4E2A\u56FE\u5C42`);
244
+ } catch (error) {
245
+ const endTimeTotal = performance.now();
246
+ const totalTime = (endTimeTotal - startTimeTotal) / 1e3;
247
+ console.error(`[\u9519\u8BEF] \u5904\u7406\u6587\u4EF6\u5931\u8D25 (\u8017\u65F6: ${totalTime.toFixed(2)}\u79D2):`, error);
248
+ hideLoading();
249
+ updateStatus("\u9519\u8BEF: " + error.message, true);
250
+ }
251
+ }
252
+ async function loadFromId(id) {
253
+ if (DEBUG) console.log("[simulation] loadFromId \u88AB\u8C03\u7528, ID:", id);
254
+ const startTimeTotal = performance.now();
255
+ showLoading("\u6B63\u5728\u4ECE\u670D\u52A1\u5668\u52A0\u8F7D...");
256
+ try {
257
+ if (DEBUG) console.log("[simulation] \u4F7F\u7528 fetchFileById \u83B7\u53D6\u6587\u4EF6...");
258
+ await fetchFileById(
259
+ id,
260
+ async (files) => {
261
+ if (DEBUG) console.log("[simulation] \u6536\u5230\u6587\u4EF6\uFF0C\u5F00\u59CB\u5904\u7406...");
262
+ await handleFiles(files);
263
+ const finalTime = performance.now();
264
+ const grandTotal = (finalTime - startTimeTotal) / 1e3;
265
+ console.log(`[\u6027\u80FD] ========== \u4ECE\u5F00\u59CB\u52A0\u8F7D\u5230\u5904\u7406\u5B8C\u6210\u603B\u8017\u65F6: ${grandTotal.toFixed(2)}\u79D2 ==========`);
266
+ },
267
+ (message, type) => {
268
+ console.log(`[simulation] \u72B6\u6001: ${message}`);
269
+ }
270
+ );
271
+ } catch (error) {
272
+ const endTime = performance.now();
273
+ const errorTime = (endTime - startTimeTotal) / 1e3;
274
+ console.error(`[simulation] \u83B7\u53D6\u6587\u4EF6\u5931\u8D25 (\u8017\u65F6: ${errorTime.toFixed(2)}\u79D2):`, error);
275
+ hideLoading();
276
+ updateStatus(`\u83B7\u53D6\u6587\u4EF6\u5931\u8D25: ${error.message}`, true);
277
+ }
278
+ }
279
+ async function loadFromUrl(url) {
280
+ if (DEBUG) console.log("[simulation] loadFromUrl \u88AB\u8C03\u7528, URL:", url);
281
+ const startTimeTotal = performance.now();
282
+ showLoading("\u6B63\u5728\u4ECE\u94FE\u63A5\u52A0\u8F7D...");
283
+ try {
284
+ if (DEBUG) console.log("[simulation] \u4F7F\u7528 fetchFileByUrl \u83B7\u53D6\u6587\u4EF6...");
285
+ await fetchFileByUrl(
286
+ url,
287
+ async (files) => {
288
+ if (DEBUG) console.log("[simulation] \u6536\u5230\u6587\u4EF6\uFF0C\u5F00\u59CB\u5904\u7406...");
289
+ await handleFiles(files);
290
+ const finalTime = performance.now();
291
+ const grandTotal = (finalTime - startTimeTotal) / 1e3;
292
+ console.log(`[\u6027\u80FD] ========== \u4ECE\u5F00\u59CB\u52A0\u8F7D\u5230\u5904\u7406\u5B8C\u6210\u603B\u8017\u65F6: ${grandTotal.toFixed(2)}\u79D2 ==========`);
293
+ },
294
+ (message, type) => {
295
+ console.log(`[simulation] \u72B6\u6001: ${message}`);
296
+ }
297
+ );
298
+ } catch (error) {
299
+ const endTime = performance.now();
300
+ const errorTime = (endTime - startTimeTotal) / 1e3;
301
+ console.error(`[simulation] \u83B7\u53D6\u6587\u4EF6\u5931\u8D25 (\u8017\u65F6: ${errorTime.toFixed(2)}\u79D2):`, error);
302
+ hideLoading();
303
+ updateStatus(`\u83B7\u53D6\u6587\u4EF6\u5931\u8D25: ${error.message}`, true);
304
+ }
305
+ }
306
+ async function extractZip(zipFile) {
307
+ const zip = await import_jszip.default.loadAsync(zipFile);
308
+ const files = [];
309
+ for (const filename in zip.files) {
310
+ if (zip.files[filename].dir) continue;
311
+ if (!isGerberFile(filename)) continue;
312
+ const content = await zip.files[filename].async("blob");
313
+ files.push(new File([content], filename));
314
+ }
315
+ return files;
316
+ }
317
+ function isGerberFile(fileName) {
318
+ const validExtensions = [
319
+ ".gtl",
320
+ ".gbl",
321
+ ".gts",
322
+ ".gbs",
323
+ ".gto",
324
+ ".gbo",
325
+ ".gtp",
326
+ ".gbp",
327
+ ".gm1",
328
+ ".drl",
329
+ ".gbr",
330
+ ".gko",
331
+ ".gdl",
332
+ ".gdd",
333
+ ".gm",
334
+ ".gd1",
335
+ ".gg1",
336
+ ".gpb",
337
+ ".gpt",
338
+ ".txt",
339
+ ".art",
340
+ ".d",
341
+ ".rou",
342
+ ".pho"
343
+ ];
344
+ const ext = "." + fileName.split(".").pop().toLowerCase();
345
+ return validExtensions.includes(ext) || /^\.g\d+$/i.test(ext) || /^\.gm\d+$/i.test(ext) || /^\.gp\d+$/i.test(ext);
346
+ }
347
+ async function parseGerberFiles(files) {
348
+ layers = {};
349
+ boardBounds = null;
350
+ refBounds = null;
351
+ const parseQueue = [];
352
+ for (const file of files) {
353
+ const layerType = getLayerType(file.name);
354
+ if (!layerType) continue;
355
+ const isDrill = layerType === "DRL" || layerType === "DRL2";
356
+ parseQueue.push({ file, layerType, isDrill });
357
+ }
358
+ parseQueue.sort((a, b) => a.isDrill === b.isDrill ? 0 : a.isDrill ? 1 : -1);
359
+ if (DEBUG) console.log("[\u4EFF\u771F\u56FE] \u6587\u4EF6\u89E3\u6790\u987A\u5E8F:", parseQueue.map((x) => x.file.name));
360
+ for (const { file, layerType } of parseQueue) {
361
+ try {
362
+ showLoading(`\u6B63\u5728\u89E3\u6790 ${file.name}...`);
363
+ const result = await GerberParser.parseFile(file, "#ffffff");
364
+ if (!result || !result.data) {
365
+ console.warn(`\u89E3\u6790 ${file.name} \u8FD4\u56DE\u7A7A\u6570\u636E`);
366
+ continue;
367
+ }
368
+ if ((layerType === "DRL" || layerType === "DRL2") && layers[layerType]) {
369
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u5408\u5E76\u94BB\u5B54\u5C42 ${layerType}: ${file.name}`);
370
+ const existing = layers[layerType];
371
+ if (result.data.vertices && existing.data.vertices) {
372
+ const newVertices = new Float32Array(existing.data.vertices.length + result.data.vertices.length);
373
+ newVertices.set(existing.data.vertices, 0);
374
+ newVertices.set(result.data.vertices, existing.data.vertices.length);
375
+ existing.data.vertices = newVertices;
376
+ }
377
+ if (result.data.lineStrips && existing.data.lineStrips) {
378
+ const vertexOffset = existing.data.vertices ? (existing.data.vertices.length - result.data.vertices.length) / 3 : 0;
379
+ const adjustedStrips = result.data.lineStrips.map((strip) => ({
380
+ start: strip.start + vertexOffset,
381
+ count: strip.count
382
+ }));
383
+ existing.data.lineStrips = [...existing.data.lineStrips, ...adjustedStrips];
384
+ }
385
+ if (result.bounds && existing.bounds) {
386
+ existing.bounds.minX = Math.min(existing.bounds.minX, result.bounds.minX);
387
+ existing.bounds.minY = Math.min(existing.bounds.minY, result.bounds.minY);
388
+ existing.bounds.maxX = Math.max(existing.bounds.maxX, result.bounds.maxX);
389
+ existing.bounds.maxY = Math.max(existing.bounds.maxY, result.bounds.maxY);
390
+ }
391
+ existing.name += `, ${file.name.split(/[/\\]/).pop()}`;
392
+ } else {
393
+ layers[layerType] = {
394
+ name: file.name,
395
+ type: layerType,
396
+ data: result.data,
397
+ bounds: result.bounds,
398
+ units: result.data.units || "mm"
399
+ };
400
+ }
401
+ } catch (error) {
402
+ console.warn(`\u89E3\u6790 ${file.name} \u5931\u8D25:`, error);
403
+ }
404
+ }
405
+ const refLayerTypes = ["GTL", "GBL", "GTS", "GBS", "GTO", "GBO"];
406
+ for (const type of refLayerTypes) {
407
+ const layer = layers[type];
408
+ if (!layer || !layer.bounds) continue;
409
+ const b = layer.bounds;
410
+ if (!refBounds) {
411
+ refBounds = { minX: b.minX, minY: b.minY, maxX: b.maxX, maxY: b.maxY };
412
+ } else {
413
+ refBounds.minX = Math.min(refBounds.minX, b.minX);
414
+ refBounds.minY = Math.min(refBounds.minY, b.minY);
415
+ refBounds.maxX = Math.max(refBounds.maxX, b.maxX);
416
+ refBounds.maxY = Math.max(refBounds.maxY, b.maxY);
417
+ }
418
+ }
419
+ for (const type of Object.keys(layers)) {
420
+ if (!/^SIG\d+$/i.test(type)) continue;
421
+ const layer = layers[type];
422
+ if (!layer || !layer.bounds) continue;
423
+ const b = layer.bounds;
424
+ if (!refBounds) {
425
+ refBounds = { minX: b.minX, minY: b.minY, maxX: b.maxX, maxY: b.maxY };
426
+ } else {
427
+ refBounds.minX = Math.min(refBounds.minX, b.minX);
428
+ refBounds.minY = Math.min(refBounds.minY, b.minY);
429
+ refBounds.maxX = Math.max(refBounds.maxX, b.maxX);
430
+ refBounds.maxY = Math.max(refBounds.maxY, b.maxY);
431
+ }
432
+ }
433
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u53C2\u8003\u8FB9\u754C\uFF08\u94DC\u5C42/\u963B\u710A\u5C42\uFF09:`, refBounds);
434
+ if (DEBUG) {
435
+ console.log(`[\u4EFF\u771F\u56FE] ===== \u5404\u56FE\u5C42\u4FE1\u606F =====`);
436
+ for (const [type, layer] of Object.entries(layers)) {
437
+ const b = layer.bounds;
438
+ const bStr = b ? `(${b.minX.toFixed(3)}, ${b.minY.toFixed(3)}) - (${b.maxX.toFixed(3)}, ${b.maxY.toFixed(3)})` : "null";
439
+ console.log(`[\u4EFF\u771F\u56FE] ${type}: units=${layer.units}, bounds=${bStr}`);
440
+ }
441
+ console.log(`[\u4EFF\u771F\u56FE] ===========================`);
442
+ }
443
+ if (refBounds) {
444
+ boardBounds = [refBounds.minX, refBounds.minY, refBounds.maxX, refBounds.maxY];
445
+ } else {
446
+ for (const [type, layer] of Object.entries(layers)) {
447
+ if (type === "GKO" || type === "DRL" || type === "DRL2") continue;
448
+ if (!layer.bounds) continue;
449
+ const b = layer.bounds;
450
+ if (!boardBounds) {
451
+ boardBounds = [b.minX, b.minY, b.maxX, b.maxY];
452
+ } else {
453
+ boardBounds[0] = Math.min(boardBounds[0], b.minX);
454
+ boardBounds[1] = Math.min(boardBounds[1], b.minY);
455
+ boardBounds[2] = Math.max(boardBounds[2], b.maxX);
456
+ boardBounds[3] = Math.max(boardBounds[3], b.maxY);
457
+ }
458
+ }
459
+ }
460
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u677F\u5B50\u8FB9\u754C:`, boardBounds);
461
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u5DF2\u52A0\u8F7D\u56FE\u5C42:`, Object.keys(layers));
462
+ checkAndFixDrillAlignment();
463
+ }
464
+ function checkAndFixDrillAlignment() {
465
+ if (!refBounds) {
466
+ if (DEBUG) console.log("[\u94BB\u5B54\u5BF9\u9F50] \u672A\u627E\u5230\u53C2\u8003\u8FB9\u754C\uFF0C\u8DF3\u8FC7\u5BF9\u9F50\u68C0\u67E5");
467
+ return;
468
+ }
469
+ const refLayerTypes = ["GTL", "GBL", "GTS", "GBS", "GTO", "GBO"];
470
+ let refLayerName = "REF";
471
+ for (const t of refLayerTypes) {
472
+ if (layers[t] && layers[t].bounds) {
473
+ refLayerName = t;
474
+ break;
475
+ }
476
+ }
477
+ if (refLayerName === "REF") {
478
+ const sigKeys = Object.keys(layers).filter((k) => /^SIG\d+$/i.test(k)).sort();
479
+ for (const t of sigKeys) {
480
+ if (layers[t] && layers[t].bounds) {
481
+ refLayerName = t;
482
+ break;
483
+ }
484
+ }
485
+ }
486
+ const computeOverflow = (b, ref) => {
487
+ const left = Math.max(0, ref.minX - b.minX);
488
+ const right = Math.max(0, b.maxX - ref.maxX);
489
+ const bottom = Math.max(0, ref.minY - b.minY);
490
+ const top = Math.max(0, b.maxY - ref.maxY);
491
+ const total = left + right + bottom + top;
492
+ return { left, right, bottom, top, total };
493
+ };
494
+ const translateBounds = (b, dx, dy) => ({
495
+ minX: b.minX + dx,
496
+ maxX: b.maxX + dx,
497
+ minY: b.minY + dy,
498
+ maxY: b.maxY + dy
499
+ });
500
+ const extractCircleCenters = (layerData, layerName = "") => {
501
+ if (!layerData || !layerData.vertices || !layerData.lineStrips) {
502
+ if (DEBUG) console.log(`[\u7279\u5F81\u63D0\u53D6] ${layerName}: \u6570\u636E\u65E0\u6548`);
503
+ return [];
504
+ }
505
+ const centers = [];
506
+ const vertices = layerData.vertices;
507
+ const lineStrips = layerData.lineStrips;
508
+ if (DEBUG) console.log(`[\u7279\u5F81\u63D0\u53D6] ${layerName}: \u5171\u6709 ${lineStrips.length} \u4E2A lineStrips`);
509
+ let skippedTooFew = 0;
510
+ let skippedAspect = 0;
511
+ let skippedTooSmall = 0;
512
+ for (const strip of lineStrips) {
513
+ if (strip.count < 6) {
514
+ skippedTooFew++;
515
+ continue;
516
+ }
517
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
518
+ for (let i = 0; i < strip.count; i++) {
519
+ const ptr = (strip.start + i) * 3;
520
+ const x = vertices[ptr];
521
+ const y = vertices[ptr + 1];
522
+ minX = Math.min(minX, x);
523
+ minY = Math.min(minY, y);
524
+ maxX = Math.max(maxX, x);
525
+ maxY = Math.max(maxY, y);
526
+ }
527
+ const w = maxX - minX;
528
+ const h = maxY - minY;
529
+ const aspectRatio = Math.max(w, h) / Math.max(1e-4, Math.min(w, h));
530
+ if (aspectRatio > 2) {
531
+ skippedAspect++;
532
+ continue;
533
+ }
534
+ if (w < 0.01 || h < 0.01) {
535
+ skippedTooSmall++;
536
+ continue;
537
+ }
538
+ const cx = (minX + maxX) / 2;
539
+ const cy = (minY + maxY) / 2;
540
+ const r = (w + h) / 4;
541
+ centers.push({ x: cx, y: cy, r });
542
+ }
543
+ if (DEBUG) {
544
+ console.log(`[\u7279\u5F81\u63D0\u53D6] ${layerName}: \u63D0\u53D6\u4E86 ${centers.length} \u4E2A\u5706\u5F62\u7279\u5F81\u70B9 (\u8DF3\u8FC7: \u70B9\u6570\u4E0D\u8DB3=${skippedTooFew}, \u5BBD\u9AD8\u6BD4\u5F02\u5E38=${skippedAspect}, \u5C3A\u5BF8\u592A\u5C0F=${skippedTooSmall})`);
545
+ if (centers.length > 0) {
546
+ const showCount = Math.min(5, centers.length);
547
+ console.log(`[\u7279\u5F81\u63D0\u53D6] ${layerName}: \u524D ${showCount} \u4E2A\u7279\u5F81\u70B9\u5750\u6807:`);
548
+ for (let i = 0; i < showCount; i++) {
549
+ console.log(` ${i}: (${centers[i].x.toFixed(4)}, ${centers[i].y.toFixed(4)}), \u76F4\u5F84=${(centers[i].r * 2).toFixed(4)}`);
550
+ }
551
+ }
552
+ }
553
+ return centers;
554
+ };
555
+ const estimateOffsetByVoting = (drillCenters, refCenters, binSize = 0.5) => {
556
+ if (drillCenters.length < 2 || refCenters.length < 2) return null;
557
+ const coarseBinSize = Math.max(binSize, 0.02);
558
+ const votes = /* @__PURE__ */ new Map();
559
+ for (const drill of drillCenters) {
560
+ for (const ref of refCenters) {
561
+ const dx = ref.x - drill.x;
562
+ const dy = ref.y - drill.y;
563
+ const qx = Math.round(dx / coarseBinSize) * coarseBinSize;
564
+ const qy = Math.round(dy / coarseBinSize) * coarseBinSize;
565
+ const key = `${qx.toFixed(3)}_${qy.toFixed(3)}`;
566
+ if (!votes.has(key)) {
567
+ votes.set(key, { dx: qx, dy: qy, count: 0, pairs: [] });
568
+ }
569
+ const v = votes.get(key);
570
+ v.count++;
571
+ v.pairs.push({ drill, ref, dx, dy });
572
+ }
573
+ }
574
+ const allVotes = Array.from(votes.values()).sort((a, b) => b.count - a.count);
575
+ if (DEBUG && allVotes.length > 0) {
576
+ const topVotes = allVotes.slice(0, 5);
577
+ console.log(`[\u6295\u7968] \u7C97\u5339\u914D\u524D ${topVotes.length} \u4E2A\u5019\u9009\u504F\u79FB (binSize=${coarseBinSize}):`);
578
+ topVotes.forEach((v, i) => {
579
+ console.log(` ${i + 1}. dx=${v.dx.toFixed(2)}, dy=${v.dy.toFixed(2)}, votes=${v.count}`);
580
+ });
581
+ }
582
+ if (allVotes.length === 0 || allVotes[0].count < 2) return null;
583
+ const bestCoarse = allVotes[0];
584
+ const tolerance = coarseBinSize * 1.5;
585
+ const sizeToleranceRatio = 3;
586
+ const allPairs = [];
587
+ for (let di = 0; di < drillCenters.length; di++) {
588
+ const drill = drillCenters[di];
589
+ for (let ri = 0; ri < refCenters.length; ri++) {
590
+ const ref = refCenters[ri];
591
+ const drillR = drill.r || 0;
592
+ const refR = ref.r || 0;
593
+ if (drillR > 0 && refR > 0) {
594
+ const sizeRatio = Math.max(drillR, refR) / Math.min(drillR, refR);
595
+ if (sizeRatio > sizeToleranceRatio) {
596
+ continue;
597
+ }
598
+ }
599
+ const shiftedDx = drill.x + bestCoarse.dx - ref.x;
600
+ const shiftedDy = drill.y + bestCoarse.dy - ref.y;
601
+ const dist = Math.sqrt(shiftedDx * shiftedDx + shiftedDy * shiftedDy);
602
+ if (dist < tolerance) {
603
+ allPairs.push({
604
+ drillIdx: di,
605
+ refIdx: ri,
606
+ drill,
607
+ ref,
608
+ dist,
609
+ dx: ref.x - drill.x,
610
+ dy: ref.y - drill.y
611
+ });
612
+ }
613
+ }
614
+ }
615
+ allPairs.sort((a, b) => a.dist - b.dist);
616
+ const usedDrill = /* @__PURE__ */ new Set();
617
+ const usedRef = /* @__PURE__ */ new Set();
618
+ const refinedPairs = [];
619
+ for (const pair of allPairs) {
620
+ if (usedDrill.has(pair.drillIdx) || usedRef.has(pair.refIdx)) continue;
621
+ refinedPairs.push(pair);
622
+ usedDrill.add(pair.drillIdx);
623
+ usedRef.add(pair.refIdx);
624
+ }
625
+ if (refinedPairs.length < 2) {
626
+ return {
627
+ dx: bestCoarse.dx,
628
+ dy: bestCoarse.dy,
629
+ votes: bestCoarse.count
630
+ };
631
+ }
632
+ const tolRefine = 0.012;
633
+ const matchedPairs = [];
634
+ const usedRefIndices = /* @__PURE__ */ new Set();
635
+ for (const drill of drillCenters) {
636
+ const transformedX = drill.x + bestCoarse.dx;
637
+ const transformedY = drill.y + bestCoarse.dy;
638
+ let minDistance = Infinity;
639
+ let nearestRef = null;
640
+ let nearestIdx = -1;
641
+ for (let i = 0; i < refCenters.length; i++) {
642
+ if (usedRefIndices.has(i)) continue;
643
+ const ref = refCenters[i];
644
+ const dist = Math.sqrt(Math.pow(transformedX - ref.x, 2) + Math.pow(transformedY - ref.y, 2));
645
+ if (dist < minDistance) {
646
+ minDistance = dist;
647
+ nearestRef = ref;
648
+ nearestIdx = i;
649
+ }
650
+ }
651
+ if (minDistance <= tolRefine && nearestIdx !== -1) {
652
+ usedRefIndices.add(nearestIdx);
653
+ matchedPairs.push({
654
+ drill,
655
+ ref: nearestRef,
656
+ dx: nearestRef.x - drill.x,
657
+ dy: nearestRef.y - drill.y,
658
+ error: minDistance
659
+ });
660
+ }
661
+ }
662
+ if (matchedPairs.length < 2) {
663
+ return {
664
+ dx: bestCoarse.dx,
665
+ dy: bestCoarse.dy,
666
+ votes: bestCoarse.count
667
+ };
668
+ }
669
+ let sumDx = 0, sumDy = 0;
670
+ for (const p of matchedPairs) {
671
+ sumDx += p.dx;
672
+ sumDy += p.dy;
673
+ }
674
+ const finalDx = sumDx / matchedPairs.length;
675
+ const finalDy = sumDy / matchedPairs.length;
676
+ if (DEBUG) {
677
+ console.log(`[\u6295\u7968] \u7CBE\u7EC6\u5316(2dPage\u65B9\u5F0F): \u7C97\u5339\u914D=(${bestCoarse.dx.toFixed(3)}, ${bestCoarse.dy.toFixed(3)}), \u5339\u914D\u5BF9=${matchedPairs.length}/${drillCenters.length}`);
678
+ const errors = matchedPairs.map((p) => p.error);
679
+ const avgError = errors.reduce((s, v) => s + v, 0) / errors.length;
680
+ const maxError = Math.max(...errors);
681
+ console.log(`[\u6295\u7968] \u5339\u914D\u8BEF\u5DEE: avg=${avgError.toFixed(4)}, max=${maxError.toFixed(4)}`);
682
+ console.log(`[\u6295\u7968] \u914D\u5BF9\u8BE6\u60C5:`);
683
+ for (let i = 0; i < Math.min(matchedPairs.length, 5); i++) {
684
+ const p = matchedPairs[i];
685
+ console.log(` ${i}: \u94BB\u5B54(${p.drill.x.toFixed(3)}, ${p.drill.y.toFixed(3)}) -> \u710A\u76D8(${p.ref.x.toFixed(3)}, ${p.ref.y.toFixed(3)}), offset=(${p.dx.toFixed(3)}, ${p.dy.toFixed(3)}), err=${p.error.toFixed(4)}`);
686
+ }
687
+ console.log(`[\u6295\u7968] \u7CBE\u786E\u504F\u79FB(\u5E73\u5747\u503C): dx=${finalDx.toFixed(3)}, dy=${finalDy.toFixed(3)}`);
688
+ }
689
+ return {
690
+ dx: finalDx,
691
+ dy: finalDy,
692
+ votes: refinedPairs.length
693
+ };
694
+ };
695
+ const verifyOffset = (drillCenters, refCenters, dx, dy, tolerance = 0.5) => {
696
+ let matchCount = 0;
697
+ let totalError = 0;
698
+ for (const drill of drillCenters) {
699
+ const shiftedX = drill.x + dx;
700
+ const shiftedY = drill.y + dy;
701
+ let minDist = Infinity;
702
+ for (const ref of refCenters) {
703
+ const dist = Math.sqrt((shiftedX - ref.x) ** 2 + (shiftedY - ref.y) ** 2);
704
+ minDist = Math.min(minDist, dist);
705
+ }
706
+ if (minDist < tolerance) {
707
+ matchCount++;
708
+ totalError += minDist;
709
+ }
710
+ }
711
+ return {
712
+ matchCount,
713
+ matchRate: drillCenters.length > 0 ? matchCount / drillCenters.length : 0,
714
+ avgError: matchCount > 0 ? totalError / matchCount : Infinity
715
+ };
716
+ };
717
+ const drillLayerTypes = ["DRL", "DRL2"];
718
+ let needRecalcBounds = false;
719
+ for (const drlType of drillLayerTypes) {
720
+ const drillLayer = layers[drlType];
721
+ if (!drillLayer || !drillLayer.bounds) continue;
722
+ let drillBounds = drillLayer.bounds;
723
+ const refLayerBounds = refBounds;
724
+ const refWidth = refLayerBounds.maxX - refLayerBounds.minX;
725
+ const refHeight = refLayerBounds.maxY - refLayerBounds.minY;
726
+ let drillWidth = drillBounds.maxX - drillBounds.minX;
727
+ let drillHeight = drillBounds.maxY - drillBounds.minY;
728
+ const ratioX = drillWidth > 0 && refWidth > 0 ? drillWidth / refWidth : 1;
729
+ const ratioY = drillHeight > 0 && refHeight > 0 ? drillHeight / refHeight : 1;
730
+ if (DEBUG) {
731
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u8FB9\u754C: (${drillBounds.minX.toFixed(3)}, ${drillBounds.minY.toFixed(3)}) - (${drillBounds.maxX.toFixed(3)}, ${drillBounds.maxY.toFixed(3)})`);
732
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u5C3A\u5BF8: ${drillWidth.toFixed(3)} x ${drillHeight.toFixed(3)}`);
733
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u53C2\u8003\u8FB9\u754C(\u5408\u5E76${refLayerName}\u7B49): (${refLayerBounds.minX.toFixed(3)}, ${refLayerBounds.minY.toFixed(3)}) - (${refLayerBounds.maxX.toFixed(3)}, ${refLayerBounds.maxY.toFixed(3)})`);
734
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u53C2\u8003\u8FB9\u754C\u5C3A\u5BF8: ${refWidth.toFixed(3)} x ${refHeight.toFixed(3)}`);
735
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u6BD4\u4F8B: X=${ratioX.toFixed(2)}, Y=${ratioY.toFixed(2)}`);
736
+ }
737
+ const decidePow10Scale = (ratio) => {
738
+ if (!Number.isFinite(ratio) || ratio <= 0) return 1;
739
+ if (ratio > 2) {
740
+ const pow10 = Math.pow(10, Math.round(Math.log10(ratio)));
741
+ const cand = 1 / pow10;
742
+ const newRatio = ratio * cand;
743
+ if (cand !== 1 && newRatio >= 0.7 && newRatio <= 1.3) return cand;
744
+ } else if (ratio < 0.5) {
745
+ const inv = 1 / ratio;
746
+ const pow10 = Math.pow(10, Math.round(Math.log10(inv)));
747
+ const cand = pow10;
748
+ const newRatio = ratio * cand;
749
+ if (cand !== 1 && newRatio >= 0.7 && newRatio <= 1.3) return cand;
750
+ }
751
+ return 1;
752
+ };
753
+ const scaleX = decidePow10Scale(ratioX);
754
+ const scaleY = decidePow10Scale(ratioY);
755
+ const data = drillLayer.data;
756
+ if (!data || !data.vertices || !data.lineStrips) continue;
757
+ const vertices = data.vertices;
758
+ const lineStrips = data.lineStrips;
759
+ if (scaleX !== 1 || scaleY !== 1) {
760
+ console.warn(`[\u94BB\u5B54\u5BF9\u9F50] \u26A0\uFE0F ${drlType} \u9700\u8981\u7F29\u653E: scaleX=${scaleX}, scaleY=${scaleY}`);
761
+ for (const strip of lineStrips) {
762
+ if (strip.count < 3) continue;
763
+ let sumX = 0, sumY = 0;
764
+ for (let i = 0; i < strip.count; i++) {
765
+ const ptr = (strip.start + i) * 3;
766
+ sumX += vertices[ptr];
767
+ sumY += vertices[ptr + 1];
768
+ }
769
+ const cx = sumX / strip.count;
770
+ const cy = sumY / strip.count;
771
+ const newCx = cx * scaleX;
772
+ const newCy = cy * scaleY;
773
+ const dx = newCx - cx;
774
+ const dy = newCy - cy;
775
+ for (let i = 0; i < strip.count; i++) {
776
+ const ptr = (strip.start + i) * 3;
777
+ vertices[ptr] += dx;
778
+ vertices[ptr + 1] += dy;
779
+ }
780
+ }
781
+ drillBounds = {
782
+ minX: drillBounds.minX * scaleX,
783
+ maxX: drillBounds.maxX * scaleX,
784
+ minY: drillBounds.minY * scaleY,
785
+ maxY: drillBounds.maxY * scaleY
786
+ };
787
+ drillLayer.bounds = drillBounds;
788
+ drillWidth = drillBounds.maxX - drillBounds.minX;
789
+ drillHeight = drillBounds.maxY - drillBounds.minY;
790
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 ${drlType} \u5DF2\u7F29\u653E\uFF0C\u65B0\u5C3A\u5BF8: ${drillWidth.toFixed(3)} x ${drillHeight.toFixed(3)}`);
791
+ needRecalcBounds = true;
792
+ }
793
+ const refLayerPriority = ["GTL", "GBL", "GTS", "GBS"];
794
+ let featureOffset = null;
795
+ const drillCenters = extractCircleCenters(drillLayer.data, drlType);
796
+ const isNonPlated = drlType === "DRL2" || drillLayer.name && /np|non-plated/i.test(drillLayer.name);
797
+ const skipFeatureAlignment = isNonPlated || drillCenters.length < 3;
798
+ if (skipFeatureAlignment) {
799
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType}: \u8DF3\u8FC7\u7279\u5F81\u70B9\u5BF9\u9F50 (isNonPlated=${isNonPlated}, count=${drillCenters.length})`);
800
+ }
801
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ===== \u5F00\u59CB\u7279\u5F81\u70B9\u5339\u914D =====`);
802
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u8FB9\u754C\u4E2D\u5FC3: (${((drillBounds.minX + drillBounds.maxX) / 2).toFixed(3)}, ${((drillBounds.minY + drillBounds.maxY) / 2).toFixed(3)})`);
803
+ if (skipFeatureAlignment) {
804
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ===== \u8DF3\u8FC7\u7279\u5F81\u70B9\u5339\u914D (DRL2/NPTH \u6216 count < 3) =====`);
805
+ }
806
+ for (const refType of refLayerPriority) {
807
+ if (skipFeatureAlignment) break;
808
+ const refLayer = layers[refType];
809
+ if (!refLayer || !refLayer.data) {
810
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${refType}: \u56FE\u5C42\u4E0D\u5B58\u5728\u6216\u65E0\u6570\u636E`);
811
+ continue;
812
+ }
813
+ const refCenters = extractCircleCenters(refLayer.data, refType);
814
+ if (DEBUG) {
815
+ const refBds = refLayer.bounds;
816
+ if (refBds) {
817
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${refType} \u8FB9\u754C\u4E2D\u5FC3: (${((refBds.minX + refBds.maxX) / 2).toFixed(3)}, ${((refBds.minY + refBds.maxY) / 2).toFixed(3)})`);
818
+ }
819
+ }
820
+ if (drillCenters.length < 2 || refCenters.length < 2) {
821
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${refType}: \u7279\u5F81\u70B9\u4E0D\u8DB3 (drill=${drillCenters.length}, ref=${refCenters.length})`);
822
+ continue;
823
+ }
824
+ const offset = estimateOffsetByVoting(drillCenters, refCenters, 0.02);
825
+ if (!offset) {
826
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${refType}: \u6295\u7968\u672A\u5F97\u5230\u7ED3\u679C`);
827
+ continue;
828
+ }
829
+ const verification = verifyOffset(drillCenters, refCenters, offset.dx, offset.dy, 1);
830
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${refType} \u6295\u7968\u7ED3\u679C: dx=${offset.dx.toFixed(3)}, dy=${offset.dy.toFixed(3)}, votes=${offset.votes}, matchRate=${(verification.matchRate * 100).toFixed(1)}%`);
831
+ if (verification.matchRate > 0.3 && offset.votes >= 2) {
832
+ featureOffset = {
833
+ dx: offset.dx,
834
+ dy: offset.dy,
835
+ matchRate: verification.matchRate,
836
+ refType
837
+ };
838
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 \u9009\u62E9 ${refType} \u4F5C\u4E3A\u53C2\u8003\u5C42\u8FDB\u884C\u7279\u5F81\u70B9\u5BF9\u9F50`);
839
+ break;
840
+ }
841
+ }
842
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ===== \u7279\u5F81\u70B9\u5339\u914D\u7ED3\u675F =====`);
843
+ if (featureOffset && (Math.abs(featureOffset.dx) > 0.3 || Math.abs(featureOffset.dy) > 0.3)) {
844
+ console.warn(`[\u94BB\u5B54\u5BF9\u9F50] \u26A0\uFE0F ${drlType} \u5E94\u7528\u7279\u5F81\u70B9\u5BF9\u9F50(\u53C2\u8003${featureOffset.refType}): X=${featureOffset.dx.toFixed(3)}, Y=${featureOffset.dy.toFixed(3)} (\u5339\u914D\u7387=${(featureOffset.matchRate * 100).toFixed(1)}%)`);
845
+ for (let k = 0; k < vertices.length; k += 3) {
846
+ vertices[k] += featureOffset.dx;
847
+ vertices[k + 1] += featureOffset.dy;
848
+ }
849
+ drillLayer.bounds = translateBounds(drillBounds, featureOffset.dx, featureOffset.dy);
850
+ if (DEBUG) {
851
+ const newBounds = drillLayer.bounds;
852
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 ${drlType} \u7279\u5F81\u70B9\u5BF9\u9F50\u540E\u8FB9\u754C: (${newBounds.minX.toFixed(3)}, ${newBounds.minY.toFixed(3)}) - (${newBounds.maxX.toFixed(3)}, ${newBounds.maxY.toFixed(3)})`);
853
+ if (lineStrips && lineStrips.length > 0) {
854
+ const firstStrip = lineStrips[0];
855
+ let sumX = 0, sumY = 0;
856
+ for (let i = 0; i < firstStrip.count; i++) {
857
+ const ptr = (firstStrip.start + i) * 3;
858
+ sumX += vertices[ptr];
859
+ sumY += vertices[ptr + 1];
860
+ }
861
+ const cx = sumX / firstStrip.count;
862
+ const cy = sumY / firstStrip.count;
863
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u9A8C\u8BC1: \u7B2C\u4E00\u4E2A\u94BB\u5B54\u4E2D\u5FC3\u4FEE\u6B63\u540E = (${cx.toFixed(3)}, ${cy.toFixed(3)})`);
864
+ const gtsLayer = layers.GTS;
865
+ if (gtsLayer && gtsLayer.data && gtsLayer.data.lineStrips && gtsLayer.data.lineStrips.length > 0) {
866
+ const gtsVerts = gtsLayer.data.vertices;
867
+ const gtsStrip = gtsLayer.data.lineStrips[0];
868
+ let gtsMinX = Infinity, gtsMinY = Infinity, gtsMaxX = -Infinity, gtsMaxY = -Infinity;
869
+ for (let i = 0; i < gtsStrip.count; i++) {
870
+ const ptr = (gtsStrip.start + i) * 3;
871
+ gtsMinX = Math.min(gtsMinX, gtsVerts[ptr]);
872
+ gtsMaxX = Math.max(gtsMaxX, gtsVerts[ptr]);
873
+ gtsMinY = Math.min(gtsMinY, gtsVerts[ptr + 1]);
874
+ gtsMaxY = Math.max(gtsMaxY, gtsVerts[ptr + 1]);
875
+ }
876
+ const gtsCx = (gtsMinX + gtsMaxX) / 2;
877
+ const gtsCy = (gtsMinY + gtsMaxY) / 2;
878
+ console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u5BF9\u6BD4: GTS \u7B2C\u4E00\u4E2A\u5F62\u72B6\u4E2D\u5FC3 = (${gtsCx.toFixed(3)}, ${gtsCy.toFixed(3)})`);
879
+ }
880
+ }
881
+ }
882
+ needRecalcBounds = true;
883
+ continue;
884
+ }
885
+ const drillCenterX = (drillBounds.minX + drillBounds.maxX) / 2;
886
+ const drillCenterY = (drillBounds.minY + drillBounds.maxY) / 2;
887
+ const refCenterX = (refLayerBounds.minX + refLayerBounds.maxX) / 2;
888
+ const refCenterY = (refLayerBounds.minY + refLayerBounds.maxY) / 2;
889
+ const centerDistX = Math.abs(drillCenterX - refCenterX);
890
+ const centerDistY = Math.abs(drillCenterY - refCenterY);
891
+ const centerDist = Math.sqrt(centerDistX * centerDistX + centerDistY * centerDistY);
892
+ const sizeRatioX = drillWidth > 0 && refWidth > 0 ? Math.min(drillWidth / refWidth, refWidth / drillWidth) : 0;
893
+ const sizeRatioY = drillHeight > 0 && refHeight > 0 ? Math.min(drillHeight / refHeight, refHeight / drillHeight) : 0;
894
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u4E2D\u5FC3\u8DDD\u79BB: X=${centerDistX.toFixed(3)}, Y=${centerDistY.toFixed(3)}, total=${centerDist.toFixed(3)}`);
895
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u5C3A\u5BF8\u6BD4\u4F8B: X=${sizeRatioX.toFixed(3)}, Y=${sizeRatioY.toFixed(3)}`);
896
+ const overflowBefore = computeOverflow(drillBounds, refLayerBounds);
897
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} Overflow(before): left=${overflowBefore.left.toFixed(3)} right=${overflowBefore.right.toFixed(3)} bottom=${overflowBefore.bottom.toFixed(3)} top=${overflowBefore.top.toFixed(3)} total=${overflowBefore.total.toFixed(3)}`);
898
+ const applyThresholdMm = 0.5;
899
+ const centerThresholdMm = 2;
900
+ const isFullyContained = overflowBefore.total < 0.01;
901
+ if (isFullyContained) {
902
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 ${drlType} \u5B8C\u5168\u5728\u53C2\u8003\u5C42\u8FB9\u754C\u5185\uFF0C\u65E0\u9700\u5BF9\u9F50`);
903
+ continue;
904
+ }
905
+ const needsAlignment = (centerDist > centerThresholdMm || overflowBefore.total > applyThresholdMm) && sizeRatioX > 0.5 && sizeRatioY > 0.5;
906
+ if (needsAlignment) {
907
+ const useCenterAlign = sizeRatioX > 0.85 && sizeRatioY > 0.85;
908
+ let finalDx = 0, finalDy = 0;
909
+ if (useCenterAlign) {
910
+ finalDx = refCenterX - drillCenterX;
911
+ finalDy = refCenterY - drillCenterY;
912
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u5C3A\u5BF8\u6BD4\u4F8B\u63A5\u8FD1(${sizeRatioX.toFixed(2)}, ${sizeRatioY.toFixed(2)})\uFF0C\u4F7F\u7528\u4E2D\u5FC3\u5BF9\u9F50`);
913
+ } else {
914
+ const candidatesX = [
915
+ 0,
916
+ refLayerBounds.minX - drillBounds.minX,
917
+ // 左边对齐
918
+ refLayerBounds.maxX - drillBounds.maxX,
919
+ // 右边对齐
920
+ refCenterX - drillCenterX
921
+ // 中心对齐
922
+ ];
923
+ const candidatesY = [
924
+ 0,
925
+ refLayerBounds.minY - drillBounds.minY,
926
+ // 底边对齐
927
+ refLayerBounds.maxY - drillBounds.maxY,
928
+ // 顶边对齐
929
+ refCenterY - drillCenterY
930
+ // 中心对齐
931
+ ];
932
+ let best = { dx: 0, dy: 0, error: Infinity, shift: Infinity };
933
+ for (const dx of candidatesX) {
934
+ for (const dy of candidatesY) {
935
+ const afterBounds = translateBounds(drillBounds, dx, dy);
936
+ const ov = computeOverflow(afterBounds, refLayerBounds);
937
+ const shift = Math.abs(dx) + Math.abs(dy);
938
+ if (ov.total < best.error - 1e-6 || Math.abs(ov.total - best.error) < 1e-6 && shift < best.shift) {
939
+ best = { dx, dy, error: ov.total, shift };
940
+ }
941
+ }
942
+ }
943
+ finalDx = best.dx;
944
+ finalDy = best.dy;
945
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u4F7F\u7528\u8FB9\u754C\u5BF9\u9F50\uFF0C\u5019\u9009\u504F\u79FB: X=${best.dx.toFixed(3)}, Y=${best.dy.toFixed(3)} (overflow after=${best.error.toFixed(3)})`);
946
+ }
947
+ if (Math.abs(finalDx) >= applyThresholdMm || Math.abs(finalDy) >= applyThresholdMm) {
948
+ console.warn(`[\u94BB\u5B54\u5BF9\u9F50] \u26A0\uFE0F ${drlType} \u5E94\u7528${useCenterAlign ? "\u4E2D\u5FC3" : "\u8FB9\u754C"}\u5BF9\u9F50: X=${finalDx.toFixed(3)}, Y=${finalDy.toFixed(3)}`);
949
+ for (let k = 0; k < vertices.length; k += 3) {
950
+ vertices[k] += finalDx;
951
+ vertices[k + 1] += finalDy;
952
+ }
953
+ drillLayer.bounds = translateBounds(drillBounds, finalDx, finalDy);
954
+ const overflowAfter = computeOverflow(drillLayer.bounds, refLayerBounds);
955
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 ${drlType} Overflow(after): left=${overflowAfter.left.toFixed(3)} right=${overflowAfter.right.toFixed(3)} bottom=${overflowAfter.bottom.toFixed(3)} top=${overflowAfter.top.toFixed(3)} total=${overflowAfter.total.toFixed(3)}`);
956
+ needRecalcBounds = true;
957
+ } else {
958
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] ${drlType} \u504F\u79FB\u4E0D\u663E\u8457\uFF0C\u8DF3\u8FC7`);
959
+ }
960
+ } else {
961
+ if (DEBUG) console.log(`[\u94BB\u5B54\u5BF9\u9F50] \u2713 ${drlType} \u5DF2\u5BF9\u9F50\uFF08\u4E2D\u5FC3\u8DDD\u79BB < ${centerThresholdMm}mm\uFF0C\u8D8A\u754C < ${applyThresholdMm}mm\uFF09`);
962
+ }
963
+ }
964
+ if (needRecalcBounds) {
965
+ recalculateBoardBounds();
966
+ }
967
+ }
968
+ function recalculateBoardBounds() {
969
+ if (refBounds) {
970
+ boardBounds = [refBounds.minX, refBounds.minY, refBounds.maxX, refBounds.maxY];
971
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u91CD\u65B0\u8BA1\u7B97\u677F\u5B50\u8FB9\u754C\uFF08\u4F7F\u7528\u53C2\u8003\u8FB9\u754C\uFF09:`, boardBounds);
972
+ return;
973
+ }
974
+ boardBounds = null;
975
+ for (const [type, layer] of Object.entries(layers)) {
976
+ if (type === "DRL" || type === "DRL2" || type === "GKO") continue;
977
+ if (!layer.bounds) continue;
978
+ const b = layer.bounds;
979
+ if (!boardBounds) {
980
+ boardBounds = [b.minX, b.minY, b.maxX, b.maxY];
981
+ } else {
982
+ boardBounds[0] = Math.min(boardBounds[0], b.minX);
983
+ boardBounds[1] = Math.min(boardBounds[1], b.minY);
984
+ boardBounds[2] = Math.max(boardBounds[2], b.maxX);
985
+ boardBounds[3] = Math.max(boardBounds[3], b.maxY);
986
+ }
987
+ }
988
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u91CD\u65B0\u8BA1\u7B97\u677F\u5B50\u8FB9\u754C:`, boardBounds);
989
+ }
990
+ function getLayerType(fileName) {
991
+ const name = fileName.toLowerCase();
992
+ const ext = "." + name.split(".").pop();
993
+ if (ext === ".gtl") return "GTL";
994
+ if (ext === ".gbl") return "GBL";
995
+ if (ext === ".gts") return "GTS";
996
+ if (ext === ".gbs") return "GBS";
997
+ if (ext === ".gto") return "GTO";
998
+ if (ext === ".gbo") return "GBO";
999
+ if (ext === ".gtp") return "GTP";
1000
+ if (ext === ".gbp") return "GBP";
1001
+ if (ext === ".gko" || ext === ".gm1") return "GKO";
1002
+ if (ext === ".drl" || ext === ".txt") {
1003
+ const fileNameOnly = fileName.split(/[/\\]/).pop().toLowerCase();
1004
+ const baseName = fileNameOnly.replace(/\.[^.]+$/, "");
1005
+ if (baseName === "np" || baseName.startsWith("np-") || baseName.endsWith("-np")) {
1006
+ return "DRL2";
1007
+ }
1008
+ return "DRL";
1009
+ }
1010
+ if (ext === ".bot") return "GBL";
1011
+ if (ext === ".top") return "GTL";
1012
+ if (ext === ".sob") return "GBS";
1013
+ if (ext === ".sot") return "GTS";
1014
+ if (ext === ".sst") return "GTO";
1015
+ if (ext === ".ssb") return "GBO";
1016
+ if (ext === ".smt") return "GTS";
1017
+ if (ext === ".smb") return "GBS";
1018
+ if (ext === ".pmt") return "GTP";
1019
+ if (ext === ".pmb") return "GBP";
1020
+ if (ext === ".ser") return "GTO";
1021
+ if (ext === ".dri") return "DRL";
1022
+ if (ext === ".d") return "DRL";
1023
+ if (ext === ".art") {
1024
+ const fileNameOnly = fileName.split(/[/\\]/).pop();
1025
+ const dot = fileNameOnly.lastIndexOf(".");
1026
+ const stemUpper = (dot > 0 ? fileNameOnly.substring(0, dot) : fileNameOnly).toUpperCase();
1027
+ if (stemUpper === "SST") return "GTO";
1028
+ if (stemUpper === "SMT") return "GTS";
1029
+ if (stemUpper === "TOP") return "GTL";
1030
+ if (stemUpper === "BOT") return "GBL";
1031
+ if (stemUpper === "SMB") return "GBS";
1032
+ if (stemUpper === "SSB") return "GBO";
1033
+ if (stemUpper === "OUT") return "GKO";
1034
+ const layMatch = stemUpper.match(/^LAY(\d+)$/);
1035
+ if (layMatch) {
1036
+ return null;
1037
+ }
1038
+ return null;
1039
+ }
1040
+ if (ext === ".pho") {
1041
+ const fileNameOnly = fileName.split(/[/\\]/).pop();
1042
+ const nameUpper = fileNameOnly.toUpperCase();
1043
+ const isTopLayer = (numberStr) => {
1044
+ if (!numberStr) return true;
1045
+ const prefix3 = numberStr.substring(0, 3);
1046
+ const num = parseInt(prefix3, 10);
1047
+ return num <= 1;
1048
+ };
1049
+ if (nameUpper.startsWith("SST")) {
1050
+ return "GTO";
1051
+ }
1052
+ if (nameUpper.startsWith("SSB")) {
1053
+ return "GBO";
1054
+ }
1055
+ if (nameUpper.startsWith("SMD")) {
1056
+ const numberMatch = nameUpper.match(/^SMD(\d+)/);
1057
+ if (numberMatch) {
1058
+ return isTopLayer(numberMatch[1]) ? "GTP" : "GBP";
1059
+ }
1060
+ return "GTP";
1061
+ }
1062
+ if (nameUpper.startsWith("SM")) {
1063
+ const numberMatch = nameUpper.match(/^SM(\d+)/);
1064
+ if (numberMatch) {
1065
+ return isTopLayer(numberMatch[1]) ? "GTS" : "GBS";
1066
+ }
1067
+ return "GTS";
1068
+ }
1069
+ if (nameUpper.startsWith("ART")) {
1070
+ const numberMatch = nameUpper.match(/^ART(\d+)/);
1071
+ if (numberMatch) {
1072
+ return isTopLayer(numberMatch[1]) ? "GTL" : "GBL";
1073
+ }
1074
+ return "GTL";
1075
+ }
1076
+ if (nameUpper.startsWith("DRL")) {
1077
+ return "DRL";
1078
+ }
1079
+ if (nameUpper.startsWith("OUT")) {
1080
+ return "GKO";
1081
+ }
1082
+ return null;
1083
+ }
1084
+ if (ext === ".gbr") {
1085
+ const fileNameOnly = fileName.split(/[/\\]/).pop() || "";
1086
+ const nu = fileNameOnly.toUpperCase();
1087
+ if (/EDGE[._-]CUTS/i.test(nu)) return "GKO";
1088
+ if (/F[._-]CU\b/i.test(nu)) return "GTL";
1089
+ if (/B[._-]CU\b/i.test(nu)) return "GBL";
1090
+ const inM = nu.match(/IN(\d+)[._-]CU/i);
1091
+ if (inM) return "SIG" + (parseInt(inM[1], 10) + 1);
1092
+ }
1093
+ if (/^\.g\d+$/.test(ext)) {
1094
+ const fileNameOnly = fileName.split(/[/\\]/).pop() || "";
1095
+ const nu = fileNameOnly.toUpperCase();
1096
+ const inM = nu.match(/IN(\d+)[._-]CU/i);
1097
+ if (inM) return "SIG" + (parseInt(inM[1], 10) + 1);
1098
+ const gd = ext.match(/^\.g(\d+)$/);
1099
+ if (gd) return "SIG" + (parseInt(gd[1], 10) + 1);
1100
+ }
1101
+ if (/^\.gp\d+$/.test(ext)) {
1102
+ const gpd = ext.match(/^\.gp(\d+)$/);
1103
+ if (gpd) return "GP" + parseInt(gpd[1], 10);
1104
+ }
1105
+ if (name.includes("top") && name.includes("copper")) return "GTL";
1106
+ if (name.includes("bottom") && name.includes("copper")) return "GBL";
1107
+ if (name.includes("top") && name.includes("mask")) return "GTS";
1108
+ if (name.includes("bottom") && name.includes("mask")) return "GBS";
1109
+ if (name.includes("top") && name.includes("silk")) return "GTO";
1110
+ if (name.includes("bottom") && name.includes("silk")) return "GBO";
1111
+ if (name.includes("outline") || name.includes("profile")) return "GKO";
1112
+ if (name.includes("drill")) return "DRL";
1113
+ return null;
1114
+ }
1115
+ function render() {
1116
+ if (renderRAF) {
1117
+ cancelAnimationFrame(renderRAF);
1118
+ }
1119
+ renderRAF = requestAnimationFrame(doRender);
1120
+ }
1121
+ function invalidateCache() {
1122
+ layerCache.dirty = true;
1123
+ layerCache.top = null;
1124
+ layerCache.bottom = null;
1125
+ }
1126
+ function doRender() {
1127
+ renderRAF = null;
1128
+ const container = document.getElementById("canvasContainer");
1129
+ const containerWidth = container.clientWidth;
1130
+ const containerHeight = container.clientHeight;
1131
+ const pixelRatio = window.devicePixelRatio || 1;
1132
+ canvas.width = containerWidth * pixelRatio;
1133
+ canvas.height = containerHeight * pixelRatio;
1134
+ canvas.style.width = `${containerWidth}px`;
1135
+ canvas.style.height = `${containerHeight}px`;
1136
+ ctx.fillStyle = "#1a1a1a";
1137
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1138
+ if (!boardBounds || Object.keys(layers).length === 0) return;
1139
+ const boardWidth = boardBounds[2] - boardBounds[0];
1140
+ const boardHeight = boardBounds[3] - boardBounds[1];
1141
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] render: boardBounds = [${boardBounds.map((v) => v.toFixed(2)).join(", ")}], \u5C3A\u5BF8 = ${boardWidth.toFixed(2)} x ${boardHeight.toFixed(2)}`);
1142
+ const firstLayer = Object.values(layers)[0];
1143
+ unitScale = firstLayer && (firstLayer.units === "in" || firstLayer.units === "inch") ? 25.4 : 1;
1144
+ const displayWidth = boardWidth * unitScale;
1145
+ const displayHeight = boardHeight * unitScale;
1146
+ const baseScale = Math.min(
1147
+ containerWidth / displayWidth,
1148
+ containerHeight / displayHeight
1149
+ ) * 0.9;
1150
+ const finalScale = baseScale * pixelRatio * unitScale * zoomLevel;
1151
+ const useDirectRender = zoomLevel > 1.01;
1152
+ if (useDirectRender) {
1153
+ renderDirect(ctx, canvas.width, canvas.height, boardBounds, boardWidth, boardHeight, finalScale, baseScale, displayWidth, displayHeight, pixelRatio);
1154
+ } else {
1155
+ const needRebuildCache = layerCache.dirty || layerCache.lastWidth !== canvas.width || layerCache.lastHeight !== canvas.height;
1156
+ if (needRebuildCache) {
1157
+ const baseRenderScale = baseScale * pixelRatio * unitScale;
1158
+ layerCache.top = createViewCache("top", canvas.width, canvas.height, boardBounds, boardWidth, boardHeight, baseRenderScale, baseScale, displayWidth, displayHeight, pixelRatio);
1159
+ layerCache.bottom = createViewCache("bottom", canvas.width, canvas.height, boardBounds, boardWidth, boardHeight, baseRenderScale, baseScale, displayWidth, displayHeight, pixelRatio);
1160
+ layerCache.dirty = false;
1161
+ layerCache.lastWidth = canvas.width;
1162
+ layerCache.lastHeight = canvas.height;
1163
+ }
1164
+ const cachedCanvas = currentView === "top" ? layerCache.top : layerCache.bottom;
1165
+ if (cachedCanvas) {
1166
+ const centerX = canvas.width / 2;
1167
+ const centerY = canvas.height / 2;
1168
+ ctx.save();
1169
+ ctx.imageSmoothingEnabled = false;
1170
+ ctx.translate(centerX, centerY);
1171
+ ctx.scale(zoomLevel, zoomLevel);
1172
+ ctx.translate(-centerX + panOffset.x * pixelRatio, -centerY + panOffset.y * pixelRatio);
1173
+ ctx.drawImage(cachedCanvas, 0, 0);
1174
+ ctx.restore();
1175
+ }
1176
+ }
1177
+ }
1178
+ function renderDirect(ctx2, width, height, boardBounds2, boardWidth, boardHeight, finalScale, baseScale, displayWidth, displayHeight, pixelRatio) {
1179
+ const containerWidth = width / pixelRatio;
1180
+ const containerHeight = height / pixelRatio;
1181
+ const scaledWidth = displayWidth * baseScale * zoomLevel;
1182
+ const scaledHeight = displayHeight * baseScale * zoomLevel;
1183
+ const offsetX = (containerWidth - scaledWidth) / 2 * pixelRatio + panOffset.x * pixelRatio;
1184
+ const offsetY = (containerHeight - scaledHeight) / 2 * pixelRatio + panOffset.y * pixelRatio;
1185
+ ctx2.save();
1186
+ ctx2.translate(offsetX, offsetY);
1187
+ const isTop = currentView === "top";
1188
+ if (isTop) {
1189
+ ctx2.scale(finalScale, finalScale);
1190
+ ctx2.translate(-boardBounds2[0], -boardBounds2[1]);
1191
+ } else {
1192
+ ctx2.translate(scaledWidth * pixelRatio, 0);
1193
+ ctx2.scale(-finalScale, finalScale);
1194
+ ctx2.translate(-boardBounds2[0], -boardBounds2[1]);
1195
+ }
1196
+ const copperLayer = isTop ? layers.GTL : layers.GBL;
1197
+ const maskLayer = isTop ? layers.GTS : layers.GBS;
1198
+ const silkLayer = isTop ? layers.GTO : layers.GBO;
1199
+ const outlineLayer = layers.GKO;
1200
+ const drillLayer = layers.DRL;
1201
+ ctx2.fillStyle = "#226B0E";
1202
+ ctx2.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1203
+ if (outlineLayer && outlineLayer.data) {
1204
+ ctx2.fillStyle = "#2d5a28";
1205
+ drawOutlineFillAllClosed(ctx2, outlineLayer.data);
1206
+ } else {
1207
+ ctx2.fillStyle = "#2d5a28";
1208
+ ctx2.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1209
+ }
1210
+ if (copperLayer && copperLayer.data) {
1211
+ ctx2.fillStyle = "#40721E";
1212
+ ctx2.strokeStyle = "#40721E";
1213
+ ctx2.lineWidth = 0.08 / unitScale;
1214
+ drawLayerData(ctx2, copperLayer.data, true);
1215
+ }
1216
+ if (config.showMask) {
1217
+ ctx2.fillStyle = hexToRgba(config.maskColor, 0.65);
1218
+ if (outlineLayer && outlineLayer.data) {
1219
+ drawOutlineFillAllClosed(ctx2, outlineLayer.data);
1220
+ } else {
1221
+ ctx2.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1222
+ }
1223
+ }
1224
+ if (maskLayer && maskLayer.data) {
1225
+ ctx2.fillStyle = FINISH_COLORS[config.surfaceFinish];
1226
+ ctx2.strokeStyle = FINISH_COLORS[config.surfaceFinish];
1227
+ drawLayerData(ctx2, maskLayer.data, false);
1228
+ }
1229
+ if (config.showSilk && silkLayer && silkLayer.data) {
1230
+ ctx2.fillStyle = config.silkColor;
1231
+ ctx2.strokeStyle = config.silkColor;
1232
+ ctx2.lineWidth = 0.4 / unitScale;
1233
+ drawLayerData(ctx2, silkLayer.data, false);
1234
+ }
1235
+ if (config.showDrill) {
1236
+ ctx2.fillStyle = "#1a1a1a";
1237
+ if (drillLayer && drillLayer.data) {
1238
+ drawDrillHoles(ctx2, drillLayer.data);
1239
+ }
1240
+ const drillLayer2 = layers.DRL2;
1241
+ if (drillLayer2 && drillLayer2.data) {
1242
+ drawDrillHoles(ctx2, drillLayer2.data);
1243
+ }
1244
+ }
1245
+ ctx2.restore();
1246
+ }
1247
+ function createViewCache(view, width, height, boardBounds2, boardWidth, boardHeight, baseRenderScale, baseScale, displayWidth, displayHeight, pixelRatio) {
1248
+ const offscreen = document.createElement("canvas");
1249
+ offscreen.width = width;
1250
+ offscreen.height = height;
1251
+ const offCtx = offscreen.getContext("2d");
1252
+ offCtx.fillStyle = "#1a1a1a";
1253
+ offCtx.fillRect(0, 0, width, height);
1254
+ const containerWidth = width / pixelRatio;
1255
+ const containerHeight = height / pixelRatio;
1256
+ const scaledWidth = displayWidth * baseScale;
1257
+ const scaledHeight = displayHeight * baseScale;
1258
+ const offsetX = (containerWidth - scaledWidth) / 2 * pixelRatio;
1259
+ const offsetY = (containerHeight - scaledHeight) / 2 * pixelRatio;
1260
+ offCtx.save();
1261
+ offCtx.translate(offsetX, offsetY);
1262
+ const isTop = view === "top";
1263
+ if (isTop) {
1264
+ offCtx.scale(baseRenderScale, baseRenderScale);
1265
+ offCtx.translate(-boardBounds2[0], -boardBounds2[1]);
1266
+ } else {
1267
+ offCtx.translate(scaledWidth * pixelRatio, 0);
1268
+ offCtx.scale(-baseRenderScale, baseRenderScale);
1269
+ offCtx.translate(-boardBounds2[0], -boardBounds2[1]);
1270
+ }
1271
+ const copperLayer = isTop ? layers.GTL : layers.GBL;
1272
+ const maskLayer = isTop ? layers.GTS : layers.GBS;
1273
+ const silkLayer = isTop ? layers.GTO : layers.GBO;
1274
+ const outlineLayer = layers.GKO;
1275
+ const drillLayer = layers.DRL;
1276
+ offCtx.fillStyle = "#226B0E";
1277
+ offCtx.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1278
+ if (outlineLayer && outlineLayer.data) {
1279
+ offCtx.fillStyle = "#2d5a28";
1280
+ drawOutlineFillAllClosed(offCtx, outlineLayer.data);
1281
+ } else {
1282
+ offCtx.fillStyle = "#2d5a28";
1283
+ offCtx.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1284
+ }
1285
+ if (copperLayer && copperLayer.data) {
1286
+ offCtx.fillStyle = "#40721E";
1287
+ offCtx.strokeStyle = "#40721E";
1288
+ offCtx.lineWidth = 0.08 / unitScale;
1289
+ drawLayerData(offCtx, copperLayer.data, true);
1290
+ }
1291
+ if (config.showMask) {
1292
+ offCtx.fillStyle = hexToRgba(config.maskColor, 0.65);
1293
+ if (outlineLayer && outlineLayer.data) {
1294
+ drawOutlineFillAllClosed(offCtx, outlineLayer.data);
1295
+ } else {
1296
+ offCtx.fillRect(boardBounds2[0], boardBounds2[1], boardWidth, boardHeight);
1297
+ }
1298
+ }
1299
+ if (maskLayer && maskLayer.data) {
1300
+ if (DEBUG && view === "top" && maskLayer.data.lineStrips && maskLayer.data.lineStrips.length > 0) {
1301
+ const gtsVerts = maskLayer.data.vertices;
1302
+ const showCount = Math.min(3, maskLayer.data.lineStrips.length);
1303
+ console.log(`[\u710A\u76D8\u6E32\u67D3] GTS \u524D ${showCount} \u4E2A\u5F62\u72B6\u5750\u6807:`);
1304
+ for (let i = 0; i < showCount; i++) {
1305
+ const strip = maskLayer.data.lineStrips[i];
1306
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1307
+ for (let j = 0; j < strip.count; j++) {
1308
+ const ptr = (strip.start + j) * 3;
1309
+ minX = Math.min(minX, gtsVerts[ptr]);
1310
+ maxX = Math.max(maxX, gtsVerts[ptr]);
1311
+ minY = Math.min(minY, gtsVerts[ptr + 1]);
1312
+ maxY = Math.max(maxY, gtsVerts[ptr + 1]);
1313
+ }
1314
+ const cx = (minX + maxX) / 2;
1315
+ const cy = (minY + maxY) / 2;
1316
+ console.log(` GTS\u5F62\u72B6${i}: \u4E2D\u5FC3=(${cx.toFixed(4)}, ${cy.toFixed(4)}), \u5C3A\u5BF8=${(maxX - minX).toFixed(4)}x${(maxY - minY).toFixed(4)}`);
1317
+ }
1318
+ }
1319
+ offCtx.fillStyle = FINISH_COLORS[config.surfaceFinish];
1320
+ offCtx.strokeStyle = FINISH_COLORS[config.surfaceFinish];
1321
+ drawLayerData(offCtx, maskLayer.data, false);
1322
+ }
1323
+ if (config.showSilk && silkLayer && silkLayer.data) {
1324
+ offCtx.fillStyle = config.silkColor;
1325
+ offCtx.strokeStyle = config.silkColor;
1326
+ offCtx.lineWidth = 0.4 / unitScale;
1327
+ drawLayerData(offCtx, silkLayer.data, false);
1328
+ }
1329
+ if (config.showDrill) {
1330
+ offCtx.fillStyle = "#1a1a1a";
1331
+ if (drillLayer && drillLayer.data) {
1332
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u6E32\u67D3\u94BB\u5B54\u5C42 DRL (${view}):`, drillLayer.data.lineStrips?.length);
1333
+ drawDrillHoles(offCtx, drillLayer.data);
1334
+ }
1335
+ const drillLayer2 = layers.DRL2;
1336
+ if (drillLayer2 && drillLayer2.data) {
1337
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u6E32\u67D3\u94BB\u5B54\u5C42 DRL2 (${view}):`, drillLayer2.data.lineStrips?.length);
1338
+ drawDrillHoles(offCtx, drillLayer2.data);
1339
+ }
1340
+ }
1341
+ offCtx.restore();
1342
+ return offscreen;
1343
+ }
1344
+ function drawLayerData(ctx2, data, includeTraces = false) {
1345
+ if (!data || !data.lineStrips || !data.vertices) return;
1346
+ const vertices = data.vertices;
1347
+ const lineStrips = data.lineStrips;
1348
+ const shapes = [];
1349
+ const traces = [];
1350
+ for (const strip of lineStrips) {
1351
+ if (strip.count < 2) continue;
1352
+ let ptr = strip.start * 3;
1353
+ const startX = vertices[ptr];
1354
+ const startY = vertices[ptr + 1];
1355
+ const lastPtr = (strip.start + strip.count - 1) * 3;
1356
+ const lastX = vertices[lastPtr];
1357
+ const lastY = vertices[lastPtr + 1];
1358
+ const dist = Math.abs(startX - lastX) + Math.abs(startY - lastY);
1359
+ const isClosed = dist < 2e-3;
1360
+ if (isClosed) {
1361
+ let area = 0;
1362
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1363
+ for (let i = 0; i < strip.count; i++) {
1364
+ const p1 = (strip.start + i) * 3;
1365
+ const p2 = (strip.start + (i + 1) % strip.count) * 3;
1366
+ const x1 = vertices[p1], y1 = vertices[p1 + 1];
1367
+ const x2 = vertices[p2], y2 = vertices[p2 + 1];
1368
+ area += x1 * y2 - x2 * y1;
1369
+ if (x1 < minX) minX = x1;
1370
+ if (y1 < minY) minY = y1;
1371
+ if (x1 > maxX) maxX = x1;
1372
+ if (y1 > maxY) maxY = y1;
1373
+ }
1374
+ area = Math.abs(area) / 2;
1375
+ shapes.push({ strip, area, bbox: { minX, minY, maxX, maxY } });
1376
+ } else {
1377
+ traces.push(strip);
1378
+ }
1379
+ }
1380
+ if (shapes.length > 1 && includeTraces) {
1381
+ const totalArea = shapes.reduce((sum, s) => sum + s.area, 0);
1382
+ const maxArea = Math.max(...shapes.map((s) => s.area));
1383
+ const threshold = totalArea * 0.4;
1384
+ const filteredShapes = shapes.filter((s) => !(s.area > threshold && s.area === maxArea));
1385
+ shapes.length = 0;
1386
+ shapes.push(...filteredShapes);
1387
+ }
1388
+ const SKIP_CONTAINMENT_THRESHOLD = 200;
1389
+ const shapeParent = /* @__PURE__ */ new Map();
1390
+ const shapeChildren = /* @__PURE__ */ new Map();
1391
+ if (shapes.length <= SKIP_CONTAINMENT_THRESHOLD) {
1392
+ for (let i = 0; i < shapes.length; i++) {
1393
+ shapeChildren.set(i, []);
1394
+ }
1395
+ const sortedIndices = shapes.map((_, i) => i).sort((a, b) => shapes[b].area - shapes[a].area);
1396
+ const MAX_PARENT_CANDIDATES = 50;
1397
+ const parentCandidates = sortedIndices.slice(0, MAX_PARENT_CANDIDATES);
1398
+ for (let i = 0; i < sortedIndices.length; i++) {
1399
+ const childIdx = sortedIndices[i];
1400
+ const child = shapes[childIdx];
1401
+ let bestParent = -1;
1402
+ let bestParentArea = Infinity;
1403
+ for (const parentIdx of parentCandidates) {
1404
+ if (parentIdx === childIdx) continue;
1405
+ const parent = shapes[parentIdx];
1406
+ if (parent.area <= child.area) continue;
1407
+ const eps = 1e-3;
1408
+ if (child.bbox.minX >= parent.bbox.minX - eps && child.bbox.maxX <= parent.bbox.maxX + eps && child.bbox.minY >= parent.bbox.minY - eps && child.bbox.maxY <= parent.bbox.maxY + eps) {
1409
+ if (parent.area < bestParentArea) {
1410
+ bestParentArea = parent.area;
1411
+ bestParent = parentIdx;
1412
+ }
1413
+ }
1414
+ }
1415
+ if (bestParent >= 0) {
1416
+ shapeParent.set(childIdx, bestParent);
1417
+ shapeChildren.get(bestParent).push(childIdx);
1418
+ }
1419
+ }
1420
+ } else {
1421
+ for (let i = 0; i < shapes.length; i++) {
1422
+ shapeChildren.set(i, []);
1423
+ }
1424
+ }
1425
+ const topLevelShapes = [];
1426
+ for (let i = 0; i < shapes.length; i++) {
1427
+ if (!shapeParent.has(i)) {
1428
+ topLevelShapes.push(i);
1429
+ }
1430
+ }
1431
+ const rendered = /* @__PURE__ */ new Set();
1432
+ for (const topIdx of topLevelShapes) {
1433
+ if (rendered.has(topIdx)) continue;
1434
+ const shape = shapes[topIdx];
1435
+ const children = shapeChildren.get(topIdx) || [];
1436
+ ctx2.beginPath();
1437
+ let ptr = shape.strip.start * 3;
1438
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1439
+ for (let i = 1; i < shape.strip.count; i++) {
1440
+ ptr = (shape.strip.start + i) * 3;
1441
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1442
+ }
1443
+ ctx2.closePath();
1444
+ for (const childIdx of children) {
1445
+ const child = shapes[childIdx];
1446
+ ptr = child.strip.start * 3;
1447
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1448
+ for (let i = 1; i < child.strip.count; i++) {
1449
+ ptr = (child.strip.start + i) * 3;
1450
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1451
+ }
1452
+ ctx2.closePath();
1453
+ rendered.add(childIdx);
1454
+ const grandChildren = shapeChildren.get(childIdx) || [];
1455
+ for (const grandIdx of grandChildren) {
1456
+ if (!rendered.has(grandIdx)) {
1457
+ topLevelShapes.push(grandIdx);
1458
+ }
1459
+ }
1460
+ }
1461
+ ctx2.fill("evenodd");
1462
+ rendered.add(topIdx);
1463
+ }
1464
+ if (includeTraces) {
1465
+ for (const strip of traces) {
1466
+ ctx2.beginPath();
1467
+ let ptr = strip.start * 3;
1468
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1469
+ for (let i = 1; i < strip.count; i++) {
1470
+ ptr = (strip.start + i) * 3;
1471
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1472
+ }
1473
+ ctx2.stroke();
1474
+ }
1475
+ }
1476
+ }
1477
+ function drawOutlineFillAllClosed(ctx2, data) {
1478
+ if (!data || !data.lineStrips || !data.vertices) return;
1479
+ const vertices = data.vertices;
1480
+ const lineStrips = data.lineStrips;
1481
+ const closedPaths = [];
1482
+ for (const strip of lineStrips) {
1483
+ if (strip.count < 3) continue;
1484
+ let ptr2 = strip.start * 3;
1485
+ const startX = vertices[ptr2];
1486
+ const startY = vertices[ptr2 + 1];
1487
+ const lastPtr = (strip.start + strip.count - 1) * 3;
1488
+ const lastX = vertices[lastPtr];
1489
+ const lastY = vertices[lastPtr + 1];
1490
+ const dist = Math.sqrt(Math.pow(startX - lastX, 2) + Math.pow(startY - lastY, 2));
1491
+ if (dist < 1) {
1492
+ let area = 0;
1493
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1494
+ for (let i = 0; i < strip.count; i++) {
1495
+ const p1 = (strip.start + i) * 3;
1496
+ const p2 = (strip.start + (i + 1) % strip.count) * 3;
1497
+ area += vertices[p1] * vertices[p2 + 1];
1498
+ area -= vertices[p2] * vertices[p1 + 1];
1499
+ const x = vertices[p1];
1500
+ const y = vertices[p1 + 1];
1501
+ if (x < minX) minX = x;
1502
+ if (x > maxX) maxX = x;
1503
+ if (y < minY) minY = y;
1504
+ if (y > maxY) maxY = y;
1505
+ }
1506
+ area = Math.abs(area) / 2;
1507
+ closedPaths.push({ strip, area, bbox: { minX, minY, maxX, maxY } });
1508
+ }
1509
+ }
1510
+ const sortedByAreaForCheck = [...closedPaths].sort((a, b) => b.area - a.area);
1511
+ const topPaths = sortedByAreaForCheck.slice(0, Math.min(10, sortedByAreaForCheck.length));
1512
+ const strokeOutlineCount = topPaths.filter((path) => {
1513
+ const w = path.bbox.maxX - path.bbox.minX;
1514
+ const h = path.bbox.maxY - path.bbox.minY;
1515
+ const aspectRatio = Math.max(w / h, h / w);
1516
+ return aspectRatio > 50;
1517
+ }).length;
1518
+ const isStrokeOutlineMode = closedPaths.length > 0 && strokeOutlineCount >= topPaths.length * 0.8;
1519
+ if (closedPaths.length === 0 || isStrokeOutlineMode) {
1520
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u68C0\u6D4B\u5230\u7EBF\u6761\u8F6E\u5ED3\u6A21\u5F0F\uFF0C\u4F7F\u7528\u53C2\u8003\u8FB9\u754C\u4F5C\u4E3A\u77E9\u5F62\u8F6E\u5ED3`);
1521
+ if (refBounds) {
1522
+ const refWidth = refBounds.maxX - refBounds.minX;
1523
+ const refHeight = refBounds.maxY - refBounds.minY;
1524
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u77E9\u5F62\u8F6E\u5ED3 ${refWidth.toFixed(2)} x ${refHeight.toFixed(2)}`);
1525
+ ctx2.beginPath();
1526
+ ctx2.rect(refBounds.minX, refBounds.minY, refWidth, refHeight);
1527
+ ctx2.fill();
1528
+ }
1529
+ return;
1530
+ }
1531
+ let minDimThreshold = 1e-3;
1532
+ let minAreaThreshold = 1e-4;
1533
+ if (refBounds) {
1534
+ const refWidth = refBounds.maxX - refBounds.minX;
1535
+ const refHeight = refBounds.maxY - refBounds.minY;
1536
+ minDimThreshold = Math.min(refWidth, refHeight) * 5e-3;
1537
+ minAreaThreshold = refWidth * refHeight * 1e-3;
1538
+ }
1539
+ const sortedByArea = [...closedPaths].sort((a, b) => b.area - a.area);
1540
+ if (sortedByArea.length > 0) {
1541
+ const top5 = sortedByArea.slice(0, 5);
1542
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u9762\u79EF\u6700\u5927\u7684 5 \u4E2A\u533A\u57DF:`);
1543
+ top5.forEach((p, i) => {
1544
+ const w = p.bbox.maxX - p.bbox.minX;
1545
+ const h = p.bbox.maxY - p.bbox.minY;
1546
+ const ar = Math.max(w / h, h / w);
1547
+ if (DEBUG) console.log(` ${i + 1}. \u9762\u79EF=${p.area.toFixed(4)}, \u5C3A\u5BF8=${w.toFixed(3)} x ${h.toFixed(3)}, \u5BBD\u9AD8\u6BD4=${ar.toFixed(1)}`);
1548
+ });
1549
+ }
1550
+ const validShapePaths = closedPaths.filter((path) => {
1551
+ const pathWidth = path.bbox.maxX - path.bbox.minX;
1552
+ const pathHeight = path.bbox.maxY - path.bbox.minY;
1553
+ const pathArea = path.area;
1554
+ if (pathWidth < minDimThreshold || pathHeight < minDimThreshold) return false;
1555
+ if (pathArea < minAreaThreshold) return false;
1556
+ const aspectRatio = Math.max(pathWidth / pathHeight, pathHeight / pathWidth);
1557
+ if (aspectRatio > 10) return false;
1558
+ return true;
1559
+ });
1560
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u5F62\u72B6\u8FC7\u6EE4\u540E ${closedPaths.length} \u2192 ${validShapePaths.length} \u4E2A\u6709\u6548\u533A\u57DF (\u9608\u503C: \u5C3A\u5BF8>${minDimThreshold.toFixed(4)}, \u9762\u79EF>${minAreaThreshold.toFixed(4)})`);
1561
+ let candidatePaths = validShapePaths;
1562
+ if (refBounds && validShapePaths.length > 0) {
1563
+ const refWidth = refBounds.maxX - refBounds.minX;
1564
+ const refHeight = refBounds.maxY - refBounds.minY;
1565
+ const refArea = refWidth * refHeight;
1566
+ const refCenterX = (refBounds.minX + refBounds.maxX) / 2;
1567
+ const refCenterY = (refBounds.minY + refBounds.maxY) / 2;
1568
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u53C2\u8003\u8FB9\u754C = ${refWidth.toFixed(2)} x ${refHeight.toFixed(2)}, \u4E2D\u5FC3 = (${refCenterX.toFixed(2)}, ${refCenterY.toFixed(2)})`);
1569
+ const containingPaths = validShapePaths.filter((path) => {
1570
+ const bbox = path.bbox;
1571
+ const margin = Math.max(refWidth, refHeight) * 0.1;
1572
+ const containsRef = bbox.minX <= refBounds.minX + margin && bbox.maxX >= refBounds.maxX - margin && bbox.minY <= refBounds.minY + margin && bbox.maxY >= refBounds.maxY - margin;
1573
+ return containsRef;
1574
+ });
1575
+ if (containingPaths.length > 0) {
1576
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u627E\u5230 ${containingPaths.length} \u4E2A\u5305\u542B\u53C2\u8003\u8FB9\u754C\u7684\u8F6E\u5ED3`);
1577
+ candidatePaths = containingPaths;
1578
+ } else {
1579
+ const overlappingPaths = validShapePaths.filter((path) => {
1580
+ const bbox = path.bbox;
1581
+ const pathWidth = bbox.maxX - bbox.minX;
1582
+ const pathHeight = bbox.maxY - bbox.minY;
1583
+ const pathArea = pathWidth * pathHeight;
1584
+ const hasOverlap = !(bbox.maxX < refBounds.minX || bbox.minX > refBounds.maxX || bbox.maxY < refBounds.minY || bbox.minY > refBounds.maxY);
1585
+ const isReasonableSize = pathArea >= refArea * 0.3;
1586
+ return hasOverlap && isReasonableSize;
1587
+ });
1588
+ if (overlappingPaths.length > 0) {
1589
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u627E\u5230 ${overlappingPaths.length} \u4E2A\u4E0E\u53C2\u8003\u8FB9\u754C\u91CD\u53E0\u7684\u8F6E\u5ED3`);
1590
+ candidatePaths = overlappingPaths;
1591
+ } else {
1592
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42: \u8FC7\u6EE4\u540E\u65E0\u6709\u6548\u533A\u57DF\uFF0C\u4F7F\u7528\u6240\u6709 ${validShapePaths.length} \u4E2A\u6709\u6548\u5F62\u72B6\u533A\u57DF`);
1593
+ candidatePaths = validShapePaths;
1594
+ }
1595
+ }
1596
+ }
1597
+ let mainOutline = null;
1598
+ let useRefBoundsAsOutline = false;
1599
+ if (candidatePaths.length > 1) {
1600
+ candidatePaths.sort((a, b) => b.area - a.area);
1601
+ mainOutline = candidatePaths[0];
1602
+ const selectedWidth = mainOutline.bbox.maxX - mainOutline.bbox.minX;
1603
+ const selectedHeight = mainOutline.bbox.maxY - mainOutline.bbox.minY;
1604
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42\u9009\u62E9: \u4ECE ${candidatePaths.length} \u4E2A\u5019\u9009\u533A\u57DF\u4E2D\uFF0C\u9009\u62E9\u9762\u79EF\u6700\u5927\u7684 ${selectedWidth.toFixed(2)} x ${selectedHeight.toFixed(2)} \u7684\u533A\u57DF (\u9762\u79EF=${mainOutline.area.toFixed(4)})`);
1605
+ } else if (candidatePaths.length === 1) {
1606
+ mainOutline = candidatePaths[0];
1607
+ const selectedWidth = mainOutline.bbox.maxX - mainOutline.bbox.minX;
1608
+ const selectedHeight = mainOutline.bbox.maxY - mainOutline.bbox.minY;
1609
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42\u9009\u62E9: \u53EA\u6709 1 \u4E2A\u5019\u9009\u533A\u57DF\uFF0C\u5C3A\u5BF8 = ${selectedWidth.toFixed(2)} x ${selectedHeight.toFixed(2)}`);
1610
+ } else if (refBounds) {
1611
+ const refWidth = refBounds.maxX - refBounds.minX;
1612
+ const refHeight = refBounds.maxY - refBounds.minY;
1613
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42\u9009\u62E9: \u65E0\u6709\u6548\u8F6E\u5ED3\uFF0C\u4F7F\u7528\u53C2\u8003\u8FB9\u754C\u4F5C\u4E3A\u77E9\u5F62\u8F6E\u5ED3 ${refWidth.toFixed(2)} x ${refHeight.toFixed(2)}`);
1614
+ useRefBoundsAsOutline = true;
1615
+ ctx2.beginPath();
1616
+ ctx2.rect(refBounds.minX, refBounds.minY, refWidth, refHeight);
1617
+ ctx2.fill();
1618
+ return;
1619
+ } else {
1620
+ closedPaths.sort((a, b) => b.area - a.area);
1621
+ mainOutline = closedPaths[0];
1622
+ const selectedWidth = mainOutline.bbox.maxX - mainOutline.bbox.minX;
1623
+ const selectedHeight = mainOutline.bbox.maxY - mainOutline.bbox.minY;
1624
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42\u9009\u62E9: \u65E0\u5019\u9009\u533A\u57DF\uFF0C\u9009\u62E9\u9762\u79EF\u6700\u5927\u7684\u533A\u57DF ${selectedWidth.toFixed(2)} x ${selectedHeight.toFixed(2)}`);
1625
+ }
1626
+ const mainBbox = mainOutline.bbox;
1627
+ const innerPaths = candidatePaths.filter((path) => {
1628
+ if (path === mainOutline) return false;
1629
+ const bbox = path.bbox;
1630
+ const isInside = bbox.minX >= mainBbox.minX - 0.1 && bbox.maxX <= mainBbox.maxX + 0.1 && bbox.minY >= mainBbox.minY - 0.1 && bbox.maxY <= mainBbox.maxY + 0.1;
1631
+ const pathWidth = bbox.maxX - bbox.minX;
1632
+ const pathHeight = bbox.maxY - bbox.minY;
1633
+ const mainWidth = mainBbox.maxX - mainBbox.minX;
1634
+ const mainHeight = mainBbox.maxY - mainBbox.minY;
1635
+ const minSize = Math.max(1, Math.min(mainWidth, mainHeight) * 5e-3);
1636
+ const isReasonableSize = pathWidth > minSize && pathHeight > minSize;
1637
+ return isInside && isReasonableSize;
1638
+ });
1639
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] GKO\u5C42\u5F02\u5F62: \u4E3B\u8F6E\u5ED3 + ${innerPaths.length} \u4E2A\u5185\u90E8\u5F00\u53E3/\u94E3\u69FD`);
1640
+ ctx2.beginPath();
1641
+ let ptr = mainOutline.strip.start * 3;
1642
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1643
+ for (let i = 1; i < mainOutline.strip.count; i++) {
1644
+ ptr = (mainOutline.strip.start + i) * 3;
1645
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1646
+ }
1647
+ ctx2.closePath();
1648
+ for (const innerPath of innerPaths) {
1649
+ ptr = innerPath.strip.start * 3;
1650
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1651
+ for (let i = 1; i < innerPath.strip.count; i++) {
1652
+ ptr = (innerPath.strip.start + i) * 3;
1653
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1654
+ }
1655
+ ctx2.closePath();
1656
+ }
1657
+ ctx2.fill("evenodd");
1658
+ }
1659
+ function drawDrillHoles(ctx2, data) {
1660
+ if (!data) return;
1661
+ const vertices = data.vertices;
1662
+ const lineStrips = data.lineStrips;
1663
+ if (!lineStrips || !vertices) {
1664
+ if (DEBUG) console.log("[\u4EFF\u771F\u56FE] \u94BB\u5B54\u6570\u636E\u7F3A\u5931 lineStrips \u6216 vertices");
1665
+ return;
1666
+ }
1667
+ const circles = [];
1668
+ const slots = [];
1669
+ let skippedCount = 0;
1670
+ for (const strip of lineStrips) {
1671
+ if (strip.count < 8) {
1672
+ skippedCount++;
1673
+ continue;
1674
+ }
1675
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1676
+ for (let i = 0; i < strip.count; i++) {
1677
+ const ptr = (strip.start + i) * 3;
1678
+ minX = Math.min(minX, vertices[ptr]);
1679
+ minY = Math.min(minY, vertices[ptr + 1]);
1680
+ maxX = Math.max(maxX, vertices[ptr]);
1681
+ maxY = Math.max(maxY, vertices[ptr + 1]);
1682
+ }
1683
+ const bboxWidth = maxX - minX;
1684
+ const bboxHeight = maxY - minY;
1685
+ const aspectRatio = Math.max(bboxWidth, bboxHeight) / Math.max(1e-3, Math.min(bboxWidth, bboxHeight));
1686
+ const cx = (minX + maxX) / 2;
1687
+ const cy = (minY + maxY) / 2;
1688
+ if (aspectRatio > 1.3) {
1689
+ slots.push({ strip });
1690
+ } else {
1691
+ let sumR = 0;
1692
+ for (let i = 0; i < strip.count; i++) {
1693
+ const ptr = (strip.start + i) * 3;
1694
+ const dx = vertices[ptr] - cx;
1695
+ const dy = vertices[ptr + 1] - cy;
1696
+ sumR += Math.sqrt(dx * dx + dy * dy);
1697
+ }
1698
+ const avgR = sumR / strip.count;
1699
+ if (avgR > 1e-3) {
1700
+ circles.push({ cx, cy, r: avgR });
1701
+ } else {
1702
+ skippedCount++;
1703
+ }
1704
+ }
1705
+ }
1706
+ if (circles.length > 0) {
1707
+ ctx2.beginPath();
1708
+ for (const c of circles) {
1709
+ ctx2.moveTo(c.cx + c.r, c.cy);
1710
+ ctx2.arc(c.cx, c.cy, c.r, 0, Math.PI * 2);
1711
+ }
1712
+ ctx2.fill();
1713
+ }
1714
+ if (slots.length > 0) {
1715
+ ctx2.beginPath();
1716
+ for (const s of slots) {
1717
+ const strip = s.strip;
1718
+ let ptr = strip.start * 3;
1719
+ ctx2.moveTo(vertices[ptr], vertices[ptr + 1]);
1720
+ for (let i = 1; i < strip.count; i++) {
1721
+ ptr = (strip.start + i) * 3;
1722
+ ctx2.lineTo(vertices[ptr], vertices[ptr + 1]);
1723
+ }
1724
+ ctx2.closePath();
1725
+ }
1726
+ ctx2.fill();
1727
+ }
1728
+ if (DEBUG) console.log(`[\u4EFF\u771F\u56FE] \u94BB\u5B54\u6E32\u67D3\u5B8C\u6210: \u7ED8\u5236 ${circles.length + slots.length} \u4E2A (\u5706\u5B54=${circles.length}, \u69FD\u5B54=${slots.length}), \u8DF3\u8FC7 ${skippedCount} \u4E2A`);
1729
+ }
1730
+ function hexToRgba(hex, alpha) {
1731
+ const r = parseInt(hex.slice(1, 3), 16);
1732
+ const g = parseInt(hex.slice(3, 5), 16);
1733
+ const b = parseInt(hex.slice(5, 7), 16);
1734
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1735
+ }
1736
+ function setView(view) {
1737
+ currentView = view;
1738
+ document.getElementById("btnTopView").classList.toggle("active", view === "top");
1739
+ document.getElementById("btnBottomView").classList.toggle("active", view === "bottom");
1740
+ panOffset = { x: 0, y: 0 };
1741
+ render();
1742
+ }
1743
+ function fitToView() {
1744
+ zoomLevel = 1;
1745
+ panOffset = { x: 0, y: 0 };
1746
+ }
1747
+ function showLoading(text) {
1748
+ loadingText.textContent = text;
1749
+ loadingOverlay.classList.remove("hidden");
1750
+ }
1751
+ function hideLoading() {
1752
+ loadingOverlay.classList.add("hidden");
1753
+ }
1754
+ function updateStatus(text, isError = false) {
1755
+ if (statusLeft) {
1756
+ statusLeft.textContent = text;
1757
+ }
1758
+ if (isError) {
1759
+ console.error(`[simulation] \u9519\u8BEF: ${text}`);
1760
+ } else if (text.includes("%")) {
1761
+ console.log(`[simulation] \u72B6\u6001: ${text}`);
1762
+ } else {
1763
+ console.log(`[simulation] \u72B6\u6001: ${text}`);
1764
+ }
1765
+ }
1766
+ function updateLayerList() {
1767
+ }
1768
+ function clearAll() {
1769
+ layers = {};
1770
+ boardBounds = null;
1771
+ refBounds = null;
1772
+ zoomLevel = 1;
1773
+ panOffset = { x: 0, y: 0 };
1774
+ invalidateCache();
1775
+ render();
1776
+ updateLayerList();
1777
+ updateStatus("\u5C31\u7EEA");
1778
+ }
1779
+ function exportImage() {
1780
+ if (!canvas || !boardBounds) {
1781
+ alert("\u8BF7\u5148\u52A0\u8F7D Gerber \u6587\u4EF6");
1782
+ return;
1783
+ }
1784
+ const link = document.createElement("a");
1785
+ link.download = `pcb_simulation_${currentView}.png`;
1786
+ link.href = canvas.toDataURL("image/png");
1787
+ link.click();
1788
+ }
1789
+ window.simulationViewer = {
1790
+ loadFromId,
1791
+ loadFromUrl,
1792
+ render,
1793
+ setView,
1794
+ config,
1795
+ handleFiles,
1796
+ // 导出文件处理函数,供 render 页面调用
1797
+ initSimulation
1798
+ // 导出初始化函数
1799
+ };
1800
+ window.simulationHandleFiles = handleFiles;
1801
+ //# sourceMappingURL=gerber-simulation-entry-EBDX72XE.js.map