More work on the GCode viewer

- confirmation dialog when trying to visualize large files (different threshold for mobile and "regular" devices, configurable of course, although only via config file right now) - should also help a bit with issues leading to #215
 - proper clearing of the viewer area when reconnecting to the backend (e.g. after server restart)

 Also all of this plus previous commits closes #35
This commit is contained in:
Gina Häußge 2014-01-06 00:16:33 +01:00
parent b9f49a83e0
commit ad556e7413
9 changed files with 250 additions and 171 deletions

View file

@ -486,7 +486,7 @@ class Printer():
self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()})
def mcSdFiles(self, files):
eventManager().fire(Events.UPDATED_FILES, {"type": "gcode", "files": files})
eventManager().fire(Events.UPDATED_FILES, {"type": "gcode"})
self._sdFilelistAvailable.set()
def mcFileSelected(self, filename, filesize, sd):

View file

@ -51,7 +51,7 @@ def index():
"index.jinja2",
webcamStream=settings().get(["webcam", "stream"]),
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
enableGCodeVisualizer=settings().get(["gcodeViewer", "enabled"]),
enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]),
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0,
enableAccessControl=userManager is not None,
@ -60,7 +60,9 @@ def index():
debug=debug,
gitBranch=branch,
gitCommit=commit,
stylesheet=settings().get(["devel", "stylesheet"])
stylesheet=settings().get(["devel", "stylesheet"]),
gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]),
gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"])
)

View file

@ -54,7 +54,7 @@ def getSettings():
"flipV": s.getBoolean(["webcam", "flipV"])
},
"feature": {
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
"gcodeViewer": s.getBoolean(["gcodeViewer", "enabled"]),
"temperatureGraph": s.getBoolean(["feature", "temperatureGraph"]),
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]),
"alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]),
@ -130,7 +130,7 @@ def setSettings():
if "flipV" in data["webcam"].keys(): s.setBoolean(["webcam", "flipV"], data["webcam"]["flipV"])
if "feature" in data.keys():
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["gcodeViewer", "enabled"], data["feature"]["gcodeViewer"])
if "temperatureGraph" in data["feature"].keys(): s.setBoolean(["feature", "temperatureGraph"], data["feature"]["temperatureGraph"])
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"])

View file

@ -55,8 +55,12 @@ default_settings = {
"options": {}
}
},
"gcodeViewer": {
"enabled": True,
"mobileSizeThreshold": 2 * 1024 * 1024, # 2MB
"sizeThreshold": 20 * 1024 * 1024, # 20MB
},
"feature": {
"gCodeVisualizer": True,
"temperatureGraph": True,
"waitForStartOnConnect": False,
"alwaysSendChecksum": False,

View file

@ -120,13 +120,12 @@ GCODE.gCodeReader = (function(){
// ***** PUBLIC *******
return {
clear: function() {
delete lines;
delete gcode;
model = [];
z_heights = [];
},
loadFile: function(reader){
model = [];
z_heights = [];
this.clear();
var totalSize = reader.target.result.length;
lines = reader.target.result.split(/\n/);
@ -144,7 +143,6 @@ GCODE.gCodeReader = (function(){
}
}
);
this.clear();
},
setOption: function(options){

View file

@ -17,10 +17,10 @@ GCODE.renderer = (function(){
var layerNumStore, progressStore={from: 0, to: -1};
var lastX, lastY;
var dragStart,dragged;
var dragStart, dragged;
var scaleFactor = 1.1;
var model;
var initialized=false;
var initialized = false;
var renderOptions = {
colorGrid: "#bbbbbb",
bgColorGrid: "#ffffff",
@ -55,7 +55,6 @@ GCODE.renderer = (function(){
var speeds = [];
var speedsByLayer = {};
var reRender = function(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
@ -183,8 +182,11 @@ GCODE.renderer = (function(){
};
var drawGrid = function() {
console.log("Drawing grid");
ctx.translate(offsetBedX, offsetBedY);
ctx.beginPath();
var width = renderOptions["bed"]["x"] * zoomFactor;
var height = renderOptions["bed"]["y"] * zoomFactor;
var origin = {
@ -220,6 +222,8 @@ GCODE.renderer = (function(){
};
var drawLayer = function(layerNum, fromProgress, toProgress, isNotCurrentLayer){
console.log("Drawing layer " + layerNum + " from " + fromProgress + " to " + toProgress + " (current: " + !isNotCurrentLayer + ")");
var i;
isNotCurrentLayer = typeof isNotCurrentLayer !== 'undefined' ? isNotCurrentLayer : false;
@ -279,7 +283,7 @@ GCODE.renderer = (function(){
x = cmds[i].x;
}
if (typeof(cmds[i].y) === 'undefined' || isNaN(cmds[i].y)) {
y=prevY/zoomFactor;
y = prevY / zoomFactor;
} else {
y = -cmds[i].y;
}
@ -329,30 +333,58 @@ GCODE.renderer = (function(){
ctx.stroke();
};
/*
var calculateBedOffset = function(bedDimensions) {
if (!bedDimensions) bedDimensions = renderOptions["bed"];
var max = Math.max(bedDimensions.x, bedDimensions.y);
return {
x: (max - bedDimensions.x) / 2 * zoomFactor,
y: (max - bedDimensions.y) / 2 * zoomFactor
};
var applyOffsets = function(mdlInfo) {
// determine bed and model offsets
if (ctx) ctx.translate(-offsetModelX, -offsetModelY);
if (renderOptions["centerViewport"] || renderOptions["zoomInOnModel"]) {
var canvasCenter = ctx.transformedPoint(canvas.width / 2, canvas.height / 2);
if (mdlInfo) {
offsetModelX = canvasCenter.x - (mdlInfo.min.x + mdlInfo.modelSize.x / 2) * zoomFactor;
offsetModelY = canvasCenter.y + (mdlInfo.min.y + mdlInfo.modelSize.y / 2) * zoomFactor;
} else {
offsetModelX = 0;
offsetModelY = 0;
}
offsetBedX = 0;
offsetBedY = 0;
} else if (mdlInfo && renderOptions["moveModel"]) {
offsetModelX = (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor;
offsetModelY = -1 * (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor;
offsetBedX = -1 * (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor;
offsetBedY = (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor;
} else {
offsetModelX = 0;
offsetModelY = 0;
offsetBedX = 0;
offsetBedY = 0;
}
if (ctx) ctx.translate(offsetModelX, offsetModelY);
};
var applyBedOffset = function(bedDimensions, onlyNew) {
if (true) return;
var bedOffset = calculateBedOffset(bedDimensions);
if (offsetBedX == bedOffset.x && offsetBedY == bedOffset.y) return;
ctx.translate(-offsetBedX, offsetBedY);
offsetBedX = bedOffset.x;
offsetBedY = bedOffset.y;
ctx.translate(bedOffset.x, -bedOffset.y);
}
*/
var applyZoom = function(mdlInfo) {
var pt = ctx.transformedPoint(canvas.width/2,canvas.height/2);
var transform = ctx.getTransform();
var scaleF;
if (scaleX && scaleY && transform.a && transform.d) {
ctx.translate(pt.x, pt.y);
ctx.scale(1 / scaleX, 1 / scaleY);
ctx.translate(-pt.x, -pt.y);
}
if (mdlInfo && renderOptions["zoomInOnModel"]) {
scaleF = mdlInfo.modelSize.x > mdlInfo.modelSize.y ? (canvas.width - 10) / mdlInfo.modelSize.x : (canvas.height - 10) / mdlInfo.modelSize.y;
scaleF /= zoomFactor;
if (transform.a && transform.d) {
scaleX = scaleF / transform.a;
scaleY = scaleF / transform.d;
ctx.translate(pt.x,pt.y);
ctx.scale(scaleX, scaleY);
ctx.translate(-pt.x, -pt.y);
}
} else {
scaleX = 1;
scaleY = 1;
}
};
// ***** PUBLIC *******
return {
@ -366,22 +398,24 @@ GCODE.renderer = (function(){
offsetModelY = 0;
offsetBedX = 0;
offsetBedY = 0;
//applyBedOffset();
},
setOption: function(options){
var mustRefresh = false;
var dirty = false;
for (var opt in options) {
if (!options.hasOwnProperty(opt)) continue;
if (options[opt] === undefined) continue;
if (options.hasOwnProperty(opt)) renderOptions[opt] = options[opt];
dirty = dirty || (renderOptions[opt] != options[opt]);
renderOptions[opt] = options[opt];
if ($.inArray(opt, ["moveModel", "centerViewport", "zoomInOnModel", "bed"])) {
mustRefresh = true;
}
}
if (!dirty) return;
if(initialized) {
if (mustRefresh) {
//applyBedOffset();
this.refresh();
} else {
reRender();
@ -401,7 +435,7 @@ GCODE.renderer = (function(){
var p2 = ctx.transformedPoint(canvas.width, canvas.height);
ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
drawGrid();
if (model) {
if (model && model.length) {
if (layerNum < model.length) {
if (renderOptions['showNextLayer'] && layerNum < model.length - 1) {
drawLayer(layerNum + 1, 0, this.getLayerNumSegments(layerNum + 1), true);
@ -426,68 +460,39 @@ GCODE.renderer = (function(){
}
},
clear: function() {
model = undefined;
initialized = false;
this.render();
offsetModelX = 0;
offsetModelY = 0;
offsetBedX = 0;
offsetBedY = 0;
scaleX = 1;
scaleY = 1;
speeds = [];
speedsByLayer = {};
this.doRender([], 0);
},
doRender: function(mdl, layerNum){
model = mdl;
if (!model || !model[layerNum]) return;
var mdlInfo;
var mdlInfo = undefined;
prevX = 0;
prevY = 0;
if (!initialized) this.init();
mdlInfo = GCODE.gCodeReader.getModelInfo();
speeds = mdlInfo.speeds;
speedsByLayer = mdlInfo.speedsByLayer;
// determine bed and model offsets
if (ctx) ctx.translate(-offsetModelX, -offsetModelY);
if (renderOptions["centerViewport"] || renderOptions["zoomInOnModel"]) {
var canvasCenter = ctx.transformedPoint(canvas.width / 2, canvas.height / 2);
offsetModelX = canvasCenter.x - (mdlInfo.min.x + mdlInfo.modelSize.x / 2) * zoomFactor;
offsetModelY = canvasCenter.y + (mdlInfo.min.y + mdlInfo.modelSize.y / 2) * zoomFactor;
offsetBedX = 0;
offsetBedY = 0;
} else if (renderOptions["moveModel"]) {
offsetModelX = (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor;
offsetModelY = -1 * (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor;
offsetBedX = -1 * (renderOptions["bed"]["x"] / 2 - (mdlInfo.min.x + mdlInfo.modelSize.x / 2)) * zoomFactor;
offsetBedY = (renderOptions["bed"]["y"] / 2 - (mdlInfo.min.y + mdlInfo.modelSize.y / 2)) * zoomFactor;
} else {
offsetModelX = 0;
offsetModelY = 0;
offsetBedX = 0;
offsetBedY = 0;
}
if (ctx) ctx.translate(offsetModelX, offsetModelY);
var pt = ctx.transformedPoint(canvas.width/2,canvas.height/2);
var transform = ctx.getTransform();
var scaleF;
if (scaleX && scaleY && transform.a && transform.d) {
ctx.translate(pt.x, pt.y);
ctx.scale(1 / scaleX, 1 / scaleY);
ctx.translate(-pt.x, -pt.y);
}
if (renderOptions["zoomInOnModel"]) {
scaleF = mdlInfo.modelSize.x > mdlInfo.modelSize.y ? (canvas.width - 10) / mdlInfo.modelSize.x : (canvas.height - 10) / mdlInfo.modelSize.y;
scaleF /= zoomFactor;
if (transform.a && transform.d) {
scaleX = scaleF / transform.a;
scaleY = scaleF / transform.d;
ctx.translate(pt.x,pt.y);
ctx.scale(scaleX, scaleY);
ctx.translate(-pt.x, -pt.y);
var toProgress = 1;
if (model) {
mdlInfo = GCODE.gCodeReader.getModelInfo();
speeds = mdlInfo.speeds;
speedsByLayer = mdlInfo.speedsByLayer;
if (model[layerNum]) {
toProgress = model[layerNum].length;
}
} else {
scaleX = 1;
scaleY = 1;
}
this.render(layerNum, 0, model[layerNum].length);
applyOffsets(mdlInfo);
applyZoom(mdlInfo);
this.render(layerNum, 0, toProgress);
},
refresh: function(layerNum) {
if (!layerNum) layerNum = layerNumStore;

View file

@ -7,8 +7,6 @@
var GCODE = {};
GCODE.ui = (function(){
var gCodeLines = {first: 0, last: 0};
var uiOptions = {
container: undefined,
toolOffsets: undefined,
@ -28,7 +26,6 @@ GCODE.ui = (function(){
if (!onlyInfo) {
var segmentCount = GCODE.renderer.getLayerNumSegments(layerNum);
GCODE.renderer.render(layerNum, 0, segmentCount - 1);
gCodeLines = GCODE.gCodeReader.getGCodeLines(layerNum, 0, segmentCount - 1);
}
if (uiOptions["onLayerSelected"]) {
@ -45,7 +42,6 @@ GCODE.ui = (function(){
};
var switchCommands = function(layerNum, first, last) {
gCodeLines = GCODE.gCodeReader.getGCodeLines(layerNum, first, last);
GCODE.renderer.render(layerNum, first, last);
};
@ -166,11 +162,16 @@ GCODE.ui = (function(){
},
clear: function() {
GCODE.renderer.clear();
GCODE.gCodeReader.clear();
delete this.worker;
GCODE.renderer.clear();
this.init(uiOptions);
setProgress("", 0);
if (uiOptions["onLayerSelected"]) {
uiOptions.onLayerSelected();
}
if (uiOptions["onModelLoaded"]) {
uiOptions.onModelLoaded();
}
},
updateLayerInfo: function(layerNum){
@ -185,12 +186,10 @@ GCODE.ui = (function(){
changeSelectedLayer: function(newLayerNum) {
switchLayer(newLayerNum);
if (callback) callback(newLayerNum, segmentCount);
},
changeSelectedCommands: function(layerNum, first, last) {
switchCommands(layerNum, first, last);
if (callback) callback(first, last);
}
}
}());

View file

@ -28,6 +28,15 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.ui_modelInfo = ko.observable("");
self.ui_layerInfo = ko.observable("");
self.enableReload = ko.observable(false);
self.waitForApproval = ko.observable(false);
self.selectedFile = {
name: ko.observable(undefined),
date: ko.observable(undefined),
size: ko.observable(undefined)
};
self.renderer_centerModel = ko.observable(false);
self.renderer_centerViewport = ko.observable(false);
self.renderer_zoomOnModel = ko.observable(false);
@ -116,6 +125,9 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.layerSlider = undefined;
self.layerCommandSlider = undefined;
self.currentLayer = undefined;
self.currentCommand = undefined;
self.initialize = function() {
self._configureLayerSlider();
self._configureLayerCommandSlider();
@ -136,13 +148,16 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.reinitialize = function() {
self.enabled = false;
self.enableReload(false);
self.loadedFilename = undefined;
self.loadedFileDate = undefined;
GCODE.ui.clear();
self.initialize();
self.clear();
};
self.clear = function() {
GCODE.ui.clear();
}
self._configureLayerSlider = function() {
self.layerSlider = $("#gcode_slider_layers").slider({
id: "gcode_layer_slider",
@ -172,6 +187,7 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
};
self.loadFile = function(filename, date){
self.enableReload(false);
if (self.status == "idle" && self.errorCount < 3) {
self.status = "request";
$.ajax({
@ -184,13 +200,14 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.loadedFilename = filename;
self.loadedFileDate = date;
self.status = "idle";
self.enableReload(true);
}
},
error: function() {
self.status = "idle";
self.errorCount++;
}
})
});
}
};
@ -206,7 +223,8 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.layerCommandSlider.slider("disable");
};
self.refresh = function() {
self.reload = function() {
if (!self.enableReload()) return;
self.loadFile(self.loadedFilename, self.loadedFileDate);
};
@ -219,19 +237,25 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
};
self._processData = function(data) {
if (!self.enabled) return;
if (!data.job.file || !data.job.file.name && (self.loadedFilename || self.loadedFileDate)) {
self.waitForApproval(false);
self.loadedFilename = undefined;
self.loadedFileDate = undefined;
GCODE.renderer.clear();
self.selectedFile.name(undefined);
self.selectedFile.date(undefined);
self.selectedFile.size(undefined);
self.clear();
return;
}
if (!self.enabled) return;
self.currentlyPrinting = data.state.flags && (data.state.flags.printing || data.state.flags.paused);
if(self.loadedFilename
&& self.loadedFilename == data.job.file.name
&& self.loadedFileDate == data.job.file.date) {
if (self.currentlyPrinting && self.renderer_syncProgress()) {
if (self.currentlyPrinting && self.renderer_syncProgress() && !self.waitForApproval()) {
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.completion);
if(cmdIndex){
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
@ -242,11 +266,31 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
}
}
self.errorCount = 0
} else if (data.job.file.name && data.job.file.origin != "sdcard") {
self.loadFile(data.job.file.name, data.job.file.date);
} else {
self.clear();
if (data.job.file.name && data.job.file.origin != "sdcard"
&& (!self.waitForApproval() || (self.selectedFile.name() != data.job.file.name || self.selectedFile.date() != data.job.file.date))) {
self.selectedFile.name(data.job.file.name);
self.selectedFile.date(data.job.file.date);
self.selectedFile.size(data.job.file.size);
if (data.job.file.size > CONFIG_GCODE_SIZE_THRESHOLD || ($.browser.mobile && data.job.file.size > CONFIG_GCODE_MOBILE_SIZE_THRESHOLD)) {
self.waitForApproval(true);
self.loadedFilename = undefined;
self.loadedFileDate = undefined;
} else {
self.waitForApproval(false);
self.loadFile(data.job.file.name, data.job.file.date);
}
}
}
};
self.approveLargeFile = function() {
self.waitForApproval(false);
self.loadFile(self.selectedFile.name(), self.selectedFile.date());
}
self._onProgress = function(type, percentage) {
self.ui_progress_type(type);
self.ui_progress_percentage(percentage);
@ -258,6 +302,7 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.layerSlider.slider("disable");
self.layerSlider.slider("setMax", 1);
self.layerSlider.slider("setValue", 0);
self.currentLayer = 0;
} else {
var output = [];
output.push("Model size is: " + model.width.toFixed(2) + "mm &times; " + model.depth.toFixed(2) + "mm &times; " + model.height.toFixed(2) + "mm");
@ -286,6 +331,7 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
self.layerCommandSlider.slider("disable");
self.layerCommandSlider.slider("setMax", 1);
self.layerCommandSlider.slider("setValue", [0, 1]);
self.currentCommand = [0, 1];
} else {
var output = [];
output.push("Layer number: " + layer.number);
@ -312,6 +358,9 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
if (self.currentlyPrinting && self.renderer_syncProgress()) self.renderer_syncProgress(false);
var value = event.value;
if (self.currentLayer !== undefined && self.currentLayer == value) return;
self.currentLayer = value;
GCODE.ui.changeSelectedLayer(value);
};
@ -319,6 +368,9 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
if (self.currentlyPrinting && self.renderer_syncProgress()) self.renderer_syncProgress(false);
var tuple = event.value;
if (self.currentCommand !== undefined && self.currentCommand[0] == tuple[0] && self.currentCommand[1] == tuple[1]) return;
self.currentCommand = tuple;
GCODE.ui.changeSelectedCommands(self.layerSlider.slider("getValue"), tuple[0], tuple[1]);
};
}

View file

@ -7,18 +7,18 @@
<link rel="apple-touch-icon" sizes="114x114" href="{{ url_for('static', filename='img/apple-touch-icon-114x114.png') }}">
<link rel="apple-touch-icon" sizes="144x144" href="{{ url_for('static', filename='img/apple-touch-icon-144x144.png') }}">
{% if stylesheet == "less" %}
<link href="{{ url_for('static', filename='less/octoprint.less') }}" rel="stylesheet/less" type="text/css" media="screen">
<script src="{{ url_for('static', filename='js/lib/less.min.js') }}" type="text/javascript"></script>
{% else %}
<link href="{{ url_for('static', filename='css/octoprint.css') }}" rel="stylesheet" type="text/css" media="screen">
{% endif %}
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/bootstrap-modal.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/bootstrap-slider.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/jquery.fileupload-ui.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/jquery.pnotify.default.css') }}" rel="stylesheet" media="screen">
{% if stylesheet == "less" %}
<link href="{{ url_for('static', filename='less/octoprint.less') }}" rel="stylesheet/less" type="text/css" media="screen">
<script src="{{ url_for('static', filename='js/lib/less.min.js') }}" type="text/javascript"></script>
{% else %}
<link href="{{ url_for('static', filename='css/octoprint.css') }}" rel="stylesheet" type="text/css" media="screen">
{% endif %}
<script lang="javascript">
var BASEURL = "{{ url_for('index') }}";
@ -33,6 +33,8 @@
var CONFIG_SD_SUPPORT = {% if enableSdSupport -%} true; {% else %} false; {%- endif %}
var CONFIG_FIRST_RUN = {% if firstRun -%} true; {% else %} false; {%- endif %}
var CONFIG_TEMPERATURE_GRAPH = {% if enableTemperatureGraph -%} true; {% else %} false; {%- endif %}
var CONFIG_GCODE_SIZE_THRESHOLD = {{ gcodeThreshold }};
var CONFIG_GCODE_MOBILE_SIZE_THRESHOLD = {{ gcodeMobileThreshold }};
var SOCKJS_URI = window.location.protocol.slice(0, -1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + "/sockjs";
var SOCKJS_DEBUG = {% if debug -%} true; {% else %} false; {%- endif %}
@ -425,64 +427,80 @@
</div>
{% if enableGCodeVisualizer %}
<div class="tab-pane" id="gcode">
<input id="gcode_slider_layers" type="text">
<canvas id="gcode_canvas" width="568" height="568"></canvas>
<input id="gcode_slider_commands" type="text" style="width: 554px">
<div data-bind="visible: !waitForApproval()">
<input id="gcode_slider_layers" type="text">
<canvas id="gcode_canvas" width="568" height="568"></canvas>
<input id="gcode_slider_commands" type="text" style="width: 554px">
<div class="progress" >
<div class="bar" style="width: 0%;" data-bind="text: ui_progress_text, style: { width: ui_progress_percentage() + '%' }"></div>
<div class="progress" >
<div class="bar" style="width: 0%;" data-bind="text: ui_progress_text, style: { width: ui_progress_percentage() + '%' }"></div>
</div>
<div class="row-fluid">
<div class="span7">
<h1>Model info</h1>
<p data-bind="html: ui_modelInfo"></p>
<h1>Layer info</h1>
<p data-bind="html: ui_layerInfo"></p>
</div>
<div class="span5">
<h1>Renderer options</h1>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_syncProgress">Sync with job progress
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_centerViewport">Center viewport on model
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_zoomOnModel">Zoom in on model
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showMoves">Show moves
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showRetracts">Show retracts
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showPrevious">Also show previous layer
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showNext">Also show next layer
</label>
</p>
<p>
<button class="btn btn-block" data-bind="click: reload, enable: enableReload">Reload</button>
</p>
</div>
</div>
</div>
<div data-bind="visible: waitForApproval">
<h1>Warning</h1>
<div class="row-fluid">
<div class="span7">
<h1>Model info</h1>
<p data-bind="html: ui_modelInfo"></p>
<p>
You've selected <strong data-bind="text: selectedFile.name"></strong> for printing which has a size of
<strong data-bind="text: formatSize(selectedFile.size())"></strong>. Depending on your machine this
might be too large for rendering and cause your browser to become unresponsive or crash.
</p>
<h1>Layer info</h1>
<p data-bind="html: ui_layerInfo"></p>
</div>
<div class="span5">
<h1>Renderer options</h1>
<p>
Are you sure you want to visualize this file nevertheless?
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_syncProgress">Sync with job progress
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_centerModel">Center model on bed
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_centerViewport">Center viewport on model
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_zoomOnModel">Zoom in on model
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showMoves">Show moves
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showRetracts">Show retracts
</label>
</p>
<p>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showPrevious">Also show previous layer
</label>
<label class="checkbox">
<input type="checkbox" data-bind="checked: renderer_showNext">Also show next layer
</label>
</p>
<p>
<button class="btn btn-block" data-bind="click: refresh">Refresh</button>
</p>
</div>
<button class="btn btn-warning btn-block" data-bind="click: approveLargeFile">
Yes, please visualize <span data-bind="text: selectedFile.name"></span> regardless of its size
</button>
</div>
</div>
{% endif %}
@ -608,6 +626,7 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pusher.color.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
<!-- Include OctoPrint files -->
<!-- TODO: merge/minimize in the future -->