mirror of
https://github.com/Doodle3D/Doodle3D-Slicer.git
synced 2024-11-26 15:34:57 +01:00
Merge branch 'develop'
This commit is contained in:
commit
e723db92a0
19
.babelrc
Normal file
19
.babelrc
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
// transpile to common node & browser compatible js, keeping modules
|
||||||
|
"module": {
|
||||||
|
"presets": [
|
||||||
|
["latest", {
|
||||||
|
"modules": false
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// transpile to common node & browser compatible js, using commonjs
|
||||||
|
"main": {
|
||||||
|
"presets": ["latest"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"babel-plugin-transform-object-rest-spread"
|
||||||
|
]
|
||||||
|
}
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
|
||||||
jspm_packages/*
|
jspm_package
|
||||||
|
|
||||||
node_modules/*
|
node_modules
|
||||||
|
|
||||||
bundle.js
|
lib
|
||||||
|
module
|
||||||
|
dist
|
4
.npmignore
Normal file
4
.npmignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
example
|
||||||
|
simpleExample
|
55
README.md
55
README.md
@ -1,10 +1,12 @@
|
|||||||
# Doodle3D-Slicer
|
# Doodle3D-Slicer
|
||||||
|
|
||||||
JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box
|
JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { defaultSettings, Slicer } from 'Doodle3D/Doodle3D-Slicer';
|
import { defaultSettings, sliceGeometry } from 'Doodle3D/Doodle3D-Slicer';
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings.base,
|
...defaultSettings.base,
|
||||||
@ -13,13 +15,48 @@ const settings = {
|
|||||||
...defaultSettings.quality.high
|
...defaultSettings.quality.high
|
||||||
};
|
};
|
||||||
|
|
||||||
const geometry = new THREE.TorusGeometry(20, 10, 30, 30);
|
const geometry = new THREE.TorusGeometry(20, 10, 30, 30).clone();
|
||||||
|
|
||||||
const slicer = new SLICER.Slicer();
|
const gcode = await sliceGeometry(settings, geometry);
|
||||||
slicer.setGeometry(geometry);
|
|
||||||
const gcode = await slicer.slice(settings)
|
|
||||||
.progress(({ done, total, action }) => {
|
|
||||||
const percentage = `${(done / total * 100).toFixed()}%`
|
|
||||||
console.log(action, percentage);
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
**Settings**
|
||||||
|
```javascript
|
||||||
|
import { defaultSettings } from 'Doodle3D/Doodle3D-Slicer';
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
...defaultSettings.base,
|
||||||
|
...defaultSettings.material.pla,
|
||||||
|
...defaultSettings.printer.ultimaker2go,
|
||||||
|
...defaultSettings.quality.high
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Create settings object to be used by the slicer
|
||||||
|
|
||||||
|
**Slice Mesh**
|
||||||
|
```javascript
|
||||||
|
import { sliceMesh } from 'Doodle3D/Doodle3D-Slicer';
|
||||||
|
|
||||||
|
GCode: String = sliceMesh(settings: Object, mesh: THREE.Mesh, [sync: Boolean = false, onProgress: Func ])
|
||||||
|
```
|
||||||
|
Slice function that accepts Meshes
|
||||||
|
- Settings: settings object (see [settings](#settings))
|
||||||
|
- Mesh: THREE.Mesh instance that contains the geometry
|
||||||
|
- Sync: determines if the slicing progress will be sync (blocking) or async (non-blocking). A webworker is used to slice async
|
||||||
|
- onProgress: progress callback
|
||||||
|
|
||||||
|
**Slice Geometry**
|
||||||
|
```javascript
|
||||||
|
import { sliceGeometry } from 'Doodle3D/Doodle3D-Slicer';
|
||||||
|
|
||||||
|
GCode: String = sliceGeometry(settings: Object, geometry: THREE.Geometry | THREE.BufferGeometry, [matrix: THREE.Matrix, sync: Boolean = false, onProgress: Func ])
|
||||||
|
```
|
||||||
|
|
||||||
|
Slice function that accepts Geometry
|
||||||
|
- Settings: settings object (see [settings](#settings))
|
||||||
|
- Geometry: THREE.Geometry instance
|
||||||
|
- matrix: matrix that can control the scale, rotation and position of the model
|
||||||
|
- Sync: determines if the slicing progress will be sync (blocking) or async (non-blocking). A webworker is used to slice async
|
||||||
|
- onProgress: progress callback
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { defaultSettings, Slicer } from 'src/index.js';
|
import { defaultSettings, sliceGeometry } from 'src/index.js';
|
||||||
import { saveAs } from 'file-saver';
|
import fileSaver from 'file-saver';
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
...defaultSettings.base,
|
...defaultSettings.base,
|
||||||
@ -12,16 +12,16 @@ const settings = {
|
|||||||
const jsonLoader = new THREE.JSONLoader();
|
const jsonLoader = new THREE.JSONLoader();
|
||||||
jsonLoader.load('models/airplane.json', async geometry => {
|
jsonLoader.load('models/airplane.json', async geometry => {
|
||||||
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2));
|
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / -2));
|
||||||
geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.1, 50)));
|
geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(50, -0.0, 50)));
|
||||||
geometry.computeFaceNormals();
|
geometry.computeFaceNormals();
|
||||||
|
|
||||||
const slicer = new Slicer().setGeometry(geometry);
|
const onProgress = ({ progress: { done, total, action } }) => {
|
||||||
const gcode = await slicer.slice(settings)
|
const percentage = `${(done / total * 100).toFixed()}%`
|
||||||
.progress(({ done, total, action }) => {
|
document.write(`<p>${action}, ${percentage}</p>`);
|
||||||
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' });
|
const file = new File([gcode], 'gcode.gcode', { type: 'text/plain' });
|
||||||
saveAs(file);
|
fileSaver.saveAs(file);
|
||||||
});
|
});
|
||||||
|
479
jspm.config.js
479
jspm.config.js
@ -1,479 +0,0 @@
|
|||||||
SystemJS.config({
|
|
||||||
paths: {
|
|
||||||
"github:": "jspm_packages/github/",
|
|
||||||
"npm:": "jspm_packages/npm/",
|
|
||||||
"example/": "example/",
|
|
||||||
"slicer/": "src/"
|
|
||||||
},
|
|
||||||
browserConfig: {
|
|
||||||
"baseURL": "/"
|
|
||||||
},
|
|
||||||
devConfig: {
|
|
||||||
"map": {
|
|
||||||
"babel-runtime": "npm:babel-runtime@5.8.38",
|
|
||||||
"core-js": "npm:core-js@1.2.7",
|
|
||||||
"plugin-babel": "npm:systemjs-plugin-babel@0.0.12",
|
|
||||||
"react": "npm:react@15.3.2",
|
|
||||||
"domain": "github:jspm/nodelibs-domain@0.2.0-alpha",
|
|
||||||
"zlib": "github:jspm/nodelibs-zlib@0.2.0-alpha",
|
|
||||||
"https": "github:jspm/nodelibs-https@0.2.0-alpha",
|
|
||||||
"react-dom": "npm:react-dom@15.3.2",
|
|
||||||
"babel-plugin-transform-react-jsx": "npm:babel-plugin-transform-react-jsx@6.8.0",
|
|
||||||
"file-saver": "npm:file-saver@1.3.3"
|
|
||||||
},
|
|
||||||
"packages": {
|
|
||||||
"npm:babel-runtime@5.8.38": {
|
|
||||||
"map": {}
|
|
||||||
},
|
|
||||||
"npm:react@15.3.2": {
|
|
||||||
"map": {
|
|
||||||
"object-assign": "npm:object-assign@4.1.0",
|
|
||||||
"loose-envify": "npm:loose-envify@1.2.0",
|
|
||||||
"fbjs": "npm:fbjs@0.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:fbjs@0.8.5": {
|
|
||||||
"map": {
|
|
||||||
"loose-envify": "npm:loose-envify@1.2.0",
|
|
||||||
"object-assign": "npm:object-assign@4.1.0",
|
|
||||||
"promise": "npm:promise@7.1.1",
|
|
||||||
"isomorphic-fetch": "npm:isomorphic-fetch@2.2.1",
|
|
||||||
"ua-parser-js": "npm:ua-parser-js@0.7.10",
|
|
||||||
"immutable": "npm:immutable@3.8.1",
|
|
||||||
"core-js": "npm:core-js@1.2.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:loose-envify@1.2.0": {
|
|
||||||
"map": {
|
|
||||||
"js-tokens": "npm:js-tokens@1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:promise@7.1.1": {
|
|
||||||
"map": {
|
|
||||||
"asap": "npm:asap@2.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:isomorphic-fetch@2.2.1": {
|
|
||||||
"map": {
|
|
||||||
"whatwg-fetch": "npm:whatwg-fetch@1.0.0",
|
|
||||||
"node-fetch": "npm:node-fetch@1.6.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:node-fetch@1.6.3": {
|
|
||||||
"map": {
|
|
||||||
"encoding": "npm:encoding@0.1.12",
|
|
||||||
"is-stream": "npm:is-stream@1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:encoding@0.1.12": {
|
|
||||||
"map": {
|
|
||||||
"iconv-lite": "npm:iconv-lite@0.4.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-zlib@0.1.4": {
|
|
||||||
"map": {
|
|
||||||
"readable-stream": "npm:readable-stream@2.1.5",
|
|
||||||
"pako": "npm:pako@0.2.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:babel-plugin-transform-react-jsx@6.8.0": {
|
|
||||||
"map": {
|
|
||||||
"babel-helper-builder-react-jsx": "npm:babel-helper-builder-react-jsx@6.9.0",
|
|
||||||
"babel-plugin-syntax-jsx": "npm:babel-plugin-syntax-jsx@6.13.0",
|
|
||||||
"babel-runtime": "npm:babel-runtime@6.11.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:babel-helper-builder-react-jsx@6.9.0": {
|
|
||||||
"map": {
|
|
||||||
"babel-runtime": "npm:babel-runtime@6.11.6",
|
|
||||||
"esutils": "npm:esutils@2.0.2",
|
|
||||||
"babel-types": "npm:babel-types@6.16.0",
|
|
||||||
"lodash": "npm:lodash@4.16.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:babel-runtime@6.11.6": {
|
|
||||||
"map": {
|
|
||||||
"core-js": "npm:core-js@2.4.1",
|
|
||||||
"regenerator-runtime": "npm:regenerator-runtime@0.9.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:babel-types@6.16.0": {
|
|
||||||
"map": {
|
|
||||||
"lodash": "npm:lodash@4.16.4",
|
|
||||||
"babel-runtime": "npm:babel-runtime@6.11.6",
|
|
||||||
"esutils": "npm:esutils@2.0.2",
|
|
||||||
"to-fast-properties": "npm:to-fast-properties@1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-zlib@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"zlib-browserify": "npm:browserify-zlib@0.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-domain@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"domain-browserify": "npm:domain-browser@1.1.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
transpiler: "plugin-babel",
|
|
||||||
packages: {
|
|
||||||
"slicer": {
|
|
||||||
"main": "index.js"
|
|
||||||
},
|
|
||||||
"example": {
|
|
||||||
"main": "example/index.js",
|
|
||||||
"format": "esm",
|
|
||||||
"meta": {
|
|
||||||
"*.js": {
|
|
||||||
"loader": "plugin-babel",
|
|
||||||
"babelOptions": {
|
|
||||||
"stage1": true,
|
|
||||||
"plugins": [
|
|
||||||
"babel-plugin-transform-react-jsx"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
map: {
|
|
||||||
"babel": "npm:babel-core@5.8.38"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SystemJS.config({
|
|
||||||
packageConfigPaths: [
|
|
||||||
"npm:@*/*.json",
|
|
||||||
"npm:*.json",
|
|
||||||
"github:*/*.json"
|
|
||||||
],
|
|
||||||
map: {
|
|
||||||
"three": "npm:three@0.83.0",
|
|
||||||
"progress-promise": "npm:progress-promise@0.0.6",
|
|
||||||
"text": "github:systemjs/plugin-text@0.0.11",
|
|
||||||
"js-yaml": "npm:js-yaml@3.9.0",
|
|
||||||
"clipper-js": "github:Doodle3D/clipper-js@1.0.2",
|
|
||||||
"assert": "github:jspm/nodelibs-assert@0.2.0-alpha",
|
|
||||||
"buffer": "github:jspm/nodelibs-buffer@0.2.0-alpha",
|
|
||||||
"child_process": "github:jspm/nodelibs-child_process@0.2.0-alpha",
|
|
||||||
"constants": "github:jspm/nodelibs-constants@0.2.0-alpha",
|
|
||||||
"crypto": "github:jspm/nodelibs-crypto@0.2.0-alpha",
|
|
||||||
"events": "github:jspm/nodelibs-events@0.2.2",
|
|
||||||
"fs": "github:jspm/nodelibs-fs@0.2.0-alpha",
|
|
||||||
"http": "github:jspm/nodelibs-http@0.2.0-alpha",
|
|
||||||
"json": "github:systemjs/plugin-json@0.1.2",
|
|
||||||
"Doodle3D/clipper-js": "github:Doodle3D/clipper-js@master",
|
|
||||||
"module": "npm:jspm-nodelibs-module@0.2.0",
|
|
||||||
"os": "github:jspm/nodelibs-os@0.2.2",
|
|
||||||
"path": "github:jspm/nodelibs-path@0.2.3",
|
|
||||||
"process": "github:jspm/nodelibs-process@0.2.0-alpha",
|
|
||||||
"stream": "github:jspm/nodelibs-stream@0.2.0-alpha",
|
|
||||||
"string_decoder": "github:jspm/nodelibs-string_decoder@0.2.0-alpha",
|
|
||||||
"tty": "npm:jspm-nodelibs-tty@0.2.0",
|
|
||||||
"url": "github:jspm/nodelibs-url@0.2.0-alpha",
|
|
||||||
"util": "github:jspm/nodelibs-util@0.2.0-alpha",
|
|
||||||
"vm": "github:jspm/nodelibs-vm@0.2.0-alpha",
|
|
||||||
"worker": "github:casperlamboo/plugin-worker@master"
|
|
||||||
},
|
|
||||||
packages: {
|
|
||||||
"github:Doodle3D/clipper-js@master": {
|
|
||||||
"map": {
|
|
||||||
"clipper-lib": "npm:clipper-lib@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:clipper-lib@1.0.0": {
|
|
||||||
"map": {}
|
|
||||||
},
|
|
||||||
"npm:stream-browserify@2.0.1": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"readable-stream": "npm:readable-stream@2.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:buffer@4.9.1": {
|
|
||||||
"map": {
|
|
||||||
"base64-js": "npm:base64-js@1.2.1",
|
|
||||||
"isarray": "npm:isarray@1.0.0",
|
|
||||||
"ieee754": "npm:ieee754@1.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:url@0.11.0": {
|
|
||||||
"map": {
|
|
||||||
"querystring": "npm:querystring@0.2.0",
|
|
||||||
"punycode": "npm:punycode@1.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:readable-stream@2.1.5": {
|
|
||||||
"map": {
|
|
||||||
"string_decoder": "npm:string_decoder@0.10.31",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"isarray": "npm:isarray@1.0.0",
|
|
||||||
"buffer-shims": "npm:buffer-shims@1.0.0",
|
|
||||||
"core-util-is": "npm:core-util-is@1.0.2",
|
|
||||||
"process-nextick-args": "npm:process-nextick-args@1.0.7",
|
|
||||||
"util-deprecate": "npm:util-deprecate@1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:public-encrypt@4.0.0": {
|
|
||||||
"map": {
|
|
||||||
"randombytes": "npm:randombytes@2.0.5",
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"parse-asn1": "npm:parse-asn1@5.1.0",
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"browserify-rsa": "npm:browserify-rsa@4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:diffie-hellman@5.0.2": {
|
|
||||||
"map": {
|
|
||||||
"randombytes": "npm:randombytes@2.0.5",
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"miller-rabin": "npm:miller-rabin@4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-cipher@1.0.0": {
|
|
||||||
"map": {
|
|
||||||
"browserify-des": "npm:browserify-des@1.0.0",
|
|
||||||
"evp_bytestokey": "npm:evp_bytestokey@1.0.0",
|
|
||||||
"browserify-aes": "npm:browserify-aes@1.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:create-ecdh@4.0.0": {
|
|
||||||
"map": {
|
|
||||||
"elliptic": "npm:elliptic@6.4.0",
|
|
||||||
"bn.js": "npm:bn.js@4.11.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-des@1.0.0": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"cipher-base": "npm:cipher-base@1.0.4",
|
|
||||||
"des.js": "npm:des.js@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:evp_bytestokey@1.0.0": {
|
|
||||||
"map": {
|
|
||||||
"create-hash": "npm:create-hash@1.1.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-aes@1.0.6": {
|
|
||||||
"map": {
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"evp_bytestokey": "npm:evp_bytestokey@1.0.0",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"cipher-base": "npm:cipher-base@1.0.4",
|
|
||||||
"buffer-xor": "npm:buffer-xor@1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-rsa@4.0.1": {
|
|
||||||
"map": {
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"randombytes": "npm:randombytes@2.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:miller-rabin@4.0.0": {
|
|
||||||
"map": {
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"brorand": "npm:brorand@1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:des.js@1.0.0": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"minimalistic-assert": "npm:minimalistic-assert@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:stream-http@2.4.0": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"readable-stream": "npm:readable-stream@2.1.5",
|
|
||||||
"to-arraybuffer": "npm:to-arraybuffer@1.0.1",
|
|
||||||
"builtin-status-codes": "npm:builtin-status-codes@2.0.0",
|
|
||||||
"xtend": "npm:xtend@4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:Doodle3D/clipper-js@1.0.2": {
|
|
||||||
"map": {
|
|
||||||
"Breush/clipper-lib": "github:Breush/clipper-lib@patch-1",
|
|
||||||
"clipper-lib": "npm:clipper-lib@6.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:js-yaml@3.9.0": {
|
|
||||||
"map": {
|
|
||||||
"argparse": "npm:argparse@1.0.9",
|
|
||||||
"esprima": "npm:esprima@4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:argparse@1.0.9": {
|
|
||||||
"map": {
|
|
||||||
"sprintf-js": "npm:sprintf-js@1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-buffer@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"buffer-browserify": "npm:buffer@4.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-crypto@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"crypto-browserify": "npm:crypto-browserify@3.11.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:crypto-browserify@3.11.1": {
|
|
||||||
"map": {
|
|
||||||
"browserify-cipher": "npm:browserify-cipher@1.0.0",
|
|
||||||
"browserify-sign": "npm:browserify-sign@4.0.4",
|
|
||||||
"create-ecdh": "npm:create-ecdh@4.0.0",
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"create-hmac": "npm:create-hmac@1.1.6",
|
|
||||||
"diffie-hellman": "npm:diffie-hellman@5.0.2",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"pbkdf2": "npm:pbkdf2@3.0.12",
|
|
||||||
"public-encrypt": "npm:public-encrypt@4.0.0",
|
|
||||||
"randombytes": "npm:randombytes@2.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-sign@4.0.4": {
|
|
||||||
"map": {
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"create-hmac": "npm:create-hmac@1.1.6",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"elliptic": "npm:elliptic@6.4.0",
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"parse-asn1": "npm:parse-asn1@5.1.0",
|
|
||||||
"browserify-rsa": "npm:browserify-rsa@4.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:create-hash@1.1.3": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"ripemd160": "npm:ripemd160@2.0.1",
|
|
||||||
"sha.js": "npm:sha.js@2.4.8",
|
|
||||||
"cipher-base": "npm:cipher-base@1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:create-hmac@1.1.6": {
|
|
||||||
"map": {
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"ripemd160": "npm:ripemd160@2.0.1",
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1",
|
|
||||||
"sha.js": "npm:sha.js@2.4.8",
|
|
||||||
"cipher-base": "npm:cipher-base@1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:randombytes@2.0.5": {
|
|
||||||
"map": {
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:pbkdf2@3.0.12": {
|
|
||||||
"map": {
|
|
||||||
"create-hmac": "npm:create-hmac@1.1.6",
|
|
||||||
"ripemd160": "npm:ripemd160@2.0.1",
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1",
|
|
||||||
"sha.js": "npm:sha.js@2.4.8",
|
|
||||||
"create-hash": "npm:create-hash@1.1.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:ripemd160@2.0.1": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"hash-base": "npm:hash-base@2.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:sha.js@2.4.8": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:elliptic@6.4.0": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"hmac-drbg": "npm:hmac-drbg@1.0.1",
|
|
||||||
"hash.js": "npm:hash.js@1.1.3",
|
|
||||||
"brorand": "npm:brorand@1.1.0",
|
|
||||||
"minimalistic-crypto-utils": "npm:minimalistic-crypto-utils@1.0.1",
|
|
||||||
"minimalistic-assert": "npm:minimalistic-assert@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:parse-asn1@5.1.0": {
|
|
||||||
"map": {
|
|
||||||
"browserify-aes": "npm:browserify-aes@1.0.6",
|
|
||||||
"create-hash": "npm:create-hash@1.1.3",
|
|
||||||
"evp_bytestokey": "npm:evp_bytestokey@1.0.0",
|
|
||||||
"pbkdf2": "npm:pbkdf2@3.0.12",
|
|
||||||
"asn1.js": "npm:asn1.js@4.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:cipher-base@1.0.4": {
|
|
||||||
"map": {
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1",
|
|
||||||
"inherits": "npm:inherits@2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:hash-base@2.0.2": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:hmac-drbg@1.0.1": {
|
|
||||||
"map": {
|
|
||||||
"hash.js": "npm:hash.js@1.1.3",
|
|
||||||
"minimalistic-assert": "npm:minimalistic-assert@1.0.0",
|
|
||||||
"minimalistic-crypto-utils": "npm:minimalistic-crypto-utils@1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:hash.js@1.1.3": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"minimalistic-assert": "npm:minimalistic-assert@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:asn1.js@4.9.1": {
|
|
||||||
"map": {
|
|
||||||
"bn.js": "npm:bn.js@4.11.7",
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"minimalistic-assert": "npm:minimalistic-assert@1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-stream@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"stream-browserify": "npm:stream-browserify@2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:readable-stream@2.3.3": {
|
|
||||||
"map": {
|
|
||||||
"inherits": "npm:inherits@2.0.3",
|
|
||||||
"isarray": "npm:isarray@1.0.0",
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1",
|
|
||||||
"string_decoder": "npm:string_decoder@1.0.3",
|
|
||||||
"util-deprecate": "npm:util-deprecate@1.0.2",
|
|
||||||
"process-nextick-args": "npm:process-nextick-args@1.0.7",
|
|
||||||
"core-util-is": "npm:core-util-is@1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:string_decoder@1.0.3": {
|
|
||||||
"map": {
|
|
||||||
"safe-buffer": "npm:safe-buffer@5.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-string_decoder@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"string_decoder-browserify": "npm:string_decoder@0.10.31"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-http@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"http-browserify": "npm:stream-http@2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"github:jspm/nodelibs-url@0.2.0-alpha": {
|
|
||||||
"map": {
|
|
||||||
"url-browserify": "npm:url@0.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
1641
package-lock.json
generated
Normal file
1641
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
115
package.json
115
package.json
@ -1,92 +1,35 @@
|
|||||||
{
|
{
|
||||||
"jspm": {
|
"name": "@doodle3d/doodle3d-slicer",
|
||||||
"name": "slicer",
|
"version": "0.0.1",
|
||||||
"main": "index.js",
|
"description": "JavaScript gcode slicer, Intended to use with the Doodle3D WiFi-Box # Usage",
|
||||||
"directories": {
|
"main": "lib/index.js",
|
||||||
"lib": "src"
|
"module": "module/index.js",
|
||||||
},
|
"esnext": "src/index.js",
|
||||||
"dependencies": {
|
"scripts": {
|
||||||
"Doodle3D/clipper-js": "github:Doodle3D/clipper-js@master",
|
"prepare": "npm run build",
|
||||||
"clipper-js": "github:Doodle3D/clipper-js@1.0.2",
|
"build": "npm run build:main && npm run build:main:settings && npm run build:module && npm run build:module:settings ",
|
||||||
"js-yaml": "npm:js-yaml@^3.9.0",
|
"build:main": "BABEL_ENV=main babel src -s -d lib",
|
||||||
"json": "github:systemjs/plugin-json@^0.1.2",
|
"build:module": "BABEL_ENV=module babel src -s -d module",
|
||||||
"progress-promise": "npm:progress-promise@^0.0.6",
|
"build:main:settings": "cp -r src/settings lib",
|
||||||
"text": "github:systemjs/plugin-text@^0.0.11",
|
"build:module:settings": "cp -r src/settings module"
|
||||||
"three": "npm:three@0.83.0",
|
|
||||||
"worker": "github:casperlamboo/plugin-worker@master"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-plugin-transform-react-jsx": "npm:babel-plugin-transform-react-jsx@^6.8.0",
|
|
||||||
"babel-runtime": "npm:babel-runtime@^5.1.13",
|
|
||||||
"core-js": "npm:core-js@^1.2.0",
|
|
||||||
"file-saver": "npm:file-saver@^1.3.3",
|
|
||||||
"domain": "github:jspm/nodelibs-domain@^0.2.0-alpha",
|
|
||||||
"https": "github:jspm/nodelibs-https@^0.2.0-alpha",
|
|
||||||
"plugin-babel": "npm:systemjs-plugin-babel@^0.0.12",
|
|
||||||
"react": "npm:react@^15.3.2",
|
|
||||||
"react-dom": "npm:react-dom@^15.3.2",
|
|
||||||
"zlib": "github:jspm/nodelibs-zlib@^0.2.0-alpha"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"assert": "github:jspm/nodelibs-assert@^0.2.0-alpha",
|
|
||||||
"buffer": "github:jspm/nodelibs-buffer@^0.2.0-alpha",
|
|
||||||
"child_process": "github:jspm/nodelibs-child_process@^0.2.0-alpha",
|
|
||||||
"constants": "github:jspm/nodelibs-constants@^0.2.0-alpha",
|
|
||||||
"crypto": "github:jspm/nodelibs-crypto@^0.2.0-alpha",
|
|
||||||
"events": "github:jspm/nodelibs-events@^0.2.0-alpha",
|
|
||||||
"fs": "github:jspm/nodelibs-fs@^0.2.0-alpha",
|
|
||||||
"http": "github:jspm/nodelibs-http@^0.2.0-alpha",
|
|
||||||
"module": "npm:jspm-nodelibs-module@^0.2.0",
|
|
||||||
"os": "github:jspm/nodelibs-os@^0.2.0-alpha",
|
|
||||||
"path": "github:jspm/nodelibs-path@^0.2.0-alpha",
|
|
||||||
"process": "github:jspm/nodelibs-process@^0.2.0-alpha",
|
|
||||||
"stream": "github:jspm/nodelibs-stream@^0.2.0-alpha",
|
|
||||||
"string_decoder": "github:jspm/nodelibs-string_decoder@^0.2.0-alpha",
|
|
||||||
"tty": "npm:jspm-nodelibs-tty@^0.2.0",
|
|
||||||
"url": "github:jspm/nodelibs-url@^0.2.0-alpha",
|
|
||||||
"util": "github:jspm/nodelibs-util@^0.2.0-alpha",
|
|
||||||
"vm": "github:jspm/nodelibs-vm@^0.2.0-alpha"
|
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"npm:babel-runtime@5.8.38": {
|
|
||||||
"main": false,
|
|
||||||
"dependencies": {},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"core-js": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:browserify-zlib@0.1.4": {
|
|
||||||
"dependencies": {
|
|
||||||
"readable-stream": "^2.0.2",
|
|
||||||
"pako": "~0.2.0"
|
|
||||||
},
|
|
||||||
"map": {
|
|
||||||
"_stream_transform": "readable-stream/transform"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:inherits@2.0.3": {
|
|
||||||
"ignore": [
|
|
||||||
"test.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"npm:lodash@4.16.4": {
|
|
||||||
"map": {
|
|
||||||
"buffer": "@empty",
|
|
||||||
"process": "@empty"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm:pbkdf2@3.0.12": {
|
|
||||||
"main": "browser.js"
|
|
||||||
},
|
|
||||||
"npm:safe-buffer@5.1.1": {
|
|
||||||
"browser": "index.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-yaml": "^3.9.0"
|
"clipper-js": "^1.0.2",
|
||||||
|
"three": "^0.83.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jspm": "^0.17.0-beta.28"
|
"babel-cli": "^6.24.1",
|
||||||
}
|
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
||||||
|
"babel-preset-latest": "^6.24.1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Doodle3D/Doodle3D-Slicer.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Doodle3D/Doodle3D-Slicer/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Doodle3D/Doodle3D-Slicer#readme"
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Doodle3D Slicer - Simple example</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('simpleExample/index.js');
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="gcode"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,16 +1,20 @@
|
|||||||
import 'three.js';
|
import * as THREE from 'three';
|
||||||
import * as SLICER from 'src/index.js';
|
import { defaultSettings, sliceGeometry } from 'doodle3d-slicer';
|
||||||
|
|
||||||
const settings = new SLICER.Settings({
|
const settings = {
|
||||||
...SLICER.printerSettings['ultimaker2go'],
|
...defaultSettings.base,
|
||||||
...SLICER.userSettings.low
|
...defaultSettings.material.pla,
|
||||||
});
|
...defaultSettings.printer.ultimaker2go,
|
||||||
|
...defaultSettings.quality.high
|
||||||
|
};
|
||||||
|
|
||||||
const geometry = new THREE.TorusGeometry(20, 10, 30, 30).clone();
|
const geometry = new THREE.TorusGeometry(20, 10, 30, 30).clone();
|
||||||
|
|
||||||
const slicer = new SLICER.Slicer();
|
const onProgress = ({ progress: { done, total, action } }) => {
|
||||||
|
const percentage = `${(done / total * 100).toFixed()}%`
|
||||||
|
document.write(`<p>${action}, ${percentage}</p>`);
|
||||||
|
};
|
||||||
|
|
||||||
slicer.setGeometry(geometry);
|
sliceGeometry(settings, geometry, null, false, onProgress).then(gcode => {
|
||||||
slicer.slice(settings, false).then(gcode => {
|
document.body.innerHTML = gcode.replace(/(?:\r\n|\r|\n)/g, '<br />');
|
||||||
document.getElementById('gcode').innerHTML = gcode.replace(/(?:\r\n|\r|\n)/g, '<br />');
|
|
||||||
});
|
});
|
4513
simpleExample/package-lock.json
generated
Normal file
4513
simpleExample/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
simpleExample/package.json
Normal file
27
simpleExample/package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "doodle3d-simple-example",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack -p",
|
||||||
|
"start": "webpack-dev-server -w"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"babel-polyfill": "^6.23.0",
|
||||||
|
"three": "^0.83.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.25.0",
|
||||||
|
"babel-loader": "^7.1.1",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
||||||
|
"babel-preset-latest": "^6.24.1",
|
||||||
|
"html-webpack-plugin": "^2.29.0",
|
||||||
|
"webpack": "^3.3.0",
|
||||||
|
"webpack-dev-server": "^2.5.1",
|
||||||
|
"worker-loader": "^0.8.1",
|
||||||
|
"yml-loader": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
56
simpleExample/webpack.config.js
Normal file
56
simpleExample/webpack.config.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const path = require('path');
|
||||||
|
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
const HTMLWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
const babelLoader = {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: [
|
||||||
|
['latest', {
|
||||||
|
'modules': false,
|
||||||
|
'loose': true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
plugins: [require('babel-plugin-transform-object-rest-spread')],
|
||||||
|
babelrc: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './index.js',
|
||||||
|
output: {
|
||||||
|
filename: 'bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'dist')
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'doodle3d-slicer': path.resolve(__dirname, '../src/index.js')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: babelLoader
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.yml$/,
|
||||||
|
use: 'yml-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.worker\.js$/,
|
||||||
|
use: ['worker-loader', babelLoader]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HTMLWebpackPlugin({
|
||||||
|
title: 'Doodle3D Slicer - Simple example'
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
devtool: "source-map",
|
||||||
|
devServer: {
|
||||||
|
contentBase: 'dist'
|
||||||
|
}
|
||||||
|
};
|
@ -1,63 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import slice from './sliceActions/slice.js';
|
|
||||||
import SlicerWorker from './slicerWorker.js!worker';
|
|
||||||
import ProgressPromise from 'progress-promise';
|
|
||||||
|
|
||||||
export default class {
|
|
||||||
setMesh(mesh) {
|
|
||||||
mesh.updateMatrix();
|
|
||||||
|
|
||||||
this.setGeometry(mesh.geometry, mesh.matrix);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
setGeometry(geometry, matrix) {
|
|
||||||
if (geometry.isBufferGeometry) {
|
|
||||||
geometry = new THREE.Geometry().fromBufferGeometry(geometry);
|
|
||||||
} else if (geometry.isGeometry) {
|
|
||||||
geometry = geometry.clone();
|
|
||||||
} else {
|
|
||||||
throw new Error('Geometry is not an instance of BufferGeometry or Geometry');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matrix) {
|
|
||||||
geometry.applyMatrix(matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.geometry = geometry;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
sliceSync(settings, onprogress) {
|
|
||||||
return slice(this.geometry, settings, onprogress);
|
|
||||||
}
|
|
||||||
slice(settings) {
|
|
||||||
const slicerWorker = new SlicerWorker();
|
|
||||||
|
|
||||||
const geometry = this.geometry.toJSON();
|
|
||||||
|
|
||||||
return new ProgressPromise((resolve, reject, progress) => {
|
|
||||||
slicerWorker.onerror = reject;
|
|
||||||
|
|
||||||
slicerWorker.addEventListener('message', (event) => {
|
|
||||||
const { message, data } = event.data;
|
|
||||||
switch (message) {
|
|
||||||
case 'SLICE': {
|
|
||||||
slicerWorker.terminate();
|
|
||||||
resolve(data.gcode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'PROGRESS': {
|
|
||||||
progress(data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
slicerWorker.postMessage({
|
|
||||||
message: 'SLICE',
|
|
||||||
data: { geometry, settings }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
22
src/index.js
22
src/index.js
@ -1,18 +1,18 @@
|
|||||||
import Slicer from './Slicer.js';
|
import { sliceGeometry, sliceMesh } from './slicer.js';
|
||||||
import baseSettings from './settings/default.yml!text';
|
import baseSettings from './settings/default.yml';
|
||||||
import printerSettings from './settings/printer.yml!text';
|
import printerSettings from './settings/printer.yml';
|
||||||
import materialSettings from './settings/material.yml!text';
|
import materialSettings from './settings/material.yml';
|
||||||
import qualitySettings from './settings/quality.yml!text';
|
import qualitySettings from './settings/quality.yml';
|
||||||
import yaml from 'js-yaml';
|
|
||||||
|
|
||||||
const defaultSettings = {
|
const defaultSettings = {
|
||||||
base: yaml.safeLoad(baseSettings),
|
base: baseSettings,
|
||||||
printer: yaml.safeLoad(printerSettings),
|
printer: printerSettings,
|
||||||
material: yaml.safeLoad(materialSettings),
|
material: materialSettings,
|
||||||
quality: yaml.safeLoad(qualitySettings)
|
quality: qualitySettings
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Slicer,
|
sliceGeometry,
|
||||||
|
sliceMesh,
|
||||||
defaultSettings
|
defaultSettings
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
import { PRECISION } from '../constants.js';
|
import { PRECISION } from '../constants.js';
|
||||||
|
|
||||||
const offsetOptions = {
|
const offsetOptions = {
|
||||||
@ -8,16 +8,17 @@ const offsetOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function addBrim(slices, settings) {
|
export default function addBrim(slices, settings) {
|
||||||
let { brim: { offset: brimOffset } } = settings;
|
let {
|
||||||
|
brim: { offset: brimOffset }
|
||||||
|
} = settings;
|
||||||
brimOffset /= PRECISION;
|
brimOffset /= PRECISION;
|
||||||
|
|
||||||
const [firstLayer] = slices;
|
const [firstLayer] = slices;
|
||||||
|
|
||||||
firstLayer.brim = firstLayer.parts.reduce((brim, { shape }) => {
|
firstLayer.brim = firstLayer.parts.reduce((brim, { shape }) => (
|
||||||
brim.join(shape.offset(brimOffset, {
|
brim.join(shape.offset(brimOffset, {
|
||||||
...offsetOptions,
|
...offsetOptions,
|
||||||
endType: shape.closed ? 'etClosedPolygon' : 'etOpenRound'
|
endType: shape.closed ? 'etClosedPolygon' : 'etOpenRound'
|
||||||
}));
|
}))
|
||||||
return brim;
|
), new Shape([], true)).simplify('pftNonZero');
|
||||||
}, new Shape([], true)).simplify('pftNonZero');
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
export default function calculateLayersIntersections(lines, settings) {
|
export default function calculateLayersIntersections(lines, settings) {
|
||||||
const { layerHeight, dimensions: { z: dimensionsZ } } = settings;
|
const {
|
||||||
|
layerHeight,
|
||||||
|
dimensions: { z: dimensionsZ }
|
||||||
|
} = settings;
|
||||||
|
|
||||||
const numLayers = Math.floor(dimensionsZ / layerHeight);
|
const numLayers = Math.floor(dimensionsZ / layerHeight);
|
||||||
|
|
||||||
@ -9,9 +12,9 @@ export default function calculateLayersIntersections(lines, settings) {
|
|||||||
const layerIntersectionPoints = Array.from(Array(numLayers)).map(() => []);
|
const layerIntersectionPoints = Array.from(Array(numLayers)).map(() => []);
|
||||||
|
|
||||||
for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
|
||||||
const line = lines[lineIndex].line;
|
const { line, isFlat } = lines[lineIndex];
|
||||||
|
|
||||||
if (line.isFlat) continue;
|
if (isFlat) continue;
|
||||||
|
|
||||||
const min = Math.ceil(Math.min(line.start.y, line.end.y) / layerHeight);
|
const min = Math.ceil(Math.min(line.start.y, line.end.y) / layerHeight);
|
||||||
const max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight);
|
const max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight);
|
||||||
|
@ -25,7 +25,7 @@ export default function createLines(geometry, settings) {
|
|||||||
const lookupB = lineLookup[`${face.c}_${face.b}`];
|
const lookupB = lineLookup[`${face.c}_${face.b}`];
|
||||||
const lookupC = lineLookup[`${face.a}_${face.c}`];
|
const lookupC = lineLookup[`${face.a}_${face.c}`];
|
||||||
|
|
||||||
const isFlat = face.normal.y !== 1 && face.normal.y !== -1;
|
const isFlat = face.normal.y > 0.999 || face.normal.y < -0.999;
|
||||||
|
|
||||||
// only add unique lines
|
// only add unique lines
|
||||||
// returns index of said line
|
// returns index of said line
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PRECISION } from '../constants.js'
|
import { PRECISION } from '../constants.js'
|
||||||
import getFillTemplate from './getFillTemplate.js';
|
import getFillTemplate from './getFillTemplate.js';
|
||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
export default function generateInfills(slices, settings) {
|
export default function generateInfills(slices, settings) {
|
||||||
let {
|
let {
|
||||||
@ -26,8 +26,8 @@ export default function generateInfills(slices, settings) {
|
|||||||
|
|
||||||
let surroundingLayer;
|
let surroundingLayer;
|
||||||
if (layer - bottomSkinCount >= 0 && layer + topSkinCount < slices.length) {
|
if (layer - bottomSkinCount >= 0 && layer + topSkinCount < slices.length) {
|
||||||
const downSkin = slices[layer - bottomSkinCount].getOutline();
|
const downSkin = slices[layer - bottomSkinCount].outline;
|
||||||
const upSkin = slices[layer + topSkinCount].getOutline();
|
const upSkin = slices[layer + topSkinCount].outline;
|
||||||
surroundingLayer = upSkin.intersect(downSkin);
|
surroundingLayer = upSkin.intersect(downSkin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,11 @@ const offsetOptions = {
|
|||||||
|
|
||||||
export default function generateInnerLines(slices, settings) {
|
export default function generateInnerLines(slices, settings) {
|
||||||
// need to scale up everything because of clipper rounding errors
|
// need to scale up everything because of clipper rounding errors
|
||||||
let { layerHeight, nozzleDiameter, shell: { thickness: shellThickness } } = settings;
|
let {
|
||||||
|
layerHeight,
|
||||||
|
nozzleDiameter,
|
||||||
|
shell: { thickness: shellThickness }
|
||||||
|
} = settings;
|
||||||
nozzleDiameter /= PRECISION;
|
nozzleDiameter /= PRECISION;
|
||||||
shellThickness /= PRECISION;
|
shellThickness /= PRECISION;
|
||||||
const nozzleRadius = nozzleDiameter / 2;
|
const nozzleRadius = nozzleDiameter / 2;
|
||||||
|
12
src/sliceActions/generateOutlines.js
Normal file
12
src/sliceActions/generateOutlines.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
|
export default function calculateOutlines(slices, settings) {
|
||||||
|
for (let layer = 0; layer < slices.length; layer ++) {
|
||||||
|
const slice = slices[layer];
|
||||||
|
|
||||||
|
slice.outline = slice.parts.reduce((shape, part) => {
|
||||||
|
if (part.outerLine) shape.join(part.outerLine);
|
||||||
|
return shape;
|
||||||
|
}, new Shape([], true));
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import getFillTemplate from './getFillTemplate.js';
|
import getFillTemplate from './getFillTemplate.js';
|
||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
import { PRECISION } from '../constants.js';
|
import { PRECISION } from '../constants.js';
|
||||||
|
|
||||||
export default function generateSupport(slices, settings) {
|
export default function generateSupport(slices, settings) {
|
||||||
@ -9,9 +9,9 @@ export default function generateSupport(slices, settings) {
|
|||||||
layerHeight,
|
layerHeight,
|
||||||
support: {
|
support: {
|
||||||
gridSize: supportGridSize,
|
gridSize: supportGridSize,
|
||||||
margin: AcceptanceMargin,
|
margin: supportMargin,
|
||||||
plateSize: plateSize,
|
plateSize: plateSize,
|
||||||
distanceY: DistanceY
|
distanceY: supportDistanceY
|
||||||
},
|
},
|
||||||
nozzleDiameter
|
nozzleDiameter
|
||||||
} = settings;
|
} = settings;
|
||||||
@ -30,7 +30,7 @@ export default function generateSupport(slices, settings) {
|
|||||||
if (supportAreas.length > 0) {
|
if (supportAreas.length > 0) {
|
||||||
|
|
||||||
if (layer >= supportDistanceLayers) {
|
if (layer >= supportDistanceLayers) {
|
||||||
var sliceSkin = slices[layer - supportDistanceLayers].getOutline();
|
var sliceSkin = slices[layer - supportDistanceLayers].outline;
|
||||||
sliceSkin = sliceSkin;
|
sliceSkin = sliceSkin;
|
||||||
|
|
||||||
var supportAreasSlimmed = supportAreas.difference(sliceSkin.offset(supportMargin));
|
var supportAreasSlimmed = supportAreas.difference(sliceSkin.offset(supportMargin));
|
||||||
@ -52,7 +52,7 @@ export default function generateSupport(slices, settings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportSkin = slices[layer + supportDistanceLayers - 1].getOutline();
|
var supportSkin = slices[layer + supportDistanceLayers - 1].outline;
|
||||||
|
|
||||||
var slice = slices[layer + supportDistanceLayers];
|
var slice = slices[layer + supportDistanceLayers];
|
||||||
for (var i = 0; i < slice.parts.length; i ++) {
|
for (var i = 0; i < slice.parts.length; i ++) {
|
||||||
@ -62,10 +62,10 @@ export default function generateSupport(slices, settings) {
|
|||||||
var outerLine = slicePart.outerLine;
|
var outerLine = slicePart.outerLine;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var outerLine = slicePart.intersect.offset(supportAcceptanceMargin);
|
var outerLine = slicePart.intersect.offset(supportMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlap = supportSkin.offset(supportAcceptanceMargin).intersect(outerLine);
|
var overlap = supportSkin.offset(supportMargin).intersect(outerLine);
|
||||||
var overhang = outerLine.difference(overlap);
|
var overhang = outerLine.difference(overlap);
|
||||||
|
|
||||||
if (overlap.length === 0 || overhang.length > 0) {
|
if (overlap.length === 0 || overhang.length > 0) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
export default function getFillTemplate(bounds, size, even, uneven) {
|
export default function getFillTemplate(bounds, size, even, uneven) {
|
||||||
const paths = [];
|
const paths = [];
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.parts = [];
|
this.parts = [];
|
||||||
}
|
}
|
||||||
getOutline() {
|
|
||||||
return this.parts.reduce((shape, part) => {
|
|
||||||
if (part.outerLine) shape.join(part.outerLine);
|
|
||||||
return shape;
|
|
||||||
}, new Shape([], true));
|
|
||||||
}
|
|
||||||
add(shape) {
|
add(shape) {
|
||||||
const part = { shape };
|
const part = { shape };
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
export default function intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings) {
|
export default function intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings) {
|
||||||
const layers = [];
|
const layers = [];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
|
|
||||||
export default function optimizePaths(slices, settings) {
|
export default function optimizePaths(slices, settings) {
|
||||||
const start = new THREE.Vector2(0, 0);
|
const start = new THREE.Vector2(0, 0);
|
||||||
@ -21,12 +21,8 @@ export default function optimizePaths(slices, settings) {
|
|||||||
for (let i = 0; i < slice.parts.length; i ++) {
|
for (let i = 0; i < slice.parts.length; i ++) {
|
||||||
const part = slice.parts[i];
|
const part = slice.parts[i];
|
||||||
|
|
||||||
let bounds;
|
const shape = part.shape.closed ? part.outerLine : part.shape;
|
||||||
if (part.shape.closed) {
|
const bounds = shape.shapeBounds();
|
||||||
bounds = part.outerLine.shapeBounds();
|
|
||||||
} else {
|
|
||||||
bounds = part.shape.shapeBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
const top = bounds.top - start.y;
|
const top = bounds.top - start.y;
|
||||||
const bottom = start.y - bounds.bottom;
|
const bottom = start.y - bounds.bottom;
|
||||||
@ -41,7 +37,7 @@ export default function optimizePaths(slices, settings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const part = slice.parts.splice(closestPart, 1)[0];
|
const [part] = slice.parts.splice(closestPart, 1);
|
||||||
parts.push(part);
|
parts.push(part);
|
||||||
|
|
||||||
if (part.shape.closed) {
|
if (part.shape.closed) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Shape from 'Doodle3D/clipper-js';
|
import Shape from 'clipper-js';
|
||||||
import Slice from '../Slice.js';
|
import Slice from './helpers/Slice.js';
|
||||||
|
|
||||||
import { CLEAN_DELTA, PRECISION } from '../constants.js';
|
import { CLEAN_DELTA, PRECISION } from '../constants.js';
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import calculateLayersIntersections from './calculateLayersIntersections.js';
|
|||||||
import createLines from './createLines.js';
|
import createLines from './createLines.js';
|
||||||
import generateInfills from './generateInfills.js';
|
import generateInfills from './generateInfills.js';
|
||||||
import generateInnerLines from './generateInnerLines.js';
|
import generateInnerLines from './generateInnerLines.js';
|
||||||
|
import generateOutlines from './generateOutlines.js';
|
||||||
import generateSupport from './generateSupport.js';
|
import generateSupport from './generateSupport.js';
|
||||||
import intersectionsToShapes from './intersectionsToShapes.js';
|
import intersectionsToShapes from './intersectionsToShapes.js';
|
||||||
import addBrim from './addBrim.js';
|
import addBrim from './addBrim.js';
|
||||||
@ -12,12 +13,20 @@ import detectOpenClosed from './detectOpenClosed.js';
|
|||||||
import applyPrecision from './applyPrecision.js';
|
import applyPrecision from './applyPrecision.js';
|
||||||
import removePrecision from './removePrecision.js';
|
import removePrecision from './removePrecision.js';
|
||||||
|
|
||||||
export default function(geometry, settings, onProgress) {
|
export default function(settings, geometry, onProgress) {
|
||||||
const totalStages = 11;
|
const totalStages = 12;
|
||||||
let current = -1;
|
let current = -1;
|
||||||
const updateProgress = (action) => {
|
const updateProgress = (action) => {
|
||||||
current ++;
|
current ++;
|
||||||
if (onProgress) onProgress({ done: current, total: totalStages, action });
|
if (typeof onProgress !== 'undefined') {
|
||||||
|
onProgress({
|
||||||
|
progress: {
|
||||||
|
done: current,
|
||||||
|
total: totalStages,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
geometry.computeFaceNormals();
|
geometry.computeFaceNormals();
|
||||||
@ -27,7 +36,7 @@ export default function(geometry, settings, onProgress) {
|
|||||||
const lines = createLines(geometry, settings);
|
const lines = createLines(geometry, settings);
|
||||||
|
|
||||||
updateProgress('Detecting open vs closed shapes');
|
updateProgress('Detecting open vs closed shapes');
|
||||||
const openClosed = detectOpenClosed(lines);
|
detectOpenClosed(lines);
|
||||||
|
|
||||||
updateProgress('Calculating layer intersections');
|
updateProgress('Calculating layer intersections');
|
||||||
const {
|
const {
|
||||||
@ -45,6 +54,8 @@ export default function(geometry, settings, onProgress) {
|
|||||||
|
|
||||||
updateProgress('Generating inner lines');
|
updateProgress('Generating inner lines');
|
||||||
generateInnerLines(slices, settings);
|
generateInnerLines(slices, settings);
|
||||||
|
updateProgress('Generating out lines');
|
||||||
|
generateOutlines(slices, settings);
|
||||||
updateProgress('Generating infills');
|
updateProgress('Generating infills');
|
||||||
generateInfills(slices, settings);
|
generateInfills(slices, settings);
|
||||||
updateProgress('Generating support');
|
updateProgress('Generating support');
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import GCode from '../GCode.js';
|
import GCode from './helpers/GCode.js';
|
||||||
|
|
||||||
export default function slicesToGCode(slices, settings) {
|
export default function slicesToGCode(slices, settings) {
|
||||||
const gcode = new GCode(settings);
|
const gcode = new GCode(settings);
|
||||||
|
83
src/slicer.js
Normal file
83
src/slicer.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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) {
|
||||||
|
throw new Error('Provided mesh is not intance of THREE.Mesh');
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.updateMatrix();
|
||||||
|
const { geometry, matrix } = mesh;
|
||||||
|
return sliceGeometry(settings, geometry, matrix, sync, onProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sliceGeometry(settings, geometry, matrix, sync = false, onProgress) {
|
||||||
|
if (typeof geometry === 'undefined') {
|
||||||
|
throw new Error('Missing required geometry argument');
|
||||||
|
} else if (geometry.isBufferGeometry) {
|
||||||
|
geometry = new THREE.Geometry().fromBufferGeometry(geometry);
|
||||||
|
} else if (geometry.isGeometry) {
|
||||||
|
geometry = geometry.clone();
|
||||||
|
} else {
|
||||||
|
throw new Error('Geometry is not an instance of BufferGeometry or Geometry');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geometry.faces.length === 0) {
|
||||||
|
throw new Error('Geometry does not contain any data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix) {
|
||||||
|
geometry.applyMatrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
return sliceSync(settings, geometry, onProgress);
|
||||||
|
} else {
|
||||||
|
return sliceAsync(settings, geometry, onProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliceSync(settings, geometry, onProgress) {
|
||||||
|
return slice(settings, geometry, onProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliceAsync(settings, geometry, onProgress) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// create the slicer worker
|
||||||
|
const slicerWorker = new SlicerWorker();
|
||||||
|
|
||||||
|
slicerWorker.onerror = error => {
|
||||||
|
slicerWorker.terminate();
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
// listen to messages send from worker
|
||||||
|
slicerWorker.addEventListener('message', (event) => {
|
||||||
|
const { message, data } = event.data;
|
||||||
|
switch (message) {
|
||||||
|
case 'SLICE': {
|
||||||
|
slicerWorker.terminate();
|
||||||
|
resolve(data.gcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'PROGRESS': {
|
||||||
|
if (typeof onProgress !== 'undefined') {
|
||||||
|
onProgress(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// send geometry and settings to worker to start the slicing progress
|
||||||
|
geometry = geometry.toJSON();
|
||||||
|
slicerWorker.postMessage({
|
||||||
|
message: 'SLICE',
|
||||||
|
data: {
|
||||||
|
settings,
|
||||||
|
geometry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -14,11 +14,10 @@ self.addEventListener('message', (event) => {
|
|||||||
const { message, data } = event.data;
|
const { message, data } = event.data;
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case 'SLICE': {
|
case 'SLICE': {
|
||||||
const { geometry: JSONGeometry, settings } = data;
|
const { settings, geometry: JSONGeometry } = data;
|
||||||
|
const { geometry } = loader.parse(JSONGeometry.data);
|
||||||
|
|
||||||
const { geometry } = new loader.parse(JSONGeometry.data);
|
const gcode = slice(settings, geometry, onProgress);
|
||||||
|
|
||||||
const gcode = slice(geometry, settings, onProgress);
|
|
||||||
|
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
message: 'SLICE',
|
message: 'SLICE',
|
Loading…
Reference in New Issue
Block a user