Added button to download current GCode file, fixed file handling problems
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Tue, 4 Dec 2018 23:09:25 +0000 (15:09 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Tue, 4 Dec 2018 23:09:25 +0000 (15:09 -0800)
CHANGELOG.md
src/js/app.js
src/js/control-view.js
src/js/gcode-viewer.js
src/js/path-viewer.js
src/pug/templates/control-view.pug
src/py/bbctrl/FileHandler.py
src/py/bbctrl/State.py
src/py/bbctrl/Web.py
src/stylus/style.styl

index 5a3ad263c7fef7a275430caf43ade1d71f5fad64..69c3ff56172b17f10533f5d6f92439ca5e503412 100644 (file)
@@ -7,6 +7,7 @@ Buildbotics CNC Controller Firmware Changelog
  - Show simulation progress with or with out 3D view.
  - Synchronize file list between browsers.
  - Increased max simulation time to 24hrs.
+ - Added button to download current GCode file.
 
 ## v0.4.2
  - Suppress ``Auto-creating missing tool`` warning.
index c8f4e5ed6d2976d8c5f57872dc1f97b1cdf5bb25..8ba50dccc7d4ec771966d1b3e9a82433755ee31a 100644 (file)
@@ -48,6 +48,14 @@ function compare_versions(a, b) {
 
 
 function is_object(o) {return o !== null && typeof o == 'object'}
+function is_array(o) {return Array.isArray(o)}
+
+
+function update_array(dst, src) {
+  while (dst.length) dst.pop()
+  for (var i = 0; i < src.length; i++)
+    Vue.set(dst, i, src[i]);
+}
 
 
 function update_object(dst, src, remove) {
@@ -68,7 +76,10 @@ function update_object(dst, src, remove) {
     key = props[index];
     value = src[key];
 
-    if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
+    if (is_array(value) && dst.hasOwnProperty(key) && is_array(dst[key]))
+      update_array(dst[key], value);
+
+    else if (is_object(value) && dst.hasOwnProperty(key) && is_object(dst[key]))
       update_object(dst[key], value, remove);
 
     else Vue.set(dst, key, value);
index aaead5904657c19698538a0390f5fe990a556124..0c48b66c103f74c79bec672539742a9bb1207c30 100644 (file)
@@ -97,7 +97,9 @@ module.exports = {
     },
 
 
-    'state.selected': function () {this.load()}
+    'state.selected': function () {this.load()},
+    'state.files': function () {
+      console.log('Files changed: ' + JSON.stringify(this.state.files))}
   },
 
 
@@ -199,11 +201,10 @@ module.exports = {
 
     load: function () {
       var file = this.state.selected;
-      if (this.last_file == file || typeof file == 'undefined' ||
-          typeof file == 'null') return;
+      if (this.last_file == file) return;
       this.last_file = file;
 
-      if (typeof file != 'undefined') this.$broadcast('gcode-load', file);
+      this.$broadcast('gcode-load', file);
       this.$broadcast('gcode-line', this.state.line);
       this.toolpath_progress = 0;
       this.load_toolpath(file);
@@ -213,7 +214,7 @@ module.exports = {
     load_toolpath: function (file) {
       this.toolpath = {};
 
-      if (typeof file == 'undefined' || typeof file == 'null') return;
+      if (!file) return;
 
       api.get('path/' + file).done(function (toolpath) {
         if (this.last_file != file) return;
index 5f0716cd1b22216f1b1ee493bf56f90347e15f1a..028de4d4fe4c1104df8c19a0338eb8e8d0d80b56 100644 (file)
@@ -67,7 +67,7 @@ module.exports = {
       rows: [],
       scrollElem: $(this.$el).find('.clusterize-scroll')[0],
       contentElem: $(this.$el).find('.clusterize-content')[0],
-      no_data_text: 'Loading GCode...',
+      no_data_text: 'GCode viewer...',
       callbacks: {clusterChanged: this.highlight}
     });
   },
@@ -81,11 +81,12 @@ module.exports = {
 
   methods: {
     load: function (file) {
-      if (file == this.file || typeof file == 'undefined' ||
-          typeof file == 'null') return;
+      if (file == this.file) return;
       this.clear();
       this.file = file;
 
+      if (!file) return;
+
       var xhr = new XMLHttpRequest();
       xhr.open('GET', '/api/file/' + file + '?' + Math.random(), true);
       xhr.responseType = 'text';
index 30daea2c252be8ed94261669fff747073881c72f..2c5bca625a021bf3b6356873a711df54c8634489 100644 (file)
@@ -122,7 +122,11 @@ module.exports = {
 
   methods: {
     update: function () {
-      if (!this.toolpath.filename && !this.loading) {
+      if (!this.state.selected) {
+        this.dirty = true;
+        this.scene = new THREE.Scene();
+
+      } else if (!this.toolpath.filename && !this.loading) {
         this.loading = true;
         this.dirty = true;
         this.draw_loading();
index 3f4b116dca7fff6ad7fff9a53e0f0301d04e0105..9876e0deb67122262395c50fbac49eac87b0ed0d 100644 (file)
@@ -232,6 +232,11 @@ script#control-view-template(type="text/x-template")
             input(type="file", @change="upload", :disabled="!is_ready",
               accept="text/*,.nc,.gcode,.gc,.ngc,.txt,.tap,.cnc")
 
+          a.pure-button(:disabled="!state.selected", download,
+            :href="'/api/file/' + state.selected",
+            title="Download the selected GCode program.")
+            .fa.fa-download
+
           button.pure-button(title="Delete current GCode program.",
             @click="deleteGCode = true",
             :disabled="!state.selected || !is_ready")
@@ -253,9 +258,12 @@ script#control-view-template(type="text/x-template")
             v-model="state.selected", @change="load", :disabled="!is_ready")
             option(v-for="file in state.files", :value="file") {{file}}
 
-          .progress(v-if="toolpath_progress && toolpath_progress < 1")
+          .progress(v-if="toolpath_progress && toolpath_progress < 1",
+            title="Simulating GCode to check for errors, calculate ETA and " +
+              "generate 3D view.  You can run GCode before the simulation " +
+              "finishes.")
             div(:style="'width:' + (toolpath_progress || 0) * 100 + '%'")
-              label Simulating run {{(toolpath_progress || 0) | percent}}
+              label Simulating {{(toolpath_progress || 0) | percent}}
 
         path-viewer(:toolpath="toolpath", :state="state", :config="config")
         gcode-viewer
index 9596b08f0042c1f4892a5e8253511cd1c2ccbd0a..52cf476a39679b240ab4b76bfec653ad945dee43 100644 (file)
@@ -31,6 +31,7 @@ import glob
 import html
 import logging
 from tornado import gen
+from tornado.web import HTTPError
 
 
 log = logging.getLogger('FileHandler')
@@ -55,30 +56,30 @@ class FileHandler(bbctrl.APIHandler):
 
         else:
             # Delete a single file
-            safe_remove('upload' + filename)
+            filename = os.path.basename(filename)
+            safe_remove('upload/' + filename)
             self.ctrl.preplanner.delete_plans(filename)
             self.ctrl.state.remove_file(filename)
 
 
-    def put_ok(self, path):
+    def put_ok(self, *args):
         gcode = self.request.files['gcode'][0]
-        filename = gcode['filename']
+        filename = os.path.basename(gcode['filename'])
 
         if not os.path.exists('upload'): os.mkdir('upload')
 
-        path ='upload/' + filename
-
-        with open(path, 'wb') as f:
+        with open('upload/' + filename, 'wb') as f:
             f.write(gcode['body'])
 
         self.ctrl.preplanner.invalidate(filename)
         self.ctrl.state.add_file(filename)
-        log.info('GCode updated: ' + filename)
+        log.info('GCode received: ' + filename)
 
 
     @gen.coroutine
     def get(self, filename):
-        filename = filename[1:] # Remove /
+        if not filename: raise HTTPError(400, 'Missing filename')
+        filename = os.path.basename(filename)
 
         with open('upload/' + filename, 'r') as f:
             self.write(f.read())
index c3cb560abf35a004eb80801549f128ee865c2a61..d00b32452137de28eb656f73b07d8cd4c96bc6fb 100644 (file)
@@ -111,24 +111,26 @@ class State(object):
     def clear_files(self):
         self.select_file('')
         self.files = []
-        self.set('files', self.files)
+        self.changes['files'] = self.files
 
 
     def add_file(self, filename):
-        if filename not in self.files:
+        if not filename in self.files:
             self.files.append(filename)
             self.files.sort()
-            self.set('files', self.files)
+            self.changes['files'] = self.files
 
         self.select_file(filename)
 
 
     def remove_file(self, filename):
-        if self.get('selected', '') == filename: self.select_file('')
-
         if filename in self.files:
             self.files.remove(filename)
-            self.set('files', self.files)
+            self.changes['files'] = self.files
+
+        if self.get('selected', filename) == filename:
+            if len(self.files): self.select_file(self.files[0])
+            else: self.select_file('')
 
 
     def select_file(self, filename): self.set('selected', filename)
index f29c28f72a61ca0a9a58c96152238dad5ecddc30..ee24ea0a93ab79021e1747e6c90cf923973b5c3d 100644 (file)
@@ -467,7 +467,7 @@ class Web(tornado.web.Application):
             (r'/api/config/reset', ConfigResetHandler),
             (r'/api/firmware/update', FirmwareUpdateHandler),
             (r'/api/upgrade', UpgradeHandler),
-            (r'/api/file(/[^/]+)', bbctrl.FileHandler),
+            (r'/api/file(/[^/]+)?', bbctrl.FileHandler),
             (r'/api/path/([^/]+)((/positions)|(/speeds))?', PathHandler),
             (r'/api/home(/[xyzabcXYZABC]((/set)|(/clear))?)?', HomeHandler),
             (r'/api/start', StartHandler),
index 44f5776e648ebf7aa18fbbe3e6567b485eeee0e9..a57d997320dd4b5349f078a8b6a1cf3d5f8fc1b4 100644 (file)
@@ -189,7 +189,7 @@ span.unit
         height 12em
 
       > select, > input:not([type=checkbox])
-        min-width 13em
+        min-width 11em
 
       > tt
         min-width 15.25em
@@ -404,10 +404,10 @@ span.unit
     clear both
 
     > *
-      margin 0.25em
+      margin 0.25em 0.125em
 
     select
-      max-width 13em
+      max-width 11em
 
     .progress
       display inline-block
@@ -415,7 +415,7 @@ span.unit
       line-height 2em
       border 1px solid #aaa
       border-radius 3px
-      width 327px
+      width 300px
       vertical-align middle
       text-align center