meshfix-wasm 0.1.0

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.
@@ -0,0 +1,20 @@
1
+ declare function importScripts(...urls: string[]): void;
2
+ declare function createMeshFixCore(opts?: Record<string, unknown>): Promise<any>;
3
+ interface WorkerRequest {
4
+ id: number;
5
+ method: string;
6
+ params: Record<string, unknown>;
7
+ buffer?: ArrayBuffer;
8
+ }
9
+ interface WorkerResponse {
10
+ id: number;
11
+ result?: unknown;
12
+ error?: {
13
+ message: string;
14
+ };
15
+ }
16
+ declare var module: any;
17
+ declare var analyzer: any;
18
+ declare function initModule(coreUrl?: string, wasmUrl?: string): Promise<void>;
19
+ declare function postProgress(requestId: number, step: string, stepIndex: number, totalSteps: number): void;
20
+ declare function handleMessage(method: string, params: Record<string, unknown>, buffer?: ArrayBuffer, requestId?: number): unknown;
package/dist/worker.js ADDED
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ // Classic Web Worker script — NO import/export statements.
3
+ // TypeScript treats files without imports/exports as scripts,
4
+ // so tsc emits no module syntax → valid for importScripts() workers.
5
+ var module = null;
6
+ var analyzer = null;
7
+ async function initModule(coreUrl, wasmUrl) {
8
+ importScripts(coreUrl || "meshfix-core.js");
9
+ const opts = {};
10
+ if (wasmUrl) {
11
+ opts.locateFile = (path) => {
12
+ if (path.endsWith(".wasm"))
13
+ return wasmUrl;
14
+ return path;
15
+ };
16
+ }
17
+ module = await createMeshFixCore(opts);
18
+ analyzer = new module.MeshAnalyzer();
19
+ }
20
+ function postProgress(requestId, step, stepIndex, totalSteps) {
21
+ self.postMessage({ type: "progress", requestId, step, stepIndex, totalSteps });
22
+ }
23
+ function handleMessage(method, params, buffer, requestId) {
24
+ switch (method) {
25
+ case "init":
26
+ // handled separately (async)
27
+ return null;
28
+ case "analyze": {
29
+ const uint8 = new Uint8Array(buffer);
30
+ const path = "/tmp/input.stl";
31
+ try {
32
+ module.FS.writeFile(path, uint8);
33
+ const ok = analyzer.loadFromFile(path);
34
+ if (!ok)
35
+ throw new Error(analyzer.getLastError() || "Failed to load mesh");
36
+ return analyzer.getStats();
37
+ }
38
+ finally {
39
+ try {
40
+ module.FS.unlink(path);
41
+ }
42
+ catch { }
43
+ }
44
+ }
45
+ case "analyzeDetailed": {
46
+ const uint8 = new Uint8Array(buffer);
47
+ const path = "/tmp/input.stl";
48
+ try {
49
+ module.FS.writeFile(path, uint8);
50
+ const ok = analyzer.loadFromFile(path);
51
+ if (!ok)
52
+ throw new Error(analyzer.getLastError() || "Failed to load mesh");
53
+ return analyzer.getAnalysis();
54
+ }
55
+ finally {
56
+ try {
57
+ module.FS.unlink(path);
58
+ }
59
+ catch { }
60
+ }
61
+ }
62
+ case "analyzeTestShape": {
63
+ const name = params.name;
64
+ const ok = analyzer.loadTestShape(name);
65
+ if (!ok)
66
+ throw new Error(analyzer.getLastError() || "Failed to create test shape");
67
+ return analyzer.getStats();
68
+ }
69
+ case "analyzeTestShapeDetailed": {
70
+ const name = params.name;
71
+ const ok = analyzer.loadTestShape(name);
72
+ if (!ok)
73
+ throw new Error(analyzer.getLastError() || "Failed to create test shape");
74
+ return analyzer.getAnalysis();
75
+ }
76
+ case "weldVertices": {
77
+ if (!analyzer.isLoaded())
78
+ throw new Error("No mesh loaded");
79
+ const epsilon = params.epsilon ?? 1e-6;
80
+ return analyzer.weldVertices(epsilon);
81
+ }
82
+ case "removeDegenerates": {
83
+ if (!analyzer.isLoaded())
84
+ throw new Error("No mesh loaded");
85
+ const minArea = params.minArea ?? 1e-10;
86
+ return analyzer.removeDegenerates(minArea);
87
+ }
88
+ case "fixNormals": {
89
+ if (!analyzer.isLoaded())
90
+ throw new Error("No mesh loaded");
91
+ return analyzer.fixNormals();
92
+ }
93
+ case "fillHoles": {
94
+ if (!analyzer.isLoaded())
95
+ throw new Error("No mesh loaded");
96
+ const maxEdges = params.maxEdges ?? 100;
97
+ return analyzer.fillHoles(maxEdges);
98
+ }
99
+ case "splitVertices": {
100
+ if (!analyzer.isLoaded())
101
+ throw new Error("No mesh loaded");
102
+ return analyzer.splitVertices();
103
+ }
104
+ case "repair": {
105
+ if (!analyzer.isLoaded())
106
+ throw new Error("No mesh loaded");
107
+ const weldEpsilon = params.weldEpsilon ?? 1e-6;
108
+ const minArea = params.minArea ?? 1e-10;
109
+ const maxHoleEdges = params.maxHoleEdges ?? 100;
110
+ const id = requestId;
111
+ const totalSteps = 5;
112
+ const verticesBefore = analyzer.getVertexCount();
113
+ const facesBefore = analyzer.getFaceCount();
114
+ postProgress(id, "weld", 0, totalSteps);
115
+ const weld = analyzer.weldVertices(weldEpsilon);
116
+ // Only remove degenerates if mesh is not watertight — removing
117
+ // faces from a closed surface tears holes that may not fill cleanly.
118
+ var midAnalysis = analyzer.getAnalysis();
119
+ postProgress(id, "removeDegenerates", 1, totalSteps);
120
+ var removeDegenerates = null;
121
+ if (!midAnalysis.isWatertight) {
122
+ removeDegenerates = analyzer.removeDegenerates(minArea);
123
+ }
124
+ postProgress(id, "splitVertices", 2, totalSteps);
125
+ const splitVertices = analyzer.splitVertices();
126
+ postProgress(id, "fillHoles", 3, totalSteps);
127
+ const fillHoles = analyzer.fillHoles(maxHoleEdges);
128
+ postProgress(id, "fixNormals", 4, totalSteps);
129
+ const fixNormals = analyzer.fixNormals();
130
+ return {
131
+ weld,
132
+ removeDegenerates,
133
+ splitVertices,
134
+ fillHoles,
135
+ fixNormals,
136
+ verticesBefore,
137
+ verticesAfter: analyzer.getVertexCount(),
138
+ facesBefore,
139
+ facesAfter: analyzer.getFaceCount(),
140
+ };
141
+ }
142
+ case "reanalyze": {
143
+ if (!analyzer.isLoaded())
144
+ throw new Error("No mesh loaded");
145
+ return analyzer.getAnalysis();
146
+ }
147
+ case "toRenderData": {
148
+ if (!analyzer.isLoaded())
149
+ throw new Error("No mesh loaded");
150
+ var renderPath = "/tmp/render.bin";
151
+ try {
152
+ var ok = analyzer.writeRenderData(renderPath);
153
+ if (!ok)
154
+ throw new Error(analyzer.getLastError() || "Failed to write render data");
155
+ var data = module.FS.readFile(renderPath);
156
+ var buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
157
+ return { buffer: buf };
158
+ }
159
+ finally {
160
+ try {
161
+ module.FS.unlink(renderPath);
162
+ }
163
+ catch { }
164
+ }
165
+ }
166
+ case "exportMesh": {
167
+ if (!analyzer.isLoaded())
168
+ throw new Error("No mesh loaded");
169
+ var format = params.format || "stl";
170
+ var exportPath = "/tmp/export." + format;
171
+ try {
172
+ var ok = analyzer.exportMesh(exportPath);
173
+ if (!ok)
174
+ throw new Error(analyzer.getLastError() || "Failed to export mesh");
175
+ var data = module.FS.readFile(exportPath);
176
+ var buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
177
+ return { buffer: buf };
178
+ }
179
+ finally {
180
+ try {
181
+ module.FS.unlink(exportPath);
182
+ }
183
+ catch { }
184
+ }
185
+ }
186
+ case "dispose": {
187
+ if (analyzer) {
188
+ analyzer.delete();
189
+ analyzer = null;
190
+ }
191
+ module = null;
192
+ return null;
193
+ }
194
+ default:
195
+ throw new Error("Unknown method: " + method);
196
+ }
197
+ }
198
+ self.onmessage = async function (event) {
199
+ const { id, method, params, buffer } = event.data;
200
+ try {
201
+ if (method === "init") {
202
+ await initModule(params.coreUrl, params.wasmUrl);
203
+ self.postMessage({ id, result: true });
204
+ }
205
+ else {
206
+ const result = handleMessage(method, params, buffer, id);
207
+ // Transfer ArrayBuffer back for exportMesh and toRenderData
208
+ if ((method === "exportMesh" || method === "toRenderData") && result && result.buffer instanceof ArrayBuffer) {
209
+ self.postMessage({ id, result }, [result.buffer]);
210
+ }
211
+ else {
212
+ self.postMessage({ id, result });
213
+ }
214
+ }
215
+ }
216
+ catch (e) {
217
+ const message = e && e.message ? e.message : String(e);
218
+ self.postMessage({ id, error: { message } });
219
+ }
220
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "meshfix-wasm",
3
+ "version": "0.1.0",
4
+ "description": "Client-side 3D mesh repair library using WASM",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/"
16
+ ],
17
+ "scripts": {
18
+ "install-emsdk": "bash scripts/install-emsdk.sh",
19
+ "build:wasm": "bash scripts/build-wasm.sh",
20
+ "build:ts": "tsc",
21
+ "build": "npm run build:wasm && npm run build:ts",
22
+ "prepublishOnly": "npm run build",
23
+ "serve": "npx http-server . -p 8080 -c-1"
24
+ },
25
+ "keywords": [
26
+ "wasm",
27
+ "webassembly",
28
+ "mesh",
29
+ "3d-printing",
30
+ "stl",
31
+ "obj",
32
+ "repair",
33
+ "geometry",
34
+ "pmp"
35
+ ],
36
+ "author": "Anthony Greco <anthony.greco@gmail.com> (https://justfixstl.com)",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/anthonygreco/meshfix-wasm.git"
41
+ },
42
+ "homepage": "https://github.com/anthonygreco/meshfix-wasm",
43
+ "bugs": {
44
+ "url": "https://github.com/anthonygreco/meshfix-wasm/issues"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.3.0",
48
+ "http-server": "^14.1.1"
49
+ }
50
+ }