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",