From 369df932041ddcdd32dc3c9eb946171b419c6cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Mon, 4 Mar 2024 00:02:01 +0100 Subject: [PATCH] web worker --- package.json | 1 + pnpm-lock.yaml | 53 +++--- src/lib/components/App.svelte | 60 ++++++- src/lib/components/Scene.svelte | 305 +++++++------------------------- src/lib/slicer/generator.ts | 0 src/lib/slicer/worker-data.ts | 39 ++++ src/lib/slicer/worker.ts | 253 ++++++++++++++++++++++++++ src/lib/style/global.scss | 5 + src/routes/+layout.svelte | 5 + 9 files changed, 446 insertions(+), 275 deletions(-) create mode 100644 src/lib/slicer/generator.ts create mode 100644 src/lib/slicer/worker-data.ts create mode 100644 src/lib/slicer/worker.ts create mode 100644 src/lib/style/global.scss create mode 100644 src/routes/+layout.svelte diff --git a/package.json b/package.json index b72ea77..ee805b3 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@dimforge/rapier3d": "^0.12.0", "@threlte/core": "^7.1.2", "@threlte/extras": "^8.8.1", + "sass": "^1.71.1", "three": "^0.159.0", "three-mesh-bvh": "^0.7.3", "vite-plugin-wasm": "^3.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffab0f5..c1b911b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@threlte/extras': specifier: ^8.8.1 version: 8.8.1(svelte@4.2.12)(three@0.159.0) + sass: + specifier: ^1.71.1 + version: 1.71.1 three: specifier: ^0.159.0 version: 0.159.0 @@ -48,7 +51,7 @@ devDependencies: version: 4.2.12 svelte-check: specifier: ^3.6.0 - version: 3.6.6(svelte@4.2.12) + version: 3.6.6(sass@1.71.1)(svelte@4.2.12) tslib: specifier: ^2.4.1 version: 2.6.2 @@ -57,7 +60,7 @@ devDependencies: version: 5.3.3 vite: specifier: ^5.0.3 - version: 5.1.4 + version: 5.1.4(sass@1.71.1) packages: @@ -430,7 +433,7 @@ packages: sirv: 2.0.4 svelte: 4.2.12 tiny-glob: 0.2.9 - vite: 5.1.4 + vite: 5.1.4(sass@1.71.1) dev: true /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.4): @@ -444,7 +447,7 @@ packages: '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.4) debug: 4.3.4 svelte: 4.2.12 - vite: 5.1.4 + vite: 5.1.4(sass@1.71.1) transitivePeerDependencies: - supports-color dev: true @@ -463,7 +466,7 @@ packages: magic-string: 0.30.7 svelte: 4.2.12 svelte-hmr: 0.15.3(svelte@4.2.12) - vite: 5.1.4 + vite: 5.1.4(sass@1.71.1) vitefu: 0.2.5(vite@5.1.4) transitivePeerDependencies: - supports-color @@ -532,7 +535,6 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -557,7 +559,6 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: true /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -571,7 +572,6 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -595,7 +595,6 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 - dev: true /code-red@1.0.4: resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} @@ -721,7 +720,6 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -739,7 +737,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - dev: true /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -764,6 +761,9 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true + /immutable@4.3.5: + resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -792,24 +792,20 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: true /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} @@ -898,7 +894,6 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -931,7 +926,6 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true /postcss@8.4.35: resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} @@ -966,7 +960,6 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -1034,6 +1027,15 @@ packages: rimraf: 2.7.1 dev: true + /sass@1.71.1: + resolution: {integrity: sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + chokidar: 3.6.0 + immutable: 4.3.5 + source-map-js: 1.0.2 + /set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} dev: true @@ -1068,7 +1070,7 @@ packages: min-indent: 1.0.1 dev: true - /svelte-check@3.6.6(svelte@4.2.12): + /svelte-check@3.6.6(sass@1.71.1)(svelte@4.2.12): resolution: {integrity: sha512-b9q9rOHOMYF3U8XllK7LmXTq1LeWQ98waGfEJzrFutViadkNl1tgdEtxIQ8yuPx+VQ4l7YrknYol+0lfZocaZw==} hasBin: true peerDependencies: @@ -1081,7 +1083,7 @@ packages: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.2.12 - svelte-preprocess: 5.1.3(svelte@4.2.12)(typescript@5.3.3) + svelte-preprocess: 5.1.3(sass@1.71.1)(svelte@4.2.12)(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: - '@babel/core' @@ -1104,7 +1106,7 @@ packages: svelte: 4.2.12 dev: true - /svelte-preprocess@5.1.3(svelte@4.2.12)(typescript@5.3.3): + /svelte-preprocess@5.1.3(sass@1.71.1)(svelte@4.2.12)(typescript@5.3.3): resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} engines: {node: '>= 16.0.0', pnpm: ^8.0.0} requiresBuild: true @@ -1145,6 +1147,7 @@ packages: '@types/pug': 2.0.10 detect-indent: 6.1.0 magic-string: 0.30.7 + sass: 1.71.1 sorcery: 0.11.0 strip-indent: 3.0.0 svelte: 4.2.12 @@ -1204,7 +1207,6 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} @@ -1278,10 +1280,10 @@ packages: peerDependencies: vite: ^2 || ^3 || ^4 || ^5 dependencies: - vite: 5.1.4 + vite: 5.1.4(sass@1.71.1) dev: false - /vite@5.1.4: + /vite@5.1.4(sass@1.71.1): resolution: {integrity: sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1312,6 +1314,7 @@ packages: esbuild: 0.19.12 postcss: 8.4.35 rollup: 4.12.0 + sass: 1.71.1 optionalDependencies: fsevents: 2.3.3 @@ -1323,7 +1326,7 @@ packages: vite: optional: true dependencies: - vite: 5.1.4 + vite: 5.1.4(sass@1.71.1) dev: true /webgl-sdf-generator@1.1.1: diff --git a/src/lib/components/App.svelte b/src/lib/components/App.svelte index ca42b98..5a33c8d 100644 --- a/src/lib/components/App.svelte +++ b/src/lib/components/App.svelte @@ -1,10 +1,12 @@ @@ -12,19 +14,65 @@
- - +
- diff --git a/src/lib/components/Scene.svelte b/src/lib/components/Scene.svelte index 3aab16f..de9301e 100644 --- a/src/lib/components/Scene.svelte +++ b/src/lib/components/Scene.svelte @@ -1,7 +1,7 @@ @@ -282,9 +99,9 @@ gridSize={[buildSurface[0], buildSurface[1]]} /> -{#each layers as { geometry, type }, i} - {@const visible = showSlices >= i / layers.length} - {@const color = new Color(0, i / layers.length, 0.2)} +{#each $layers as { geometry, type }, i} + {@const visible = showSlices >= i / $layers.length} + {@const color = new Color(0, i / $layers.length, 0.2)} {#if type === LayerType.Line} diff --git a/src/lib/slicer/generator.ts b/src/lib/slicer/generator.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/slicer/worker-data.ts b/src/lib/slicer/worker-data.ts new file mode 100644 index 0000000..96fd438 --- /dev/null +++ b/src/lib/slicer/worker-data.ts @@ -0,0 +1,39 @@ +import type { Vector3Tuple } from 'three'; + +export interface SliceArguments { + stl: object; + bedNormal: Vector3Tuple; + maxNonPlanarAngle: number; + tolerance: number; + layerHeight: number; +} + +export interface SliceEvent { + type: 'slice'; + data: SliceArguments; +} + +export type WorkerEvent = SliceEvent; + +export type WorkerMessage = LayerMessage | ProgressMessage; + +export interface LayerMessage { + type: 'layer'; + data: Layer; +} + +export interface ProgressMessage { + type: 'progress'; + percent?: number; + layer: number; +} + +export interface Layer { + type: LayerType; + geometry: object; +} + +export const enum LayerType { + Line, + Surface +} diff --git a/src/lib/slicer/worker.ts b/src/lib/slicer/worker.ts new file mode 100644 index 0000000..fcde595 --- /dev/null +++ b/src/lib/slicer/worker.ts @@ -0,0 +1,253 @@ +import { + BufferGeometry, + BufferGeometryLoader, + Float32BufferAttribute, + Line3, + Matrix4, + Plane, + Vector3 +} from 'three'; +import { ExtendedTriangle, MeshBVH, type HitPointInfo } from 'three-mesh-bvh'; +import { + LayerType, + type LayerMessage, + type SliceArguments, + type ProgressMessage, + type WorkerEvent +} from './worker-data'; + +addEventListener('message', (event: MessageEvent) => { + if (event.data.type === 'slice') { + console.log(event.data.data); + slice(event.data.data); + } +}); + +function slice({ + stl, + bedNormal: bedNormalArray, + maxNonPlanarAngle, + tolerance, + layerHeight +}: SliceArguments) { + self.postMessage({ type: 'progress', percent: 0, layer: 0 } satisfies ProgressMessage); + const bedNormal = new Vector3(...bedNormalArray); + const geometry = new BufferGeometryLoader().parse(stl); + const bvh = new MeshBVH(geometry); + const positions = geometry.getAttribute('position'); + const normals = geometry.getAttribute('normal'); + const index = geometry.index!; + + const qualifyingTriangles = Array.from({ length: index.count / 3 }, () => false); + let qualifyingTrianglesCount = 0; + const triangle = new ExtendedTriangle(); + const normal = new Vector3(); + for (let i = 0; i < index.count / 3; i++) { + triangle.setFromAttributeAndIndices( + positions, + index.array[i * 3], + index.array[i * 3 + 1], + index.array[i * 3 + 2] + ); + triangle.getNormal(normal); + const angle = normal.angleTo(bedNormal); + // TODO: bottom layers + if (angle < maxNonPlanarAngle) { + qualifyingTriangles[i] = true; + qualifyingTrianglesCount++; + } + } + const includedTriangles = [...qualifyingTriangles]; + const includedTrianglesCount = qualifyingTrianglesCount; + + const surfaces: number[][] = []; + while (qualifyingTrianglesCount > 0) { + const faceIndex = qualifyingTriangles.findIndex((it) => it); + qualifyingTriangles[faceIndex] = false; + qualifyingTrianglesCount--; + const surface = [faceIndex]; + let cursor = 0; + while (cursor < surface.length) { + triangle.setFromAttributeAndIndices( + positions, + index.array[surface[cursor] * 3], + index.array[surface[cursor] * 3 + 1], + index.array[surface[cursor] * 3 + 2] + ); + + bvh.shapecast({ + intersectsBounds(box, _isLeaf, _score, _depth, _nodeIndex) { + return triangle.intersectsBox(box); + }, + intersectsTriangle(target, triangleIndex, _contained, _depth) { + if ( + qualifyingTriangles[triangleIndex] && + target.distanceToTriangle(triangle) < tolerance + ) { + qualifyingTriangles[triangleIndex] = false; + qualifyingTrianglesCount--; + surface.push(triangleIndex); + } + } + }); + + cursor++; + } + surfaces.push(surface); + } + + const nonPlanarSurfaces = surfaces.map((surface) => { + const geometry = new BufferGeometry(); + geometry.setAttribute('position', positions); + geometry.setAttribute('normal', normals); + const indices: number[] = Array.from({ length: surface.length * 3 }); + for (let i = 0; i < surface.length; i++) { + const pos = surface[i] * 3; + indices[i * 3] = index.array[pos]; + indices[i * 3 + 1] = index.array[pos + 1]; + indices[i * 3 + 2] = index.array[pos + 2]; + } + geometry.setIndex(indices); + const bvh = new MeshBVH(geometry); + geometry.boundsTree = bvh; + return bvh; + }); + const activeNonPlanarSurfaces: [number, MeshBVH][] = []; + const consumedNonPlanarSurfaces = nonPlanarSurfaces.map(() => false); + const withheld: Array< + | { type: LayerType.Line; geometry: number[] } + | { type: LayerType.Surface; id: [number, MeshBVH] } + >[] = nonPlanarSurfaces.map(() => [{ type: LayerType.Line, geometry: [] }]); + const blacklist = Array.from({ length: index.count / 3 }).map(() => false); + + const line = new Line3(); + const targetVector1 = new Vector3(); + const targetVector2 = new Vector3(); + const targetVector3 = new Vector3(); + const hit1: HitPointInfo = { point: new Vector3(), distance: 0, faceIndex: 0 }; + const hit2: HitPointInfo = { point: new Vector3(), distance: 0, faceIndex: 0 }; + const layerPlane = new Plane(); + function deactivateSurface(surface: MeshBVH, index: number) { + self.postMessage({ + type: 'layer', + data: { type: LayerType.Surface, geometry: surface.geometry.toJSON() } + } satisfies LayerMessage); + for (const thing of withheld[index]) { + if (thing.type === LayerType.Line) { + if (thing.geometry.length === 0) continue; + const additionalGeometry = new BufferGeometry(); + additionalGeometry.setAttribute('position', new Float32BufferAttribute(thing.geometry, 3)); + self.postMessage({ + type: 'layer', + data: { type: LayerType.Line, geometry: additionalGeometry.toJSON() } + }); + } else if (thing.type === LayerType.Surface) { + deactivateSurface(thing.id[1], thing.id[0]); + } + } + delete withheld[index]; + } + for (let layer = 0; layer < geometry.boundingBox!.max.z; layer += layerHeight) { + layerPlane.set(bedNormal, -layer); + const layerGeometry = new BufferGeometry(); + const positions: number[] = []; + for (let i = 0; i < nonPlanarSurfaces.length; i++) { + if (consumedNonPlanarSurfaces[i]) continue; + if (nonPlanarSurfaces[i].geometry.boundingBox!.min.z > layer) { + consumedNonPlanarSurfaces[i] = true; + activeNonPlanarSurfaces.push([i, nonPlanarSurfaces[i]]); + } + } + deactivate: for (let i = 0; i < activeNonPlanarSurfaces.length; i++) { + const [index, surface] = activeNonPlanarSurfaces[i]; + if (surface.geometry.boundingBox!.max.z <= layer) { + activeNonPlanarSurfaces.splice(i, 1); + i--; + + for (const [activeIndex, active] of activeNonPlanarSurfaces) { + if (activeIndex === index) continue; + const hit = active.closestPointToGeometry(surface.geometry, new Matrix4(), hit1, hit2); + if ( + hit && + hit1.point.z < hit2.point.z && + hit1.point.clone().sub(hit2.point).angleTo(bedNormal) > maxNonPlanarAngle + ) { + withheld[activeIndex].push({ type: LayerType.Surface, id: [index, surface] }); + withheld[activeIndex].push({ type: LayerType.Line, geometry: [] }); + continue deactivate; + } + } + deactivateSurface(surface, index); + } + withheld[index]?.push({ type: LayerType.Line, geometry: [] }); + } + + bvh.shapecast({ + intersectsBounds(box, _isLeaf, _score, _depth, _nodeIndex) { + return layerPlane.intersectsBox(box); + }, + intersectsTriangle(target, triangleIndex, _contained, _depth) { + if (includedTriangles[triangleIndex] || blacklist[triangleIndex]) return; + function intersect(a: Vector3, b: Vector3, targetVector: Vector3) { + line.set(a, b); + return layerPlane.intersectLine(line, targetVector); + } + const a = intersect(target.a, target.b, targetVector1); + const b = intersect(target.b, target.c, targetVector2); + const c = intersect(target.c, target.a, targetVector3); + + function add(a: Vector3, b: Vector3) { + for (let i = 0; i < activeNonPlanarSurfaces.length; i++) { + const [index, surface] = activeNonPlanarSurfaces[i]; + const withheldLayer = withheld[index].at(-1)!; + if (withheldLayer.type === LayerType.Surface) throw new Error('Unexpected surface'); + const h1 = surface.closestPointToPoint(a); + if ( + h1 && + h1.point.z < a.z && + h1.point.clone().sub(a).angleTo(bedNormal) > maxNonPlanarAngle + ) { + withheldLayer.geometry.push(a.x, a.y, a.z, b.x, b.y, b.z); + return; + } + const h2 = surface.closestPointToPoint(b); + if ( + h2 && + h2.point.z < b.z && + h2.point.clone().sub(b).angleTo(bedNormal) > maxNonPlanarAngle + ) { + withheldLayer.geometry.push(a.x, a.y, a.z, b.x, b.y, b.z); + return; + } + } + positions.push(a.x, a.y, a.z, b.x, b.y, b.z); + } + + if (a && b) { + add(a, b); + } else if (b && c) { + add(b, c); + } else if (c && a) { + add(c, a); + } + } + }); + layerGeometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); + self.postMessage({ + type: 'layer', + data: { type: LayerType.Line, geometry: layerGeometry.toJSON() } + } satisfies LayerMessage); + self.postMessage({ + type: 'progress', + percent: layer / geometry.boundingBox!.max.z, + layer: Math.round(layer / layerHeight) + } satisfies ProgressMessage); + } + for (const [index, surface] of activeNonPlanarSurfaces) { + deactivateSurface(surface, index); + } + self.postMessage({ + type: 'progress', + layer: Math.round(geometry.boundingBox!.max.z / layerHeight) + } satisfies ProgressMessage); +} diff --git a/src/lib/style/global.scss b/src/lib/style/global.scss new file mode 100644 index 0000000..1c2b583 --- /dev/null +++ b/src/lib/style/global.scss @@ -0,0 +1,5 @@ +* { + box-sizing: border-box; + appearance: none; + font-family: sans-serif; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..8a1303a --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,5 @@ + + +