diff --git a/README.md b/README.md index b541626..7dd80ab 100644 --- a/README.md +++ b/README.md @@ -68,162 +68,9 @@ See `run --help` for further information. Configuration ------------- -If not specified via the commandline, the configfile `config.yaml` for OctoPrint is expected in its settings folder, -which is located at `~/.octoprint` on Linux, at `%APPDATA%/OctoPrint` on Windows and at -`~/Library/Application Support/OctoPrint` on MacOS. +If not specified via the commandline, the configfile `config.yaml` for OctoPrint is expected in the settings folder, which is located at ~/.octoprint on Linux, at %APPDATA%/OctoPrint on Windows and at ~/Library/Application Support/OctoPrint on MacOS. -The following example config should explain the available options, most of which can also be configured via the -settings dialog within OctoPrint: - - # Use the following settings to configure the serial connection to the printer - serial: - # Use the following option to define the default serial port, defaults to unset (= AUTO) - port: /dev/ttyACM0 - - # Use the following option to define the default baudrate, defaults to unset (= AUTO) - baudrate: 115200 - - # Use the following settings to configure the web server - server: - # Use this option to define the host to which to bind the server, defaults to "0.0.0.0" (= all interfaces) - host: 0.0.0.0 - - # Use this option to define the port to which to bind the server, defaults to 5000 - port: 5000 - - # Use the following settings to configure webcam support - webcam: - # Use this option to enable display of a webcam stream in the UI, e.g. via MJPG-Streamer. - # Webcam support will be disabled if not set - stream: http://:/?action=stream - - # Use this option to enable timelapse support via snapshot, e.g. via MJPG-Streamer. - # Timelapse support will be disabled if not set - snapshot: http://:/?action=snapshot - - # Path to ffmpeg binary to use for creating timelapse recordings. - # Timelapse support will be disabled if not set - ffmpeg: /path/to/ffmpeg - - # The bitrate to use for rendering the timelapse video. This gets directly passed to ffmpeg. - bitrate: 5000k - - # Use the following settings to enable or disable OctoPrint features - feature: - # Whether to enable the gcode viewer in the UI or not - gCodeVisualizer: true - - # Specified whether OctoPrint should wait for the start response from the printer before trying to send commands - # during connect - waitForStartOnConnect: false - - # Use the following settings to set custom paths for folders used by OctoPrint - folder: - # Absolute path where to store gcode uploads. Defaults to the uploads folder in the OctoPrint settings folder - uploads: /path/to/upload/folder - - # Absolute path where to store finished timelapse recordings. Defaults to the timelapse folder in the OctoPrint - # settings dir - timelapse: /path/to/timelapse/folder - - # Absolute path where to store temporary timelapse files. Defaults to the timelapse/tmp folder in the OctoPrint - # settings dir - timelapse_tmp: /path/to/timelapse/tmp/folder - - # Absolute path where to store log files. Defaults to the logs folder in the OctoPrint settings dir - logs: /path/to/logs/folder - - # Use the following settings to configure temperature profiles which will be displayed in the temperature tab. - temperature: - profiles: - - name: ABS - extruder: 210 - bed: 100 - - name: PLA - extruder: 180 - bed: 60 - - # Use the following settings to configure printer parameters - printerParameters: - # Use this to define the movement speed on X, Y, Z and E to use for the controls on the controls tab - movementSpeed: - x: 6000 - y: 6000 - z: 200 - e: 300 - - # Use the following settings to tweak OctoPrint's appearance a bit to better distinguish multiple instances/printers - appearance: - # Use this to give your printer a name. It will be displayed in the title bar (as " [OctoPrint]") and in the - # navigation bar (as "OctoPrint: ") - name: My Printer Model - - # Use this to color the navigation bar. Supported colors are red, orange, yellow, green, blue, violet and default. - color: blue - - # Use the following settings to add custom controls to the "Controls" tab within OctoPrint - # - # Controls consist at least of a name, a type and type-specific further attributes. Currently recognized types are - # - section: Creates a visual section in the UI, you can use this to separate functional blocks - # - command: Creates a button that sends a defined GCODE command to the printer when clicked - # - commands: Creates a button that sends multiple defined GCODE commands to the printer when clicked - # - parametric_command: Creates a button that sends a parameterized GCODE command to the printer, parameters - # needed for the command are added to the UI as input fields, are named and can such be referenced from the command - # - parametric_commands: Like parametric_command, but supports multiple commands - # - # The following example defines a control for enabling the cooling fan with a variable speed defined by the user - # (default 255) and a control for disabling the fan, all within a section named "Fan", and two example controls with - # multiple commands in a section "Example for multiple commands". - controls: - - name: Fan - type: section - children: - - name: Enable Fan - type: parametric_command - command: M106 S%(speed)s - input: - - name: Speed (0-255) - parameter: speed - default: 255 - - name: Disable Fan - type: command - command: M107 - - name: Example for multiple commands - type: section - children: - - name: Move X (static) - type: commands - commands: - - G91 - - G1 X10 F3000 - - G90 - - name: Move X (parametric) - type: parametric_commands - commands: - - G91 - - G1 X%(distance)s F%(speed)s - - G90 - input: - - default: 10 - name: Distance - parameter: distance - - default: 3000 - name: Speed - parameter: speed - - # Use the following settings to add custom system commands to the "System" dropdown within OctoPrint's top bar - # - # Commands consist of a name, an action identifier, the commandline to execute and an optional confirmation message - # to display before actually executing the command (should be set to False if a confirmation dialog is not desired). - # - # The following example defines a command for shutting down the system under Linux. It assumes that the user under - # which OctoPrint is running is allowed to do this without password entry. - system: - actions: - - name: Shutdown - action: shutdown - command: sudo shutdown -h now - confirm: You are about to shutdown the system. +A comprehensive overview of all available configuration settings can be found [on the wiki](https://github.com/foosel/OctoPrint/wiki/Configuration). Setup on a Raspberry Pi running Raspbian ---------------------------------------- @@ -234,7 +81,7 @@ Credits ------- OctoPrint started out as a fork of Cura (https://github.com/daid/Cura) for adding a web interface to its -printing functionality and was originally named Printer WebUI. It still uses Cura's communication code for talking to +printing functionality and was originally named "Printer WebUI". It still uses Cura's communication code for talking to the printer, but has been reorganized to only include those parts of Cura necessary for its targeted use case. It also uses the following libraries and frameworks for backend and frontend: @@ -246,6 +93,7 @@ It also uses the following libraries and frameworks for backend and frontend: * Socket.io: http://socket.io/ * jQuery: http://jquery.com/ * Bootstrap: http://twitter.github.com/bootstrap/ +* Font Awesome: http://fortawesome.github.com/Font-Awesome/ * Knockout.js: http://knockoutjs.com/ * Underscore.js: http://underscorejs.org/ * Flot: http://www.flotcharts.org/ @@ -263,19 +111,3 @@ Why is it called OctoPrint and what's with the crystal ball in the logo? ------------------------------------------------------------------------ It so happens that I needed a favicon and also OctoPrint's first name -- Printer WebUI -- simply lacked a certain coolness to it. So I asked The Internet(tm) for advise. After some brainstorming, the idea of a cute Octopus watching his print job remotely through a crystal ball was born... [or something like that](https://plus.google.com/u/0/106003970953341660077/posts/UmLD5mW8yBQ). - -What do I have to do after the rename from Printer WebUI to OctoPrint? ----------------------------------------------------------------------- - -If you did checkout OctoPrint from its previous location at https://github.com/foosel/PrinterWebUI.git, you'll have to -update your so-called remote references in git in order to make `git pull` use the new repository location as origin. - -To do so you'll only need to execute the following command in your OctoPrint/PrinterWebUI folder: - - git remote set-url origin https://github.com/foosel/OctoPrint.git - -After that you might also want to rename your base directory (which probably still is called `PrinterWebUI`) to `OctoPrint` -and delete the folder `printer_webui` in your base folder (which stays there thanks to Python's compiled bytecode files -even after a rename of the Python package to `octoprint`). - -After that you are set, the configuration files are migrated automatically. diff --git a/octoprint.init b/octoprint.init new file mode 100755 index 0000000..ad0a983 --- /dev/null +++ b/octoprint.init @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copy this script to /etc/init.d/octoprint and adjust the variables +# at the top to match your installation (should be okay for a Raspian +# setup). Then link it to the correct run levels. On Debian/Rasbian +# just call 'sudo update-rc.d octoprint defaults' + +### BEGIN INIT INFO +# Provides: octoprint +# Required-Start: $local_fs networking +# Required-Stop: +# Should-Start: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Run octoprint +# Description: Octoprint provides a responsive web interface for +# controlling a 3D printer +### END INIT INFO + + +# OctoPrint's run script +DAEMON=/home/pi/OctoPrint/run + +# Port to use +PORT=5000 + +# Run as this user +RUNAS=pi + +# Exit if the run script is not found +[ -x "$DAEMON" ] || exit 0 + + +case "$1" in + start) + su $RUNAS -c "$DAEMON --port=$PORT --daemon start" + ;; + stop) + su $RUNAS -c "$DAEMON --port=$PORT --daemon stop" + ;; + restart) + su $RUNAS -c "$DAEMON --port=$PORT --daemon restart" + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + ;; +esac + +: diff --git a/octoprint/printer.py b/octoprint/printer.py index 2740494..5b0083a 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -158,7 +158,7 @@ class Printer(): self._comm.setFeedrateModifier(self._feedrateModifierMapping[structure], percentage / 100.0) - def loadGcode(self, file): + def loadGcode(self, file, printAfterLoading=False): """ Loads the gcode from the given file as the new print job. Aborts if the printer is currently printing or another gcode file is currently being loaded. @@ -168,11 +168,15 @@ class Printer(): self._setJobData(None, None) - self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, self._onGcodeLoaded) + onGcodeLoadedCallback = self._onGcodeLoaded + if printAfterLoading: + onGcodeLoadedCallback = self._onGcodeLoadedToPrint + + self._gcodeLoader = GcodeLoader(file, self._onGcodeLoadingProgress, onGcodeLoadedCallback) self._gcodeLoader.start() self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - + def startPrint(self): """ Starts the currently loaded print job. @@ -417,6 +421,10 @@ class Printer(): self._stateMonitor.setGcodeData({"filename": None, "progress": None}) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) + def _onGcodeLoadedToPrint(self, filename, gcodeList): + self._onGcodeLoaded(filename, gcodeList) + self.startPrint() + #~~ state reports def feedrateState(self): diff --git a/octoprint/server.py b/octoprint/server.py index 1025f58..a625261 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -261,9 +261,12 @@ def uploadGcodeFile(): @app.route(BASEURL + "gcodefiles/load", methods=["POST"]) def loadGcodeFile(): if "filename" in request.values.keys(): + printAfterLoading = False + if "print" in request.values.keys() and request.values["print"]: + printAfterLoading = True filename = gcodeManager.getAbsolutePath(request.values["filename"]) if filename is not None: - printer.loadGcode(filename) + printer.loadGcode(filename, printAfterLoading) return jsonify(SUCCESS) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) @@ -525,20 +528,20 @@ class Server(): } }, "loggers": { - #"octoprint.util.comm": { - # "level": "DEBUG" - #}, - "SERIAL": { - "level": "DEBUG", - "handlers": ["serialFile"], - "propagate": False - } }, "root": { "level": "INFO", "handlers": ["console", "file"] } } + + if debug: + self._config["loggers"]["SERIAL"] = { + "level": "DEBUG", + "handlers": ["serialFile"], + "propagate": False + } + logging.config.dictConfig(self._config) if __name__ == "__main__": diff --git a/octoprint/static/css/font-awesome.min.css b/octoprint/static/css/font-awesome.min.css new file mode 100644 index 0000000..d4e45b3 --- /dev/null +++ b/octoprint/static/css/font-awesome.min.css @@ -0,0 +1,33 @@ +/*! + * Font Awesome 3.0.2 + * the iconic font designed for use with Twitter Bootstrap + * ------------------------------------------------------- + * The full suite of pictographic icons, examples, and documentation + * can be found at: http://fortawesome.github.com/Font-Awesome/ + * + * License + * ------------------------------------------------------- + * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL + * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - + * http://opensource.org/licenses/mit-license.html + * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ + * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: + * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" + + * Contact + * ------------------------------------------------------- + * Email: dave@davegandy.com + * Twitter: http://twitter.com/fortaweso_me + * Work: Lead Product Designer @ http://kyruus.com + */ + +@font-face{ + font-family:'FontAwesome'; + src:url('../font/fontawesome-webfont.eot?v=3.0.1'); + src:url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), + url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'), + url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); + font-weight:normal; + font-style:normal } + +[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"} \ No newline at end of file diff --git a/octoprint/static/css/octoprint.less b/octoprint/static/css/octoprint.less index ce1be6c..b66ef6f 100644 --- a/octoprint/static/css/octoprint.less +++ b/octoprint/static/css/octoprint.less @@ -65,6 +65,10 @@ body { @base: #7728FF; .navbar-inner-color(@base); } + + .brand img { + vertical-align: bottom; + } } /** OctoPrint application tabs */ @@ -115,6 +119,11 @@ body { a.accordion-toggle { display: inline-block; } + + [class^="icon-"], + [class*=" icon-"] { + color: #000; + } } } @@ -145,7 +154,17 @@ table { &.gcode_files_action { text-align: center; - width: 45px; + width: 70px; + + a { + text-decoration: none; + color: #000; + + &.disabled { + color: #ccc; + cursor: default; + } + } } // timelapse files @@ -307,6 +326,21 @@ ul.dropdown-menu li a { #settings_dialog { width: 650px; } + +/** Footer */ +.footer { + text-align: right; + + ul li { + display: inline; + margin-left: 1em; + font-size: 85%; + a { + color: #555; + } + } +} + /** General helper classes */ .text-right { diff --git a/octoprint/static/font/FontAwesome.otf b/octoprint/static/font/FontAwesome.otf new file mode 100644 index 0000000..d13308e Binary files /dev/null and b/octoprint/static/font/FontAwesome.otf differ diff --git a/octoprint/static/font/fontawesome-webfont.eot b/octoprint/static/font/fontawesome-webfont.eot new file mode 100644 index 0000000..7d81019 Binary files /dev/null and b/octoprint/static/font/fontawesome-webfont.eot differ diff --git a/octoprint/static/font/fontawesome-webfont.svg b/octoprint/static/font/fontawesome-webfont.svg new file mode 100644 index 0000000..ba0afe5 --- /dev/null +++ b/octoprint/static/font/fontawesome-webfont.svg @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/octoprint/static/font/fontawesome-webfont.ttf b/octoprint/static/font/fontawesome-webfont.ttf new file mode 100644 index 0000000..d461724 Binary files /dev/null and b/octoprint/static/font/fontawesome-webfont.ttf differ diff --git a/octoprint/static/font/fontawesome-webfont.woff b/octoprint/static/font/fontawesome-webfont.woff new file mode 100644 index 0000000..3c89ae0 Binary files /dev/null and b/octoprint/static/font/fontawesome-webfont.woff differ diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 1cb4364..240dfe3 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -658,6 +658,14 @@ function TerminalViewModel() { function GcodeFilesViewModel() { var self = this; + self.isErrorOrClosed = ko.observable(undefined); + self.isOperational = ko.observable(undefined); + self.isPrinting = ko.observable(undefined); + self.isPaused = ko.observable(undefined); + self.isError = ko.observable(undefined); + self.isReady = ko.observable(undefined); + self.isLoading = ko.observable(undefined); + // initialize list helper self.listHelper = new ItemListHelper( "gcodeFiles", @@ -689,7 +697,33 @@ function GcodeFilesViewModel() { "name", [], CONFIG_GCODEFILESPERPAGE - ) + ); + + self.isLoadActionPossible = ko.computed(function() { + return !self.isPrinting() && !self.isPaused() && !self.isLoading(); + }); + + self.isLoadAndPrintActionPossible = ko.computed(function() { + return self.isOperational() && self.isLoadActionPossible(); + }); + + self.fromCurrentData = function(data) { + self._processStateData(data.state); + } + + self.fromHistoryData = function(data) { + self._processStateData(data.state); + } + + self._processStateData = function(data) { + self.isErrorOrClosed(data.flags.closedOrError); + self.isOperational(data.flags.operational); + self.isPaused(data.flags.paused); + self.isPrinting(data.flags.printing); + self.isError(data.flags.error); + self.isReady(data.flags.ready); + self.isLoading(data.flags.loading); + } self.requestData = function() { $.ajax({ @@ -711,12 +745,12 @@ function GcodeFilesViewModel() { } } - self.loadFile = function(filename) { + self.loadFile = function(filename, printAfterLoad) { $.ajax({ url: AJAX_BASEURL + "gcodefiles/load", type: "POST", dataType: "json", - data: {filename: filename} + data: {filename: filename, print: printAfterLoad} }) } @@ -1150,6 +1184,7 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView self.terminalViewModel.fromHistoryData(data); self.webcamViewModel.fromHistoryData(data); self.gcodeViewModel.fromHistoryData(data); + self.gcodeFilesViewModel.fromCurrentData(data); }) self._socket.on("current", function(data) { self.connectionViewModel.fromCurrentData(data); @@ -1159,6 +1194,7 @@ function DataUpdater(connectionViewModel, printerStateViewModel, temperatureView self.terminalViewModel.fromCurrentData(data); self.webcamViewModel.fromCurrentData(data); self.gcodeViewModel.fromCurrentData(data); + self.gcodeFilesViewModel.fromCurrentData(data); }) self._socket.on("updateTrigger", function(type) { if (type == "gcodeFiles") { diff --git a/octoprint/templates/index.html b/octoprint/templates/index.html index 8491186..ed22f68 100644 --- a/octoprint/templates/index.html +++ b/octoprint/templates/index.html @@ -8,6 +8,7 @@ + @@ -127,7 +128,9 @@ -  |  + +  |  |  + @@ -519,6 +522,14 @@ + {% include 'settings.html' %} diff --git a/octoprint/timelapse.py b/octoprint/timelapse.py index 49b2063..4283323 100644 --- a/octoprint/timelapse.py +++ b/octoprint/timelapse.py @@ -1,4 +1,6 @@ # coding=utf-8 +import logging + __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' @@ -32,6 +34,8 @@ def getFinishedTimelapses(): class Timelapse(object): def __init__(self): + self._logger = logging.getLogger(__name__) + self._imageNumber = None self._inTimelapse = False self._gcodeFile = None @@ -56,6 +60,7 @@ class Timelapse(object): pass def startTimelapse(self, gcodeFile): + self._logger.debug("Starting timelapse for %s" % gcodeFile) self.cleanCaptureDir() self._imageNumber = 0 @@ -63,6 +68,7 @@ class Timelapse(object): self._gcodeFile = os.path.basename(gcodeFile) def stopTimelapse(self): + self._logger.debug("Stopping timelapse") self._renderThread = threading.Thread(target=self._createMovie) self._renderThread.daemon = True self._renderThread.start() @@ -72,11 +78,13 @@ class Timelapse(object): def captureImage(self): if self._captureDir is None: + self._logger.warn("Cannot capture image, capture directory is unset") return with self._captureMutex: filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber)) self._imageNumber += 1; + self._logger.debug("Capturing image to %s" % filename) captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename}) captureThread.daemon = True @@ -84,11 +92,13 @@ class Timelapse(object): def _captureWorker(self, filename): urllib.urlretrieve(self._snapshotUrl, filename) + self._logger.debug("Image %s captured from %s" % (filename, self._snapshotUrl)) def _createMovie(self): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: + self._logger.warn("Cannot create movie, path to ffmpeg is unset") return input = os.path.join(self._captureDir, "tmp_%05d.jpg") @@ -111,9 +121,11 @@ class Timelapse(object): # finalize command with output file command.append(output) subprocess.call(command) + self._logger.debug("Rendering movie to %s" % output) def cleanCaptureDir(self): if not os.path.isdir(self._captureDir): + self._logger.warn("Cannot clean capture directory, it is unset") return for filename in os.listdir(self._captureDir): @@ -124,8 +136,10 @@ class Timelapse(object): class ZTimelapse(Timelapse): def __init__(self): Timelapse.__init__(self) + self._logger.debug("ZTimelapse initialized") def onZChange(self, oldZ, newZ): + self._logger.debug("Z change detected, capturing image") self.captureImage() class TimedTimelapse(Timelapse): @@ -138,6 +152,8 @@ class TimedTimelapse(Timelapse): self._timerThread = None + self._logger.debug("TimedTimelapse initialized") + def interval(self): return self._interval @@ -151,6 +167,7 @@ class TimedTimelapse(Timelapse): self._timerThread.start() def timerWorker(self): + self._logger.debug("Starting timer for interval based timelapse") while self._inTimelapse: self.captureImage() time.sleep(self._interval)