@omrips/viewstl 2.0.0-rc.1 → 2.0.0-rc.3
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.
- package/README.md +41 -13
- package/build/v2/README.md +4 -0
- package/build/v2/node/analysis-worker.js +3833 -0
- package/build/v2/node/index.cjs +382 -0
- package/build/v2/node/index.d.cts +122 -0
- package/build/v2/node/index.d.ts +122 -0
- package/build/v2/node/index.js +352 -0
- package/build/v2/stl_viewer.min.js +1 -1
- package/build/v2/viewstl.cjs +1 -1
- package/build/v2/viewstl.js +1 -1
- package/build/v2/viewstl.min.js +1 -1
- package/docs/v2-compatibility.md +1 -1
- package/docs/v2-usage.md +123 -2
- package/docs/v2-verification.md +48 -0
- package/package.json +15 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/v2/node/commonjs.js
|
|
20
|
+
var commonjs_exports = {};
|
|
21
|
+
__export(commonjs_exports, {
|
|
22
|
+
analyzeBuffer: () => analyzeBuffer,
|
|
23
|
+
analyzeFile: () => analyzeFile,
|
|
24
|
+
createAnalyzer: () => createAnalyzer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(commonjs_exports);
|
|
27
|
+
var import_node_path2 = require("node:path");
|
|
28
|
+
var import_node_url2 = require("node:url");
|
|
29
|
+
|
|
30
|
+
// src/v2/node/api.js
|
|
31
|
+
var import_node_worker_threads = require("node:worker_threads");
|
|
32
|
+
var import_node_path = require("node:path");
|
|
33
|
+
var import_node_url = require("node:url");
|
|
34
|
+
|
|
35
|
+
// src/v2/memory.js
|
|
36
|
+
var MEMORY_OVERHEAD = 64 * 1024 * 1024;
|
|
37
|
+
|
|
38
|
+
// src/v2/geometry.js
|
|
39
|
+
var DEFAULT_LIMITS = Object.freeze({
|
|
40
|
+
max_file_bytes: 256 * 1024 * 1024,
|
|
41
|
+
max_triangles: 5e6,
|
|
42
|
+
max_uncompressed_bytes: 256 * 1024 * 1024,
|
|
43
|
+
max_archive_entries: 1024,
|
|
44
|
+
max_memory_bytes: Number.MAX_SAFE_INTEGER
|
|
45
|
+
});
|
|
46
|
+
function limitsWith(options = {}) {
|
|
47
|
+
const limits = { ...DEFAULT_LIMITS };
|
|
48
|
+
for (const key of Object.keys(limits))
|
|
49
|
+
if (options[key] !== void 0) {
|
|
50
|
+
if (!Number.isSafeInteger(options[key]) || options[key] < 1)
|
|
51
|
+
throw new Error(`Invalid ${key}`);
|
|
52
|
+
limits[key] = options[key];
|
|
53
|
+
}
|
|
54
|
+
return limits;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/v2/print-options.js
|
|
58
|
+
function printOptions(input = {}) {
|
|
59
|
+
const level = input.level ?? "basic";
|
|
60
|
+
if (!["basic", "detailed"].includes(level))
|
|
61
|
+
throw new Error("Choose basic or detailed print checks");
|
|
62
|
+
const number = (v, name, max = 1e9) => {
|
|
63
|
+
if (!Number.isFinite(v) || v <= 0 || v > max)
|
|
64
|
+
throw new Error(`Invalid ${name}`);
|
|
65
|
+
return v;
|
|
66
|
+
};
|
|
67
|
+
let profile = null;
|
|
68
|
+
if (input.profile != null) {
|
|
69
|
+
if (input.profile.technology !== "fdm")
|
|
70
|
+
throw new Error("Only FDM profiles are supported");
|
|
71
|
+
profile = { technology: "fdm" };
|
|
72
|
+
for (const key of ["min_wall_mm", "line_width_mm"])
|
|
73
|
+
if (input.profile[key] != null)
|
|
74
|
+
profile[key] = number(input.profile[key], key);
|
|
75
|
+
if (input.profile.overhang_degrees != null)
|
|
76
|
+
profile.overhang_degrees = number(
|
|
77
|
+
input.profile.overhang_degrees,
|
|
78
|
+
"overhang angle",
|
|
79
|
+
89.9
|
|
80
|
+
);
|
|
81
|
+
if (input.profile.build_volume_mm != null) {
|
|
82
|
+
if (!Array.isArray(input.profile.build_volume_mm) || input.profile.build_volume_mm.length !== 3)
|
|
83
|
+
throw new Error("Build volume requires X, Y and Z");
|
|
84
|
+
profile.build_volume_mm = input.profile.build_volume_mm.map(
|
|
85
|
+
(v) => number(v, "build volume")
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const time = number(input.max_time_ms ?? 3e4, "check time limit", 12e4);
|
|
90
|
+
return {
|
|
91
|
+
level,
|
|
92
|
+
profile,
|
|
93
|
+
apply_transforms: input.apply_transforms !== false,
|
|
94
|
+
units_confirmed: input.units_confirmed === true,
|
|
95
|
+
max_time_ms: time
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/v2/node/api.js
|
|
100
|
+
var MB = 1024 ** 2;
|
|
101
|
+
var defaults = {
|
|
102
|
+
max_file_bytes: 64 * MB,
|
|
103
|
+
max_triangles: 1e6,
|
|
104
|
+
max_uncompressed_bytes: 128 * MB,
|
|
105
|
+
max_archive_entries: 1024,
|
|
106
|
+
max_memory_bytes: 1024 * MB
|
|
107
|
+
};
|
|
108
|
+
var formats = /* @__PURE__ */ new Map([
|
|
109
|
+
["stl", "stl"],
|
|
110
|
+
["obj", "obj"],
|
|
111
|
+
["3mf", "3mf"],
|
|
112
|
+
["step", "step"],
|
|
113
|
+
["stp", "step"],
|
|
114
|
+
["iges", "iges"],
|
|
115
|
+
["igs", "iges"]
|
|
116
|
+
]);
|
|
117
|
+
var units = /* @__PURE__ */ new Set(["mm", "cm", "m", "in", "ft", "um"]);
|
|
118
|
+
function failure(message, code, name = "Error") {
|
|
119
|
+
return Object.assign(new Error(message), { code, name });
|
|
120
|
+
}
|
|
121
|
+
function integer(value, name, min, max) {
|
|
122
|
+
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
123
|
+
throw new TypeError(`Invalid ${name}`);
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
function localPath(input) {
|
|
127
|
+
if (input instanceof URL) {
|
|
128
|
+
if (input.protocol !== "file:" || input.hostname && input.hostname !== "localhost")
|
|
129
|
+
throw new TypeError("Only local file paths and file: URLs are supported");
|
|
130
|
+
input = (0, import_node_url.fileURLToPath)(input);
|
|
131
|
+
}
|
|
132
|
+
if (typeof input !== "string" || !input || input.includes("\0") || /^[\\/]{2}/.test(input) || /^[a-z][a-z\d+.-]*:/i.test(input) && !/^[a-z]:[\\/]/i.test(input))
|
|
133
|
+
throw new TypeError("Only local file paths and file: URLs are supported");
|
|
134
|
+
return (0, import_node_path.resolve)(input);
|
|
135
|
+
}
|
|
136
|
+
function settings(input, filename) {
|
|
137
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
138
|
+
throw new TypeError("Analysis options must be an object");
|
|
139
|
+
const format = formats.get(
|
|
140
|
+
String(input.format ?? filename.split(".").pop()).toLowerCase()
|
|
141
|
+
);
|
|
142
|
+
if (!format)
|
|
143
|
+
throw failure(
|
|
144
|
+
"Choose STL, OBJ, 3MF, STEP/STP or IGES/IGS",
|
|
145
|
+
"ERR_UNSUPPORTED_FORMAT"
|
|
146
|
+
);
|
|
147
|
+
if (input.units !== void 0 && !units.has(input.units))
|
|
148
|
+
throw new TypeError("Units must be mm, cm, m, in, ft or um");
|
|
149
|
+
if (input.units !== void 0 && input.units !== "mm" && !["stl", "obj"].includes(format))
|
|
150
|
+
throw new TypeError(
|
|
151
|
+
"3MF and CAD file units are honored and converted to mm; units cannot override them"
|
|
152
|
+
);
|
|
153
|
+
if (input.limits !== void 0 && (!input.limits || typeof input.limits !== "object" || Array.isArray(input.limits)))
|
|
154
|
+
throw new TypeError("limits must be an object");
|
|
155
|
+
for (const key of Object.keys(input.limits ?? {}))
|
|
156
|
+
if (!Object.hasOwn(defaults, key))
|
|
157
|
+
throw new TypeError(`Unknown analysis limit: ${key}`);
|
|
158
|
+
const overrides = Object.fromEntries(
|
|
159
|
+
Object.entries(input.limits ?? {}).filter(
|
|
160
|
+
([, value]) => value !== void 0
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
const limits = limitsWith({ ...defaults, ...overrides });
|
|
164
|
+
const timeout_ms = integer(
|
|
165
|
+
input.timeout_ms ?? 12e4,
|
|
166
|
+
"timeout_ms",
|
|
167
|
+
1,
|
|
168
|
+
2147483647
|
|
169
|
+
);
|
|
170
|
+
const checks = printOptions({
|
|
171
|
+
level: input.level ?? "detailed",
|
|
172
|
+
profile: input.profile,
|
|
173
|
+
max_time_ms: input.max_time_ms ?? 3e4
|
|
174
|
+
});
|
|
175
|
+
if (input.on_progress !== void 0 && typeof input.on_progress !== "function")
|
|
176
|
+
throw new TypeError("on_progress must be a function");
|
|
177
|
+
if (input.signal !== void 0 && !(input.signal instanceof AbortSignal))
|
|
178
|
+
throw new TypeError("signal must be an AbortSignal");
|
|
179
|
+
return { filename, format, units: input.units, limits, timeout_ms, checks };
|
|
180
|
+
}
|
|
181
|
+
function createAPI(workerURL) {
|
|
182
|
+
function createAnalyzer2(options = {}) {
|
|
183
|
+
const concurrency = integer(options.concurrency ?? 2, "concurrency", 1, 8);
|
|
184
|
+
const maxQueue = integer(options.max_queue ?? 16, "max_queue", 0, 1024);
|
|
185
|
+
const maxQueuedBytes = integer(
|
|
186
|
+
options.max_queued_bytes ?? 128 * MB,
|
|
187
|
+
"max_queued_bytes",
|
|
188
|
+
1,
|
|
189
|
+
Number.MAX_SAFE_INTEGER
|
|
190
|
+
);
|
|
191
|
+
const queue = [], active = /* @__PURE__ */ new Set();
|
|
192
|
+
let closed = false, queuedBytes = 0;
|
|
193
|
+
const abortError = () => failure("Analysis cancelled", "ABORT_ERR", "AbortError");
|
|
194
|
+
function finish(job, error, result) {
|
|
195
|
+
if (job.done) return job.finished;
|
|
196
|
+
job.done = true;
|
|
197
|
+
clearTimeout(job.timer);
|
|
198
|
+
job.signal?.removeEventListener("abort", job.abort);
|
|
199
|
+
const index = queue.indexOf(job);
|
|
200
|
+
if (index >= 0) {
|
|
201
|
+
queue.splice(index, 1);
|
|
202
|
+
queuedBytes -= job.bytes;
|
|
203
|
+
}
|
|
204
|
+
job.task = null;
|
|
205
|
+
job.finished = (async () => {
|
|
206
|
+
if (job.worker) await job.worker.terminate().catch(() => {
|
|
207
|
+
});
|
|
208
|
+
active.delete(job);
|
|
209
|
+
error ? job.reject(error) : job.resolve(result);
|
|
210
|
+
pump();
|
|
211
|
+
})();
|
|
212
|
+
return job.finished;
|
|
213
|
+
}
|
|
214
|
+
function pump() {
|
|
215
|
+
while (!closed && active.size < concurrency && queue.length) {
|
|
216
|
+
const job = queue.shift();
|
|
217
|
+
queuedBytes -= job.bytes;
|
|
218
|
+
active.add(job);
|
|
219
|
+
try {
|
|
220
|
+
const task = job.task;
|
|
221
|
+
job.worker = new import_node_worker_threads.Worker(workerURL, {
|
|
222
|
+
workerData: task,
|
|
223
|
+
transferList: task.buffer ? [task.buffer] : [],
|
|
224
|
+
// CLI eval/test flags are not valid entry flags for a file worker.
|
|
225
|
+
execArgv: [],
|
|
226
|
+
resourceLimits: {
|
|
227
|
+
maxOldGenerationSizeMb: Math.max(
|
|
228
|
+
16,
|
|
229
|
+
Math.min(
|
|
230
|
+
4096,
|
|
231
|
+
Math.floor(task.limits.max_memory_bytes / MB / 2)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
job.task = null;
|
|
237
|
+
job.worker.on("message", (message) => {
|
|
238
|
+
if (job.done) return;
|
|
239
|
+
if (message.progress) {
|
|
240
|
+
try {
|
|
241
|
+
Promise.resolve(job.progress?.(message.progress)).catch(
|
|
242
|
+
(error) => void finish(job, error)
|
|
243
|
+
);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
void finish(job, error);
|
|
246
|
+
}
|
|
247
|
+
} else if (message.error) {
|
|
248
|
+
void finish(
|
|
249
|
+
job,
|
|
250
|
+
failure(
|
|
251
|
+
message.error.message,
|
|
252
|
+
message.error.code,
|
|
253
|
+
message.error.name
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
} else if (message.result) void finish(job, null, message.result);
|
|
257
|
+
else
|
|
258
|
+
void finish(
|
|
259
|
+
job,
|
|
260
|
+
failure(
|
|
261
|
+
"Invalid analysis worker response",
|
|
262
|
+
"ERR_ANALYSIS_WORKER"
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
job.worker.on("error", (error) => void finish(job, error));
|
|
267
|
+
job.worker.on("exit", (code) => {
|
|
268
|
+
if (!job.done)
|
|
269
|
+
void finish(
|
|
270
|
+
job,
|
|
271
|
+
failure(
|
|
272
|
+
`Analysis worker stopped (exit ${code})`,
|
|
273
|
+
"ERR_ANALYSIS_WORKER"
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
} catch (error) {
|
|
278
|
+
void finish(job, error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function submit(task, input, source) {
|
|
283
|
+
if (closed)
|
|
284
|
+
return Promise.reject(
|
|
285
|
+
failure("Analyzer is closed", "ERR_ANALYZER_CLOSED")
|
|
286
|
+
);
|
|
287
|
+
if (input.signal?.aborted) return Promise.reject(abortError());
|
|
288
|
+
if (active.size >= concurrency && queue.length >= maxQueue)
|
|
289
|
+
return Promise.reject(
|
|
290
|
+
failure("Analysis queue is full", "ERR_ANALYZER_BUSY")
|
|
291
|
+
);
|
|
292
|
+
const bytes = source?.byteLength ?? 0;
|
|
293
|
+
if (bytes > task.limits.max_file_bytes)
|
|
294
|
+
return Promise.reject(
|
|
295
|
+
failure("File size limit exceeded", "ERR_FILE_TOO_LARGE")
|
|
296
|
+
);
|
|
297
|
+
if (bytes * 2 + 64 * MB > task.limits.max_memory_bytes)
|
|
298
|
+
return Promise.reject(
|
|
299
|
+
failure(
|
|
300
|
+
"Input exceeds the analysis memory budget",
|
|
301
|
+
"ERR_ANALYSIS_LIMIT"
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
if (active.size >= concurrency && queuedBytes + bytes > maxQueuedBytes)
|
|
305
|
+
return Promise.reject(
|
|
306
|
+
failure("Queued input memory limit exceeded", "ERR_ANALYZER_BUSY")
|
|
307
|
+
);
|
|
308
|
+
if (source) task.buffer = new Uint8Array(source).buffer;
|
|
309
|
+
return new Promise((resolveJob, reject) => {
|
|
310
|
+
const job = {
|
|
311
|
+
task,
|
|
312
|
+
bytes,
|
|
313
|
+
signal: input.signal,
|
|
314
|
+
progress: input.on_progress,
|
|
315
|
+
resolve: resolveJob,
|
|
316
|
+
reject
|
|
317
|
+
};
|
|
318
|
+
job.abort = () => void finish(job, abortError());
|
|
319
|
+
job.signal?.addEventListener("abort", job.abort, { once: true });
|
|
320
|
+
job.timer = setTimeout(
|
|
321
|
+
() => void finish(
|
|
322
|
+
job,
|
|
323
|
+
failure("Analysis timed out", "ERR_ANALYSIS_TIMEOUT")
|
|
324
|
+
),
|
|
325
|
+
task.timeout_ms
|
|
326
|
+
);
|
|
327
|
+
queue.push(job);
|
|
328
|
+
queuedBytes += bytes;
|
|
329
|
+
pump();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
async analyzeFile(path, options2 = {}) {
|
|
334
|
+
path = localPath(path);
|
|
335
|
+
return submit({ ...settings(options2, (0, import_node_path.basename)(path)), path }, options2);
|
|
336
|
+
},
|
|
337
|
+
async analyzeBuffer(data, options2 = {}) {
|
|
338
|
+
let bytes;
|
|
339
|
+
if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);
|
|
340
|
+
else if (data instanceof Uint8Array && data.buffer instanceof ArrayBuffer)
|
|
341
|
+
bytes = data;
|
|
342
|
+
else
|
|
343
|
+
throw new TypeError(
|
|
344
|
+
"Expected an ArrayBuffer, Uint8Array or Node Buffer"
|
|
345
|
+
);
|
|
346
|
+
const filename = options2.filename ?? (options2.format ? `model.${options2.format}` : "");
|
|
347
|
+
if (typeof filename !== "string" || !filename || filename.includes("\0"))
|
|
348
|
+
throw new TypeError("Provide a filename or format for buffer input");
|
|
349
|
+
return submit(
|
|
350
|
+
settings(options2, filename.split(/[\\/]/).pop()),
|
|
351
|
+
options2,
|
|
352
|
+
bytes
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
async dispose() {
|
|
356
|
+
closed = true;
|
|
357
|
+
await Promise.all(
|
|
358
|
+
[...queue, ...active].map(
|
|
359
|
+
(job) => finish(job, failure("Analyzer is closed", "ERR_ANALYZER_CLOSED"))
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const shared = createAnalyzer2();
|
|
366
|
+
return {
|
|
367
|
+
analyzeFile: shared.analyzeFile,
|
|
368
|
+
analyzeBuffer: shared.analyzeBuffer,
|
|
369
|
+
createAnalyzer: createAnalyzer2
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/v2/node/commonjs.js
|
|
374
|
+
var { analyzeFile, analyzeBuffer, createAnalyzer } = createAPI(
|
|
375
|
+
(0, import_node_url2.pathToFileURL)((0, import_node_path2.join)(__dirname, "analysis-worker.js"))
|
|
376
|
+
);
|
|
377
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
378
|
+
0 && (module.exports = {
|
|
379
|
+
analyzeBuffer,
|
|
380
|
+
analyzeFile,
|
|
381
|
+
createAnalyzer
|
|
382
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Physical scale for unitless STL/OBJ files. 3MF/STEP/IGES honor file units. */
|
|
2
|
+
export type AnalysisUnits = "mm" | "cm" | "m" | "in" | "ft" | "um";
|
|
3
|
+
export type AnalysisFormat =
|
|
4
|
+
"stl" | "obj" | "3mf" | "step" | "stp" | "iges" | "igs";
|
|
5
|
+
export interface AnalysisLimits {
|
|
6
|
+
max_file_bytes?: number;
|
|
7
|
+
max_triangles?: number;
|
|
8
|
+
max_uncompressed_bytes?: number;
|
|
9
|
+
max_archive_entries?: number;
|
|
10
|
+
/** Estimated working-set admission budget, not a process RSS cap. */
|
|
11
|
+
max_memory_bytes?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AnalysisProfile {
|
|
14
|
+
technology: "fdm";
|
|
15
|
+
min_wall_mm?: number;
|
|
16
|
+
line_width_mm?: number;
|
|
17
|
+
overhang_degrees?: number;
|
|
18
|
+
build_volume_mm?: [number, number, number];
|
|
19
|
+
}
|
|
20
|
+
export interface AnalysisProgress {
|
|
21
|
+
phase: string;
|
|
22
|
+
elapsed_ms: number;
|
|
23
|
+
loaded?: number;
|
|
24
|
+
total?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface AnalysisOptions {
|
|
27
|
+
format?: AnalysisFormat;
|
|
28
|
+
units?: AnalysisUnits;
|
|
29
|
+
/** Defaults to detailed. Basic reports omit intersection/validated-volume checks. */
|
|
30
|
+
level?: "basic" | "detailed";
|
|
31
|
+
profile?: AnalysisProfile | null;
|
|
32
|
+
/** Whole job deadline, including queueing and file parsing; default 120000. */
|
|
33
|
+
timeout_ms?: number;
|
|
34
|
+
/** Mesh-check deadline, at most 120000; default 30000. May return a partial report. */
|
|
35
|
+
max_time_ms?: number;
|
|
36
|
+
limits?: AnalysisLimits;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
on_progress?: (progress: AnalysisProgress) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface BufferAnalysisOptions extends AnalysisOptions {
|
|
41
|
+
/** Supply filename or format for Buffer/Uint8Array/ArrayBuffer input. */
|
|
42
|
+
filename?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface AnalysisCheck {
|
|
45
|
+
code: string;
|
|
46
|
+
label: string;
|
|
47
|
+
severity: "error" | "warning" | "info";
|
|
48
|
+
status: "pass" | "issue" | "not_run" | "inconclusive";
|
|
49
|
+
count: number;
|
|
50
|
+
triangle_ids: number[];
|
|
51
|
+
edge_vertices: number[];
|
|
52
|
+
message: string;
|
|
53
|
+
estimated?: boolean;
|
|
54
|
+
area_mm2?: number;
|
|
55
|
+
samples?: number;
|
|
56
|
+
minimum_sampled_mm?: number | null;
|
|
57
|
+
unresolved_samples?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface AnalysisReport {
|
|
60
|
+
schema_version: 1;
|
|
61
|
+
model_id: number;
|
|
62
|
+
name: string;
|
|
63
|
+
filename: string;
|
|
64
|
+
format: "stl" | "obj" | "3mf" | "step" | "iges";
|
|
65
|
+
file_bytes: number;
|
|
66
|
+
triangles: number;
|
|
67
|
+
/** Known physical measurements are normalized to mm; null means unitless. */
|
|
68
|
+
units: "mm" | null;
|
|
69
|
+
units_source: "file" | "option" | null;
|
|
70
|
+
/** Measurements in mm when units are known, otherwise in input coordinate units. */
|
|
71
|
+
dimensions: [number, number, number] | null;
|
|
72
|
+
surface_area: number;
|
|
73
|
+
volume: number | null;
|
|
74
|
+
dimensions_mm: [number, number, number] | null;
|
|
75
|
+
surface_area_mm2: number | null;
|
|
76
|
+
volume_mm3: number | null;
|
|
77
|
+
/** Validated mesh volume; does not by itself confirm physical units or printability. */
|
|
78
|
+
volume_valid: boolean;
|
|
79
|
+
checks: AnalysisCheck[];
|
|
80
|
+
summary: "no_issues_found" | "issues_found" | "incomplete";
|
|
81
|
+
elapsed_ms: number;
|
|
82
|
+
tolerance_mm?: number;
|
|
83
|
+
limit_reason?: "time";
|
|
84
|
+
settings: {
|
|
85
|
+
level: "basic" | "detailed";
|
|
86
|
+
profile: AnalysisProfile | null;
|
|
87
|
+
apply_transforms: boolean;
|
|
88
|
+
units_confirmed: boolean;
|
|
89
|
+
max_time_ms: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface AnalyzerOptions {
|
|
93
|
+
/** Number of simultaneous disposable workers; default 2, range 1–8. */
|
|
94
|
+
concurrency?: number;
|
|
95
|
+
/** Number of waiting jobs; default 16. */
|
|
96
|
+
max_queue?: number;
|
|
97
|
+
/** Maximum copied buffer bytes waiting in the queue; default 128 MiB. */
|
|
98
|
+
max_queued_bytes?: number;
|
|
99
|
+
}
|
|
100
|
+
export interface Analyzer {
|
|
101
|
+
analyzeFile(
|
|
102
|
+
path: string | URL,
|
|
103
|
+
options?: AnalysisOptions,
|
|
104
|
+
): Promise<AnalysisReport>;
|
|
105
|
+
analyzeBuffer(
|
|
106
|
+
data: Uint8Array | ArrayBuffer,
|
|
107
|
+
options: BufferAnalysisOptions,
|
|
108
|
+
): Promise<AnalysisReport>;
|
|
109
|
+
/** Cancel queued/running jobs and wait for worker termination. */
|
|
110
|
+
dispose(): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
/** Analyze a local regular file. HTTP(S) URLs and network-share paths are rejected. */
|
|
113
|
+
export function analyzeFile(
|
|
114
|
+
path: string | URL,
|
|
115
|
+
options?: AnalysisOptions,
|
|
116
|
+
): Promise<AnalysisReport>;
|
|
117
|
+
/** The caller's data is copied and is never detached or modified. */
|
|
118
|
+
export function analyzeBuffer(
|
|
119
|
+
data: Uint8Array | ArrayBuffer,
|
|
120
|
+
options: BufferAnalysisOptions,
|
|
121
|
+
): Promise<AnalysisReport>;
|
|
122
|
+
export function createAnalyzer(options?: AnalyzerOptions): Analyzer;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Physical scale for unitless STL/OBJ files. 3MF/STEP/IGES honor file units. */
|
|
2
|
+
export type AnalysisUnits = "mm" | "cm" | "m" | "in" | "ft" | "um";
|
|
3
|
+
export type AnalysisFormat =
|
|
4
|
+
"stl" | "obj" | "3mf" | "step" | "stp" | "iges" | "igs";
|
|
5
|
+
export interface AnalysisLimits {
|
|
6
|
+
max_file_bytes?: number;
|
|
7
|
+
max_triangles?: number;
|
|
8
|
+
max_uncompressed_bytes?: number;
|
|
9
|
+
max_archive_entries?: number;
|
|
10
|
+
/** Estimated working-set admission budget, not a process RSS cap. */
|
|
11
|
+
max_memory_bytes?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AnalysisProfile {
|
|
14
|
+
technology: "fdm";
|
|
15
|
+
min_wall_mm?: number;
|
|
16
|
+
line_width_mm?: number;
|
|
17
|
+
overhang_degrees?: number;
|
|
18
|
+
build_volume_mm?: [number, number, number];
|
|
19
|
+
}
|
|
20
|
+
export interface AnalysisProgress {
|
|
21
|
+
phase: string;
|
|
22
|
+
elapsed_ms: number;
|
|
23
|
+
loaded?: number;
|
|
24
|
+
total?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface AnalysisOptions {
|
|
27
|
+
format?: AnalysisFormat;
|
|
28
|
+
units?: AnalysisUnits;
|
|
29
|
+
/** Defaults to detailed. Basic reports omit intersection/validated-volume checks. */
|
|
30
|
+
level?: "basic" | "detailed";
|
|
31
|
+
profile?: AnalysisProfile | null;
|
|
32
|
+
/** Whole job deadline, including queueing and file parsing; default 120000. */
|
|
33
|
+
timeout_ms?: number;
|
|
34
|
+
/** Mesh-check deadline, at most 120000; default 30000. May return a partial report. */
|
|
35
|
+
max_time_ms?: number;
|
|
36
|
+
limits?: AnalysisLimits;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
on_progress?: (progress: AnalysisProgress) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface BufferAnalysisOptions extends AnalysisOptions {
|
|
41
|
+
/** Supply filename or format for Buffer/Uint8Array/ArrayBuffer input. */
|
|
42
|
+
filename?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface AnalysisCheck {
|
|
45
|
+
code: string;
|
|
46
|
+
label: string;
|
|
47
|
+
severity: "error" | "warning" | "info";
|
|
48
|
+
status: "pass" | "issue" | "not_run" | "inconclusive";
|
|
49
|
+
count: number;
|
|
50
|
+
triangle_ids: number[];
|
|
51
|
+
edge_vertices: number[];
|
|
52
|
+
message: string;
|
|
53
|
+
estimated?: boolean;
|
|
54
|
+
area_mm2?: number;
|
|
55
|
+
samples?: number;
|
|
56
|
+
minimum_sampled_mm?: number | null;
|
|
57
|
+
unresolved_samples?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface AnalysisReport {
|
|
60
|
+
schema_version: 1;
|
|
61
|
+
model_id: number;
|
|
62
|
+
name: string;
|
|
63
|
+
filename: string;
|
|
64
|
+
format: "stl" | "obj" | "3mf" | "step" | "iges";
|
|
65
|
+
file_bytes: number;
|
|
66
|
+
triangles: number;
|
|
67
|
+
/** Known physical measurements are normalized to mm; null means unitless. */
|
|
68
|
+
units: "mm" | null;
|
|
69
|
+
units_source: "file" | "option" | null;
|
|
70
|
+
/** Measurements in mm when units are known, otherwise in input coordinate units. */
|
|
71
|
+
dimensions: [number, number, number] | null;
|
|
72
|
+
surface_area: number;
|
|
73
|
+
volume: number | null;
|
|
74
|
+
dimensions_mm: [number, number, number] | null;
|
|
75
|
+
surface_area_mm2: number | null;
|
|
76
|
+
volume_mm3: number | null;
|
|
77
|
+
/** Validated mesh volume; does not by itself confirm physical units or printability. */
|
|
78
|
+
volume_valid: boolean;
|
|
79
|
+
checks: AnalysisCheck[];
|
|
80
|
+
summary: "no_issues_found" | "issues_found" | "incomplete";
|
|
81
|
+
elapsed_ms: number;
|
|
82
|
+
tolerance_mm?: number;
|
|
83
|
+
limit_reason?: "time";
|
|
84
|
+
settings: {
|
|
85
|
+
level: "basic" | "detailed";
|
|
86
|
+
profile: AnalysisProfile | null;
|
|
87
|
+
apply_transforms: boolean;
|
|
88
|
+
units_confirmed: boolean;
|
|
89
|
+
max_time_ms: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface AnalyzerOptions {
|
|
93
|
+
/** Number of simultaneous disposable workers; default 2, range 1–8. */
|
|
94
|
+
concurrency?: number;
|
|
95
|
+
/** Number of waiting jobs; default 16. */
|
|
96
|
+
max_queue?: number;
|
|
97
|
+
/** Maximum copied buffer bytes waiting in the queue; default 128 MiB. */
|
|
98
|
+
max_queued_bytes?: number;
|
|
99
|
+
}
|
|
100
|
+
export interface Analyzer {
|
|
101
|
+
analyzeFile(
|
|
102
|
+
path: string | URL,
|
|
103
|
+
options?: AnalysisOptions,
|
|
104
|
+
): Promise<AnalysisReport>;
|
|
105
|
+
analyzeBuffer(
|
|
106
|
+
data: Uint8Array | ArrayBuffer,
|
|
107
|
+
options: BufferAnalysisOptions,
|
|
108
|
+
): Promise<AnalysisReport>;
|
|
109
|
+
/** Cancel queued/running jobs and wait for worker termination. */
|
|
110
|
+
dispose(): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
/** Analyze a local regular file. HTTP(S) URLs and network-share paths are rejected. */
|
|
113
|
+
export function analyzeFile(
|
|
114
|
+
path: string | URL,
|
|
115
|
+
options?: AnalysisOptions,
|
|
116
|
+
): Promise<AnalysisReport>;
|
|
117
|
+
/** The caller's data is copied and is never detached or modified. */
|
|
118
|
+
export function analyzeBuffer(
|
|
119
|
+
data: Uint8Array | ArrayBuffer,
|
|
120
|
+
options: BufferAnalysisOptions,
|
|
121
|
+
): Promise<AnalysisReport>;
|
|
122
|
+
export function createAnalyzer(options?: AnalyzerOptions): Analyzer;
|