diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 71e3f33..85cf8a3 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,4 +1,5 @@
-v1.0.176: Test Fix Linux Application Icons Size
+v1.0.178: New 3D Viewer backend with support for Large Gcode Files, massively faster rendering
+v1.0.176/7: Rpi Unofficial Support https://github.com/OpenBuilds/OpenBuilds-CONTROL/wiki/Run-on-a-Raspberry-Pi-(No-Compile-Method)
v1.0.175: Fixed Accidental keybindings on some more Input fields, improved ERROR and ALARM dialogs
v1.0.174: Added Inch/MM mode to Mobile Jog
v1.0.173: Added support (beta) for G2/3 to the Simulator
diff --git a/app/lib/3dview/3dview.js b/app/lib/3dview/3dview.js
index c37a688..3400bf8 100644
--- a/app/lib/3dview/3dview.js
+++ b/app/lib/3dview/3dview.js
@@ -4,64 +4,69 @@ var draw, line, timefactor = 1,
var loader = new THREE.ObjectLoader();
-var worker = new Worker('lib/3dview/workers/gcodeparser.js');
-worker.addEventListener('message', function(e) {
- // console.log('webworker message')
- if (scene.getObjectByName('gcodeobject')) {
- scene.remove(scene.getObjectByName('gcodeobject'))
- object = false;
- }
- object = loader.parse(JSON.parse(e.data));
- if (object && object.userData.lines.length > 1) {
- scene.add(object);
- if (object.userData.inch) {
- object.scale.x = 25.4
- object.scale.y = 25.4
- object.scale.z = 25.4
- }
- redrawGrid(parseInt(object.userData.bbbox2.min.x), parseInt(object.userData.bbbox2.max.x), parseInt(object.userData.bbbox2.min.y), parseInt(object.userData.bbbox2.max.y), object.userData.inch)
- // animate();
- setTimeout(function() {
- if (webgl) {
- $('#gcodeviewertab').click();
- }
- clearSceneFlag = true;
- resetView();
- // animate();
- var timeremain = object.userData.lines[object.userData.lines.length - 1].p2.timeMinsSum;
- if (!isNaN(timeremain)) {
- var mins_num = parseFloat(timeremain, 10); // don't forget the second param
- var hours = Math.floor(mins_num / 60);
- var minutes = Math.floor((mins_num - ((hours * 3600)) / 60));
- var seconds = Math.floor((mins_num * 60) - (hours * 3600) - (minutes * 60));
-
- // Appends 0 when unit is less than 10
- if (hours < 10) {
- hours = "0" + hours;
- }
- if (minutes < 10) {
- minutes = "0" + minutes;
- }
- if (seconds < 10) {
- seconds = "0" + seconds;
- }
- var formattedTime = hours + ':' + minutes + ':' + seconds;
- console.log('Remaining time: ', formattedTime)
- // output formattedTime to UI here
- $('#timeRemaining').html(" / " + formattedTime);
- printLog("[ GCODE Parser ] GCODE Preview Rendered Succesfully: Estimated GCODE Run Time: " + formattedTime + "")
- }
- }, 200);
- $('#3dviewicon').removeClass('fa-pulse')
- $('#3dviewlabel').html(' 3D View')
- }
-}, false);
function parseGcodeInWebWorker(gcode) {
simstop()
scene.remove(object)
object = false;
+
+ // var worker = new Worker('lib/3dview/workers/gcodeparser.js');
+ var worker = new Worker('lib/3dview/workers/litegcodeviewer.js');
+ worker.addEventListener('message', function(e) {
+ // console.log('webworker message')
+ if (scene.getObjectByName('gcodeobject')) {
+ scene.remove(scene.getObjectByName('gcodeobject'))
+ object = false;
+ }
+ object = loader.parse(JSON.parse(e.data));
+ if (object && object.userData.lines.length > 1) {
+ worker.terminate();
+ scene.add(object);
+ if (object.userData.inch) {
+ object.scale.x = 25.4
+ object.scale.y = 25.4
+ object.scale.z = 25.4
+ }
+ redrawGrid(parseInt(object.userData.bbbox2.min.x), parseInt(object.userData.bbbox2.max.x), parseInt(object.userData.bbbox2.min.y), parseInt(object.userData.bbbox2.max.y), object.userData.inch)
+ // animate();
+ setTimeout(function() {
+ if (webgl) {
+ $('#gcodeviewertab').click();
+ }
+ clearSceneFlag = true;
+ resetView();
+ // animate();
+ var timeremain = object.userData.lines[object.userData.lines.length - 1].p2.timeMinsSum;
+
+ if (!isNaN(timeremain)) {
+ var mins_num = parseFloat(timeremain, 10); // don't forget the second param
+ var hours = Math.floor(mins_num / 60);
+ var minutes = Math.floor((mins_num - ((hours * 3600)) / 60));
+ var seconds = Math.floor((mins_num * 60) - (hours * 3600) - (minutes * 60));
+
+ // Appends 0 when unit is less than 10
+ if (hours < 10) {
+ hours = "0" + hours;
+ }
+ if (minutes < 10) {
+ minutes = "0" + minutes;
+ }
+ if (seconds < 10) {
+ seconds = "0" + seconds;
+ }
+ var formattedTime = hours + ':' + minutes + ':' + seconds;
+ console.log('Remaining time: ', formattedTime)
+ // output formattedTime to UI here
+ $('#timeRemaining').html(" / " + formattedTime);
+ printLog("[ GCODE Parser ] GCODE Preview Rendered Succesfully: Estimated GCODE Run Time: " + formattedTime + "")
+ }
+ }, 200);
+ $('#3dviewicon').removeClass('fa-pulse')
+ $('#3dviewlabel').html(' 3D View')
+ }
+ }, false);
+
worker.postMessage({
'data': gcode
});
@@ -327,4 +332,4 @@ function simstop() {
editor.gotoLine(0)
cone.visible = false;
clearSceneFlag = true;
-}
\ No newline at end of file
+}
diff --git a/app/lib/3dview/workers/gcodeparser.js b/app/lib/3dview/workers/gcodeparser.js
index f489414..15f8696 100644
--- a/app/lib/3dview/workers/gcodeparser.js
+++ b/app/lib/3dview/workers/gcodeparser.js
@@ -1282,4 +1282,4 @@ function convertLineGeometryToBufferGeometry(lineGeometry, color) {
geometry.computeBoundingSphere();
return geometry;
-};
\ No newline at end of file
+};
diff --git a/app/lib/3dview/workers/gcodeparser.js-old b/app/lib/3dview/workers/gcodeparser.js-old
deleted file mode 100644
index f87418a..0000000
--- a/app/lib/3dview/workers/gcodeparser.js-old
+++ /dev/null
@@ -1,145 +0,0 @@
-self.addEventListener('message', function(e) {
- importScripts("/lib/threejs/three.min.js");
- var data = e.data;
- var result = parseGcode(e.data.data);
- self.postMessage(result.toJSON());
-}, false);
-
-var object;
-var lastpos = {
- x: 0,
- y: 0,
- z: 0
-};
-
-function parseGcode(input) {
- const lines = input
- .split('\n')
- // .filter(l => l.length > 0); // discard empty lines
- const commands = lines.map(parseLine);
- lastpos = {
- x: 0,
- y: 0,
- z: 0
- };
- var object = new THREE.Group();
- var cmdindex = 0;
- for (let cmd of commands) {
- var geometry = new THREE.Geometry();
- var oldVector = new THREE.Vector3(lastpos.x, lastpos.y, lastpos.z)
- var newVector = new THREE.Vector3(newx(cmd, lastpos), newy(cmd, lastpos), newz(cmd, lastpos))
- geometry.vertices.push(oldVector);
- geometry.vertices.push(newVector);
- var distance = distanceVector(oldVector, newVector);
- if (distance > 0) {
- var fr;
- if (cmd.f > 0) {
- fr = cmd.f;
- } else {
- fr = 5000;
- }
- var timeMinutes = distance / fr;
- // adjust for acceleration
- var timeSeconds = (timeMinutes * 1.32) * 60;
- }
- var line = false;
- if (cmd.g == 0) {
- var material = new THREE.LineBasicMaterial({
- color: 0x00cc00
- });
- line = new THREE.Line(geometry, material);
- line.userData.cmd = cmd
- line.userData.distance = distance
- line.userData.time = timeSeconds
- line.userData.index = cmdindex;
- object.add(line);
- } else if (cmd.g == 1) {
- var material = new THREE.LineBasicMaterial({
- color: 0xcc0000
- });
- line = new THREE.Line(geometry, material);
- line.userData.cmd = cmd
- line.userData.distance = distance
- line.userData.time = timeSeconds
- line.userData.index = cmdindex;
- object.add(line);
- } else {
- var dotGeometry = new THREE.Geometry();
- dotGeometry.vertices.push(new THREE.Vector3(lastpos.x, lastpos.y, lastpos.z));
- var dotMaterial = new THREE.PointsMaterial({
- size: 0,
- color: 0xdddddd,
- sizeAttenuation: false
- });
- var dot = new THREE.Points(dotGeometry, dotMaterial);
- dot.userData.cmd = cmd
- dot.userData.index = cmdindex;
- object.add(dot);
- }
- lastpos = {
- x: newx(cmd, lastpos),
- y: newy(cmd, lastpos),
- z: newz(cmd, lastpos),
- }
- cmdindex++
- }
- var helper = new THREE.BoxHelper(object);
- helper.update();
- var box3 = new THREE.Box3();
- box3.setFromObject(helper);
- // var size = {
- // minx: box3.min.x;
- // miny: box3.min.y;
- // maxx: box3.max.x;
- // maxy: box3.max.y;
- // minz: box3.min.z;
- // maxz: box3.max.z;
- // }
- object.userData.size = box3;
- return (object);
-}
-
-function parseLine(line, index) {
- const cmd = {};
- if (line.startsWith(';'))
- cmd.comment = line.slice(1);
- else {
- const values = line.split(' ');
- values.forEach(v => {
- cmd[v.slice(0, 1).toLowerCase()] = +v.slice(1);
- });
- };
- return cmd;
-};
-
-function newx(cmd, lastpos) {
- if (cmd.x) {
- return cmd.x
- } else {
- return lastpos.x
- };
-}
-
-function newy(cmd, lastpos) {
- if (cmd.y) {
- return cmd.y
- } else {
- return lastpos.y
- };
-}
-
-function newz(cmd, lastpos) {
- if (cmd.z) {
- return cmd.z
- } else {
- return lastpos.z
- };
-}
-
-function distanceVector(v1, v2) {
- var dx = v1.x - v2.x;
- var dy = v1.y - v2.y;
- var dz = v1.z - v2.z;
-
- return Math.sqrt(dx * dx + dy * dy + dz * dz);
-}
\ No newline at end of file
diff --git a/app/lib/3dview/workers/litegcodeviewer.js b/app/lib/3dview/workers/litegcodeviewer.js
new file mode 100644
index 0000000..095c819
--- /dev/null
+++ b/app/lib/3dview/workers/litegcodeviewer.js
@@ -0,0 +1,966 @@
+self.addEventListener('message', function(e) {
+ console.log("New message received by worker", e.data.data.length)
+ importScripts("/lib/threejs/three.min.js");
+ var data = e.data;
+ var result = openGCodeFromText(e.data.data)
+ self.postMessage(JSON.stringify(result));
+}, false);
+
+// This is a simplified and updated version of http://gcode.joewalnes.com/
+// Updated with code from http://chilipeppr.com/tinyg's 3D viewer to support more CNC type Gcode
+// Simplified by Andrew Hodel in 2015
+// Updated by PvdW in 2016 for S-Value lasers
+// Updates by PvdW in 2017 - new arc code from http://chilipeppr.com
+// Updates by PvdW in 2017 - AUTODETECT MAX S VALUE
+// Updated by PvdW in 2017 - Parse GCODE to find starting temperatures (preheat machine)
+// Updated by PvdW in 2018 - Webworker Version
+// Updated by PvdW in 2019 - Improve Performance
+
+var lastLine = {
+ x: 0,
+ y: 0,
+ z: 0,
+ e: 0,
+ f: 0,
+ feedrate: null,
+ extruding: false
+};
+
+function openGCodeFromText(gcode) {
+ // Reset start points
+
+ var parsedData = createObjectFromGCode(gcode);
+ console.log(parsedData)
+
+ var geometry = new THREE.BufferGeometry();
+ var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
+ var positions = [];
+ var colors = [];
+
+ for (i=0; i< parsedData.lines.length; i++) {
+ if (!parsedData.lines[i].args.isFake) {
+ var x = parsedData.lines[i].p2.x;
+ var y = parsedData.lines[i].p2.y;
+ var z = parsedData.lines[i].p2.z;
+ positions.push( x, y, z );
+
+ if (parsedData.lines[i].p2.g0) {
+ colors.push( 0 );
+ colors.push( 200 );
+ colors.push( 0 );
+ }
+
+ if (parsedData.lines[i].p2.g1) {
+ colors.push( 200 );
+ colors.push( 0 );
+ colors.push( 0 );
+ }
+
+ if (parsedData.lines[i].p2.g2) {
+ colors.push( 0 );
+ colors.push( 0 );
+ colors.push( 200 );
+ }
+
+ }
+ }
+
+ geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+ geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
+
+ geometry.computeBoundingSphere();
+
+ var line = new THREE.Line( geometry, material );
+ line.geometry.computeBoundingBox();
+ var box = line.geometry.boundingBox.clone();
+ line.userData.lines = parsedData.lines
+ line.userData.bbbox2 = box
+ line.name = 'gcodeobject'
+ return line;
+}
+
+GCodeParser = function(handlers, modecmdhandlers) {
+ this.handlers = handlers || {};
+ this.modecmdhandlers = modecmdhandlers || {};
+
+ this.lastArgs = {
+ cmd: null
+ };
+ this.lastFeedrate = null;
+ this.isUnitsMm = true;
+
+ this.parseLine = function(text, info) {
+ // console.log("LINE: " + text)
+ var origtext = text;
+ // remove line numbers if exist
+ if (text.match(/^N/i)) {
+ // yes, there's a line num
+ text = text.replace(/^N\d+\s*/ig, "");
+ }
+
+ // collapse leading zero g cmds to no leading zero
+ text = text.replace(/G00/i, 'G0');
+ 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();
+
+ // see if comment
+ var isComment = false;
+ if (text.match(/^(;|\(|<)/)) {
+ text = origtext;
+ isComment = true;
+ } else {
+ // make sure to remove inline comments
+ text = text.replace(/\(.*?\)/g, "");
+ }
+ //console.log("gcode txt:", text);
+
+ if (text && !isComment) {
+ text = text.replace(/(;|\().*$/, ""); // ; or () trailing // strip off end of line comment
+ var tokens = [];
+ // Execute any non-motion commands on the line immediately
+ // Add other commands to the tokens list for later handling
+ // Segments are not created for non-motion commands;
+ // the segment for this line is created later
+ text.split(/\s+/).forEach(function(token) {
+ var modehandler = modecmdhandlers[token.toUpperCase()];
+ if (modehandler) {
+ modehandler();
+ } else {
+ tokens.push(token);
+ }
+ });
+
+ if (tokens.length) {
+ var cmd = tokens[0];
+ cmd = cmd.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
+ isComment = false;
+ if (!cmd.match(/^(G|M|T|S)/i)) {
+ cmd = this.lastArgs.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)
+ }
+ var args = {
+ 'cmd': cmd,
+ 'text': text,
+ 'origtext': origtext,
+ 'indx': info,
+ 'isComment': isComment,
+ 'feedrate': null
+ };
+ //console.log("args:", args);
+ if (tokens.length > 1 && !isComment) {
+ tokens.splice(1).forEach(function(token) {
+ //console.log("token:", token);
+ if (token && token.length > 0) {
+ var key = token[0].toLowerCase();
+ var value = parseFloat(token.substring(1));
+ args[key] = value;
+ } else {
+ //console.log("couldn't parse token in foreach. weird:", token);
+ }
+ });
+ }
+ var handler = this.handlers[cmd] || this.handlers['default'];
+ // don't save if saw a comment
+ if (!args.isComment) {
+ this.lastArgs = args;
+ //console.log("just saved lastArgs for next use:", this.lastArgs);
+ } else {
+ //console.log("this was a comment, so didn't save lastArgs");
+ }
+ //console.log("calling handler: cmd:", cmd, "args:", args, "info:", info);
+ if (handler) {
+ // scan for feedrate
+ if (args.text.match(/F([\d.]+)/i)) {
+ // we have a new feedrate
+ var feedrate = parseFloat(RegExp.$1);
+ args.feedrate = feedrate;
+ this.lastFeedrate = feedrate;
+ } else {
+ // use feedrate from prior lines
+ args.feedrate = this.lastFeedrate;
+ }
+
+ if (args.text.match(/S([\d.]+)/i)) {
+ // we have a new S-Value
+ var svalue = parseFloat(RegExp.$1);
+ args.svalue = svalue;
+ this.lastsvalue = svalue;
+ } else {
+ // use feedrate from prior lines
+ args.svalue = this.lastsvalue;
+ }
+ //console.log("about to call handler. args:", args, "info:", info, "this:", this);
+ return handler(args, info, this);
+ } else {
+ console.error("No handler for gcode command!!!");
+ }
+
+ }
+ } else {
+ // 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
+ var args = {
+ 'cmd': 'empty or comment',
+ 'text': text,
+ 'origtext': origtext,
+ 'indx': info,
+ 'isComment': isComment
+ };
+ var handler = this.handlers['default'];
+ return handler(args, info, this);
+ }
+ }
+
+ this.parse = function(gcode) {
+ // console.log(gcode)
+
+
+
+ var lines = gcode.split(/\r{0,1}\n/);
+ // var lines = gcode
+ for (var i = 0; i < lines.length; i++) {
+ if (this.parseLine(lines[i], i) === false) {
+ break;
+ }
+ }
+ }
+ },
+ colorG0 = 0x00cc00, //bootstrap color
+ colorG1 = 0xcc0000,
+ colorG2 = 0x0000cc,
+ createObjectFromGCode = function(gcode) {
+ // console.log(gcode)
+
+ // Reset Starting Point
+ lastLine = {
+ x: 0,
+ y: 0,
+ z: 0,
+ e: 0,
+ f: 0,
+ feedrate: null,
+ extruding: false
+ };
+
+
+ setUnits = function(units) {
+ if (units == "mm")
+ this.isUnitsMm = true;
+ else
+ this.isUnitsMm = false;
+ this.onUnitsChanged();
+ }
+
+ onUnitsChanged = function() {
+ //console.log("onUnitsChanged");
+ // we need to publish back the units
+ var units = "mm";
+ if (!this.isUnitsMm) units = "inch";
+ // $('.com-chilipeppr-widget-3dviewer-units-indicator').text(units);
+ console.log("USING UNITS:" + units)
+
+ }
+ // these are extra Object3D elements added during
+ // the gcode rendering to attach to scene
+ this.extraObjects = [];
+ this.extraObjects["G17"] = [];
+ this.extraObjects["G18"] = [];
+ this.extraObjects["G19"] = [];
+ this.offsetG92 = {
+ x: 0,
+ y: 0,
+ z: 0,
+ e: 0
+ };
+ this.setUnits("mm");
+
+
+ // 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
+ var layers = [];
+ var layer = undefined;
+ var lines = [];
+ var totalDist = 0;
+
+
+ this.newLayer = function(line) {
+ //console.log("layers:", layers, "layers.length", layers.length);
+ layer = {
+ type: {},
+ layer: layers.length,
+ z: line.z,
+ };
+ layers.push(layer);
+ };
+
+ this.drawArc = function(aX, aY, aZ, endaZ, aRadius, aStartAngle, aEndAngle, aClockwise, plane) {
+ //console.log("drawArc:", aX, aY, aZ, aRadius, aStartAngle, aEndAngle, aClockwise);
+ var ac = new THREE.ArcCurve(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise);
+ //console.log("ac:", ac);
+ var acmat = new THREE.LineBasicMaterial({
+ color: colorG2,
+ opacity: 0.5,
+ transparent: true
+ });
+ var acgeo = new THREE.Geometry();
+ var ctr = 0;
+ var z = aZ;
+ var points = []
+ ac.getPoints(20).forEach(function(v) {
+ //console.log(v);
+ z = (((endaZ - aZ) / 20) * ctr) + aZ;
+ acgeo.vertices.push(new THREE.Vector3(v.x, v.y, z));
+ ctr++;
+ points.push({
+ 'x': v.x,
+ 'y': v.y,
+ 'z': z,
+ })
+ });
+ var aco = new THREE.Line(acgeo, acmat);
+ aco.userData.points = points;
+ //aco.position.set(pArc.x, pArc.y, pArc.z);
+ //console.log("aco:", aco);
+ this.extraObjects[plane].push(aco);
+ return aco;
+ };
+
+ this.drawArcFrom2PtsAndCenter = function(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
+ var p1deltaX = vpArc.x - vp1.x;
+ var p1deltaY = vpArc.y - vp1.y;
+ var p1deltaZ = vpArc.z - vp1.z;
+
+ var p2deltaX = vpArc.x - vp2.x;
+ var p2deltaY = vpArc.y - vp2.y;
+ var p2deltaZ = vpArc.z - vp2.z;
+
+ switch (args.plane) {
+ case "G18":
+ var anglepArcp1 = Math.atan(p1deltaZ / p1deltaX);
+ var anglepArcp2 = Math.atan(p2deltaZ / p2deltaX);
+ break;
+ case "G19":
+ var anglepArcp1 = Math.atan(p1deltaZ / p1deltaY);
+ var anglepArcp2 = Math.atan(p2deltaZ / p2deltaY);
+ break;
+ default:
+ var anglepArcp1 = Math.atan(p1deltaY / p1deltaX);
+ var anglepArcp2 = Math.atan(p2deltaY / p2deltaX);
+ }
+
+ // Draw arc from arc center
+ var radius = vpArc.distanceTo(vp1);
+ var 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 = true;
+ if (args.clockwise === false) clwise = false;
+ //if (anglepArcp1 < 0) clockwise = 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)
+ // Draw full circle if angles are both zero,
+ // start & end points are same point... I think
+ switch (args.plane) {
+ case "G18":
+ var threeObj = this.drawArc(vpArc.x, vpArc.z, (-1 * vp1.y), (-1 * vp2.y), radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, "G18");
+ break;
+ case "G19":
+ var threeObj = this.drawArc(vpArc.y, vpArc.z, vp1.x, vp2.x, radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, "G19");
+ break;
+ default:
+ var threeObj = this.drawArc(vpArc.x, vpArc.y, vp1.z, vp2.z, radius, anglepArcp1, (anglepArcp2 + (2 * Math.PI)), clwise, "G17");
+ }
+ else
+ switch (args.plane) {
+ case "G18":
+ var threeObj = this.drawArc(vpArc.x, vpArc.z, (-1 * vp1.y), (-1 * vp2.y), radius, anglepArcp1, anglepArcp2, clwise, "G18");
+ break;
+ case "G19":
+ var threeObj = this.drawArc(vpArc.y, vpArc.z, vp1.x, vp2.x, radius, anglepArcp1, anglepArcp2, clwise, "G19");
+ break;
+ default:
+ var threeObj = this.drawArc(vpArc.x, vpArc.y, vp1.z, vp2.z, radius, anglepArcp1, anglepArcp2, clwise, "G17");
+ }
+ return threeObj;
+ };
+
+ this.addSegment = function(p1, p2, args) {
+
+ if (p2.arc) {
+ //console.log("");
+ //console.log("drawing arc. p1:", p1, ", p2:", p2);
+
+ //var 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"){
+ // var vp2 = new THREE.Vector3(p1.x, p1.y, p1.z);
+ // var vp1 = new THREE.Vector3(p2.x, p2.y, p2.z);
+ //}
+ //else {
+ var vp1 = new THREE.Vector3(p1.x, p1.y, p1.z);
+ var vp2 = new THREE.Vector3(p2.x, p2.y, p2.z);
+ //}
+ var 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);
+
+ 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).
+ var 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.
+ var x3 = (p1.x + p2.x) / 2;
+ var y3 = (p1.y + p2.y) / 2;
+ var 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
+ var pArc_1 = undefined;
+ var pArc_2 = undefined;
+ var calc = Math.sqrt((radius * radius) - Math.pow(q / 2, 2));
+
+ // calc can be NaN if q/2 is epsilon larger than radius due to finite precision
+ // When that happens, the calculated center is incorrect
+ if (isNaN(calc)) {
+ calc = 0.0;
+ }
+ var angle_point = undefined;
+
+ 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))) {
+ var cw = pArc_1;
+ var ccw = pArc_2;
+ } else {
+ var cw = pArc_2;
+ var 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))) {
+ var cw = pArc_1;
+ var ccw = pArc_2;
+ } else {
+ var cw = pArc_2;
+ var 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))) {
+ var cw = pArc_1;
+ var ccw = pArc_2;
+ } else {
+ var cw = pArc_2;
+ var ccw = pArc_1;
+ }
+ }
+
+ if ((p2.clockwise === true && radius >= 0) || (p2.clockwise === false && radius < 0)) vpArc = new THREE.Vector3(cw.x, cw.y, cw.z);
+ else vpArc = new THREE.Vector3(ccw.x, ccw.y, ccw.z);
+
+ } else {
+ // this code deals with IJK gcode commands
+ /*if(args.clockwise === false || args.cmd === "G3")
+ var 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*/
+ var pArc = {
+ x: p2.arci,
+ y: p2.arcj,
+ z: p2.arck,
+ };
+ //console.log("new pArc:", pArc);
+ vpArc = new THREE.Vector3(pArc.x, pArc.y, pArc.z);
+ //console.log("vpArc:", vpArc);
+ }
+
+ var threeObjArc = this.drawArcFrom2PtsAndCenter(vp1, vp2, vpArc, args);
+
+ // still push the normal p1/p2 point for debug
+ p2.g2 = true;
+ p2.threeObjArc = threeObjArc;
+ // 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.vertices.push(
+ new THREE.Vector3(p1.x, p1.y, p1.z));
+ geometry.vertices.push(
+ new THREE.Vector3(p2.x, p2.y, p2.z));
+ geometry.colors.push(group.color);
+ geometry.colors.push(group.color);
+ */
+ // end of if p2.arc
+ // console.log( p2.threeObjArc.userData.points)
+ lines.push({
+ p2: p2,
+ 'args': args
+ });
+
+ } else { // not an arc
+ lines.push({
+ p2: p2,
+ 'args': args
+ });
+ }
+
+
+ // DISTANCE CALC
+ // add distance so we can calc estimated time to run
+ // see if arc
+ var dist = 0;
+ if (p2.arc) {
+ // calc dist of all lines
+ //console.log("this is an arc to calc dist for. p2.threeObjArc:", p2.threeObjArc, "p2:", p2);
+ var arcGeo = p2.threeObjArc.geometry;
+ //console.log("arcGeo:", arcGeo);
+
+ var tad2 = 0;
+ for (var arcLineCtr = 0; arcLineCtr < arcGeo.vertices.length - 1; arcLineCtr++) {
+ tad2 += arcGeo.vertices[arcLineCtr].distanceTo(arcGeo.vertices[arcLineCtr + 1]);
+ }
+ //console.log("tad2:", tad2);
+
+
+ // just do straight line calc
+ var a = new THREE.Vector3(p1.x, p1.y, p1.z);
+ var b = new THREE.Vector3(p2.x, p2.y, p2.z);
+ var straightDist = a.distanceTo(b);
+
+ //console.log("diff of straight line calc vs arc sum. straightDist:", straightDist);
+
+ dist = tad2;
+
+ } else {
+ // just do straight line calc
+ var a = new THREE.Vector3(p1.x, p1.y, p1.z);
+ var b = new THREE.Vector3(p2.x, p2.y, p2.z);
+ dist = a.distanceTo(b);
+ }
+
+ if (dist > 0) {
+ this.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
+ var timeMinutes = 0;
+ if (dist > 0) {
+ var fr;
+ if (args.feedrate > 0) {
+ fr = args.feedrate
+ } else {
+ fr = 100;
+ }
+ timeMinutes = dist / fr;
+
+ // 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;
+ }
+ this.totalTime += timeMinutes;
+
+ p2.feedrate = args.feedrate;
+ p2.dist = dist;
+ p2.distSum = this.totalDist;
+ p2.timeMins = timeMinutes;
+ p2.timeMinsSum = this.totalTime;
+
+ // console.log("calculating distance. dist:", dist, "totalDist:", this.totalDist, "feedrate:", args.feedrate, "timeMinsToExecute:", timeMinutes, "totalTime:", this.totalTime, "p1:", p1, "p2:", p2, "args:", args);
+
+ }
+ this.totalDist = 0;
+ this.totalTime = 0;
+
+ var relative = false;
+
+ this.delta = function(v1, v2) {
+ return relative ? v2 : v2 - v1;
+ }
+
+ this.absolute = function(v1, v2) {
+ return relative ? v1 + v2 : v2;
+ }
+
+ var ijkrelative = true; // For Mach3 Arc IJK Absolute mode
+ this.ijkabsolute = function(v1, v2) {
+ return ijkrelative ? v1 + v2 : v2;
+ }
+
+ this.addFakeSegment = function(args) {
+ //line.args = args;
+ var arg2 = {
+ isFake: true,
+ text: args.text,
+ indx: args.indx
+ };
+ if (arg2.text.match(/^(;|\(|<)/)) arg2.isComment = true;
+ lines.push({
+ p2: lastLine, // since this is fake, just use lastLine as xyz
+ 'args': arg2
+ });
+ }
+
+ var cofg = this;
+ var parser = new this.GCodeParser({
+ //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(args, indx) {
+ //G1.apply(this, args, line, 0x00ff00);
+ //console.log("G0", args);
+ var newLine = {
+ x: args.x !== undefined ? cofg.absolute(lastLine.x, args.x) + cofg.offsetG92.x : lastLine.x,
+ y: args.y !== undefined ? cofg.absolute(lastLine.y, args.y) + cofg.offsetG92.y : lastLine.y,
+ z: args.z !== undefined ? cofg.absolute(lastLine.z, args.z) + cofg.offsetG92.z : lastLine.z,
+ e: args.e !== undefined ? cofg.absolute(lastLine.e, args.e) + cofg.offsetG92.e : lastLine.e,
+ f: args.f !== undefined ? cofg.absolute(lastLine.f, args.f) : lastLine.f,
+ s: args.s !== undefined ? cofg.absolute(lastLine.s, args.s) : lastLine.s,
+ };
+ newLine.g0 = true;
+ //cofg.newLayer(newLine);
+ cofg.addSegment(lastLine, newLine, args);
+ //console.log("G0", lastLine, newLine, args, cofg.offsetG92);
+ lastLine = newLine;
+ },
+ G1: function(args, indx) {
+ // 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.
+ var newLine = {
+ x: args.x !== undefined ? cofg.absolute(lastLine.x, args.x) + cofg.offsetG92.x : lastLine.x,
+ y: args.y !== undefined ? cofg.absolute(lastLine.y, args.y) + cofg.offsetG92.y : lastLine.y,
+ z: args.z !== undefined ? cofg.absolute(lastLine.z, args.z) + cofg.offsetG92.z : lastLine.z,
+ e: args.e !== undefined ? cofg.absolute(lastLine.e, args.e) + cofg.offsetG92.e : lastLine.e,
+ f: args.f !== undefined ? cofg.absolute(lastLine.f, args.f) : lastLine.f,
+ s: args.s !== undefined ? cofg.absolute(lastLine.s, args.s) : lastLine.s,
+ };
+ /* layer change detection is or made by watching Z, it's made by
+ watching when we extrude at a new Z position */
+ if (cofg.delta(lastLine.e, newLine.e) > 0) {
+ newLine.extruding = cofg.delta(lastLine.e, newLine.e) > 0;
+ if (layer == undefined || newLine.z != layer.z) cofg.newLayer(newLine);
+ }
+ newLine.g1 = true;
+ cofg.addSegment(lastLine, newLine, args);
+ //console.log("G1", lastLine, newLine, args, cofg.offsetG92);
+ lastLine = newLine;
+ },
+ G2: function(args, indx, gcp) {
+ /* 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 . */
+ var newLine = {
+ x: args.x !== undefined ? cofg.absolute(lastLine.x, args.x) + cofg.offsetG92.x : lastLine.x,
+ y: args.y !== undefined ? cofg.absolute(lastLine.y, args.y) + cofg.offsetG92.y : lastLine.y,
+ z: args.z !== undefined ? cofg.absolute(lastLine.z, args.z) + cofg.offsetG92.z : lastLine.z,
+ e: args.e !== undefined ? cofg.absolute(lastLine.e, args.e) + cofg.offsetG92.e : lastLine.e,
+ f: args.f !== undefined ? cofg.absolute(lastLine.f, args.f) : lastLine.f,
+ arci: args.i !== undefined ? cofg.ijkabsolute(lastLine.x, args.i) : lastLine.x,
+ arcj: args.j !== undefined ? cofg.ijkabsolute(lastLine.y, args.j) : lastLine.y,
+ arck: args.k !== undefined ? cofg.ijkabsolute(lastLine.z, args.k) : lastLine.z,
+ arcr: args.r ? args.r : null,
+ };
+ //console.log("G2 newLine:", newLine);
+ //newLine.g2 = true;
+ newLine.arc = true;
+ newLine.clockwise = true;
+ if (args.clockwise === false) newLine.clockwise = args.clockwise;
+ cofg.addSegment(lastLine, newLine, args);
+ //console.log("G2", lastLine, newLine, args, cofg.offsetG92);
+ lastLine = newLine;
+ //console.log("G2. args:", args);
+ },
+ G3: function(args, indx, gcp) {
+ /* this is an arc move from lastLine's xy to the new xy. same
+ as G2 but reverse*/
+ args.arc = true;
+ args.clockwise = false;
+ gcp.handlers.G2(args, indx, gcp);
+ },
+
+ G73: function(args, indx, gcp) {
+ // peck drilling. just treat as g1
+ newLine.g73 = true;
+ console.log("G73 gcp:", gcp);
+ gcp.handlers.G1(args);
+ },
+
+ G92: function(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
+ var newLine = lastLine;
+
+ cofg.offsetG92.x = (args.x !== undefined ? (args.x === 0 ? newLine.x : newLine.x - args.x) : 0);
+ cofg.offsetG92.y = (args.y !== undefined ? (args.y === 0 ? newLine.y : newLine.y - args.y) : 0);
+ cofg.offsetG92.z = (args.z !== undefined ? (args.z === 0 ? newLine.z : newLine.z - args.z) : 0);
+ cofg.offsetG92.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, args, cofg.offsetG92);
+
+ //lastLine = newLine;
+ cofg.addFakeSegment(args);
+ },
+ M30: function(args) {
+ cofg.addFakeSegment(args);
+ },
+
+ 'default': function(args, info) {
+ //if (!args.isComment)
+ //console.log('Unknown command:', args.cmd, args, info);
+ cofg.addFakeSegment(args);
+ },
+ },
+ // Mode-setting non-motion commands, of which many may appear on one line
+ // These take no arguments
+ {
+ G17: function() {
+ console.log("SETTING XY PLANE");
+ },
+
+ G18: function() {
+ console.log("SETTING XZ PLANE");
+ },
+
+ G19: function() {
+ console.log("SETTING YZ PLANE");
+ },
+
+ G20: function() {
+ // 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
+ cofg.setUnits("inch");
+ },
+
+ G21: function() {
+ // G21: Set Units to Millimeters
+ // Example: G21
+ // Units from now on are in millimeters. (This is the RepRap default.)
+ cofg.setUnits("mm");
+ },
+
+ // A bunch of no-op modes that do not affect the viewer
+ G40: function() {}, // Tool radius compensation off
+ G41: function() {}, // Tool radius compensation left
+ G42: function() {}, // Tool radius compensation right
+ G45: function() {}, // Axis offset single increase
+ G46: function() {}, // Axis offset single decrease
+ G47: function() {}, // Axis offset double increase
+ G48: function() {}, // Axis offset double decrease
+ G49: function() {}, // Tool length offset compensation cancle
+ G54: function() {}, // Select work coordinate system 1
+ G55: function() {}, // Select work coordinate system 2
+ G56: function() {}, // Select work coordinate system 3
+ G57: function() {}, // Select work coordinate system 4
+ G58: function() {}, // Select work coordinate system 5
+ G59: function() {}, // Select work coordinate system 6
+ G61: function() {}, // Exact stop check mode
+ G64: function() {}, // Cancel G61
+ G69: function() {}, // Cancel G68
+
+ G90: function() {
+ // 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.)
+ relative = false;
+ },
+
+ 'G90.1': function() {
+ // G90.1: Set to Arc Absolute IJK Positioning
+ // Example: G90.1
+ // From now on, arc centers are specified directly by
+ // the IJK parameters, e.g. center_x = I_value
+ // This is Mach3-specific
+ ijkrelative = false;
+ },
+
+ G91: function() {
+ // G91: Set to Relative Positioning
+ // Example: G91
+ // All coordinates from now on are relative to the last position.
+ relative = true;
+ },
+
+ 'G91.1': function() {
+ // G91.1: Set to Arc Relative IJK Positioning
+ // Example: G91.1
+ // From now on, arc centers are relative to the starting
+ // coordinate, e.g. center_x = this_x + I_value
+ // This is the default, and the only possibility for most
+ // controllers other than Mach3
+ ijkrelative = true;
+ },
+
+ // No-op modal macros that do not affect the viewer
+ M07: function() {}, // Coolant on (mist)
+ M08: function() {}, // Coolant on (flood)
+ M09: function() {}, // Coolant off
+ M10: function() {}, // Pallet clamp on
+ M11: function() {}, // Pallet clamp off
+ M21: function() {}, // Mirror X axis
+ M22: function() {}, // Mirror Y axis
+ M23: function() {}, // Mirror off
+ M24: function() {}, // Thread pullout gradual off
+ M41: function() {}, // Select gear 1
+ M42: function() {}, // Select gear 2
+ M43: function() {}, // Select gear 3
+ M44: function() {}, // Select gear 4
+ M48: function() {}, // Allow feedrate override
+ M49: function() {}, // Disallow feedrate override
+ M52: function() {}, // Empty spindle
+ M60: function() {}, // Automatic pallet change
+
+ M82: function() {
+ // M82: Set E codes absolute (default)
+ // Descriped in Sprintrun source code.
+
+ // No-op, so long as M83 is not supported.
+ },
+
+ M84: function() {
+ // 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
+ },
+ });
+ // console.log("GCODE LENGTH " + gcode.length)
+
+ parser.parse(gcode);
+ var data = {
+ lines: lines,
+ inch: false,
+ totalDist: this.totalDist,
+ totalTime: this.totalTime,
+ }
+
+ if (!isUnitsMm) {
+ data.inch = true;
+ } else {
+ data.inch = false;
+ }
+
+ return data;
+ } // end of createObjectFromGCode()
diff --git a/package.json b/package.json
index 0b4f99d..e2615fc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "OpenBuildsCONTROL",
- "version": "1.0.177",
+ "version": "1.0.178",
"license": "AGPL-3.0",
"description": "Machine Interface Driver for OpenBuilds",
"author": "github.com/openbuilds ",