mirror of
https://github.com/Doodle3D/Doodle3D-Slicer.git
synced 2025-03-14 08:31:43 +01:00
Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
41be8dcfd1 | ||
|
a54a716d6d | ||
|
c472739baa | ||
|
8e01a5c2e2 | ||
|
06efd35119 | ||
|
3b440b346a | ||
|
8015cd780d | ||
|
01f40eb5f8 | ||
|
49b4a2be59 | ||
|
7df13f6db0 | ||
|
766cadcd44 | ||
|
cbc439b4dc | ||
|
7da0b2fa17 | ||
|
5cb91e6c5a | ||
|
92593cef14 | ||
|
ebbc985d67 | ||
|
246f1627c5 | ||
|
6971c3c4b5 | ||
|
fc3dc7355c | ||
|
3944202a83 | ||
|
fd6e5cebbd | ||
|
ead6be081f | ||
|
d63754dbd0 | ||
|
2044929808 | ||
|
7dceeda291 | ||
|
86eed64255 | ||
|
caf36a5505 | ||
|
1493ae3536 | ||
|
5900bbcc50 | ||
|
2c953496f7 | ||
|
c642375295 | ||
|
942addf8d7 | ||
|
b7269da172 | ||
|
50ff72a037 | ||
|
60fb966ccb | ||
|
0f70855989 | ||
|
e966bc89b2 | ||
|
6c8b8e9d44 | ||
|
79e4acd3d1 | ||
|
59681e8023 | ||
|
f109147e62 | ||
|
0119c91001 | ||
|
8467da3894 | ||
|
808d585f2a | ||
|
0804dd5282 | ||
|
0957d53b41 | ||
|
0be1ee6d51 | ||
|
5a40c7c647 | ||
|
141c38d878 | ||
|
97cb6e062d | ||
|
264ea9ff00 | ||
|
374fc4a32e | ||
|
396502948b | ||
|
a0b9cd1306 | ||
|
75c3e9d632 |
33
.eslintrc
Normal file
33
.eslintrc
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"extends": "eslint-config-airbnb",
|
||||
"parser": "babel-eslint",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"modules": true,
|
||||
"jsx": true
|
||||
},
|
||||
"rules": {
|
||||
"comma-dangle": [1, "never"],
|
||||
"no-else-return": 0,
|
||||
"no-use-before-define": [2, "nofunc"],
|
||||
"no-param-reassign": 0,
|
||||
"no-var": 1,
|
||||
"no-labels": 0,
|
||||
"guard-for-in": 0,
|
||||
"prefer-const": 0,
|
||||
"no-unused-vars": 1,
|
||||
"key-spacing": [1, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
|
||||
"no-loop-func": 1,
|
||||
"react/sort-comp": [0],
|
||||
"max-len": [1, 110, 4],
|
||||
"camelcase": 1,
|
||||
"new-cap": 0
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"THREE": false
|
||||
}
|
||||
}
|
97
comb.js
Normal file
97
comb.js
Normal file
@ -0,0 +1,97 @@
|
||||
import comb from './src/sliceActions/helpers/comb.js';
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
document.body.appendChild(canvas);
|
||||
canvas.width = 800;
|
||||
canvas.height = 800;
|
||||
const context = canvas.getContext('2d');
|
||||
context.lineJoin = 'bevel';
|
||||
|
||||
function circle(radius = 10, x = 0, y = 0, clockWise = true, segments = 40) {
|
||||
const shape = [];
|
||||
|
||||
for (let rad = 0; rad < Math.PI * 2; rad += Math.PI * 2 / segments) {
|
||||
if (clockWise) {
|
||||
shape.push({ x: Math.cos(rad) * radius + x, y: Math.sin(rad) * radius + y });
|
||||
} else {
|
||||
shape.push({ x: Math.cos(rad) * radius + x, y: -Math.sin(rad) * radius + y });
|
||||
}
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
const START = { x: 200, y: 400 };
|
||||
const END = { x: 400, y: 300 };
|
||||
|
||||
const POLYGON = [[
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 600, y: 10 },
|
||||
{ x: 500, y: 200 },
|
||||
{ x: 600, y: 600 },
|
||||
{ x: 10, y: 600 }
|
||||
], [
|
||||
{ x: 160, y: 120 },
|
||||
{ x: 120, y: 400 },
|
||||
{ x: 400, y: 400 }
|
||||
]];
|
||||
// const POLYGON = [
|
||||
// circle(300, 305, 305, true, 4),
|
||||
// circle(40, 305, 105, false, 4),
|
||||
// circle(40, 305, 205, false, 4),
|
||||
// circle(40, 305, 305, false, 4),
|
||||
// circle(40, 305, 405, false, 4),
|
||||
// circle(40, 305, 505, false, 4)
|
||||
// ];
|
||||
|
||||
canvas.onmousedown = (event) => {
|
||||
START.x = event.offsetX;
|
||||
START.y = event.offsetY;
|
||||
compute();
|
||||
};
|
||||
canvas.onmousemove = (event) => {
|
||||
END.x = event.offsetX;
|
||||
END.y = event.offsetY;
|
||||
compute();
|
||||
};
|
||||
compute();
|
||||
|
||||
function compute() {
|
||||
const path = comb(POLYGON, START, END);
|
||||
|
||||
// draw
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
context.beginPath();
|
||||
for (const shape of POLYGON) {
|
||||
let first = true;
|
||||
for (const { x, y } of shape) {
|
||||
if (first) {
|
||||
context.moveTo(x, y);
|
||||
} else {
|
||||
context.lineTo(x, y);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
context.closePath();
|
||||
context.fillStyle = 'lightgray';
|
||||
context.fill();
|
||||
|
||||
context.beginPath();
|
||||
for (const { x, y } of path) {
|
||||
context.lineTo(x, y);
|
||||
}
|
||||
context.lineWidth = 2;
|
||||
context.stroke();
|
||||
|
||||
context.beginPath();
|
||||
context.arc(START.x, START.y, 3, 0, Math.PI * 2);
|
||||
context.fillStyle = 'blue';
|
||||
context.fill();
|
||||
|
||||
context.beginPath();
|
||||
context.arc(END.x, END.y, 3, 0, Math.PI * 2);
|
||||
context.fillStyle = 'red';
|
||||
context.fill();
|
||||
}
|
BIN
data/bunny.stl
Normal file
BIN
data/bunny.stl
Normal file
Binary file not shown.
27
index.js
27
index.js
@ -1,15 +1,18 @@
|
||||
import 'babel-polyfill'
|
||||
import 'babel-polyfill';
|
||||
import React from 'react';
|
||||
import { Interface } from 'doodle3d-slicer';
|
||||
import { Interface } from './src/index.js';
|
||||
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 queryString from 'query-string';
|
||||
import getMuiTheme from 'material-ui/styles/getMuiTheme';
|
||||
import { grey400, blue500, blue700 } from 'material-ui/styles/colors';
|
||||
import bunny_url from './data/bunny.stl';
|
||||
import * as THREE from 'three';
|
||||
import 'three/examples/js/loaders/STLLoader.js';
|
||||
import fileSaver from 'file-saver';
|
||||
|
||||
const muiTheme = getMuiTheme({
|
||||
palette: {
|
||||
@ -19,8 +22,6 @@ const muiTheme = getMuiTheme({
|
||||
}
|
||||
});
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
jss.setup(preset());
|
||||
jss.createStyleSheet(normalize).attach();
|
||||
jss.createStyleSheet({
|
||||
@ -32,11 +33,15 @@ jss.createStyleSheet({
|
||||
}
|
||||
}).attach();
|
||||
|
||||
let { file, selectedPrinter, actions } = queryString.parse(location.search);
|
||||
if (actions) actions = JSON.parse(actions);
|
||||
|
||||
render((
|
||||
new THREE.STLLoader().load(bunny_url, geometry => {
|
||||
const material = new THREE.MeshPhongMaterial({ color: 0xff5533, specular: 0x111111, shininess: 200 });
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
render((
|
||||
<MuiThemeProvider muiTheme={muiTheme}>
|
||||
<Interface actions={actions} fileUrl={file} selectedPrinter={selectedPrinter} name="doodle"/>
|
||||
<Interface
|
||||
mesh={mesh}
|
||||
onSliceSucces={({ gcode }) => fileSaver.saveAs(gcode, 'bunny.gcode')}
|
||||
/>
|
||||
</MuiThemeProvider>
|
||||
), document.getElementById('app'));
|
||||
), document.getElementById('app'));
|
||||
});
|
||||
|
24159
package-lock.json
generated
24159
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "@doodle3d/doodle3d-slicer",
|
||||
"version": "0.0.18",
|
||||
"description": "JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box # Usage",
|
||||
"description": "JavaScript gcode slicer for Doodle3D Transform",
|
||||
"main": "lib/index.js",
|
||||
"module": "module/index.js",
|
||||
"esnext": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server -w",
|
||||
"dist": "NODE_ENV=production webpack -p",
|
||||
"lint": "eslint src",
|
||||
"prepare": "npm run build",
|
||||
"upload": "npm run dist && scp -r dist/* doodle3d.com:/domains/doodle3d.com/print",
|
||||
"analyze": "NODE_ENV=production ANALYZE_BUNDLE=true webpack -p",
|
||||
@ -19,16 +20,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@doodle3d/clipper-js": "^1.0.10",
|
||||
"@doodle3d/doodle3d-api": "^1.0.5",
|
||||
"@doodle3d/doodle3d-core": "github:doodle3d/doodle3d-core#0.18.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"file-saver": "^1.3.3",
|
||||
"lodash": "^4.17.4",
|
||||
"material-ui": "^0.19.4",
|
||||
"material-ui-icons": "^1.0.0-beta.17",
|
||||
"material-ui-textfield-icon": "^0.2.2-1",
|
||||
"proptypes": "^1.1.0",
|
||||
"query-string": "^5.0.1",
|
||||
"react": "^16.0.0",
|
||||
"react-addons-update": "^15.6.2",
|
||||
"react-dom": "^16.0.0",
|
||||
@ -36,12 +32,14 @@
|
||||
"react-resize-detector": "^1.1.0",
|
||||
"shortid": "^2.2.8",
|
||||
"three": "^0.88.0",
|
||||
"validate-ip": "^1.0.1",
|
||||
"webpack-bundle-analyzer": "^2.9.2"
|
||||
"validate-ip": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"file-saver": "^1.3.3",
|
||||
"babel-cli": "6.24.1",
|
||||
"babel-eslint": "^5.0.4",
|
||||
"babel-loader": "7.0.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-es2015-classes": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
@ -51,15 +49,20 @@
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"eslint": "^1.10.3",
|
||||
"eslint-config-airbnb": "^3.1.0",
|
||||
"eslint-plugin-react": "^3.16.1",
|
||||
"file-loader": "^1.1.11",
|
||||
"html-webpack-plugin": "^2.29.0",
|
||||
"html-webpack-template": "^6.0.2",
|
||||
"query-string": "^5.0.1",
|
||||
"image-webpack-loader": "^4.2.0",
|
||||
"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": "^3.8.1",
|
||||
"webpack-bundle-analyzer": "^2.9.2",
|
||||
"webpack-dev-server": "^2.5.1",
|
||||
"worker-loader": "^0.8.1",
|
||||
"yml-loader": "^2.1.0"
|
||||
@ -68,9 +71,9 @@
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Doodle3D/Doodle3D-Slicer.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"author": "Casper @Doodle3D",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"bugs": {
|
||||
"url": "https://github.com/Doodle3D/Doodle3D-Slicer/issues"
|
||||
},
|
||||
|
65
src/interface/Accordion.js
Normal file
65
src/interface/Accordion.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'proptypes';
|
||||
import injectSheet from 'react-jss';
|
||||
import ExpandIcon from 'material-ui-icons/ExpandMore';
|
||||
|
||||
const styles = {
|
||||
button: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
body: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
closed: {
|
||||
maxHeight: '0px'
|
||||
},
|
||||
title: {
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end'
|
||||
}
|
||||
};
|
||||
|
||||
class Accordion extends React.Component {
|
||||
static propTypes = {
|
||||
elements: PropTypes.arrayOf(PropTypes.shape({ body: PropTypes.node, title: PropTypes.string })),
|
||||
classes: PropTypes.objectOf(PropTypes.string)
|
||||
};
|
||||
static defaultProps: {
|
||||
elements: []
|
||||
};
|
||||
|
||||
state = {
|
||||
openAccordion: null
|
||||
};
|
||||
|
||||
changeAccordion = (name) => {
|
||||
const { openAccordion } = this.state;
|
||||
if (openAccordion === name) {
|
||||
this.setState({ openAccordion: null });
|
||||
} else {
|
||||
this.setState({ openAccordion: name });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { openAccordion } = this.state;
|
||||
const { elements, classes } = this.props;
|
||||
|
||||
return elements.map(({ body, title }, i) => (
|
||||
<span key={i}>
|
||||
<span onClick={() => this.changeAccordion(title)} className={classes.title}>
|
||||
<ExpandIcon />
|
||||
<p style={{
|
||||
fontWeight: openAccordion === title ? 'bold' : 'normal'
|
||||
}} className={classes.button}>{title}</p>
|
||||
</span>
|
||||
<div className={`${classes.body} ${openAccordion === title ? '' : classes.closed}`}>
|
||||
{body}
|
||||
</div>
|
||||
</span>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(Accordion);
|
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'proptypes';
|
||||
import _ from 'lodash';
|
||||
import injectSheet from 'react-jss';
|
||||
import MaterialUISelectField from 'material-ui/SelectField'
|
||||
import MaterialUISelectField from 'material-ui/SelectField';
|
||||
import MaterialUICheckbox from 'material-ui/Checkbox';
|
||||
import TextFieldIcon from 'material-ui-textfield-icon';
|
||||
import RefreshIcon from 'material-ui-icons/Refresh';
|
||||
@ -39,7 +38,7 @@ const _TextField = ({ name, muiTheme: { palette }, ...props }, context) => (
|
||||
{...props}
|
||||
icon={context.advancedFields.includes(name) && <RefreshIcon
|
||||
style={{ fill: palette.textColor }}
|
||||
onTouchTap={() => context.onChange(name, null)}
|
||||
onClick={() => context.onChange(name, null)}
|
||||
/>}
|
||||
floatingLabelStyle={{
|
||||
color: context.advancedFields.includes(name) ? palette.primary1Color : palette.primary3Color
|
||||
@ -59,8 +58,8 @@ const _NumberField = ({ name, min, max, muiTheme: { palette }, ...props }, conte
|
||||
type="number"
|
||||
icon={context.advancedFields.includes(name) && <RefreshIcon
|
||||
style={{ fill: palette.textColor }}
|
||||
onTouchTap={() => context.onChange(name, null)} />
|
||||
}
|
||||
onClick={() => context.onChange(name, null)}
|
||||
/>}
|
||||
floatingLabelStyle={{
|
||||
color: context.advancedFields.includes(name) ? palette.primary1Color : palette.primary3Color
|
||||
}}
|
||||
@ -96,7 +95,7 @@ const _Checkbox = ({ name, muiTheme: { palette }, ...props }, context) => (
|
||||
onCheck={(event, value) => context.onChange(name, value)}
|
||||
/>
|
||||
{context.advancedFields.includes(name) && <RefreshIcon
|
||||
onTouchTap={() => context.onChange(name, null)}
|
||||
onClick={() => context.onChange(name, null)}
|
||||
/>}
|
||||
</span>
|
||||
);
|
||||
|
@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'proptypes';
|
||||
import muiThemeable from 'material-ui/styles/muiThemeable';
|
||||
import injectSheet from 'react-jss';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import { sleep, getMalyanStatus } from './utils.js';
|
||||
|
||||
const styles = {
|
||||
|
||||
};
|
||||
|
||||
class MalyanControl extends React.Component {
|
||||
static propTypes = {
|
||||
ip: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
status: null,
|
||||
mounted: true
|
||||
};
|
||||
|
||||
// componentDidMount = async () => {
|
||||
// const { ip } = this.props;
|
||||
// while (this.state.mounted) {
|
||||
// const status = await getMalyanStatus(ip).catch(() => null);
|
||||
// this.setState({ status });
|
||||
// await sleep(1000);
|
||||
// }
|
||||
// };
|
||||
|
||||
home = () => {
|
||||
const { ip } = this.props;
|
||||
fetch(`http://${ip}/set?code=G28`, { method: 'GET', mode: 'no-cors' });
|
||||
};
|
||||
|
||||
stop = () => {
|
||||
const { ip } = this.props;
|
||||
fetch(`http://${ip}/set?cmd={P:X}`, { method: 'GET', mode: 'no-cors' });
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.setState({ mounted: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status } = this.state;
|
||||
return (
|
||||
<div>
|
||||
{status && <span>
|
||||
<p>Nozzle temperature: {status.nozzleTemperature}/{status.nozzleTargetTemperature}</p>
|
||||
<p>Bed temperature: {status.bedTemperature}/{status.bedTargetTemperature}</p>
|
||||
{status.state === 'printing' && <p>Progress: {status.progress}%</p>}
|
||||
</span>}
|
||||
<FlatButton label="Stop" onTouchTap={this.stop} />
|
||||
<FlatButton label="Home" onTouchTap={this.home} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default muiThemeable()(injectSheet(styles)(MalyanControl));
|
@ -21,13 +21,7 @@ import update from 'react-addons-update';
|
||||
import SettingsIcon from 'material-ui-icons/Settings';
|
||||
import ExitToAppIcon from 'material-ui-icons/ExitToApp';
|
||||
import validateIp from 'validate-ip';
|
||||
import { Doodle3DManager } from 'doodle3d-api';
|
||||
|
||||
const DOODLE_3D_MANAGER = new Doodle3DManager();
|
||||
DOODLE_3D_MANAGER.checkNonServerBoxes = false;
|
||||
DOODLE_3D_MANAGER.setAutoUpdate(true, 5000);
|
||||
|
||||
const CONNECT_URL = 'http://connect.doodle3d.com/';
|
||||
import Accordion from './Accordion.js';
|
||||
|
||||
const styles = {
|
||||
textFieldRow: {
|
||||
@ -39,19 +33,22 @@ const styles = {
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
'& p': {
|
||||
fontWeight: 'bold',
|
||||
// fontWeight: 'bold',
|
||||
margin: '30px 0 0 0'
|
||||
},
|
||||
'& h3': {
|
||||
fontWeight: 'bold',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px',
|
||||
marginBottom: '20px'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
color: red500
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const updateLocalStorage = (localStorage) => {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(localStorage));
|
||||
};
|
||||
|
||||
const getLocalStorage = () => {
|
||||
@ -66,10 +63,6 @@ const getLocalStorage = () => {
|
||||
return localStorage;
|
||||
};
|
||||
|
||||
const updateLocalStorage = (localStorage) => {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(localStorage));
|
||||
};
|
||||
|
||||
class Settings extends React.Component {
|
||||
static propTypes = {
|
||||
selectedPrinter: PropTypes.string,
|
||||
@ -92,7 +85,6 @@ class Settings extends React.Component {
|
||||
|
||||
state = {
|
||||
localStorage: getLocalStorage(),
|
||||
wifiBoxes: [],
|
||||
addPrinter: {
|
||||
open: false,
|
||||
name: '',
|
||||
@ -110,9 +102,8 @@ class Settings extends React.Component {
|
||||
const { localStorage } = this.state;
|
||||
|
||||
if (selectedPrinter && localStorage.active) {
|
||||
const activePrinter = selectedPrinter && Object.entries(localStorage.printers)
|
||||
.map(([key, value]) => ({ key, value }))
|
||||
.find(({ key, value: { ip } }) => ip === selectedPrinter);
|
||||
const activePrinter = selectedPrinter && Object.values(localStorage.printers)
|
||||
.find(({ ip }) => ip === selectedPrinter);
|
||||
|
||||
if (activePrinter) {
|
||||
const state = this.changeSettings('activePrinter', activePrinter.key);
|
||||
@ -127,15 +118,6 @@ class Settings extends React.Component {
|
||||
} else if (!selectedPrinter && !localStorage.active) {
|
||||
this.openAddPrinterDialog();
|
||||
}
|
||||
|
||||
const eventListener = ({ boxes }) => this.setState({ wifiBoxes: boxes });
|
||||
this.setState({ wifiBoxes: DOODLE_3D_MANAGER.boxes, eventListener });
|
||||
DOODLE_3D_MANAGER.addEventListener('boxeschanged', eventListener);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { eventListener } = this.state;
|
||||
DOODLE_3D_MANAGER.removeEventListener('boxeschanged', eventListener);
|
||||
}
|
||||
|
||||
changeSettings = (fieldName, value) => {
|
||||
@ -212,7 +194,6 @@ class Settings extends React.Component {
|
||||
case 'settings.support.density':
|
||||
case 'settings.support.minArea':
|
||||
case 'settings.support.margin':
|
||||
case 'settings.support.speed':
|
||||
case 'settings.support.flowRate':
|
||||
if (!localStorage.active) return this.openAddPrinterDialog();
|
||||
|
||||
@ -307,14 +288,22 @@ class Settings extends React.Component {
|
||||
};
|
||||
|
||||
editPrinter = () => {
|
||||
const { localStorage: { active, printers }, managePrinter: { printer, name, ip } } = this.state;
|
||||
const { localStorage: { active }, managePrinter: { printer, name, ip } } = this.state;
|
||||
|
||||
if (!name) {
|
||||
this.setState(update(this.state, { managePrinter: { error: { $set: 'Please enter a name' } } }));
|
||||
this.setState(update(this.state, {
|
||||
managePrinter: {
|
||||
error: { $set: 'Please enter a name' }
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (printer === 'doodle3d_printer' && !validateIp(ip)) {
|
||||
this.setState(update(this.state, { managePrinter: { error: { $set: 'Please enter a valid IP adress' } } }));
|
||||
this.setState(update(this.state, {
|
||||
managePrinter: {
|
||||
error: { $set: 'Please enter a valid IP adress' }
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -356,7 +345,8 @@ class Settings extends React.Component {
|
||||
|
||||
closeAddPrinterDialog = (override) => this.setAddPrinterDialog(false, override);
|
||||
openAddPrinterDialog = (override) => this.setAddPrinterDialog(true, override);
|
||||
setAddPrinterDialog = (open, override = {}) => this.setState({
|
||||
setAddPrinterDialog = (open, override = {}) => {
|
||||
this.setState({
|
||||
addPrinter: {
|
||||
ip: '',
|
||||
name: '',
|
||||
@ -366,12 +356,13 @@ class Settings extends React.Component {
|
||||
...override
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
closeManagePrinterDialog = () => this.setManagePrinterDialog(false);
|
||||
openManagePrinterDialog = () => this.setManagePrinterDialog(true);
|
||||
setManagePrinterDialog = (open) => {
|
||||
const { localStorage: { active, printers } } = this.state;
|
||||
if (!active) return this.setState({ managePrinter: { open: false } });
|
||||
|
||||
this.setState({
|
||||
managePrinter: {
|
||||
open,
|
||||
@ -384,8 +375,8 @@ class Settings extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { addPrinter, managePrinter, localStorage, wifiBoxes } = this.state;
|
||||
const { classes, disabled } = this.props;
|
||||
const { addPrinter, managePrinter, localStorage } = this.state;
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
@ -395,10 +386,10 @@ class Settings extends React.Component {
|
||||
<MenuItem key={id} value={id} primaryText={name} />
|
||||
))}
|
||||
<Divider />
|
||||
<MenuItem onTouchTap={this.openAddPrinterDialog} value="add_printer" primaryText="Add Printer" />
|
||||
<MenuItem onClick={this.openAddPrinterDialog} value="add_printer" primaryText="Add Printer" />
|
||||
</SelectField>
|
||||
{localStorage.active && <SettingsIcon
|
||||
onTouchTap={this.openManagePrinterDialog}
|
||||
onClick={this.openManagePrinterDialog}
|
||||
style={{ fill: grey800, marginLeft: '10px', cursor: 'pointer' }}
|
||||
/>}
|
||||
</div>
|
||||
@ -425,23 +416,38 @@ class Settings extends React.Component {
|
||||
</Tab>
|
||||
<Tab buttonStyle={{ color: grey800, backgroundColor: 'white' }} label="Advanced">
|
||||
<div>
|
||||
<p>Layer</p>
|
||||
<NumberField name="settings.layerHeight" min={0.05} max={3} fullWidth floatingLabelText="Height" />
|
||||
<p>Thickness</p>
|
||||
<Accordion elements={[{
|
||||
title: 'Layer',
|
||||
body: (<NumberField name="settings.layerHeight" min={0.05} max={3} fullWidth floatingLabelText="Height" />)
|
||||
}, {
|
||||
title: 'Thickness',
|
||||
body: (<span>
|
||||
<NumberField name="settings.thickness.top" min={0} fullWidth floatingLabelText="top" />
|
||||
<NumberField name="settings.thickness.bottom" min={0} fullWidth floatingLabelText="bottom" />
|
||||
<NumberField name="settings.thickness.shell" min={0} fullWidth floatingLabelText="shell" />
|
||||
<p>Material</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Material',
|
||||
body: (<span>
|
||||
<NumberField name="settings.filamentThickness" min={0.1} max={10} fullWidth floatingLabelText="Thickness" />
|
||||
<NumberField name="settings.temperature" min={100} max={400} fullWidth floatingLabelText="Temperature" />
|
||||
<p>Bed</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Bed',
|
||||
body: (<span>
|
||||
<NumberField name="settings.bedTemperature" min={30} max={150} fullWidth floatingLabelText="Temperature" />
|
||||
<Checkbox name="settings.heatedBed" label="Heated" />
|
||||
<p>Brim</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Brim',
|
||||
body: (<span>
|
||||
<NumberField name="settings.brim.size" min={0} max={20} fullWidth floatingLabelText="Size" />
|
||||
<NumberField name="settings.brim.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.brim.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Support</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Support',
|
||||
body: (<span>
|
||||
<Checkbox name="settings.support.enabled" label="Enabled" />
|
||||
<NumberField name="settings.support.distanceY" min={0.1} fullWidth floatingLabelText="Distance Y" />
|
||||
<NumberField name="settings.support.density" min={0} max={100} fullWidth floatingLabelText="Density" />
|
||||
@ -449,38 +455,67 @@ class Settings extends React.Component {
|
||||
<NumberField name="settings.support.minArea" min={1} fullWidth floatingLabelText="Min Area" />
|
||||
<NumberField name="settings.support.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.support.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>First layer</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'First layer',
|
||||
body: (<span>
|
||||
<NumberField name="settings.firstLayer.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.firstLayer.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Inner shell</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Inner shell',
|
||||
body: (<span>
|
||||
<NumberField name="settings.innerShell.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.innerShell.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Outer shell</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Outer shell',
|
||||
body: (<span>
|
||||
<NumberField name="settings.outerShell.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.outerShell.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Inner infill</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Inner infill',
|
||||
body: (<span>
|
||||
<NumberField name="settings.innerInfill.density" min={0} max={100} fullWidth floatingLabelText="Density" />
|
||||
<NumberField name="settings.innerInfill.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.innerInfill.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Outer infill</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Outer infill',
|
||||
body: (<span>
|
||||
<NumberField name="settings.outerInfill.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.outerInfill.flowRate" min={0.1} max={4} fullWidth floatingLabelText="Flow rate" />
|
||||
<p>Travel</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Travel',
|
||||
body: (<span>
|
||||
<NumberField name="settings.travel.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<Checkbox name="settings.combing" label="Combing" />
|
||||
<p>Retraction</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Retraction',
|
||||
body: (<span>
|
||||
<Checkbox name="settings.retraction.enabled" label="Enabled" />
|
||||
<NumberField name="settings.retraction.amount" min={0} max={10} fullWidth floatingLabelText="Amount" />
|
||||
<NumberField name="settings.retraction.speed" min={10} max={200} fullWidth floatingLabelText="Speed" />
|
||||
<NumberField name="settings.retraction.minDistance" min={0} fullWidth floatingLabelText="Min distance" />
|
||||
<p>Printer dimensions</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Printer dimensions',
|
||||
body: (<span>
|
||||
<div className={classes.textFieldRow}>
|
||||
<NumberField name="settings.dimensions.x" min={1} fullWidth floatingLabelText="X" />
|
||||
<NumberField name="settings.dimensions.y" min={1} fullWidth floatingLabelText="Y" />
|
||||
<NumberField name="settings.dimensions.z" min={1} fullWidth floatingLabelText="Z" />
|
||||
</div>
|
||||
<p>Nozzle</p>
|
||||
</span>)
|
||||
}, {
|
||||
title: 'Nozzle',
|
||||
body: (<span>
|
||||
<NumberField name="settings.nozzleDiameter" min={0.1} max={5} fullWidth floatingLabelText="Diameter" />
|
||||
</span>)
|
||||
}]} />
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@ -493,7 +528,6 @@ class Settings extends React.Component {
|
||||
|
||||
function printDialog(props, state, title, form, submitText, data, closeDialog, removeActivePrinter, save) {
|
||||
const { classes } = props;
|
||||
const { wifiBoxes } = state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -505,16 +539,16 @@ function printDialog(props, state, title, form, submitText, data, closeDialog, r
|
||||
actions={[
|
||||
closeDialog && <FlatButton
|
||||
label="Close"
|
||||
onTouchTap={closeDialog}
|
||||
onClick={closeDialog}
|
||||
/>,
|
||||
removeActivePrinter && <FlatButton
|
||||
label="Remove Printer"
|
||||
onTouchTap={removeActivePrinter}
|
||||
onClick={removeActivePrinter}
|
||||
/>,
|
||||
<RaisedButton
|
||||
label={submitText}
|
||||
primary
|
||||
onTouchTap={save}
|
||||
onClick={save}
|
||||
/>
|
||||
]}
|
||||
>
|
||||
@ -523,22 +557,12 @@ function printDialog(props, state, title, form, submitText, data, closeDialog, r
|
||||
<MenuItem key={value} value={value} primaryText={title} />
|
||||
))}
|
||||
</SelectField>
|
||||
<TextField name={`${form}.name`} floatingLabelText="Name" fullWidth />
|
||||
{(data.printer === 'doodle3d_printer') ?
|
||||
<TextField name={`${form}.ip`} floatingLabelText="IP Adress" fullWidth /> :
|
||||
<div className={classes.textFieldRow}>
|
||||
<SelectField name={`${form}.ip`} floatingLabelText="Doodle3D WiFi-Box" fullWidth>
|
||||
{wifiBoxes.map(({ localip, id, wifiboxid }) => (<MenuItem key={id} value={localip} primaryText={wifiboxid} />))}
|
||||
</SelectField>
|
||||
{data.ip && <ExitToAppIcon
|
||||
onTouchTap={() => window.open(`${CONNECT_URL}/?uuid=0#control?localip=${data.ip}`, '_blank')}
|
||||
style={{ fill: grey800, marginLeft: '10px', cursor: 'pointer' }}
|
||||
/>}
|
||||
</div>
|
||||
}
|
||||
{data.error && <p className={classes.error}>{data.error}</p>}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
printDialog.propTypes = {
|
||||
classes: PropTypes.objectOf(PropTypes.string)
|
||||
};
|
||||
|
||||
export default injectSheet(styles)(Settings);
|
||||
|
@ -1,59 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'proptypes';
|
||||
import muiThemeable from 'material-ui/styles/muiThemeable';
|
||||
import injectSheet from 'react-jss';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import { Doodle3DBox } from 'doodle3d-api';
|
||||
|
||||
const styles = {
|
||||
|
||||
};
|
||||
|
||||
class WifiBoxControl extends React.Component {
|
||||
static propTypes = {
|
||||
ip: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
box: null,
|
||||
status: null
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
const { ip } = this.props;
|
||||
|
||||
const box = new Doodle3DBox(ip);
|
||||
window.d3dbox = box;
|
||||
box.addEventListener('update', ({ state }) => this.setState({ status: state }));
|
||||
box.setAutoUpdate(true, 5000);
|
||||
|
||||
this.setState({ box });
|
||||
|
||||
const alive = await box.checkAlive();
|
||||
};
|
||||
|
||||
stop = async () => {
|
||||
const { box } = this.state;
|
||||
const result = await box.printer.stop();
|
||||
console.log('result: ', result);
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
const { box } = this.state;
|
||||
if (box) box.setAutoUpdate(false);
|
||||
|
||||
this.setState({ mounted: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FlatButton label="Stop" onTouchTap={this.stop} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default muiThemeable()(injectSheet(styles)(WifiBoxControl));
|
@ -1,12 +1,9 @@
|
||||
import * as THREE from 'three';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import PropTypes from 'proptypes';
|
||||
import { centerGeometry, placeOnGround, createScene, slice, TabTemplate } from './utils.js';
|
||||
import injectSheet from 'react-jss';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import Slider from 'material-ui/Slider';
|
||||
import LinearProgress from 'material-ui/LinearProgress';
|
||||
import { grey50, grey300, grey800, red500 } from 'material-ui/styles/colors';
|
||||
import Popover from 'material-ui/Popover/Popover';
|
||||
@ -14,14 +11,8 @@ 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 MalyanControl from './MalyanControl.js';
|
||||
// import WifiBoxControl from './WifiBoxControl.js';
|
||||
import ReactResizeDetector from 'react-resize-detector';
|
||||
import JSONToSketchData from 'doodle3d-core/shape/JSONToSketchData';
|
||||
import createSceneData from 'doodle3d-core/d3/createSceneData.js';
|
||||
import { generateExportMesh } from 'doodle3d-core/utils/exportUtils.js';
|
||||
import muiThemeable from 'material-ui/styles/muiThemeable';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import logo from '../../img/logo.png';
|
||||
|
||||
const MAX_FULLSCREEN_WIDTH = 720;
|
||||
@ -55,10 +46,11 @@ const styles = {
|
||||
boxSizing: 'border-box',
|
||||
padding: '10px 20px',
|
||||
backgroundColor: 'white',
|
||||
overflowY: 'auto',
|
||||
borderLeft: `1px solid ${grey300}`
|
||||
},
|
||||
sliceActions: {
|
||||
flexShrink: 0,
|
||||
flexShrink: 0
|
||||
},
|
||||
sliceInfo: {
|
||||
margin: '10px 0',
|
||||
@ -105,34 +97,24 @@ const styles = {
|
||||
|
||||
class Interface extends React.Component {
|
||||
static propTypes = {
|
||||
fileUrl: PropTypes.string,
|
||||
selectedPrinter: PropTypes.string,
|
||||
mesh: PropTypes.shape({ isMesh: PropTypes.oneOf([true]) }),
|
||||
classes: PropTypes.objectOf(PropTypes.string),
|
||||
pixelRatio: PropTypes.number.isRequired,
|
||||
onCancel: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
muiTheme: PropTypes.object.isRequired,
|
||||
allowDragDrop: PropTypes.bool.isRequired,
|
||||
actions: PropTypes.arrayOf(PropTypes.shape({ target: PropTypes.string }))
|
||||
onSliceSucces: PropTypes.func.isRequired,
|
||||
muiTheme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
actions: [{
|
||||
target: 'WIFI_PRINT',
|
||||
title: 'Print over WiFi'
|
||||
}, {
|
||||
target: 'DOWNLOAD',
|
||||
title: 'Download GCode'
|
||||
}],
|
||||
pixelRatio: 1,
|
||||
name: 'Doodle3D',
|
||||
allowDragDrop: true
|
||||
pixelRatio: 1
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.canvasElement = React.createRef();
|
||||
|
||||
const scene = createScene(this.props);
|
||||
this.state = {
|
||||
scene,
|
||||
@ -147,31 +129,15 @@ class Interface extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { canvas } = this.refs;
|
||||
const { scene } = this.state;
|
||||
scene.updateCanvas(canvas);
|
||||
scene.updateCanvas(this.canvasElement.current);
|
||||
|
||||
const { mesh, fileUrl } = this.props;
|
||||
const { mesh } = this.props;
|
||||
if (mesh) {
|
||||
this.updateMesh(mesh, scene);
|
||||
} else if (fileUrl) {
|
||||
this.loadFile(fileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
loadFile = (fileUrl) => {
|
||||
const { origin, pathname, password, username, port } = new URL(fileUrl);
|
||||
const headers = {};
|
||||
if (password && username) headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
|
||||
|
||||
fetch(`${origin}${port}${pathname}`, { headers })
|
||||
.then(resonse => resonse.json())
|
||||
.then(json => JSONToSketchData(json))
|
||||
.then(file => createSceneData(file))
|
||||
.then(sketch => generateExportMesh(sketch, { offsetSingleWalls: false, matrix: new THREE.Matrix4() }))
|
||||
.then(mesh => this.updateMesh(mesh));
|
||||
};
|
||||
|
||||
updateMesh(mesh, scene = this.state.scene) {
|
||||
scene.mesh.geometry = mesh.geometry;
|
||||
centerGeometry(scene.mesh);
|
||||
@ -231,19 +197,15 @@ class Interface extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
slice = async (action) => {
|
||||
const { isSlicing, settings, mesh, scene: { material, mesh: { matrix } } } = this.state;
|
||||
const { name } = this.props;
|
||||
slice = async () => {
|
||||
const { isSlicing, settings, mesh, scene: { mesh: { matrix } } } = this.state;
|
||||
const { onSliceSucces } = this.props;
|
||||
|
||||
if (isSlicing) return;
|
||||
if (!settings) {
|
||||
this.setState({ error: 'please select a printer first' });
|
||||
return;
|
||||
}
|
||||
if (action.target === 'WIFI_PRINT' && !settings.ip) {
|
||||
this.setState({ error: 'please connect to a WiFi enabled printer' });
|
||||
return;
|
||||
}
|
||||
if (!mesh) {
|
||||
this.setState({ error: 'there is no file to slice' });
|
||||
return;
|
||||
@ -257,7 +219,8 @@ class Interface extends React.Component {
|
||||
|
||||
try {
|
||||
const updateProgres = progress => this.setState({ progress: { ...this.state.progress, ...progress } });
|
||||
await slice(action, name, exportMesh, settings, updateProgres);
|
||||
const sliceResults = await slice(exportMesh, settings, updateProgres);
|
||||
onSliceSucces(sliceResults);
|
||||
} catch (error) {
|
||||
this.setState({ error: error.message });
|
||||
throw error;
|
||||
@ -287,8 +250,7 @@ class Interface extends React.Component {
|
||||
|
||||
componentDidUpdate() {
|
||||
const { scene: { updateCanvas } } = this.state;
|
||||
const { canvas } = this.refs;
|
||||
if (updateCanvas && canvas) updateCanvas(canvas);
|
||||
if (updateCanvas && this.canvasElement.current) updateCanvas(this.canvasElement.current);
|
||||
}
|
||||
|
||||
onResize3dView = (width, height) => {
|
||||
@ -323,26 +285,9 @@ class Interface extends React.Component {
|
||||
this.setState({ objectDimensions: `${Math.round(y)}x${Math.round(z)}x${Math.round(x)}mm` });
|
||||
};
|
||||
|
||||
onDrop = (event) => {
|
||||
event.preventDefault();
|
||||
if (!this.props.allowDragDrop) return;
|
||||
|
||||
for (const file of event.dataTransfer.files) {
|
||||
const extentions = file.name.split('.').pop();
|
||||
|
||||
switch (extentions.toUpperCase()) {
|
||||
case 'D3SKETCH':
|
||||
this.loadFile(URL.createObjectURL(file));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, onCancel, selectedPrinter, actions } = this.props;
|
||||
const { isSlicing, progress, showFullScreen, error, objectDimensions, settings } = this.state;
|
||||
const { classes, onCancel, selectedPrinter } = this.props;
|
||||
const { isSlicing, settings, progress, showFullScreen, error, objectDimensions } = this.state;
|
||||
|
||||
const style = { ...(showFullScreen ? {} : { maxWidth: 'inherit', width: '100%', height: '100%' }) };
|
||||
|
||||
@ -363,45 +308,16 @@ class Interface extends React.Component {
|
||||
{onCancel && <RaisedButton
|
||||
label="Close"
|
||||
className={`${classes.button}`}
|
||||
onTouchTap={onCancel}
|
||||
onClick={onCancel}
|
||||
/>}
|
||||
{/* (settings && settings.ip) && ((settings.printer === 'doodle3d_printer') ?
|
||||
<MalyanControl ip={settings.ip} /> :
|
||||
<WifiBoxControl ip={settings.ip} />
|
||||
) */}
|
||||
{actions.length === 1 ? (
|
||||
<RaisedButton
|
||||
primary
|
||||
label={actions[0].title}
|
||||
onTouchTap={() => this.slice(actions[0])}
|
||||
className={`${classes.button}`}
|
||||
disabled={isSlicing}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
<RaisedButton
|
||||
label="Print"
|
||||
label="Download GCODE"
|
||||
ref="button"
|
||||
primary
|
||||
className={`${classes.button}`}
|
||||
onTouchTap={this.openPopover}
|
||||
disabled={isSlicing}
|
||||
onClick={() => this.slice()}
|
||||
/>
|
||||
<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>
|
||||
{actions.map((action) => (
|
||||
<MenuItem key={action.target} primaryText={action.title} onTouchTap={() => this.slice(action)} />
|
||||
))}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -410,32 +326,24 @@ class Interface extends React.Component {
|
||||
const d3Panel = (
|
||||
<div className={classes.d3View}>
|
||||
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize3dView} />
|
||||
<canvas className={classes.canvas} ref="canvas" />
|
||||
<canvas className={classes.canvas} ref={this.canvasElement} />
|
||||
<div className={classes.controlBar}>
|
||||
<div className={classes.detail}>
|
||||
<p>Dimensions: {objectDimensions}</p>
|
||||
</div>
|
||||
<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" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.resetMesh} label="reset" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.scaleUp} label="scale down" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.scaleDown} label="scale up" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.rotateX} label="rotate x" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.rotateY} label="rotate y" />
|
||||
<RaisedButton disabled={isSlicing} className={classes.controlButton} onClick={this.rotateZ} label="rotate z" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (showFullScreen) {
|
||||
return (
|
||||
<div
|
||||
className={classes.container}
|
||||
ref={(container) => {
|
||||
if (container) {
|
||||
container.addEventListener('dragover', event => event.preventDefault());
|
||||
container.addEventListener('drop', this.onDrop);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={classes.container}>
|
||||
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
|
||||
<img src={logo} className={classes.logo} />
|
||||
{d3Panel}
|
||||
@ -444,15 +352,7 @@ class Interface extends React.Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={classes.container}
|
||||
ref={(container) => {
|
||||
if (container) {
|
||||
container.addEventListener('dragover', event => event.preventDefault());
|
||||
container.addEventListener('drop', this.onDrop);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={classes.container}>
|
||||
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
|
||||
<Tabs
|
||||
style={{ width: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
|
@ -4,11 +4,8 @@ import printerSettings from '../settings/printer.yml';
|
||||
import materialSettings from '../settings/material.yml';
|
||||
import qualitySettings from '../settings/quality.yml';
|
||||
import { sliceGeometry } from '../slicer.js';
|
||||
import { grey800, red500 } from 'material-ui/styles/colors';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import fileSaver from 'file-saver';
|
||||
import { Doodle3DBox } from 'doodle3d-api';
|
||||
|
||||
export function placeOnGround(mesh) {
|
||||
const boundingBox = new THREE.Box3().setFromObject(mesh);
|
||||
@ -24,7 +21,7 @@ export function centerGeometry(mesh) {
|
||||
mesh.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z));
|
||||
}
|
||||
|
||||
export function createScene({ pixelRatio, muiTheme }) {
|
||||
export function createScene({ muiTheme }) {
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
|
||||
@ -52,7 +49,7 @@ export function createScene({ pixelRatio, muiTheme }) {
|
||||
let renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
let editorControls = new THREE.EditorControls(camera, renderer.domElement);
|
||||
|
||||
box.scale.set(1., 1., 1.);
|
||||
box.scale.set(1, 1, 1);
|
||||
box.updateMatrix();
|
||||
|
||||
const render = () => renderer.render(scene, camera);
|
||||
@ -85,93 +82,13 @@ export function createScene({ pixelRatio, muiTheme }) {
|
||||
return { editorControls, scene, mesh, camera, renderer, render, box, setSize, updateCanvas, focus };
|
||||
}
|
||||
|
||||
export function fetchProgress(url, data = {}, onProgress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = new Request(url, data);
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onload = () => {
|
||||
resolve(new Response(xhr.response));
|
||||
// const headers = new Headers(xhr.getAllResponseHeaders() || '');
|
||||
// const { status, statusText, response, responseText, responseURL: url = headers.get('X-Request-URL') } = xhr;
|
||||
// resolve(new Response(response || responseText, { headers, status, statusText, url }));
|
||||
}
|
||||
xhr.onerror = () => reject(new TypeError('Network request failed'));
|
||||
xhr.ontimeout = () => reject(new TypeError('Network request failed'));
|
||||
|
||||
xhr.open(request.method, url, true);
|
||||
|
||||
if (request.credentials === 'include') {
|
||||
xhr.withCredentials = true
|
||||
} else if (request.credentials === 'omit') {
|
||||
xhr.withCredentials = false
|
||||
}
|
||||
if (xhr.upload && onProgress) xhr.upload.onprogress = onProgress;
|
||||
if (xhr.responseType) xhr.responseType = 'blob';
|
||||
|
||||
// request.headers.forEach((value, name) => xhr.setRequestHeader(name, value));
|
||||
|
||||
xhr.send(data.body);
|
||||
});
|
||||
}
|
||||
|
||||
export function getMalyanStatus(ip) {
|
||||
return fetch(`http://${ip}/inquiry`, { method: 'GET' })
|
||||
.then(response => response.text())
|
||||
.then(statusText => {
|
||||
const [nozzleTemperature, nozzleTargetTemperature, bedTemperature, bedTargetTemperature, progress] = statusText.match(/\d+/g);
|
||||
const status = { nozzleTemperature, nozzleTargetTemperature, bedTemperature, bedTargetTemperature, progress };
|
||||
|
||||
switch (statusText.charAt(statusText.length - 1)) {
|
||||
case 'I':
|
||||
status.state = 'idle';
|
||||
break;
|
||||
case 'P':
|
||||
status.state = 'printing';
|
||||
break;
|
||||
default:
|
||||
status.state = 'unknown';
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
})
|
||||
}
|
||||
|
||||
export function sleep(time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
const GCODE_SERVER_URL = 'https://gcodeserver.doodle3d.com';
|
||||
|
||||
export async function slice(action, name, mesh, settings, updateProgress) {
|
||||
let steps;
|
||||
export async function slice(mesh, settings, updateProgress) {
|
||||
let steps = 1;
|
||||
let currentStep = 0;
|
||||
let wifiBox;
|
||||
switch (action.target) {
|
||||
case 'DOWNLOAD':
|
||||
steps = 1;
|
||||
break;
|
||||
case 'WIFI_PRINT':
|
||||
if (settings.printer === 'doodle3d_printer') {
|
||||
const { state } = await getMalyanStatus(settings.ip);
|
||||
if (state !== 'idle') throw { message: 'printer is busy', code: 0 };
|
||||
|
||||
} else {
|
||||
wifiBox = new Doodle3DBox(settings.ip);
|
||||
if (!await wifiBox.checkAlive()) throw { message: `can't connect to printer`, code: 4 }
|
||||
|
||||
const { state } = await wifiBox.info.status();
|
||||
if (state !== 'idle') throw { message: 'printer is busy', code: 0 };
|
||||
}
|
||||
steps = 2;
|
||||
break;
|
||||
case 'CUSTOM_UPLOAD':
|
||||
steps = 2;
|
||||
break;
|
||||
default:
|
||||
throw { message: 'unknown target', code: 1 };
|
||||
break;
|
||||
}
|
||||
|
||||
const { dimensions } = settings;
|
||||
const centerX = dimensions.x / 2;
|
||||
@ -181,9 +98,8 @@ export async function slice(action, name, mesh, settings, updateProgress) {
|
||||
.multiply(new THREE.Matrix4().makeRotationY(-Math.PI / 2.0))
|
||||
.multiply(mesh.matrix);
|
||||
|
||||
const { gcode } = await sliceGeometry({
|
||||
const sliceResult = await sliceGeometry({
|
||||
...settings,
|
||||
name: `${name}.gcode`,
|
||||
printer: { type: settings.printers, title: printerSettings[settings.printer].title },
|
||||
material: { type: settings.material, title: materialSettings[settings.material].title },
|
||||
quality: { type: settings.quality, title: qualitySettings[settings.quality].title }
|
||||
@ -197,81 +113,7 @@ export async function slice(action, name, mesh, settings, updateProgress) {
|
||||
});
|
||||
currentStep ++;
|
||||
|
||||
switch (action.target) {
|
||||
case 'DOWNLOAD': {
|
||||
fileSaver.saveAs(gcode, `${name}.gcode`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'WIFI_PRINT': {
|
||||
if (settings.printer === 'doodle3d_printer') {
|
||||
const body = new FormData();
|
||||
body.append('file', gcode, 'doodle.gcode');
|
||||
|
||||
// because fetch has no way of retrieving progress we fake progress
|
||||
let loaded = 0;
|
||||
const interval = setInterval(() => {
|
||||
loaded += 15 * 1024;
|
||||
updateProgress({
|
||||
action: 'Uploading to printer',
|
||||
percentage: (currentStep + loaded / file.size) / steps
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// await fetchProgress(`http://${settings.ip}/set?code=M563 S4`, { method: 'GET' });
|
||||
await fetch(`http://${settings.ip}/upload`, { method: 'POST', body, mode: 'no-cors' }, (progress) => {
|
||||
updateProgress({
|
||||
action: 'Uploading to printer',
|
||||
percentage: (currentStep + progress.loaded / progress.total) / steps
|
||||
});
|
||||
});
|
||||
clearInterval(interval);
|
||||
await fetch(`http://${settings.ip}/set?code=M566 ${name}.gcode`, { method: 'GET', mode: 'no-cors' });
|
||||
await fetch(`http://${settings.ip}/set?code=M565`, { method: 'GET', mode: 'no-cors' });
|
||||
|
||||
currentStep ++;
|
||||
} else {
|
||||
// upload G-code file to AWS S3
|
||||
const { data: { reservation: { fields, url }, id } } = await fetch(`${GCODE_SERVER_URL}/upload`, { method: 'POST' })
|
||||
.then(response => response.json());
|
||||
|
||||
const body = new FormData();
|
||||
for (const key in fields) {
|
||||
body.append(key, fields[key]);
|
||||
}
|
||||
|
||||
body.append('file', gcode, 'doodle.gcode');
|
||||
|
||||
await fetchProgress(url, { method: 'POST', body }, progress => {
|
||||
updateProgress({
|
||||
action: 'Uploading',
|
||||
percentage: (currentStep + progress.loaded / progress.total) / steps
|
||||
});
|
||||
});
|
||||
currentStep ++;
|
||||
|
||||
const result = await wifiBox.printer.fetch(id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CUSTOM_UPLOAD': {
|
||||
const body = new FormData();
|
||||
body.append('file', gcode, 'doodle.gcode');
|
||||
|
||||
await fetchProgress(action.url, { method: 'POST', body }, progress => {
|
||||
updateProgress({
|
||||
action: 'Uploading',
|
||||
percentage: (currentStep + progress.loaded / progress.total) / steps
|
||||
});
|
||||
});
|
||||
currentStep ++;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw { message: 'unknown target', code: 1 };
|
||||
break;
|
||||
}
|
||||
return sliceResult;
|
||||
}
|
||||
|
||||
export const TabTemplate = ({ children, selected, style }) => {
|
||||
@ -297,5 +139,5 @@ export const TabTemplate = ({ children, selected, style }) => {
|
||||
TabTemplate.propTypes = {
|
||||
children: PropTypes.node,
|
||||
selected: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
@ -35,7 +35,7 @@ filamentThickness: 2.85
|
||||
temperature: 210
|
||||
bedTemperature: 50
|
||||
layerHeight: 0.15
|
||||
combing: true
|
||||
combing: false
|
||||
thickness:
|
||||
top: 0.45
|
||||
bottom: 0.45
|
||||
|
@ -1,33 +1,3 @@
|
||||
doodle3d_printer:
|
||||
startCode: |-
|
||||
M140 S{bedTemperature}
|
||||
{if heatedBed}M104 S{temperature}
|
||||
G28
|
||||
M109 S{temperature}
|
||||
{if heatedBed}M190 S{bedTemperature}
|
||||
G90
|
||||
M82
|
||||
G1 Z10.0 F6000
|
||||
G92 E0
|
||||
G1 F200 E3
|
||||
G92 E0
|
||||
endCode: |-
|
||||
M104 S0
|
||||
M140 S0
|
||||
G92 E1
|
||||
G1 E-1 F300
|
||||
G28 X0 Y0
|
||||
M84
|
||||
M82
|
||||
M104 S0
|
||||
title: Doodle3D Printer
|
||||
heatedBed: true
|
||||
bedTemperature: 50
|
||||
filamentThickness: 1.75
|
||||
dimensions:
|
||||
x: 120
|
||||
y: 120
|
||||
z: 120
|
||||
_3Dison_plus:
|
||||
title: 3Dison plus
|
||||
heatedBed: false
|
||||
@ -368,3 +338,30 @@ wanhao_duplicator_i3_plus:
|
||||
title: Wanhao Duplicator i3 Plus
|
||||
heatedBed: false
|
||||
filamentThickness: 1.75
|
||||
wanhao_duplicator_i3_mini:
|
||||
duplicator_i3_mini:
|
||||
startCode: |-
|
||||
M104 S{temperature}
|
||||
G28
|
||||
M109 S{temperature}
|
||||
G90
|
||||
M82
|
||||
G1 Z10.0 F6000
|
||||
G92 E0
|
||||
G1 F200 E3
|
||||
G92 E0
|
||||
endCode: |-
|
||||
M104 S0
|
||||
G92 E1
|
||||
G1 E-1 F300
|
||||
G28 X0 Y0
|
||||
M84
|
||||
M82
|
||||
M104 S0
|
||||
title: Wanhao Duplicator i3 Mini
|
||||
heatedBed: false
|
||||
filamentThickness: 1.75
|
||||
dimensions:
|
||||
x: 120
|
||||
y: 135
|
||||
z: 120
|
@ -1,4 +1,4 @@
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
import { PRECISION } from '../constants.js';
|
||||
|
||||
const OFFSET_OPTIONS = {
|
||||
@ -20,8 +20,8 @@ export default function addBrim(slices, settings) {
|
||||
|
||||
const [firstLayer] = slices;
|
||||
|
||||
const brim = firstLayer.parts.reduce((brim, { shape }) => (
|
||||
brim.join(shape.offset(nozzleRadius, {
|
||||
const brim = firstLayer.parts.reduce((_brim, { shape }) => (
|
||||
_brim.join(shape.offset(nozzleRadius, {
|
||||
...OFFSET_OPTIONS,
|
||||
endType: shape.closed ? 'etClosedPolygon' : 'etOpenRound'
|
||||
}))
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PRECISION } from '../constants.js';
|
||||
import { divide } from './helpers/vector2.js';
|
||||
import { PRECISION } from '../constants.js'
|
||||
|
||||
export default function applyPrecision(layers) {
|
||||
for (let layer = 0; layer < layers.length; layer ++) {
|
||||
|
@ -21,7 +21,8 @@ export default function calculateLayersIntersections(lines, settings) {
|
||||
if (layerIndex >= 0 && layerIndex < numLayers) {
|
||||
const y = layerIndex * layerHeight + Z_OFFSET;
|
||||
|
||||
let x, z;
|
||||
let x;
|
||||
let z;
|
||||
if (line.start.y === line.end.y) {
|
||||
x = line.start.x;
|
||||
z = line.start.z;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vector2 from './helpers/vector2.js';
|
||||
import * as vector3 from './helpers/vector3.js';
|
||||
|
||||
export default function createLines(geometry, settings) {
|
||||
export default function createLines(geometry) {
|
||||
const faces = [];
|
||||
const lines = [];
|
||||
const lineLookup = {};
|
||||
@ -12,7 +12,7 @@ export default function createLines(geometry, settings) {
|
||||
const normal = calculateNormal(geometry.vertices, a, b, c);
|
||||
|
||||
// skip faces that point up or down
|
||||
if (normal.y > .999 || normal.y < -.999) {
|
||||
if (normal.y > 0.999 || normal.y < -0.999) {
|
||||
faces.push(null);
|
||||
continue;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { PRECISION } from '../constants.js'
|
||||
import { PRECISION } from '../constants.js';
|
||||
import getFillTemplate from './getFillTemplate.js';
|
||||
import Shape from 'clipper-js';
|
||||
|
||||
export default function generateInfills(slices, settings) {
|
||||
let {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PRECISION } from '../constants.js'
|
||||
import { PRECISION } from '../constants.js';
|
||||
|
||||
const OFFSET_OPTIONS = {
|
||||
jointType: 'jtSquare',
|
||||
@ -10,7 +10,6 @@ const OFFSET_OPTIONS = {
|
||||
export default function generateInnerLines(slices, settings) {
|
||||
// need to scale up everything because of clipper rounding errors
|
||||
let {
|
||||
layerHeight,
|
||||
nozzleDiameter,
|
||||
thickness: { shell: shellThickness }
|
||||
} = settings;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
|
||||
export default function calculateOutlines(slices, settings) {
|
||||
export default function calculateOutlines(slices) {
|
||||
for (let layer = 0; layer < slices.length; layer ++) {
|
||||
const slice = slices[layer];
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import getFillTemplate from './getFillTemplate.js';
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
import { PRECISION } from '../constants.js';
|
||||
|
||||
const PRECISION_SQUARED = Math.pow(PRECISION, 2);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
|
||||
export default function getFillTemplate(bounds, gridSize, even, uneven) {
|
||||
const paths = [];
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { scale, distanceTo } from './vector2.js';
|
||||
import { PRECISION, VERSION } from '../../constants.js';
|
||||
import { distanceTo } from './vector2.js';
|
||||
import { VERSION } from '../../constants.js';
|
||||
|
||||
export const MOVE = 'G';
|
||||
export const M_COMMAND = 'M';
|
||||
@ -14,7 +14,7 @@ export default class GCode {
|
||||
constructor(settings) {
|
||||
this._nozzleToFilamentRatio = 1;
|
||||
this._gcode = [
|
||||
`; ${JSON.stringify(settings).trim()}`,
|
||||
`; ${JSON.stringify(settings)}`,
|
||||
`; Generated with Doodle3D Slicer V${VERSION}`
|
||||
];
|
||||
this._currentValues = {};
|
||||
@ -55,7 +55,7 @@ export default class GCode {
|
||||
}
|
||||
|
||||
moveTo(x, y, z, { speed }) {
|
||||
const newNozzlePosition = scale({ x, y }, PRECISION);
|
||||
const newNozzlePosition = { x, y };
|
||||
const lineLength = distanceTo(this._nozzlePosition, newNozzlePosition);
|
||||
|
||||
this._duration += lineLength / speed;
|
||||
@ -74,7 +74,7 @@ export default class GCode {
|
||||
}
|
||||
|
||||
lineTo(x, y, z, { speed, flowRate }) {
|
||||
const newNozzlePosition = scale({ x, y }, PRECISION);
|
||||
const newNozzlePosition = { x, y };
|
||||
const lineLength = distanceTo(this._nozzlePosition, newNozzlePosition);
|
||||
|
||||
this._extruder += this._nozzleToFilamentRatio * lineLength * flowRate;
|
||||
@ -132,9 +132,9 @@ export default class GCode {
|
||||
|
||||
addGCode(gcode, { temperature, bedTemperature, heatedBed }) {
|
||||
gcode = gcode
|
||||
.replace(/{temperature}/gi, temperature)
|
||||
.replace(/{bedTemperature}/gi, bedTemperature)
|
||||
.replace(/{if heatedBed}/gi, heatedBed ? '' : ';');
|
||||
.replace(/{temperature}/g, temperature)
|
||||
.replace(/{if heatedBed}.*?\n/g, str => heatedBed ? str.replace(/{if heatedBed}/g, '') : '')
|
||||
.replace(/{bedTemperature}/g, bedTemperature);
|
||||
|
||||
this._addGCode(gcode);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
|
||||
export default class Slice {
|
||||
constructor() {
|
||||
|
@ -1,8 +1,10 @@
|
||||
export function hslToRgb(h, s, l){
|
||||
let r, g, b;
|
||||
export function hslToRgb(h, s, l) {
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = lightness;
|
||||
r = g = b = l;
|
||||
} else {
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
@ -14,7 +16,7 @@ export function hslToRgb(h, s, l){
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
function hueToRgb(p, q, t){
|
||||
function hueToRgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
|
@ -1,131 +1,197 @@
|
||||
import Shape from 'clipper-js';
|
||||
import { subtract, add, scale, normalize, dot, length, distanceTo } from './vector2.js';
|
||||
import { PRECISION } from '../../constants.js';
|
||||
import { angle, subtract, distanceTo } from './vector2.js';
|
||||
|
||||
const TOLERANCE = 1 / PRECISION;
|
||||
const graphs = new WeakMap();
|
||||
export default function comb(polygons, start, end) {
|
||||
if (!graphs.has(polygons)) graphs.set(polygons, createGraph(polygons));
|
||||
let { edges, graph, points } = graphs.get(polygons);
|
||||
|
||||
export default function comb(outline, start, end) {
|
||||
if (distanceTo(start, end) < TOLERANCE) {
|
||||
return [start, end];
|
||||
}
|
||||
points = [...points, start, end];
|
||||
graph = [...graph];
|
||||
|
||||
let combPath = new Shape([[start, end]], false, true, false);
|
||||
const startNode = createNode(graph, points, edges, start);
|
||||
const endNode = createNode(graph, points, edges, end);
|
||||
|
||||
for (let i = 0; i < outline.paths.length; i ++) {
|
||||
let outlinePart = new Shape([outline.paths[i]], true, false, false, true);
|
||||
|
||||
let snappedCombPaths = outlinePart.orientation(0) ? combPath.intersect(outlinePart) : combPath.difference(outlinePart);
|
||||
|
||||
snappedCombPaths = snappedCombPaths.mapToLower();
|
||||
outlinePart = outlinePart.mapToLower()[0];
|
||||
|
||||
if (distanceTo(start, outlinePart[outlinePart.length - 1]) < distanceTo(start, outlinePart[0])) {
|
||||
outlinePart = outlinePart.reverse();
|
||||
}
|
||||
|
||||
const distanceMap = new WeakMap();
|
||||
|
||||
for (let i = 0; i < snappedCombPaths.length; i ++) {
|
||||
const snappedCombPath = snappedCombPaths[i];
|
||||
|
||||
const distanceStart = distanceTo(start, snappedCombPath[0]);
|
||||
const distanceEnd = distanceTo(start, snappedCombPath[snappedCombPath.length - 1]);
|
||||
|
||||
if (distanceStart < distanceEnd) {
|
||||
distanceMap.set(snappedCombPath, distanceStart);
|
||||
let result;
|
||||
if (graph[startNode].some(node => node.to === endNode)) {
|
||||
result = [start, end];
|
||||
} else {
|
||||
snappedCombPath.reverse();
|
||||
distanceMap.set(snappedCombPath, distanceEnd);
|
||||
}
|
||||
}
|
||||
snappedCombPaths.sort((a, b) => distanceMap.get(a) - distanceMap.get(b));
|
||||
|
||||
const firstPath = snappedCombPaths[0];
|
||||
const lastPath = snappedCombPaths[snappedCombPaths.length - 1];
|
||||
|
||||
if (snappedCombPaths.length === 0) {
|
||||
snappedCombPaths.push([start], [end]);
|
||||
} else if (distanceTo(firstPath[0], start) > 1.) {
|
||||
snappedCombPaths.unshift([start]);
|
||||
} else if (distanceTo(lastPath[lastPath.length - 1], end) > 1.) {
|
||||
snappedCombPaths.push([end]);
|
||||
}
|
||||
|
||||
if (snappedCombPaths.length === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startPath = snappedCombPaths[0];
|
||||
const startPoint = startPath[startPath.length - 1];
|
||||
|
||||
const endPath = snappedCombPaths[snappedCombPaths.length - 1];
|
||||
const endPoint = endPath[0];
|
||||
|
||||
const lineIndexStart = findClosestLineOnPath(outlinePart, startPoint);
|
||||
const lineIndexEnd = findClosestLineOnPath(outlinePart, endPoint);
|
||||
|
||||
const path = [];
|
||||
if (lineIndexEnd === lineIndexStart) {
|
||||
continue;
|
||||
} else if (lineIndexEnd > lineIndexStart) {
|
||||
if (lineIndexStart + outlinePart.length - lineIndexEnd < lineIndexEnd - lineIndexStart) {
|
||||
for (let i = lineIndexStart + outlinePart.length; i > lineIndexEnd; i --) {
|
||||
path.push(outlinePart[i % outlinePart.length]);
|
||||
}
|
||||
const path = shortestPath(graph, startNode, endNode);
|
||||
if (path) {
|
||||
result = path.map(index => points[index]);
|
||||
} else {
|
||||
for (let i = lineIndexStart; i < lineIndexEnd; i ++) {
|
||||
path.push(outlinePart[i + 1]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lineIndexEnd + outlinePart.length - lineIndexStart < lineIndexStart - lineIndexEnd) {
|
||||
for (let i = lineIndexStart; i < lineIndexEnd + outlinePart.length; i ++) {
|
||||
path.push(outlinePart[(i + 1) % outlinePart.length]);
|
||||
}
|
||||
} else {
|
||||
for (let i = lineIndexStart; i > lineIndexEnd; i --) {
|
||||
path.push(outlinePart[i]);
|
||||
}
|
||||
result = [start, end];
|
||||
}
|
||||
}
|
||||
|
||||
combPath = new Shape([[...startPath, ...path, ...endPath]], false, true, false, true);
|
||||
}
|
||||
|
||||
return combPath.mapToLower()[0];
|
||||
return result;
|
||||
}
|
||||
|
||||
function findClosestLineOnPath(path, point) {
|
||||
let distance = Infinity;
|
||||
let lineIndex;
|
||||
function createGraph(polygons) {
|
||||
const points = [];
|
||||
const edges = [];
|
||||
const nextPoints = new WeakMap();
|
||||
const previousPoints = new WeakMap();
|
||||
for (let i = 0; i < polygons.length; i ++) {
|
||||
const polygon = polygons[i];
|
||||
for (let j = 0; j < polygon.length; j ++) {
|
||||
const point = polygon[j];
|
||||
const nextPoint = polygon[(j + 1) % polygon.length];
|
||||
const previousPoint = polygon[(j - 1 + polygon.length) % polygon.length];
|
||||
|
||||
for (let i = 0; i < path.length; i ++) {
|
||||
const pointA = path[i];
|
||||
const pointB = path[(i + 1) % path.length];
|
||||
|
||||
const tempClosestPoint = findClosestPointOnLine(pointA, pointB, point);
|
||||
const tempDistance = distanceTo(tempClosestPoint, point);
|
||||
|
||||
if (tempDistance < distance) {
|
||||
distance = tempDistance;
|
||||
lineIndex = i;
|
||||
points.push(point);
|
||||
edges.push([point, nextPoint]);
|
||||
nextPoints.set(point, nextPoint);
|
||||
previousPoints.set(point, previousPoint);
|
||||
}
|
||||
}
|
||||
|
||||
return lineIndex;
|
||||
const graph = points.map(() => ([]));
|
||||
for (let i = 0; i < points.length; i ++) {
|
||||
const a = points[i];
|
||||
|
||||
for (let j = i + 1; j < points.length; j ++) {
|
||||
const b = points[j];
|
||||
const nextPoint = nextPoints.get(a);
|
||||
const previousPoint = previousPoints.get(a);
|
||||
|
||||
if (!lineIsVisible(previousPoint, nextPoint, edges, a, b)) continue;
|
||||
|
||||
const distance = distanceTo(a, b);
|
||||
|
||||
const connectNodeA = graph[i];
|
||||
connectNodeA.push({ to: j, distance });
|
||||
|
||||
const connectNodeB = graph[j];
|
||||
connectNodeB.push({ to: i, distance });
|
||||
}
|
||||
}
|
||||
return { graph, edges, points };
|
||||
}
|
||||
|
||||
function findClosestPointOnLine(a, b, c) {
|
||||
const b_ = subtract(b, a);
|
||||
const c_ = subtract(c, a);
|
||||
function createNode(graph, points, edges, point) {
|
||||
const node = [];
|
||||
const to = graph.length;
|
||||
graph.push(node);
|
||||
|
||||
const lambda = dot(normalize(b_), c_) / length(b_);
|
||||
|
||||
if (lambda >= 1) {
|
||||
return b;
|
||||
} else if (lambda > 0) {
|
||||
return add(a, scale(b_, lambda));
|
||||
} else {
|
||||
return a;
|
||||
let previousPoint;
|
||||
let nextPoint;
|
||||
for (let j = 0; j < edges.length; j ++) {
|
||||
const edge = edges[j];
|
||||
if (pointOnLine(edge, point)) [previousPoint, nextPoint] = edge;
|
||||
}
|
||||
|
||||
for (let i = 0; i < graph.length; i ++) {
|
||||
const b = points[i];
|
||||
|
||||
if (!lineIsVisible(previousPoint, nextPoint, edges, point, b)) continue;
|
||||
|
||||
const distance = distanceTo(point, b);
|
||||
|
||||
node.push({ to: i, distance });
|
||||
graph[i] = [...graph[i], { to, distance }];
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
function lineIsVisible(previousPoint, nextPoint, edges, a, b) {
|
||||
if (b === nextPoint || b === previousPoint) return true;
|
||||
|
||||
if (previousPoint && nextPoint) {
|
||||
const angleLine = angle(subtract(b, a));
|
||||
const anglePrevious = angle(subtract(previousPoint, a));
|
||||
const angleNext = angle(subtract(nextPoint, a));
|
||||
|
||||
if (betweenAngles(angleLine, anglePrevious, angleNext)) return false;
|
||||
}
|
||||
|
||||
if (lineCrossesEdges(edges, a, b)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function lineCrossesEdges(edges, a, b) {
|
||||
for (let i = 0; i < edges.length; i ++) {
|
||||
const [c, d] = edges[i];
|
||||
if (lineSegmentsCross(a, b, c, d)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lineSegmentsCross(a, b, c, d) {
|
||||
const denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x));
|
||||
|
||||
if (denominator === 0.0) return false;
|
||||
|
||||
const numerator1 = ((a.y - c.y) * (d.x - c.x)) - ((a.x - c.x) * (d.y - c.y));
|
||||
const numerator2 = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y));
|
||||
if (numerator1 === 0.0 || numerator2 === 0.0) return false;
|
||||
|
||||
const r = numerator1 / denominator;
|
||||
const s = numerator2 / denominator;
|
||||
return (r > 0.0 && r < 1.0) && (s >= 0.0 && s <= 1.0);
|
||||
}
|
||||
|
||||
const TAU = Math.PI * 2.0;
|
||||
function normalizeAngle(a) {
|
||||
a %= TAU;
|
||||
return a > 0.0 ? a : a + TAU;
|
||||
}
|
||||
|
||||
function betweenAngles(n, a, b) {
|
||||
n = normalizeAngle(n);
|
||||
a = normalizeAngle(a);
|
||||
b = normalizeAngle(b);
|
||||
return a < b ? a <= n && n <= b : a <= n || n <= b;
|
||||
}
|
||||
|
||||
// dijkstra's algorithm
|
||||
function shortestPath(graph, start, end) {
|
||||
const distances = graph.map(() => Infinity);
|
||||
distances[start] = 0;
|
||||
const traverse = [];
|
||||
const queue = [];
|
||||
for (let i = 0; i < distances.length; i ++) {
|
||||
queue.push(i);
|
||||
}
|
||||
|
||||
while (queue.length > 0) {
|
||||
let queueIndex;
|
||||
let minDistance = Infinity;
|
||||
for (let index = 0; index < queue.length; index ++) {
|
||||
const nodeIndex = queue[index];
|
||||
const distance = distances[nodeIndex];
|
||||
if (distances[nodeIndex] < minDistance) {
|
||||
queueIndex = index;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
const [nodeIndex] = queue.splice(queueIndex, 1);
|
||||
const node = graph[nodeIndex];
|
||||
|
||||
for (let i = 0; i < node.length; i ++) {
|
||||
const child = node[i];
|
||||
const distance = distances[nodeIndex] + child.distance;
|
||||
if (distance < distances[child.to]) {
|
||||
distances[child.to] = distance;
|
||||
traverse[child.to] = nodeIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!traverse.hasOwnProperty(end)) return null;
|
||||
|
||||
const path = [end];
|
||||
let nodeIndex = end;
|
||||
do {
|
||||
nodeIndex = traverse[nodeIndex];
|
||||
path.push(nodeIndex);
|
||||
} while (nodeIndex !== start);
|
||||
|
||||
return path.reverse();
|
||||
}
|
||||
|
||||
function pointOnLine([a, b], point) {
|
||||
return (a.x - point.x) * (a.y - point.y) === (b.x - point.x) * (b.y - point.y);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export const almostEquals = (a, b) => Math.abs(a.x - b.x) < 0.001 && Math.abs(a.
|
||||
export const dot = (a, b) => a.x * b.x + a.y * b.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 angle = (v) => Math.atan2(v.y, v.x);
|
||||
export const normalize = (v) => {
|
||||
const l = length(v);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { subtract, normal, normalize, dot, almostEquals } from './helpers/vector2.js';
|
||||
|
||||
export default function intersectionsToShapes(layerPoints, layerFaceIndexes, faces, openObjectIndexes, settings) {
|
||||
export default function intersectionsToShapes(layerPoints, layerFaceIndexes, faces, openObjectIndexes) {
|
||||
const layers = [];
|
||||
|
||||
for (let layer = 0; layer < layerPoints.length; layer ++) {
|
||||
@ -59,6 +59,7 @@ export default function intersectionsToShapes(layerPoints, layerFaceIndexes, fac
|
||||
} else {
|
||||
lineSegment.push(...startConnects[pointB]);
|
||||
endConnects[lineSegment[lineSegment.length - 1]] = lineSegment;
|
||||
shapes[objectIndex].splice(shapes[objectIndex].indexOf(startConnects[pointB]), 1);
|
||||
}
|
||||
} else {
|
||||
lineSegment.push(pointB);
|
||||
@ -70,6 +71,7 @@ export default function intersectionsToShapes(layerPoints, layerFaceIndexes, fac
|
||||
if (endConnects[pointA]) {
|
||||
lineSegment.unshift(...endConnects[pointA]);
|
||||
startConnects[lineSegment[0]] = lineSegment;
|
||||
shapes[objectIndex].splice(shapes[objectIndex].indexOf(endConnects[pointA]), 1);
|
||||
} else {
|
||||
lineSegment.unshift(pointA);
|
||||
startConnects[pointA] = lineSegment;
|
||||
@ -87,7 +89,7 @@ export default function intersectionsToShapes(layerPoints, layerFaceIndexes, fac
|
||||
for (const objectIndex in shapes) {
|
||||
const shape = shapes[objectIndex]
|
||||
.map(lineSegment => lineSegment.map(pointIndex => points[pointIndex]))
|
||||
.filter(lineSegment => lineSegment.some(i => !almostEquals(lineSegment[0], lineSegment[1])));
|
||||
.filter(lineSegment => lineSegment.some(point => !almostEquals(lineSegment[0], point)));
|
||||
const openShape = openObjectIndexes[objectIndex];
|
||||
|
||||
const connectPoints = [];
|
||||
@ -120,7 +122,7 @@ export default function intersectionsToShapes(layerPoints, layerFaceIndexes, fac
|
||||
}
|
||||
}
|
||||
|
||||
connectPoints.sort(({ previous }) => -previous);
|
||||
connectPoints.sort((a, b) => b.previous - a.previous);
|
||||
|
||||
while (connectPoints.length !== 0) {
|
||||
let { next, previous } = connectPoints.pop();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { length, distanceTo } from './helpers/vector2.js';
|
||||
import Shape from 'clipper-js';
|
||||
import { distanceTo } from './helpers/vector2.js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
|
||||
export default function optimizePaths(slices, settings) {
|
||||
export default function optimizePaths(slices) {
|
||||
let start = { x: 0, y: 0 };
|
||||
|
||||
for (let layer = 0; layer < slices.length; layer ++) {
|
||||
@ -82,12 +82,12 @@ export default function optimizePaths(slices, settings) {
|
||||
}
|
||||
|
||||
function optimizeShape(shape, start) {
|
||||
const inputPaths = shape.mapToLower();
|
||||
const inputPaths = shape.mapToLower().filter(path => path.length > 0);
|
||||
const optimizedPaths = [];
|
||||
const donePaths = [];
|
||||
|
||||
while (optimizedPaths.length !== inputPaths.length) {
|
||||
let minLength = false;
|
||||
let minLength = Infinity;
|
||||
let reverse;
|
||||
let minPath;
|
||||
let offset;
|
||||
@ -101,7 +101,7 @@ function optimizeShape(shape, start) {
|
||||
if (shape.closed) {
|
||||
for (let j = 0; j < path.length; j += 1) {
|
||||
const length = distanceTo(path[j], start);
|
||||
if (minLength === false || length < minLength) {
|
||||
if (length < minLength) {
|
||||
minPath = path;
|
||||
minLength = length;
|
||||
offset = j;
|
||||
@ -110,7 +110,7 @@ function optimizeShape(shape, start) {
|
||||
}
|
||||
} else {
|
||||
const lengthToStart = distanceTo(path[0], start);
|
||||
if (minLength === false || lengthToStart < minLength) {
|
||||
if (lengthToStart < minLength) {
|
||||
minPath = path;
|
||||
minLength = lengthToStart;
|
||||
reverse = false;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Shape from 'clipper-js';
|
||||
import Shape from '@doodle3d/clipper-js';
|
||||
import Slice from './helpers/Slice.js';
|
||||
|
||||
import { PRECISION, MIN_AREA } from '../constants.js';
|
||||
|
||||
export default function shapesToSlices(shapes, settings) {
|
||||
export default function shapesToSlices(shapes) {
|
||||
const sliceLayers = [];
|
||||
|
||||
for (let layer = 0; layer < shapes.length; layer ++) {
|
||||
@ -14,7 +14,7 @@ export default function shapesToSlices(shapes, settings) {
|
||||
.simplify('pftNonZero')
|
||||
.clean(1)
|
||||
.thresholdArea(MIN_AREA / Math.pow(PRECISION, 2))
|
||||
.seperateShapes();
|
||||
.separateShapes();
|
||||
|
||||
lineShapesClosed = new Shape(lineShapesClosed, true, true, true, true)
|
||||
.clean(1);
|
||||
|
@ -11,9 +11,9 @@ import shapesToSlices from './shapesToSlices.js';
|
||||
import slicesToGCode from './slicesToGCode.js';
|
||||
import applyPrecision from './applyPrecision.js';
|
||||
import { hslToRgb } from './helpers/color.js';
|
||||
// // import removePrecision from './removePrecision.js';
|
||||
import removePrecision from './removePrecision.js';
|
||||
|
||||
export default function(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) {
|
||||
export default function slice(settings, geometry, openObjectIndexes, constructLinePreview, onProgress) {
|
||||
const total = 11;
|
||||
let done = -1;
|
||||
const updateProgress = action => {
|
||||
@ -48,7 +48,7 @@ export default function(settings, geometry, openObjectIndexes, constructLinePrev
|
||||
updateProgress('Optimizing paths');
|
||||
optimizePaths(slices, settings);
|
||||
|
||||
// removePrecision(slices);
|
||||
removePrecision(slices);
|
||||
|
||||
updateProgress('Constructing gcode');
|
||||
const gcode = slicesToGCode(slices, settings);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import GCode from './helpers/GCode.js';
|
||||
import comb from './helpers/comb.js';
|
||||
import { divide } from './helpers/vector2.js';
|
||||
import { PRECISION, Z_OFFSET } from '../constants.js';
|
||||
import { Z_OFFSET } from '../constants.js';
|
||||
|
||||
const PROFILE_TYPES = ['support', 'innerShell', 'outerShell', 'innerInfill', 'outerInfill', 'brim'];
|
||||
|
||||
@ -10,14 +9,13 @@ export default function slicesToGCode(slices, settings) {
|
||||
layerHeight,
|
||||
filamentThickness,
|
||||
nozzleDiameter,
|
||||
travelSpeed,
|
||||
retraction,
|
||||
travel,
|
||||
combing
|
||||
} = settings;
|
||||
|
||||
const gcode = new GCode(settings);
|
||||
gcode.updateLayerHeight(Z_OFFSET, nozzleDiameter, filamentThickness)
|
||||
gcode.updateLayerHeight(Z_OFFSET, nozzleDiameter, filamentThickness);
|
||||
|
||||
if (settings.startCode) gcode.addGCode(settings.startCode, settings);
|
||||
|
||||
@ -37,12 +35,12 @@ export default function slicesToGCode(slices, settings) {
|
||||
isFirstLayer = false;
|
||||
}
|
||||
|
||||
const profiles = PROFILE_TYPES.reduce((profiles, profileType) => {
|
||||
profiles[profileType] = {
|
||||
const profiles = PROFILE_TYPES.reduce((_profiles, profileType) => {
|
||||
_profiles[profileType] = {
|
||||
...defaultProfile,
|
||||
lineProfile: isFirstLayer ? settings.firstLayer : settings[profileType]
|
||||
};
|
||||
return profiles;
|
||||
return _profiles;
|
||||
}, {});
|
||||
|
||||
if (typeof slice.brim !== 'undefined') {
|
||||
@ -53,7 +51,7 @@ export default function slicesToGCode(slices, settings) {
|
||||
const part = slice.parts[i];
|
||||
|
||||
if (part.closed) {
|
||||
const outline = part.shell[0];
|
||||
const outline = part.shell[0].mapToLower();
|
||||
|
||||
for (let i = 0; i < part.shell.length; i ++) {
|
||||
const shell = part.shell[i];
|
||||
@ -73,7 +71,8 @@ export default function slicesToGCode(slices, settings) {
|
||||
}
|
||||
|
||||
if (typeof slice.support !== 'undefined') {
|
||||
pathToGCode(slice.supportOutline, combing, gcode, slice.support, true, true, z, profiles.support);
|
||||
const supportOutline = slice.supportOutline.mapToLower();
|
||||
pathToGCode(supportOutline, combing, gcode, slice.support, true, true, z, profiles.support);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +81,8 @@ export default function slicesToGCode(slices, settings) {
|
||||
return gcode.getGCode();
|
||||
}
|
||||
|
||||
function pathToGCode(outline, combing, gcode, shape, retract, unRetract, z, { lineProfile, travelProfile, retractionProfile }) {
|
||||
function pathToGCode(outline, combing, gcode, shape, retract, unRetract, z, profiles) {
|
||||
const { lineProfile, travelProfile, retractionProfile } = profiles;
|
||||
const { closed } = shape;
|
||||
const paths = shape.mapToLower();
|
||||
|
||||
@ -95,7 +95,7 @@ function pathToGCode(outline, combing, gcode, shape, retract, unRetract, z, { li
|
||||
|
||||
if (i === 0) {
|
||||
if (combing) {
|
||||
const combPath = comb(outline, divide(gcode._nozzlePosition, PRECISION), point);
|
||||
const combPath = comb(outline, gcode._nozzlePosition, point);
|
||||
for (let i = 0; i < combPath.length; i ++) {
|
||||
const combPoint = combPath[i];
|
||||
gcode.moveTo(combPoint.x, combPoint.y, z, travelProfile);
|
||||
|
@ -99,6 +99,9 @@ function sliceAsync(settings, geometry, openObjectIndexes, constructLinePreview,
|
||||
if (typeof onProgress !== 'undefined') onProgress(data);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@ const onProgress = progress => {
|
||||
message: 'PROGRESS',
|
||||
data: progress
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
const { message, data } = event.data;
|
||||
@ -28,5 +28,7 @@ self.addEventListener('message', (event) => {
|
||||
}, buffers);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
const path = require('path');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const HTMLWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
@ -29,16 +29,6 @@ module.exports = {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'dist')
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'doodle3d-slicer': path.resolve(__dirname, 'src/'),
|
||||
'clipper-lib': '@doodle3d/clipper-lib',
|
||||
'clipper-js': '@doodle3d/clipper-js',
|
||||
'doodle3d-core': `@doodle3d/doodle3d-core/${devMode ? 'module' : 'lib'}`,
|
||||
'doodle3d-api': `@doodle3d/doodle3d-api/${devMode ? 'module' : 'lib'}`,
|
||||
'cal': '@doodle3d/cal'
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@ -59,10 +49,26 @@ module.exports = {
|
||||
inline: false,
|
||||
name: '[name].js'
|
||||
}
|
||||
}, babelLoader],
|
||||
}, babelLoader]
|
||||
}, {
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: ['url-loader?name=images/[name].[ext]']
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: { name: '[path][name].[ext]' }
|
||||
},
|
||||
...(!devMode ? [{
|
||||
loader: 'image-webpack-loader',
|
||||
options: {
|
||||
mozjpeg: { progressive: true, quality: 65 },
|
||||
optipng: { enabled: false },
|
||||
pngquant: { quality: '65-90', speed: 4 }
|
||||
}
|
||||
}] : [])]
|
||||
}, {
|
||||
test: /\.stl$/,
|
||||
use: {
|
||||
loader: 'file-loader'
|
||||
}
|
||||
}, {
|
||||
test: /\.glsl$/,
|
||||
use: ['raw-loader']
|
||||
@ -75,8 +81,9 @@ module.exports = {
|
||||
title: 'Doodle3D Slicer',
|
||||
template: require('html-webpack-template'),
|
||||
inject: false,
|
||||
hash: !devMode,
|
||||
appMountId: 'app'
|
||||
})
|
||||
}),
|
||||
],
|
||||
devtool: devMode ? 'source-map' : false,
|
||||
devServer: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user