Merge branch 'master' into develop

This commit is contained in:
casperlamboo 2018-01-15 11:53:48 +01:00
commit 6f735b5de4
38 changed files with 6584 additions and 6718 deletions

View File

@ -3,7 +3,10 @@
"module": { "module": {
"presets": [ "presets": [
["env", { ["env", {
"targets": { "node": "6" }, "targets": {
"node": "6",
"browsers": ["last 2 versions", "safari >= 7", "not ie < 11"]
},
"modules": false "modules": false
}], }],
"stage-0", "stage-0",
@ -15,11 +18,9 @@
} }
}, },
"plugins": [ "plugins": [
"babel-plugin-transform-regenerator", "transform-class-properties",
"babel-plugin-transform-object-rest-spread", "transform-object-rest-spread",
"babel-plugin-inline-import", "transform-runtime",
"babel-plugin-transform-class-properties", "transform-es2015-classes"
"babel-plugin-transform-es2015-classes",
"babel-plugin-syntax-dynamic-import"
] ]
} }

4
.gitignore vendored
View File

@ -1,10 +1,8 @@
*.DS_Store *.DS_Store
jspm_package
node_modules node_modules
lib lib
module module
dist dist

View File

@ -1,4 +1,2 @@
node_modules node_modules
jspm_packages jspm_packages
example
simpleExample

View File

@ -9,7 +9,7 @@ import * as THREE from 'three';
import { defaultSettings, sliceGeometry } from 'Doodle3D/Doodle3D-Slicer'; import { defaultSettings, sliceGeometry } from 'Doodle3D/Doodle3D-Slicer';
const settings = { const settings = {
...defaultSettings.base, ...defaultSettings.default,
...defaultSettings.material.pla, ...defaultSettings.material.pla,
...defaultSettings.printer.ultimaker2go, ...defaultSettings.printer.ultimaker2go,
...defaultSettings.quality.high ...defaultSettings.quality.high
@ -27,7 +27,7 @@ const gcode = await sliceGeometry(settings, geometry);
import { defaultSettings } from 'Doodle3D/Doodle3D-Slicer'; import { defaultSettings } from 'Doodle3D/Doodle3D-Slicer';
const settings = { const settings = {
...defaultSettings.base, ...defaultSettings.default,
...defaultSettings.material.pla, ...defaultSettings.material.pla,
...defaultSettings.printer.ultimaker2go, ...defaultSettings.printer.ultimaker2go,
...defaultSettings.quality.high ...defaultSettings.quality.high

View File

@ -1,33 +0,0 @@
import React from 'react';
import * as THREE from 'three';
import { Interface } from 'doodle3d-slicer';
import fileURL from '!url-loader!./models/shape.json';
import { render } from 'react-dom';
import fileSaver from 'file-saver';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
document.body.style.margin = 0;
document.body.style.padding = 0;
document.body.style.height = '100%';
document.documentElement.style.height = '100%'
document.getElementById('app').style.height = '100%';
const downloadGCode = gcode => {
const file = new File([gcode], 'gcode.gcode', { type: 'text/plain' });
fileSaver.saveAs(file);
};
const jsonLoader = new THREE.JSONLoader();
jsonLoader.load(fileURL, geometry => {
render((
<MuiThemeProvider>
<Interface
geometry={geometry}
onCompleteActions={[{ title: 'Download', callback: downloadGCode }]}
/>
</MuiThemeProvider>
), document.getElementById('app'));
});

5812
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +0,0 @@
{
"name": "doodle3d-simple-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack -p",
"start": "webpack-dev-server -w"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel-polyfill": "^6.23.0",
"file-saver": "^1.3.3",
"html-webpack-template": "^6.0.2",
"imports-loader": "^0.7.1",
"material-ui": "^0.19.4",
"react": "^16.1.0",
"react-dom": "^16.1.0",
"react-tap-event-plugin": "^3.0.2",
"three": "^0.83.0",
"url-loader": "^0.5.9"
},
"devDependencies": {
"babel-plugin-transform-runtime": "^6.23.0",
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-latest": "^6.24.1",
"html-webpack-plugin": "^2.29.0",
"webpack": "^3.3.0",
"webpack-dev-server": "^2.5.1",
"worker-loader": "^0.8.1",
"yml-loader": "^2.1.0"
}
}

42
index.js Normal file
View File

@ -0,0 +1,42 @@
import 'babel-polyfill'
import React from 'react';
import { Interface } from 'doodle3d-slicer';
import doodleURL from '!url-loader!./models/Doodle_2.d3sketch';
import { render } from 'react-dom';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import injectTapEventPlugin from 'react-tap-event-plugin';
import jss from 'jss';
import preset from 'jss-preset-default';
import normalize from 'normalize-jss';
import JSONToSketchData from 'doodle3d-core/shape/JSONToSketchData';
import createSceneData from 'doodle3d-core/d3/createSceneData.js';
import { generateExportMesh } from 'doodle3d-core/utils/exportUtils.js';
import { Matrix4 } from 'three/src/math/Matrix4.js';
injectTapEventPlugin();
jss.setup(preset());
jss.createStyleSheet(normalize).attach();
jss.createStyleSheet({
'@global': {
'*': { margin: 0, padding: 0 },
'#app, body, html': { height: '100%', fontFamily: 'sans-serif' },
body: { overflow: 'auto' },
html: { overflow: 'hidden' }
}
}).attach();
function init(mesh) {
render((
<MuiThemeProvider>
<Interface mesh={mesh} name="doodle"/>
</MuiThemeProvider>
), document.getElementById('app'));
}
fetch(doodleURL)
.then(resonse => resonse.json())
.then(json => JSONToSketchData(json))
.then(file => createSceneData(file))
.then(sketch => generateExportMesh(sketch, { offsetSingleWalls: false, matrix: new Matrix4() }))
.then(init);

1
models/Doodle.d3sketch Normal file
View File

@ -0,0 +1 @@
{"data":"{\"spaces\":[{\"matrix\":{\"metadata\":{\"type\":\"Matrix4\",\"library\":\"three.js\"},\"elements\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"objects\":[{\"height\":9.266873708001008,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,26.586102719033242,0,1,-4.229607250755304]},\"z\":10.733126291998994,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":false,\"star\":{\"rays\":5,\"innerRadius\":20.54380664652568,\"outerRadius\":40.48338368580059},\"color\":6873597,\"type\":\"STAR\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-12.688821752265852,0,1,-12.68882175226588]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":true,\"star\":{\"rays\":5,\"innerRadius\":20.54380664652568,\"outerRadius\":40.48338368580059},\"color\":6873597,\"type\":\"STAR\"}]}]}","appVersion":"0.17.4"}

1
models/Doodle_2.d3sketch Normal file
View File

@ -0,0 +1 @@
{"data":"{\"spaces\":[{\"matrix\":{\"metadata\":{\"type\":\"Matrix4\",\"library\":\"three.js\"},\"elements\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"objects\":[{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-32.27848101265822,0,1,5.3797468354430436]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":false,\"solid\":true,\"star\":{\"rays\":5,\"innerRadius\":10,\"outerRadius\":25},\"color\":6873597,\"type\":\"STAR\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,47.784810126582286,0,1,0.6329113924050631]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":true,\"star\":{\"rays\":5,\"innerRadius\":22.468354430379748,\"outerRadius\":25.9493670886076},\"color\":6873597,\"type\":\"STAR\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-46.83544303797467,0,1,9.810126582278485]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":false,\"solid\":false,\"rectSize\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Vector\"},\"x\":120.8860759493671,\"y\":34.49367088607595},\"color\":6873597,\"type\":\"RECT\"},{\"height\":20,\"transform\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Matrix\"},\"matrix\":[1,0,-47.1518987341772,0,1,-37.341772151898724]},\"z\":0,\"sculpt\":[{\"pos\":0,\"scale\":1},{\"pos\":1,\"scale\":1}],\"twist\":0,\"fill\":true,\"solid\":false,\"rectSize\":{\"metadata\":{\"library\":\"CAL\",\"type\":\"Vector\"},\"x\":120.8860759493671,\"y\":34.49367088607595},\"color\":6873597,\"type\":\"RECT\"}]}]}","appVersion":"0.17.4"}

5710
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,12 @@
{ {
"name": "@doodle3d/doodle3d-slicer", "name": "@doodle3d/doodle3d-slicer",
"version": "0.0.13", "version": "0.0.18",
"description": "JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box # Usage", "description": "JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box # Usage",
"main": "lib/index.js", "main": "lib/index.js",
"module": "module/index.js", "module": "module/index.js",
"esnext": "src/index.js", "esnext": "src/index.js",
"scripts": { "scripts": {
"start": "webpack-dev-server -w",
"prepare": "npm run build", "prepare": "npm run build",
"build": "npm run build:main && npm run build:main:settings && npm run build:module && npm run build:module:settings ", "build": "npm run build:main && npm run build:main:settings && npm run build:module && npm run build:module:settings ",
"build:main": "BABEL_ENV=main babel src -s -d lib", "build:main": "BABEL_ENV=main babel src -s -d lib",
@ -15,29 +16,42 @@
}, },
"dependencies": { "dependencies": {
"@doodle3d/clipper-js": "^1.0.7", "@doodle3d/clipper-js": "^1.0.7",
"babel-plugin-transform-class-properties": "^6.24.1",
"file-saver": "^1.3.3",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"material-ui": "^0.19.4", "material-ui": "^0.19.4",
"proptypes": "^1.1.0", "proptypes": "^1.1.0",
"react": "^16.1.0", "react": "^16.0.0",
"react-dom": "^16.1.0", "react-dom": "^16.0.0",
"react-jss": "^7.2.0", "react-jss": "^7.2.0",
"react-resize-detector": "^1.1.0", "react-resize-detector": "^1.1.0",
"three": "^0.83.0" "three": "^0.88.0"
}, },
"devDependencies": { "devDependencies": {
"babel-plugin-inline-import": "^2.0.6", "@doodle3d/doodle3d-core": "github:doodle3d/doodle3d-core",
"babel-preset-stage-0": "^6.24.1", "babel-cli": "6.24.1",
"babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-loader": "7.0.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-es2015-classes": "^6.24.1", "babel-plugin-transform-es2015-classes": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-preset-es2015": "6.24.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babel-cli": "6.24.1", "babel-preset-stage-0": "^6.24.1",
"babel-core": "6.24.1", "babel-runtime": "^6.26.0",
"babel-loader": "7.0.0", "html-webpack-plugin": "^2.29.0",
"babel-plugin-add-module-exports": "0.2.1", "html-webpack-template": "^6.0.2",
"babel-preset-es2015": "6.24.1" "imports-loader": "^0.7.1",
"material-ui": "^0.19.4",
"normalize-jss": "^4.0.0",
"raw-loader": "^0.5.1",
"react-tap-event-plugin": "^3.0.2",
"url-loader": "^0.5.9",
"webpack": "^3.3.0",
"webpack-dev-server": "^2.5.1",
"worker-loader": "^0.8.1",
"yml-loader": "^2.1.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,12 +1,12 @@
import { sliceGeometry, sliceMesh } from './slicer.js'; import { sliceGeometry, sliceMesh } from './slicer.js';
import Interface from './interface/index.js'; import Interface from './interface/index.js';
import baseSettings from './settings/default.yml'; import _defaultSettings from './settings/default.yml';
import printerSettings from './settings/printer.yml'; import printerSettings from './settings/printer.yml';
import materialSettings from './settings/material.yml'; import materialSettings from './settings/material.yml';
import qualitySettings from './settings/quality.yml'; import qualitySettings from './settings/quality.yml';
const defaultSettings = { const defaultSettings = {
base: baseSettings, default: _defaultSettings,
printer: printerSettings, printer: printerSettings,
material: materialSettings, material: materialSettings,
quality: qualitySettings quality: qualitySettings

View File

@ -5,56 +5,35 @@ import injectSheet from 'react-jss';
import MaterialUISelectField from 'material-ui/SelectField' import MaterialUISelectField from 'material-ui/SelectField'
import MaterialUICheckbox from 'material-ui/Checkbox'; import MaterialUICheckbox from 'material-ui/Checkbox';
import MaterialUITextField from 'material-ui/TextField'; import MaterialUITextField from 'material-ui/TextField';
import { grey100, grey300, grey500 } from 'material-ui/styles/colors';
const styles = { const contextTypes = { state: PropTypes.object, onChange: PropTypes.func, disabled: PropTypes.bool };
fieldSet: {
border: 'none',
backgroundColor: grey100,
marginTop: '20px',
'& legend': {
border: `1px solid ${grey300}`,
backgroundColor: 'white',
padding: '3px 13px',
color: grey500
}
}
};
export const SettingsGroup = injectSheet(styles)(({ name, classes, children }) => (
<fieldset className={classes.fieldSet}>
<legend>{name}</legend>
{children}
</fieldset>
));
SettingsGroup.propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
name: PropTypes.string.isRequired,
children: PropTypes.node
};
export const SelectField = (props, context) => ( export const SelectField = (props, context) => (
<MaterialUISelectField <MaterialUISelectField
{ ...props } { ...props }
value={context.state[props.name]} disabled={context.disabled}
value={_.get(context.state, props.name)}
onChange={(event, index, value) => context.onChange(props.name, value)} onChange={(event, index, value) => context.onChange(props.name, value)}
/> />
); );
SelectField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func }; SelectField.contextTypes = contextTypes;
export const TextField = (props, context) => ( export const TextField = (props, context) => (
<MaterialUITextField <MaterialUITextField
{ ...props } { ...props }
disabled={context.disabled}
value={_.get(context.state, props.name)} value={_.get(context.state, props.name)}
onChange={(event, value) => context.onChange(props.name, value)} onChange={(event, value) => context.onChange(props.name, value)}
/> />
); );
TextField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func }; TextField.contextTypes = contextTypes;
export const Checkbox = (props, context) => ( export const Checkbox = (props, context) => (
<MaterialUICheckbox <MaterialUICheckbox
{ ...props } { ...props }
disabled={context.disabled}
checked={_.get(context.state, props.name)} checked={_.get(context.state, props.name)}
onCheck={(event, value) => context.onChange(props.name, value)} onCheck={(event, value) => context.onChange(props.name, value)}
/> />
); );
Checkbox.contextTypes = { state: PropTypes.object, onChange: PropTypes.func }; Checkbox.contextTypes = contextTypes;

View File

@ -4,18 +4,43 @@ import _ from 'lodash';
import { Tabs, Tab } from 'material-ui/Tabs'; import { Tabs, Tab } from 'material-ui/Tabs';
import MenuItem from 'material-ui/MenuItem'; import MenuItem from 'material-ui/MenuItem';
import injectSheet from 'react-jss'; import injectSheet from 'react-jss';
import { SettingsGroup, SelectField, TextField, Checkbox } from './FormComponents.js'; import { SelectField, TextField, Checkbox } from './FormComponents.js';
import { grey500 } from 'material-ui/styles/colors'; import { grey800, cyan500 } from 'material-ui/styles/colors';
const styles = { const styles = {
textFieldRow: { textFieldRow: {
display: 'flex' display: 'flex'
},
container: {
width: '100%',
flexGrow: 1,
overflowY: 'auto',
'& p, h3': {
fontWeight: 'bold',
margin: '30px 0 0 0'
}
} }
}; };
class Settings extends React.Component { class Settings extends React.Component {
static childContextTypes = { state: PropTypes.object, onChange: PropTypes.func, disabled: PropTypes.bool };
static defaultProps: {
disabled: false
};
static propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
onChange: PropTypes.func,
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
initialSettings: PropTypes.object.isRequired,
disabled: PropTypes.bool.isRequired
};
constructor(props) { constructor(props) {
super(props); super();
this.state = { this.state = {
settings: props.initialSettings, settings: props.initialSettings,
printers: props.defaultPrinter, printers: props.defaultPrinter,
@ -42,116 +67,96 @@ class Settings extends React.Component {
state = _.set(_.cloneDeep(this.state), fieldName, value); state = _.set(_.cloneDeep(this.state), fieldName, value);
break; break;
} }
if (onChange) onChange(state.settings); if (onChange) onChange(state);
if (state) this.setState(state); if (state) this.setState(state);
}; };
getChildContext() { getChildContext() {
return { state: this.state, onChange: this.changeSettings }; return { state: this.state, onChange: this.changeSettings, disabled: this.props.disabled };
} }
render() { render() {
const { classes, printers, quality, material } = this.props; const { classes, printers, quality, material, disabled } = this.props;
return ( return (
<Tabs> <div className={classes.container}>
<Tab label="basic"> <SelectField name="printers" floatingLabelText="Printer" fullWidth>
<div> {Object.entries(printers).map(([value, { title }]) => (
<SelectField name="printers" floatingLabelText="Printer" fullWidth> <MenuItem key={value} value={value} primaryText={title} />
{Object.entries(printers).map(([value, { title }]) => ( ))}
<MenuItem key={value} value={value} primaryText={title} /> </SelectField>
))} <SelectField name="material" floatingLabelText="Material" fullWidth>
</SelectField> {Object.entries(material).map(([value, { title }]) => (
<SelectField name="quality" floatingLabelText="Quality" fullWidth> <MenuItem key={value} value={value} primaryText={title} />
{Object.entries(quality).map(([value, { title }]) => ( ))}
<MenuItem key={value} value={value} primaryText={title} /> </SelectField>
))} <h3>Printer Setup</h3>
</SelectField> <Tabs inkBarStyle={{ backgroundColor: cyan500 }}>
<SelectField name="material" floatingLabelText="Material" fullWidth> <Tab buttonStyle={{ color: grey800, backgroundColor: 'white' }} label="Basic">
{Object.entries(material).map(([value, { title }]) => ( <div>
<MenuItem key={value} value={value} primaryText={title} /> <SelectField name="quality" floatingLabelText="Quality" fullWidth>
))} {Object.entries(quality).map(([value, { title }]) => (
</SelectField> <MenuItem key={value} value={value} primaryText={title} />
</div> ))}
</Tab> </SelectField>
<Tab label="advanced"> </div>
<div> </Tab>
<SettingsGroup name="Printer dimensions"> <Tab buttonStyle={{ color: grey800, backgroundColor: 'white' }} label="Advanced">
<div>
<p>Layer</p>
<TextField name="settings.layerHeight" fullWidth floatingLabelText="Height" type="number" />
<p>Printer dimensions</p>
<div className={classes.textFieldRow}> <div className={classes.textFieldRow}>
<TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" /> <TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" />
<TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" /> <TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" />
<TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" /> <TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" />
</div> </div>
</SettingsGroup> <p>Nozzle</p>
<SettingsGroup name="Nozzle">
<TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" /> <TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" />
</SettingsGroup> <p>Bed</p>
<SettingsGroup name="Bed">
<TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" /> <TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" />
<Checkbox name="settings.heatedBed" label="Heated" /> <Checkbox name="settings.heatedBed" label="Heated" />
</SettingsGroup> <p>Material</p>
<SettingsGroup name="Material">
<TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" /> <TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" />
<TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" /> <TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" />
</SettingsGroup> <p>Thickness</p>
<SettingsGroup name="Thickness">
<TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" /> <TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" />
<TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" /> <TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" />
<TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" /> <TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" />
</SettingsGroup> <p>Retraction</p>
<SettingsGroup name="Retraction">
<Checkbox name="settings.retraction.enabled" label="Enabled" /> <Checkbox name="settings.retraction.enabled" label="Enabled" />
<TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" /> <TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" />
<TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" /> <TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" />
</SettingsGroup> <p>Travel</p>
<SettingsGroup name="Travel">
<TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" />
<Checkbox name="settings.combing" label="Combing" /> <Checkbox name="settings.combing" label="Combing" />
</SettingsGroup> <p>Inner shell</p>
<SettingsGroup name="Inner shell">
<TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> <p>Outer shell</p>
<SettingsGroup name="Outer shell">
<TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> <p>Inner infill</p>
<SettingsGroup name="Inner infill">
<TextField name="settings.innerInfill.gridSize" fullWidth floatingLabelText="Grid size" type="number" /> <TextField name="settings.innerInfill.gridSize" fullWidth floatingLabelText="Grid size" type="number" />
<TextField name="settings.innerInfill.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.innerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.innerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.innerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> <p>Outer infill</p>
<SettingsGroup name="Outer infill">
<TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> <p>Brim</p>
<SettingsGroup name="Brim">
<TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" /> <TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" />
<TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> <p>First layer</p>
<SettingsGroup name="First layer">
<TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" /> <TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" /> <TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup> </div>
</div> </Tab>
</Tab> </Tabs>
</Tabs> </div>
); );
} }
} }
Settings.childContextTypes = { state: PropTypes.object, onChange: PropTypes.func };
Settings.propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
onChange: PropTypes.func,
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
initialSettings: PropTypes.object.isRequired
};
export default injectSheet(styles)(Settings); export default injectSheet(styles)(Settings);

View File

@ -1,27 +1,38 @@
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import * as THREE from 'three'; import { Quaternion } from 'three/src/math/Quaternion.js';
import { Vector3 } from 'three/src/math/Vector3.js';
import { Mesh } from 'three/src/objects/Mesh.js';
import PropTypes from 'proptypes'; import PropTypes from 'proptypes';
import { placeOnGround, createScene, createGcodeGeometry } from './utils.js'; import { placeOnGround, createScene, fetchProgress, slice, TabTemplate } from './utils.js';
import injectSheet from 'react-jss'; import injectSheet from 'react-jss';
import { sliceGeometry } from '../slicer.js';
import RaisedButton from 'material-ui/RaisedButton'; import RaisedButton from 'material-ui/RaisedButton';
import FlatButton from 'material-ui/FlatButton';
import Slider from 'material-ui/Slider'; import Slider from 'material-ui/Slider';
import { grey100, grey300 } from 'material-ui/styles/colors'; import LinearProgress from 'material-ui/LinearProgress';
import { grey50, grey300, grey800, red500 } from 'material-ui/styles/colors';
import Popover from 'material-ui/Popover/Popover';
import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
import { Tabs, Tab } from 'material-ui/Tabs';
import Settings from './Settings.js'; import Settings from './Settings.js';
import baseSettings from '../settings/default.yml'; import defaultSettings from '../settings/default.yml';
import printerSettings from '../settings/printer.yml'; import printerSettings from '../settings/printer.yml';
import materialSettings from '../settings/material.yml'; import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml'; import qualitySettings from '../settings/quality.yml';
import ReactResizeDetector from 'react-resize-detector'; import ReactResizeDetector from 'react-resize-detector';
const MAX_FULLSCREEN_WIDTH = 720;
const styles = { const styles = {
container: { container: {
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
height: '100%', height: '100%',
backgroundColor: grey100, backgroundColor: grey50,
overflow: 'hidden' color: grey800,
overflow: 'hidden',
fontFamily: 'roboto, sans-serif'
}, },
controlBar: { controlBar: {
position: 'absolute', position: 'absolute',
@ -29,46 +40,87 @@ const styles = {
left: '10px' left: '10px'
}, },
d3View: { d3View: {
flexGrow: 1 flexGrow: 1,
flexBasis: 0
}, },
canvas: { canvas: {
position: 'absolute' position: 'absolute'
}, },
sliceBar: { settingsBar: {
width: '240px', display: 'flex',
padding: '0 10px', flexDirection: 'column',
overflowY: 'auto', maxWidth: '380px',
boxSizing: 'border-box',
padding: '10px',
backgroundColor: 'white', backgroundColor: 'white',
borderLeft: `1px solid ${grey300}` borderLeft: `1px solid ${grey300}`
}, },
overlay: {
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
color: 'white',
top: 0,
right: 0,
bottom: 0,
left: 0
},
sliceActions: { sliceActions: {
listStyleType: 'none' flexShrink: 0,
},
sliceButtons: {
justifyContent: 'flex-end',
display: 'flex'
}, },
button: { button: {
margin: '5px 0' margin: '5px 0 5px 5px'
}, },
controlButton: { controlButton: {
marginRight: '2px' marginRight: '2px'
},
buttonContainer: {
width: '100%',
padding: '10px'
},
error: {
color: red500
},
title: {
position: 'absolute'
} }
}; };
class Interface extends React.Component { class Interface extends React.Component {
static propTypes = {
mesh: PropTypes.shape({ isMesh: PropTypes.oneOf([true]) }).isRequired,
classes: PropTypes.objectOf(PropTypes.string),
defaultSettings: PropTypes.object.isRequired,
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
pixelRatio: PropTypes.number.isRequired,
onCancel: PropTypes.func,
name: PropTypes.string.isRequired
};
static defaultProps = {
defaultSettings: defaultSettings,
printers: printerSettings,
quality: qualitySettings,
defaultQuality: 'medium',
material: materialSettings,
defaultMaterial: 'pla',
pixelRatio: 1,
name: 'Doodle3D'
};
constructor(props) { constructor(props) {
super(props); super(props);
const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props; const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props;
this.state = { this.state = {
controlMode: 'translate', showFullScreen: false,
isSlicing: false, isSlicing: false,
sliced: false, error: null,
printers: defaultPrinter,
quality: defaultQuality,
material: defaultMaterial,
popover: {
element: null,
open: false
},
settings: _.merge( settings: _.merge(
{}, {},
defaultSettings, defaultSettings,
@ -81,12 +133,21 @@ class Interface extends React.Component {
componentDidMount() { componentDidMount() {
const { canvas } = this.refs; const { canvas } = this.refs;
const scene = createScene(canvas, this.props, this.state); const scene = createScene(canvas, this.props, this.state);
this.setState(scene); this.setState({ ...scene });
}
componentWillUnmount() {
const { editorControls, mesh: { material }, renderer } = this.state;
editorControls.dispose();
material.dispose();
renderer.dispose();
} }
resetMesh = () => { resetMesh = () => {
const { mesh, render } = this.state; const { mesh, render, isSlicing } = this.state;
if (isSlicing) return;
if (mesh) { if (mesh) {
mesh.position.set(0, 0, 0); mesh.position.set(0, 0, 0);
mesh.scale.set(1, 1, 1); mesh.scale.set(1, 1, 1);
@ -97,176 +158,210 @@ class Interface extends React.Component {
} }
}; };
reset = () => { scaleUp = () => this.scaleMesh(0.9);
const { control, mesh, render, gcode, scene } = this.state; scaleDown = () => this.scaleMesh(1.0 / 0.9);
control.enabled = true; scaleMesh = (factor) => {
control.setSize(1); const { mesh, render, isSlicing } = this.state;
control.visible = true; if (isSlicing) return;
mesh.visible = true; if (mesh) {
mesh.scale.multiplyScalar(factor);
scene.remove(gcode.linePreview); mesh.updateMatrix();
gcode.linePreview.geometry.dispose(); placeOnGround(mesh);
render();
this.setState({ sliced: false, gcode: null }); }
render();
}; };
slice = async () => { rotateX = () => this.rotate(new Vector3(0, 0, 1), Math.PI / 2.0);
const { mesh, render, scene, control, settings } = this.state; rotateY = () => this.rotate(new Vector3(1, 0, 0), Math.PI / 2.0);
rotateZ = () => this.rotate(new Vector3(0, 1, 0), Math.PI / 2.0);
rotate = (axis, angle) => {
const { mesh, render, isSlicing } = this.state;
if (isSlicing) return;
if (mesh) {
mesh.rotateOnWorldAxis(axis, angle);
placeOnGround(mesh);
render();
}
};
const { dimensions } = settings; slice = async (target) => {
const centerX = dimensions.x / 2; const { isSlicing, settings, printers, quality, material, mesh: { matrix } } = this.state;
const centerY = dimensions.y / 2; const { name, mesh } = this.props;
const geometry = mesh.geometry.clone(); if (isSlicing) return;
mesh.updateMatrix();
this.setState({ isSlicing: true, progress: { actions: [], percentage: 0 } }); this.closePopover();
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix); this.setState({ isSlicing: true, progress: { action: '', percentage: 0, step: 0 }, error: null });
const gcode = await sliceGeometry(settings, geometry, matrix, false, true, ({ progress }) => {
this.setState({ progress: { const exportMesh = new Mesh(mesh.geometry, mesh.material);
actions: [...this.state.progress.actions, progress.action], exportMesh.applyMatrix(matrix);
percentage: progress.done / progress.total
} }); try {
await slice(target, name, exportMesh, settings, printers, quality, material, progress => {
this.setState({ progress: { ...this.state.progress, ...progress } });
});
} catch (error) {
this.setState({ error: error.message });
throw error;
} finally {
this.setState({ isSlicing: false });
}
};
openPopover = (event) => {
event.preventDefault();
this.setState({
popover: {
element: event.currentTarget,
open: true
}
});
};
closePopover = () => {
this.setState({
popover: {
element: null,
open: false
}
}); });
this.setState({ isSlicing: false });
// TODO
// can't disable control ui still interacts with mouse input
control.enabled = false;
// hack to disable control
control.setSize(0);
control.visible = false;
mesh.visible = false;
gcode.linePreview.position.x = -centerY;
gcode.linePreview.position.z = -centerX;
scene.add(gcode.linePreview);
this.setState({ sliced: true, gcode });
render();
}; };
onChangeSettings = (settings) => { onChangeSettings = (settings) => {
this.setState({ settings }); this.setState(settings);
}; };
updateDrawRange = (event, value) => {
const { gcode, render } = this.state;
gcode.linePreview.geometry.setDrawRange(0, value);
render();
};
componentWillUnmount() {
if (this.state.editorControls) this.state.editorControls.dispose();
if (this.state.control) this.state.control.dispose();
}
componentWillUpdate(nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
const { control, box, render, setSize } = this.state; const { box, render, setSize } = this.state;
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode); let changed = false;
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) { if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
const { dimensions } = nextState.settings; const { dimensions } = nextState.settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x); box.scale.set(dimensions.y, dimensions.z, dimensions.x);
render(); box.updateMatrix();
} changed = true;
if (setSize && nextProps.width !== this.props.width || nextProps.height !== this.props.height || nextProps.pixelRatio !== this.props.pixelRatio) {
setSize(nextProps.width, nextProps.height, nextProps.pixelRatio);
} }
if (changed) render();
} }
onResize = (width, height) => { componentDidUpdate() {
const { updateCanvas } = this.state;
const { canvas } = this.refs;
if (updateCanvas && canvas) updateCanvas(canvas);
}
onResize3dView = (width, height) => {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
const { setSize } = this.state; const { setSize } = this.state;
const { pixelRatio } = this.props; const { pixelRatio } = this.props;
setSize(width, height, pixelRatio); if (setSize) setSize(width, height, pixelRatio);
}); });
}; };
render() { onResizeContainer = (width) => {
const { width, height, classes, onCompleteActions, defaultPrinter, defaultQuality, defaultMaterial } = this.props; this.setState({ showFullScreen: width > MAX_FULLSCREEN_WIDTH });
const { sliced, isSlicing, progress, gcode, controlMode, settings } = this.state; };
return ( render() {
<div className={classes.container}> const { classes, defaultPrinter, defaultQuality, defaultMaterial, onCancel } = this.props;
<div className={classes.d3View}> const { isSlicing, progress, settings, printers, quality, material, showFullScreen, error } = this.state;
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
<canvas className={classes.canvas} ref="canvas" width={width} height={height} /> const style = { ...(showFullScreen ? {} : { maxWidth: 'inherit', width: '100%', height: '100%' }) };
{!sliced && <div className={classes.controlBar}>
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} primary label="reset" /> const settingsPanel = (
<RaisedButton className={classes.controlButton} disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} primary label="translate" /> <div className={classes.settingsBar} style={style}>
<RaisedButton className={classes.controlButton} disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} primary label="rotate" /> <Settings
<RaisedButton className={classes.controlButton} disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} primary label="scale" /> disabled={isSlicing}
</div>} printers={printerSettings}
defaultPrinter={defaultPrinter}
quality={qualitySettings}
defaultQuality={defaultQuality}
material={materialSettings}
defaultMaterial={defaultMaterial}
initialSettings={settings}
onChange={this.onChangeSettings}
/>
<div className={classes.sliceActions}>
{error && <p className={classes.error}>{error}</p>}
{isSlicing && <p>{progress.action}</p>}
{isSlicing && <LinearProgress mode="determinate" value={progress.percentage * 100.0} />}
<div className={classes.sliceButtons}>
{onCancel && <RaisedButton
label="Cancel"
className={`${classes.button}`}
onTouchTap={onCancel}
/>}
<RaisedButton
label="Print"
ref="button"
primary
className={`${classes.button}`}
onTouchTap={this.openPopover}
disabled={isSlicing}
/>
<Popover
open={this.state.popover.open}
anchorEl={this.state.popover.element}
anchorOrigin={{horizontal: 'left', vertical: 'bottom'}}
targetOrigin={{horizontal: 'left', vertical: 'bottom'}}
onRequestClose={this.closePopover}
>
<Menu>
<MenuItem primaryText="Send over WiFi" onTouchTap={() => this.slice('WIFI')} />
<MenuItem primaryText="Download GCode" onTouchTap={() => this.slice('DOWNLOAD')} />
</Menu>
</Popover>
</div>
</div> </div>
{sliced && <div className={classes.controlBar}>
<Slider
axis="y"
style={{ height: '300px' }}
step={2}
min={1}
max={gcode.linePreview.geometry.getAttribute('position').count}
defaultValue={gcode.linePreview.geometry.getAttribute('position').count}
onChange={this.updateDrawRange}
/>
</div>}
{!sliced && <div className={classes.sliceBar}>
<Settings
printers={printerSettings}
defaultPrinter={defaultPrinter}
quality={qualitySettings}
defaultQuality={defaultQuality}
material={materialSettings}
defaultMaterial={defaultMaterial}
initialSettings={settings}
onChange={this.onChangeSettings}
/>
<RaisedButton className={classes.button} fullWidth disabled={isSlicing} onTouchTap={this.slice} primary label="slice" />
</div>}
{sliced && <div className={classes.sliceBar}>
<RaisedButton className={classes.button} fullWidth onTouchTap={this.reset} primary label="slice again" />
{onCompleteActions.map(({ title, callback }, i) => (
<RaisedButton className={classes.button} key={i} fullWidth onTouchTap={() => callback(gcode.gcode, settings)} primary label={title} />
))}
</div>}
{isSlicing && <div className={classes.overlay}>
<p>Slicing: {progress.percentage.toLocaleString(navigator.language, { style: 'percent' })}</p>
<ul className={classes.sliceActions}>
{progress.actions.map((action, i) => <li key={i}>{action}</li>)}
</ul>
</div>}
</div> </div>
); );
const d3Panel = (
<div className={classes.d3View}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize3dView} />
<canvas className={classes.canvas} ref="canvas" />
<div className={classes.controlBar}>
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.resetMesh} label="reset" />
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.scaleUp} label="scale down" />
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.scaleDown} label="scale up" />
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.rotateX} label="rotate x" />
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.rotateY} label="rotate y" />
<RaisedButton disabled={isSlicing} className={classes.controlButton} onTouchTap={this.rotateZ} label="rotate z" />
</div>
</div>
);
if (showFullScreen) {
return (
<div className={classes.container}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
<h1 className={classes.title}>Print</h1>
{d3Panel}
{settingsPanel}
</div>
);
} else {
return (
<div className={classes.container}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
<Tabs
style={{ width: '100%', display: 'flex', flexDirection: 'column' }}
tabItemContainerStyle={{ flexShrink: 0 }}
contentContainerStyle={{ flexGrow: 1, display: 'flex' }}
tabTemplateStyle={{ display: 'flex' }}
tabTemplate={TabTemplate}
>
<Tab label="Settings">
{settingsPanel}
</Tab>
<Tab label="Edit Model">
{d3Panel}
</Tab>
</Tabs>
</div>
);
}
} }
} }
Interface.propTypes = {
geometry(props, propName) {
if (!(props[propName].isGeometry || props[propName].isBufferGeometry)) {
throw new Error('invalid prop, is not geometry');
}
},
classes: PropTypes.objectOf(PropTypes.string),
onCompleteActions: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string, callback: PropTypes.func })).isRequired,
defaultSettings: PropTypes.object.isRequired,
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
pixelRatio: PropTypes.number.isRequired
};
Interface.defaultProps = {
defaultSettings: baseSettings,
printers: printerSettings,
defaultPrinter: 'ultimaker2',
quality: qualitySettings,
defaultQuality: 'medium',
material: materialSettings,
defaultMaterial: 'pla',
pixelRatio: 1
};
export default injectSheet(styles)(Interface); export default injectSheet(styles)(Interface);

View File

@ -1,33 +1,71 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { Box3 } from 'three/src/math/Box3.js';
import { Matrix4 } from 'three/src/math/Matrix4.js';
import { Scene } from 'three/src/scenes/Scene.js';
import { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera.js';
import { AmbientLight } from 'three/src/lights/AmbientLight.js';
import { DirectionalLight } from 'three/src/lights/DirectionalLight.js';
import { MeshPhongMaterial } from 'three/src/materials/MeshPhongMaterial.js';
import { BoxGeometry } from 'three/src/geometries/BoxGeometry.js';
import { Mesh } from 'three/src/objects/Mesh.js';
import { BoxHelper } from 'three/src/helpers/BoxHelper.js';
import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer.js';
import { DoubleSide } from 'three/src/constants.js';
import 'three/examples/js/controls/EditorControls'; import 'three/examples/js/controls/EditorControls';
import 'three/examples/js/controls/TransformControls'; import printerSettings from '../settings/printer.yml';
import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml';
import { sliceGeometry } from '../slicer.js';
import React from 'react';
import PropTypes from 'prop-types';
import fileSaver from 'file-saver';
export function placeOnGround(mesh) { export function placeOnGround(mesh) {
const boundingBox = new THREE.Box3().setFromObject(mesh); const boundingBox = new Box3().setFromObject(mesh);
mesh.position.y -= boundingBox.min.y; mesh.position.y -= boundingBox.min.y;
mesh.updateMatrix(); mesh.updateMatrix();
} }
export function createScene(canvas, props, state) { export function createScene(canvas, props, state) {
const { geometry, pixelRatio } = props; const { pixelRatio, mesh: { geometry } } = props;
const { controlMode, settings } = state; const { settings } = state;
// center geometry // center geometry
geometry.computeBoundingBox(); geometry.computeBoundingBox();
const centerX = (geometry.boundingBox.max.x + geometry.boundingBox.min.x) / 2; const center = geometry.boundingBox.getCenter();
const centerY = (geometry.boundingBox.max.y + geometry.boundingBox.min.y) / 2; geometry.applyMatrix(new Matrix4().makeTranslation(-center.x, -center.y, -center.z));
const centerZ = (geometry.boundingBox.max.z + geometry.boundingBox.min.z) / 2;
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-centerX, -centerY, -centerZ));
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true }); const scene = new Scene();
renderer.setClearColor(0xffffff, 0);
const scene = new THREE.Scene(); const camera = new PerspectiveCamera(50, 1, 1, 10000);
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
camera.position.set(0, 400, 300); camera.position.set(0, 400, 300);
const directionalLightA = new DirectionalLight(0xa2a2a2);
directionalLightA.position.set(1, 1, 1);
scene.add(directionalLightA);
const directionalLightB = new DirectionalLight(0xa2a2a2);
directionalLightB.position.set(-1, 1, -1);
scene.add(directionalLightB);
const light = new AmbientLight(0x656565);
scene.add(light);
const material = new MeshPhongMaterial({ color: 0x2194ce, side: DoubleSide, specular: 0xc5c5c5, shininess: 5 });
const mesh = new Mesh(geometry, material);
placeOnGround(mesh);
scene.add(mesh);
const box = new BoxHelper(new Mesh(new BoxGeometry(1, 1, 1).applyMatrix(new Matrix4().makeTranslation(0, 0.5, 0))), 0x72bcd4);
scene.add(box);
const { dimensions } = settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
box.updateMatrix();
const render = () => renderer.render(scene, camera);
const setSize = (width, height, pixelRatio = 1) => { const setSize = (width, height, pixelRatio = 1) => {
renderer.setSize(width, height); renderer.setSize(width, height);
renderer.setPixelRatio(pixelRatio); renderer.setPixelRatio(pixelRatio);
@ -36,47 +74,155 @@ export function createScene(canvas, props, state) {
render(); render();
}; };
const directionalLight = new THREE.DirectionalLight(0xd5d5d5); let editorControls;
directionalLight.position.set(1, 1, 1); let renderer;
scene.add(directionalLight); const updateCanvas = (canvas) => {
if (!renderer || renderer.domElement !== canvas) {
if (renderer) renderer.dispose();
renderer = new WebGLRenderer({ canvas, alpha: true, antialias: true });
renderer.setClearColor(0xffffff, 0);
}
if (!editorControls || editorControls.domElement !== canvas) {
if (editorControls) editorControls.dispose();
editorControls = new THREE.EditorControls(camera, canvas);
editorControls.focus(mesh);
editorControls.addEventListener('change', render);
}
const light = new THREE.AmbientLight(0x808080); render();
scene.add(light);
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: 0x2194ce }));
placeOnGround(mesh);
scene.add(mesh);
const editorControls = new THREE.EditorControls(camera, canvas);
editorControls.focus(mesh);
const control = new THREE.TransformControls(camera, canvas);
control.setMode(controlMode);
control.setRotationSnap(THREE.Math.degToRad(45));
control.addEventListener('mouseDown', () => editorControls.enabled = false);
control.addEventListener('mouseUp', () => {
editorControls.enabled = true;
placeOnGround(mesh);
});
control.attach(mesh);
scene.add(control);
const render = () => {
control.update();
renderer.render(scene, camera);
}; };
updateCanvas(canvas);
control.addEventListener('change', render); const focus = () => editorControls.focus(mesh);
editorControls.addEventListener('change', render);
const box = new THREE.BoxHelper(); return { editorControls, scene, mesh, camera, renderer, render, box, setSize, updateCanvas, focus };
box.update(new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1).applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0)))); }
box.material.color.setHex(0x72bcd4);
scene.add(box); export function fetchProgress(url, { method = 'get', headers = {}, body = {} } = {}, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
if (headers) {
for (const key in headers) {
const header = headers[key];
xhr.setRequestHeader(key, header);
}
}
xhr.onload = event => resolve(event.target.responseText);
xhr.onerror = reject;
if (xhr.upload && onProgress) xhr.upload.onprogress = onProgress;
xhr.send(body);
});
}
const GCODE_SERVER_URL = 'https://gcodeserver.doodle3d.com';
const CONNECT_URL = 'http://connect.doodle3d.com/';
export async function slice(target, name, mesh, settings, printers, quality, material, updateProgress) {
if (!printers) throw new Error('Please select a printer');
let steps;
let currentStep = 0;
switch (target) {
case 'DOWNLOAD':
steps = 1;
break;
case 'WIFI':
steps = 2;
break;
default:
steps = 1;
break;
}
const { dimensions } = settings; const { dimensions } = settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x); const centerX = dimensions.x / 2;
const centerY = dimensions.y / 2;
return { control, editorControls, scene, mesh, camera, renderer, render, box, setSize }; const matrix = new Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
const { gcode } = await sliceGeometry(settings, mesh.geometry, mesh.material, matrix, false, false, ({ progress }) => {
updateProgress({
action: progress.action,
percentage: currentStep / steps + progress.done / progress.total / steps
});
});
currentStep ++;
switch (target) {
case 'DOWNLOAD': {
const blob = new File([gcode], `${name}.gcode`, { type: 'text/plain;charset=utf-8' });
fileSaver.saveAs(blob);
break;
}
case 'WIFI': {
// upload G-code file to AWS S3
const { data: { reservation, id } } = await fetch(`${GCODE_SERVER_URL}/upload`, { method: 'POST' })
.then(response => response.json());
const body = new FormData();
const { fields } = reservation;
for (const key in fields) {
body.append(key, fields[key]);
}
const file = ';' + JSON.stringify({
name: `${name}.gcode`,
...settings,
printer: {
type: printers,
title: printerSettings[printers].title
},
material: {
type: material,
title: materialSettings[material].title
},
quality: {
type: quality,
title: qualitySettings[quality].title
}
}).trim() + '\n' + gcode;
body.append('file', file);
await fetchProgress(reservation.url, { method: 'POST', body }, (progess) => {
updateProgress({
action: 'Uploading',
percentage: currentStep / steps + progess.loaded / progess.total / steps
});
});
currentStep ++;
const popup = window.open(`${CONNECT_URL}?uuid=${id}`, '_blank');
if (!popup) throw new Error('popup was blocked by browser');
}
default:
break;
}
} }
export const TabTemplate = ({ children, selected, style }) => {
const templateStyle = {
width: '100%',
position: 'relative',
textAlign: 'initial',
...style,
...(selected ? {} : {
height: 0,
width: 0,
overflow: 'hidden'
})
};
return (
<div style={templateStyle}>
{children}
</div>
);
};
TabTemplate.propTypes = {
children: PropTypes.node,
selected: PropTypes.bool,
style: PropTypes.object,
};

View File

@ -1,3 +1,4 @@
zOffset: 0.3
dimensions: dimensions:
x: 200 x: 200
y: 200 y: 200
@ -10,8 +11,8 @@ bedTemperature: 70
layerHeight: 0.15 layerHeight: 0.15
combing: true combing: true
thickness: thickness:
top: 1.2 top: 0.45
bottom: 1.2 bottom: 0.45
shell: 0.8 shell: 0.8
retraction: retraction:
enabled: true enabled: true
@ -38,7 +39,7 @@ outerShell:
innerInfill: innerInfill:
flowRate: 1.0 flowRate: 1.0
speed: 80.0 speed: 80.0
gridSize: 5.0 gridSize: 15.0
outerInfill: outerInfill:
flowRate: 1.0 flowRate: 1.0
speed: 50.0 speed: 50.0

View File

@ -1,40 +1,79 @@
_3Dison_plus: _3Dison_plus:
title: 3Dison plus title: 3Dison plus
heatedBed: false
filamentThickness: 2.85
dimensions: dimensions:
x: 227 x: 227
y: 147 y: 147
z: 150 z: 150
bigbuilder3d: bigbuilder3d:
title: Big Builder 3D title: Big Builder 3D
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 200
y: 200
z: 200
builder3d: builder3d:
title: Builder 3D title: Builder 3D
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 200
y: 200
z: 200
bukobot: bukobot:
title: Bukobot title: Bukobot
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 200
y: 200
z: 200
cartesio: cartesio:
title: Cartesio title: Cartesio
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 200
y: 200
z: 200
colido_2_0_plus: colido_2_0_plus:
title: ColiDo 2.0 Plus title: ColiDo 2.0 Plus
heatedBed: true heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 230 x: 230
y: 150 y: 150
z: 140 z: 140
colido_compact: colido_compact:
title: ColiDo Compact title: ColiDo Compact
heatedBed: false
filamentThickness: 2.85
dimensions: dimensions:
x: 130 x: 130
y: 130 y: 130
z: 115 z: 115
colido_diy: colido_diy:
title: ColiDo DIY title: ColiDo DIY
heatedBed: false
filamentThickness: 2.85
dimensions: dimensions:
x: 200
y: 200
z: 170 z: 170
colido_m2020: colido_m2020:
title: ColiDo M2020 title: ColiDo M2020
heatedBed: true heatedBed: true
filamentThickness: 2.85
dimensions:
x: 200
y: 200
z: 200
colido_x3045: colido_x3045:
title: ColiDo X3045 title: ColiDo X3045
heatedBed: true heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 300 x: 300
y: 300 y: 300
@ -42,23 +81,27 @@ colido_x3045:
craftbot_plus: craftbot_plus:
title: CraftBot PLUS title: CraftBot PLUS
heatedBed: true heatedBed: true
filamentThickness: 1.75 filamentThickness: 2.85
dimensions: dimensions:
x: 250 x: 250
y: 200
z: 200
cyrus: cyrus:
title: Cyrus title: Cyrus
heatedBed: false
dimensions:
x: 195
y: 195
z: 200
delta_rostockmax: delta_rostockmax:
title: Delta RostockMax title: Delta RostockMax
dimensions: heatedBed: false
x: 0
y: 0
deltamaker: deltamaker:
title: Deltamaker title: Deltamaker
dimensions: heatedBed: false
x: 0
y: 0
doodle_dream: doodle_dream:
title: Doodle Dream title: Doodle Dream
heatedBed: false
filamentThickness: 1.75 filamentThickness: 1.75
dimensions: dimensions:
x: 120 x: 120
@ -66,68 +109,119 @@ doodle_dream:
z: 80 z: 80
eventorbot: eventorbot:
title: EventorBot title: EventorBot
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 203
y: 250
z: 150
felix: felix:
title: Felix title: Felix
heatedBed: false
filamentThickness: 2.85
dimensions:
x: 237
y: 244
z: 235
gigabot: gigabot:
title: Gigabot title: Gigabot
heatedBed: false
filamentThickness: 2.85
kossel: kossel:
title: Kossel title: Kossel
dimensions: heatedBed: false
x: 0 filamentThickness: 2.85
y: 0
leapfrog_creatr: leapfrog_creatr:
title: LeapFrog Creatr title: LeapFrog Creatr
heatedBed: false
filamentThickness: 2.85
lulzbot_aO_101: lulzbot_aO_101:
title: LulzBot AO-101 title: LulzBot AO-101
heatedBed: false
filamentThickness: 2.85
lulzbot_taz_4: lulzbot_taz_4:
title: LulzBot TAZ 4 title: LulzBot TAZ 4
heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 298 x: 298
y: 275 y: 275
z: 250 z: 250
heatedBed: true
makerbot_generic: makerbot_generic:
title: Generic Makerbot Printer title: Generic Makerbot Printer
heatedBed: false
filamentThickness: 2.85
makerbot_replicator2: makerbot_replicator2:
title: MakerBot Replicator2 title: MakerBot Replicator2
heatedBed: false
filamentThickness: 2.85
makerbot_replicator2x: makerbot_replicator2x:
title: MakerBot Replicator2x title: MakerBot Replicator2x
heatedBed: true heatedBed: true
filamentThickness: 2.85
makerbot_thingomatic: makerbot_thingomatic:
title: MakerBot Thing-o-matic title: MakerBot Thing-o-matic
heatedBed: false
filamentThickness: 2.85
makergear_m2: makergear_m2:
title: MakerGear M2 title: MakerGear M2
heatedBed: false
filamentThickness: 2.85
makergear_prusa: makergear_prusa:
title: MakerGear Prusa title: MakerGear Prusa
heatedBed: false
filamentThickness: 2.85
makibox: makibox:
title: Makibox title: Makibox
heatedBed: false
filamentThickness: 2.85
mamba3d: mamba3d:
title: Mamba3D title: Mamba3D
heatedBed: false
filamentThickness: 2.85
marlin_generic: marlin_generic:
title: Generic Marlin Printer title: Generic Marlin Printer
heatedBed: false
filamentThickness: 2.85
minifactory: minifactory:
title: miniFactory title: miniFactory
heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 150 x: 150
y: 150 y: 150
z: 155 z: 155
heatedBed: true
orca_0_3: orca_0_3:
title: Orca 0.3 title: Orca 0.3
heatedBed: false
filamentThickness: 2.85
ord_bot_hadron: ord_bot_hadron:
title: ORD Bot Hadron title: ORD Bot Hadron
heatedBed: false
filamentThickness: 2.85
printrbot: printrbot:
title: Printrbot title: Printrbot
heatedBed: false
filamentThickness: 2.85
printxel_3d: printxel_3d:
title: Printxel 3D title: Printxel 3D
heatedBed: false
filamentThickness: 2.85
prusa_i3: prusa_i3:
title: Prusa I3 title: Prusa I3
heatedBed: false
filamentThickness: 2.85
prusa_iteration_2: prusa_iteration_2:
title: Prusa Iteration 2 title: Prusa Iteration 2
heatedBed: false
filamentThickness: 2.85
rapman: rapman:
title: RapMan title: RapMan
heatedBed: false
filamentThickness: 2.85
renkforce_rf100: renkforce_rf100:
title: Renkforce RF100 title: Renkforce RF100
heatedBed: false
filamentThickness: 1.75 filamentThickness: 1.75
dimensions: dimensions:
x: 100 x: 100
@ -135,21 +229,36 @@ renkforce_rf100:
z: 100 z: 100
reprappro_huxley: reprappro_huxley:
title: RepRapPro Huxley title: RepRapPro Huxley
heatedBed: false
filamentThickness: 2.85
reprappro_mendel: reprappro_mendel:
title: RepRapPro Mendel title: RepRapPro Mendel
heatedBed: false
filamentThickness: 2.85
rigidbot: rigidbot:
title: Rigidbot title: Rigidbot
heatedBed: false
filamentThickness: 2.85
robo_3d_printer: robo_3d_printer:
title: RoBo 3D Printer title: RoBo 3D Printer
heatedBed: false
filamentThickness: 2.85
shapercube: shapercube:
title: ShaperCube title: ShaperCube
heatedBed: false
filamentThickness: 2.85
tantillus: tantillus:
title: Tantillus title: Tantillus
heatedBed: false
filamentThickness: 2.85
ultimaker: ultimaker:
title: Ultimaker Original title: Ultimaker Original
heatedBed: false
filamentThickness: 2.85
ultimaker2: ultimaker2:
title: Ultimaker 2 title: Ultimaker 2
heatedBed: true heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 223 x: 223
y: 223 y: 223
@ -157,12 +266,15 @@ ultimaker2:
ultimaker2_plus: ultimaker2_plus:
title: Ultimaker 2+ title: Ultimaker 2+
heatedBed: true heatedBed: true
filamentThickness: 2.85
dimensions: dimensions:
x: 223 x: 223
y: 223 y: 223
z: 305 z: 305
ultimaker2go: ultimaker2go:
title: Ultimaker 2 Go title: Ultimaker 2 Go
heatedBed: false
filamentThickness: 2.85
dimensions: dimensions:
x: 120 x: 120
y: 120 y: 120
@ -170,17 +282,20 @@ ultimaker2go:
ultimaker_original_plus: ultimaker_original_plus:
title: Ultimaker Original Plus title: Ultimaker Original Plus
heatedBed: true heatedBed: true
filamentThickness: 2.85
vision_3d_printer: vision_3d_printer:
title: Vision 3D Printer title: Vision 3D Printer
heatedBed: false
filamentThickness: 2.85
wanhao_duplicator4: wanhao_duplicator4:
title: Wanhao Duplicator 4 title: Wanhao Duplicator 4
filamentThickness: 1.75
heatedBed: true heatedBed: true
filamentThickness: 1.75
dimensions: dimensions:
x: 210 x: 210
y: 140 y: 140
z: 140 z: 140
wanhao_duplicator_i3_plus: wanhao_duplicator_i3_plus:
title: Wanhao Duplicator i3 Plus title: Wanhao Duplicator i3 Plus
filamentThickness: 1.75
heatedBed: false heatedBed: false
filamentThickness: 1.75

View File

@ -1,11 +1,54 @@
low: low:
title: "Low" title: "Low"
thickness:
top: 0.30
bottom: 0.30
shell: 0.4
layerHeight: .2 layerHeight: .2
fill: innerShell:
gridSize: 15.0 speed: 80.0
outerShell:
speed: 70.0
outerInfill:
speed: 80.0
firstLayer:
speed: 70.0
innerInfill:
speed: 80.0
gridSize: 25.0
medium: medium:
title: "Medium" title: "Medium"
layerHeight: .15 layerHeight: .15
thickness:
top: 0.45
bottom: 0.45
shell: 0.8
innerShell:
speed: 50.0
outerShell:
speed: 40.0
outerInfill:
speed: 50.0
firstLayer:
speed: 40.0
innerInfill:
speed: 80.0
gridSize: 25.0
high: high:
title: "High" title: "High"
thickness:
top: 0.60
bottom: 0.60
shell: 1.2
layerHeight: .1 layerHeight: .1
innerShell:
speed: 40.0
outerShell:
speed: 30.0
outerInfill:
speed: 40.0
firstLayer:
speed: 30.0
innerInfill:
speed: 70.0
gridSize: 10.0

View File

@ -1,8 +1,9 @@
import { devide } from './helpers/VectorUtils.js';
import { PRECISION } from '../constants.js' import { PRECISION } from '../constants.js'
export default function applyPrecision(shapes) { export default function applyPrecision(layers) {
for (let i = 0; i < shapes.length; i ++) { for (let layer = 0; layer < layers.length; layer ++) {
const { fillShapes, lineShapesOpen, lineShapesClosed } = shapes[i]; const { fillShapes, lineShapesOpen, lineShapesClosed } = layers[layer];
scaleUpShape(fillShapes); scaleUpShape(fillShapes);
scaleUpShape(lineShapesOpen); scaleUpShape(lineShapesOpen);
@ -15,9 +16,7 @@ function scaleUpShape(shape) {
const path = shape[i]; const path = shape[i];
for (let i = 0; i < path.length; i ++) { for (let i = 0; i < path.length; i ++) {
const point = path[i]; path[i] = devide(path[i], PRECISION);
point.copy(point.divideScalar(PRECISION));
} }
} }
} }

View File

@ -1,30 +1,26 @@
import * as THREE from 'three';
export default function calculateLayersIntersections(lines, settings) { export default function calculateLayersIntersections(lines, settings) {
const { const {
dimensions: { z: dimensionsZ },
layerHeight, layerHeight,
dimensions: { z: dimensionsZ } zOffset
} = settings; } = settings;
const numLayers = Math.floor(dimensionsZ / layerHeight); const numLayers = Math.floor((dimensionsZ - zOffset) / layerHeight);
const layerIntersectionIndexes = Array.from(Array(numLayers)).map(() => []); const layers = Array.from(Array(numLayers)).map(() => ({
const layerIntersectionPoints = Array.from(Array(numLayers)).map(() => []); points: {},
faceIndexes: []
}));
for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) { for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
const { line, isFlat } = lines[lineIndex]; const { line, faces } = lines[lineIndex];
if (isFlat) continue; const min = Math.ceil((Math.min(line.start.y, line.end.y) - zOffset) / layerHeight);
const max = Math.floor((Math.max(line.start.y, line.end.y) - zOffset) / layerHeight);
const min = Math.ceil(Math.min(line.start.y, line.end.y) / layerHeight);
const max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight);
for (let layerIndex = min; layerIndex <= max; layerIndex ++) { for (let layerIndex = min; layerIndex <= max; layerIndex ++) {
if (layerIndex >= 0 && layerIndex < numLayers) { if (layerIndex >= 0 && layerIndex < numLayers) {
const y = layerIndex * layerHeight + zOffset;
layerIntersectionIndexes[layerIndex].push(lineIndex);
const y = layerIndex * layerHeight;
let x, z; let x, z;
if (line.start.y === line.end.y) { if (line.start.y === line.end.y) {
@ -37,10 +33,20 @@ export default function calculateLayersIntersections(lines, settings) {
z = line.end.z * alpha + line.start.z * alpha1; z = line.end.z * alpha + line.start.z * alpha1;
} }
layerIntersectionPoints[layerIndex][lineIndex] = new THREE.Vector2(z, x); layers[layerIndex].points[lineIndex] = { x: z, y: x };
layers[layerIndex].faceIndexes.push(...faces);
} }
} }
} }
return { layerIntersectionIndexes, layerIntersectionPoints }; for (let i = 0; i < layers.length; i ++) {
const layer = layers[i];
layer.faceIndexes = layer.faceIndexes.reduce((result, faceIndex) => {
if (!result.includes(faceIndex)) result.push(faceIndex);
return result;
}, []);
}
return layers;
} }

View File

@ -1,15 +1,20 @@
import * as THREE from 'three'; import { Line3 } from 'three/src/math/Line3.js';
import { normalize } from './helpers/VectorUtils.js';
function addLine(geometry, lineLookup, lines, a, b, isFlat) { function addLine(geometry, lineLookup, lines, a, b, faceIndex) {
const index = lines.length; let index;
lineLookup[`${a}_${b}`] = index; if (typeof lineLookup[`${b}_${a}`] !== 'undefined') {
index = lineLookup[`${b}_${a}`];
} else {
index = lines.length;
lineLookup[`${a}_${b}`] = index;
lines.push({ const line = new Line3(geometry.vertices[a], geometry.vertices[b]);
line: new THREE.Line3(geometry.vertices[a], geometry.vertices[b]), lines.push({ line, faces: [] });
connects: [], }
normals: [],
isFlat const { faces } = lines[index];
}); faces.push(faceIndex);
return index; return index;
} }
@ -18,31 +23,20 @@ export default function createLines(geometry, settings) {
const lines = []; const lines = [];
const lineLookup = {}; const lineLookup = {};
for (let i = 0; i < geometry.faces.length; i ++) { const faces = geometry.faces.map((face, i) => {
const face = geometry.faces[i]; const { normal, materialIndex: objectIndex, a, b, c } = geometry.faces[i];
const lookupA = lineLookup[`${face.b}_${face.a}`]; // skip faces that point up or down
const lookupB = lineLookup[`${face.c}_${face.b}`]; if (normal.y > .999 || normal.y < -.999) return;
const lookupC = lineLookup[`${face.a}_${face.c}`];
const isFlat = face.normal.y > 0.999 || face.normal.y < -0.999; const indexA = addLine(geometry, lineLookup, lines, a, b, i);
const indexB = addLine(geometry, lineLookup, lines, b, c, i);
const indexC = addLine(geometry, lineLookup, lines, c, a, i);
// only add unique lines const flatNormal = normalize({ x: normal.z, y: normal.x });
// returns index of said line const lineIndexes = [indexA, indexB, indexC];
const lineIndexA = typeof lookupA !== 'undefined' ? lookupA : addLine(geometry, lineLookup, lines, face.a, face.b, isFlat); return { lineIndexes, flatNormal, objectIndex };
const lineIndexB = typeof lookupB !== 'undefined' ? lookupB : addLine(geometry, lineLookup, lines, face.b, face.c, isFlat); });
const lineIndexC = typeof lookupC !== 'undefined' ? lookupC : addLine(geometry, lineLookup, lines, face.c, face.a, isFlat);
// set connecting lines (based on face) return { lines, faces };
lines[lineIndexA].connects.push(lineIndexB, lineIndexC);
lines[lineIndexB].connects.push(lineIndexC, lineIndexA);
lines[lineIndexC].connects.push(lineIndexA, lineIndexB);
const normal = new THREE.Vector2(face.normal.z, face.normal.x).normalize();
lines[lineIndexA].normals.push(normal);
lines[lineIndexB].normals.push(normal);
lines[lineIndexC].normals.push(normal);
}
return lines;
} }

View File

@ -1,61 +0,0 @@
export default function detectOpenClosed(lines) {
const pools = getPools(lines);
const openLines = lines.map(line => line.connects.length === 2);
for (let i = 0; i < pools.length; i ++) {
const pool = pools[i];
const isOpenGeometry = pool.some(lineIndex => openLines[lineIndex]);
for (let j = 0; j < pool.length; j ++) {
const lineIndex = pool[j];
const line = lines[lineIndex];
line.openGeometry = isOpenGeometry;
}
}
}
function findPool(pools, lines, lineIndex) {
const { connects } = lines[lineIndex];
for (let i = 0; i < pools.length; i ++) {
const pool = pools[i];
if (pool.find(lineIndex => connects.includes(lineIndex))) {
return pool;
}
}
// no pool found
// create new pool
const pool = [];
pools.push(pool);
return pool;
}
function getPools(lines) {
const pools = [];
for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
const pool = findPool(pools, lines, lineIndex);
pool.push(lineIndex);
}
for (let i = 0; i < pools.length; i ++) {
const poolA = pools[i];
for (let j = i + 1; j < pools.length; j ++) {
const poolB = pools[j];
for (let k = 0; k < poolA.length; k ++) {
const { connects } = lines[poolA[k]];
if (poolB.find(lineIndex => connects.includes(lineIndex))) {
poolA.splice(poolA.length, 0, ...poolB);
poolB.splice(0, poolB.length);
}
}
}
}
return pools.filter(pool => pool.length > 0);
}

View File

@ -1,4 +1,4 @@
import * as THREE from 'three'; import { Vector2 } from 'three/src/math/Vector2.js';
import { PRECISION } from '../../constants.js'; import { PRECISION } from '../../constants.js';
export const MOVE = 'G'; export const MOVE = 'G';
@ -16,7 +16,7 @@ export default class {
this._gcode = []; this._gcode = [];
this._currentValues = {}; this._currentValues = {};
this._nozzlePosition = new THREE.Vector2(0, 0); this._nozzlePosition = new Vector2(0, 0);
this._extruder = 0.0; this._extruder = 0.0;
this._duration = 0.0; this._duration = 0.0;
this._isRetracted = false; this._isRetracted = false;
@ -47,7 +47,7 @@ export default class {
} }
moveTo(x, y, z, { speed }) { moveTo(x, y, z, { speed }) {
const newNozzlePosition = new THREE.Vector2(x, y).multiplyScalar(PRECISION); const newNozzlePosition = new Vector2(x, y).multiplyScalar(PRECISION);
const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition); const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition);
this._duration += lineLength / speed; this._duration += lineLength / speed;
@ -66,7 +66,7 @@ export default class {
} }
lineTo(x, y, z, { speed, flowRate }) { lineTo(x, y, z, { speed, flowRate }) {
const newNozzlePosition = new THREE.Vector2(x, y).multiplyScalar(PRECISION); const newNozzlePosition = new Vector2(x, y).multiplyScalar(PRECISION);
const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition); const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition);
this._extruder += this._nozzleToFilamentRatio * lineLength * flowRate; this._extruder += this._nozzleToFilamentRatio * lineLength * flowRate;

View File

@ -10,18 +10,23 @@ export const scale = (a, factor) => ({
x: a.x * factor, x: a.x * factor,
y: a.y * factor y: a.y * factor
}); });
export const devide = (a, factor) => ({
x: a.x / factor,
y: a.y / factor
});
export const normal = (a) => ({ export const normal = (a) => ({
x: -a.y, x: -a.y,
y: a.x y: a.x
}); });
export const dot = (a, b) => a.x * b.x + a.y * b.y; export const dot = (a, b) => a.x * b.x + a.y * b.y;
export const length = (a) => Math.sqrt(a.x * a.x + a.y * a.y); export const length = (v) => Math.sqrt(v.x * v.x + v.y * v.y);
export const distanceTo = (a, b) => length(subtract(a, b)); export const distanceTo = (a, b) => length(subtract(a, b));
export const normalize = (a) => { export const normalize = (v) => {
const l = length(a); const l = length(v);
return { return {
x: a.x / l, x: v.x / l,
y: a.y / l y: v.y / l
}; };
} }
export const clone = (v) => ({ x: v.x, y: v.y });

View File

@ -1,120 +1,136 @@
import * as THREE from 'three'; import { subtract, normal, normalize, dot, distanceTo, clone } from './helpers/VectorUtils.js';
import Shape from 'clipper-js';
export default function intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings) { export default function intersectionsToShapes(intersectionLayers, faces, openObjectIndexes, settings) {
const layers = []; const layers = [];
for (let layer = 1; layer < layerIntersectionIndexes.length; layer ++) { for (let layer = 0; layer < intersectionLayers.length; layer ++) {
const intersectionIndexes = layerIntersectionIndexes[layer];
const intersectionPoints = layerIntersectionPoints[layer];
if (intersectionIndexes.length === 0) continue;
const fillShapes = []; const fillShapes = [];
const lineShapesOpen = []; const lineShapesOpen = [];
const lineShapesClosed = []; const lineShapesClosed = [];
for (let i = 0; i < intersectionIndexes.length; i ++) {
let index = intersectionIndexes[i];
if (typeof intersectionPoints[index] === 'undefined') continue; const { points, faceIndexes } = intersectionLayers[layer];
const shape = []; if (faceIndexes.length === 0) continue;
const firstPoints = [index]; const shapes = {};
const { openGeometry } = lines[index];
let isFirstPoint = true;
let openShape = true;
while (index !== -1) { for (let i = 0; i < faceIndexes.length; i ++) {
const intersection = intersectionPoints[index]; const { lineIndexes, objectIndex, flatNormal } = faces[faceIndexes[i]];
// uppercase X and Y because clipper vector
shape.push(intersection);
delete intersectionPoints[index]; const a = points[lineIndexes[0]];
const b = points[lineIndexes[1]];
const c = points[lineIndexes[2]];
const connects = lines[index].connects; const lineSegment = [];
const faceNormals = lines[index].normals; if (a && b) {
lineSegment.push(a, b);
} else if (b && c) {
lineSegment.push(b, c);
} else if (c && a) {
lineSegment.push(c, a);
} else {
continue;
}
for (let i = 0; i < connects.length; i ++) { const segmentNormal = normalize(normal(subtract(lineSegment[1], lineSegment[0])));
index = connects[i]; if (dot(segmentNormal, flatNormal) < 0) lineSegment.reverse();
if (firstPoints.includes(index) && shape.length > 2) { if (!shapes[objectIndex]) shapes[objectIndex] = { lineSegments: [] };
openShape = false; const shape = shapes[objectIndex];
index = -1;
break; shape.lineSegments.push(lineSegment)
}
for (const objectIndex in shapes) {
const shape = shapes[objectIndex];
const openShape = openObjectIndexes[objectIndex];
const lines = [shape.lineSegments.pop()];
loop: while (shape.lineSegments.length !== 0) {
for (let i = 0; i < lines.length; i ++) {
const line = lines[i];
const lastPoint = line[line.length - 1];
let closestSegmentEnd;
let endHit = false;
const distanceEnd = new WeakMap();
for (let i = 0; i < shape.lineSegments.length; i ++) {
const lineSegment = shape.lineSegments[i];
if (lastPoint === lineSegment[0]) {
closestSegmentEnd = lineSegment;
endHit = true;
break;
}
const distance = distanceTo(lastPoint, lineSegment[0]);
distanceEnd.set(lineSegment, distance);
} }
// Check if index has an intersection or is already used if (!endHit) {
if (typeof intersectionPoints[index] !== 'undefined') { closestSegmentEnd = shape.lineSegments.sort((a, b) => {
const faceNormal = faceNormals[Math.floor(i / 2)]; const distanceA = distanceEnd.get(a);
const distanceB = distanceEnd.get(b);
if (distanceA === distanceB) return distanceTo(a[0], a[1]) - distanceTo(b[0], b[1]);
return distanceA - distanceB;
})[0];
const a = new THREE.Vector2(intersection.x, intersection.y); if (distanceTo(closestSegmentEnd[0], lastPoint) < .001) endHit = true;
const b = new THREE.Vector2(intersectionPoints[index].x, intersectionPoints[index].y); }
// can't calculate normal between points if distance is smaller as 0.0001 if (endHit) {
if ((faceNormal.x === 0 && faceNormal.y === 0) || a.distanceTo(b) < 0.0001) { shape.lineSegments.splice(shape.lineSegments.indexOf(closestSegmentEnd), 1);
if (isFirstPoint) { line.splice(line.length, 0, closestSegmentEnd[1]);
firstPoints.push(index); continue loop;
} }
delete intersectionPoints[index]; const firstPoint = line[0];
connects.push(...lines[index].connects); let closestSegmentStart;
faceNormals.push(...lines[index].normals); let hitStart = false;
index = -1; const distanceStart = new WeakMap();
} else { for (let i = 0; i < shape.lineSegments.length; i ++) {
// make sure the path goes the right direction const lineSegment = shape.lineSegments[i];
// THREE.Vector2.normal is not yet implimented if (firstPoint === lineSegment[1]) {
// const normal = a.sub(b).normal().normalize(); closestSegmentStart = lineSegment;
const normal = a.sub(b); hitStart = true;
normal.set(-normal.y, normal.x).normalize(); break;
if (normal.dot(faceNormal) > 0) {
break;
} else {
index = -1;
}
} }
} else { const distance = distanceTo(firstPoint, lineSegment[1]);
index = -1; distanceStart.set(lineSegment, distance);
}
if (!hitStart) {
closestSegmentStart = shape.lineSegments.sort((a, b) => {
const distanceA = distanceStart.get(a);
const distanceB = distanceStart.get(b);
if (distanceA === distanceB) return distanceTo(a[0], a[1]) - distanceTo(b[0], b[1]);
return distanceA - distanceB;
})[0];
if (distanceTo(closestSegmentStart[1], firstPoint) < .001) hitStart = true;
}
if (hitStart) {
shape.lineSegments.splice(shape.lineSegments.indexOf(closestSegmentStart), 1);
line.splice(0, 0, closestSegmentStart[0]);
continue loop;
} }
} }
isFirstPoint = false; lines.push(shape.lineSegments.pop());
} }
if (openShape) { if (openShape) {
index = firstPoints[0]; for (const line of lines) {
const closed = distanceTo(line[0], line[line.length - 1]) < .001;
while (index !== -1) { if (closed) {
if (!firstPoints.includes(index)) { lineShapesClosed.push(line);
const intersection = intersectionPoints[index]; } else {
shape.unshift(intersection); lineShapesOpen.push(line);
delete intersectionPoints[index];
} }
const connects = lines[index].connects;
for (let i = 0; i < connects.length; i ++) {
index = connects[i];
if (typeof intersectionPoints[index] !== 'undefined') {
break;
} else {
index = -1;
}
}
}
}
if (openGeometry) {
if (openShape) {
lineShapesOpen.push(shape);
} else {
lineShapesClosed.push(shape);
} }
} else { } else {
fillShapes.push(shape); fillShapes.push(...lines);
} }
} }

View File

@ -1,8 +1,8 @@
import * as THREE from 'three'; import { Vector2 } from 'three/src/math/Vector2.js';
import Shape from 'clipper-js'; import Shape from 'clipper-js';
export default function optimizePaths(slices, settings) { export default function optimizePaths(slices, settings) {
const start = new THREE.Vector2(0, 0); const start = new Vector2(0, 0);
for (let layer = 0; layer < slices.length; layer ++) { for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer]; const slice = slices[layer];
@ -102,7 +102,7 @@ function optimizeShape(shape, start) {
if (shape.closed) { if (shape.closed) {
for (let j = 0; j < path.length; j += 1) { for (let j = 0; j < path.length; j += 1) {
const point = new THREE.Vector2().copy(path[j]); const point = new Vector2().copy(path[j]);
const length = point.sub(start).length(); const length = point.sub(start).length();
if (minLength === false || length < minLength) { if (minLength === false || length < minLength) {
minPath = path; minPath = path;
@ -112,7 +112,7 @@ function optimizeShape(shape, start) {
} }
} }
} else { } else {
const startPoint = new THREE.Vector2().copy(path[0]); const startPoint = new Vector2().copy(path[0]);
const lengthToStart = startPoint.sub(start).length(); const lengthToStart = startPoint.sub(start).length();
if (minLength === false || lengthToStart < minLength) { if (minLength === false || lengthToStart < minLength) {
minPath = path; minPath = path;
@ -121,7 +121,7 @@ function optimizeShape(shape, start) {
pathIndex = i; pathIndex = i;
} }
const endPoint = new THREE.Vector2().copy(path[path.length - 1]); const endPoint = new Vector2().copy(path[path.length - 1]);
const lengthToEnd = endPoint.sub(start).length(); const lengthToEnd = endPoint.sub(start).length();
if (lengthToEnd < minLength) { if (lengthToEnd < minLength) {
minPath = path; minPath = path;

View File

@ -1,4 +1,9 @@
import * as THREE from 'three'; import { Color } from 'three/src/math/Color.js';
import { BufferGeometry } from 'three/src/core/BufferGeometry.js';
import { BufferAttribute } from 'three/src/core/BufferAttribute.js';
import { LineBasicMaterial } from 'three/src/materials/LineBasicMaterial.js';
import { VertexColors } from 'three/src/constants.js';
import { LineSegments } from 'three/src/objects/LineSegments.js';
import calculateLayersIntersections from './calculateLayersIntersections.js'; import calculateLayersIntersections from './calculateLayersIntersections.js';
import createLines from './createLines.js'; import createLines from './createLines.js';
import generateInfills from './generateInfills.js'; import generateInfills from './generateInfills.js';
@ -10,12 +15,11 @@ import addBrim from './addBrim.js';
import optimizePaths from './optimizePaths.js'; import optimizePaths from './optimizePaths.js';
import shapesToSlices from './shapesToSlices.js'; import shapesToSlices from './shapesToSlices.js';
import slicesToGCode from './slicesToGCode.js'; import slicesToGCode from './slicesToGCode.js';
import detectOpenClosed from './detectOpenClosed.js';
import applyPrecision from './applyPrecision.js'; import applyPrecision from './applyPrecision.js';
// import removePrecision from './removePrecision.js'; // // import removePrecision from './removePrecision.js';
export default function(settings, geometry, constructLinePreview, onProgress) { export default function(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) {
const totalStages = 12; const totalStages = 11;
let current = -1; let current = -1;
const updateProgress = (action) => { const updateProgress = (action) => {
current ++; current ++;
@ -30,23 +34,14 @@ export default function(settings, geometry, constructLinePreview, onProgress) {
} }
}; };
geometry.computeFaceNormals();
// get unique lines from geometry;
updateProgress('Constructing unique lines from geometry'); updateProgress('Constructing unique lines from geometry');
const lines = createLines(geometry, settings); const { lines, faces } = createLines(geometry, settings);
updateProgress('Detecting open vs closed shapes');
detectOpenClosed(lines);
updateProgress('Calculating layer intersections'); updateProgress('Calculating layer intersections');
const { const layers = calculateLayersIntersections(lines, settings);
layerIntersectionIndexes,
layerIntersectionPoints
} = calculateLayersIntersections(lines, settings);
updateProgress('Constructing shapes from intersections'); updateProgress('Constructing shapes from intersections');
const shapes = intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings); const shapes = intersectionsToShapes(layers, faces, openObjectIndexes, settings);
applyPrecision(shapes); applyPrecision(shapes);
@ -86,7 +81,7 @@ function gcodeToString(gcode) {
const value = command[action]; const value = command[action];
const currentValue = currentValues[action]; const currentValue = currentValues[action];
if (first) { if (first) {
string += action + value; string += `${action}${value}`;
first = false; first = false;
} else if (currentValue !== value) { } else if (currentValue !== value) {
string += ` ${action}${value}`; string += ` ${action}${value}`;
@ -99,41 +94,35 @@ function gcodeToString(gcode) {
} }
const MAX_SPEED = 100 * 60; const MAX_SPEED = 100 * 60;
const COLOR = new Color();
function createGcodeGeometry(gcode) { function createGcodeGeometry(gcode) {
const positions = []; const positions = [];
const colors = []; const colors = [];
let lastPoint let lastPoint = [0, 0, 0];
for (let i = 0; i < gcode.length; i ++) { for (let i = 0; i < gcode.length; i ++) {
const { G, F, X, Y, Z } = gcode[i]; const { G, F, X, Y, Z } = gcode[i];
if (X || Y || Z) { if (X || Y || Z) {
let color;
if (G === 0) {
color = new THREE.Color(0x00ff00);
} else if (G === 1) {
color = new THREE.Color().setHSL(F / MAX_SPEED, 0.5, 0.5);
}
if (G === 1) { if (G === 1) {
if (lastPoint) positions.push(lastPoint[0], lastPoint[1], lastPoint[2]); positions.push(lastPoint.Y, lastPoint.Z, lastPoint.X);
positions.push(Y, Z, X); positions.push(Y, Z, X);
const color = (G === 0) ? COLOR.setHex(0x00ff00) : COLOR.setHSL(F / MAX_SPEED, 0.5, 0.5);
colors.push(color.r, color.g, color.b); colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b); colors.push(color.r, color.g, color.b);
} }
lastPoint = { X, Y, Z };
lastPoint = [Y, Z, X];
} }
} }
const geometry = new THREE.BufferGeometry(); const geometry = new BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3)); geometry.addAttribute('position', new BufferAttribute(new Float32Array(positions), 3));
geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3)); geometry.addAttribute('color', new BufferAttribute(new Float32Array(colors), 3));
const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors }); const material = new LineBasicMaterial({ vertexColors: VertexColors });
const linePreview = new THREE.LineSegments(geometry, material); const linePreview = new LineSegments(geometry, material);
return linePreview; return linePreview;
} }

View File

@ -12,7 +12,8 @@ export default function slicesToGCode(slices, settings) {
travelSpeed, travelSpeed,
retraction, retraction,
travel, travel,
combing combing,
zOffset
} = settings; } = settings;
const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI; const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI;
@ -29,7 +30,7 @@ export default function slicesToGCode(slices, settings) {
let isFirstLayer = true; let isFirstLayer = true;
for (let layer = 0; layer < slices.length; layer ++) { for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer]; const slice = slices[layer];
const z = layer * layerHeight + 0.2; const z = layer * layerHeight + zOffset;
if (layer === 1) { if (layer === 1) {
gcode.turnFanOn(); gcode.turnFanOn();

View File

@ -1,6 +1,11 @@
import * as THREE from 'three'; import { VertexColors } from 'three/src/constants.js';
import { BufferAttribute } from 'three/src/core/BufferAttribute.js';
import { LineBasicMaterial } from 'three/src/materials/LineBasicMaterial.js';
import { LineSegments } from 'three/src/objects/LineSegments.js';
import slice from './sliceActions/slice.js'; import slice from './sliceActions/slice.js';
import SlicerWorker from './slicer.worker.js'; import SlicerWorker from './slicer.worker.js';
import { FrontSide, DoubleSide } from 'three/src/constants.js';
import { BufferGeometry } from 'three/src/core/BufferGeometry.js'
export function sliceMesh(settings, mesh, sync = false, constructLinePreview = false, onProgress) { export function sliceMesh(settings, mesh, sync = false, constructLinePreview = false, onProgress) {
if (!mesh || !mesh.isMesh) { if (!mesh || !mesh.isMesh) {
@ -8,15 +13,15 @@ export function sliceMesh(settings, mesh, sync = false, constructLinePreview = f
} }
mesh.updateMatrix(); mesh.updateMatrix();
const { geometry, matrix } = mesh; const { geometry, matrix, material } = mesh;
return sliceGeometry(settings, geometry, matrix, sync, onProgress); return sliceGeometry(settings, geometry, material, matrix, sync, constructLinePreview, onProgress);
} }
export function sliceGeometry(settings, geometry, matrix, sync = false, constructLinePreview = false, onProgress) { export function sliceGeometry(settings, geometry, materials, matrix, sync = false, constructLinePreview = false, onProgress) {
if (!geometry) { if (!geometry) {
throw new Error('Missing required geometry argument'); throw new Error('Missing required geometry argument');
} else if (geometry.isBufferGeometry) { } else if (geometry.isBufferGeometry) {
geometry = new THREE.Geometry().fromBufferGeometry(geometry); geometry = new Geometry().fromBufferGeometry(geometry);
} else if (geometry.isGeometry) { } else if (geometry.isGeometry) {
geometry = geometry.clone(); geometry = geometry.clone();
} else { } else {
@ -31,18 +36,29 @@ export function sliceGeometry(settings, geometry, matrix, sync = false, construc
geometry.applyMatrix(matrix); geometry.applyMatrix(matrix);
} }
const openObjectIndexes = materials instanceof Array ? materials.map(({ side }) => {
switch (side) {
case FrontSide:
return false;
case DoubleSide:
return true;
default:
return false;
}
}) : [false];
if (sync) { if (sync) {
return sliceSync(settings, geometry, constructLinePreview, onProgress); return sliceSync(settings, geometry, openObjectIndexes, constructLinePreview, onProgress);
} else { } else {
return sliceAsync(settings, geometry, constructLinePreview, onProgress); return sliceAsync(settings, geometry, openObjectIndexes, constructLinePreview, onProgress);
} }
} }
function sliceSync(settings, geometry, constructLinePreview, onProgress) { function sliceSync(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) {
return slice(settings, geometry, constructLinePreview, onProgress); return slice(settings, geometry, openObjectIndexes, constructLinePreview, onProgress);
} }
function sliceAsync(settings, geometry, constructLinePreview, onProgress) { function sliceAsync(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// create the slicer worker // create the slicer worker
const slicerWorker = new SlicerWorker(); const slicerWorker = new SlicerWorker();
@ -60,14 +76,14 @@ function sliceAsync(settings, geometry, constructLinePreview, onProgress) {
slicerWorker.terminate(); slicerWorker.terminate();
if (data.gcode.linePreview) { if (data.gcode.linePreview) {
const geometry = new THREE.BufferGeometry(); const geometry = new BufferGeometry();
const { position, color } = data.gcode.linePreview; const { position, color } = data.gcode.linePreview;
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(position), 3)); geometry.addAttribute('position', new BufferAttribute(new Float32Array(position), 3));
geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(color), 3)); geometry.addAttribute('color', new BufferAttribute(new Float32Array(color), 3));
const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors }); const material = new LineBasicMaterial({ vertexColors: VertexColors });
const linePreview = new THREE.LineSegments(geometry, material); const linePreview = new LineSegments(geometry, material);
data.gcode.linePreview = linePreview; data.gcode.linePreview = linePreview;
} }
@ -88,11 +104,7 @@ function sliceAsync(settings, geometry, constructLinePreview, onProgress) {
geometry = geometry.toJSON(); geometry = geometry.toJSON();
slicerWorker.postMessage({ slicerWorker.postMessage({
message: 'SLICE', message: 'SLICE',
data: { data: { settings, geometry, openObjectIndexes, constructLinePreview }
settings,
geometry,
constructLinePreview
}
}); });
}); });
} }

View File

@ -1,8 +1,7 @@
import 'core-js'; // polyfills import 'core-js'; // polyfills
import slice from './sliceActions/slice.js'; import slice from './sliceActions/slice.js';
import * as THREE from 'three'; import { Matrix4 } from 'three/src/math/Matrix4.js';
import { JSONLoader } from 'three/src/loaders/JSONLoader.js';
const loader = new THREE.JSONLoader();
const onProgress = progress => { const onProgress = progress => {
self.postMessage({ self.postMessage({
@ -11,16 +10,18 @@ const onProgress = progress => {
}); });
} }
self.addEventListener('message', (event) => { const loader = new JSONLoader();
self.addEventListener('message', async (event) => {
const { message, data } = event.data; const { message, data } = event.data;
switch (message) { switch (message) {
case 'SLICE': { case 'SLICE': {
const buffers = []; const { settings, geometry: JSONGeometry, constructLinePreview, openObjectIndexes } = data;
const { settings, geometry: JSONGeometry, constructLinePreview } = data;
const { geometry } = loader.parse(JSONGeometry.data); const { geometry } = loader.parse(JSONGeometry.data);
const gcode = slice(settings, geometry, constructLinePreview, onProgress); const gcode = slice(settings, geometry, openObjectIndexes, constructLinePreview, onProgress);
const buffers = [];
if (gcode.linePreview) { if (gcode.linePreview) {
const position = gcode.linePreview.geometry.getAttribute('position').array; const position = gcode.linePreview.geometry.getAttribute('position').array;
const color = gcode.linePreview.geometry.getAttribute('color').array; const color = gcode.linePreview.geometry.getAttribute('color').array;

View File

@ -2,17 +2,21 @@ const path = require('path');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HTMLWebpackPlugin = require('html-webpack-plugin'); const HTMLWebpackPlugin = require('html-webpack-plugin');
const devMode = true;
const babelLoader = { const babelLoader = {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
require('babel-preset-env'), require('babel-preset-env'),
require('babel-preset-stage-0'),
require('babel-preset-react') require('babel-preset-react')
], ],
plugins: [ plugins: [
require('babel-plugin-transform-object-rest-spread'),
require('babel-plugin-transform-class-properties'), require('babel-plugin-transform-class-properties'),
require('babel-plugin-transform-runtime') require('babel-plugin-transform-object-rest-spread'),
require('babel-plugin-transform-runtime'),
require('babel-plugin-transform-es2015-classes')
], ],
babelrc: false babelrc: false
} }
@ -26,13 +30,15 @@ module.exports = {
}, },
resolve: { resolve: {
alias: { alias: {
'doodle3d-slicer': path.resolve(__dirname, '../src/index.js'), 'doodle3d-slicer': path.resolve(__dirname, 'src/'),
'clipper-lib': '@doodle3d/clipper-lib', 'clipper-lib': '@doodle3d/clipper-lib',
'clipper-js': '@doodle3d/clipper-js' 'clipper-js': '@doodle3d/clipper-js',
'doodle3d-core': `@doodle3d/doodle3d-core/${devMode ? 'module' : 'lib'}`,
'cal': '@doodle3d/cal'
} }
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
@ -46,6 +52,12 @@ module.exports = {
}, { }, {
test: /\.worker\.js$/, test: /\.worker\.js$/,
use: ['worker-loader', babelLoader] use: ['worker-loader', babelLoader]
}, {
test: /\.(png|jpg|gif)$/,
use: ['url-loader?name=images/[name].[ext]']
}, {
test: /\.glsl$/,
use: ['raw-loader']
} }
] ]
}, },