chore: add webapp node_modules dependencies.
This commit is contained in:
190
webapp/src/logic/useNametagConverter.js
Normal file
190
webapp/src/logic/useNametagConverter.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Module from 'manifold-3d';
|
||||
import { parseSVG } from 'svg-path-parser';
|
||||
import * as THREE from 'three';
|
||||
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter';
|
||||
|
||||
// Initialize Manifold WASM once
|
||||
let wasmModule;
|
||||
|
||||
export const useManifold = () => {
|
||||
const [manifold, setManifold] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
if (!wasmModule) {
|
||||
// Fix: Explicitly tell Manifold where to find the WASM file
|
||||
// distinct from the bundle path
|
||||
wasmModule = await Module({
|
||||
locateFile: (path) => {
|
||||
if (path.endsWith('.wasm')) {
|
||||
return '/manifold.wasm';
|
||||
}
|
||||
return path;
|
||||
}
|
||||
});
|
||||
wasmModule.setup();
|
||||
}
|
||||
setManifold(wasmModule);
|
||||
};
|
||||
init();
|
||||
}, []);
|
||||
|
||||
return manifold;
|
||||
};
|
||||
|
||||
// --- Geometry Helpers ---
|
||||
|
||||
const getPathPoints = (d, scale = 1.0) => {
|
||||
// 1. Parse SVG path data using browser native API
|
||||
const pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
pathEl.setAttribute("d", d);
|
||||
|
||||
const len = pathEl.getTotalLength();
|
||||
if (len < 0.1) return [];
|
||||
|
||||
const resolution = 0.5; // mm approx
|
||||
const numPoints = Math.max(10, Math.ceil(len / resolution));
|
||||
|
||||
const loops = [];
|
||||
let currentLoop = [];
|
||||
let lastP = null;
|
||||
|
||||
for (let i = 0; i <= numPoints; i++) {
|
||||
const p = pathEl.getPointAtLength((i / numPoints) * len);
|
||||
|
||||
if (lastP) {
|
||||
const dist = Math.hypot(p.x - lastP.x, p.y - lastP.y);
|
||||
// Detect "Move" command jumps which signify new subpaths
|
||||
if (dist > 5.0) {
|
||||
if (currentLoop.length > 2) loops.push(currentLoop);
|
||||
currentLoop = [];
|
||||
}
|
||||
}
|
||||
currentLoop.push([p.x * scale, -p.y * scale]);
|
||||
lastP = p;
|
||||
}
|
||||
if (currentLoop.length > 2) loops.push(currentLoop);
|
||||
|
||||
return loops;
|
||||
};
|
||||
|
||||
export const convertFile = async (file, manifold, addLog) => {
|
||||
if (!manifold) return null;
|
||||
// CrossSection is a top-level export, NOT a static property of Manifold class
|
||||
const { Manifold, CrossSection } = manifold;
|
||||
|
||||
const text = await file.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, "image/svg+xml");
|
||||
|
||||
const paths = {
|
||||
black: [],
|
||||
white: [],
|
||||
cyan: []
|
||||
};
|
||||
|
||||
// 1. Scale Calculation
|
||||
let minX = Infinity, maxX = -Infinity;
|
||||
const backgroundNodes = [];
|
||||
|
||||
const allPathNodes = Array.from(doc.querySelectorAll('path'));
|
||||
|
||||
allPathNodes.forEach(p => {
|
||||
const cls = p.getAttribute('class');
|
||||
if (!cls) backgroundNodes.push(p);
|
||||
});
|
||||
|
||||
backgroundNodes.forEach(p => {
|
||||
const d = p.getAttribute('d');
|
||||
if (!d) return;
|
||||
const pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
pathEl.setAttribute("d", d);
|
||||
|
||||
const len = pathEl.getTotalLength();
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
const pt = pathEl.getPointAtLength(i / 10 * len);
|
||||
minX = Math.min(minX, pt.x);
|
||||
maxX = Math.max(maxX, pt.x);
|
||||
}
|
||||
});
|
||||
|
||||
let scale = 1.0;
|
||||
const TARGET_WIDTH = 87.80;
|
||||
if (minX !== Infinity && maxX !== -Infinity) {
|
||||
const currentWidth = maxX - minX;
|
||||
if (currentWidth > 0) {
|
||||
scale = TARGET_WIDTH / currentWidth;
|
||||
addLog(` Scale factor: ${scale.toFixed(4)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Process Paths with Classification
|
||||
allPathNodes.forEach(p => {
|
||||
const cls = p.getAttribute('class');
|
||||
const d = p.getAttribute('d');
|
||||
if (!d) return;
|
||||
|
||||
const loops = getPathPoints(d, scale);
|
||||
|
||||
if (cls === 'st2') paths.white.push(loops);
|
||||
else if (cls === 'st1') paths.cyan.push(loops);
|
||||
else paths.black.push(loops);
|
||||
});
|
||||
|
||||
// 3. Extrusion Helper using Manifold
|
||||
const createMeshBlob = (loopsList, height, z) => {
|
||||
const allContours = loopsList.flat();
|
||||
|
||||
if (allContours.length === 0) return null;
|
||||
|
||||
// Create CrossSection with Even-Odd rule
|
||||
// Correct usage: new CrossSection(contours, fillRule)
|
||||
const cs = new CrossSection(allContours, 'EvenOdd');
|
||||
|
||||
// Extrude
|
||||
const mesh = Manifold.extrude(cs, height, 0, 0, [1, 1]);
|
||||
|
||||
// Translate Z
|
||||
const meshZ = mesh.translate(0, 0, z);
|
||||
|
||||
// Convert to Mesh for Three.js export
|
||||
const meshGL = new THREE.Mesh(getBufferGeometryFromManifold(meshZ, THREE));
|
||||
const exporter = new STLExporter();
|
||||
const stlString = exporter.parse(meshGL, { binary: true });
|
||||
return new Blob([stlString], { type: 'application/octet-stream' });
|
||||
};
|
||||
|
||||
// Constants
|
||||
const BG_THICK = 3.0;
|
||||
const TXT_THICK = 2.0;
|
||||
const TXT_Z = 4.0; // Raised by 4mm
|
||||
|
||||
return {
|
||||
black: createMeshBlob(paths.black, BG_THICK, 0),
|
||||
white: createMeshBlob(paths.white, TXT_THICK, TXT_Z),
|
||||
cyan: createMeshBlob(paths.cyan, TXT_THICK, TXT_Z)
|
||||
};
|
||||
};
|
||||
|
||||
function getBufferGeometryFromManifold(manifoldMesh, THREE) {
|
||||
const mesh = manifoldMesh.getMesh();
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
|
||||
const numVerts = mesh.vertProperties.length / mesh.numProp;
|
||||
const vertices = new Float32Array(numVerts * 3);
|
||||
|
||||
for (let i = 0; i < numVerts; i++) {
|
||||
vertices[i * 3] = mesh.vertProperties[i * mesh.numProp];
|
||||
vertices[i * 3 + 1] = mesh.vertProperties[i * mesh.numProp + 1];
|
||||
vertices[i * 3 + 2] = mesh.vertProperties[i * mesh.numProp + 2];
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||
|
||||
const indices = new Uint32Array(mesh.triVerts);
|
||||
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
||||
|
||||
geometry.computeVertexNormals();
|
||||
return geometry;
|
||||
}
|
||||
Reference in New Issue
Block a user