diff --git a/.travis.yml b/.travis.yml index cf787f3..6ea24dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js node_js: - - "4.2" + - "4.4" - "6.9" sudo: false diff --git a/README.md b/README.md index 19ebdd1..e517a54 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,43 @@ [![GitHub version](https://badge.fury.io/gh/LaserWeb%2Flw-gcode-parser.svg)](https://badge.fury.io/gh/LaserWeb%2Flw-gcode-parser) +## Table of Contents -## General information +- [Background](#background) +- [Installation](#installation) +- [Usage](#usage) +- [API](#api) +- [Contribute](#contribute) +- [License](#license) + + +## Background + +To a large extent based on John Lauer's & contributors work + +##Installation -To a large extent based on John Lauer's & contributors work ## Usage -## TODO +## API +## Contribute + +PRs accepted. + +Small note: If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + ## LICENSE [The MIT License (MIT)](https://github.com/LaserWeb/lw-gcode-parser/blob/master/LICENSE) - - - +[![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) + [![Build Status](https://travis-ci.org/LaserWeb/lw-gcode-parser.svg?branch=master)](https://travis-ci.org/LaserWeb/lw-gcode-parser) [![Dependency Status](https://david-dm.org/LaserWeb/lw-gcode-parser.svg)](https://david-dm.org/LaserWeb/lw-gcode-parser) [![devDependency Status](https://david-dm.org/LaserWeb/lw-gcode-parser/dev-status.svg)](https://david-dm.org/LaserWeb/lw-gcode-parser#info=devDependencies) diff --git a/package.json b/package.json index 2d20a29..73317d5 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { }, "devDependencies": { + "lw-sample-files":"LaserWeb/lw-sample-files", "ava": "0.16", "babel-cli": "^6.6.5", "babel-core": "^6.2.1", diff --git a/src/gcodeHandlers.js b/src/gcodeHandlers.js new file mode 100644 index 0000000..744d94e --- /dev/null +++ b/src/gcodeHandlers.js @@ -0,0 +1,312 @@ +import { absolute, delta } from './utils' +import { addSegment, addLineSegment, addFakeSegment } from './geometries/segments' +import { newLayer } from './layers' + +export default function makeHandlers (params) { + const handlers = { + // set the g92 offsets for the parser - defaults to no offset + // When doing CNC, generally G0 just moves to a new location + // as fast as possible which means no milling or extruding is happening in G0. + // So, let's color it uniquely to indicate it's just a toolhead move. + G0: function (state, args, index) { + const {lastLine} = state + const newLine = { + g: 0, + x: args.x !== undefined ? absolute(state.relative, lastLine.x, args.x) + state.specifics.G92.offset.x : lastLine.x, + y: args.y !== undefined ? absolute(state.relative, lastLine.y, args.y) + state.specifics.G92.offset.y : lastLine.y, + z: args.z !== undefined ? absolute(state.relative, lastLine.z, args.z) + state.specifics.G92.offset.z : lastLine.z, + a: args.a !== undefined ? absolute(state.relative, lastLine.a, args.a) + state.specifics.G92.offset.a : lastLine.a, + e: args.e !== undefined ? absolute(state.relative, lastLine.e, args.e) + state.specifics.G92.offset.e : lastLine.e, + f: args.f !== undefined ? args.f : lastLine.f, + s: 100, + t: undefined + } + addLineSegment(state, args, lastLine, newLine) + state.lastLine = newLine + }, + G1: function (state, args, index) { + const {lastLine, layer} = state + // Example: G1 Z1.0 F3000 + // G1 X99.9948 Y80.0611 Z15.0 F1500.0 E981.64869 + // G1 E104.25841 F1800.0 + // Go in a straight line from the current (X, Y) point + // to the point (90.6, 13.8), extruding material as the move + // happens from the current extruded length to a length of + // 22.4 mm. + + const newLine = { + g: 1, + x: args.x !== undefined ? absolute(state.relative, lastLine.x, args.x) + state.specifics.G92.offset.x : lastLine.x, + y: args.y !== undefined ? absolute(state.relative, lastLine.y, args.y) + state.specifics.G92.offset.y : lastLine.y, + z: args.z !== undefined ? absolute(state.relative, lastLine.z, args.z) + state.specifics.G92.offset.z : lastLine.z, + a: args.a !== undefined ? absolute(state.relative, lastLine.a, args.a) + state.specifics.G92.offset.a : lastLine.a, + e: args.e !== undefined ? absolute(state.relative, lastLine.e, args.e) + state.specifics.G92.offset.e : lastLine.e, + f: args.f !== undefined ? args.f : lastLine.f, + s: args.s !== undefined ? args.s : lastLine.s, + t: args.t !== undefined ? args.t : lastLine.t + } + /* layer change detection is or made by watching Z, it's made by + watching when we extrude at a new Z position */ + if (delta(state.relative, lastLine.e, newLine.e) > 0) { + newLine.extruding = delta(state.relative, lastLine.e, newLine.e) > 0 + if (layer === undefined || newLine.z !== layer.z) { + newLayer(newLine, state.layers.layers3d) + } + } + addLineSegment(state, args, lastLine, newLine) + state.lastLine = newLine + }, + G2: function (state, args, index, gcp) { + const {plane, lastLine} = state + // this is an arc move from lastLine's xy to the new xy. we'll + // show it as a light gray line, but we'll also sub-render the + // arc itself by figuring out the sub-segments + + args.plane = plane // set the plane for this command to whatever the current plane is + + const newLine = { + x: args.x !== undefined ? absolute(state.relative, lastLine.x, args.x) + state.specifics.G92.offset.x : lastLine.x, + y: args.y !== undefined ? absolute(state.relative, lastLine.y, args.y) + state.specifics.G92.offset.y : lastLine.y, + z: args.z !== undefined ? absolute(state.relative, lastLine.z, args.z) + state.specifics.G92.offset.z : lastLine.z, + a: args.a !== undefined ? absolute(state.relative, lastLine.a, args.a) + state.specifics.G92.offset.a : lastLine.a, + e: args.e !== undefined ? absolute(state.relative, lastLine.e, args.e) + state.specifics.G92.offset.e : lastLine.e, + f: args.f !== undefined ? args.f : lastLine.f, + s: args.s !== undefined ? args.s : lastLine.s, + t: args.t !== undefined ? args.t : lastLine.t, + arci: args.i ? args.i : null, + arcj: args.j ? args.j : null, + arck: args.k ? args.k : null, + arcr: args.r ? args.r : null, + + arc: true, + clockwise: !args.clockwise ? true : args.clockwise // FIXME : always true ?? + } + // if (args.clockwise === false) newLine.clockwise = args.clockwise + addSegment(state, args, lastLine, newLine) + state.lastLine = newLine + }, + G3: function (state, args, index, gcp) { + const {plane, lastLine} = state + + // this is an arc move from lastLine's xy to the new xy. same + // as G2 but reverse + args.arc = true + args.clockwise = false + args.plane = plane // set the plane for this command to whatever the current plane is + + const newLine = { + x: args.x !== undefined ? absolute(state.relative, lastLine.x, args.x) + state.specifics.G92.offset.x : lastLine.x, + y: args.y !== undefined ? absolute(state.relative, lastLine.y, args.y) + state.specifics.G92.offset.y : lastLine.y, + z: args.z !== undefined ? absolute(state.relative, lastLine.z, args.z) + state.specifics.G92.offset.z : lastLine.z, + a: args.a !== undefined ? absolute(state.relative, lastLine.a, args.a) + state.specifics.G92.offset.a : lastLine.a, + e: args.e !== undefined ? absolute(state.relative, lastLine.e, args.e) + state.specifics.G92.offset.e : lastLine.e, + f: args.f !== undefined ? args.f : lastLine.f, + s: args.s !== undefined ? args.s : lastLine.s, + t: args.t !== undefined ? args.t : lastLine.t, + arci: args.i ? args.i : null, + arcj: args.j ? args.j : null, + arck: args.k ? args.k : null, + arcr: args.r ? args.r : null, + + arc: true, + clockwise: !args.clockwise ? true : args.clockwise // FIXME : always true ?? + } + // if (args.clockwise === false) newLine.clockwise = args.clockwise + addSegment(state, args, lastLine, newLine) + state.lastLine = newLine + }, + + G7: function (state, args, index) { + const {lastLine} = state + // Example: G7 L68 D//////sljasflsfagdxsd,.df9078rhfnxm (68 of em) + // G7 $1 L4 DAAA= + // G7 $0 L4 D2312 + // Move right (if $1) or left (if $0) 51 steps (from L68) + // (the number of steps is found when decoding the data) + // and burn the laser with the intensity in the base64-encoded + // data in D. Data in D is 51 base64-encoded bytes with grayscale + // intensity. When base64-encoded the string becomes 68 bytes long. + // + // SpotSize comes from a previous M649 S100 R0.1 + // where S is intensity (100 is max) and R gives spotsize in mm. + // Actual laser power is then D-value * S-value in every pixel + // A direction change with $0/$1 gives a spotSize long movement in Y + // for the next row. + + var buf = atob(args.d) + + if (typeof args.dollar !== 'undefined') { // Move Y, change direction + state.specifics.G7.dir = args.dollar + + const newLine = { + g: 0, + x: lastLine.x, + y: lastLine.y + state.specifics.G7.spotSize, + z: lastLine.z, + a: lastLine.a, + e: lastLine.e, + f: lastLine.f, + s: 100, + t: lastLine.t + } + addLineSegment(state, args, lastLine, newLine) + state.lastLine = newLine + } + for (var i = 0; i < buf.length; i++) { // Process a base64-encoded chunk + const intensity = 255 - buf.charCodeAt(i) // 255 - 0 + const newLine = { + g: 7, + x: lastLine.x + state.specifics.G7.spotSize * (state.specifics.G7.dir === 1 ? 1 : -1), + y: lastLine.y, + z: lastLine.z, + a: lastLine.a, + e: lastLine.e, + f: lastLine.f, + s: intensity, + t: lastLine.t + } + addLineSegment(state, args, lastLine, newLine) + state.lastLine = newLine + } + }, + + G17: function (state, args) { + console.log('SETTING XY PLANE') + state.plane = 'G17' + addFakeSegment(state, args) + }, + + G18: function (state, args) { + console.log('SETTING XZ PLANE') + state.plane = 'G18' + addFakeSegment(state, args) + }, + + G19: function (state, args) { + console.log('SETTING YZ PLANE') + state.plane = 'G19' + addFakeSegment(state, args) + }, + + G20: function (state, args) { + // G21: Set Units to Inches + // We don't really have to do anything since 3d viewer is unit agnostic + // However, we need to set a global property so the trinket decorations + // like toolhead, axes, grid, and extent labels are scaled correctly + // later on when they are drawn after the gcode is rendered + // console.log("SETTING UNITS TO INCHES!!!") + state.unitsMm = false // false means inches cuz default is mm + addFakeSegment(state, args) + }, + + G21: function (state, args) { + // G21: Set Units to Millimeters + // Example: G21 + // Units from now on are in millimeters. (This is the RepRap default.) + // console.log("SETTING UNITS TO MM!!!") + state.unitsMm = true + addFakeSegment(state, args) + }, + + G73: function (state, args, index, gcp) { + // peck drilling. just treat as g1 + console.log('G73 gcp:', gcp) + gcp.handlers.G1(args) + }, + G90: function (state, args) { + // G90: Set to Absolute Positioning + // Example: G90 + // All coordinates from now on are absolute relative to the + // origin of the machine. (This is the RepRap default.) + + state.relative = false + addFakeSegment(state, args) + }, + + G91: function (state, args) { + // G91: Set to Relative Positioning + // Example: G91 + // All coordinates from now on are relative to the last position. + + // TODO! + state.relative = true + addFakeSegment(state, args) + }, + + G92: function (state, args) { // E0 + // G92: Set Position + // Example: G92 E0 + // Allows programming of absolute zero point, by reseting the + // current position to the values specified. This would set the + // machine's X coordinate to 10, and the extrude coordinate to 90. + // No physical motion will occur. + + // TODO: Only support E0 + let newLine = state.lastLine + state.specifics.G92.offset.x = (args.x !== undefined ? (args.x === 0 ? newLine.x : newLine.x - args.x) : 0) + state.specifics.G92.offset.y = (args.y !== undefined ? (args.y === 0 ? newLine.y : newLine.y - args.y) : 0) + state.specifics.G92.offset.z = (args.z !== undefined ? (args.z === 0 ? newLine.z : newLine.z - args.z) : 0) + state.specifics.G92.offset.a = (args.a !== undefined ? (args.a === 0 ? newLine.a : newLine.a - args.a) : 0) + state.specifics.G92.offset.e = (args.e !== undefined ? (args.e === 0 ? newLine.e : newLine.e - args.e) : 0) + + // newLine.x = args.x !== undefined ? args.x + newLine.x : newLine.x + // newLine.y = args.y !== undefined ? args.y + newLine.y : newLine.y + // newLine.z = args.z !== undefined ? args.z + newLine.z : newLine.z + // newLine.e = args.e !== undefined ? args.e + newLine.e : newLine.e + + // console.log("G92", lastLine, newLine, state, args.specifics.G92.offset) + + // state.lastLine = newLine + addFakeSegment(state, args) + }, + M30: function (state, args) { + addFakeSegment(state, args) + }, + M82: function (state, args) { + // M82: Set E codes absolute (default) + // Descriped in Sprintrun source code. + + // No-op, so long as M83 is not supported. + addFakeSegment(state, args) + }, + + M84: function (state, args) { + // M84: Stop idle hold + // Example: M84 + // Stop the idle hold on all axis and extruder. In some cases the + // idle hold causes annoying noises, which can be stopped by + // disabling the hold. Be aware that by disabling idle hold during + // printing, you will get quality issues. This is recommended only + // in between or after printjobs. + + // No-op + addFakeSegment(state, args) + }, + + M649: function (state, args) { + // M649: Laser options for Marlin + // M649 S R B2 + // Intensity = lasermultiply? + if (typeof args.r !== 'undefined') { state.specifics.G7.spotSize = args.r } + }, + + // Dual Head 3D Printing Support + T0: function (state, args) { + // console.log('Found Tool: ', args) + state.lastLine.t = 0 + addFakeSegment(state, args) + }, + + T1: function (state, args) { + // console.log('Found Tool: ', args) + state.lastLine.t = 1 + addFakeSegment(state, args) + }, + + 'default': function (state, args, index) { + addFakeSegment(state, args) + } + } + + return handlers +} diff --git a/src/geometries/arcs.js b/src/geometries/arcs.js new file mode 100644 index 0000000..32e3395 --- /dev/null +++ b/src/geometries/arcs.js @@ -0,0 +1,108 @@ +export function drawArc (state, aX, aY, aZ, endaZ, aRadius, aStartAngle, aEndAngle, aClockwise, plane) { + // console.log("drawArc:", aX, aY, aZ, aRadius, aStartAngle, aEndAngle, aClockwise) + let ac = new THREE.ArcCurve(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) // FIXME a harder one ... + // console.log("ac:", ac) + const material = { + color: 0x00aaff, + opacity: 0.5 + } + let geometry = { + positions: [] + } + let ctr = 0 + let z = aZ + ac.getPoints(20).forEach(function (v) { + // console.log(v) + z = (((endaZ - aZ) / 20) * ctr) + aZ + geometry.positions.push(v.x, v.y, z) + ctr++ + }) + const aco = {geometry, material} + // aco.position.set(pArc.x, pArc.y, pArc.z) + // console.log("aco:", aco) + state.extraObjects[plane].push(aco) + return aco +} + +export function drawArcFrom2PtsAndCenter (vp1, vp2, vpArc, args) { + // console.log("drawArcFrom2PtsAndCenter. vp1:", vp1, "vp2:", vp2, "vpArc:", vpArc, "args:", args) + + // var radius = vp1.distanceTo(vpArc) + // console.log("radius:", radius) + + // Find angle + const p1deltaX = vpArc.x - vp1.x + const p1deltaY = vpArc.y - vp1.y + const p1deltaZ = vpArc.z - vp1.z + + const p2deltaX = vpArc.x - vp2.x + const p2deltaY = vpArc.y - vp2.y + const p2deltaZ = vpArc.z - vp2.z + + let anglepArcp1 + let anglepArcp2 + switch (args.plane) { + case 'G18': + anglepArcp1 = Math.atan(p1deltaZ / p1deltaX) + anglepArcp2 = Math.atan(p2deltaZ / p2deltaX) + break + case 'G19': + anglepArcp1 = Math.atan(p1deltaZ / p1deltaY) + anglepArcp2 = Math.atan(p2deltaZ / p2deltaY) + break + default: + anglepArcp1 = Math.atan(p1deltaY / p1deltaX) + anglepArcp2 = Math.atan(p2deltaY / p2deltaX) + } + + // Draw arc from arc center + const radius = vpArc.distanceTo(vp1) + const radius2 = vpArc.distanceTo(vp2) + // console.log("radius:", radius) + + if (Number((radius).toFixed(2)) != Number((radius2).toFixed(2))) { + console.log('Radiuses not equal. r1:', radius, ', r2:', radius2, ' with args:', args, ' rounded vals r1:', Number((radius).toFixed(2)), ', r2:', Number((radius2).toFixed(2))) + } + + // arccurve + var clwise = args.clockwise + //if (args.clockwise === false) clwise = false + + switch (args.plane) { + case 'G19': + if (p1deltaY >= 0) anglepArcp1 += Math.PI + if (p2deltaY >= 0) anglepArcp2 += Math.PI + break + default: + if (p1deltaX >= 0) anglepArcp1 += Math.PI + if (p2deltaX >= 0) anglepArcp2 += Math.PI + } + + if (anglepArcp1 === anglepArcp2 && clwise === false) + { + let obj = undefined + // Draw full circle if angles are both zero, + // start & end points are same point... I think + switch (args.plane) { + case 'G18': + obj = drawArc(vpArc.x, vpArc.z, (-1 * vp1.y), (-1 * vp2.y), radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, 'G18') + break + case 'G19': + obj = drawArc(vpArc.y, vpArc.z, vp1.x, vp2.x, radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, 'G19') + break + default: + obj = drawArc(vpArc.x, vpArc.y, vp1.z, vp2.z, radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, 'G17') + } + } + else + switch (args.plane) { + case 'G18': + obj = drawArc(vpArc.x, vpArc.z, (-1 * vp1.y), (-1 * vp2.y), radius, anglepArcp1, anglepArcp2, clwise, 'G18') + break + case 'G19': + obj = drawArc(vpArc.y, vpArc.z, vp1.x, vp2.x, radius, anglepArcp1, anglepArcp2, clwise, 'G19') + break + default: + obj = drawArc(vpArc.x, vpArc.y, vp1.z, vp2.z, radius, anglepArcp1, anglepArcp2, clwise, 'G17') + } +} diff --git a/src/geometries/segments.js b/src/geometries/segments.js new file mode 100644 index 0000000..2a9adc8 --- /dev/null +++ b/src/geometries/segments.js @@ -0,0 +1,383 @@ +import { getLineGroup } from '../layers' +import { drawArcFrom2PtsAndCenter } from './arcs' + +export function addSegment (state, args, p1, p2) { + if (state.debug) { + console.log('addSegment') + } + throw new Error('SEGMENT !!') + let {bbox, bbox2} = state + // console.log("") + // console.log("addSegment p2:", p2) + // add segment to array for later use + /*MM_removed + let group = getLineGroup(p2, args) + let geometry = group.geometry + group.segmentCount++ + */ + + // see if we need to draw an arc + if (p2.arc) { + // console.log("drawing arc. p1:", p1, ", p2:", p2) + // let segmentCount = 12 + // figure out the 3 pts we are dealing with + // the start, the end, and the center of the arc circle + // radius is dist from p1 x/y/z to pArc x/y/z + // if(args.clockwise === false || args.cmd === "G3"){ + // let vp2 = [p1.x, p1.y, p1.z] + // let vp1 = [p2.x, p2.y, p2.z] + // } + // else { + const vp1 = [p1.x, p1.y, p1.z] + const vp2 = [p2.x, p2.y, p2.z] + // } + let vpArc + + // if this is an R arc gcode command, we're given the radius, so we + // don't have to calculate it. however we need to determine center + // of arc + if (args.r != null) { + // console.log("looks like we have an arc with R specified. args:", args) + // console.log("anglepArcp1:", anglepArcp1, "anglepArcp2:", anglepArcp2) + const radius = parseFloat(args.r) + // First, find the distance between points 1 and 2. We'll call that q, + // and it's given by sqrt((x2-x1)^2 + (y2-y1)^2). + const q = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2) + Math.pow(p2.z - p1.z, 2)) + + // Second, find the point halfway between your two points. We'll call it + // (x3, y3). x3 = (x1+x2)/2 and y3 = (y1+y2)/2. + const x3 = (p1.x + p2.x) / 2 + const y3 = (p1.y + p2.y) / 2 + const z3 = (p1.z + p2.z) / 2 + + // There will be two circle centers as a result of this, so + // we will have to pick the correct one. In gcode we can get + // a + or - val on the R to indicate which circle to pick + // One answer will be: + // x = x3 + sqrt(r^2-(q/2)^2)*(y1-y2)/q + // y = y3 + sqrt(r^2-(q/2)^2)*(x2-x1)/q + // The other will be: + // x = x3 - sqrt(r^2-(q/2)^2)*(y1-y2)/q + // y = y3 - sqrt(r^2-(q/2)^2)*(x2-x1)/q + + let calc = Math.sqrt((radius * radius) - Math.pow(q / 2, 2)) + let pArc_1 + let pArc_2 + let angle_point + let cw + let ccw + + switch (args.plane) { + case 'G18': + pArc_1 = { + x: x3 + calc * (p1.z - p2.z) / q, + y: y3 + calc * (p2.y - p1.y) / q, + z: z3 + calc * (p2.x - p1.x) / q } + pArc_2 = { + x: x3 - calc * (p1.z - p2.z) / q, + y: y3 - calc * (p2.y - p1.y) / q, + z: z3 - calc * (p2.x - p1.x) / q } + + angle_point = Math.atan2(p1.z, p1.x) - Math.atan2(p2.z, p2.x) + if (((p1.x - pArc_1.x) * (p1.z + pArc_1.z)) + ((pArc_1.x - p2.x) * (pArc_1.z + p2.z)) >= + ((p1.x - pArc_2.x) * (p1.z + pArc_2.z)) + ((pArc_2.x - p2.x) * (pArc_2.z + p2.z))) { + cw = pArc_1 + ccw = pArc_2 + } else { + cw = pArc_2 + ccw = pArc_1 + } + break + case 'G19': + pArc_1 = { + x: x3 + calc * (p1.x - p2.x) / q, + y: y3 + calc * (p1.z - p2.z) / q, + z: z3 + calc * (p2.y - p1.y) / q } + pArc_2 = { + x: x3 - calc * (p1.x - p2.x) / q, + y: y3 - calc * (p1.z - p2.z) / q, + z: z3 - calc * (p2.y - p1.y) / q } + + if (((p1.y - pArc_1.y) * (p1.z + pArc_1.z)) + ((pArc_1.y - p2.y) * (pArc_1.z + p2.z)) >= + ((p1.y - pArc_2.y) * (p1.z + pArc_2.z)) + ((pArc_2.y - p2.y) * (pArc_2.z + p2.z))) { + cw = pArc_1 + ccw = pArc_2 + } else { + cw = pArc_2 + ccw = pArc_1 + } + break + default: + pArc_1 = { + x: x3 + calc * (p1.y - p2.y) / q, + y: y3 + calc * (p2.x - p1.x) / q, + z: z3 + calc * (p2.z - p1.z) / q } + pArc_2 = { + x: x3 - calc * (p1.y - p2.y) / q, + y: y3 - calc * (p2.x - p1.x) / q, + z: z3 - calc * (p2.z - p1.z) / q } + if (((p1.x - pArc_1.x) * (p1.y + pArc_1.y)) + ((pArc_1.x - p2.x) * (pArc_1.y + p2.y)) >= + ((p1.x - pArc_2.x) * (p1.y + pArc_2.y)) + ((pArc_2.x - p2.x) * (pArc_2.y + p2.y))) { + cw = pArc_1 + ccw = pArc_2 + } else { + cw = pArc_2 + ccw = pArc_1 + } + } + + if ((p2.clockwise === true && radius >= 0) || (p2.clockwise === false && radius < 0)) { + vpArc = [cw.x, cw.y, cw.z] + } + else vpArc = [ccw.x, ccw.y, ccw.z] + } else { + // this code deals with IJK gcode commands + /*if(args.clockwise === false || args.cmd === "G3") + let pArc = { + x: p2.arci ? p1.x + p2.arci : p1.x, + y: p2.arcj ? p1.y + p2.arcj : p1.y, + z: p2.arck ? p1.z + p2.arck : p1.z, + ยจ } + else*/ + const pArc = { + x: p2.arci ? p1.x + p2.arci : p1.x, + y: p2.arcj ? p1.y + p2.arcj : p1.y, + z: p2.arck ? p1.z + p2.arck : p1.z + } + vpArc = [pArc.x, pArc.y, pArc.z] + } + + const arcObj = drawArcFrom2PtsAndCenter(vp1, vp2, vpArc, args) + // still push the normal p1/p2 point for debug + p2.g2 = true + p2.arcObj = arcObj + // MM_removed + // group = getLineGroup(p2, args) + + // these golden lines showing start/end of a g2 or g3 arc were confusing people + // so hiding them for now. jlauer 8/15/15 + /* + geometry = group.geometry + geometry.positions.push( + [p1.x, p1.y, p1.z]) + geometry.positions.push( + [p2.x, p2.y, p2.z)) + geometry.colors.push(group.color) + geometry.colors.push(group.color) + */ + } else { + geometry.positions.push(p1.x, p1.y, p1.z) + geometry.positions.push(p2.x, p2.y, p2.z) + + /*MM_removed + geometry.colors.push(group.color) + geometry.colors.push(group.color)*/ + } + + if (p2.extruding) { + bbox.min.x = Math.min(bbox.min.x, p2.x) + bbox.min.y = Math.min(bbox.min.y, p2.y) + bbox.min.z = Math.min(bbox.min.z, p2.z) + bbox.max.x = Math.max(bbox.max.x, p2.x) + bbox.max.y = Math.max(bbox.max.y, p2.y) + bbox.max.z = Math.max(bbox.max.z, p2.z) + } + if (p2.g === 0) { + // we're in a toolhead move, label moves + /* + if (group.segmentCount < 2) { + makeSprite(scene, "webgl", { + x: p2.x, + y: p2.y, + z: p2.z + 0, + text: group.segmentCount, + color: "#ff00ff", + size: 3, + }) + } + */ + } + // global bounding box calc + bbox2.min.x = Math.min(bbox2.min.x, p2.x) + bbox2.min.y = Math.min(bbox2.min.y, p2.y) + bbox2.min.z = Math.min(bbox2.min.z, p2.z) + bbox2.max.x = Math.max(bbox2.max.x, p2.x) + bbox2.max.y = Math.max(bbox2.max.y, p2.y) + bbox2.max.z = Math.max(bbox2.max.z, p2.z) + + /* NEW METHOD OF CREATING OBJECTS + create new approach for objects which is + a unique object for each line of gcode, including g2/g3's + make sure userData is good too + */ + /*MM_removed + let gcodeObj + + if (p2.arc) { + // use the arc that already got built + gcodeObj = p2.arcObj + } else { + // make a line + let color = 0X0000ff + if (p2.extruding) { + color = 0xff00ff + } else if (p2.g === 0) { + color = 0x00ff00 + } else if (p2.g === 2) { + // color = 0x999900 + } else if (p2.arc) { + color = 0x0033ff + } + + const material = { + color: color, + opacity: 0.5 + } + gcodeObj = {geometry, material} + } + gcodeObj.p2 = p2 + gcodeObj.args = args + state.container.children.push(gcodeObj)*/ + + state.bbox = bbox + state.bbox2 = bbox2 +} + +export function addFakeSegment (state, args) { // lineObject, lastLine, lines) { + if (state.debug) { + console.log('addFakeSegment') + } + // line.args = args + const arg2 = { + isFake: true, + text: args.text, + index: args.index, + isComment: args.text.match(/^(;|\(|<)/) + } + state.lines.push({ + p2: state.lastLine, // since this is fake, just use lastLine as xyz + args: arg2 + }) +} + +export function addLineSegment (state, args, p1, p2) { + if (state.debug) { + console.log('addLineSegment') + } + let {linesData, linesDataOffset, linesDataStride, bufSize} = state + + //to store the next batch of data we need this many chunks + const chunkIndex = Math.ceil((linesDataOffset + linesDataStride) / bufSize) + const chunksNb = linesData.length / bufSize + if (chunkIndex > chunksNb) { // resize data to fit new entries + //console.log('resizing') + let resizedLinesData = new Float32Array(state.bufSize * chunkIndex) + resizedLinesData.set(linesData, 0) + linesData = state.linesData = resizedLinesData + } + // let i = lineObject.nLines * 6 + let i = state.linesDataOffset + + if (p1.a !== 0 || p2.a !== 0) { // A axis: rotate around X + const R1 = Math.sqrt(p1.y * p1.y + p1.z * p1.z) + const R2 = Math.sqrt(p2.y * p2.y + p2.z * p2.z) + const a1 = p1.y === 0 ? Math.sign(p1.z) * 90 : Math.atan2(p1.z, p1.y) * 180.0 / Math.PI + const a2 = p2.y === 0 ? Math.sign(p2.z) * 90 : Math.atan2(p2.z, p2.y) * 180.0 / Math.PI + + linesData[i + 0] = p1.g + linesData[i + 1] = p1.x + linesData[i + 2] = R1 * Math.cos((-p1.a + a1) * Math.PI / 180.0) + linesData[i + 3] = R1 * Math.sin((-p1.a + a1) * Math.PI / 180.0) + linesData[i + 4] = p1.e + linesData[i + 5] = p1.f + linesData[i + 6] = p1.a + linesData[i + 7] = p1.s + linesData[i + 8] = p1.t + + linesData[i + 9] = p2.g + linesData[i + 10] = p2.x + linesData[i + 11] = R2 * Math.cos((-p2.a + a2) * Math.PI / 180.0) + linesData[i + 12] = R2 * Math.sin((-p2.a + a2) * Math.PI / 180.0) + linesData[i + 13] = p2.e + linesData[i + 14] = p2.f + linesData[i + 15] = p2.a + linesData[i + 16] = p2.s + linesData[i + 17] = p2.t + } else { + linesData[i + 0] = p1.g + linesData[i + 1] = p1.x // positions + linesData[i + 2] = p1.y + linesData[i + 3] = p1.z + linesData[i + 4] = p1.e + linesData[i + 5] = p1.f + linesData[i + 6] = p1.a + linesData[i + 7] = p1.s + linesData[i + 8] = p1.t + + linesData[i + 9] = p2.g + linesData[i + 10] = p2.x + linesData[i + 11] = p2.y + linesData[i + 12] = p2.z + linesData[i + 13] = p2.e + linesData[i + 14] = p2.f + linesData[i + 15] = p2.a + linesData[i + 16] = p2.s + linesData[i + 17] = p2.t + } + // console.log("Segment " + p1) + state.linesDataOffset += 18 // stride + + /*MM_removed + //color : not used anymore (for now ?) + let color + let intensity + if (p2.g === 0) { // g0 + color = {r: 0, g: 1, b: 0} + intensity = 1.0 - p2.s / lasermultiply + } else if (p2.g === 1) { // g1 + color = {r: 0.7, g: 0, b: 0} + intensity = 1.0 - p2.s / lasermultiply + } else if (p2.g === 7) { // g7 + color = {r: 0, g: 0, b: 1} + intensity = 1.0 - p2.s / lasermultiply + } else { + color = {r: 0, g: 1, b: 1} + intensity = 1.0 - p2.s / lasermultiply + } + + lineObject.colors[i + 0] = color.r + (1 - color.r) * intensity // Colors + lineObject.colors[i + 1] = color.g + (1 - color.g) * intensity + lineObject.colors[i + 2] = color.b + (1 - color.b) * intensity + lineObject.colors[i + 3] = color.r + (1 - color.r) * intensity + lineObject.colors[i + 4] = color.g + (1 - color.g) * intensity + lineObject.colors[i + 5] = color.b + (1 - color.b) * intensity + + lineObject.nLines++ + + if (lineObject.nLines === bufSize) { + closeLineSegment(state) + }*/ + + const dist = Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) + (p1.z - p2.z) * (p1.z - p2.z)) + state.metrics.totalDist += dist + state.metrics.totalTime += dist / p2.f // time minutes + state.metrics.totaltimemax = state.metrics.totalTime * 60 +} + +export function closeLineSegment ({debug, lineObject, lineObjects}) { + if (debug) { + console.log('closeLineSegment', lineObject.nLines) + } + if (lineObject.nLines === 0) { + return + } + + const positions = new Float32Array(6 * lineObject.nLines) + const colors = new Float32Array(6 * lineObject.nLines) + positions.set(lineObject.positions.subarray(0, lineObject.nLines * 6)) + colors.set(lineObject.colors.subarray(0, lineObject.nLines * 6)) + + const lines = {positions, colors} + lineObjects.lines.push(lines) // Feed the objects to "object" in doChunk() + lineObject.nLines = 0 +} diff --git a/src/index.js b/src/index.js index 8bc72bb..e1ea48b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,48 @@ /** * @author kaosat-dev / https://github.com/kaosat-dev - * - * Description: A gcode parser for STL ASCII files & BINARY, + * @author kaosat-dev / https://github.com/kaosat-dev + * for the initial inspiration and example code. + * Credit goes to https://github.com/joewalnes/gcode-viewer + * GCode descriptions come from: + * http://reprap.org/wiki/G-code + * http://en.wikipedia.org/wiki/G-code + * SprintRun source code + * Description: A gcode parser **/ +import makeHandlers from './gcodeHandlers' +import parseAsChunks from './parseAsChunks' +import makeBaseState from './makeBaseState' + +export default function parse (data, doneCallback) { + // const params = Object.assign({}, machineDefaults, parameters) + const handlers = makeHandlers() + let state = makeBaseState() + + // FIXME: not sure AT ALL where/when lineObject is supposed to be created + state.lineObject = { + active: false, + positions: new Float32Array(6 * state.bufSize), // Start with bufSize line segments + colors: new Float32Array(6 * state.bufSize), // Start with bufSize line segments + nLines: 0 + }, -export default function makeGcodeStream (parameters = {}) { + parseAsChunks(state, handlers, data, doneCallback) } + +/*export default function makeGcodeStream (parameters = {}) { + +OLD code structure , for reference ! +//main entry point !! +createObjectFromGCode(gcode, indexMax) + => handlers = { + gcode(G1 etc): function + => drawArc + => drawArcFrom2PtsAndCenter + => addSegment + } + =>GCodeParser(handlers) + =>parse + =>doChunk + =>parseLine + convertLineGeometryToBufferGeometry +}*/ diff --git a/src/layers.js b/src/layers.js new file mode 100644 index 0000000..c79cc46 --- /dev/null +++ b/src/layers.js @@ -0,0 +1,10 @@ +export function newLayer (line, layers3d) { + // console.log("layers3d:", layers3d, "layers3d.length", layers3d.length) + let layer = { + type: {}, + layer: layers3d.length, + z: line.z + } + layers3d.push(layer) + return layer +} diff --git a/src/makeBaseState.js b/src/makeBaseState.js new file mode 100644 index 0000000..dff91b8 --- /dev/null +++ b/src/makeBaseState.js @@ -0,0 +1,119 @@ +export default function makeBaseState (gcode, indexMax) { + let state = { + debug: false, + + tool: null, + relative: false, + unitsMm: true, + + lineObjects: { + lines: [], + nLines: 0, + name: 'LineObjects' + }, + + linesData: [], + linesDataOffset: 0, + linesDataStride: 18,// each chunk of data is 18 (9x2) long + //we prepare the storage for data + //we have 9 components : [g, x, y, z, e, f, s, t], times 2 + + extraObjects: {// these are extra Object3D elements added during + // the gcode rendering to attach to scene + 'G17': [], + 'G18': [], + 'G19': [] + }, + plane: 'G17', // set default plane to G17 - Assume G17 if no plane specified in gcode. + lines: [], + + layers: { + layers3d: [], + layer: undefined + }, + + metrics: { + totalTime: 0, + totaltimemax: 0, + totalDist: 0 + }, + + /*previous: { + lastArgs: {cmd: null}, + lastFeedrate: null + },*/ + lastArgs: {cmd: null}, + lastFeedrate: null, + lastLine: { + x: 0, + y: 0, + z: 0, + a: 0, + e: 0, + f: 0, + feedrate: null, + extruding: false + }, + + bbox: { + min: [100000, 100000, 100000], + max: [-100000, -100000, -100000] + }, + + bbox2: { + min: [100000, 100000, 100000], + max: [-100000, -100000, -100000] + }, + + bufSize: 10000, // Arbitrary - play around with! + + lasermultiply: 100, + laserxmax: undefined, + laserymax: undefined, + + // command specific settings, not sure about this: + specifics: { + G0: { + color: 0x00ff00 + }, + G1: { + color: 0x0000ff + }, + G2: { + color: 0x999900 + }, + G7: { + dir: 0, + spotSize: undefined + }, + G17: { + extraObjects: [] + }, + G18: { + extraObjects: [] + }, + G19: { + extraObjects: [] + }, + G92: { + offset: {x: 0, y: 0, z: 0, a: 0, e: 0} + } + }, + + //container object/group + container: { + name: 'newobj', + children: [] + } + } + + // we have been using an approach where we just append + // each gcode move to one monolithic geometry. we + // are moving away from that idea and instead making each + // gcode move be it's own full-fledged line object with + // its own userData info + // G2/G3 moves are their own child of lots of lines so + // that even the simulator can follow along better + + return state +} diff --git a/src/parseAsChunks.js b/src/parseAsChunks.js new file mode 100644 index 0000000..2f1ffb7 --- /dev/null +++ b/src/parseAsChunks.js @@ -0,0 +1,38 @@ +import parseLine from './parseLine' + +function now () { + return new Date().getTime() +} + +export default function parseAsChunks (state, handlers, gcode, doneCallback) { + const lines = gcode.split(/\r{0,1}\n/) + const count = lines.length + const maxTimePerChunk = 500 + let index = 0 + + function doChunk () { + const progress = (index / count) + + let startTime = now() + while (index < count && (now() - startTime) <= maxTimePerChunk) { + // console.log('parsing ' + lines[index]) + parseLine(state, handlers, lines[index], index) + ++index + } + // console.log('done parsing ') + if (index < count) { + setTimeout(doChunk, 1) // set Timeout for async iteration + // console.log('[GCODE PARSE] ' + (index / count ) * 100 + "%") + } else { + // console.log('done parsing')//, state.lineObjects.lines) + // cleanup , resize data typedArray to match actual data size + state.linesData = state.linesData.subarray(0, state.linesDataOffset) + doneCallback(state) + + //TODO: deal with this ? + //object.translateX(laserxmax / 2 * -1) + //object.translateY(laserymax / 2 * -1) + } + } + doChunk() +} diff --git a/src/parseLine.js b/src/parseLine.js new file mode 100644 index 0000000..f4cf9ba --- /dev/null +++ b/src/parseLine.js @@ -0,0 +1,152 @@ +export default function parseLine (state, handlers, text, index) { + //console.log('Parsing: ', text) + const origtext = text + + if (text.match(/^N/i)) { // remove line numbers if exist + text = text.replace(/^N\d+\s*/ig, '') // yes, there's a line num + } + const isG7 = text.match(/^G7/) // Is is G7 raster command? + const baseParse = isG7 ? parseRaster(text, origtext) : parseDefault(text, origtext) + const isComment = baseParse.isComment + text = baseParse.text + + return generateArgs(state, handlers, text, origtext, index, isComment, isG7) +} + +/*parses 'default' text , ie non raster/G7*/ +function parseDefault (text, origtext) { + let isComment = false + + text = text.replace(/G00/i, 'G0') // collapse leading zero g cmds to no leading zero + text = text.replace(/G0(\d)/i, 'G$1') // add spaces before g cmds and xyzabcijkf params + text = text.replace(/([gmtxyzabcijkfst])/ig, ' $1') // remove spaces after xyzabcijkf params because a number should be directly after them + text = text.replace(/([xyzabcijkfst])\s+/ig, '$1') // remove front and trailing space + text = text.trim() + + if (text.match(/^(;|\(|<)/)) { // see if comment + text = origtext + isComment = true + } else { + text = text.replace(/\(.*?\)/g, '') + } + + text = text.replace(/(;|\().*$/, '') // strip off end of line comment ; or () trailing + + return {text, isComment} +} + +/* parses...raster text ! (surprising isn't it?)*/ +function parseRaster (text, origtext) { + let isComment = false + text = text.trim() + if (text.match(/^(;|\(|<)/)) { // see if comment + text = origtext + isComment = true + } + return {text, isComment} +} + +function generateArgs (state, handlers, text, origtext, index, isComment, isG7) { + let {lastArgs} = state + let args = { // defaults to an empty/comment line args + cmd: 'empty or comment', + text, + origtext, + index, + isComment} + + if (text && !isComment) { + let tokens = text.split(/\s+/) + if (tokens) { + let cmd = tokens[0].toUpperCase() // check if a g or m cmd was included in gcode line + // you are allowed to just specify coords on a line + // and it should be assumed that the last specified gcode + // cmd is what's assumed + if (!cmd.match(/^(G|M|T)/i)) { + cmd = lastArgs.cmd // we need to use the last gcode cmd + tokens.unshift(cmd) // put at spot 0 in array + } else { + // we have a normal cmd as opposed to just an xyz pos where + // it assumes you should use the last cmd + // however, need to remove inline comments (TODO. it seems parser works fine for now) + } + + args = { + cmd, + text, + origtext, + index, + isComment, + feedrate: null, + plane: undefined + } + + if (tokens.length > 1) { + tokens.splice(1).forEach(function (token) { + if (token && token.length > 0) { + let key = token[0].toLowerCase() + let value + if (!isG7) { + value = parseFloat(token.substring(1)) + if (isNaN(value)) { + value = 0 + } + args[key] = value + } else { // Special treatment for G7 with D-data + if (key === '$') key = 'dollar' // '$' doesn't work so well, use 'dollar' + value = token.substring(1) // Don't convert values to float, need the D-data + args[key] = value + } + } else { + // console.log("couldn't parse token in foreach. weird:", token) + } + }) + } + + if (!args.isComment) { // don't save if saw a comment + state.lastArgs = args + } + } + } + // OTHERWISE it was a comment or the line was empty + // we still need to create a segment with xyz in p2 + // so that when we're being asked to /gotoline we have a position + // for each gcode line, even comments. we just use the last real position + // to give each gcode line (even a blank line) a spot to go to + + // REMOVE THIS ? + // return handlers['default'](args, index) + let handler = handlers[args.cmd] || handlers['default'] + if (handler) { + adaptUnitsAndFeedrateForHandler(args, state) + return handler(state, args, index) + } else { + console.error('No handler for gcode command!!!') + } + + //return args +} + +//FIXME: switch to non mutating ? +function adaptUnitsAndFeedrateForHandler (args, state) { + // do extra check here for units. units are + // specified via G20 or G21. We need to scan + // each line to see if it's inside the line because + // we were only catching it when it was the first cmd + // of the line. + if (args.text.match(/\bG20\b/i)) { + console.log('SETTING UNITS TO INCHES from pre-parser!!!') + state.unitsMm = false // false means inches cuz default is mm + } else if (args.text.match(/\bG21\b/i)) { + console.log('SETTING UNITS TO MM!!! from pre-parser') + state.unitsMm = true // true means mm + } + + if (args.text.match(/F([\d.]+)/i)) { // scan for feedrate + const feedrate = parseFloat(RegExp.$1) // we have a new feedrate + args.feedrate = feedrate + state.lastFeedrate = feedrate + } else { + args.feedrate = state.lastFeedrate // use feedrate from prior lines + } +} diff --git a/src/semi-obsolete/drawObject.js b/src/semi-obsolete/drawObject.js new file mode 100644 index 0000000..f6e1087 --- /dev/null +++ b/src/semi-obsolete/drawObject.js @@ -0,0 +1,99 @@ +export default function drawobject (state) { + var newObject = false + // console.log("INSIDE DRAWOBJECT") + // set what units we're using in the gcode + unitsMm = state.unitsMm + + newObject = { + name: 'newObject' + } + + // old approach of monolithic line segment + for (var lid in layers3d) { + // console.log('processing Layer ' + lid) + var layer = layers3d[lid] + for (var tid in layer.type) { + var type = layer.type[tid] + var bufferGeo = convertLineGeometryToBufferGeometry(type.geometry, type.color) + newObject.add(new THREE.Line(bufferGeo, type.material, THREE.LinePieces)) + } + } + newObject.add(new THREE.Object3D()) + // XY PLANE + extraObjects['G17'].forEach(function (obj) { + var bufferGeo = convertLineGeometryToBufferGeometry(obj.geometry, obj.material.color) + newObject.add(new THREE.Line(bufferGeo, obj.material)) + }, this) + // XZ PLANE + extraObjects['G18'].forEach(function (obj) { + // buffered approach + var bufferGeo = convertLineGeometryToBufferGeometry(obj.geometry, obj.material.color) + var tmp = new THREE.Line(bufferGeo, obj.material) + tmp.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2) + newObject.add(tmp) + }, this) + // YZ PLANE + extraObjects['G19'].forEach(function (obj) { + // buffered approach + var bufferGeo = convertLineGeometryToBufferGeometry(obj.geometry, obj.material.color) + var tmp = new THREE.Line(bufferGeo, obj.material) + tmp.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI / 2) + tmp.rotateOnAxis(new THREE.Vector3(0, 1, 0), Math.PI / 2) + newObject.add(tmp) + }, this) + + // use new approach of building 3d object where each + // gcode line is its own segment with its own userData + // object = new3dObj + + // Center + var scale = 1; // TODO: Auto size + + const center = [ + bbox.min.x + ((bbox.max.x - bbox.min.x) / 2), + bbox.min.y + ((bbox.max.y - bbox.min.y) / 2), + bbox.min.z + ((bbox.max.z - bbox.min.z) / 2)] + + const center2 = [ + bbox2.min.x + ((bbox2.max.x - bbox2.min.x) / 2), + bbox2.min.y + ((bbox2.max.y - bbox2.min.y) / 2), + bbox2.min.z + ((bbox2.max.z - bbox2.min.z) / 2)] + + const dX = bbox2.max.x - bbox2.min.x + const dY = bbox2.max.y - bbox2.min.y + const dZ = bbox2.max.z - bbox2.min.z + + function toTimeString (seconds) { + // return (new Date(seconds * 1000)).toUTCString().match(/(\d\d:\d\d:\d\d)/)[0] + } + + //console.log(totaltimemax + ' seconds estimated') + // printLog('Estimated Job Time: '+totaltimemax, successcolor) + // printLog('Estimated Distance: ' + (totalDist / 1000).toFixed(1) + ' m', msgcolor, 'viewer') + + const estimatedJobTime = totaltimemax + const estimatedDistance = (totalDist / 1000).toFixed(1) + const lasertimeqty = (totalDist.toFixed(1)) / 10 + + if (fileParentGroup) { + var bbox2 = new THREE.Box3().setFromObject(fileParentGroup) + // console.log('bbox width: ', (bbox2.max.x - bbox2.min.x), 'height Y: ', (bbox2.max.y - bbox2.min.y) ) + // 2' + } else if (rastermesh) { + const bbox2 = new THREE.Box3().setFromObject(rastermesh) + // console.log('bbox width: ', (bbox2.max.x - bbox2.min.x), 'height Y: ', (bbox2.max.y - bbox2.min.y) ) + + } + const width = (bbox2.max.x - bbox2.min.x) + const height = (bbox2.max.y - bbox2.min.y) + const materialqty = ((width * height) / 1000).toFixed(3) + const quoteresult = [ + `Job moves length: ${totalDist.toFixed(1)} mm`, + `Width: ${width.toFixed(1)} mm`, + `Height: ${height.toFixed(1)} mm`, + `Material: ${((width * height) / 1000).toFixed(3)} cm` + ] + + console.groupEnd() + return newObject +} diff --git a/src/semi-obsolete/lineGroup.js b/src/semi-obsolete/lineGroup.js new file mode 100644 index 0000000..ae28be8 --- /dev/null +++ b/src/semi-obsolete/lineGroup.js @@ -0,0 +1,68 @@ +export function getLineGroup (state, line, args) { + console.log('getLineGroup:', line) + if (state.layer === undefined) newLayer(line, state.layers.layers3d) + const speed = Math.round(line.e / 1000) + let opacity = line.s + let tool = parseInt(line.t, 10) + let grouptype = speed + opacity + let color = null + + if (typeof line.s === 'undefined') { + opacity = 0.3 + } else { + opacity = line.s / state.lasermultiply + } + // LaserWeb 3D Viewer Colors + if (typeof line.extruding === 'undefined' && typeof line.s === 'undefined') { + // console.log('G1 without extrude', line) + grouptype = 'g0' + opacity = 0.3 + color = 0x00ff00 + } else { + // console.log('G1 with extrude', line) + if (line.g0) { + grouptype = 'g0' + opacity = 0.3 + color = 0x00ff00 + } else if (line.g2) { + grouptype = 'g2' + color = 0x990000 + } else if (line.t === 0) { + grouptype = 't0' + color = 0x0000ff + } else if (line.t === 1) { + grouptype = 't1' + color = 0xff00ff + } else if (line.arc) { + grouptype = 'arc' + color = 0x990000 + } else { + color = 0x990000 + } + } + + // see if we have reached indexMax, if so draw, but + // make it ghosted + // if (args.index > indexMax) { + // grouptype = "ghost" + // //console.log("args.index > indexMax", args, indexMax) + // color = 0x000000) + // } + // if (line.color) color = line.color) + if (state.layer.type[grouptype] === undefined) { + state.layer.type[grouptype] = { + type: grouptype, + feed: line.e, + extruding: line.extruding, + color: color, + segmentCount: 0, + material: { + opacity: opacity, + transparent: true, + linewidth: 1 + }, + geometry: {positions: []} + } + } + return state.layer.type[grouptype] +} diff --git a/src/timeAndDistance.js b/src/timeAndDistance.js new file mode 100644 index 0000000..69848d4 --- /dev/null +++ b/src/timeAndDistance.js @@ -0,0 +1,85 @@ + +export function updateTimeAndDistance(currentState, input){ + + totaltimemax += (timeMinutes * 60) + totalTime += timeMinutes + + return { + totalTime, + totaltimemax + } +} + + +export function updateTimeAndDistance2(state, args, p1, p2){ + // DISTANCE CALC + // add distance so we can calc estimated time to run + // see if arc + let dist = 0 + let a + let b + if (p2.arc) { + // calc dist of all lines + // console.log("this is an arc to calc dist for. p2.arcObj:", p2.arcObj, "p2:", p2) + let arcGeo = p2.arcObj.geometry + // console.log("arcGeo:", arcGeo) + + let tad2 = 0 + for (let arcLineCtr = 0; arcLineCtr < arcGeo.positions.length - 1; arcLineCtr++) { + tad2 += arcGeo.positions[arcLineCtr].distanceTo(arcGeo.positions[arcLineCtr + 1]) + } + // console.log("tad2:", tad2) + + // just do straight line calc + a = [p1.x, p1.y, p1.z] + b = [p2.x, p2.y, p2.z] + // const straightDist = a.distanceTo(b) + // console.log("diff of straight line calc vs arc sum. straightDist:", straightDist) + dist = tad2 + } else { + // just do straight line calc + a = [p1.x, p1.y, p1.z] + b = [p2.x, p2.y, p2.z] + dist = a.distanceTo(b) + } + + // Handle Laser Sxxx parameter + //sv = args.s + + // time distance computation + if (dist > 0) { + state.metrics.totalDist += dist + } + + // time to execute this move + // if this move is 10mm and we are moving at 100mm/min then + // this move will take 10/100 = 0.1 minutes or 6 seconds + let timeMinutes = 0 + if (dist > 0) { + let feedrate + if (args.feedrate > 0) { + feedrate = args.feedrate + } else { + feedrate = 100 + } + timeMinutes = dist / feedrate + + // adjust for acceleration, meaning estimate + // this will run longer than estimated from the math + // above because we don't start moving at full feedrate + // obviously, we have to slowly accelerate in and out + timeMinutes = timeMinutes * 1.32 + } + + state.metrics.totalTime += timeMinutes + + p2.feedrate = args.feedrate + p2.dist = dist + p2.distSum = state.metrics.totalDist + p2.timeMins = timeMinutes + p2.timeMinsSum = state.metrics.totalTime + + // console.log('Total Time'+totalTime) + state.metrics.totaltimemax += (timeMinutes * 60) + // console.log("calculating distance. dist:", dist, "totalDist:", totalDist, "feedrate:", args.feedrate, "timeMinsToExecute:", timeMinutes, "totalTime:", totalTime, "p1:", p1, "p2:", p2, "args:", args) +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..e02354d --- /dev/null +++ b/src/utils.js @@ -0,0 +1,7 @@ +export function delta (relative, v1, v2) { + return relative ? v2 : v2 - v1 +} + +export function absolute (relative, v1, v2) { + return relative ? v1 + v2 : v2 +} diff --git a/test/gcodeParser.spec.js b/test/gcodeParser.spec.js index 267095a..6238f35 100644 --- a/test/gcodeParser.spec.js +++ b/test/gcodeParser.spec.js @@ -1,12 +1,40 @@ import test from 'ava' // import fs from 'fs' //does not work with babel + brfs const fs = require('fs') +import parse from '../src/index' -test.cb('gcode parser tests: can parse ascii gcode files', t => { - // this.timeout(5000) - fs.createReadStream('./data/test.gcode', { encoding: null, highWaterMark: 512 * 1024 }) // 'binary' - .pipe(makeStlStream()) - .pipe(concatStream(function (parsedgcode) { - t.end() - })) +test.cb('gcode parser: can parse gcode files', t => { + const gcode = fs.readFileSync('../node_modules/lw-sample-files/gcode/Rotary Axis GCode.gcode', 'utf8') + parse(gcode, function (data) { + console.log('done with parsing', data.metrics) + t.deepEqual(data.linesData.length, 5904) + // t.deepEqual(data.positions.length / 3, 864) // we divide by three because each entry is 3 long + // t.deepEqual(data.positions[0], -0.025066649541258812) + // t.deepEqual(data.positions[data.positions.length - 1], 0.019999999552965164) + t.end() + }) +/*fs.createReadStream('./data/test.gcode', { encoding: null, highWaterMark: 512 * 1024 }) // 'binary' + .pipe(makeStlStream()) + .pipe(concatStream(function (parsedgcode) { + t.end() + }))*/ +}) + + +test.cb('gcode parser: can parse multi material 3d printer gcode', t => { + const gcode = fs.readFileSync('../node_modules/lw-sample-files/gcode/3dp_dualExtrusion_UM3.gcode', 'utf8') + parse(gcode, function (data) { + console.log('done with parsing', data.metrics) + t.deepEqual(data.linesData.length, 759060) + t.end() + }) +}) + +test.cb('gcode parser: can parse multi material 3d printer gcode (variant2)', t => { + const gcode = fs.readFileSync('../node_modules/lw-sample-files/gcode/Slicer Dual Example File loubie_aria_resculpt_base_and_eyes_v1.1.gcode', 'utf8') + parse(gcode, function (data) { + console.log('done with parsing', data.metrics) + t.deepEqual(data.linesData.length, 3537558) + t.end() + }) })