From 6a268826f47698c2a05ea0c9d36b5cfbb7c679ad Mon Sep 17 00:00:00 2001 From: will Date: Mon, 28 Mar 2022 19:59:15 -0600 Subject: [PATCH 01/16] implemented cooldown loop --- continuousprint/__init__.py | 37 +++++++++++++++++- .../templates/continuousprint_settings.jinja2 | 39 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index 602c24f..f1a3523 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -2,8 +2,10 @@ from __future__ import absolute_import import octoprint.plugin +import octoprint.util import flask import json +import time from io import BytesIO from octoprint.server.util.flask import restricted_access from octoprint.events import Events @@ -15,7 +17,6 @@ from .print_queue import PrintQueue, QueueItem from .driver import ContinuousPrintDriver - QUEUE_KEY = "cp_queue" CLEARING_SCRIPT_KEY = "cp_bed_clearing_script" FINISHED_SCRIPT_KEY = "cp_queue_finished_script" @@ -73,6 +74,10 @@ def get_settings_defaults(self): d[RESTART_MAX_RETRIES_KEY] = 3 d[RESTART_ON_PAUSE_KEY] = False d[RESTART_MAX_TIME_KEY] = 60 * 60 + d["bed_cooldown_enabled"] = False + d["cp_bed_cooldown_script"] = "; Put script to run before bed cools here\n" + d["bed_cooldown_threshold"] = 30 + d["bed_cooldown_timeout"] = 60 return d def _rm_temp_files(self): @@ -186,7 +191,34 @@ def cancel_print(self): self._msg("Print cancelled", type="error") self._printer.cancel_print() + def bed_cooldown(self): + self._logger.info("Running bed cooldown script") + bed_cooldown_script = self._settings.get(["cp_bed_cooldown_script"]).split("\n") + self._printer.commands(bed_cooldown_script, force=True) + self._logger.info("Preparing for Bed Cooldown") + self._printer.set_temperature("bed", 0) # turn bed off + timeout = 60 * float( + self._settings.get(["bed_cooldown_timeout"]) + ) # timeout converted to seconds + start_time = time.time() + + while (time.time() - start_time) <= timeout: + bed_temp = self._printer.get_current_temperatures()["bed"]["actual"] + if bed_temp <= float(self._settings.get(["bed_cooldown_threshold"])): + self._logger.info( + f"Cooldown threshold of {self._settings.get(['bed_cooldown_threshold'])} has been met" + ) + return + + self._logger.info( + f"Timeout of {self._settings.get(['bed_cooldown_timeout'])} minutes exceeded" + ) + return + def clear_bed(self): + if self._settings.get(["bed_cooldown_enabled"]): + # If bed cooldown management is enabled activate cooldown routine + self.bed_cooldown() path = self._write_temp_gcode(CLEARING_SCRIPT_KEY) self._printer.select_file(path, sd=False, printAfterSelect=True) @@ -484,5 +516,6 @@ def __plugin_load__(): __plugin_hooks__ = { "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information, "octoprint.access.permissions": __plugin_implementation__.add_permissions, - "octoprint.comm.protocol.action": __plugin_implementation__.resume_action_handler, # register to listen for "M118 //action:" commands + "octoprint.comm.protocol.action": __plugin_implementation__.resume_action_handler, + # register to listen for "M118 //action:" commands } diff --git a/continuousprint/templates/continuousprint_settings.jinja2 b/continuousprint/templates/continuousprint_settings.jinja2 index 15e640c..bdaa6a3 100644 --- a/continuousprint/templates/continuousprint_settings.jinja2 +++ b/continuousprint/templates/continuousprint_settings.jinja2 @@ -3,6 +3,45 @@

When continually printing the bed needs to be cleared in order for the next print to work.

+ +
Bed Cooldown Settings
+

+ If enabled, when print in queue is finished OctoPrint will run Bed Cooldown Script, + turn the heated bed off and monitor the bed temperature. + After either the Bed Cooldown Threshold or Bed Cooldown Timeout is + met the Bed clearing routine will continue. +

+ +
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +

If your printer requires manual intervention to clear the bed, you can use @ Commands such as @pause to tell OctoPrint to pause indefinitely until the resume button is pressed.

From dfa06e9484ee91aae6fc995f40298c7b3a18d8fa Mon Sep 17 00:00:00 2001 From: will Date: Sat, 2 Apr 2022 18:02:26 -0600 Subject: [PATCH 02/16] moved cooldown_timout so it will dynamically update mid-cooldown when settings are changed --- continuousprint/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index f1a3523..7b04773 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -197,12 +197,9 @@ def bed_cooldown(self): self._printer.commands(bed_cooldown_script, force=True) self._logger.info("Preparing for Bed Cooldown") self._printer.set_temperature("bed", 0) # turn bed off - timeout = 60 * float( - self._settings.get(["bed_cooldown_timeout"]) - ) # timeout converted to seconds start_time = time.time() - while (time.time() - start_time) <= timeout: + while (time.time() - start_time) <= (60 * float(self._settings.get(["bed_cooldown_timeout"]))): # timeout converted to seconds bed_temp = self._printer.get_current_temperatures()["bed"]["actual"] if bed_temp <= float(self._settings.get(["bed_cooldown_threshold"])): self._logger.info( From 94c124ce1f8d8d3fc30d1bff3e6dcb77383ad2e8 Mon Sep 17 00:00:00 2001 From: will Date: Sun, 3 Apr 2022 17:30:46 -0600 Subject: [PATCH 03/16] updated plugin version 1.5.0rc3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 643b178..80176c7 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ plugin_name = "continuousprint" # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module -plugin_version = "1.4.2" +plugin_version = "1.5.0rc3" # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # module From 1f547bf9e577405776e1bd0f4fbad9cffb25cb76 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 01:54:53 +0000 Subject: [PATCH 04/16] Update spaghetti detection logic to match new TSD event forwarding --- continuousprint/__init__.py | 30 ++++++++++++++++++------------ continuousprint/driver.py | 6 +++--- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index 7b04773..4ac13ee 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -100,20 +100,24 @@ def on_after_startup(self): ) self._update_driver_settings() self._rm_temp_files() + self.next_pause_is_spaghetti = False self._logger.info("Continuous Print Plugin started") # part of EventHandlerPlugin def on_event(self, event, payload): if not hasattr(self, "d"): # Ignore any messages arriving before init return - - is_current_path = ( - payload is not None and payload.get("path") == self.d.current_path() - ) - is_finish_script = ( - payload is not None - and payload.get("path") == TEMP_FILES[FINISHED_SCRIPT_KEY] - ) + + # Access current file via `get_current_job` instead of `is_current_file` because the latter may go away soon + # See https://docs.octoprint.org/en/master/modules/printer.html#octoprint.printer.PrinterInterface.is_current_file + # Avoid using payload.get('path') as some events may not express path info. + current_file = self._printer.get_current_job().get('file', {}).get('name') + is_current_path = (current_file == self.d.current_path()) + is_finish_script = (current_file == TEMP_FILES[FINISHED_SCRIPT_KEY]) + + # This custom event is only defined when OctoPrint-TheSpaghettiDetective plugin is installed. + # try to fetch the attribute but default to None + tsd_command = getattr(octoprint.events.Events, 'PLUGIN_THESPAGHETTIDETECTIVE_COMMAND', None) if event == Events.METADATA_ANALYSIS_FINISHED: # OctoPrint analysis writes to the printing file - we must remove @@ -140,11 +144,13 @@ def on_event(self, event, payload): elif is_current_path and event == Events.PRINT_CANCELLED: self.d.on_print_cancelled() self.paused = False - self._msg(type="reload") # reload UI + self._msg(type="reload") # reload UI + elif is_current_path and tsd_command is not None and event == tsd_command and payload.get('cmd') == "pause" and payload.get('initiator') == "system": + self._logger.info(f"Got spaghetti detection event; flagging next pause event for restart") + self.next_pause_is_spaghetti = True elif is_current_path and event == Events.PRINT_PAUSED: - self.d.on_print_paused( - is_temp_file=(payload["path"] in TEMP_FILES.values()) - ) + self.d.on_print_paused(is_temp_file=(payload['path'] in TEMP_FILES.values()), is_spaghetti=self.next_pause_is_spaghetti) + self.next_pause_is_spaghetti = False self.paused = True self._msg(type="reload") # reload UI elif is_current_path and event == Events.PRINT_RESUMED: diff --git a/continuousprint/driver.py b/continuousprint/driver.py index 77566fb..d9b8aa1 100644 --- a/continuousprint/driver.py +++ b/continuousprint/driver.py @@ -175,14 +175,14 @@ def on_print_cancelled(self): self.active = False self._set_status("Inactive (print cancelled with too many retries)") - def on_print_paused(self, elapsed=None, is_temp_file=False): - if not self.active or not self.retry_on_pause or is_temp_file: + def on_print_paused(self, elapsed=None, is_temp_file=False, is_spaghetti=False): + if not self.active or not self.retry_on_pause or is_temp_file or not is_spaghetti: self._set_status("Print paused") return elapsed = elapsed or (time.time() - self.q[self._cur_idx()].start_ts) if elapsed < self.retry_threshold_seconds: - self._set_status("Cancelling print (paused early, likely adhesion failure)") + self._set_status("Cancelling print (spaghetti detected {timeAgo(elapsed)} into print)") self.cancel_print_fn() # self.actions.append(self.cancel_print_fn) else: From 75a7435ebf6572d43095e5296d4ef6d3142eb966 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 10:42:14 +0000 Subject: [PATCH 05/16] Add CodeCov action --- .github/workflows/coverage.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..46f136e --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,34 @@ +name: CodeCov + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + workflow_dispatch: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + run: + name: CodeCov + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@master + with: + python-version: 3.8 + + - name: Generate Report + run: | + pip install coverage + cd continuousprint + coverage run -m unittest "*_test.py" + - name: Upload to Codecov + uses: codecov/codecov-action@v2 From 63d1f5cf2a2d6b12175fb44ff5e4db9d3557552b Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 10:49:00 +0000 Subject: [PATCH 06/16] Add build and codecov shields.io to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cfa64db..feba426 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Continuous Print Queue Plugin +![build status](https://img.shields.io/travis/smartin015/continuousprint/master?style=plastic) +![code coverage](https://img.shields.io/codecov/c/github/smartin015/continuousprint/master) + This plugin automates your printing! * **Add gcode files to the queue and set a number of times to print each.** The plugin will print them in sequence, running "bed clearing" script after each. From ed0913dd7c7801dec6b14489900aab9e391065e4 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 12:22:43 +0000 Subject: [PATCH 07/16] Update docs, now using mkdocs. --- README.md | 145 +-------------------------------------- docs/advanced-queuing.md | 54 +++++++++++++++ docs/api.md | 65 ++++++++++++++++++ docs/contributing.md | 80 +++++++++++++++++++++ docs/getting-started.md | 106 ++++++++++++++++++++++++++++ docs/index.md | 12 ++++ mkdocs.yml | 26 +++++++ setup.py | 7 +- 8 files changed, 350 insertions(+), 145 deletions(-) create mode 100644 docs/advanced-queuing.md create mode 100644 docs/api.md create mode 100644 docs/contributing.md create mode 100644 docs/getting-started.md create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/README.md b/README.md index feba426..b70d200 100644 --- a/README.md +++ b/README.md @@ -13,148 +13,5 @@ WARNING: Your printer must have a method of clearing the bed automatically, with # Setup -## Add the plugin +See `/docs/getting-started.md`. -1. In the OctoPrint UI, go to `Settings` -> `Plugin Manager` -> `Get More` -1. Search for "Continuous Print", and click Install, following any instructions - * If you can't find the plugin, you can also put https://github.com/smartin015/continuousprint/archive/master.zip into the "...from URL" section of the Get More page. -1. Restart OctoPrint - -That's it! Now let's configure it to work with your printer. - -## Configure the plugin - -Go to `Settings` -> `Continuous Print` and ensure the bed cleaning and queue finished scripts are correct for your 3D printer. - -You can also enable settings here for compatibility with [The Spaghetti Detective](https://www.thespaghettidetective.com/) for automatic retries when the print starts failing. - -## Add prints to the queue - -1. Navigate to the file you wish to add in the Files dialog on the left of the page. -1. Add it to the print queue by clicking the `+` button to the right of the file name. - * If you want to print more than one, you can click multiple times to add more copies, or set a specific count in the `Continuous Print` tab. - -## Start the queue - -The print queue won't start your prints just yet. To run the queue: - -1. Click the 'Continuous Print` tab (it may be hidden in the extra tabs fold-out on the right) -1. Double check the order and count of your prints - set the count and order using the buttons and number box to the right of the queued print, and delete with the red `X`. -1. Click `Start Managing`. - -The plugin will wait until your printer is ready to start a print, then it'll begin with the top of the queue and proceed until the bottom. - -Note that when it's time to clear the print bed or finish up, a temporary `cp_\*.gcode` file will appear in your local files, and disappear when it completes. This is a change from older "gcode injecting" behavior that is necessary to support [at-commands](https://docs.octoprint.org/en/master/features/atcommands.html) in the clearing and finish scripts. - -## Inspect queue items - -As the print queue is managed and prints complete, you can see the status of individual prints by clicking the small triangle to the left of any individual queue item. - -This opens a sub-panel showing individual print stats and results. - -## Stop the queue - -When all prints are finished, the plugin stops managing the queue and waits for you to start it again. - -If you need to stop early, click `Stop Managing` (**Note: any currently running print will continue unless you cancel it**) - -## Clean up the queue - -Click the triple-dot menu for several convenient queue cleanup options. You can also remove individual queue items with the red `X` next to the item. - -# Development - -*Based on the instructions at https://docs.octoprint.org/en/master/plugins/gettingstarted.html* - -Install octoprint locally: - -```shell -git clone https://github.com/OctoPrint/OctoPrint -cd OctoPrint -virtualenv venv -source venv/bin/activate -pip install -e . -``` - -In the same terminal as the one where you activated the environment, Install the plugin in dev mode and launch the server: - -```shell -git clone https://github.com/smartin015/continuousprint.git -cd continuousprint -octoprint dev plugin:install -pre-commit install # Cleans up files when you commit them - see https://pre-commit.com/. Note that venv must be activated or else flake8 raises improper errors -octoprint serve -``` - -You should see "Successfully installed continuousprint" when running the install command, and you can view the page at http://localhost:5000. - -## Testing - -Backend unit tests are currently run manually: -``` -python3 continuousprint/print_queue_test.py -python3 continuousprint/driver_test.py -``` - -Frontend unit tests require some additional setup (make sure [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable) and its dependencies are installed): - -``` -cd .../continuousprint -yarn install -yarn run test -``` - -This will run all frontend JS test files (`continuousprint/static/js/\*.test.js`). You can also `yarn run watch-test` to set up a test process which re-runs whenever you save a JS test file. - -## Installing dev version on OctoPi - -Users of [OctoPi](https://octoprint.org/download/) can install a development version directly on their pi as follows: - -1. `ssh pi@` and provide your password (the default is `raspberry`, but for security reasons you should change it with `passwd` when you can) -1. `git clone https://github.com/smartin015/continuousprint.git` -1. Uninstall any existing continuous print installations (see `Settings` -> `Plugin Manager` in the browser) -1. `cd continuousprint && ~/oprint/bin/python3 setup.py install` - -Note that we're using the bundled version of python3 that comes with octoprint, **NOT** the system installed python3. If you try the latter, it'll give an error that sounds like octoprint isn't installed. - -## Developer tips - -* The backend (`__init__.py` and dependencies) stores a flattened representation of the print queue and - iterates through it from beginning to end. Each item is loaded as a QueueItem (see `print_queue.py`). -* The frontend talks to the backend with the flattened queue, but operates on an inmemory structured version: - * Each flattened queue item is loaded as a `CPQueueItem` (see continuousprint/static/js/continuousprint_queueitem.js) - * Sets of the same queue item are aggregated into a `CPQueueSet` (see continuousprint/static/js/continuousprint_queueset.js) - * Multiple queuesets are grouped together and run one or more times as a `CPJob` (see continuousprint/static/js/continuousprint_job.js) - * For simplicity, each level only understands the level below it - e.g. a Job doesn't care about QueueItems. -* Remember, you can enable the virtual printer under `Virtual Printer` in OctoPrint settings. -* Octoprint currently uses https://fontawesome.com/v5.15/icons/ for icons. -* Drag-and-drop functionality uses SortableJS wrapped with Knockout-SortableJS, both of which are heavily customized. For more details on changes see: - * Applied fix from https://github.com/SortableJS/knockout-sortablejs/pull/13 - * Applied fix from https://github.com/SortableJS/knockout-sortablejs/issues/14 - * Discussion at https://github.com/smartin015/continuousprint/issues/14 (conflict with a different `knockout-sortable` library) - -## QA - -Check these before releasing: - -* All buttons under the triple-dot menu work as intended -* Jobs and items can be drag-reordered -* Jobs can't be dragged into items, items can't be dragged outside jobs -* Adding a file from the Files dialog works as intended -* Setting the count for jobs and items behaves as expected -* [At-commands](https://docs.octoprint.org/en/master/features/atcommands.html) work in clearing/finish scripts -* Temporary gcode files are cleaned up after use -* Pausing and resuming the print works -* Cancelling restarts the print -* Print queue can be started and stopped; queue items complete in order -* Stylings look good in light and dark themes - -## Potential future work - -* File integrity checking (resilience to renames/deletions) -* Save/remember and allow re-adding of jobs -* Improved queue history/status with more stats -* Segmented status bars to better indicate run completion -* Client library to support queue management automation -* Bed clearing profiles for specific printers -* Multi-user queue modification with attribution (shows who added which prints, prevents overwriting others' jobs) diff --git a/docs/advanced-queuing.md b/docs/advanced-queuing.md new file mode 100644 index 0000000..c331438 --- /dev/null +++ b/docs/advanced-queuing.md @@ -0,0 +1,54 @@ +# Advanced Queuing + +![british queueing meme](https://y.yarn.co/276d8bc3-5a86-4b5c-ace4-99b363f9c43b_text.gif) + +In the [quickstart](/getting-started/) we covered the basics of adding prints to the queue and running them, but you can do much more once you understand the nested structure of the print queue. + +## Sets and Jobs + +The queue is actually made up of two levels: sets and jobs. + +**Sets** are a single print file, printed one or more times. You created a set by following the "Add prints to the queue" step in the [quickstart](/getting-started/). + +**Jobs** are a collection of sets, printed one or more times. Jobs are always printed sequentially, from the top of the queue to the bottom. + +## "Add" behavior + +By default, every print file you add (as a set) is appended to a default, unnamed job at the end of the queue. + +If you give this job a name (by clicking the title box, typing a name, then hitting enter or clicking away) it will stop collecting new prints and a new default job will be created when the next print is added. + +## Example 1: Batched strategy + +Let's consider an empty queue. If you add `A.gcode` with 5 copies and `B.gcode` with 5 copies, the print order will be: + +`AAAAA BBBBB` + +This is great if you want all of your `A` files to print before all your `B` files, for instance when you're working on a project that uses `A` but plan use `B` for something later. + +## Example 2: Interleaved strategy + +Let's start again with an empty queue, but now suppose we add `A.gcode` with 1 copy, `B.gcode` with 1 copy, then set the job count to `5`. The print order will now be: + +`AB AB AB AB AB` + +This is exactly the pattern you would want if you were, for example, printing a box with `A.gcode` as the base and `B.gcode` as the lid. Each box would be completed in order, so you can use the first box without waiting for all the bases to print, then for the first lid to print. + +## Example 3: Combined strategy + +From an empty queue, you could even add `A.gcode` with 1 copy and `B.gcode` with 4 copies, and set the job count to `3`. The outcome is then: + +`ABBBB ABBBB ABBBB` + +We're simply mixing examples 1 and 2 together, but this would be ideal for a base print with multiple smaller additions - a table with four legs, for instance. + +## Drag and drop reordering + +At any time, you can click and drag jobs and sets with the grips on the left (the two vertical lines): + +* Dragging a job reorders it among the jobs in the queue. +* Dragging a set reorders it within a job. + +!!! tip + + You can also drag a set from one job to another! Note however that this may change the total number of that file printed if the destination job has a different count than the origin job. diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..af8075f --- /dev/null +++ b/docs/api.md @@ -0,0 +1,65 @@ +# Continuous Print API + +This plugin comes with a basic API to fetch state and start/stop the queue. This allows for other scripts and plugins to interact with the continuous print queue to unlock even more autonomous printing! + +!!! important + + Other internal web requests exist than what's presented here, but they aren't for external use and are not guaranteed to be stable. + + If you want additional API features, [please submit a feature request](https://github.com/smartin015/continuousprint/issues/new?template=feature_request.md). + +!!! tip "Tip: Usage Examples" + + See [`api_examples/`](https://github.com/smartin015/continuousprint/tree/master/api_examples) for reference implementations in different languages. + +## Fetch the queue state + +**Request** + +**`HTTP GET http://printer:5000/plugin/continuousprint/state`** + +Returns the current internal state of the printer as a JSON string. List entries within `queue` may include fields which are not listed here - those +fields may be subject to change without notice, so be wary of depending on them. + +**Response** + +``` +{ + "active": true/false, + "status": string, + "queue": [ + { + "name": string, + "path": string, + "sd": bool + "job": string, + "run": number + "start_ts": null | number (seconds), + "end_ts": null | number (seconds), + "result": string (todo specific stirngs), + "retries": number + }, + ... + ] +} +``` + +## Start/stop managing the queue + +**Request** + +**`HTTP POST http://printer:5000/plugin/continuousprint/set_active`** + +Payload: `active=true` or `active=false` + +This starts and stops continuous print management of the printer. + +!!! warning + + If `active=false` is sent, the plugin will stop managing the queue **but it will not stop any currently active print**. This must be done separately. + + See [the OctoPrint REST API](https://docs.octoprint.org/en/master/api/job.html#issue-a-job-command) for additional print job management options which include cancelling active prints. + +**Response** + +Same as `/state` above diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..8dccecb --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,80 @@ +# Contributor Guide + +*Based on the [octoprint plugin quickstart guide](https://docs.octoprint.org/en/master/plugins/gettingstarted.html)* + +## 1. Install octoprint from source + +Install octoprint locally: + +```shell +git clone https://github.com/OctoPrint/OctoPrint +cd OctoPrint +virtualenv venv +source venv/bin/activate +pip install -e . +``` + +## 2. Install and start the continuous print plugin in local dev mode + +In the same terminal as the one where you activated the environment, Install the plugin in dev mode and launch the server: + +```shell +git clone https://github.com/smartin015/continuousprint.git +cd continuousprint +octoprint dev plugin:install +pre-commit install # Cleans up files when you commit them - see https://pre-commit.com/. Note that venv must be activated or else flake8 raises improper errors +octoprint serve +``` + +You should see "Successfully installed continuousprint" when running the install command, and you can view the page at [http://localhost:5000](http://localhost:5000). + +## 3. Run unit tests to verify changes + +Backend unit tests are currently run manually: +``` +python3 continuousprint/print_queue_test.py +python3 continuousprint/driver_test.py +``` + +Frontend unit tests require some additional setup (make sure [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable) and its dependencies are installed): + +``` +cd .../continuousprint +yarn install +yarn run test +``` + +This will run all frontend JS test files (`continuousprint/static/js/\*.test.js`). You can also `yarn run watch-test` to set up a test process which re-runs whenever you save a JS test file. + +## 4. Install a dev version on OctoPi + +Users of [OctoPi](https://octoprint.org/download/) can install a development version directly on their pi to test their changes on actual hardware. + +!!! warning + + Editing code - especially unfamiliar code - can lead to unpredictable behavior. You're controlling a robot that can pinch fingers and melt plastic, so be careful and consider using the [built-in virtual printer](https://docs.octoprint.org/en/master/development/virtual_printer.html) before a physical test. + +1. `ssh pi@` and provide your password (the default is `raspberry`, but for security reasons you should change it with `passwd` when you can) +1. `git clone https://github.com/smartin015/continuousprint.git` +1. Uninstall any existing continuous print installations (see `Settings` -> `Plugin Manager` in the browser) +1. `cd continuousprint && ~/oprint/bin/python3 setup.py install` + +Note that we're using the bundled version of python3 that comes with octoprint, **NOT** the system installed python3. If you try the latter, it'll give an error that sounds like octoprint isn't installed. + +## Tips and Tricks + +This is a collection of random tidbits intended to help you get your bearings. If you're new to this plugin (and/or plugin development in general), please take a look! + +* The backend (`__init__.py` and dependencies) stores a flattened representation of the print queue and + iterates through it from beginning to end. Each item is loaded as a QueueItem (see `print_queue.py`). +* The frontend talks to the backend with the flattened queue, but operates on an inmemory structured version: + * Each flattened queue item is loaded as a `CPQueueItem` (see continuousprint/static/js/continuousprint_queueitem.js) + * Sets of the same queue item are aggregated into a `CPQueueSet` (see continuousprint/static/js/continuousprint_queueset.js) + * Multiple queuesets are grouped together and run one or more times as a `CPJob` (see continuousprint/static/js/continuousprint_job.js) + * For simplicity, each level only understands the level below it - e.g. a Job doesn't care about QueueItems. +* Octoprint currently uses https://fontawesome.com/v5.15/icons/ for icons. +* Drag-and-drop functionality uses SortableJS wrapped with Knockout-SortableJS, both of which are heavily customized. For more details on changes see: + * Applied fix from https://github.com/SortableJS/knockout-sortablejs/pull/13 + * Applied fix from https://github.com/SortableJS/knockout-sortablejs/issues/14 + * Discussion at https://github.com/smartin015/continuousprint/issues/14 (conflict with a different `knockout-sortable` library) + diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..f8aaf7d --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,106 @@ +# Getting Started + +## Install the plugin + +1. In the OctoPrint UI, go to `Settings` -> `Plugin Manager` -> `Get More` +1. Search for "Continuous Print", and click Install, following any instructions + * If you can't find the plugin, you can also put https://github.com/smartin015/continuousprint/archive/master.zip into the "...from URL" section of the Get More page. +1. Restart OctoPrint + +That's it! Now let's configure it to work with your printer. + +!!! tip "Want cool features?" + + There's [much more automation on the way](github.com/smartin015/continuousprint/issues)! + + To help speed up development and get features early (and if you don't mind the odd bug here and there), turn on `Release Candidates` under `Settings -> Software Update` (more info [here](https://community.octoprint.org/t/how-to-use-the-release-channels-to-help-test-release-candidates/402)) + +## Configure the plugin + +Go to `Settings` -> `Continuous Print` and ensure the bed cleaning and queue finished scripts are correct for your 3D printer. + +You can also enable settings here for compatibility with [The Spaghetti Detective](https://www.thespaghettidetective.com/) for automatic retries when the print starts failing. + +## Add prints to the queue + +1. Navigate to the file you wish to add in the Files dialog on the left of the page. +1. Add it to the print queue by clicking the `+` button to the right of the file name. + * If you want to print more than one, you can click multiple times to add more copies, or set a specific count in the `Continuous Print` tab. + +### Use Jobs to group your print files + +The queue is actually made up of two levels: sets and jobs. + +**Sets** are a single print file, printed one or more times. You created a set by following the "Add prints to the queue" step above. + +**Jobs** are a collection of sets, printed one or more times. + +By default, every print file you add (as a set) is appended to a default, unnamed job at the end of the queue. If you give this job a name (by clicking the title box, typing a name, then hitting enter or clicking away) it will stop collecting new prints and a new default job will be created + +**Example 1: Batched** + +Let's consider an empty queue. If you add `A.gcode` with 5 copies and `B.gcode` with 5 copies, the print order will be: + +`A A A A A B B B B B` + +This is great if you want all of your `A` files to print before all your `B` files, e.g. if you're working on a project that uses `A` but plan use `B` for something later. + +**Example 2: Interleaved** + +Let's start again with an empty queue, but now suppose we add `A.gcode` with 1 copy, `B.gcode` with 1 copy, then set the job count to `5`. The print order will now be: + +`A B A B A B A B A B` + +This is exactly the pattern you would want if you were, for example, printing a box with `A.gcode` as the base and `B.gcode` as the lid. Each box would be completed in order, so you can use the first box without waiting for all the bases to print, then for the first lid to print. + +You can mix and match + +## Start the queue + +!!! warning "Safety check!" + + If you glossed over "Configure the plugin" above, read it now. Seriously. + + You can permanently damage your printer if you don't set up the correct GCODE instructions to a + clear the bed and finish the queue. + + Supporting specific printer profiles is [on the to-do list](https://github.com/smartin015/continuousprint/issues/21), but not yet available, so you'll have to do this on your own for now. + +The print queue won't start your prints just yet. To run the queue: + +1. Click the 'Continuous Print` tab (it may be hidden in the extra tabs fold-out on the right) +1. Double check the order and count of your prints - set the count and order using the buttons and number box to the right of the queued print, and delete with the red `X`. +1. Click `Start Managing`. + +The plugin will wait until your printer is ready to start a print, then it'll begin with the top of the queue and proceed until the bottom. + +Note that when it's time to clear the print bed or finish up, a temporary `cp\_\*.gcode` file will appear in your local files, and disappear when it completes. This is a change from older "gcode injecting" behavior that is necessary to support [at-commands](https://docs.octoprint.org/en/master/features/atcommands.html) in the clearing and finish scripts. + +## Inspect queue items + +As the print queue is managed and prints complete, you can see the status of individual prints by clicking the small triangle to the left of any individual queue item. + +This opens a sub-panel showing individual print stats and results. + +## Stop the queue + +When all prints are finished, the plugin stops managing the queue and waits for you to start it again. + +If you need to stop early, click `Stop Managing`. + +!!! important + + The queue may not be managed any more, but **any currently running print will continue printing** unless you cancel it with the `Cancel` button. + +## Clean up the queue + +Click the triple-dot menu in the top right corner of the plugin tab for several convenient queue cleanup options. You can also remove individual queue items with the red `X` next to the item. + +## Troubleshooting + +If at any point you're stuck or see unexpected behavior or bugs, please file a [bug report](https://github.com/smartin015/continuousprint/issues/new?assignees=&labels=bug&template=bug_report.md&title=). + +**Be sure to include system info and browser logs** so the problem can be quickly diagnosed and fixed. + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..41f870e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ +# Continuous Print Queue Plugin + +![build status](https://img.shields.io/travis/smartin015/continuousprint/master?style=plastic) +![code coverage](https://img.shields.io/codecov/c/github/smartin015/continuousprint/master) + +Thanks for your interest in the OctoPrint Continuous Print plugin! + +For an overview, check out the [plugin repository page](https://plugins.octoprint.org/plugins/continuousprint/). + +If you're looking for source code, head over to the [github repository](github.com/smartin015/continuousprint). + +Use the nav section on the left to learn more. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..4173baf --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,26 @@ +site_name: Continuous Print +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: green + accent: indigo + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: green + accent: indigo + toggle: + icon: material/toggle-switch + name: Switch to light mode +markdown_extensions: + - admonition +nav: + - Home: index.md + - Getting Started: getting-started.md + - Advanced Queuing: advanced-queuing.md + - Contributing: contributing.md + - API: api.md diff --git a/setup.py b/setup.py index 80176c7..b19ea20 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,12 @@ plugin_license = "AGPLv3" # Any additional requirements besides OctoPrint should be listed here -plugin_requires = ["pre-commit"] +plugin_requires = [ + "pre-commit", # For running automated precommit scripts + "mkdocs-material", # Theme for documentation + "mkdocs", # Documentation library + "pymdown-extensions", # Fancy extensions for documentation +] ### -------------------------------------------------------------------------------------------------------------------- ### More advanced options that you usually shouldn't have to touch follow after this point From 3278dbfc7c84b1b7bfbee606f95979af2f11854c Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 12:26:02 +0000 Subject: [PATCH 08/16] Add github pages link to readme, update gitignore to ignore site data --- .gitignore | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ecfcd6f..32d7dc4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist *.egg* .DS_Store *.zip +site/ diff --git a/README.md b/README.md index b70d200..af8e509 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This plugin automates your printing! WARNING: Your printer must have a method of clearing the bed automatically, with correct GCODE instructions set up in this plugin's settings page - damage to your printer may occur if this is not done correctly. If you want to manually remove prints, look in the plugin settings for details on how to use `@pause` so the queue is paused before another print starts. -# Setup +# Documentation -See `/docs/getting-started.md`. +See https://smartin015.github.io/continuousprint/ for all documentation on installation, setup, queueing strategies, and development. From 5d6c0a25667ab4d8c04bdf21693e2a25d26ed4f3 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 12:36:55 +0000 Subject: [PATCH 09/16] Fix tests and styles --- continuousprint/__init__.py | 31 ++++++++++++++++++++++--------- continuousprint/driver.py | 11 +++++++++-- continuousprint/driver_test.py | 15 +++++++++++---- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index 4ac13ee..ca176ba 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -107,17 +107,19 @@ def on_after_startup(self): def on_event(self, event, payload): if not hasattr(self, "d"): # Ignore any messages arriving before init return - + # Access current file via `get_current_job` instead of `is_current_file` because the latter may go away soon # See https://docs.octoprint.org/en/master/modules/printer.html#octoprint.printer.PrinterInterface.is_current_file # Avoid using payload.get('path') as some events may not express path info. - current_file = self._printer.get_current_job().get('file', {}).get('name') - is_current_path = (current_file == self.d.current_path()) - is_finish_script = (current_file == TEMP_FILES[FINISHED_SCRIPT_KEY]) + current_file = self._printer.get_current_job().get("file", {}).get("name") + is_current_path = current_file == self.d.current_path() + is_finish_script = current_file == TEMP_FILES[FINISHED_SCRIPT_KEY] # This custom event is only defined when OctoPrint-TheSpaghettiDetective plugin is installed. # try to fetch the attribute but default to None - tsd_command = getattr(octoprint.events.Events, 'PLUGIN_THESPAGHETTIDETECTIVE_COMMAND', None) + tsd_command = getattr( + octoprint.events.Events, "PLUGIN_THESPAGHETTIDETECTIVE_COMMAND", None + ) if event == Events.METADATA_ANALYSIS_FINISHED: # OctoPrint analysis writes to the printing file - we must remove @@ -144,12 +146,23 @@ def on_event(self, event, payload): elif is_current_path and event == Events.PRINT_CANCELLED: self.d.on_print_cancelled() self.paused = False - self._msg(type="reload") # reload UI - elif is_current_path and tsd_command is not None and event == tsd_command and payload.get('cmd') == "pause" and payload.get('initiator') == "system": - self._logger.info(f"Got spaghetti detection event; flagging next pause event for restart") + self._msg(type="reload") # reload UI + elif ( + is_current_path + and tsd_command is not None + and event == tsd_command + and payload.get("cmd") == "pause" + and payload.get("initiator") == "system" + ): + self._logger.info( + f"Got spaghetti detection event; flagging next pause event for restart" + ) self.next_pause_is_spaghetti = True elif is_current_path and event == Events.PRINT_PAUSED: - self.d.on_print_paused(is_temp_file=(payload['path'] in TEMP_FILES.values()), is_spaghetti=self.next_pause_is_spaghetti) + self.d.on_print_paused( + is_temp_file=(payload["path"] in TEMP_FILES.values()), + is_spaghetti=self.next_pause_is_spaghetti, + ) self.next_pause_is_spaghetti = False self.paused = True self._msg(type="reload") # reload UI diff --git a/continuousprint/driver.py b/continuousprint/driver.py index d9b8aa1..37315d8 100644 --- a/continuousprint/driver.py +++ b/continuousprint/driver.py @@ -176,13 +176,20 @@ def on_print_cancelled(self): self._set_status("Inactive (print cancelled with too many retries)") def on_print_paused(self, elapsed=None, is_temp_file=False, is_spaghetti=False): - if not self.active or not self.retry_on_pause or is_temp_file or not is_spaghetti: + if ( + not self.active + or not self.retry_on_pause + or is_temp_file + or not is_spaghetti + ): self._set_status("Print paused") return elapsed = elapsed or (time.time() - self.q[self._cur_idx()].start_ts) if elapsed < self.retry_threshold_seconds: - self._set_status("Cancelling print (spaghetti detected {timeAgo(elapsed)} into print)") + self._set_status( + "Cancelling print (spaghetti detected {timeAgo(elapsed)} into print)" + ) self.cancel_print_fn() # self.actions.append(self.cancel_print_fn) else: diff --git a/continuousprint/driver_test.py b/continuousprint/driver_test.py index 43dc9ff..b6e79d2 100644 --- a/continuousprint/driver_test.py +++ b/continuousprint/driver_test.py @@ -115,17 +115,24 @@ def test_success_after_queue_prepend_starts_prepended(self): self.d.start_print_fn.assert_called_once self.assertEqual(self.d.start_print_fn.call_args[0][0], n) - def test_paused_early_triggers_cancel(self): + def test_paused_with_spaghetti_early_triggers_cancel(self): self.d.set_active() - self.d.on_print_paused(self.d.retry_threshold_seconds - 1) + self.d.on_print_paused(self.d.retry_threshold_seconds - 1, is_spaghetti=True) flush(self.d) self.d.cancel_print_fn.assert_called_once_with() + def test_paused_manually_early_falls_through(self): + self.d.set_active() + + self.d.on_print_paused(self.d.retry_threshold_seconds - 1, is_spaghetti=False) + flush(self.d) + self.d.cancel_print_fn.assert_not_called() + def test_paused_on_temp_file_falls_through(self): self.d.set_active() self.d.start_print_fn.reset_mock() - self.d.on_print_paused(is_temp_file=True) + self.d.on_print_paused(is_temp_file=True, is_spaghetti=True) self.d.cancel_print_fn.assert_not_called() self.assertEqual(self.d.pending_actions(), 0) @@ -158,7 +165,7 @@ def test_paused_late_waits_for_user(self): self.d.set_active() self.d.start_print_fn.reset_mock() - self.d.on_print_paused(self.d.retry_threshold_seconds + 1) + self.d.on_print_paused(self.d.retry_threshold_seconds + 1, is_spaghetti=True) self.d.start_print_fn.assert_not_called() def test_failure_sets_inactive(self): From 83ff29af3d86c42426fd1bd01de7a1ad11ea46be Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 19 Mar 2022 12:42:17 +0000 Subject: [PATCH 10/16] Linter pass --- .github/workflows/coverage.yml | 3 +-- .github/workflows/lint.yml | 2 +- continuousprint/__init__.py | 2 +- setup.py | 8 ++++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 46f136e..7d11366 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -28,7 +28,6 @@ jobs: - name: Generate Report run: | pip install coverage - cd continuousprint - coverage run -m unittest "*_test.py" + cd continuousprint && coverage run -m unittest '*_test.py' - name: Upload to Codecov uses: codecov/codecov-action@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87f7a03..91de8c9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,4 +36,4 @@ jobs: black: true flake8: true # These must match .pre-commit-config.yaml - flake8_args: "--max-line-length=88 --ignore=E203,E266,E501,W503,F403,F401,E402" + flake8_args: "--max-line-length=88 --ignore=E203,E266,E501,W503,F403,F401,E402,F821" diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index ca176ba..db634cc 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -155,7 +155,7 @@ def on_event(self, event, payload): and payload.get("initiator") == "system" ): self._logger.info( - f"Got spaghetti detection event; flagging next pause event for restart" + "Got spaghetti detection event; flagging next pause event for restart" ) self.next_pause_is_spaghetti = True elif is_current_path and event == Events.PRINT_PAUSED: diff --git a/setup.py b/setup.py index b19ea20..36e0492 100644 --- a/setup.py +++ b/setup.py @@ -34,10 +34,10 @@ # Any additional requirements besides OctoPrint should be listed here plugin_requires = [ - "pre-commit", # For running automated precommit scripts - "mkdocs-material", # Theme for documentation - "mkdocs", # Documentation library - "pymdown-extensions", # Fancy extensions for documentation + "pre-commit", # For running automated precommit scripts + "mkdocs-material", # Theme for documentation + "mkdocs", # Documentation library + "pymdown-extensions", # Fancy extensions for documentation ] ### -------------------------------------------------------------------------------------------------------------------- From d5dd00b7c4101598f4d3c1e1093b86a907812aab Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Wed, 16 Mar 2022 17:35:40 -0400 Subject: [PATCH 11/16] Add API documentation, deprecate unused web handlers and add examples for querying API --- api_examples/example.py | 30 ++++ api_examples/jquery.js | 28 ++++ continuousprint/__init__.py | 134 ++++++++++-------- .../static/js/continuousprint_api.js | 2 + 4 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 api_examples/example.py create mode 100644 api_examples/jquery.js diff --git a/api_examples/example.py b/api_examples/example.py new file mode 100644 index 0000000..b7792d6 --- /dev/null +++ b/api_examples/example.py @@ -0,0 +1,30 @@ +import requests + +# See https://docs.octoprint.org/en/master/api/general.html#authorization for +# where to get this value +UI_API_KEY = "CHANGEME" + +# Change this to match your printer +HOST_URL = "http://localhost:5000" + + +def set_active(active=True): + return requests.post( + f"{HOST_URL}/plugin/continuousprint/set_active", + headers={"X-Api-Key": UI_API_KEY}, + data={"active": active}, + ).json() + + +def get_state(): + return requests.get( + f"{HOST_URL}/plugin/continuousprint/state", headers={"X-Api-Key": UI_API_KEY} + ).json() + + +if __name__ == "__main__": + print( + "Sending example requests - will stop printer and get its state in two requests" + ) + set_active(active=False) + print(get_state()) diff --git a/api_examples/jquery.js b/api_examples/jquery.js new file mode 100644 index 0000000..a3b25a7 --- /dev/null +++ b/api_examples/jquery.js @@ -0,0 +1,28 @@ +// See https://docs.octoprint.org/en/master/api/general.html#authorization for +// where to get this value +const UI_API_KEY = "YOUR_KEY_HERE"; + +const setActive = function(active=true, callback) { + $.ajax({ + url: "plugin/continuousprint/set_active", + type: "POST", + dataType: "json", + headers: {"X-Api-Key":UI_API_KEY}, + data: {active} + }).done(callback); +}; + +const getState = function(callback) { + $.ajax({ + url: "plugin/continuousprint/state", + type: "GET", + dataType: "json", + headers: {"X-Api-Key":UI_API_KEY}, + }).done(callback) +}; + +console.log("Stopping print queue"); +setActive(false, function(data) {console.log('stopped');}); + +console.log("Getting state"); +getState(function(data) {console.log(data);}); diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index db634cc..14e4fd6 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -250,20 +250,23 @@ def start_print(self, item, clear_bed=True): except InvalidFileType: self._msg("File not gcode: " + item.path, type="error") - def state_json(self, changed=None): - # Values are stored serialized, so we need to create a json string and inject them + def state_json(self, extra_message=None): + # Values are stored json-serialized, so we need to create a json string and inject them into it q = self._settings.get([QUEUE_KEY]) - if changed is not None: - q = json.loads(q) - for i in changed: - if i < len(q): # no deletion of last item - q[i]["changed"] = True - q = json.dumps(q) - - resp = '{"active": %s, "status": "%s", "queue": %s}' % ( + + # Format extra message as key:value + if extra_message is not None: + extra_message = f', extra_message: "{extra_message}"' + else: + extra_message = "" + + # IMPORTANT: Non-additive changes to this response string must be released in a MAJOR version bump + # (e.g. 1.4.1 -> 2.0.0). + resp = '{"active": %s, "status": "%s", "queue": %s%s}' % ( "true" if hasattr(self, "d") and self.d.active else "false", "Initializing" if not hasattr(self, "d") else self.d.status, q, + extra_message, ) return resp @@ -274,24 +277,52 @@ def resume_action_handler(self, comm, line, action, *args, **kwargs): if self.paused: self.d.set_active() - # API methods + # Public API method returning the full state of the plugin in JSON format. + # See `state_json()` for return values. @octoprint.plugin.BlueprintPlugin.route("/state", methods=["GET"]) @restricted_access def state(self): return self.state_json() - @octoprint.plugin.BlueprintPlugin.route("/move", methods=["POST"]) + # Public method - enables/disables management and returns the current state + # IMPORTANT: Non-additive changes to this method MUST be done via MAJOR version bump + # (e.g. 1.4.1 -> 2.0.0) + @octoprint.plugin.BlueprintPlugin.route("/set_active", methods=["POST"]) @restricted_access - def move(self): - if not Permissions.PLUGIN_CONTINUOUSPRINT_CHQUEUE.can(): + def set_active(self): + if not Permissions.PLUGIN_CONTINUOUSPRINT_STARTQUEUE.can(): return flask.make_response("Insufficient Rights", 403) self._logger.info("attempt failed due to insufficient permissions.") - idx = int(flask.request.form["idx"]) - count = int(flask.request.form["count"]) - offs = int(flask.request.form["offs"]) - self.q.move(idx, count, offs) - return self.state_json(changed=range(idx + offs, idx + offs + count)) + self.d.set_active( + flask.request.form["active"] == "true", + printer_ready=(self._printer.get_state_id() == "OPERATIONAL"), + ) + return self.state_json() + + # PRIVATE API method - may change without warning. + @octoprint.plugin.BlueprintPlugin.route("/clear", methods=["POST"]) + @restricted_access + def clear(self): + i = 0 + keep_failures = flask.request.form["keep_failures"] == "true" + keep_non_ended = flask.request.form["keep_non_ended"] == "true" + self._logger.info( + f"Clearing queue (keep_failures={keep_failures}, keep_non_ended={keep_non_ended})" + ) + changed = [] + while i < len(self.q): + v = self.q[i] + self._logger.info(f"{v.name} -- end_ts {v.end_ts} result {v.result}") + if v.end_ts is None and keep_non_ended: + i = i + 1 + elif v.result == "failure" and keep_failures: + i = i + 1 + else: + del self.q[i] + changed.append(i) + return self.state_json() + # PRIVATE API METHOD - may change without warning. @octoprint.plugin.BlueprintPlugin.route("/assign", methods=["POST"]) @restricted_access def assign(self): @@ -315,8 +346,24 @@ def assign(self): for i in items ] ) - return self.state_json(changed=[]) + return self.state_json() + + # DEPRECATED + @octoprint.plugin.BlueprintPlugin.route("/move", methods=["POST"]) + @restricted_access + def move(self): + if not Permissions.PLUGIN_CONTINUOUSPRINT_CHQUEUE.can(): + return flask.make_response("Insufficient Rights", 403) + self._logger.info("attempt failed due to insufficient permissions.") + idx = int(flask.request.form["idx"]) + count = int(flask.request.form["count"]) + offs = int(flask.request.form["offs"]) + self.q.move(idx, count, offs) + depr = "DEPRECATED: plugin/continuousprint/move is no longer used and will be removed in the next major release." + self._logger.warn(depr) + return self.state_json(depr) + # DEPRECATED @octoprint.plugin.BlueprintPlugin.route("/add", methods=["POST"]) @restricted_access def add(self): @@ -342,8 +389,11 @@ def add(self): ], idx, ) - return self.state_json(changed=range(idx, idx + len(items))) + depr = "DEPRECATED: plugin/continuousprint/add is no longer used and will be removed in the next major release." + self._logger.warn(depr) + return self.state_json(depr) + # DEPRECATED @octoprint.plugin.BlueprintPlugin.route("/remove", methods=["POST"]) @restricted_access def remove(self): @@ -353,42 +403,12 @@ def remove(self): idx = int(flask.request.form["idx"]) count = int(flask.request.form["count"]) self.q.remove(idx, count) - return self.state_json(changed=[idx]) - - @octoprint.plugin.BlueprintPlugin.route("/set_active", methods=["POST"]) - @restricted_access - def set_active(self): - if not Permissions.PLUGIN_CONTINUOUSPRINT_STARTQUEUE.can(): - return flask.make_response("Insufficient Rights", 403) - self._logger.info("attempt failed due to insufficient permissions.") - self.d.set_active( - flask.request.form["active"] == "true", - printer_ready=(self._printer.get_state_id() == "OPERATIONAL"), - ) - return self.state_json() - @octoprint.plugin.BlueprintPlugin.route("/clear", methods=["POST"]) - @restricted_access - def clear(self): - i = 0 - keep_failures = flask.request.form["keep_failures"] == "true" - keep_non_ended = flask.request.form["keep_non_ended"] == "true" - self._logger.info( - f"Clearing queue (keep_failures={keep_failures}, keep_non_ended={keep_non_ended})" - ) - changed = [] - while i < len(self.q): - v = self.q[i] - self._logger.info(f"{v.name} -- end_ts {v.end_ts} result {v.result}") - if v.end_ts is None and keep_non_ended: - i = i + 1 - elif v.result == "failure" and keep_failures: - i = i + 1 - else: - del self.q[i] - changed.append(i) - return self.state_json(changed=changed) + depr = "DEPRECATED: plugin/continuousprint/remove is no longer used and will be removed in the next major release." + self._logger.warn(depr) + return self.state_json(depr) + # DEPRECATED @octoprint.plugin.BlueprintPlugin.route("/reset", methods=["POST"]) @restricted_access def reset(self): @@ -398,7 +418,9 @@ def reset(self): i.start_ts = None i.end_ts = None self.q.remove(idx, len(idxs)) - return self.state_json(changed=[idx]) + depr = "DEPRECATED: plugin/continuousprint/reset is no longer used and will be removed in the next major release." + self._logger.warn(depr) + return self.state_json(depr) # part of TemplatePlugin def get_template_vars(self): diff --git a/continuousprint/static/js/continuousprint_api.js b/continuousprint/static/js/continuousprint_api.js index 6bd6569..fc82aa5 100644 --- a/continuousprint/static/js/continuousprint_api.js +++ b/continuousprint/static/js/continuousprint_api.js @@ -1,3 +1,5 @@ +// This is an INTERNAL implementation, not to be used for any external integrations with the continuous print plugin. +// If you see something here you want, open a FR to expose it: https://github.com/smartin015/continuousprint/issues class CPAPI { BASE = "plugin/continuousprint/" From d93007f3e4d02cb4cadc447d4d9d216cc04d0b18 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Thu, 17 Mar 2022 09:16:02 -0400 Subject: [PATCH 12/16] Update release branch --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 36e0492..b910370 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ # Any additional requirements besides OctoPrint should be listed here plugin_requires = [ - "pre-commit", # For running automated precommit scripts + "pre-commit", # For running automated precommit scripts "mkdocs-material", # Theme for documentation "mkdocs", # Documentation library "pymdown-extensions", # Fancy extensions for documentation From 4cf9383838b8f15da096e17791139567efc640fc Mon Sep 17 00:00:00 2001 From: will Date: Sun, 3 Apr 2022 19:16:50 -0600 Subject: [PATCH 13/16] removed plugin requirements from setup.py --- setup.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/setup.py b/setup.py index b910370..cb9dd91 100644 --- a/setup.py +++ b/setup.py @@ -33,12 +33,7 @@ plugin_license = "AGPLv3" # Any additional requirements besides OctoPrint should be listed here -plugin_requires = [ - "pre-commit", # For running automated precommit scripts - "mkdocs-material", # Theme for documentation - "mkdocs", # Documentation library - "pymdown-extensions", # Fancy extensions for documentation -] +plugin_requires = [] ### -------------------------------------------------------------------------------------------------------------------- ### More advanced options that you usually shouldn't have to touch follow after this point From 40db02e4c5e67408d09dffad22ed14267774b29c Mon Sep 17 00:00:00 2001 From: will Date: Sun, 10 Apr 2022 15:54:56 -0600 Subject: [PATCH 14/16] converted constants --- continuousprint/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index a551452..23c6fef 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -26,7 +26,10 @@ RESTART_MAX_RETRIES_KEY = "cp_restart_on_pause_max_restarts" RESTART_ON_PAUSE_KEY = "cp_restart_on_pause_enabled" RESTART_MAX_TIME_KEY = "cp_restart_on_pause_max_seconds" - +BED_COOLDOWN_ENABLED_KEY = "bed_cooldown_enabled" +BED_COOLDOWN_SCRIPT_KEY = "cp_bed_cooldown_script" +BED_COOLDOWN_THRESHOLD_KEY = "bed_cooldown_threshold" +BED_COOLDOWN_TIMEOUT_KEY = "bed_cooldown_timeout" class ContinuousprintPlugin( octoprint.plugin.SettingsPlugin, @@ -74,10 +77,10 @@ def get_settings_defaults(self): d[RESTART_MAX_RETRIES_KEY] = 3 d[RESTART_ON_PAUSE_KEY] = False d[RESTART_MAX_TIME_KEY] = 60 * 60 - d["bed_cooldown_enabled"] = False - d["cp_bed_cooldown_script"] = "; Put script to run before bed cools here\n" - d["bed_cooldown_threshold"] = 30 - d["bed_cooldown_timeout"] = 60 + d[BED_COOLDOWN_ENABLED_KEY] = False + d[BED_COOLDOWN_SCRIPT_KEY] = "; Put script to run before bed cools here\n" + d[BED_COOLDOWN_THRESHOLD_KEY] = 30 + d[BED_COOLDOWN_TIMEOUT_KEY] = 60 return d def _rm_temp_files(self): From 8e3a3cca0d923fe81afb96399854a23f7f9886c8 Mon Sep 17 00:00:00 2001 From: will Date: Sun, 10 Apr 2022 15:58:35 -0600 Subject: [PATCH 15/16] removed comment --- continuousprint/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index 23c6fef..0141dda 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -250,7 +250,6 @@ def bed_cooldown(self): def clear_bed(self): if self._settings.get(["bed_cooldown_enabled"]): - # If bed cooldown management is enabled activate cooldown routine self.bed_cooldown() path = self._write_temp_gcode(CLEARING_SCRIPT_KEY) self._printer.select_file(path, sd=False, printAfterSelect=True) From 3219614a126e4686e1455c15bbad32bda15b89d4 Mon Sep 17 00:00:00 2001 From: will Date: Sun, 10 Apr 2022 15:59:43 -0600 Subject: [PATCH 16/16] bed_cooldown function rename --- continuousprint/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/continuousprint/__init__.py b/continuousprint/__init__.py index 0141dda..a382a37 100644 --- a/continuousprint/__init__.py +++ b/continuousprint/__init__.py @@ -227,7 +227,7 @@ def cancel_print(self): self._msg("Print cancelled", type="error") self._printer.cancel_print() - def bed_cooldown(self): + def wait_for_bed_cooldown(self): self._logger.info("Running bed cooldown script") bed_cooldown_script = self._settings.get(["cp_bed_cooldown_script"]).split("\n") self._printer.commands(bed_cooldown_script, force=True) @@ -250,7 +250,7 @@ def bed_cooldown(self): def clear_bed(self): if self._settings.get(["bed_cooldown_enabled"]): - self.bed_cooldown() + self.wait_for_bed_cooldown() path = self._write_temp_gcode(CLEARING_SCRIPT_KEY) self._printer.select_file(path, sd=False, printAfterSelect=True)