Convert doxygen to rst for GcodeWriter, LegacyProfileReader,

MachineSettingsAction, ModelChecker
This commit is contained in:
Nino van Hooff 2020-05-08 16:20:55 +02:00
parent 40327c4259
commit 553b09b6cf
5 changed files with 117 additions and 87 deletions

View file

@ -14,34 +14,40 @@ from cura.Machines.ContainerTree import ContainerTree
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
## Writes g-code to a file.
#
# While this poses as a mesh writer, what this really does is take the g-code
# in the entire scene and write it to an output device. Since the g-code of a
# single mesh isn't separable from the rest what with rafts and travel moves
# and all, it doesn't make sense to write just a single mesh.
#
# So this plug-in takes the g-code that is stored in the root of the scene
# node tree, adds a bit of extra information about the profiles and writes
# that to the output device.
class GCodeWriter(MeshWriter):
## The file format version of the serialised g-code.
#
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 3
## Dictionary that defines how characters are escaped when embedded in class GCodeWriter(MeshWriter):
# g-code. """Writes g-code to a file.
#
# Note that the keys of this dictionary are regex strings. The values are While this poses as a mesh writer, what this really does is take the g-code
# not. in the entire scene and write it to an output device. Since the g-code of a
single mesh isn't separable from the rest what with rafts and travel moves
and all, it doesn't make sense to write just a single mesh.
So this plug-in takes the g-code that is stored in the root of the scene
node tree, adds a bit of extra information about the profiles and writes
that to the output device.
"""
version = 3
"""The file format version of the serialised g-code.
It can only read settings with the same version as the version it was
written with. If the file format is changed in a way that breaks reverse
compatibility, increment this version number!
"""
escape_characters = { escape_characters = {
re.escape("\\"): "\\\\", # The escape character. re.escape("\\"): "\\\\", # The escape character.
re.escape("\n"): "\\n", # Newlines. They break off the comment. re.escape("\n"): "\\n", # Newlines. They break off the comment.
re.escape("\r"): "\\r" # Carriage return. Windows users may need this for visualisation in their editors. re.escape("\r"): "\\r" # Carriage return. Windows users may need this for visualisation in their editors.
} }
"""Dictionary that defines how characters are escaped when embedded in
g-code.
Note that the keys of this dictionary are regex strings. The values are
not.
"""
_setting_keyword = ";SETTING_" _setting_keyword = ";SETTING_"
@ -50,17 +56,19 @@ class GCodeWriter(MeshWriter):
self._application = Application.getInstance() self._application = Application.getInstance()
## Writes the g-code for the entire scene to a stream.
#
# Note that even though the function accepts a collection of nodes, the
# entire scene is always written to the file since it is not possible to
# separate the g-code for just specific nodes.
#
# \param stream The stream to write the g-code to.
# \param nodes This is ignored.
# \param mode Additional information on how to format the g-code in the
# file. This must always be text mode.
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode): def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
"""Writes the g-code for the entire scene to a stream.
Note that even though the function accepts a collection of nodes, the
entire scene is always written to the file since it is not possible to
separate the g-code for just specific nodes.
:param stream: The stream to write the g-code to.
:param nodes: This is ignored.
:param mode: Additional information on how to format the g-code in the
file. This must always be text mode.
"""
if mode != MeshWriter.OutputMode.TextMode: if mode != MeshWriter.OutputMode.TextMode:
Logger.log("e", "GCodeWriter does not support non-text mode.") Logger.log("e", "GCodeWriter does not support non-text mode.")
self.setInformation(catalog.i18nc("@error:not supported", "GCodeWriter does not support non-text mode.")) self.setInformation(catalog.i18nc("@error:not supported", "GCodeWriter does not support non-text mode."))
@ -88,8 +96,9 @@ class GCodeWriter(MeshWriter):
self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting.")) self.setInformation(catalog.i18nc("@warning:status", "Please prepare G-code before exporting."))
return False return False
## Create a new container with container 2 as base and container 1 written over it.
def _createFlattenedContainerInstance(self, instance_container1, instance_container2): def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
"""Create a new container with container 2 as base and container 1 written over it."""
flat_container = InstanceContainer(instance_container2.getName()) flat_container = InstanceContainer(instance_container2.getName())
# The metadata includes id, name and definition # The metadata includes id, name and definition
@ -106,15 +115,15 @@ class GCodeWriter(MeshWriter):
return flat_container return flat_container
## Serialises a container stack to prepare it for writing at the end of the
# g-code.
#
# The settings are serialised, and special characters (including newline)
# are escaped.
#
# \param settings A container stack to serialise.
# \return A serialised string of the settings.
def _serialiseSettings(self, stack): def _serialiseSettings(self, stack):
"""Serialises a container stack to prepare it for writing at the end of the g-code.
The settings are serialised, and special characters (including newline)
are escaped.
:param stack: A container stack to serialise.
:return: A serialised string of the settings.
"""
container_registry = self._application.getContainerRegistry() container_registry = self._application.getContainerRegistry()
prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line.

View file

@ -16,58 +16,67 @@ from UM.Settings.InstanceContainer import InstanceContainer # The new profile t
from cura.ReaderWriters.ProfileReader import ProfileReader # The plug-in type to implement. from cura.ReaderWriters.ProfileReader import ProfileReader # The plug-in type to implement.
## A plugin that reads profile data from legacy Cura versions.
#
# It reads a profile from an .ini file, and performs some translations on it.
# Not all translations are correct, mind you, but it is a best effort.
class LegacyProfileReader(ProfileReader): class LegacyProfileReader(ProfileReader):
## Initialises the legacy profile reader. """A plugin that reads profile data from legacy Cura versions.
#
# This does nothing since the only other function is basically stateless. It reads a profile from an .ini file, and performs some translations on it.
Not all translations are correct, mind you, but it is a best effort.
"""
def __init__(self): def __init__(self):
"""Initialises the legacy profile reader.
This does nothing since the only other function is basically stateless.
"""
super().__init__() super().__init__()
## Prepares the default values of all legacy settings.
#
# These are loaded from the Dictionary of Doom.
#
# \param json The JSON file to load the default setting values from. This
# should not be a URL but a pre-loaded JSON handle.
# \return A dictionary of the default values of the legacy Cura version.
def prepareDefaults(self, json: Dict[str, Dict[str, str]]) -> Dict[str, str]: def prepareDefaults(self, json: Dict[str, Dict[str, str]]) -> Dict[str, str]:
"""Prepares the default values of all legacy settings.
These are loaded from the Dictionary of Doom.
:param json: The JSON file to load the default setting values from. This
should not be a URL but a pre-loaded JSON handle.
:return: A dictionary of the default values of the legacy Cura version.
"""
defaults = {} defaults = {}
if "defaults" in json: if "defaults" in json:
for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict. for key in json["defaults"]: # We have to copy over all defaults from the JSON handle to a normal dict.
defaults[key] = json["defaults"][key] defaults[key] = json["defaults"][key]
return defaults return defaults
## Prepares the local variables that can be used in evaluation of computing
# new setting values from the old ones.
#
# This fills a dictionary with all settings from the legacy Cura version
# and their values, so that they can be used in evaluating the new setting
# values as Python code.
#
# \param config_parser The ConfigParser that finds the settings in the
# legacy profile.
# \param config_section The section in the profile where the settings
# should be found.
# \param defaults The default values for all settings in the legacy Cura.
# \return A set of local variables, one for each setting in the legacy
# profile.
def prepareLocals(self, config_parser, config_section, defaults): def prepareLocals(self, config_parser, config_section, defaults):
"""Prepares the local variables that can be used in evaluation of computing
new setting values from the old ones.
This fills a dictionary with all settings from the legacy Cura version
and their values, so that they can be used in evaluating the new setting
values as Python code.
:param config_parser: The ConfigParser that finds the settings in the
legacy profile.
:param config_section: The section in the profile where the settings
should be found.
:param defaults: The default values for all settings in the legacy Cura.
:return: A set of local variables, one for each setting in the legacy
profile.
"""
copied_locals = defaults.copy() # Don't edit the original! copied_locals = defaults.copy() # Don't edit the original!
for option in config_parser.options(config_section): for option in config_parser.options(config_section):
copied_locals[option] = config_parser.get(config_section, option) copied_locals[option] = config_parser.get(config_section, option)
return copied_locals return copied_locals
## Reads a legacy Cura profile from a file and returns it.
#
# \param file_name The file to read the legacy Cura profile from.
# \return The legacy Cura profile that was in the file, if any. If the
# file could not be read or didn't contain a valid profile, \code None
# \endcode is returned.
def read(self, file_name): def read(self, file_name):
"""Reads a legacy Cura profile from a file and returns it.
:param file_name: The file to read the legacy Cura profile from.
:return: The legacy Cura profile that was in the file, if any. If the
file could not be read or didn't contain a valid profile, None is returned.
"""
if file_name.split(".")[-1] != "ini": if file_name.split(".")[-1] != "ini":
return None return None
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()

View file

@ -13,7 +13,7 @@ import UM.PluginRegistry # To mock the plug-in registry out.
import UM.Settings.ContainerRegistry # To mock the container registry out. import UM.Settings.ContainerRegistry # To mock the container registry out.
import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function. import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function.
import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module. import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
@pytest.fixture @pytest.fixture
@ -126,9 +126,11 @@ test_prepareLocalsNoSectionErrorData = [
) )
] ]
## Test cases where a key error is expected.
@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsNoSectionErrorData) @pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsNoSectionErrorData)
def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, defaults): def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, defaults):
"""Test cases where a key error is expected."""
parser = configparser.ConfigParser() parser = configparser.ConfigParser()
parser.read_dict(parser_data) parser.read_dict(parser_data)

View file

@ -23,9 +23,11 @@ if TYPE_CHECKING:
catalog = UM.i18n.i18nCatalog("cura") catalog = UM.i18n.i18nCatalog("cura")
## This action allows for certain settings that are "machine only") to be modified.
# It automatically detects machine definitions that it knows how to change and attaches itself to those.
class MachineSettingsAction(MachineAction): class MachineSettingsAction(MachineAction):
"""This action allows for certain settings that are "machine only") to be modified.
It automatically detects machine definitions that it knows how to change and attaches itself to those.
"""
def __init__(self, parent: Optional["QObject"] = None) -> None: def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
self._qml_url = "MachineSettingsAction.qml" self._qml_url = "MachineSettingsAction.qml"
@ -56,9 +58,11 @@ class MachineSettingsAction(MachineAction):
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
## Triggered when the global container stack changes or when the g-code
# flavour setting is changed.
def _updateHasMaterialsInContainerTree(self) -> None: def _updateHasMaterialsInContainerTree(self) -> None:
"""Triggered when the global container stack changes or when the g-code
flavour setting is changed.
"""
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None: if global_stack is None:
return return

View file

@ -18,8 +18,8 @@ catalog = i18nCatalog("cura")
class ModelChecker(QObject, Extension): class ModelChecker(QObject, Extension):
## Signal that gets emitted when anything changed that we need to check.
onChanged = pyqtSignal() onChanged = pyqtSignal()
"""Signal that gets emitted when anything changed that we need to check."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -47,11 +47,13 @@ class ModelChecker(QObject, Extension):
if not isinstance(args[0], Camera): if not isinstance(args[0], Camera):
self._change_timer.start() self._change_timer.start()
## Called when plug-ins are initialized.
#
# This makes sure that we listen to changes of the material and that the
# button is created that indicates warnings with the current set-up.
def _pluginsInitialized(self): def _pluginsInitialized(self):
"""Called when plug-ins are initialized.
This makes sure that we listen to changes of the material and that the
button is created that indicates warnings with the current set-up.
"""
Application.getInstance().getMachineManager().rootMaterialChanged.connect(self.onChanged) Application.getInstance().getMachineManager().rootMaterialChanged.connect(self.onChanged)
self._createView() self._createView()
@ -106,8 +108,12 @@ class ModelChecker(QObject, Extension):
if node.callDecoration("isSliceable"): if node.callDecoration("isSliceable"):
yield node yield node
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
def _createView(self): def _createView(self):
"""Creates the view used by show popup.
The view is saved because of the fairly aggressive garbage collection.
"""
Logger.log("d", "Creating model checker view.") Logger.log("d", "Creating model checker view.")
# Create the plugin dialog component # Create the plugin dialog component