diff --git a/simpleExample/index.js b/simpleExample/index.js index 282237a..43f9cd1 100644 --- a/simpleExample/index.js +++ b/simpleExample/index.js @@ -9,6 +9,7 @@ const settings = { }; const geometry = new THREE.TorusGeometry(20, 10, 30, 30).clone(); +geometry.mergeVertices(); const onProgress = ({ progress: { done, total, action } }) => { const percentage = `${(done / total * 100).toFixed()}%` @@ -17,4 +18,4 @@ const onProgress = ({ progress: { done, total, action } }) => { sliceGeometry(settings, geometry, null, false, onProgress).then(gcode => { document.body.innerHTML = gcode.replace(/(?:\r\n|\r|\n)/g, '
'); -}); \ No newline at end of file +}); diff --git a/src/settings/default.yml b/src/settings/default.yml index 0fd05a0..128a25c 100644 --- a/src/settings/default.yml +++ b/src/settings/default.yml @@ -2,49 +2,49 @@ dimensions: x: 200 y: 200 z: 200 -temperature: 210 -bedTemperature: 70 -# heatBedTemperature: 20 -# heatTemperature: 20 -# heatupEnabled: true -travelSpeed: 200.0 -layerHeight: 0.15 heatedBed: false nozzleDiameter: 0.4 filamentThickness: 2.85 +temperature: 210 +bedTemperature: 70 +layerHeight: 0.15 +thickness: + top: 1.2 + bottom: 1.2 + shell: 0.8 retraction: - amount: 3.0 enabled: true + amount: 3.0 speed: 50.0 minDistance: 0.0 +travel: + speed: 200.0 support: + enabled: false acceptanceMargin: 1.5 distanceY: 0.4 - enabled: false gridSize: 6.0 margin: 2.0 plateSize: 4.0 flowRate: 0.8 speed: 40.0 -outerLine: +innerShell: + flowRate: 1.0 + speed: 50.0 +outerShell: flowRate: 1.0 speed: 40.0 -innerLine: +innerInfill: flowRate: 1.0 - speed: 50.0 -fill: - flowRate: 1.0 - speed: 50.0 + speed: 80.0 gridSize: 5.0 +outerInfill: + flowRate: 1.0 + speed: 50.0 brim: + offset: 4.0 flowRate: 1.0 speed: 40.0 - offset: 4.0 -top: - thickness: 1.2 -bottom: +firstLayer: flowRate: 1.2 speed: 40.0 - thickness: 0.4 -shell: - thickness: 0.4 diff --git a/src/sliceActions/applyPrecision.js b/src/sliceActions/applyPrecision.js index 8fa3187..d7f8089 100644 --- a/src/sliceActions/applyPrecision.js +++ b/src/sliceActions/applyPrecision.js @@ -2,10 +2,11 @@ import { PRECISION } from '../constants.js' export default function applyPrecision(shapes) { for (let i = 0; i < shapes.length; i ++) { - const { closedShapes, openShapes } = shapes[i]; + const { fillShapes, lineShapesOpen, lineShapesClosed } = shapes[i]; - scaleUpShape(closedShapes); - scaleUpShape(openShapes); + scaleUpShape(fillShapes); + scaleUpShape(lineShapesOpen); + scaleUpShape(lineShapesClosed); } } diff --git a/src/sliceActions/generateInfills.js b/src/sliceActions/generateInfills.js index 0d0ca29..3ddfa56 100644 --- a/src/sliceActions/generateInfills.js +++ b/src/sliceActions/generateInfills.js @@ -5,19 +5,21 @@ import Shape from 'clipper-js'; export default function generateInfills(slices, settings) { let { layerHeight, - fill: { gridSize: fillGridSize }, - bottom: { thickness: bottomThickness }, - top: { thickness: topThickness }, + innerInfill: { gridSize: infillGridSize }, + thickness: { + top: topThickness, + bottom: bottomThickness + }, nozzleDiameter } = settings; - fillGridSize /= PRECISION; + infillGridSize /= PRECISION; nozzleDiameter /= PRECISION; const bottomSkinCount = Math.ceil(bottomThickness/layerHeight); const topSkinCount = Math.ceil(topThickness/layerHeight); const nozzleRadius = nozzleDiameter / 2; - const hightemplateSize = Math.sqrt(2 * Math.pow(nozzleDiameter, 2)); + const outerFillTemplateSize = Math.sqrt(2 * Math.pow(nozzleDiameter, 2)); for (let layer = 0; layer < slices.length; layer ++) { const slice = slices[layer]; @@ -32,39 +34,35 @@ export default function generateInfills(slices, settings) { for (let i = 0; i < slice.parts.length; i ++) { const part = slice.parts[i]; - if (!part.shape.closed) { - continue; + if (!part.closed) continue; + + const innerShell = part.shell[part.shell.length - 1]; + + if (innerShell.paths.length === 0) continue; + + const fillArea = innerShell.offset(-nozzleRadius); + let innerFillArea; + let outerFillArea; + if (surroundingLayer) { + outerFillArea = fillArea.difference(surroundingLayer).intersect(fillArea); + innerFillArea = fillArea.difference(outerFillArea); + } else { + outerFillArea = fillArea; } - const outerLine = part.outerLine; + if (innerFillArea && innerFillArea.paths.length > 0) { + const bounds = innerFillArea.shapeBounds(); + const innerFillTemplate = getFillTemplate(bounds, infillGridSize, true, true); - if (outerLine.paths.length > 0) { - const inset = (part.innerLines.length > 0) ? part.innerLines[part.innerLines.length - 1] : outerLine; + part.innerFill.join(innerFillTemplate.intersect(innerFillArea)); + } - const fillArea = inset.offset(-nozzleRadius); - let lowFillArea; - let highFillArea; - if (surroundingLayer) { - highFillArea = fillArea.difference(surroundingLayer).intersect(fillArea); - lowFillArea = fillArea.difference(highFillArea); - } else { - highFillArea = fillArea; - } + if (outerFillArea.paths.length > 0) { + const bounds = outerFillArea.shapeBounds(); + const even = (layer % 2 === 0); + const outerFillTemplate = getFillTemplate(bounds, outerFillTemplateSize, even, !even); - if (lowFillArea && lowFillArea.paths.length > 0) { - const bounds = lowFillArea.shapeBounds(); - const lowFillTemplate = getFillTemplate(bounds, fillGridSize, true, true); - - part.fill.join(lowFillTemplate.intersect(lowFillArea)); - } - - if (highFillArea.paths.length > 0) { - const bounds = highFillArea.shapeBounds(); - const even = (layer % 2 === 0); - const highFillTemplate = getFillTemplate(bounds, hightemplateSize, even, !even); - - part.fill.join(highFillTemplate.intersect(highFillArea)); - } + part.outerFill.join(outerFillTemplate.intersect(outerFillArea)); } } } diff --git a/src/sliceActions/generateInnerLines.js b/src/sliceActions/generateInnerLines.js index cfab81e..0c97a5d 100644 --- a/src/sliceActions/generateInnerLines.js +++ b/src/sliceActions/generateInnerLines.js @@ -12,12 +12,14 @@ export default function generateInnerLines(slices, settings) { let { layerHeight, nozzleDiameter, - shell: { thickness: shellThickness } + thickness: { shell: shellThickness } } = settings; + nozzleDiameter /= PRECISION; shellThickness /= PRECISION; + const nozzleRadius = nozzleDiameter / 2; - const shells = Math.round(shellThickness / nozzleDiameter); + const numShells = Math.round(shellThickness / nozzleDiameter); for (let layer = 0; layer < slices.length; layer ++) { const slice = slices[layer]; @@ -25,21 +27,21 @@ export default function generateInnerLines(slices, settings) { for (let i = 0; i < slice.parts.length; i ++) { const part = slice.parts[i]; - if (!part.shape.closed) continue; + if (!part.closed) continue; const outerLine = part.shape.offset(-nozzleRadius, offsetOptions); if (outerLine.paths.length > 0) { - part.outerLine.join(outerLine); + part.shell.push(outerLine); // start with 1 because outerLine is the 1st (0) shell - for (let shell = 1; shell < shells; shell += 1) { - const offset = shell * nozzleDiameter; + for (let inset = 1; inset < numShells; inset += 1) { + const offset = inset * nozzleDiameter; - const innerLine = outerLine.offset(-offset, offsetOptions); + const shell = outerLine.offset(-offset, offsetOptions); - if (innerLine.paths.length > 0) { - part.innerLines.push(innerLine); + if (shell.paths.length > 0) { + part.shell.push(shell); } else { break; } diff --git a/src/sliceActions/generateOutlines.js b/src/sliceActions/generateOutlines.js index 9fca57b..036b519 100644 --- a/src/sliceActions/generateOutlines.js +++ b/src/sliceActions/generateOutlines.js @@ -5,7 +5,10 @@ export default function calculateOutlines(slices, settings) { const slice = slices[layer]; slice.outline = slice.parts.reduce((shape, part) => { - if (part.outerLine) shape.join(part.outerLine); + if (part.closed) { + const [outerLine] = part.shell; + shape.join(outerLine); + } return shape; }, new Shape([], true)); } diff --git a/src/sliceActions/helpers/GCode.js b/src/sliceActions/helpers/GCode.js index 48bc6dc..4323ce9 100644 --- a/src/sliceActions/helpers/GCode.js +++ b/src/sliceActions/helpers/GCode.js @@ -10,16 +10,15 @@ const POSITION_Y = 'Y'; const POSITION_Z = 'Z'; export default class { - constructor(settings) { + constructor(nozzleToFilamentRatio) { + this._nozzleToFilamentRatio = nozzleToFilamentRatio; + this._gcode = ''; this._currentValues = {}; - this._settings = settings; this._nozzlePosition = new THREE.Vector2(0, 0); this._extruder = 0.0; this._isRetracted = false; this._isFanOn = false; - - this.bottom = true; } _addGCode(command) { @@ -62,14 +61,8 @@ export default class { return this; } - moveTo(x, y, layer) { - const { - layerHeight, - travelSpeed - } = this._settings; - - const z = layer * layerHeight + 0.2; - const speed = travelSpeed * 60; + moveTo(x, y, z, { speed }) { + speed *= 60; this._addGCode({ [MOVE]: 0, @@ -84,30 +77,13 @@ export default class { return this; } - lineTo(x, y, layer, type) { + lineTo(x, y, z, { speed, flowRate }) { const newNozzlePosition = new THREE.Vector2(x, y); - const { - layerHeight, - nozzleDiameter, - filamentThickness, - travelSpeed - } = this._settings; - - const profile = this._settings[(this.bottom ? 'bottom' : type)]; - - let { - speed, - flowRate - } = profile; - speed *= 60; - const z = layer * layerHeight + 0.2; const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition); - - const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI; - this._extruder += lineLength * ((nozzleDiameter * layerHeight) / filamentSurfaceArea) * flowRate; + this._extruder += this._nozzleToFilamentRatio * lineLength * flowRate; this._addGCode({ [MOVE]: 1, @@ -123,21 +99,13 @@ export default class { return this; } - unRetract() { - const { - retraction: { - enabled: retractionEnabled, - minDistance: retractionMinDistance, - speed: retractionSpeed - } - } = this._settings; - - if (this._isRetracted && retractionEnabled) { + unRetract({ enabled, speed, minDistance }) { + if (this._isRetracted && enabled) { this._isRetracted = false; - const speed = retractionSpeed * 60; + speed *= 60; - if (this._extruder > retractionMinDistance) { + if (this._extruder > minDistance) { this._addGCode({ [MOVE]: 0, [EXTRUDER]: this._extruder.toFixed(3), @@ -149,25 +117,16 @@ export default class { return this; } - retract() { - const { - retraction: { - amount: retractionAmount, - enabled: retractionEnabled, - minDistance: retractionMinDistance, - speed: retractionSpeed - } - } = this._settings; - - if (!this._isRetracted && retractionEnabled) { + retract({ enabled, speed, minDistance, amount }) { + if (!this._isRetracted && enabled) { this._isRetracted = true; - const speed = retractionSpeed * 60; + speed *= 60; - if (this._extruder > retractionMinDistance) { + if (this._extruder > minDistance) { this._addGCode({ [MOVE]: 0, - [EXTRUDER]: (this._extruder - retractionAmount).toFixed(3), + [EXTRUDER]: (this._extruder - amount).toFixed(3), [SPEED]: speed.toFixed(3) }); } diff --git a/src/sliceActions/helpers/Slice.js b/src/sliceActions/helpers/Slice.js index 968f70a..10b22fe 100644 --- a/src/sliceActions/helpers/Slice.js +++ b/src/sliceActions/helpers/Slice.js @@ -4,13 +4,13 @@ export default class { constructor() { this.parts = []; } - add(shape) { - const part = { shape }; + add(shape, closed) { + const part = { shape, closed }; - if (shape.closed) { - part.innerLines = []; - part.outerLine = new Shape([], true); - part.fill = new Shape([], false); + if (closed) { + part.shell = []; + part.innerFill = new Shape([], false); + part.outerFill = new Shape([], false); } this.parts.push(part); diff --git a/src/sliceActions/intersectionsToShapes.js b/src/sliceActions/intersectionsToShapes.js index c46fc05..f416f79 100644 --- a/src/sliceActions/intersectionsToShapes.js +++ b/src/sliceActions/intersectionsToShapes.js @@ -10,8 +10,9 @@ export default function intersectionsToShapes(layerIntersectionIndexes, layerInt if (intersectionIndexes.length === 0) continue; - const closedShapes = []; - const openShapes = []; + const fillShapes = []; + const lineShapesOpen = []; + const lineShapesClosed = []; for (let i = 0; i < intersectionIndexes.length; i ++) { let index = intersectionIndexes[i]; @@ -107,14 +108,17 @@ export default function intersectionsToShapes(layerIntersectionIndexes, layerInt } if (openGeometry) { - if (!openShape) shape.push(shape[0].clone()); - openShapes.push(shape); + if (openShape) { + lineShapesOpen.push(shape); + } else { + lineShapesClosed.push(shape); + } } else { - closedShapes.push(shape); + fillShapes.push(shape); } } - layers.push({ closedShapes, openShapes }); + layers.push({ fillShapes, lineShapesOpen, lineShapesClosed }); } return layers; diff --git a/src/sliceActions/optimizePaths.js b/src/sliceActions/optimizePaths.js index 5c6e718..18043ef 100644 --- a/src/sliceActions/optimizePaths.js +++ b/src/sliceActions/optimizePaths.js @@ -17,7 +17,7 @@ export default function optimizePaths(slices, settings) { for (let i = 0; i < slice.parts.length; i ++) { const part = slice.parts[i]; - const shape = part.shape.closed ? part.outerLine : part.shape; + const shape = part.closed ? part.shell[0] : part.shape; const bounds = shape.shapeBounds(); boundingBoxes.set(part, bounds); @@ -47,24 +47,24 @@ export default function optimizePaths(slices, settings) { const [part] = slice.parts.splice(closestPart, 1); parts.push(part); - if (part.shape.closed) { - if (part.outerLine.paths.length > 0) { - part.outerLine = optimizeShape(part.outerLine, start); - start.copy(part.outerLine.lastPoint(true)); + if (part.closed) { + for (let i = 0; i < part.shell.length; i ++) { + const shell = part.shell[i]; + + if (shell.paths.length === 0) continue; + + part.shell[i] = optimizeShape(shell, start); + start.copy(part.shell[i].lastPoint(true)); } - for (let i = 0; i < part.innerLines.length; i ++) { - const innerLine = part.innerLines[i]; - - if (innerLine.paths.length > 0) { - part.innerLines[i] = optimizeShape(innerLine, start); - start.copy(part.innerLines[i].lastPoint(true)); - } + if (part.outerFill.paths.length > 0) { + part.outerFill = optimizeShape(part.outerFill, start); + start.copy(part.outerFill.lastPoint(true)); } - if (part.fill.paths.length > 0) { - part.fill = optimizeShape(part.fill, start); - start.copy(part.fill.lastPoint(true)); + if (part.innerFill.paths.length > 0) { + part.innerFill = optimizeShape(part.innerFill, start); + start.copy(part.innerFill.lastPoint(true)); } } else { part.shape = optimizeShape(part.shape, start); diff --git a/src/sliceActions/removePrecision.js b/src/sliceActions/removePrecision.js index ec02f25..2ecd8df 100644 --- a/src/sliceActions/removePrecision.js +++ b/src/sliceActions/removePrecision.js @@ -9,13 +9,13 @@ export default function removePrecision(slices) { for (let i = 0; i < slice.parts.length; i ++) { const part = slice.parts[i]; - if (part.shape.closed) { - part.outerLine.scaleDown(inversePrecision); - for (let i = 0; i < part.innerLines.length; i ++) { - const innerLine = part.innerLines[i]; + if (part.closed) { + for (let i = 0; i < part.shell.length; i ++) { + const innerLine = part.shell[i]; innerLine.scaleDown(inversePrecision); } - part.fill.scaleDown(inversePrecision); + part.innerFill.scaleDown(inversePrecision); + part.outerFill.scaleDown(inversePrecision); } else { part.shape.scaleDown(inversePrecision); } diff --git a/src/sliceActions/shapesToSlices.js b/src/sliceActions/shapesToSlices.js index ea0e5be..2d0fe94 100644 --- a/src/sliceActions/shapesToSlices.js +++ b/src/sliceActions/shapesToSlices.js @@ -9,15 +9,18 @@ export default function shapesToSlices(shapes, settings) { const sliceLayers = []; for (let layer = 0; layer < shapes.length; layer ++) { - let { closedShapes, openShapes } = shapes[layer]; + let { fillShapes, lineShapesOpen, lineShapesClosed } = shapes[layer]; - closedShapes = new Shape(closedShapes, true, true, true, true) + fillShapes = new Shape(fillShapes, true, true, true, true) .fixOrientation() .simplify('pftNonZero') .clean(cleanDelta) .seperateShapes(); - openShapes = new Shape(openShapes, false, true, true, true); + lineShapesClosed = new Shape(lineShapesClosed, true, true, true, true) + .clean(cleanDelta); + + lineShapesOpen = new Shape(lineShapesOpen, false, true, true, true); // .clean(cleanDelta); // TODO // Cleaning is actually wanted here but there is a bug in the clean function @@ -25,17 +28,24 @@ export default function shapesToSlices(shapes, settings) { const slice = new Slice(); - for (let i = 0; i < closedShapes.length; i ++) { - const closedShape = closedShapes[i]; - slice.add(closedShape); + for (let i = 0; i < fillShapes.length; i ++) { + const fillShape = fillShapes[i]; + slice.add(fillShape, true); - // if (openShapes.path.length > 0) { - // openShapes = openShapes.difference(closedShape); + // if (lineShapesClosed.paths.length > 0) { + // lineShapesClosed = lineShapesClosed.difference(closedShape); + // } + // if (lineShapesOpen.paths.length > 0) { + // lineShapesOpen = lineShapesOpen.difference(closedShape); // } } - if (openShapes.paths.length > 0) { - slice.add(openShapes); + if (lineShapesClosed.paths.length > 0) { + slice.add(lineShapesClosed, false); + } + + if (lineShapesOpen.paths.length > 0) { + slice.add(lineShapesOpen, false); } sliceLayers.push(slice); diff --git a/src/sliceActions/slicesToGCode.js b/src/sliceActions/slicesToGCode.js index 407619a..fd38039 100644 --- a/src/sliceActions/slicesToGCode.js +++ b/src/sliceActions/slicesToGCode.js @@ -1,47 +1,80 @@ import GCode from './helpers/GCode.js'; -export default function slicesToGCode(slices, settings) { - const gcode = new GCode(settings); +const PROFILE_TYPES = ['support', 'innerShell', 'outerShell', 'innerInfill', 'outerInfill', 'brim']; +export default function slicesToGCode(slices, settings) { + const { + layerHeight, + filamentThickness, + nozzleDiameter, + travelSpeed, + retraction, + travel + } = settings; + + const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI; + const lineSurfaceArea = nozzleDiameter * layerHeight; + const nozzleToFilamentRatio = lineSurfaceArea / filamentSurfaceArea; + + const gcode = new GCode(nozzleToFilamentRatio); + + const defaultProfile = { + travelProfile: travel, + retractionProfile: retraction + }; + + let isFirstLayer = true; for (let layer = 0; layer < slices.length; layer ++) { const slice = slices[layer]; + const z = layer * layerHeight + 0.2; if (layer === 1) { gcode.turnFanOn(); - gcode.bottom = false; + isFirstLayer = false; } + const profiles = PROFILE_TYPES.reduce((profiles, profileType) => { + profiles[profileType] = { + ...defaultProfile, + lineProfile: isFirstLayer ? settings.firstLayer : settings[profileType] + }; + return profiles; + }, {}); + if (typeof slice.brim !== 'undefined') { - pathToGCode(gcode, slice.brim, true, true, layer, 'brim'); + pathToGCode(gcode, slice.brim, true, true, z, profiles.brim); } for (let i = 0; i < slice.parts.length; i ++) { const part = slice.parts[i]; - if (part.shape.closed) { - pathToGCode(gcode, part.outerLine, false, true, layer, 'outerLine'); + if (part.closed) { + for (let i = 0; i < part.shell.length; i ++) { + const shell = part.shell[i]; + const isOuterShell = i === 0; - for (let i = 0; i < part.innerLines.length; i ++) { - const innerLine = part.innerLines[i]; - pathToGCode(gcode, innerLine, false, false, layer, 'innerLine'); + const unRetract = isOuterShell; + const profile = isOuterShell ? profiles.outerShell : profiles.innerShell; + pathToGCode(gcode, shell, false, unRetract, z, profile); } - pathToGCode(gcode, part.fill, true, false, layer, 'fill'); + pathToGCode(gcode, part.outerFill, false, false, z, profiles.outerInfill); + pathToGCode(gcode, part.innerFill, true, false, z, profiles.innerInfill); } else { const retract = !(slice.parts.length === 1 && typeof slice.support === 'undefined'); - pathToGCode(gcode, part.shape, retract, retract, layer, 'outerLine'); + pathToGCode(gcode, part.shape, retract, retract, z, profiles.outerShell); } } if (typeof slice.support !== 'undefined') { - pathToGCode(gcode, slice.support, true, true, layer, 'support'); + pathToGCode(gcode, slice.support, true, true, z, profiles.support); } } return gcode.getGCode(); } -function pathToGCode(gcode, shape, retract, unRetract, layer, type) { +function pathToGCode(gcode, shape, retract, unRetract, z, { lineProfile, travelProfile, retractionProfile }) { const { closed } = shape; const paths = shape.mapToLower(); @@ -55,18 +88,18 @@ function pathToGCode(gcode, shape, retract, unRetract, layer, type) { if (i === 0) { // TODO // moveTo should impliment combing - gcode.moveTo(point.x, point.y, layer); + gcode.moveTo(point.x, point.y, z, travelProfile); if (unRetract) { - gcode.unRetract(); + gcode.unRetract(retractionProfile); } } else { - gcode.lineTo(point.x, point.y, layer, type); + gcode.lineTo(point.x, point.y, z, lineProfile); } } } if (retract) { - gcode.retract(); + gcode.retract(retractionProfile); } }