3.5 KiB
Loaders, exporters and text/SVG shapes
All of these live under three/addons/, which the import map of template.html
already provides. Import from the addon file, NOT from the THREE namespace
(THREE.STLExporter is undefined):
import { STLExporter } from 'three/addons/exporters/STLExporter.js';
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
Export STL
STLExporter.parse(object3d) traverses object3d and exports every Mesh in
WORLD coordinates. Give it a throw-away Group of plain meshes - never the
scene (grid, lights, helpers would be included) and never Brush objects with
stale matrices:
function toSTL(objs, binary = false) {
const g = new THREE.Group();
for (const o of objs) { // o.mesh: Mesh or Brush (result of CSG)
const m = new THREE.Mesh(o.mesh.geometry); // plain Mesh, geometry is shared
m.matrix.copy(o.mesh.matrixWorld); m.matrixAutoUpdate = false;
g.add(m);
}
g.updateMatrixWorld(true);
return new STLExporter().parse(g, { binary }); // string (ASCII) or DataView (binary)
}
Call obj.updateMatrixWorld(true) after changing position/rotation and before
exporting or evaluating CSG. Holes must already be subtracted (export the group
results, not the hole brushes).
Import STL
pickFile('.stl', false, buf => { // ArrayBuffer (see single-file-webapp skill)
const geo = new STLLoader().parse(buf); // handles ASCII and binary
geo.computeVertexNormals(); geo.center(); // optional
addObject({ type: 'stl', geometry: geo });
});
Extrude an SVG
pickFile('.svg', true, text => {
const data = new SVGLoader().parse(text);
const shapes = data.paths.flatMap(p => SVGLoader.createShapes(p));
const geo = new THREE.ExtrudeGeometry(shapes, { depth: 2, bevelEnabled: false });
geo.rotateX(Math.PI); // SVG y points down
geo.center();
addObject({ type: 'svg', geometry: geo });
});
SVG units are px; scale with geo.scale(s, s, 1) (e.g. s = 0.1 for mm-ish sizes).
Solid text
const FONT_URL = 'https://cdn.jsdelivr.net/npm/three@0.160.0/examples/fonts/helvetiker_regular.typeface.json';
const font = await new FontLoader().loadAsync(FONT_URL); // load once, cache
const geo = new TextGeometry('Hi', { font, size: 10, height: 2, curveSegments: 6 });
geo.center();
(height is the extrusion depth; in three >= r163 the option is called depth.)
Group with holes = CSG result
function buildGroup(members) { // members: [{mesh, hole}]
const ev = new Evaluator(); ev.useGroups = false;
const solids = members.filter(m => !m.hole), holes = members.filter(m => m.hole);
let result = null;
for (const s of solids) { s.mesh.updateMatrixWorld(true);
result = result ? ev.evaluate(result, s.mesh, ADDITION) : s.mesh; }
for (const h of holes) { h.mesh.updateMatrixWorld(true);
result = ev.evaluate(result, h.mesh, SUBTRACTION); }
result.material = solids[0].mesh.material;
return result; // a Brush; keep `members` for ungroup
}
Operands must be Brush instances (create every object as a Brush from the
start, so any object can take part in CSG later). Render holes with a
transparent material (transparent: true, opacity: 0.35) until grouped.