From c62a5a633945312325d71071b00245462e93e3ba Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Wed, 16 Mar 2016 06:04:39 -0700 Subject: [PATCH] Working on controller web interface --- .gitignore | 1 + Makefile | 71 ++++-- bbctrl.py | 50 +++-- inevent/EventHandler.py | 1 - inevent/JogHandler.py | 5 +- src/jade/index.jade | 125 ++++++----- src/jade/templates/admin-view.jade | 4 + src/jade/templates/axis-view.jade | 10 + src/jade/templates/config-view.jade | 14 ++ src/jade/templates/gcode-view.jade | 8 + src/jade/templates/motor-view.jade | 10 + src/jade/templates/spindle-view.jade | 8 + src/jade/templates/status-view.jade | 43 ++++ src/jade/templates/switches-view.jade | 10 + src/jade/templates/templated-input.jade | 26 +++ src/js/admin-view.js | 21 ++ src/js/app.js | 80 +++---- src/js/axis-view.js | 48 ++++ src/js/config-view.js | 44 ++++ src/js/gcode-view.js | 43 ++++ src/js/main.js | 3 + src/js/motor-view.js | 50 +++++ src/js/spindle-view.js | 43 ++++ src/js/status-view.js | 86 ++++++++ src/js/switches-view.js | 46 ++++ src/js/templated-input.js | 15 ++ src/resources/config-template.json | 207 ++++++++++++++++++ src/resources/css/side-menu-old-ie.css | 254 ++++++++++++++++++++++ src/resources/css/side-menu.css | 248 +++++++++++++++++++++ src/resources/images/buildbotics_logo.png | Bin 0 -> 3571 bytes src/resources/js/ui.js | 35 +++ src/stylus/style.styl | 49 +++++ 32 files changed, 1515 insertions(+), 143 deletions(-) create mode 100644 src/jade/templates/admin-view.jade create mode 100644 src/jade/templates/axis-view.jade create mode 100644 src/jade/templates/config-view.jade create mode 100644 src/jade/templates/gcode-view.jade create mode 100644 src/jade/templates/motor-view.jade create mode 100644 src/jade/templates/spindle-view.jade create mode 100644 src/jade/templates/status-view.jade create mode 100644 src/jade/templates/switches-view.jade create mode 100644 src/jade/templates/templated-input.jade create mode 100644 src/js/admin-view.js create mode 100644 src/js/axis-view.js create mode 100644 src/js/config-view.js create mode 100644 src/js/gcode-view.js create mode 100644 src/js/motor-view.js create mode 100644 src/js/spindle-view.js create mode 100644 src/js/status-view.js create mode 100644 src/js/switches-view.js create mode 100644 src/js/templated-input.js create mode 100644 src/resources/config-template.json create mode 100644 src/resources/css/side-menu-old-ie.css create mode 100644 src/resources/css/side-menu.css create mode 100644 src/resources/images/buildbotics_logo.png create mode 100644 src/resources/js/ui.js diff --git a/.gitignore b/.gitignore index 6f2b850..70a8c44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .sconf_temp/ .sconsign.dblite /http +/build node_modules *~ \#* diff --git a/Makefile b/Makefile index ff09db9..9e79f23 100644 --- a/Makefile +++ b/Makefile @@ -6,48 +6,50 @@ STYLUS := $(NODE_MODS)/stylus/bin/stylus AP := $(NODE_MODS)/autoprefixer/autoprefixer BROWSERIFY := $(NODE_MODS)/browserify/bin/cmd.js -HTTP := http HTML := index -HTML := $(patsubst %,$(HTTP)/%.html,$(HTML)) -CSS := style -CSS := $(patsubst %,$(HTTP)/%.css,$(CSS)) +HTML := $(patsubst %,http/%.html,$(HTML)) +CSS := $(wildcard src/stylus/*.styl) +CSS_ASSETS := build/css/style.css JS := $(wildcard src/js/*.js) -JS_ASSETS := $(HTTP)/js/assets.js -TEMPLS := $(wildcard src/jade/templates/*.jade) +JS_ASSETS := http/js/assets.js STATIC := $(shell find src/resources -type f) STATIC := $(patsubst src/resources/%,http/%,$(STATIC)) +TEMPLS := $(wildcard src/jade/templates/*.jade) ifndef DEST DEST=bbctrl/ endif -WATCH := src/jade src/stylus src/js Makefile - -TARGETS := $(HTML) $(CSS) $(JS_ASSETS) $(STATIC) +WATCH := src/jade src/jade/templates src/stylus src/js src/resources Makefile -all: node_modules $(TARGETS) +all: html css js static -copy: $(TARGETS) +copy: all cp -r *.py inevent http/ $(DEST) -$(HTTP)/admin.html: build/templates.jade +html: templates $(HTML) -$(HTTP)/%.html: src/jade/%.jade - $(JADE) -P $< --out $(shell dirname $@) || \ - (rm -f $@; exit 1) +css: $(CSS_ASSETS) $(CSS_ASSETS).sha256 + install -D $< http/css/style-$(shell cat $(CSS_ASSETS).sha256).css -$(HTTP)/%.css: src/stylus/%.styl - mkdir -p $(shell dirname $@) - $(STYLUS) < $< > $@ || (rm -f $@; exit 1) +js: $(JS_ASSETS) $(JS_ASSETS).sha256 + install -D $< http/js/assets-$(shell cat $(JS_ASSETS).sha256).js -$(HTTP)/%: src/resources/% - install -D $< $@ +static: $(STATIC) + +templates: build/templates.jade build/templates.jade: $(TEMPLS) mkdir -p build cat $(TEMPLS) >$@ -$(JS_ASSETS): $(JS) +build/hashes.jade: $(CSS_ASSETS).sha256 $(JS_ASSETS).sha256 + echo "- var css_hash = '$(shell cat $(CSS_ASSETS).sha256)'" > $@ + echo "- var js_hash = '$(shell cat $(JS_ASSETS).sha256)'" >> $@ + +http/index.html: build/templates.jade build/hashes.jade + +$(JS_ASSETS): $(JS) node_modules @mkdir -p $(shell dirname $@) $(BROWSERIFY) src/js/main.js -s main -o $@ || \ (rm -f $@; exit 1) @@ -55,6 +57,21 @@ $(JS_ASSETS): $(JS) node_modules: npm install +%.sha256: % + mkdir -p $(shell dirname $@) + sha256sum $< | sed 's/^\([a-f0-9]\+\) .*$$/\1/' > $@ + +http/%: src/resources/% + install -D $< $@ + +http/%.html: src/jade/%.jade $(wildcard src/jade/*.jade) node_modules + @mkdir -p $(shell dirname $@) + $(JADE) -P $< -o http || (rm -f $@; exit 1) + +build/css/%.css: src/stylus/%.styl node_modules + mkdir -p $(shell dirname $@) + $(STYLUS) < $< > $@ || (rm -f $@; exit 1) + watch: @clear $(MAKE) @@ -65,5 +82,13 @@ watch: $(MAKE); \ done -clean: - rm -rf $(HTTP) +tidy: + rm -f $(shell find "$(DIR)" -name \*~) + +clean: tidy + rm -rf build html + +dist-clean: clean + rm -rf node_modules + +.PHONY: all install html css static templates clean tidy diff --git a/bbctrl.py b/bbctrl.py index 623f077..daa3457 100755 --- a/bbctrl.py +++ b/bbctrl.py @@ -3,6 +3,8 @@ ## Change this to match your local settings SERIAL_PORT = '/dev/ttyAMA0' SERIAL_BAUDRATE = 115200 +HTTP_PORT = 8080 +HTTP_ADDR = '0.0.0.0' import os from tornado import web, ioloop @@ -45,7 +47,7 @@ class SerialProcess(multiprocessing.Process): def close(self): self.sp.close() - + def writeSerial(self, data): self.sp.write(data.encode()) @@ -57,12 +59,12 @@ class SerialProcess(multiprocessing.Process): def run(self): self.sp.flushInput() - + while True: # look for incoming tornado request if not self.input_queue.empty(): data = self.input_queue.get() - + # send it to the serial device self.writeSerial(data) @@ -71,14 +73,17 @@ class SerialProcess(multiprocessing.Process): data = self.readSerial() # send it back to tornado self.output_queue.put(data) - print(data.decode('utf-8')) + try: + print(data.decode('utf-8')) + except Exception as e: + print(e, data) class Connection(SockJSConnection): def on_open(self, info): clients.append(self) - self.send(state) + self.send(str.encode(json.dumps(state))) def on_close(self): @@ -114,7 +119,17 @@ class JogHandler(inevent.JogHandler): def __init__(self, config): super().__init__(config) - self.lastV = [0.0] * 4 + self.v = [0.0] * 4 + self.lastV = self.v + + + def processed_events(self): + if self.v != self.lastV: + self.lastV = self.v + + v = ["{:6.5f}".format(x) for x in self.v] + cmd = '$jog ' + ' '.join(v) + '\n' + input_queue.put(cmd) def changed(self): @@ -123,17 +138,14 @@ class JogHandler(inevent.JogHandler): if self.speed == 3: scale = 1.0 / 4.0 if self.speed == 4: scale = 1.0 - v = [x * scale for x in self.axes] + self.v = [x * scale for x in self.axes] - if v != self.lastV: - self.lastV = v - v = ["{:6.5f}".format(x) for x in v] - cmd = '$jog ' + ' '.join(v) + '\n' - input_queue.put(cmd) +def checkEvents(): + eventProcessor.process_events(eventHandler) + eventHandler.processed_events() -def checkEvents(): eventProcessor.process_events(eventHandler) eventProcessor = inevent.InEvent(types = "js kbd".split()) eventHandler = JogHandler(config) @@ -144,9 +156,12 @@ if __name__ == "__main__": logging.getLogger().setLevel(logging.DEBUG) # Start the serial worker - sp = SerialProcess(input_queue, output_queue) - sp.daemon = True - sp.start() + try: + sp = SerialProcess(input_queue, output_queue) + sp.daemon = True + sp.start() + except Exception as e: + print(e) # Adjust the interval according to frames sent by serial port ioloop.PeriodicCallback(checkQueue, 100).start() @@ -154,5 +169,6 @@ if __name__ == "__main__": # Start the web server app = web.Application(router.urls + handlers) - app.listen(8080) + app.listen(HTTP_PORT, address = HTTP_ADDR) + print('Listening on http://{}:{}/'.format(HTTP_ADDR, HTTP_PORT)) ioloop.IOLoop.instance().start() diff --git a/inevent/EventHandler.py b/inevent/EventHandler.py index 7a93a35..97642503 100644 --- a/inevent/EventHandler.py +++ b/inevent/EventHandler.py @@ -117,4 +117,3 @@ class EventHandler(object): if self.buttons[k] != 0: k_list.append(k) return k_list - diff --git a/inevent/JogHandler.py b/inevent/JogHandler.py index 5ab3fb6..f14b55d 100644 --- a/inevent/JogHandler.py +++ b/inevent/JogHandler.py @@ -26,7 +26,7 @@ class JogHandler: def __init__(self, config): self.config = config self.axes = [0.0, 0.0, 0.0, 0.0] - self.speed = 1 + self.speed = 3 self.activate = 0 @@ -74,7 +74,8 @@ class JogHandler: self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]] if abs(self.axes[axis]) < self.config['deadband']: self.axes[axis] = 0 - if not (1 << axis) & self.activate: self.axes[axis] = 0 + if not (1 << axis) & self.activate and self.activate: + self.axes[axis] = 0 if old_axes != self.axes: changed = True diff --git a/src/jade/index.jade b/src/jade/index.jade index 1118b18..6431dd6 100644 --- a/src/jade/index.jade +++ b/src/jade/index.jade @@ -1,65 +1,90 @@ +include ../../build/hashes.jade + + doctype html html(lang="en") head - title Buildbotics Controller - Web interface meta(charset="utf-8") + meta(name="viewport", content="width=device-width, initial-scale=1.0") - script(src="//code.jquery.com/jquery-1.11.3.min.js") - script(src="//cdn.jsdelivr.net/vue/1.0.13/vue.min.js") - script(src="js/sockjs.min.js") - script(src="js/gauge.min.js") - script(src="js/fd-slider.min.js") - script(src="js/assets.js") + title Buildbotics Controller - Web interface + + link(rel="stylesheet", + href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css") + //if lte IE 8 + link(rel="stylesheet", href="css/side-menu-old-ie.css") + // [if gt IE 8] Loading...'}, + 'status-view': require('./status-view'), + 'axis-view': require('./axis-view'), + 'motor-view': require('./motor-view'), + 'spindle-view': require('./spindle-view'), + 'switches-view': require('./switches-view'), + 'gcode-view': require('./gcode-view'), + 'admin-view': require('./admin-view') }, - watch: { - currentx: function (value) {this.current('x', value);}, - currenty: function (value) {this.current('y', value);}, - currentz: function (value) {this.current('z', value);}, - currenta: function (value) {this.current('a', value);} + events: { + 'config-changed': function () {this.modified = true;} }, ready: function () { - this.sock = new SockJS('//' + window.location.host + '/ws'); - - this.sock.onmessage = function (e) { - var data = e.data; - console.debug(data); + $.get('/config-template.json').success(function (data, status, xhr) { + this.template = data; - for (var key in data) { - this.$set('state.' + key, data[key]); + $.get('/default-config.json').success(function (data, status, xhr) { + this.config = data; - for (var axis of ['x', 'y', 'z', 'a']) - if (key == 'dcur' + axis && typeof this.$get('current' + axis) == 'undefined') - this.$set('current' + axis, (32 * data[key]).toFixed()); - } - }.bind(this); + this.parse_hash(); + $(window).on('hashchange', this.parse_hash); + }.bind(this)) + }.bind(this)) }, methods: { - send: function (data) { - this.sock.send(JSON.stringify(data)); - }, + parse_hash: function () { + var hash = location.hash.substr(1); + var parts = hash.split(':'); + if (parts.length == 2) this.index = parts[1]; - jog: function (axis, dir) { - var pos = this.state['pos' + axis] + dir * this.step; - this.sock.send('g0' + axis + pos); + this.currentView = parts[0]; }, - current: function (axis, value) { - var x = value / 32.0; - if (this.state['dcur' + axis] == x) return; - - var data = {}; - data['dcur' + axis] = x; - this.send(data); - } - }, - - - filters: { - percent: function (value, precision) { - if (typeof precision == 'undefined') precision = 2; - return (value * 100.0).toFixed(precision) + '%'; + save: function () { + this.modified = false; } } }) diff --git a/src/js/axis-view.js b/src/js/axis-view.js new file mode 100644 index 0000000..a50a385 --- /dev/null +++ b/src/js/axis-view.js @@ -0,0 +1,48 @@ +'use strict' + + +module.exports = { + template: '#axis-view-template', + props: ['index', 'config', 'template'], + + + data: function () { + return {axis: {}} + }, + + + watch: { + index: function() {this.update();} + }, + + + events: { + 'input-changed': function() { + this.$dispatch('config-changed'); + return false; + } + }, + + + ready: function () { + this.update(); + }, + + + methods: { + update: function () { + Vue.nextTick(function () { + if (this.config.hasOwnProperty('axes')) + this.axis = this.config.axes[this.index]; + else this.axes = {}; + + var template = this.template.axes; + for (var category in template) + for (var key in template[category]) + if (!this.axis.hasOwnProperty(key)) + this.$set('axis["' + key + '"]', + template[category][key].default); + }.bind(this)); + } + } +} diff --git a/src/js/config-view.js b/src/js/config-view.js new file mode 100644 index 0000000..0a8ea27 --- /dev/null +++ b/src/js/config-view.js @@ -0,0 +1,44 @@ +'use strict' + + +module.exports = { + template: '#config-view-template', + + + data: function () { + return { + page: 'motor', + motor: 0, + template: {}, + config: {"motors": [{}]} + } + }, + + + components: { + 'motor-view': require('./motor-view'), + 'switch-view': require('./switch-view') + }, + + + ready: function () { + $.get('/config-template.json').success(function (data, status, xhr) { + this.template = data; + + $.get('/default-config.json').success(function (data, status, xhr) { + this.config = data; + }.bind(this)) + }.bind(this)) + }, + + + methods: { + back: function() { + if (this.motor) this.motor--; + }, + + next: function () { + if (this.motor < this.config.motors.length - 1) this.motor++; + } + } +} diff --git a/src/js/gcode-view.js b/src/js/gcode-view.js new file mode 100644 index 0000000..9e95a46 --- /dev/null +++ b/src/js/gcode-view.js @@ -0,0 +1,43 @@ +'use strict' + + +module.exports = { + template: '#gcode-view-template', + props: ['config', 'template'], + + + data: function () { + return { + gcode: {} + } + }, + + + events: { + 'input-changed': function() { + this.$dispatch('config-changed'); + return false; + } + }, + + + ready: function () { + this.update(); + }, + + + methods: { + update: function () { + Vue.nextTick(function () { + if (this.config.hasOwnProperty('gcode')) + this.gcode = this.config.gcode; + + var template = this.template.gcode; + for (var key in template) + if (!this.gcode.hasOwnProperty(key)) + this.$set('gcode["' + key + '"]', + template[key].default); + }.bind(this)); + } + } +} diff --git a/src/js/main.js b/src/js/main.js index 8cff07a..4e3ba2a 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -3,6 +3,9 @@ $(function() { Vue.config.debug = true; Vue.util.warn = function (msg) {console.debug('[Vue warn]: ' + msg)} + // Register global components + Vue.component('templated-input', require('./templated-input')); + // Vue app require('./app'); }); diff --git a/src/js/motor-view.js b/src/js/motor-view.js new file mode 100644 index 0000000..2237ff5 --- /dev/null +++ b/src/js/motor-view.js @@ -0,0 +1,50 @@ +'use strict' + + +module.exports = { + template: '#motor-view-template', + props: ['index', 'config', 'template'], + + + data: function () { + return { + motor: {} + } + }, + + + watch: { + index: function() {this.update();} + }, + + + events: { + 'input-changed': function() { + this.$dispatch('config-changed'); + return false; + } + }, + + + ready: function () { + this.update(); + }, + + + methods: { + update: function () { + Vue.nextTick(function () { + if (this.config.hasOwnProperty('motors')) + this.motor = this.config.motors[this.index]; + else this.motor = {}; + + var template = this.template.motors; + for (var category in template) + for (var key in template[category]) + if (!this.motor.hasOwnProperty(key)) + this.$set('motor["' + key + '"]', + template[category][key].default); + }.bind(this)); + } + } +} diff --git a/src/js/spindle-view.js b/src/js/spindle-view.js new file mode 100644 index 0000000..2423ff1 --- /dev/null +++ b/src/js/spindle-view.js @@ -0,0 +1,43 @@ +'use strict' + + +module.exports = { + template: '#spindle-view-template', + props: ['config', 'template'], + + + data: function () { + return { + spindle: {} + } + }, + + + events: { + 'input-changed': function() { + this.$dispatch('config-changed'); + return false; + } + }, + + + ready: function () { + this.update(); + }, + + + methods: { + update: function () { + Vue.nextTick(function () { + if (this.config.hasOwnProperty('spindle')) + this.spindle = this.config.spindle; + + var template = this.template.spindle; + for (var key in template) + if (!this.spindle.hasOwnProperty(key)) + this.$set('spindle["' + key + '"]', + template[key].default); + }.bind(this)); + } + } +} diff --git a/src/js/status-view.js b/src/js/status-view.js new file mode 100644 index 0000000..1cc7bef --- /dev/null +++ b/src/js/status-view.js @@ -0,0 +1,86 @@ +'use strict' + + +function is_array(x) { + return Object.prototype.toString.call(x) === '[object Array]'; +} + + +module.exports = { + template: '#status-view-template', + + + data: function () { + return { + axes: 'xyza', + state: { + dcurx: 1, dcury: 1, dcurz: 1, dcura: 1 + //sguardx: 1, sguardy: 1, sguardz: 1, sguarda: 1 + }, + step: 10 + } + }, + + + components: { + gauge: require('./gauge') + }, + + + watch: { + currentx: function (value) {this.current('x', value);}, + currenty: function (value) {this.current('y', value);}, + currentz: function (value) {this.current('z', value);}, + currenta: function (value) {this.current('a', value);} + }, + + + ready: function () { + this.sock = new SockJS('//' + window.location.host + '/ws'); + + this.sock.onmessage = function (e) { + var data = e.data; + console.debug(data); + + for (var key in data) { + this.$set('state.' + key, data[key]); + + for (var axis of ['x', 'y', 'z', 'a']) + if (key == 'dcur' + axis && + typeof this.$get('current' + axis) == 'undefined') + this.$set('current' + axis, (32 * data[key]).toFixed()); + } + }.bind(this); + }, + + + methods: { + send: function (data) { + this.sock.send(JSON.stringify(data)); + }, + + + jog: function (axis, dir) { + var pos = this.state['pos' + axis] + dir * this.step; + this.sock.send('g0' + axis + pos); + }, + + + current: function (axis, value) { + var x = value / 32.0; + if (this.state['dcur' + axis] == x) return; + + var data = {}; + data['dcur' + axis] = x; + this.send(data); + } + }, + + + filters: { + percent: function (value, precision) { + if (typeof precision == 'undefined') precision = 2; + return (value * 100.0).toFixed(precision) + '%'; + } + } +} diff --git a/src/js/switches-view.js b/src/js/switches-view.js new file mode 100644 index 0000000..1d4c554 --- /dev/null +++ b/src/js/switches-view.js @@ -0,0 +1,46 @@ +'use strict' + + +module.exports = { + template: '#switches-view-template', + props: ['config', 'template'], + + + data: function () { + return { + 'switches': [] + } + }, + + + events: { + 'input-changed': function() { + this.$dispatch('config-changed'); + return false; + } + }, + + + ready: function () { + this.update(); + }, + + + methods: { + update: function () { + Vue.nextTick(function () { + if (this.config.hasOwnProperty('switches')) + this.switches = this.config.switches; + else this.switches = []; + + for (var i = 0; i < this.switches.length; i++) { + var template = this.template.switches; + for (var key in template) + if (!this.switches[i].hasOwnProperty(key)) + this.$set('switches[' + i + ']["' + key + '"]', + template[key].default); + } + }.bind(this)); + } + } +} diff --git a/src/js/templated-input.js b/src/js/templated-input.js new file mode 100644 index 0000000..8454882 --- /dev/null +++ b/src/js/templated-input.js @@ -0,0 +1,15 @@ +'use strict' + + +module.exports = { + replace: true, + template: '#templated-input-template', + props: ['name', 'model', 'template'], + + + methods: { + change: function () { + this.$dispatch('input-changed'); + } + } +} diff --git a/src/resources/config-template.json b/src/resources/config-template.json new file mode 100644 index 0000000..35f3f00 --- /dev/null +++ b/src/resources/config-template.json @@ -0,0 +1,207 @@ +{ + "axes": { + "motion": { + "mode": { + "type": "enum", + "values": ["standard", "radius", "disabled"], + "default": "disabled" + }, + "max-velocity": { + "type": "float", + "min": 0, + "unit": "mm/min", + "default": 16000 + }, + "max-feedrate": { + "type": "float", + "min": 0, "unit": + "mm/min", + "default": 16000 + }, + "max-jerk": { + "type": "float", + "min": 0, + "unit": "km/min^3", + "default": 20 + }, + "junction-deviation": { + "type": "float", + "min": 0, + "unit": "mm", + "default": 0.05 + } + }, + + "limits": { + "min-soft-limit": { + "type": "float", + "unit": "mm", + "default": 0 + }, + "max-soft-limit": { + "type": "float", + "unit": "mm", + "default": 150 + }, + "min-switch": { + "type": "int", + "unit": "id", + "min": 0, + "max": 8, + "default": 0 + }, + "max-switch": { + "type": "int", + "unit": "id", + "min": 0, + "max": 8, + "default": 0 + } + }, + + "homing": { + "max-homing-jerk": { + "type": "float", + "min": 0, + "unit": "km/min^3", + "default": 40 + }, + "search-velocity": { + "type": "float", + "min": 0, + "unit": "mm/min", + "default": 500 + }, + "latch-velocity": { + "type": "float", + "min": 0, + "unit": "mm/min", + "default": 100 + }, + "latch-backoff": { + "type": "float", + "min": 0, + "unit": "mm", + "default": 5 + }, + "zero-backoff": { + "type": "float", + "min": 0, + "unit": "mm", + "default": 1 + } + } + }, + + "motors": { + "general": { + "axis-mapping": { + "type": "enum", + "values": ["x", "y", "z", "a", "b", "c"], + "default": "x" + }, + "step-angle": { + "type": "float", + "unit": "degrees", + "default": 1.8 + }, + "travel-per-rev": { + "type": "float", + "unit": "mm", + "default": 3.175 + }, + "microsteps": { + "type": "int", + "values": [1, 2, 4, 8, 16, 32, 64, 128, 256], + "unit": "per full step", + "default": 16 + }, + "polarity": { + "type": "enum", + "values": ["normal", "reversed"], + "default": "normal" + } + }, + + "power": { + "power-mode": { + "type": "enum", + "values": ["always-on", "in-cycle", "when-moving"], + "default": "in-cycle" + }, + "power-level": { + "type": "float", + "min": 0, + "max": 100, + "unit": "%", + "default": 80 + }, + "stall-guard": { + "type": "float", + "min": 0, + "max": 100, + "unit": "%", + "default": 70 + }, + "cool-step": { + "type": "bool", + "default": true + } + } + }, + + "spindle": { + "max-speed": { + "type": "float", + "unit": "RPM", + "min": 0, + "default": 10000 + }, + "type": { + "type": "enum", + "values": ["RS485", "PWM"], + "default": "RS485" + }, + "min-pulse-width": { + "type": "float", + "unit": "ms", + "default": 20 + }, + "max-pulse-width": { + "type": "float", + "unit": "ms", + "default": 100 + }, + "polarity": { + "type": "enum", + "values": ["normal", "reversed"], + "default": "normal" + }, + "ramp-up-velocity": { + "type": "float", + "unit": "rev/min^2", + "min": 0, + "default": 48000 + }, + "ramp-down-velocity": { + "type": "float", + "unit": "rev/min^2", + "min": 0, + "default": 48000 + } + }, + + "switches": { + "type": { + "type": "enum", + "values": ["normally-open", "normally-closed"], + "default": "normally-closed" + } + }, + + "gcode": { + "preamble": {"type": "text"}, + "tool-change": {"type": "text"}, + "epilogue": {"type": "text"} + } +} diff --git a/src/resources/css/side-menu-old-ie.css b/src/resources/css/side-menu-old-ie.css new file mode 100644 index 0000000..0b0e738 --- /dev/null +++ b/src/resources/css/side-menu-old-ie.css @@ -0,0 +1,254 @@ +body { + color: #777; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* +Add transition to containers so they can push in and out. +*/ + +#layout, +#menu, +.menu-link { + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* +This is the parent `
` that contains the menu and the content area. +*/ + +#layout { + position: relative; + padding-left: 0; +} + +#layout.active #menu { + left: 150px; + width: 150px; +} + +#layout.active .menu-link { + left: 150px; +} + +/* +The content `
` is where all your content goes. +*/ + +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; +} + +.header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; +} + +.header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; +} + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + +/* +The `#menu` `
` is the parent `
` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; + /* "#menu" width */ + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; + /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +/* + All anchors inside the menu should be styled like this. + */ + +#menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; +} + +/* + Remove all background/borders, since we are applying them to #menu. + */ + +#menu .pure-menu, +#menu .pure-menu ul { + border: none; + background: transparent; +} + +/* + Add that light border to separate items into groups. + */ + +#menu .pure-menu ul, +#menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; +} + +/* + Change color of the anchor links on hover/focus. + */ + +#menu .pure-menu li a:hover, +#menu .pure-menu li a:focus { + background: #333; +} + +/* + This styles the selected menu item `
  • `. + */ + +#menu .pure-menu-selected, +#menu .pure-menu-heading { + background: #1f8dd6; +} + +/* + This styles a link within a selected menu item `
  • `. + */ + +#menu .pure-menu-selected a { + color: #fff; +} + +/* + This styles the menu heading. + */ + +#menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; +} + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ + +.menu-link { + position: fixed; + display: block; + /* show this only on small screens */ + top: 0; + left: 0; + /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; + /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + +.menu-link:hover, +.menu-link:focus { + background: #000; +} + +.menu-link span { + position: relative; + display: block; +} + +.menu-link span, +.menu-link span:before, +.menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; +} + +.menu-link span:before, +.menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; +} + +.menu-link span:after { + margin-top: 0.6em; +} + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ + +.header, +.content { + padding-left: 2em; + padding-right: 2em; +} + +#layout { + padding-left: 150px; + /* left col width "#menu" */ + left: 0; +} + +#menu { + left: 150px; +} + +.menu-link { + position: fixed; + left: 150px; + display: none; +} + +#layout.active .menu-link { + left: 150px; +} \ No newline at end of file diff --git a/src/resources/css/side-menu.css b/src/resources/css/side-menu.css new file mode 100644 index 0000000..b5ce7ae --- /dev/null +++ b/src/resources/css/side-menu.css @@ -0,0 +1,248 @@ +body { + color: #777; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +/* +Add transition to containers so they can push in and out. +*/ +#layout, +#menu, +.menu-link { + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} + +/* +This is the parent `
    ` that contains the menu and the content area. +*/ +#layout { + position: relative; + padding-left: 0; +} + #layout.active #menu { + left: 150px; + width: 150px; + } + + #layout.active .menu-link { + left: 150px; + } +/* +The content `
    ` is where all your content goes. +*/ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; + } + .header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; + } + .header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; + } + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + + + +/* +The `#menu` `
    ` is the parent `
    ` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; /* "#menu" width */ + width: 150px; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + /* + All anchors inside the menu should be styled like this. + */ + #menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; + } + + /* + Remove all background/borders, since we are applying them to #menu. + */ + #menu .pure-menu, + #menu .pure-menu ul { + border: none; + background: transparent; + } + + /* + Add that light border to separate items into groups. + */ + #menu .pure-menu ul, + #menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; + } + /* + Change color of the anchor links on hover/focus. + */ + #menu .pure-menu li a:hover, + #menu .pure-menu li a:focus { + background: #333; + } + + /* + This styles the selected menu item `
  • `. + */ + #menu .pure-menu-selected, + #menu .pure-menu-heading { + background: #1f8dd6; + } + /* + This styles a link within a selected menu item `
  • `. + */ + #menu .pure-menu-selected a { + color: #fff; + } + + /* + This styles the menu heading. + */ + #menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + } + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ +.menu-link { + position: fixed; + display: block; /* show this only on small screens */ + top: 0; + left: 0; /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + + .menu-link:hover, + .menu-link:focus { + background: #000; + } + + .menu-link span { + position: relative; + display: block; + } + + .menu-link span, + .menu-link span:before, + .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; + } + + .menu-link span:before, + .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; + } + + .menu-link span:after { + margin-top: 0.6em; + } + + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ +@media (min-width: 48em) { + + .header, + .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 48em) { + /* Only apply this when the window is small. Otherwise, the following + case results in extra padding on the left: + * Make the window small. + * Tap the menu to trigger the active state. + * Make the window large again. + */ + #layout.active { + position: relative; + left: 150px; + } +} + diff --git a/src/resources/images/buildbotics_logo.png b/src/resources/images/buildbotics_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c6026d745f7775974dfd2adc809e5e0c5df21f34 GIT binary patch literal 3571 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 tg^yj#H|dhxFmZ{i2x2f8c`4n;{{zyHSsThSB~$