628 lines
25 KiB
Python
628 lines
25 KiB
Python
# coding=utf-8
|
|
"""
|
|
In this module the slicing support of OctoPrint is encapsulated.
|
|
|
|
.. autoclass:: SlicingProfile
|
|
:members:
|
|
|
|
.. autoclass:: TemporaryProfile
|
|
:members:
|
|
|
|
.. autoclass:: SlicingManager
|
|
:members:
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
|
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
|
|
|
|
|
import os
|
|
import octoprint.plugin
|
|
import octoprint.events
|
|
from octoprint.settings import settings
|
|
|
|
import logging
|
|
|
|
from .exceptions import *
|
|
|
|
|
|
class SlicingProfile(object):
|
|
"""
|
|
A wrapper for slicing profiles, both meta data and actual profile data.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer this profile belongs to.
|
|
name (str): Identifier of this slicing profile.
|
|
data (object): Profile data, actual structure depends on individual slicer implementation.
|
|
display_name (str): Displayable name for this slicing profile.
|
|
description (str): Description of this slicing profile.
|
|
default (bool): Whether this is the default slicing profile for the slicer.
|
|
"""
|
|
|
|
def __init__(self, slicer, name, data, display_name=None, description=None, default=False):
|
|
self.slicer = slicer
|
|
self.name = name
|
|
self.data = data
|
|
self.display_name = display_name
|
|
self.description = description
|
|
self.default = default
|
|
|
|
|
|
class TemporaryProfile(object):
|
|
"""
|
|
A wrapper for a temporary slicing profile to be used for a slicing job, based on a :class:`SlicingProfile` with
|
|
optional ``overrides`` applied through the supplied ``save_profile`` method.
|
|
|
|
Usage example:
|
|
|
|
.. code-block:: python
|
|
|
|
temporary = TemporaryProfile(my_slicer.save_slicer_profile, my_default_profile,
|
|
overrides=my_overrides)
|
|
with (temporary) as profile_path:
|
|
my_slicer.do_slice(..., profile_path=profile_path, ...)
|
|
|
|
Arguments:
|
|
save_profile (callable): Method to use for saving the temporary profile, also responsible for applying the
|
|
supplied ``overrides``. This will be called according to the method signature of
|
|
:meth:`~octoprint.plugin.SlicerPlugin.save_slicer_profile`.
|
|
profile (SlicingProfile): The profile from which to derive the temporary profile.
|
|
overrides (dict): Optional overrides to apply to the ``profile`` for creation of the temporary profile.
|
|
"""
|
|
|
|
def __init__(self, save_profile, profile, overrides=None):
|
|
self.save_profile = save_profile
|
|
self.profile = profile
|
|
self.overrides = overrides
|
|
|
|
def __enter__(self):
|
|
import tempfile
|
|
temp_profile = tempfile.NamedTemporaryFile(prefix="slicing-profile-temp-", suffix=".profile", delete=False)
|
|
temp_profile.close()
|
|
|
|
self.temp_path = temp_profile.name
|
|
self.save_profile(self.temp_path, self.profile, overrides=self.overrides)
|
|
return self.temp_path
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
import os
|
|
try:
|
|
os.remove(self.temp_path)
|
|
except:
|
|
pass
|
|
|
|
|
|
class SlicingManager(object):
|
|
"""
|
|
The :class:`SlicingManager` is responsible for managing available slicers and slicing profiles.
|
|
|
|
Arguments:
|
|
profile_path (str): Absolute path to the base folder where all slicing profiles are stored.
|
|
printer_profile_manager (~octoprint.printer.profile.PrinterProfileManager): :class:`~octoprint.printer.profile.PrinterProfileManager`
|
|
instance to use for accessing available printer profiles, most importantly the currently selected one.
|
|
"""
|
|
|
|
def __init__(self, profile_path, printer_profile_manager):
|
|
self._logger = logging.getLogger(__name__)
|
|
|
|
self._profile_path = profile_path
|
|
self._printer_profile_manager = printer_profile_manager
|
|
|
|
self._slicers = dict()
|
|
self._slicer_names = dict()
|
|
|
|
def initialize(self):
|
|
"""
|
|
Initializes the slicing manager by loading and initializing all available
|
|
:class:`~octoprint.plugin.SlicerPlugin` implementations.
|
|
"""
|
|
self.reload_slicers()
|
|
|
|
def reload_slicers(self):
|
|
"""
|
|
Retrieves all registered :class:`~octoprint.plugin.SlicerPlugin` implementations and registers them as
|
|
available slicers.
|
|
"""
|
|
plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SlicerPlugin)
|
|
slicers = dict()
|
|
for plugin in plugins:
|
|
try:
|
|
slicers[plugin.get_slicer_properties()["type"]] = plugin
|
|
except:
|
|
self._logger.exception("Error while getting properties from slicer {}, ignoring it".format(plugin._identifier))
|
|
continue
|
|
self._slicers = slicers
|
|
|
|
@property
|
|
def slicing_enabled(self):
|
|
"""
|
|
Returns:
|
|
boolean: True if there is at least one configured slicer available, False otherwise.
|
|
"""
|
|
return len(self.configured_slicers) > 0
|
|
|
|
@property
|
|
def registered_slicers(self):
|
|
"""
|
|
Returns:
|
|
list of str: Identifiers of all available slicers.
|
|
"""
|
|
return self._slicers.keys()
|
|
|
|
@property
|
|
def configured_slicers(self):
|
|
"""
|
|
Returns:
|
|
list of str: Identifiers of all available configured slicers.
|
|
"""
|
|
return map(lambda slicer: slicer.get_slicer_properties()["type"], filter(lambda slicer: slicer.is_slicer_configured(), self._slicers.values()))
|
|
|
|
@property
|
|
def default_slicer(self):
|
|
"""
|
|
Retrieves the default slicer.
|
|
|
|
Returns:
|
|
str: The identifier of the default slicer or ``None`` if the default slicer is not registered in the
|
|
system.
|
|
"""
|
|
slicer_name = settings().get(["slicing", "defaultSlicer"])
|
|
if slicer_name in self.registered_slicers:
|
|
return slicer_name
|
|
else:
|
|
return None
|
|
|
|
def get_slicer(self, slicer, require_configured=True):
|
|
"""
|
|
Retrieves the slicer named ``slicer``. If ``require_configured`` is set to True (the default) an exception
|
|
will be raised if the slicer is not yet configured.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer to return
|
|
require_configured (boolean): Whether to raise an exception if the slicer has not been configured yet (True,
|
|
the default), or also return an unconfigured slicer (False).
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The ``slicer`` is unknown.
|
|
~octoprint.slicing.exceptions.SlicerNotConfigured: The ``slicer`` is not yet configured and ``require_configured`` was set to True.
|
|
"""
|
|
|
|
if not slicer in self._slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
if require_configured and not self._slicers[slicer].is_slicer_configured():
|
|
raise SlicerNotConfigured(slicer)
|
|
|
|
return self._slicers[slicer]
|
|
|
|
def slice(self, slicer_name, source_path, dest_path, profile_name, callback,
|
|
callback_args=None, callback_kwargs=None, overrides=None,
|
|
on_progress=None, on_progress_args=None, on_progress_kwargs=None, printer_profile_id=None, position=None):
|
|
"""
|
|
Slices ``source_path`` to ``dest_path`` using slicer ``slicer_name`` and slicing profile ``profile_name``.
|
|
Since slicing happens asynchronously, ``callback`` will be called when slicing has finished (either successfully
|
|
or not), with ``callback_args`` and ``callback_kwargs`` supplied.
|
|
|
|
If ``callback_args`` is left out, an empty argument list will be assumed for the callback. If ``callback_kwargs``
|
|
is left out, likewise an empty keyword argument list will be assumed for the callback. Note that in any case
|
|
the callback *must* support being called with the following optional keyword arguments:
|
|
|
|
_analysis
|
|
If the slicer returned analysis data of the created machine code as part of its slicing result, this keyword
|
|
argument will contain that data.
|
|
_error
|
|
If there was an error while slicing this keyword argument will contain the error message as returned from
|
|
the slicer.
|
|
_cancelled
|
|
If the slicing job was cancelled this keyword argument will be set to True.
|
|
|
|
Additionally callees may specify ``overrides`` for the specified slicing profile, e.g. a different extrusion
|
|
temperature than defined in the profile or a different layer height.
|
|
|
|
With ``on_progress``, ``on_progress_args`` and ``on_progress_kwargs``, callees may specify a callback plus
|
|
arguments and keyword arguments to call upon progress reports from the slicing job. The progress callback will
|
|
be called with a keyword argument ``_progress`` containing the current slicing progress as a value between 0
|
|
and 1 plus all additionally specified args and kwargs.
|
|
|
|
If a different printer profile than the currently selected one is to be used for slicing, its id can be provided
|
|
via the keyword argument ``printer_profile_id``.
|
|
|
|
If the ``source_path`` is to be a sliced at a different position than the print bed center, this ``position`` can
|
|
be supplied as a dictionary defining the ``x`` and ``y`` coordinate in print bed coordinates of the model's center.
|
|
|
|
Arguments:
|
|
slicer_name (str): The identifier of the slicer to use for slicing.
|
|
source_path (str): The absolute path to the source file to slice.
|
|
dest_path (str): The absolute path to the destination file to slice to.
|
|
profile_name (str): The name of the slicing profile to use.
|
|
callback (callable): A callback to call after slicing has finished.
|
|
callback_args (list or tuple): Arguments of the callback to call after slicing has finished. Defaults to
|
|
an empty list.
|
|
callback_kwargs (dict): Keyword arguments for the callback to call after slicing has finished, will be
|
|
extended by ``_analysis``, ``_error`` or ``_cancelled`` as described above! Defaults to an empty
|
|
dictionary.
|
|
overrides (dict): Overrides for the printer profile to apply.
|
|
on_progress (callable): Callback to call upon slicing progress.
|
|
on_progress_args (list or tuple): Arguments of the progress callback. Defaults to an empty list.
|
|
on_progress_kwargs (dict): Keyword arguments of the progress callback, will be extended by ``_progress``
|
|
as described above! Defaults to an empty dictionary.
|
|
printer_profile_id (str): Identifier of the printer profile for which to slice, if another than the
|
|
one currently selected is to be used.
|
|
position (dict): Dictionary containing the ``x`` and ``y`` coordinate in the print bed's coordinate system
|
|
of the sliced model's center. If not provided the model will be positioned at the print bed's center.
|
|
Example: ``dict(x=10,y=20)``.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer specified via ``slicer_name`` is unknown.
|
|
~octoprint.slicing.exceptions.SlicerNotConfigured: The slice specified via ``slicer_name`` is not configured yet.
|
|
"""
|
|
|
|
if callback_args is None:
|
|
callback_args = ()
|
|
if callback_kwargs is None:
|
|
callback_kwargs = dict()
|
|
|
|
if not slicer_name in self.configured_slicers:
|
|
if not slicer_name in self.registered_slicers:
|
|
error = "No such slicer: {slicer_name}".format(**locals())
|
|
exc = UnknownSlicer(slicer_name)
|
|
else:
|
|
error = "Slicer not configured: {slicer_name}".format(**locals())
|
|
exc = SlicerNotConfigured(slicer_name)
|
|
callback_kwargs.update(dict(_error=error, _exc=exc))
|
|
callback(*callback_args, **callback_kwargs)
|
|
raise exc
|
|
|
|
slicer = self.get_slicer(slicer_name)
|
|
|
|
printer_profile = None
|
|
if printer_profile_id is not None:
|
|
printer_profile = self._printer_profile_manager.get(printer_profile_id)
|
|
|
|
if printer_profile is None:
|
|
printer_profile = self._printer_profile_manager.get_current_or_default()
|
|
|
|
def slicer_worker(slicer, model_path, machinecode_path, profile_name, overrides, printer_profile, position, callback, callback_args, callback_kwargs):
|
|
try:
|
|
slicer_name = slicer.get_slicer_properties()["type"]
|
|
with self._temporary_profile(slicer_name, name=profile_name, overrides=overrides) as profile_path:
|
|
ok, result = slicer.do_slice(
|
|
model_path,
|
|
printer_profile,
|
|
machinecode_path=machinecode_path,
|
|
profile_path=profile_path,
|
|
position=position,
|
|
on_progress=on_progress,
|
|
on_progress_args=on_progress_args,
|
|
on_progress_kwargs=on_progress_kwargs
|
|
)
|
|
|
|
if not ok:
|
|
callback_kwargs.update(dict(_error=result))
|
|
elif result is not None and isinstance(result, dict) and "analysis" in result:
|
|
callback_kwargs.update(dict(_analysis=result["analysis"]))
|
|
except SlicingCancelled:
|
|
callback_kwargs.update(dict(_cancelled=True))
|
|
finally:
|
|
callback(*callback_args, **callback_kwargs)
|
|
|
|
import threading
|
|
slicer_worker_thread = threading.Thread(target=slicer_worker,
|
|
args=(slicer, source_path, dest_path, profile_name, overrides, printer_profile, position, callback, callback_args, callback_kwargs))
|
|
slicer_worker_thread.daemon = True
|
|
slicer_worker_thread.start()
|
|
|
|
def cancel_slicing(self, slicer_name, source_path, dest_path):
|
|
"""
|
|
Cancels the slicing job on slicer ``slicer_name`` from ``source_path`` to ``dest_path``.
|
|
|
|
Arguments:
|
|
slicer_name (str): Identifier of the slicer on which to cancel the job.
|
|
source_path (str): The absolute path to the source file being sliced.
|
|
dest_path (str): The absolute path to the destination file being sliced to.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer specified via ``slicer_name`` is unknown.
|
|
"""
|
|
|
|
slicer = self.get_slicer(slicer_name)
|
|
slicer.cancel_slicing(dest_path)
|
|
|
|
def load_profile(self, slicer, name, require_configured=True):
|
|
"""
|
|
Loads the slicing profile for ``slicer`` with the given profile ``name`` and returns it. If it can't be loaded
|
|
due to an :class:`IOError` ``None`` will be returned instead.
|
|
|
|
If ``require_configured`` is True (the default) a :class:`SlicerNotConfigured` exception will be raised
|
|
if the indicated ``slicer`` has not yet been configured.
|
|
|
|
Returns:
|
|
SlicingProfile: The requested slicing profile or None if it could not be loaded.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer specified via ``slicer`` is unknown.
|
|
~octoprint.slicing.exceptions.SlicerNotConfigured: The slicer specified via ``slicer`` has not yet been configured and
|
|
``require_configured`` was True.
|
|
~octoprint.slicing.exceptions.UnknownProfile: The profile for slicer ``slicer`` named ``name`` does not exist.
|
|
"""
|
|
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
try:
|
|
path = self.get_profile_path(slicer, name, must_exist=True)
|
|
except IOError:
|
|
return None
|
|
return self._load_profile_from_path(slicer, path, require_configured=require_configured)
|
|
|
|
def save_profile(self, slicer, name, profile, overrides=None, allow_overwrite=True, display_name=None, description=None):
|
|
"""
|
|
Saves the slicer profile ``profile`` for slicer ``slicer`` under name ``name``.
|
|
|
|
``profile`` may be either a :class:`SlicingProfile` or a :class:`dict`.
|
|
|
|
If it's a :class:`SlicingProfile`, its :attr:`~SlicingProfile.slicer``, :attr:`~SlicingProfile.name` and - if
|
|
provided - :attr:`~SlicingProfile.display_name` and :attr:`~SlicingProfile.description` attributes will be
|
|
overwritten with the supplied values.
|
|
|
|
If it's a :class:`dict`, a new :class:`SlicingProfile` instance will be created with the supplied meta data and
|
|
the profile data as the :attr:`~SlicingProfile.data` attribute.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer for which to save the ``profile``.
|
|
name (str): Identifier under which to save the ``profile``.
|
|
profile (SlicingProfile or dict): The :class:`SlicingProfile` or a :class:`dict` containing the profile
|
|
data of the profile the save.
|
|
overrides (dict): Overrides to apply to the ``profile`` before saving it.
|
|
allow_overwrite (boolean): If True (default) if a profile for the same ``slicer`` of the same ``name``
|
|
already exists, it will be overwritten. Otherwise an exception will be thrown.
|
|
display_name (str): The name to display to the user for the profile.
|
|
description (str): A description of the profile.
|
|
|
|
Returns:
|
|
SlicingProfile: The saved profile (including the applied overrides).
|
|
|
|
Raises:
|
|
ValueError: The supplied ``profile`` is neither a :class:`SlicingProfile` nor a :class:`dict`.
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer`` is unknown.
|
|
~octoprint.slicing.exceptions.ProfileAlreadyExists: A profile with name ``name`` already exists for ``slicer`` and ``allow_overwrite`` is
|
|
False.
|
|
"""
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
if not isinstance(profile, SlicingProfile):
|
|
if isinstance(profile, dict):
|
|
profile = SlicingProfile(slicer, name, profile, display_name=display_name, description=description)
|
|
else:
|
|
raise ValueError("profile must be a SlicingProfile or a dict")
|
|
else:
|
|
profile.slicer = slicer
|
|
profile.name = name
|
|
if display_name is not None:
|
|
profile.display_name = display_name
|
|
if description is not None:
|
|
profile.description = description
|
|
|
|
path = self.get_profile_path(slicer, name)
|
|
self._save_profile_to_path(slicer, path, profile, overrides=overrides, allow_overwrite=allow_overwrite)
|
|
return profile
|
|
|
|
def _temporary_profile(self, slicer, name=None, overrides=None):
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
profile = self._get_default_profile(slicer)
|
|
if name:
|
|
try:
|
|
profile = self.load_profile(slicer, name)
|
|
except (UnknownProfile, IOError):
|
|
# in that case we'll use the default profile
|
|
pass
|
|
|
|
return TemporaryProfile(self.get_slicer(slicer).save_slicer_profile, profile, overrides=overrides)
|
|
|
|
def delete_profile(self, slicer, name):
|
|
"""
|
|
Deletes the profile ``name`` for the specified ``slicer``.
|
|
|
|
If the profile does not exist, nothing will happen.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer for which to delete the profile.
|
|
name (str): Identifier of the profile to delete.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer`` is unknown.
|
|
"""
|
|
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
if not name:
|
|
raise ValueError("name must be set")
|
|
|
|
try:
|
|
path = self.get_profile_path(slicer, name, must_exist=True)
|
|
except UnknownProfile:
|
|
return
|
|
os.remove(path)
|
|
|
|
def set_default_profile(self, slicer, name, require_configured=False,
|
|
require_exists=True):
|
|
"""
|
|
Sets the given profile as default profile for the slicer.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer for which to set the default
|
|
profile.
|
|
name (str): Identifier of the profile to set as default.
|
|
require_configured (bool): Whether the slicer needs to be configured
|
|
for the action to succeed. Defaults to false. Will raise a
|
|
SlicerNotConfigured error if true and the slicer has not been
|
|
configured yet.
|
|
require_exists (bool): Whether the profile is required to exist in
|
|
order to be set as default. Defaults to true. Will raise a
|
|
UnknownProfile error if true and the profile is unknown.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer``
|
|
is unknown
|
|
~octoprint.slicing.exceptions.SlicerNotConfigured: The slicer ``slicer``
|
|
has not yet been configured and ``require_configured`` was true.
|
|
~octoprint.slicing.exceptions.UnknownProfile: The profile ``name``
|
|
was unknown for slicer ``slicer`` and ``require_exists`` was
|
|
true.
|
|
"""
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
if require_configured and not slicer in self.configured_slicers:
|
|
raise SlicerNotConfigured(slicer)
|
|
|
|
if not name:
|
|
raise ValueError("name must be set")
|
|
|
|
if require_exists and not name in self.all_profiles(slicer, require_configured=require_configured):
|
|
raise UnknownProfile(slicer, name)
|
|
|
|
default_profiles = settings().get(["slicing", "defaultProfiles"])
|
|
if not default_profiles:
|
|
default_profiles = dict()
|
|
default_profiles[slicer] = name
|
|
settings().set(["slicing", "defaultProfiles"], default_profiles)
|
|
settings().save(force=True)
|
|
|
|
def all_profiles(self, slicer, require_configured=False):
|
|
"""
|
|
Retrieves all profiles for slicer ``slicer``.
|
|
|
|
If ``require_configured`` is set to True (default is False), only will return the profiles if the ``slicer``
|
|
is already configured, otherwise a :class:`SlicerNotConfigured` exception will be raised.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer for which to retrieve all slicer profiles
|
|
require_configured (boolean): Whether to require the slicer ``slicer`` to be already configured (True)
|
|
or not (False, default). If False and the slicer is not yet configured, a :class:`~octoprint.slicing.exceptions.SlicerNotConfigured`
|
|
exception will be raised.
|
|
|
|
Returns:
|
|
dict of SlicingProfile: A dict of all :class:`SlicingProfile` instances available for the slicer ``slicer``, mapped by the identifier.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer`` is unknown.
|
|
~octoprint.slicing.exceptions.SlicerNotConfigured: The slicer ``slicer`` is not configured and ``require_configured`` was True.
|
|
"""
|
|
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
if require_configured and not slicer in self.configured_slicers:
|
|
raise SlicerNotConfigured(slicer)
|
|
|
|
profiles = dict()
|
|
slicer_profile_path = self.get_slicer_profile_path(slicer)
|
|
for entry in os.listdir(slicer_profile_path):
|
|
if not entry.endswith(".profile") or entry.startswith("."):
|
|
# we are only interested in profiles and no hidden files
|
|
continue
|
|
|
|
path = os.path.join(slicer_profile_path, entry)
|
|
profile_name = entry[:-len(".profile")]
|
|
|
|
profiles[profile_name] = self._load_profile_from_path(slicer, path, require_configured=require_configured)
|
|
return profiles
|
|
|
|
def get_slicer_profile_path(self, slicer):
|
|
"""
|
|
Retrieves the path where the profiles for slicer ``slicer`` are stored.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer for which to retrieve the path.
|
|
|
|
Returns:
|
|
str: The absolute path to the folder where the slicer's profiles are stored.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer`` is unknown.
|
|
"""
|
|
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
path = os.path.join(self._profile_path, slicer)
|
|
if not os.path.exists(path):
|
|
os.makedirs(path)
|
|
return path
|
|
|
|
def get_profile_path(self, slicer, name, must_exist=False):
|
|
"""
|
|
Retrieves the path to the profile named ``name`` for slicer ``slicer``.
|
|
|
|
If ``must_exist`` is set to True (defaults to False) a :class:`UnknownProfile` exception will be raised if the
|
|
profile doesn't exist yet.
|
|
|
|
Arguments:
|
|
slicer (str): Identifier of the slicer to which the profile belongs to.
|
|
name (str): Identifier of the profile for which to retrieve the path.
|
|
must_exist (boolean): Whether the path must exist (True) or not (False, default).
|
|
|
|
Returns:
|
|
str: The absolute path to the profile identified by ``name`` for slicer ``slicer``.
|
|
|
|
Raises:
|
|
~octoprint.slicing.exceptions.UnknownSlicer: The slicer ``slicer`` is unknown.
|
|
~octoprint.slicing.exceptions.UnknownProfile: The profile named ``name`` doesn't exist and ``must_exist`` was True.
|
|
"""
|
|
|
|
if not slicer in self.registered_slicers:
|
|
raise UnknownSlicer(slicer)
|
|
|
|
if not name:
|
|
raise ValueError("name must be set")
|
|
|
|
name = self._sanitize(name)
|
|
|
|
path = os.path.join(self.get_slicer_profile_path(slicer), "{name}.profile".format(name=name))
|
|
if not os.path.realpath(path).startswith(self._profile_path):
|
|
raise IOError("Path to profile {name} tried to break out of allows sub path".format(**locals()))
|
|
if must_exist and not (os.path.exists(path) and os.path.isfile(path)):
|
|
raise UnknownProfile(slicer, name)
|
|
return path
|
|
|
|
def _sanitize(self, name):
|
|
if name is None:
|
|
return None
|
|
|
|
if "/" in name or "\\" in name:
|
|
raise ValueError("name must not contain / or \\")
|
|
|
|
import string
|
|
valid_chars = "-_.() {ascii}{digits}".format(ascii=string.ascii_letters, digits=string.digits)
|
|
sanitized_name = ''.join(c for c in name if c in valid_chars)
|
|
sanitized_name = sanitized_name.replace(" ", "_")
|
|
return sanitized_name
|
|
|
|
def _load_profile_from_path(self, slicer, path, require_configured=False):
|
|
profile = self.get_slicer(slicer, require_configured=require_configured).get_slicer_profile(path)
|
|
default_profiles = settings().get(["slicing", "defaultProfiles"])
|
|
if default_profiles and slicer in default_profiles:
|
|
profile.default = default_profiles[slicer] == profile.name
|
|
return profile
|
|
|
|
def _save_profile_to_path(self, slicer, path, profile, allow_overwrite=True, overrides=None, require_configured=False):
|
|
self.get_slicer(slicer, require_configured=require_configured).save_slicer_profile(path, profile, allow_overwrite=allow_overwrite, overrides=overrides)
|
|
|
|
def _get_default_profile(self, slicer):
|
|
default_profiles = settings().get(["slicing", "defaultProfiles"])
|
|
if default_profiles and slicer in default_profiles:
|
|
try:
|
|
return self.load_profile(slicer, default_profiles[slicer])
|
|
except (UnknownProfile, IOError):
|
|
# in that case we'll use the slicers predefined default profile
|
|
pass
|
|
|
|
return self.get_slicer(slicer).get_slicer_default_profile()
|
|
|
|
|