Merge branch 'develop'

This commit is contained in:
casperlamboo 2017-11-13 12:53:59 +01:00
commit d60b8d1ddb
35 changed files with 2418 additions and 187220 deletions

View File

@ -1,19 +1,25 @@
{
"env": {
// transpile to common node & browser compatible js, keeping modules
"module": {
"presets": [
["latest", {
["env", {
"targets": { "node": "6" },
"modules": false
}]
}],
"stage-0",
"react"
]
},
// transpile to common node & browser compatible js, using commonjs
"main": {
"presets": ["latest"]
"presets": ["env", "stage-0", "react"]
}
},
"plugins": [
"babel-plugin-transform-object-rest-spread"
"babel-plugin-transform-regenerator",
"babel-plugin-transform-object-rest-spread",
"babel-plugin-inline-import",
"babel-plugin-transform-class-properties",
"babel-plugin-transform-es2015-classes",
"babel-plugin-syntax-dynamic-import"
]
}
}

View File

@ -1,128 +0,0 @@
import React from 'react';
import { PRECISION } from '../src/constants.js';
export default class SlicerViewer extends React.Component {
state = {
layer: 0,
render: {
renderIntersectionPoints: false,
renderShape1: false,
renderShape2: true,
renderOuterLine: true,
renderInnerLine: true,
renderFill: true
}
};
changeSlider = (event) => {
this.setState({
layer: parseInt(event.target.value)
});
};
onControl = (event) => {
const section = event.target.value;
this.setState({
render: {
...this.state.render,
[section]: !this.state.render[section]
}
});
};
render() {
const { layer, render } = this.state;
const { layerIntersectionPoints, settings, layerShapes, slices } = this.props;
const numLayers = settings.dimensionsZ / settings.layerHeight;
const intersectionPoints = layerIntersectionPoints[layer + 1];
const shape = layerShapes[layer];
const slice = slices[layer];
return (
<div>
<svg viewBox={`-20 -20 ${settings.dimensionsX + 40} ${settings.dimensionsX + 40}`}>
<rect
width={settings.dimensionsX}
height={settings.dimensionsY}
fill="lightGrey"
/>
{render.renderIntersectionPoints && intersectionPoints.map(({ x, y }, i) => (
<circle key={i} cx={x} cy={y} r="0.3"/>
))}
{render.renderShape1 && shape && shape.closedShapes.map((closedShape, i) => (
<polygon
key={i}
points={closedShape.map(({ x, y }) => `${x} ${y}`).join(' ')}
fill="rgba(255, 0, 0, 0.5)"
/>
))}
{slice && slice.parts.map((slicePart, i) => (
<g key={i}>
{render.renderShape2 && <ClipperShapeSVG
shape={slicePart.shape}
scale={PRECISION}
color="rgba(0, 0, 0, 0.5)"
fill
/>}
{render.renderOuterLine && <ClipperShapeSVG
shape={slicePart.outerLine}
scale={1.0}
color="blue"
strokeWidth={settings.nozzleDiameter * 0.9}
/>}
{render.renderInnerLine && slicePart.innerLines.map((innerLine, i) => (
<ClipperShapeSVG
key={i}
shape={innerLine}
scale={1.0}
color="red"
strokeWidth={settings.nozzleDiameter * 0.9}
/>
))}
{render.renderFill && <ClipperShapeSVG
shape={slicePart.fill}
scale={1.0}
color="yellow"
strokeWidth={settings.nozzleDiameter * 0.9}
/>}
</g>
))}
</svg>
<div id="controls">
<input onChange={this.changeSlider} value={layer} type="range" min="0" max={numLayers} />
<p>Layer: {layer}</p>
<p><label><input type="checkbox" value="renderIntersectionPoints" onChange={this.onControl} checked={render.renderIntersectionPoints} />Render Intersection Points</label></p>
<p><label><input type="checkbox" value="renderShape1" onChange={this.onControl} checked={render.renderShape1} />Render Shape 1</label></p>
<p><label><input type="checkbox" value="renderShape2" onChange={this.onControl} checked={render.renderShape2} />Render Shape 2</label></p>
<p><label><input type="checkbox" value="renderOuterLine" onChange={this.onControl} checked={render.renderOuterLine} />Render Out Line</label></p>
<p><label><input type="checkbox" value="renderInnerLine" onChange={this.onControl} checked={render.renderInnerLine} />Render Inner Lines</label></p>
<p><label><input type="checkbox" value="renderFill" onChange={this.onControl} checked={render.renderFill} />Render Fill</label></p>
</div>
</div>
);
}
}
class ClipperShapeSVG extends React.Component {
render() {
const { shape, color = 'black', strokeWidth, scale, fill } = this.props;
const data = shape.paths.map(path => {
const pathData = path.map(({ X, Y }, i) => `${i === 0 ? 'M' : 'L '}${X * scale} ${Y * scale}`);
if (shape.closed) pathData.push('Z');
return pathData.join(' ');
}).join(' ');
return (
<path
d={data}
strokeWidth={typeof strokeWidth === 'number' ? strokeWidth : 1.0}
vectorEffect={typeof strokeWidth === 'number' ? 'none' : 'non-scaling-stroke'}
fill={fill ? color : 'none'}
stroke={!fill ? color : 'none'}
/>
);
}
}

View File

@ -1,54 +0,0 @@
import calculateLayersIntersections from 'src/sliceActions/calculateLayersIntersections.js';
import createLines from 'src/sliceActions/createLines.js';
import generateInfills from 'src/sliceActions/generateInfills.js';
import generateInnerLines from 'src/sliceActions/generateInnerLines.js';
import generateSupport from 'src/sliceActions/generateSupport.js';
import intersectionsToShapes from 'src/sliceActions/intersectionsToShapes.js';
import addBrim from 'src/sliceActions/addBrim.js';
import optimizePaths from 'src/sliceActions/optimizePaths.js';
import shapesToSlices from 'src/sliceActions/shapesToSlices.js';
import slicesToGCode from 'src/sliceActions/slicesToGCode.js';
import applyPrecision from 'src/sliceActions/applyPrecision.js';
import removePrecision from 'src/sliceActions/removePrecision.js';
export default function generateRawData(geometry, settings) {
const rawData = {};
const lines = createLines(geometry, settings);
const {
layerIntersectionIndexes,
layerIntersectionPoints
} = calculateLayersIntersections(lines, settings);
rawData.layerIntersectionPoints = layerIntersectionPoints
.map(intersectionPoints => intersectionPoints.map(intersectionPoint => intersectionPoint.clone()));
const layerShapes = intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings);
rawData.layerShapes = layerShapes
.map(({ closedShapes, openShapes }) => ({
closedShapes: closedShapes.map(closedShape => closedShape.map(vector => vector.clone())),
openShapes: openShapes.map(openShape => openShape.map(vector => vector.clone()))
}));
applyPrecision(layerShapes);
const slices = shapesToSlices(layerShapes, settings);
generateInnerLines(slices, settings);
generateInfills(slices, settings);
generateSupport(slices, settings);
addBrim(slices, settings);
optimizePaths(slices, settings);
removePrecision(slices);
rawData.slices = slices;
const gcode = slicesToGCode(slices, settings);
rawData.gcode = gcode;
return rawData;
}

View File

@ -1,11 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Doodle3D Slicer</title>
</head>
<body>
<p><a href="./viewer.html">Viewer</a></p>
<p><a href="./save.html">Save</a></p>
</body>
</html>

33
example/index.js Normal file
View File

@ -0,0 +1,33 @@
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'));
});

View File

@ -1,21 +0,0 @@
* {
margin: 0;
padding: 0;
}
#container {
position: relative;
}
#container, svg {
width: 100%;
height: 100%;
}
svg, #controls {
position: absolute;
}
input[type=range] {
width: 100%;
}

File diff suppressed because one or more lines are too long

View File

@ -157,6 +157,11 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1.js": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
@ -711,6 +716,15 @@
"regenerator-transform": "0.9.11"
}
},
"babel-plugin-transform-runtime": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
"integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
"dev": true,
"requires": {
"babel-runtime": "6.23.0"
}
},
"babel-plugin-transform-strict-mode": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
@ -926,6 +940,11 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"bowser": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.8.1.tgz",
"integrity": "sha512-NMPaR8ILtdLSWzxQtEs16XbxMcY8ohWGQ5V+TZSJS3fNUt/PBAGkF6YWO9B/4qWE23bK3o0moQKq8UyFEosYkA=="
},
"brace-expansion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
@ -1107,6 +1126,11 @@
"lazy-cache": "1.0.4"
}
},
"chain-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
"integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@ -1120,6 +1144,11 @@
"supports-color": "2.0.0"
}
},
"change-emitter": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
},
"chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
@ -1340,6 +1369,14 @@
"randombytes": "2.0.5"
}
},
"css-in-js-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.0.tgz",
"integrity": "sha512-yuWmPMD9FLi50Xf3k8W8oO3WM1eVnxEGCldCLyfusQ+CgivFk0s23yst4ooW6tfxMuSa03S6uUEga9UhX6GRrA==",
"requires": {
"hyphenate-style-name": "1.0.2"
}
},
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
@ -1515,6 +1552,11 @@
}
}
},
"dom-helpers": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz",
"integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo="
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@ -1596,6 +1638,14 @@
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=",
"dev": true
},
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "0.4.19"
}
},
"enhanced-resolve": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz",
@ -1895,6 +1945,27 @@
"websocket-driver": "0.6.5"
}
},
"fbjs": {
"version": "0.8.16",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
"integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
"requires": {
"core-js": "1.2.7",
"isomorphic-fetch": "2.2.1",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"promise": "7.3.1",
"setimmediate": "1.0.5",
"ua-parser-js": "0.7.17"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
}
}
},
"file-saver": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.3.tgz",
@ -3030,6 +3101,11 @@
"minimalistic-crypto-utils": "1.0.1"
}
},
"hoist-non-react-statics": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz",
"integrity": "sha1-ND24TGAYxlB3iJgkATWhQg7iLOA="
},
"home-or-tmp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@ -3120,6 +3196,11 @@
}
}
},
"html-webpack-template": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/html-webpack-template/-/html-webpack-template-6.0.2.tgz",
"integrity": "sha512-ekYCkU5t41wOu4kgGWvojVrREHap1qvZ8cbuy8ogH7EmscY4B0ElOEGQFFKpvig4GhhlVCK4mWaIik3dgz92SQ=="
},
"htmlparser2": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
@ -3230,12 +3311,31 @@
"integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=",
"dev": true
},
"hyphenate-style-name": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
"integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"ieee754": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=",
"dev": true
},
"imports-loader": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.7.1.tgz",
"integrity": "sha1-8gS180cCoywdt9SNidXoZ6BEElM=",
"requires": {
"loader-utils": "1.1.0",
"source-map": "0.5.6"
}
},
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
@ -3267,6 +3367,15 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"inline-style-prefixer": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
"integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
"requires": {
"bowser": "1.8.1",
"css-in-js-utils": "2.0.0"
}
},
"internal-ip": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz",
@ -3438,6 +3547,11 @@
"integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
"dev": true
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -3459,11 +3573,19 @@
"isarray": "1.0.0"
}
},
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"requires": {
"node-fetch": "1.7.3",
"whatwg-fetch": "2.0.3"
}
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.9.0",
@ -3519,6 +3641,11 @@
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
"dev": true
},
"keycode": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
"integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
},
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
@ -3588,6 +3715,16 @@
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
"dev": true
},
"lodash.merge": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz",
"integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@ -3598,7 +3735,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
"dev": true,
"requires": {
"js-tokens": "3.0.2"
}
@ -3634,6 +3770,24 @@
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
},
"material-ui": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.19.4.tgz",
"integrity": "sha1-ypzcqKqLtZTfrF2zjsn/BFoyNYc=",
"requires": {
"babel-runtime": "6.23.0",
"inline-style-prefixer": "3.0.8",
"keycode": "2.1.9",
"lodash.merge": "4.6.0",
"lodash.throttle": "4.1.1",
"prop-types": "15.6.0",
"react-event-listener": "0.5.1",
"react-transition-group": "1.2.1",
"recompose": "0.26.0",
"simple-assign": "0.1.0",
"warning": "3.0.0"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -3828,6 +3982,15 @@
"lower-case": "1.1.4"
}
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"requires": {
"encoding": "0.1.12",
"is-stream": "1.1.0"
}
},
"node-forge": {
"version": "0.6.33",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz",
@ -3912,8 +4075,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object.omit": {
"version": "2.0.1",
@ -4226,6 +4388,24 @@
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
"dev": true
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "2.0.6"
}
},
"prop-types": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1"
}
},
"proxy-addr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
@ -4341,6 +4521,172 @@
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
"dev": true
},
"react": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.1.0.tgz",
"integrity": "sha512-hvKYlKqde2JNnNiEzORvSA0J1L7uSZ43l+J89ZNoP4EXxQrVNH0CFj8vorfPou3w+1ou1BNMBir2VVsuXtETRA==",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
},
"dependencies": {
"asap": {
"version": "2.0.6",
"bundled": true
},
"core-js": {
"version": "1.2.7",
"bundled": true
},
"encoding": {
"version": "0.1.12",
"bundled": true,
"requires": {
"iconv-lite": "0.4.19"
}
},
"fbjs": {
"version": "0.8.16",
"bundled": true,
"requires": {
"core-js": "1.2.7",
"isomorphic-fetch": "2.2.1",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"promise": "7.3.1",
"setimmediate": "1.0.5",
"ua-parser-js": "0.7.17"
}
},
"iconv-lite": {
"version": "0.4.19",
"bundled": true
},
"is-stream": {
"version": "1.1.0",
"bundled": true
},
"isomorphic-fetch": {
"version": "2.2.1",
"bundled": true,
"requires": {
"node-fetch": "1.7.3",
"whatwg-fetch": "2.0.3"
}
},
"js-tokens": {
"version": "3.0.2",
"bundled": true
},
"loose-envify": {
"version": "1.3.1",
"bundled": true,
"requires": {
"js-tokens": "3.0.2"
}
},
"node-fetch": {
"version": "1.7.3",
"bundled": true,
"requires": {
"encoding": "0.1.12",
"is-stream": "1.1.0"
}
},
"object-assign": {
"version": "4.1.1",
"bundled": true
},
"promise": {
"version": "7.3.1",
"bundled": true,
"requires": {
"asap": "2.0.6"
}
},
"prop-types": {
"version": "15.6.0",
"bundled": true,
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1"
}
},
"setimmediate": {
"version": "1.0.5",
"bundled": true
},
"ua-parser-js": {
"version": "0.7.17",
"bundled": true
},
"whatwg-fetch": {
"version": "2.0.3",
"bundled": true
}
}
},
"react-dom": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0.tgz",
"integrity": "sha512-i9in5qW3H2PDinUPD9bnQK7tLAD8LhjYQ+fXi3nJOvVnxOO3ErHq6RNEnKY7pbjTPt155e74q7al8eBUuyLtew==",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
}
},
"react-event-listener": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.5.1.tgz",
"integrity": "sha1-ujYHbke8N8Wmf/XM1Kn/DxViEEA=",
"requires": {
"babel-runtime": "6.26.0",
"fbjs": "0.8.16",
"prop-types": "15.6.0",
"warning": "3.0.0"
},
"dependencies": {
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "2.4.1",
"regenerator-runtime": "0.11.0"
}
},
"regenerator-runtime": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
"integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A=="
}
}
},
"react-tap-event-plugin": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/react-tap-event-plugin/-/react-tap-event-plugin-3.0.2.tgz",
"integrity": "sha1-KANxZ3uIHDE3bgAnoLhtLG3gOe4=",
"requires": {
"fbjs": "0.8.16"
}
},
"react-transition-group": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
"integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
"requires": {
"chain-function": "1.0.0",
"dom-helpers": "3.2.1",
"loose-envify": "1.3.1",
"prop-types": "15.6.0",
"warning": "3.0.0"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@ -4410,6 +4756,17 @@
"set-immediate-shim": "1.0.1"
}
},
"recompose": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
"integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
"requires": {
"change-emitter": "0.1.6",
"fbjs": "0.8.16",
"hoist-non-react-statics": "2.3.1",
"symbol-observable": "1.0.4"
}
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@ -4696,8 +5053,7 @@
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
"dev": true
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
"setprototypeof": {
"version": "1.0.3",
@ -4720,6 +5076,11 @@
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"simple-assign": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
"integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
},
"slash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
@ -4770,8 +5131,7 @@
"source-map": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
"dev": true
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
},
"source-map-support": {
"version": "0.4.15",
@ -4920,6 +5280,11 @@
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
"symbol-observable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
},
"tapable": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz",
@ -4992,6 +5357,11 @@
"mime-types": "2.1.15"
}
},
"ua-parser-js": {
"version": "0.7.17",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
},
"uglify-js": {
"version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
@ -5158,6 +5528,14 @@
"indexof": "0.0.1"
}
},
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "1.3.1"
}
},
"watchpack": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz",
@ -5296,6 +5674,11 @@
"integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=",
"dev": true
},
"whatwg-fetch": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
},
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",

View File

@ -12,10 +12,17 @@
"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",

View File

@ -1,19 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Doodle3D Slicer - Save</title>
<script type="text/javascript" src="../jspm_packages/system.js"></script>
<script type="text/javascript" src="../jspm.config.js"></script>
<script type="text/javascript">
System.import('./save.js');
</script>
</head>
<body>
</body>
</html>

View File

@ -1,27 +0,0 @@
import * as THREE from 'three';
import { defaultSettings, sliceGeometry } from 'src/index.js';
import fileSaver from 'file-saver';
const settings = {
...defaultSettings.base,
...defaultSettings.material.pla,
...defaultSettings.printer.ultimaker2go,
...defaultSettings.quality.high
};
const jsonLoader = new THREE.JSONLoader();
jsonLoader.load('models/airplane.json', async geometry => {
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2));
geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.0, 50)));
geometry.computeFaceNormals();
const onProgress = ({ progress: { done, total, action } }) => {
const percentage = `${(done / total * 100).toFixed()}%`
document.write(`<p>${action}, ${percentage}</p>`);
};
const gcode = await sliceGeometry(settings, geometry, null, false, onProgress);
const file = new File([gcode], 'gcode.gcode', { type: 'text/plain' });
fileSaver.saveAs(file);
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Doodle3D Slicer - Viewer</title>
<style>
#gcode {
font-family: monospace;
}
</style>
<script type="text/javascript" src="../jspm_packages/system.js"></script>
<script type="text/javascript" src="../jspm.config.js"></script>
<link href="main.css" rel="stylesheet"/>
<script type="text/javascript">
System.import('example/viewer.js');
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>

View File

@ -1,35 +0,0 @@
import 'three.js';
import 'three.js/loaders/STLLoader';
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import * as SLICER from 'src/index.js';
import generateRawData from './generateRawData.js';
import SlicerViewer from './SlicerViewer.js';
const settings = new SLICER.Settings({
...SLICER.printerSettings['ultimaker2go'],
...SLICER.userSettings
});
const stlLoader = new THREE.STLLoader();
stlLoader.load('stl/Airplane.stl', (geometry) => {
geometry = new THREE.Geometry().fromBufferGeometry(geometry);
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2));
geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.1, 50)));
// geometry.applyMatrix(new THREE.Matrix4().scale(0.8));
geometry.mergeVertices();
geometry.computeFaceNormals();
const rawData = generateRawData(geometry, settings);
render(
<SlicerViewer
layerIntersectionPoints={rawData.layerIntersectionPoints}
layerShapes={rawData.layerShapes}
slices={rawData.slices}
settings={settings}
/>,
document.getElementById('container')
);
});

View File

@ -6,15 +6,17 @@ const babelLoader = {
loader: 'babel-loader',
options: {
presets: [
['latest', {
'modules': false,
'loose': true
}]
require('babel-preset-env'),
require('babel-preset-react')
],
plugins: [
require('babel-plugin-transform-object-rest-spread'),
require('babel-plugin-transform-class-properties'),
require('babel-plugin-transform-runtime')
],
plugins: [require('babel-plugin-transform-object-rest-spread')],
babelrc: false
}
}
};
module.exports = {
entry: './index.js',
@ -35,12 +37,13 @@ module.exports = {
test: /\.js$/,
exclude: /node_modules/,
use: babelLoader
},
{
}, { // make THREE global available to three.js examples
test: /three\/examples\/.+\.js/,
use: 'imports-loader?THREE=three'
}, {
test: /\.yml$/,
use: 'yml-loader'
},
{
}, {
test: /\.worker\.js$/,
use: ['worker-loader', babelLoader]
}
@ -48,7 +51,10 @@ module.exports = {
},
plugins: [
new HTMLWebpackPlugin({
title: 'Doodle3D Slicer - Simple example'
title: 'Doodle3D Slicer - Simple example',
template: require('html-webpack-template'),
inject: false,
appMountId: 'app'
}),
],
devtool: "source-map",

1336
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,12 +15,29 @@
},
"dependencies": {
"@doodle3d/clipper-js": "^1.0.7",
"lodash": "^4.17.4",
"material-ui": "^0.19.4",
"proptypes": "^1.1.0",
"react": "^16.1.0",
"react-dom": "^16.1.0",
"react-jss": "^7.2.0",
"react-resize-detector": "^1.1.0",
"three": "^0.83.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-latest": "^6.24.1"
"babel-plugin-inline-import": "^2.0.6",
"babel-preset-stage-0": "^6.24.1",
"babel-plugin-syntax-dynamic-import": "^6.18.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-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-cli": "6.24.1",
"babel-core": "6.24.1",
"babel-loader": "7.0.0",
"babel-plugin-add-module-exports": "0.2.1",
"babel-preset-es2015": "6.24.1"
},
"repository": {
"type": "git",

View File

@ -1,29 +0,0 @@
import * as THREE from 'three';
import { defaultSettings, sliceGeometry } from 'doodle3d-slicer';
import fileURL from '!url-loader!./models/combingtest.json';
import fileSaver from 'file-saver';
const settings = {
...defaultSettings.base,
...defaultSettings.material.pla,
...defaultSettings.printer.ultimaker2go,
...defaultSettings.quality.high
};
const jsonLoader = new THREE.JSONLoader();
jsonLoader.load(fileURL, geometry => {
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2));
geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.0, 50)));
const onProgress = ({ progress: { done, total, action } }) => {
const percentage = `${(done / total * 100).toFixed()}%`
document.write(`<p>${action}, ${percentage}</p>`);
};
const { filament, duration, gcode } = sliceGeometry(settings, geometry, null, true, onProgress);
// console.log('filament: ', filament);
// console.log('duration: ', duration);
// document.body.innerHTML = gcode.replace(/(?:\r\n|\r|\n)/g, '<br />');
const file = new File([gcode], 'gcode.gcode', { type: 'text/plain' });
fileSaver.saveAs(file);
});

View File

@ -1,4 +1,5 @@
import { sliceGeometry, sliceMesh } from './slicer.js';
import Interface from './interface/index.js';
import baseSettings from './settings/default.yml';
import printerSettings from './settings/printer.yml';
import materialSettings from './settings/material.yml';
@ -14,5 +15,6 @@ const defaultSettings = {
export {
sliceGeometry,
sliceMesh,
Interface,
defaultSettings
};

View File

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'proptypes';
import _ from 'lodash';
import injectSheet from 'react-jss';
import MaterialUISelectField from 'material-ui/SelectField'
import MaterialUICheckbox from 'material-ui/Checkbox';
import MaterialUITextField from 'material-ui/TextField';
import { grey100, grey300, grey500 } from 'material-ui/styles/colors';
const styles = {
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) => (
<MaterialUISelectField
{ ...props }
value={context.state[props.name]}
onChange={(event, index, value) => context.onChange(props.name, value)}
/>
);
SelectField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
export const TextField = (props, context) => (
<MaterialUITextField
{ ...props }
value={_.get(context.state, props.name)}
onChange={(event, value) => context.onChange(props.name, value)}
/>
);
TextField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
export const Checkbox = (props, context) => (
<MaterialUICheckbox
{ ...props }
checked={_.get(context.state, props.name)}
onCheck={(event, value) => context.onChange(props.name, value)}
/>
);
Checkbox.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };

157
src/interface/Settings.js Normal file
View File

@ -0,0 +1,157 @@
import React from 'react';
import PropTypes from 'proptypes';
import _ from 'lodash';
import { Tabs, Tab } from 'material-ui/Tabs';
import MenuItem from 'material-ui/MenuItem';
import injectSheet from 'react-jss';
import { SettingsGroup, SelectField, TextField, Checkbox } from './FormComponents.js';
import { grey500 } from 'material-ui/styles/colors';
const styles = {
textFieldRow: {
display: 'flex'
}
};
class Settings extends React.Component {
constructor(props) {
super(props);
this.state = {
settings: props.initialSettings,
printers: props.defaultPrinter,
quality: props.defaultQuality,
material: props.defaultMaterial
};
}
changeSettings = (fieldName, value) => {
const { onChange } = this.props;
let state;
switch (fieldName) {
case 'printers':
case 'quality':
case 'material':
state = {
[fieldName]: value,
settings: _.merge({}, this.state.settings, this.props[fieldName][value])
};
break;
default:
state = _.set(_.cloneDeep(this.state), fieldName, value);
break;
}
if (onChange) onChange(state.settings);
if (state) this.setState(state);
};
getChildContext() {
return { state: this.state, onChange: this.changeSettings };
}
render() {
const { classes, printers, quality, material } = this.props;
return (
<Tabs>
<Tab label="basic">
<div>
<SelectField name="printers" floatingLabelText="Printer" fullWidth>
{Object.entries(printers).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField name="quality" floatingLabelText="Quality" fullWidth>
{Object.entries(quality).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField name="material" floatingLabelText="Material" fullWidth>
{Object.entries(material).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
</div>
</Tab>
<Tab label="advanced">
<div>
<SettingsGroup name="Printer dimensions">
<div className={classes.textFieldRow}>
<TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" />
<TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" />
<TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" />
</div>
</SettingsGroup>
<SettingsGroup name="Nozzle">
<TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" />
</SettingsGroup>
<SettingsGroup name="Bed">
<TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" />
<Checkbox name="settings.heatedBed" label="Heated" />
</SettingsGroup>
<SettingsGroup name="Material">
<TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" />
<TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" />
</SettingsGroup>
<SettingsGroup name="Thickness">
<TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" />
<TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" />
<TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" />
</SettingsGroup>
<SettingsGroup name="Retraction">
<Checkbox name="settings.retraction.enabled" label="Enabled" />
<TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" />
<TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" />
</SettingsGroup>
<SettingsGroup name="Travel">
<TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" />
<Checkbox name="settings.combing" label="Combing" />
</SettingsGroup>
<SettingsGroup name="Inner shell">
<TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Outer shell">
<TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Inner infill">
<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.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Outer infill">
<TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Brim">
<TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" />
<TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="First layer">
<TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
</div>
</Tab>
</Tabs>
);
}
}
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);

272
src/interface/index.js Normal file
View File

@ -0,0 +1,272 @@
import _ from 'lodash';
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'proptypes';
import { placeOnGround, createScene, createGcodeGeometry } from './utils.js';
import injectSheet from 'react-jss';
import { sliceGeometry } from '../slicer.js';
import RaisedButton from 'material-ui/RaisedButton';
import Slider from 'material-ui/Slider';
import { grey100, grey300 } from 'material-ui/styles/colors';
import Settings from './Settings.js';
import baseSettings from '../settings/default.yml';
import printerSettings from '../settings/printer.yml';
import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml';
import ReactResizeDetector from 'react-resize-detector';
const styles = {
container: {
position: 'relative',
display: 'flex',
height: '100%',
backgroundColor: grey100,
overflow: 'hidden'
},
controlBar: {
position: 'absolute',
bottom: '10px',
left: '10px'
},
d3View: {
flexGrow: 1
},
canvas: {
position: 'absolute'
},
sliceBar: {
width: '240px',
padding: '0 10px',
overflowY: 'auto',
backgroundColor: 'white',
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: {
listStyleType: 'none'
},
button: {
margin: '5px 0'
},
controlButton: {
marginRight: '2px'
}
};
class Interface extends React.Component {
constructor(props) {
super(props);
const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props;
this.state = {
controlMode: 'translate',
isSlicing: false,
sliced: false,
settings: _.merge(
{},
defaultSettings,
printers[defaultPrinter],
quality[defaultQuality],
material[defaultMaterial]
)
};
}
componentDidMount() {
const { canvas } = this.refs;
const scene = createScene(canvas, this.props, this.state);
this.setState(scene);
}
resetMesh = () => {
const { mesh, render } = this.state;
if (mesh) {
mesh.position.set(0, 0, 0);
mesh.scale.set(1, 1, 1);
mesh.rotation.set(0, 0, 0);
mesh.updateMatrix();
placeOnGround(mesh);
render();
}
};
reset = () => {
const { control, mesh, render, gcode, scene } = this.state;
control.enabled = true;
control.setSize(1);
control.visible = true;
mesh.visible = true;
scene.remove(gcode.linePreview);
gcode.linePreview.geometry.dispose();
this.setState({ sliced: false, gcode: null });
render();
};
slice = async () => {
const { mesh, render, scene, control, settings } = this.state;
const { dimensions } = settings;
const centerX = dimensions.x / 2;
const centerY = dimensions.y / 2;
const geometry = mesh.geometry.clone();
mesh.updateMatrix();
this.setState({ isSlicing: true, progress: { actions: [], percentage: 0 } });
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
const gcode = await sliceGeometry(settings, geometry, matrix, false, true, ({ progress }) => {
this.setState({ progress: {
actions: [...this.state.progress.actions, progress.action],
percentage: progress.done / progress.total
} });
});
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) => {
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) {
const { control, box, render, setSize } = this.state;
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode);
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
const { dimensions } = nextState.settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
render();
}
if (setSize && nextProps.width !== this.props.width || nextProps.height !== this.props.height || nextProps.pixelRatio !== this.props.pixelRatio) {
setSize(nextProps.width, nextProps.height, nextProps.pixelRatio);
}
}
onResize = (width, height) => {
window.requestAnimationFrame(() => {
const { setSize } = this.state;
const { pixelRatio } = this.props;
setSize(width, height, pixelRatio);
});
};
render() {
const { width, height, classes, onCompleteActions, defaultPrinter, defaultQuality, defaultMaterial } = this.props;
const { sliced, isSlicing, progress, gcode, controlMode, settings } = this.state;
return (
<div className={classes.container}>
<div className={classes.d3View}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
<canvas className={classes.canvas} ref="canvas" width={width} height={height} />
{!sliced && <div className={classes.controlBar}>
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} primary label="reset" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} primary label="translate" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} primary label="rotate" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} primary label="scale" />
</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>
);
}
}
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);

82
src/interface/utils.js Normal file
View File

@ -0,0 +1,82 @@
import * as THREE from 'three';
import 'three/examples/js/controls/EditorControls';
import 'three/examples/js/controls/TransformControls';
export function placeOnGround(mesh) {
const boundingBox = new THREE.Box3().setFromObject(mesh);
mesh.position.y -= boundingBox.min.y;
mesh.updateMatrix();
}
export function createScene(canvas, props, state) {
const { geometry, pixelRatio } = props;
const { controlMode, settings } = state;
// center geometry
geometry.computeBoundingBox();
const centerX = (geometry.boundingBox.max.x + geometry.boundingBox.min.x) / 2;
const centerY = (geometry.boundingBox.max.y + geometry.boundingBox.min.y) / 2;
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 });
renderer.setClearColor(0xffffff, 0);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
camera.position.set(0, 400, 300);
const setSize = (width, height, pixelRatio = 1) => {
renderer.setSize(width, height);
renderer.setPixelRatio(pixelRatio);
camera.aspect = width / height;
camera.updateProjectionMatrix();
render();
};
const directionalLight = new THREE.DirectionalLight(0xd5d5d5);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
const light = new THREE.AmbientLight(0x808080);
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);
};
control.addEventListener('change', render);
editorControls.addEventListener('change', render);
const box = new THREE.BoxHelper();
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);
const { dimensions } = settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
return { control, editorControls, scene, mesh, camera, renderer, render, box, setSize };
}

View File

@ -1,20 +1,20 @@
import * as THREE from 'three';
import { PRECISION } from '../../constants.js';
const MOVE = 'G';
const M_COMMAND = 'M';
const FAN_SPEED = 'S';
const SPEED = 'F';
const EXTRUDER = 'E';
const POSITION_X = 'X';
const POSITION_Y = 'Y';
const POSITION_Z = 'Z';
export const MOVE = 'G';
export const M_COMMAND = 'M';
export const FAN_SPEED = 'S';
export const SPEED = 'F';
export const EXTRUDER = 'E';
export const POSITION_X = 'X';
export const POSITION_Y = 'Y';
export const POSITION_Z = 'Z';
export default class {
constructor(nozzleToFilamentRatio) {
this._nozzleToFilamentRatio = nozzleToFilamentRatio;
this._gcode = '';
this._gcode = [];
this._currentValues = {};
this._nozzlePosition = new THREE.Vector2(0, 0);
this._extruder = 0.0;
@ -24,24 +24,7 @@ export default class {
}
_addGCode(command) {
let str = '';
let first = true;
for (const action in command) {
const value = command[action];
const currentValue = this._currentValues[action];
if (first) {
str = action + value;
first = false;
} else if (currentValue !== value) {
str += ` ${action}${value}`;
this._currentValues[action] = value;
}
}
this._gcode += `${str}\n`;
this._gcode.push(command);
}
turnFanOn(fanSpeed) {

View File

@ -1,3 +1,4 @@
import * as THREE from 'three';
import calculateLayersIntersections from './calculateLayersIntersections.js';
import createLines from './createLines.js';
import generateInfills from './generateInfills.js';
@ -13,7 +14,7 @@ import detectOpenClosed from './detectOpenClosed.js';
import applyPrecision from './applyPrecision.js';
// import removePrecision from './removePrecision.js';
export default function(settings, geometry, onProgress) {
export default function(settings, geometry, constructLinePreview, onProgress) {
const totalStages = 12;
let current = -1;
const updateProgress = (action) => {
@ -72,5 +73,67 @@ export default function(settings, geometry, onProgress) {
updateProgress('Finished');
if (constructLinePreview) gcode.linePreview = createGcodeGeometry(gcode.gcode);
gcode.gcode = gcodeToString(gcode.gcode);
return gcode;
}
function gcodeToString(gcode) {
const currentValues = {};
return gcode.reduce((string, command) => {
let first = true;
for (const action in command) {
const value = command[action];
const currentValue = currentValues[action];
if (first) {
string += action + value;
first = false;
} else if (currentValue !== value) {
string += ` ${action}${value}`;
currentValues[action] = value;
}
}
string += '\n';
return string;
}, '');
}
const MAX_SPEED = 100 * 60;
function createGcodeGeometry(gcode) {
const positions = [];
const colors = [];
let lastPoint
for (let i = 0; i < gcode.length; i ++) {
const { G, F, X, Y, Z } = gcode[i];
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 (lastPoint) positions.push(lastPoint[0], lastPoint[1], lastPoint[2]);
positions.push(Y, Z, X);
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
}
lastPoint = [Y, Z, X];
}
}
const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors });
const linePreview = new THREE.LineSegments(geometry, material);
return linePreview;
}

View File

@ -2,8 +2,8 @@ import * as THREE from 'three';
import slice from './sliceActions/slice.js';
import SlicerWorker from './slicer.worker.js';
export function sliceMesh(settings, mesh, sync = false, onProgress) {
if (typeof mesh === 'undefined' || !mesh.isMesh) {
export function sliceMesh(settings, mesh, sync = false, constructLinePreview = false, onProgress) {
if (!mesh || !mesh.isMesh) {
throw new Error('Provided mesh is not intance of THREE.Mesh');
}
@ -12,8 +12,8 @@ export function sliceMesh(settings, mesh, sync = false, onProgress) {
return sliceGeometry(settings, geometry, matrix, sync, onProgress);
}
export function sliceGeometry(settings, geometry, matrix, sync = false, onProgress) {
if (typeof geometry === 'undefined') {
export function sliceGeometry(settings, geometry, matrix, sync = false, constructLinePreview = false, onProgress) {
if (!geometry) {
throw new Error('Missing required geometry argument');
} else if (geometry.isBufferGeometry) {
geometry = new THREE.Geometry().fromBufferGeometry(geometry);
@ -27,22 +27,22 @@ export function sliceGeometry(settings, geometry, matrix, sync = false, onProgre
throw new Error('Geometry does not contain any data');
}
if (matrix) {
if (matrix && matrix.isMatrix4) {
geometry.applyMatrix(matrix);
}
if (sync) {
return sliceSync(settings, geometry, onProgress);
return sliceSync(settings, geometry, constructLinePreview, onProgress);
} else {
return sliceAsync(settings, geometry, onProgress);
return sliceAsync(settings, geometry, constructLinePreview, onProgress);
}
}
function sliceSync(settings, geometry, onProgress) {
return slice(settings, geometry, onProgress);
function sliceSync(settings, geometry, constructLinePreview, onProgress) {
return slice(settings, geometry, constructLinePreview, onProgress);
}
function sliceAsync(settings, geometry, onProgress) {
function sliceAsync(settings, geometry, constructLinePreview, onProgress) {
return new Promise((resolve, reject) => {
// create the slicer worker
const slicerWorker = new SlicerWorker();
@ -58,6 +58,20 @@ function sliceAsync(settings, geometry, onProgress) {
switch (message) {
case 'SLICE': {
slicerWorker.terminate();
if (data.gcode.linePreview) {
const geometry = new THREE.BufferGeometry();
const { position, color } = data.gcode.linePreview;
geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(position), 3));
geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(color), 3));
const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors });
const linePreview = new THREE.LineSegments(geometry, material);
data.gcode.linePreview = linePreview;
}
resolve(data.gcode);
break;
}
@ -76,7 +90,8 @@ function sliceAsync(settings, geometry, onProgress) {
message: 'SLICE',
data: {
settings,
geometry
geometry,
constructLinePreview
}
});
});

View File

@ -15,15 +15,23 @@ self.addEventListener('message', (event) => {
const { message, data } = event.data;
switch (message) {
case 'SLICE': {
const { settings, geometry: JSONGeometry } = data;
const buffers = [];
const { settings, geometry: JSONGeometry, constructLinePreview } = data;
const { geometry } = loader.parse(JSONGeometry.data);
const gcode = slice(settings, geometry, onProgress);
const gcode = slice(settings, geometry, constructLinePreview, onProgress);
if (gcode.linePreview) {
const position = gcode.linePreview.geometry.getAttribute('position').array;
const color = gcode.linePreview.geometry.getAttribute('color').array;
buffers.push(position.buffer, color.buffer);
gcode.linePreview = { position, color };
}
self.postMessage({
message: 'SLICE',
data: { gcode }
});
}, buffers);
break;
}
}