Skip to content

Commit

Permalink
Test branch (#92)
Browse files Browse the repository at this point in the history
* Add Arc Support (G2/G3) for X Y Z I J E F form.

* Add initial print simulation support for G2/G3.

* Remove unused code block

* Add gauge

* Handle NaN in coordinates for arc simulation.  May resolve #86

* Handle NaN for i,j,r,e and f.

* Add status bar for temps

* Update status text

* Show status in fullscreen only

* Update status message

* Update version


Co-authored-by: FormerLurker <hochgebe@gmail.com>
Co-authored-by: unknown <kragrathea>
Kragrathea and FormerLurker authored Dec 14, 2020
1 parent c9a517f commit ccd3606
Showing 5 changed files with 337 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -8,3 +8,4 @@ dist
.DS_Store
/.vs
/node_modules/three
.vscode/*
16 changes: 16 additions & 0 deletions octoprint_prettygcode/static/css/prettygcode.css
Original file line number Diff line number Diff line change
@@ -63,6 +63,22 @@
display:none;
}

.pgfullscreen .pgstatus
{
display:unset;
}
.pgstatus
{
display:none;
position:absolute;
top:0px;
left:0px;
width:100%;
font-size:large;
text-align:center;
background:#ffffff50;
}

.pgfullscreen #state_wrapper {
top: 20px;
left: 20px;
341 changes: 316 additions & 25 deletions octoprint_prettygcode/static/js/prettygcode.js
Original file line number Diff line number Diff line change
@@ -32,12 +32,205 @@ $(function () {
updateJob(data.job);
};

/* Arc Interpolation Parameters */
self.mm_per_arc_segment = 1.0; // The absolute longest length of an interpolated segment
self.min_arc_segments = 20; // The minimum number of interpolated segments in a full circle, 0 to disable
// The absolute minimum length of an interpolated segment.
// Limited by mm_per_arc_segment as a max and min_arc_segments as a minimum, 0 to disable
self.min_mm_per_arc_segment = 0.1;
// This controls how many arcs will be drawn before the exact position of the
// next segment is recalculated. Reduces the number of sin/cos calls.
// 0 to disable
self.n_arc_correction = 24;

// A function to interpolate arcs into straight segments. Returns an array of positions
self.interpolateArc = function (state, arc) {
// This is adapted from the Marlin arc interpolation routine found at
// https://github.com/MarlinFirmware/Marlin/
// The license can be found here: https://github.com/MarlinFirmware/Marlin/blob/2.0.x/LICENSE
// This allows the rendered arcs to be VERY close to what would be printed,
// depending on the firmware settings.

// Create vars to hold the initial and current position so we don't affect the state
var initial_position = {}, current_position = {};
Object.assign(initial_position, state)
Object.assign(current_position, state)
// Create the results which contain the copied initial position
var interpolated_segments = [initial_position];

// note that arc.is_clockwise determines if this is a G2, else it is a G3
// I'm going to also extract all the necessary variables up front to make this easier
// to convert from the source c++ arc interpolation code

// Convert r format to i j format if necessary
// I have no code like this to test, so I am not 100% sure this will work as expected
// commenting out for now
/*
if (arc.r)
{
if (arc.x != current_position.x || arc.y != current_position.y) {
var vector = {x: (arc.x - current_position.x)/2.0, y: (arc.y - current_position.y)/2.0};
var e = arc.is_clockwise ^ (arc.r < 0) ? -1 : 1;
var len = Math.sqrt(Math.pow(vector.x,2) + Math.pow(vector.y,2));
var h2 = (arc.r - len) * (arc.r + len);
var h = (h2 >= 0) ? Math.sqrt(h2) : 0.0;
var bisector = {x: -1.0*vector.y, y: vector.x };
arc.i = (vector.x + bisector.x) / len * e * h;
arc.j = (vector.y + bisector.y) / len * e * h;
}
}*/

// Calculate the radius, we will be using it a lot.
var radius = Math.hypot(arc.i, arc.j);
// Radius Vector
var v_radius = {x: -1.0 * arc.i, y: -1.0 * arc.j};
// Center of arc
var center = {x: current_position.x - v_radius.x, y: current_position.y - v_radius.y};
// Z Travel Total
var travel_z = arc.z - current_position.z;
// Extruder Travel
var travel_e = arc.e - current_position.e;
// Radius Target Vector
var v_radius_target = {x: arc.x - center.x, y: arc.y - center.y};

var angular_travel_total = Math.atan2(
v_radius.x * v_radius_target.y - v_radius.y * v_radius_target.x,
v_radius.x * v_radius_target.x + v_radius.y * v_radius_target.y
);
// Having a positive angle is convenient here. We will make it negative later
// if we need to.
if (angular_travel_total < 0) { angular_travel_total += 2.0 * Math.PI}

// Copy our mm_per_arc_segments var because we may be modifying it for this arc
var mm_per_arc_segment = self.mm_per_arc_segment;

// Enforce min_arc_segments if it is greater than 0
if (self.min_arc_segments > 0) {
mm_per_arc_segment = (radius * ((2.0 * Math.PI) / self.min_arc_segments));
// We will need to enforce our max segment length later, flag this
}

// Enforce the minimum segment length if it is set
if (self.min_mm_per_arc_segment > 0)
{
if (mm_per_arc_segment < self.min_mm_per_arc_segment) {
mm_per_arc_segment = self.min_mm_per_arc_segment;
}
}

// Enforce the maximum segment length
if (mm_per_arc_segment > self.mm_per_arc_segment) {
mm_per_arc_segment = self.mm_per_arc_segment;
}

// Adjust the angular travel if the direction is clockwise
if (arc.is_clockwise) { angular_travel_total -= (2.0 * Math.PI); }

// Compensate for a full circle, which would give us an angle of 0 here
// We want that to be 2Pi. Note, full circles are bad in 3d printing, but they
// should still render correctly
if (current_position.x == arc.x && current_position.y == arc.y && angular_travel_total == 0)
{
angular_travel_total += 2.0 * Math.PI;
}

// Now it's time to calculate the mm of total travel along the arc, making sure we take Z into account
var mm_of_travel_arc = Math.hypot(angular_travel_total * radius, Math.abs(travel_z));

// Get the number of segments total we will be generating
var num_segments = Math.ceil(mm_of_travel_arc / mm_per_arc_segment);

// Calculate xy_segment_theta, z_segment_theta, and e_segment_theta
// This is the distance we will be moving for each interpolated segment
var xy_segment_theta = angular_travel_total / num_segments;
var z_segment_theta = travel_z / num_segments;
var e_segment_theta = travel_e / num_segments;

// Time to interpolate!
if (num_segments > 1)
{
// it's possible for num_segments to be zero. If that's true, we just need to draw a line
// from the start to the end coordinates, and this isn't needed.

// I am NOT going to use the small angel approximation for sin and cos here, but it
// could be easily added if performance is a problem. Here is code for this if it becomes
// necessary:
//var sq_theta_per_segment = theta_per_segment * theta_per_segment;
//var sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6;
//var cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
var cos_t = Math.cos(xy_segment_theta);
var sin_t = Math.sin(xy_segment_theta);
var r_axisi;

// We are going to correct sin and cos only occasionally to reduce cpu usage
var count = 0;
// Loop through each interpolated segment, minus the endpoint which will be handled separately
for (var i = 1; i < num_segments; i++) {

if (count < self.n_arc_correction)
{
// not time to recalculate X and Y.
// Apply the rotational vector
r_axisi = v_radius.x * sin_t + v_radius.y * cos_t;
v_radius.x = v_radius.x * cos_t - v_radius.y * sin_t;
v_radius.y = r_axisi;
count++;
}
else
{
// Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
// Compute exact location by applying transformation matrix from initial radius vector(=-offset).
var sin_ti = Math.sin(i * xy_segment_theta);
var cos_ti = Math.cos(i * xy_segment_theta);
v_radius.x = (-1.0 * arc.i) * cos_ti + arc.j * sin_ti;
v_radius.y = (-1.0 * arc.i) * sin_ti - arc.j * cos_ti;
count = 0;
}

// Draw the segment
var line = {
x: center.x + v_radius.x,
y: center.y + v_radius.y,
z: current_position.z + z_segment_theta,
e: current_position.e + e_segment_theta,
f: arc.f
};
/*console.debug(
"Arc Segment " + i.toString() + ":" +
" X" + line.x.toString() +
" Y" + line.y.toString() +
" Z" + line.z.toString() +
" E" + line.e.toString() +
" F" + line.f.toString()
);*/
interpolated_segments.push(line);

// Update the current state
current_position.x = line.x;
current_position.y = line.y;
current_position.z = line.z;
current_position.e = line.e;
}
}
// Move to the target position
var line = {
x: arc.x,
y: arc.y,
z: arc.z,
e: arc.e,
f: arc.f
};
interpolated_segments.push(line);
//Done!!!
return interpolated_segments;
};

//used to animate the nozzle position in response to terminal messages
function PrintHeadSimulator()
{
var buffer=[];
var HeadState =function(){
var HeadState = function(){
this.position=new THREE.Vector3(0,0,0);
this.rate=5.0*60;
this.extrude=false;
@@ -55,9 +248,9 @@ $(function () {
return(newState);
}
};
var curState= new HeadState();
var curEnd= new HeadState();
var parserCurState= new HeadState();
var curState = new HeadState();
var curEnd = new HeadState();
var parserCurState = new HeadState();

var observedLayerCount=0;
var parserLayerLineNumber=0;
@@ -72,6 +265,10 @@ $(function () {
return({position:curState.position,layerZ:curLastExtrudedZ,lineNumber:curState.layerLineNumber});
}

this.getBufferStats=function()
{
return(buffer.length);
}
//
//var currentFileOffset=0;

@@ -84,8 +281,14 @@ $(function () {
console.log("PrintHeadSimulator buffer overflow")
return;
}
if(cmd.indexOf(" G0")>-1 || cmd.indexOf(" G1")>-1)
var is_g0_g1 = cmd.indexOf(" G0")>-1 || cmd.indexOf(" G1")>-1;
var is_g2_g3 = !is_g0_g1 && cmd.indexOf(" G2")>-1 || cmd.indexOf(" G3")>-1;
if(is_g0_g1 || is_g2_g3)
{
var parserPreviousState = {};
// If this is a g2/g3, we need to know the previous state to interpolate the arcs
if (is_g2_g3) { parserPreviousState = Object.assign(parserPreviousState, parserCurState);}
// Extract x, y, z, f and e
var x= parseFloat(cmd.split("X")[1])
if(!Number.isNaN(x))
{
@@ -132,11 +335,45 @@ $(function () {
}else{
parserCurState.extrude=false;
}

parserCurState.layerLineNumber =parserLayerLineNumber;

buffer.push(parserCurState.clone());


// if this is a g0/g1, push the state to the buffer
if (is_g0_g1) {buffer.push(parserCurState.clone());}
else{
// This is a g2/g3, so we need to do things a bit differently.
// Extract I and J, R, and is_clockwise
var is_clockwise = cmd.indexOf(" G2")>-1;
var i = parseFloat(cmd.split("I")[1]);
var j = parseFloat(cmd.split("J")[1]);
var r = parseFloat(cmd.split("R")[1]);
var arc = {
// Get X Y and Z from the previous state if it is not
// provided
x: this.getCurrentCoordinate(x, parserPreviousState.position.x),
y: this.getCurrentCoordinate(y, parserPreviousState.position.y),
z: this.getCurrentCoordinate(z, parserPreviousState.position.z),
// Set I and J and R to 0 if they are not provided.
i: this.getCurrentCoordinate(i, 0),
j: this.getCurrentCoordinate(j, 0),
r: this.getCurrentCoordinate(r, 0),
// K omitted, not sure what that's supposed to do
//k: k !== undefined ? k : 0,
// Since the amount extruded doesn't really matter, set it to 1 if we are extruding,
// We don't want undefined values going into the arc interpolation routine
e: this.getCurrentCoordinate(e, parserPreviousState.extrude ? 1 : 0),
f: this.getCurrentCoordinate(r, parserPreviousState.rate),
is_clockwise: is_clockwise
};
// Need to handle R maybe
var segments = self.interpolateArc(parserPreviousState, arc);
for(var index = 1; index < segments.length; index++)
{
var cur_segment = segments[index];
var cur_state = parserCurState.clone();
cur_state.position = new THREE.Vector3(cur_segment.x,cur_segment.y,cur_segment.z);
buffer.push(cur_state);
}
}
} else if (cmd.indexOf(" G90")>-1) {
//G90: Set to Absolute Positioning
parserCurState.relative = false;
@@ -149,6 +386,11 @@ $(function () {
//window.myMaxRate=120.0;
//window.fudge=7;

// Handle undefined and NaN for current coordinates.
this.getCurrentCoordinate=function(cmdCoord, prevCoord) {
if (cmdCoord === undefined || isNaN(cmdCoord)){cmdCoord=prevCoord;}
return cmdCoord;
}
//Update the printhead position based on time elapsed.
this.updatePosition=function(timeStep){

@@ -256,6 +498,15 @@ $(function () {
terminalGcodeProxy.parse(matches[0]+'\n');
}
}
else if(e.startsWith("Recv: T:"))
{
//console.log(["GCmd:",e]);
let parts = e.substr(6).split("@");//remove Recv: and checksum.
let temps = parts[0];
let statusStr = temps;//+" Buffer:"+printHeadSim.getBufferStats()
$(".pgstatus").text(statusStr);

}
})
}
};
@@ -283,7 +534,7 @@ $(function () {
$("#pgcss").html(css);
$("#pg_add_css").val(css);
}
};
};
self.onEventFileSelected = function (payload){
//console.log(["onEventFileSelected ",payload])
}
@@ -358,7 +609,6 @@ $(function () {
}
}


var bedVolume = undefined;
var viewInitialized = false;
self.onTabChange = function (current, previous) {
@@ -934,6 +1184,14 @@ $(function () {

console.log("Finished loading GCode object.")
console.log(["layers:",layers.length,"size:",filePos])

let totalLines=0;
for(let layer of layers)
{
totalLines+=layer.vertex.length/6;
}
console.log(["lines:",totalLines])

//console.log([sceneBounds,layers])

//gcodeProxy.syncGcodeObjTo(Infinity);
@@ -1195,20 +1453,53 @@ $(function () {
state = line;
} else if (cmd === 'G2' || cmd === 'G3') {
//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
/*var arc = {
x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
i: args.i !== undefined ? absolute( state.i, args.i ) : state.i,
j: args.j !== undefined ? absolute( state.j, args.j ) : state.j,
k: args.k !== undefined ? absolute( state.k, args.k ) : state.k,
e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
};*/



console.warn('THREE.GCodeLoader: Arc command not supported');
// Not supporting K ATM
if (args.k !== undefined)
{
// I have no idea what K is for...
console.warn('THREE.GCodeLoader: Arcs with K parameter not currently supported');
}
else if (args.r !== undefined)
{
console.warn('THREE.GCodeLoader: Arc in R form are not currently supported.');
}
else
{
var arc = {
x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
i: args.i !== undefined ? args.i : 0,
j: args.j !== undefined ? args.j : 0,
r: args.r !== undefined ? args.r : null,
// What is this K I'm seeing here, lol
//k: args.k !== undefined ? absolute( state.k, args.k ) : state.k,
e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
is_clockwise: cmd === 'G2'
};
/* If R format is working, this could be used. I have no test code so I can't verify
if ((arc.i || arc.j) && arc.r)
{
console.warn('THREE.GCodeLoader: Arc contains I/J and R, which is not allowed. Removing R');
arc.r = null;
}
else
{
var segments = self.interpolateArc(state, arc);
for(var index = 1; index < segments.length; index++)
{
this.addSegment(segments[index-1], segments[index]);
}
}*/
var segments = self.interpolateArc(state, arc);
for(var index = 1; index < segments.length; index++)
{
this.addSegment(segments[index-1], segments[index]);
}
// Set the state to the last segment
state = segments[segments.length - 1];
}
} else if (cmd === 'G90') {
//G90: Set to Absolute Positioning
state.relative = false;
4 changes: 3 additions & 1 deletion octoprint_prettygcode/templates/prettygcode_tab.jinja2
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@
<div class='gwin' id='gwin' style=''>
<canvas id='mycanvas' style='width:100%;height:100%'></canvas>
{# <canvas id='pipcanvas' style='position:absolute;right:0px;width:30%;height:30%'></canvas> #}

<div id='pgstatus' class='pgstatus'>T:0/0 B:0/0</div>
<div id='mygui' class='pghidden'>View Settings</div>

<button class="pgstatetoggle" title="Toggle state window"><i class="fa icon-black fa-info-circle"></i></button>
<button class="pgfilestoggle" title="Toggle file window"><i class="fa icon-black fa-list"></i></button>
<button class="pgsettingstoggle" title="Toggle settings window">&#9881</button>
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
plugin_name = "OctoPrint-PrettyGCode"

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "1.2.3"
plugin_version = "1.2.4"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module

0 comments on commit ccd3606

Please sign in to comment.