Hey all, for what it's worth, I got intrigued by this problem to get ChiliPeppr to do on-the-fly Gcode conversion as you send your job to a 4 pulley system. I did a Google Hangout with Jordan to talk through it. I did a bit more work after the Hangout to figure out the final calculations of a 4 pulley system to calculate the length of wire needed for a 4 pulley system to achieve an XYZ coordinate.
Here's the code that calculates the length with a sample image of what I'm calculating:

// To achieve a floating/hanging object at a sample position of 6,2,3
var x = 6;
var y = 2;
var z = 3;
// what are the positions of each corner for the pulleys so we can calc
var fl_pos = {x:0,y:0,z:10};
var bl_pos = {x:0,y:10,z:10};
var br_pos = {x:10,y:10,z:10};
var fr_pos = {x:10,y:0,z:10};
// convert XYZ cartesion coordinates to XYZA (fl,bl,br,fr) pulley lengths
// fl=front left, bl=back left, br=back right, fr=front right
// Find hypotenuse length for fl,bl,br,fr in xy plane
var fl_xy_hyp = Math.sqrt((x * x) + (y * y));
var bl_xy_hyp = Math.sqrt((x * x) + ((bl_pos.y - y) * (bl_pos.y - y)));
var br_xy_hyp = Math.sqrt(((br_pos.x - x) * (br_pos.x - x)) + ((br_pos.y - y) * (br_pos.y - y)));
var fr_xy_hyp = Math.sqrt(((fr_pos.x - x) * (fr_pos.x - x)) + (Math.abs(fr_pos.y - y) * Math.abs(fr_pos.y - y)));
// Find hypotenuse length for z plane
var fl_hyp = Math.sqrt((fl_xy_hyp * fl_xy_hyp) + ((fl_pos.z - z) * (fl_pos.z - z)));
var bl_hyp = Math.sqrt((bl_xy_hyp * bl_xy_hyp) + ((bl_pos.z - z) * (bl_pos.z - z)));
var br_hyp = Math.sqrt((br_xy_hyp * br_xy_hyp) + ((br_pos.z - z) * (br_pos.z - z)));
var fr_hyp = Math.sqrt((fr_xy_hyp * fr_xy_hyp) + ((fr_pos.z - z) * (fr_pos.z - z)));
console.log("Pulley len FL:", fl_hyp, "len BL:", bl_hyp, "len BR:", br_hyp, "len FR:", fr_hyp);
Now, if that code is good, you can paste in this entire macro and run it. It will attach to the Gcode send event. Rewrite the gcode to XYZA and you should get your steppers unreeling the correct amount of wire. I mapped X to the front-left pulley, Y to back-left pulley, Z to back-right pulley, and A to front-right pulley. The A would still have to get configured to counter-act the fact that it's set for angles rather than linear coordinates. I still don't have that one figured out, but Alden gave some pointers.
// rewrite g-code to 4axis pulley system
var myMacro = {
init: function() {
// Uninit previous runs to unsubscribe correctly, i.e.
// so we don't subscribe 100's of times each time we modify
// and run this macro
if (window["myMacro"]) {
macro.status("This macro was run before. Cleaning up...");
window["myMacro"].uninit();
}
macro.status("Starting watch on pulley rewrite macro");
// subscribe to JSON send at a higher priority than the default
// so we can intercept these pubsub's before they go to the TinyG
// cuz we're going to rewrite them
chilipeppr.subscribe('/com-chilipeppr-widget-serialport/jsonSend', this, this.onJsonSend, 5);
// store macro in window object so we have it next time thru
window["myMacro"] = this;
// get 3d coordinates by asking the 3d viewer to give them to us
// we'll get this.obj3d set for us so we can use it later
this.getXyzCoords();
},
uninit: function() {
macro.status("Uninitting macro.");
chilipeppr.unsubscribe("/com-chilipeppr-widget-serialport/jsonSend", this.onJsonSend);
},
onJsonSend: function(data) {
console.group('pulley rewrite');
console.log(data);
// if the data is an array, then return immediately because we only want to do a rewrite
// when jsonSend is individual lines, not when its a multiline array object.
// this might seem odd why, but chilipeppr sends lines individually at first and does
// it's own ganging up if user picked "multi-line" mode, so let's not rewrite stuff
// over and over. just know that if you look for a single line you're safe.
if (Array.isArray(data)) {
console.log("data was an array, so returning immediately cuz don't want to modify arrays of jsonData");
console.groupEnd();
return;
}
// extract the line from the ID which is of the form "g45" for line 45
var line = parseInt(data.Id.replace(/g/, ""));
// get xyz coords and cleaned up meta data for gcode from the 3d viewer parsed data
var coord = this.getXyzCoordsForLine(line);
console.log("xyz coords for line:", line, coord);
// check if we have a cmd and if it is a G0 or G1, otherwise return
if ('cmd' in coord.meta.args && coord.meta.args.cmd.match(/g1|g0/i)) {
console.log("this is a command we want to process. good.");
} else {
console.log("This gcode line did not contain a G0 or G1 command, so not rewriting");
console.groupEnd();
return;
}
var cmd = coord.meta.args.cmd;
// get simple vars for xyz
var x = coord.end.x;
var y = coord.end.y;
var z = coord.end.z;
//var x = 6;
//var y = 2;
//var z = 3;
// what are the positions of each corner for the pulleys so we can calc
var fl_pos = {x:0,y:0,z:10};
var bl_pos = {x:0,y:10,z:10};
var br_pos = {x:10,y:10,z:10};
var fr_pos = {x:10,y:0,z:10};
// convert XYZ cartesion coordinates to XYZA (fl,bl,br,fr) pulley lengths
// fl=front left, bl=back left, br=back right, fr=front right
// Find hypotenuse length for fl,bl,br,fr in xy plane
var fl_xy_hyp = Math.sqrt((x * x) + (y * y));
var bl_xy_hyp = Math.sqrt((x * x) + ((bl_pos.y - y) * (bl_pos.y - y)));
var br_xy_hyp = Math.sqrt(((br_pos.x - x) * (br_pos.x - x)) + ((br_pos.y - y) * (br_pos.y - y)));
var fr_xy_hyp = Math.sqrt(((fr_pos.x - x) * (fr_pos.x - x)) + (Math.abs(fr_pos.y - y) * Math.abs(fr_pos.y - y)));
// Find hypotenuse length for z plane
var fl_hyp = Math.sqrt((fl_xy_hyp * fl_xy_hyp) + ((fl_pos.z - z) * (fl_pos.z - z)));
var bl_hyp = Math.sqrt((bl_xy_hyp * bl_xy_hyp) + ((bl_pos.z - z) * (bl_pos.z - z)));
var br_hyp = Math.sqrt((br_xy_hyp * br_xy_hyp) + ((br_pos.z - z) * (br_pos.z - z)));
var fr_hyp = Math.sqrt((fr_xy_hyp * fr_xy_hyp) + ((fr_pos.z - z) * (fr_pos.z - z)));
console.log("Pulley len FL:", fl_hyp, "len BL:", bl_hyp, "len BR:", br_hyp, "len FR:", fr_hyp);
var gcode = cmd + " X" + fl_hyp + " Y" + bl_hyp + " Z" + br_hyp + " A" + fr_hyp;
console.log("Final gcode:", gcode);
// see if there was a feedrate
if ('f' in coord.meta.args) {
console.log("there was a feedrate:", coord.meta.args.f);
gcode += " F" + coord.meta.args.f;
}
gcode += "\n";
//republish the gcode command that was intercepted
var obj = {D: gcode, Id: data.Id};
// unsubscribe so that we don't get our own rewritten gcode coming back to us
chilipeppr.unsubscribe('/com-chilipeppr-widget-serialport/jsonSend', this.onJsonSend);
// send the gcode off as if nothing really changed, even tho we completely rewrote it
chilipeppr.publish('/com-chilipeppr-widget-serialport/jsonSend', obj);
// resubscribe immediately so we can rewrite the next line of gcode
chilipeppr.subscribe('/com-chilipeppr-widget-serialport/jsonSend', this, this.onJsonSend, 5);
console.groupEnd();
return false;
},
obj3d: null,
getXyzCoords: function() {
chilipeppr.subscribe("/com-chilipeppr-widget-3dviewer/recv3dObject", this, this.getXyzCoordsRecv3dObj);
chilipeppr.publish("/com-chilipeppr-widget-3dviewer/request3dObject", "");
},
getXyzCoordsRecv3dObj: function(obj3d) {
console.log("Got our 3d obj. Line count:", obj3d.userData.lines.length);
this.obj3d = obj3d;
// unsub so we don't get anymore callbacks on this
chilipeppr.unsubscribe("/com-chilipeppr-widget-3dviewer/recv3dObject", this.getXyzCoordsRecv3dObj);
},
getXyzCoordsForLine: function(line) {
console.log("getXyzCoordsForLine. line:", line);
var ret = {
start: {
index: null, x: null, y: null, z: null
},
end: {
index: null, x: null, y: null, z: null
},
meta: null
};
var indx = line - 2;
if (indx < 0) indx = 0;
ret.start.index = indx;
ret.start.x = this.obj3d.userData.lines[indx].p2.x;
ret.start.y = this.obj3d.userData.lines[indx].p2.y;
ret.start.z = this.obj3d.userData.lines[indx].p2.z;
indx = line - 1;
ret.end.index = indx;
ret.end.x = this.obj3d.userData.lines[indx].p2.x;
ret.end.y = this.obj3d.userData.lines[indx].p2.y;
ret.end.z = this.obj3d.userData.lines[indx].p2.z;
ret.meta = this.obj3d.userData.lines[indx];
return ret;
}
}
myMacro.init();