diff --git a/README.md b/README.md index 9f9690f..2c33da3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Doodle3D-Slicer + JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box + # Usage ```javascript import * as THREE from 'three'; -import { defaultSettings, Slicer } from 'Doodle3D/Doodle3D-Slicer'; +import { defaultSettings, sliceGeometry } from 'Doodle3D/Doodle3D-Slicer'; const settings = { ...defaultSettings.base, @@ -15,10 +17,46 @@ const settings = { const geometry = new THREE.TorusGeometry(20, 10, 30, 30).clone(); -const slicer = new SLICER.Slicer(); -slicer.setGeometry(geometry); -const gcode = await slicer.slice(settings, ({ progress: { done, total, action } }) => { - const percentage = `${(done / total * 100).toFixed()}%` - console.log(action, percentage); -})); +const gcode = await sliceGeometry(settings, geometry); ``` + +# API + +**Settings** +```javascript +import { defaultSettings } from 'Doodle3D/Doodle3D-Slicer'; + +const settings = { + ...defaultSettings.base, + ...defaultSettings.material.pla, + ...defaultSettings.printer.ultimaker2go, + ...defaultSettings.quality.high +}; +``` +Create settings object to be used by the slicer + +**Slice Mesh** +```javascript +import { sliceMesh } from 'Doodle3D/Doodle3D-Slicer'; + +GCode: String = sliceMesh(settings: Object, mesh: THREE.Mesh, [sync: Boolean = false, onProgress: Func ]) +``` +Slice function that accepts Meshes + - Settings: settings object (see [settings](#settings)) + - Mesh: THREE.Mesh instance that contains the geometry + - Sync: determines if the slicing progress will be sync (blocking) or async (non-blocking). A webworker is used to slice async + - onProgress: progress callback + +**Slice Geometry** +```javascript +import { sliceGeometry } from 'Doodle3D/Doodle3D-Slicer'; + +GCode: String = sliceGeometry(settings: Object, geometry: THREE.Geometry | THREE.BufferGeometry, [matrix: THREE.Matrix, sync: Boolean = false, onProgress: Func ]) +``` + +Slice function that accepts Geometry + - Settings: settings object (see [settings](#settings)) + - Geometry: THREE.Geometry instance + - matrix: matrix that can control the scale, rotation and position of the model + - Sync: determines if the slicing progress will be sync (blocking) or async (non-blocking). A webworker is used to slice async + - onProgress: progress callback diff --git a/example/save.js b/example/save.js index 9524c6c..a3eeb99 100644 --- a/example/save.js +++ b/example/save.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import { defaultSettings, Slicer } from 'src/index.js'; -import { saveAs } from 'file-saver'; +import { defaultSettings, sliceGeometry } from 'src/index.js'; +import fileSaver from 'file-saver'; const settings = { ...defaultSettings.base, @@ -12,16 +12,16 @@ const settings = { const jsonLoader = new THREE.JSONLoader(); jsonLoader.load('models/airplane.json', async geometry => { geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2)); - geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.1, 50))); + geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.0, 50))); geometry.computeFaceNormals(); - const slicer = new Slicer().setGeometry(geometry); - const gcode = await slicer.slice(settings) - .progress(({ progress: { done, total, action } }) => { - const percentage = `${(done / total * 100).toFixed()}%` - document.write(`
${action}, ${percentage}
`); - }); + const onProgress = ({ progress: { done, total, action } }) => { + const percentage = `${(done / total * 100).toFixed()}%` + document.write(`${action}, ${percentage}
`); + }; + + const gcode = await sliceGeometry(settings, geometry, null, false, onProgress); const file = new File([gcode], 'gcode.gcode', { type: 'text/plain' }); - saveAs(file); + fileSaver.saveAs(file); }); diff --git a/src/Slicer.js b/src/Slicer.js index c80959b..4da1f01 100644 --- a/src/Slicer.js +++ b/src/Slicer.js @@ -2,64 +2,73 @@ import * as THREE from 'three'; import slice from './sliceActions/slice.js'; import SlicerWorker from './slicerWorker.js!worker'; -export default class { - setMesh(mesh) { - mesh.updateMatrix(); - - return this.setGeometry(mesh.geometry, mesh.matrix); +export function sliceMesh(settings, mesh, sync = false, onProgress) { + if (typeof mesh === 'undefined' || !mesh.isMesh) { + throw new Error('Provide mesh is not intance of THREE.Mesh'); } - setGeometry(geometry, matrix) { - if (geometry.isBufferGeometry) { - geometry = new THREE.Geometry().fromBufferGeometry(geometry); - } else if (geometry.isGeometry) { - geometry = geometry.clone(); - } else { - throw new Error('Geometry is not an instance of BufferGeometry or Geometry'); - } - if (typeof matrix !== 'undefined') { - geometry.applyMatrix(matrix); - } + mesh.updateMatrix(); + const { geometry, matrix } = mesh; + return sliceGeometry(settings, geometry, matrix, sync, onProgress); +} - this.geometry = geometry; - - return this; +export function sliceGeometry(settings, geometry, matrix, sync = false, onProgress) { + if (typeof geometry === 'undefined') { + throw new Error('Missing required geometry argument'); + } else if (geometry.isBufferGeometry) { + geometry = new THREE.Geometry().fromBufferGeometry(geometry); + } else if (geometry.isGeometry) { + geometry = geometry.clone(); + } else { + throw new Error('Geometry is not an instance of BufferGeometry or Geometry'); } - sliceSync(settings, onProgress) { - return slice(this.geometry, settings, onProgress); + + if (matrix) { + geometry.applyMatrix(matrix); } - slice(settings, onProgress) { - if (!this.geometry) { - throw new Error('Geometry is not set, use Slicer.setGeometry or Slicer.setMesh first'); - } - return new Promise((resolve, reject) => { - // create the slicer worker - const slicerWorker = new SlicerWorker(); - slicerWorker.onerror = reject; - - // listen to messages send from worker - slicerWorker.addEventListener('message', (event) => { - const { message, data } = event.data; - switch (message) { - case 'SLICE': { - slicerWorker.terminate(); - resolve(data.gcode); - break; - } - case 'PROGRESS': { - onProgress(data); - break; - } - } - }); - - // send geometry and settings to worker to start the slicing progress - const geometry = this.geometry.toJSON(); - slicerWorker.postMessage({ - message: 'SLICE', - data: { geometry, settings } - }); - }); + if (sync) { + return sliceSync(settings, geometry, onProgress); + } else { + return sliceAsync(settings, geometry, onProgress); } } + +function sliceSync(settings, geometry, onProgress) { + return slice(settings, geometry, onProgress); +} + +function sliceAsync(settings, geometry, onProgress) { + return new Promise((resolve, reject) => { + // create the slicer worker + const slicerWorker = new SlicerWorker(); + slicerWorker.onerror = reject; + + // listen to messages send from worker + slicerWorker.addEventListener('message', (event) => { + const { message, data } = event.data; + switch (message) { + case 'SLICE': { + slicerWorker.terminate(); + resolve(data.gcode); + break; + } + case 'PROGRESS': { + if (typeof onProgress !== 'undefined') { + onProgress(data); + } + break; + } + } + }); + + // send geometry and settings to worker to start the slicing progress + slicerWorker.postMessage({ + message: 'SLICE', + data: { + settings, + geometry: geometry.toJSON() + } + }); + }); +} diff --git a/src/index.js b/src/index.js index 0ff7898..291b02e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import Slicer from './Slicer.js'; +import { sliceGeometry, sliceMesh } from './slicer.js'; import baseSettings from './settings/default.yml!text'; import printerSettings from './settings/printer.yml!text'; import materialSettings from './settings/material.yml!text'; @@ -13,6 +13,7 @@ const defaultSettings = { }; export { - Slicer, + sliceGeometry, + sliceMesh, defaultSettings }; diff --git a/src/sliceActions/slice.js b/src/sliceActions/slice.js index e0d63e1..2d3fdd0 100644 --- a/src/sliceActions/slice.js +++ b/src/sliceActions/slice.js @@ -13,12 +13,20 @@ import detectOpenClosed from './detectOpenClosed.js'; import applyPrecision from './applyPrecision.js'; import removePrecision from './removePrecision.js'; -export default function(geometry, settings, onProgress) { +export default function(settings, geometry, onProgress) { const totalStages = 12; let current = -1; const updateProgress = (action) => { current ++; - if (onProgress) onProgress({ done: current, total: totalStages, action }); + if (typeof onProgress !== 'undefined') { + onProgress({ + progress: { + done: current, + total: totalStages, + action + } + }); + } }; geometry.computeFaceNormals(); diff --git a/src/slicerWorker.js b/src/slicerWorker.js index ff1797e..7c0bc37 100644 --- a/src/slicerWorker.js +++ b/src/slicerWorker.js @@ -6,7 +6,7 @@ const loader = new THREE.JSONLoader(); const onProgress = progress => { self.postMessage({ message: 'PROGRESS', - data: { progress } + data: progress }); } @@ -14,11 +14,10 @@ self.addEventListener('message', (event) => { const { message, data } = event.data; switch (message) { case 'SLICE': { - const { geometry: JSONGeometry, settings } = data; - + const { settings, geometry: JSONGeometry } = data; const { geometry } = loader.parse(JSONGeometry.data); - const gcode = slice(geometry, settings, onProgress); + const gcode = slice(settings, geometry, onProgress); self.postMessage({ message: 'SLICE',