diff --git a/docs/events/index.rst b/docs/events/index.rst
index 58e3dd71..bbc45947 100644
--- a/docs/events/index.rst
+++ b/docs/events/index.rst
@@ -56,7 +56,7 @@ Example
Placeholders
============
-You can use the following generic placeholders in your events:
+You can use the following generic placeholders in your event hooks:
* ``{__currentZ}``: the current Z position of the head if known, -1 if not available
* ``{__filename}``: filename of the currently selected file, "NO FILE" if not available
diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py
index d3cc6743..41f44aab 100644
--- a/src/octoprint/plugin/types.py
+++ b/src/octoprint/plugin/types.py
@@ -385,13 +385,137 @@ class TemplatePlugin(Plugin):
class SimpleApiPlugin(Plugin):
+ """
+ Utilizing the ``SimpleApiPlugin`` mixin plugins may implement a simple API based around one GET resource and one
+ resource accepting JSON commands POSTed to it. This is the easy alternative for plugin's which don't need the
+ full power of a `Flask Blueprint `_ that the :class:`BlueprintPlugin`
+ mixin offers.
+
+ Use this mixin if all you need to do is return some kind of dynamic data to your plugin from the backend
+ and/or want to react to simple commands which boil down to a type of command and a couple of flat parameters
+ supplied with it.
+
+ The simple API constructed by OctoPrint for you will be made available under ``/api/plugin//``.
+ OctoPrint will do some preliminary request validation for your defined commands, making sure the request body is in
+ the correct format (content type must be JSON) and contains all obligatory parameters for your command.
+
+ Let's take a look at a small example for such a simple API and how you would go about calling it.
+
+ Take this example of a plugin registered under plugin identifier ``mysimpleapiplugin``:
+
+ .. code-block:: python
+ :linenos:
+
+ import octoprint.plugin
+
+ import flask
+
+ class MySimpleApiPlugin(octoprint.plugin.SimpleApiPlugin):
+ def get_api_commands(self):
+ return dict(
+ command1=[],
+ command2=["some_parameter"]
+ )
+
+ def on_api_command(self, command, data):
+ import flask
+ if command == "command1":
+ parameter = "unset"
+ if "parameter" in data:
+ parameter = "set"
+ self._logger.info("command1 called, parameter is {parameter}".format(**locals()))
+ elif command == "command2":
+ self._logger.info("command2 called, some_parameter is {some_parameter}".format(**data))
+
+ def on_api_get(self, request):
+ return flask.jsonify(foo="bar")
+
+ __plugin_implementations__ = [MySimpleApiPlugin()]
+
+
+ Our plugin defines two commands, ``command1`` with no mandatory parameters and ``command2`` with one
+ mandatory parameter ``some_parameter``.
+
+ ``command1`` can also accept an optional parameter ``parameter``, and will log whether
+ that parameter was set or unset. ``command2`` will log the content of the mandatory ``some_parameter`` parameter.
+
+ A valid POST request for ``command2`` sent to ``/api/plugin/mysimpleapiplugin`` would look like this:
+
+ .. sourcecode:: http
+
+ POST /api/plugin/mysimpleapiplugin HTTP/1.1
+ Host: example.com
+ Content-Type: application/json
+ X-Api-Key: abcdef...
+
+ {
+ "command": "command2",
+ "some_parameter": "some_value",
+ "some_optional_parameter": 2342
+ }
+
+ which would produce a response like this:
+
+ .. sourcecode:: http
+
+ HTTP/1.1 204 No Content
+
+ and print something like this line to ``octoprint.log``::
+
+ 2015-02-12 17:40:21,140 - octoprint.plugins.mysimpleapiplugin - INFO - command2 called, some_parameter is some_value
+
+ A GET request on our plugin's simple API resource will only return a JSON document like this:
+
+ .. sourcecode:: http
+
+ HTTP/1.1 200 Ok
+ Content-Type: application/json
+
+ {
+ "foo": "bar"
+ }
+ """
+
def get_api_commands(self):
+ """
+ Return a dictionary here with the keys representing the accepted commands and the values being lists of
+ mandatory parameter names.
+ """
return None
def on_api_command(self, command, data):
+ """
+ Called by OctoPrint upon a POST request to ``/api/plugin/``. ``command`` will contain one of
+ the commands as specified via :func:`get_api_commands`, ``data`` will contain the full request body parsed
+ from JSON into a Python dictionary. Note that this will also contain the ``command`` attribute itself. For the
+ example given above, for the ``command2`` request the ``data`` received by the plugin would be equal to
+ ``dict(command="command2", some_parameter="some_value")``.
+
+ If your plugin returns nothing here, OctoPrint will return an empty response with return code ``204 No content``
+ for you. You may also return regular responses as you would return from any Flask view here though, e.g.
+ ``return flask.jsonify(result="some json result")`` or ``return flask.make_response("Not found", 404)``.
+
+ :param string command: the command with which the resource was called
+ :param dict data: the full request body of the POST request parsed from JSON into a Python dictionary
+ :return: ``None`` in which case OctoPrint will generate a ``204 No content`` response with empty body, or optionally
+ a proper Flask response.
+ """
return None
def on_api_get(self, request):
+ """
+ Called by OctoPrint upon a GET request to ``/api/plugin/``. ``request`` will contain the
+ received `Flask request object `_ which you may evaluate
+ for additional arguments supplied with the request.
+
+ If your plugin returns nothing here, OctoPrint will return an empty response with return code ``204 No content``
+ for you. You may also return regular responses as you would return from any Flask view here though, e.g.
+ ``return flask.jsonify(result="some json result")`` or ``return flask.make_response("Not found", 404)``.
+
+ :param request: the Flask request object
+ :return: ``None`` in which case OctoPrint will generate a ``204 No content`` response with empty body, or optionally
+ a proper Flask response.
+ """
return None
@@ -604,15 +728,60 @@ class SettingsPlugin(Plugin):
class EventHandlerPlugin(Plugin):
+ """
+ The ``EventHandlerPlugin`` mixin allows OctoPrint plugins to react to any of :ref:`OctoPrint's events `.
+ OctoPrint will call the :func:`on_event` method for any event fired on its internal event bus, supplying the
+ event type and the associated payload. Please note that until your plugin returns from that method, further event
+ processing within OctoPrint will block - the event queue itself is run asynchronously from the rest of OctoPrint,
+ but the processing of the events within the queue itself happens consecutively.
+
+ This mixin is especially interesting for plugins which want to react on things like print jobs finishing, timelapse
+ videos rendering etc.
+ """
+
def on_event(self, event, payload):
+ """
+ Called by OctoPrint upon processing of a fired event on the platform.
+
+ :param string event: the type of event that got fired, see :ref:`the list of events `_
+ for possible values
+ :param dict payload: the payload as provided with the event
+ """
pass
class SlicerPlugin(Plugin):
+ """
+ Via the ``SlicerPlugin`` mixin plugins can add support for slicing engines to be used by OctoPrint.
+
+ """
+
def is_slicer_configured(self):
+ """
+ Unless the return value of this method is ``True``, OctoPrint will not register the slicer within the slicing
+ sub system upon startup. Plugins may use this to do some start up checks to verify that e.g. the path to
+ a slicing binary as set and the binary is executable, or credentials of a cloud slicing platform are properly
+ entered etc.
+ """
return False
def get_slicer_properties(self):
+ """
+ Plugins should override this method to return a ``dict`` containing a bunch of meta data about the implemented slicer.
+
+ The expected keys in the returned ``dict`` have the following meaning:
+
+ type
+ The type identifier to use for the slicer. This should be a short unique lower case string which will be
+ used to store slicer profiles under or refer to the slicer programmatically or from the API.
+ name
+ The human readable name of the slicer. This will be displayed to the user during slicer selection.
+ same_device
+ ``True`` if the slicer runs on the same device as OctoPrint, ``False`` otherwise. Slicers running on the same
+ device will TODO
+ progress_report
+ ``True`` if the slicer can report back slicing progress to OctoPrint ``False`` otherwise.
+ """
return dict(
type=None,
name=None,
@@ -620,22 +789,71 @@ class SlicerPlugin(Plugin):
progress_report=False
)
- def get_slicer_profile_options(self):
+ def get_slicer_default_profile(self):
+ """
+ Should return a :class:`SlicingProfile` containing the default slicing profile to use with this slicer if
+ no other profile has been selected.
+ """
return None
def get_slicer_profile(self, path):
- return None
+ """
+ Should return a :class:`SlicingProfile` parsed from the slicing profile stored at the indicated ``path``.
- def get_slicer_default_profile(self):
+ :param string path: the path from which to read the slicing profile
+ """
return None
def save_slicer_profile(self, path, profile, allow_overwrite=True, overrides=None):
+ """
+ Should save the provided :class:`SlicingProfile` to the indicated ``path``, after applying any supplied
+ ``overrides``. If a profile is already saved under the indicated path and ``allow_overwrite`` is set to
+ ``False`` (defaults to ``True``), an ``IOError`` should be raised.
+
+ :param string path: the path to which to save the profile
+ :param :class:`SlicingProfile` profile: the profile to save
+ :param bool allow_overwrite: whether to allow to overwrite an existing profile at the indicated path (``True``, default)
+ or not (``False``) - if a profile already exists on the path and this is ``False``
+ and :class:`IOError` should be raised
+ :param dict overrides: profile overrides to apply to the ``profile`` before saving it
+ """
pass
- def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
+ def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None):
+ """
+ Called by OctoPrint to slice ``model_path`` for the indicated ``printer_profile``. If the ``machinecode_path`` is ``None``,
+ slicer implementations should generate it from the provided model_path.
+
+ If provided, the ``profile_path`` is guaranteed by OctoPrint to be a serialized slicing profile created through the slicing
+ plugin's own :func:`save_slicer_profile` method.
+
+ If provided, ``position`` will be a ``dict`` containing and ``x`` and a ``y`` key, indicating the position
+ the center of the model on the print bed should have in the final sliced machine code. If not provided, slicer
+ implementations should place the model in the center of the print bed.
+
+ ``on_progress`` will be a callback which expects an additional keyword argument ``_progress`` with the current
+ slicing progress which - if progress reporting is supported - the slicing plugin should call like the following:
+
+ .. code-block:: python
+
+ if on_progress is not None:
+ if on_progress_args is None:
+ on_progress_args = ()
+ if on_progress_kwargs is None:
+ on_progress_kwargs = dict()
+
+ on_progress_kwargs["_progress"] = your_plugins_slicing_progress
+ on_progress(*on_progress_args, **on_progress_kwargs)
+
+ Please note that both ``on_progress_args`` and ``on_progress_kwargs`` as supplied by OctoPrint might be ``None``,
+ so always make sure to initialize those values to sane defaults like depicted above before invoking the callback.
+ """
pass
def cancel_slicing(self, machinecode_path):
+ """
+ Cancels the slicing to the indicated file
+ """
pass
diff --git a/src/octoprint/printer/profile.py b/src/octoprint/printer/profile.py
index 2a133383..2c9363ef 100644
--- a/src/octoprint/printer/profile.py
+++ b/src/octoprint/printer/profile.py
@@ -32,6 +32,101 @@ class BedTypes(object):
return [getattr(cls, name) for name in cls.__dict__ if not name.startswith("__")]
class PrinterProfileManager(object):
+ """
+ Manager for printer profiles. Offers methods to select the globally used printer profile and to list, add, remove,
+ load and save printer profiles.
+
+ A printer profile is a ``dict`` of the following structure:
+
+ .. list-table::
+ :widths: 15 5 10 30
+ :header-rows: 1
+
+ * - Name
+ - Type
+ - Description
+ * - ``id``
+ - ``string``
+ - Internal id of the printer profile
+ * - ``name``
+ - ``string``
+ - Human readable name of the printer profile
+ * - ``model``
+ - ``string``
+ - Printer model
+ * - ``color``
+ - ``string``
+ - Color to associate with the printer profile
+ * - ``volume``
+ - ``dict``
+ - Information about the print volume
+ * - ``volume.width``
+ - ``float``
+ - Width of the print volume (X axis)
+ * - ``volume.depth``
+ - ``float``
+ - Depth of the print volume (Y axis)
+ * - ``volume.height``
+ - ``float``
+ - Height of the print volume (Z axis)
+ * - ``volume.formFactor``
+ - ``string``
+ - Form factor of the print bed, either ``rectangular`` or ``circular``
+ * - ``heatedBed``
+ - ``bool``
+ - Whether the printer has a heated bed (``True``) or not (``False``)
+ * - ``extruder``
+ - ``dict``
+ - Information about the printer's extruders
+ * - ``extruder.count``
+ - ``int``
+ - How many extruders the printer has (default 1)
+ * - ``extruder.offsets``
+ - ``list`` of ``tuple``s
+ - Extruder offsets relative to first extruder, list of (x, y) tuples, first is always (0,0)
+ * - ``extruder.nozzleDiameter``
+ - ``float``
+ - Diameter of the printer nozzle
+ * - ``axes``
+ - ``dict``
+ - Information about the printer axes
+ * - ``axes.x``
+ - ``dict``
+ - Information about the printer's X axis
+ * - ``axes.x.speed``
+ - ``float``
+ - Speed of the X axis in mm/s
+ * - ``axes.x.inverted``
+ - ``bool``
+ - Whether a positive value change moves the nozzle away from the print bed's origin (False, default) or towards it (True)
+ * - ``axes.y``
+ - ``dict``
+ - Information about the printer's Y axis
+ * - ``axes.y.speed``
+ - ``float``
+ - Speed of the Y axis in mm/s
+ * - ``axes.y.inverted``
+ - ``bool``
+ - Whether a positive value change moves the nozzle away from the print bed's origin (False, default) or towards it (True)
+ * - ``axes.z``
+ - ``dict``
+ - Information about the printer's Z axis
+ * - ``axes.z.speed``
+ - ``float``
+ - Speed of the Z axis in mm/s
+ * - ``axes.z.inverted``
+ - ``bool``
+ - Whether a positive value change moves the nozzle away from the print bed (False, default) or towards it (True)
+ * - ``axes.e``
+ - ``dict``
+ - Information about the printer's E axis
+ * - ``axes.e.speed``
+ - ``float``
+ - Speed of the E axis in mm/s
+ * - ``axes.e.inverted``
+ - ``bool``
+ - Whether a positive value change extrudes (False, default) or retracts (True) filament
+ """
default = dict(
id = "_default",