Merge branch 'master' into tests-for-um3networkplugin

This commit is contained in:
ChrisTerBeke 2018-11-19 10:58:07 +01:00
commit 951a21ead7
37 changed files with 373 additions and 116 deletions

View File

@ -3,7 +3,6 @@
from typing import TYPE_CHECKING
from UM.PluginRegistry import PluginRegistry
from cura.API.Interface.Settings import Settings
if TYPE_CHECKING:
@ -23,9 +22,6 @@ if TYPE_CHECKING:
class Interface:
# For now we use the same API version to be consistent.
VERSION = PluginRegistry.APIVersion
def __init__(self, application: "CuraApplication") -> None:
# API methods specific to the settings portion of the UI
self.settings = Settings(application)

View File

@ -4,7 +4,6 @@ from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtProperty
from UM.PluginRegistry import PluginRegistry
from cura.API.Backups import Backups
from cura.API.Interface import Interface
from cura.API.Account import Account
@ -22,7 +21,6 @@ if TYPE_CHECKING:
class CuraAPI(QObject):
# For now we use the same API version to be consistent.
VERSION = PluginRegistry.APIVersion
__instance = None # type: "CuraAPI"
_application = None # type: CuraApplication
@ -62,4 +60,4 @@ class CuraAPI(QObject):
@property
def interface(self) -> "Interface":
return self._interface
return self._interface

View File

@ -489,7 +489,9 @@ class BuildVolume(SceneNode):
def _updateRaftThickness(self):
old_raft_thickness = self._raft_thickness
self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value")
if self._global_container_stack.extruders:
# This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value")
self._raft_thickness = 0.0
if self._adhesion_type == "raft":
self._raft_thickness = (

View File

@ -134,7 +134,7 @@ except ImportError:
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
CuraBuildType = ""
CuraDebugMode = False
CuraSDKVersion = ""
CuraSDKVersion = "5.0.0"
class CuraApplication(QtApplication):
@ -164,6 +164,7 @@ class CuraApplication(QtApplication):
super().__init__(name = "cura",
app_display_name = CuraAppDisplayName,
version = CuraVersion,
api_version = CuraSDKVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode,
tray_icon_name = "cura-icon-32.png",

View File

@ -16,7 +16,7 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.Interfaces import DefinitionContainerInterface
from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup
from cura.CuraApplication import CuraApplication
@ -538,7 +538,7 @@ class QualityManager(QObject):
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3.
#
def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainer",
def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainerInterface",
default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id
if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):

View File

@ -272,7 +272,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
# Min head hull is used for the push free
convex_hull = self._compute2DConvexHeadFull()
convex_hull = self._compute2DConvexHull()
if convex_hull:
return convex_hull.getMinkowskiHull(head_and_fans)
return None

View File

@ -419,13 +419,13 @@ class ContainerManager(QObject):
self._container_name_filters[name_filter] = entry
## Import single profile, file_url does not have to end with curaprofile
@pyqtSlot(QUrl, result="QVariantMap")
def importProfile(self, file_url: QUrl):
@pyqtSlot(QUrl, result = "QVariantMap")
def importProfile(self, file_url: QUrl) -> Dict[str, str]:
if not file_url.isValid():
return
return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
path = file_url.toLocalFile()
if not path:
return
return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
return self._container_registry.importProfile(path)
@pyqtSlot(QObject, QUrl, str)

View File

@ -5,12 +5,12 @@ import os
import re
import configparser
from typing import cast, Optional
from typing import cast, Dict, Optional
from PyQt5.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.Interfaces import ContainerInterface
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
@ -28,7 +28,7 @@ from . import GlobalStack
import cura.CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.ReaderWriters.ProfileReader import NoProfileException
from cura.ReaderWriters.ProfileReader import NoProfileException, ProfileReader
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -161,20 +161,20 @@ class CuraContainerRegistry(ContainerRegistry):
## Imports a profile from a file
#
# \param file_name \type{str} the full path and filename of the profile to import
# \return \type{Dict} dict with a 'status' key containing the string 'ok' or 'error', and a 'message' key
# containing a message for the user
def importProfile(self, file_name):
# \param file_name The full path and filename of the profile to import.
# \return Dict with a 'status' key containing the string 'ok' or 'error',
# and a 'message' key containing a message for the user.
def importProfile(self, file_name: str) -> Dict[str, str]:
Logger.log("d", "Attempting to import profile %s", file_name)
if not file_name:
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>: {1}", file_name, "Invalid path")}
plugin_registry = PluginRegistry.getInstance()
extension = file_name.split(".")[-1]
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
machine_extruders = []
for position in sorted(global_stack.extruders):
@ -183,7 +183,7 @@ class CuraContainerRegistry(ContainerRegistry):
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension:
continue
profile_reader = plugin_registry.getPluginObject(plugin_id)
profile_reader = cast(ProfileReader, plugin_registry.getPluginObject(plugin_id))
try:
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
except NoProfileException:
@ -221,13 +221,13 @@ class CuraContainerRegistry(ContainerRegistry):
# Make sure we have a profile_definition in the file:
if profile_definition is None:
break
machine_definition = self.findDefinitionContainers(id = profile_definition)
if not machine_definition:
machine_definitions = self.findDefinitionContainers(id = profile_definition)
if not machine_definitions:
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
return {"status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
}
machine_definition = machine_definition[0]
machine_definition = machine_definitions[0]
# Get the expected machine definition.
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
@ -274,11 +274,12 @@ class CuraContainerRegistry(ContainerRegistry):
setting_value = global_profile.getProperty(qc_setting_key, "value")
setting_definition = global_stack.getSettingDefinition(qc_setting_key)
new_instance = SettingInstance(setting_definition, profile)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
profile.addInstance(new_instance)
profile.setDirty(True)
if setting_definition is not None:
new_instance = SettingInstance(setting_definition, profile)
new_instance.setProperty("value", setting_value)
new_instance.resetState() # Ensure that the state is not seen as a user state.
profile.addInstance(new_instance)
profile.setDirty(True)
global_profile.removeInstance(qc_setting_key, postpone_emit=True)
extruder_profiles.append(profile)
@ -290,7 +291,7 @@ class CuraContainerRegistry(ContainerRegistry):
for profile_index, profile in enumerate(profile_or_list):
if profile_index == 0:
# This is assumed to be the global profile
profile_id = (global_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
profile_id = (cast(ContainerInterface, global_stack.getBottom()).getId() + "_" + name_seed).lower().replace(" ", "_")
elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile

View File

@ -5,6 +5,7 @@ from typing import Any, List, Optional, TYPE_CHECKING
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from UM.Settings.SettingFunction import SettingFunction
from UM.Logger import Logger
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
@ -38,7 +39,11 @@ class CuraFormulaFunctions:
extruder_position = int(machine_manager.defaultExtruderPosition)
global_stack = machine_manager.activeMachine
extruder_stack = global_stack.extruders[str(extruder_position)]
try:
extruder_stack = global_stack.extruders[str(extruder_position)]
except KeyError:
Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available" % (property_key, extruder_position))
return None
value = extruder_stack.getRawProperty(property_key, "value", context = context)
if isinstance(value, SettingFunction):

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
@ -29,6 +29,7 @@ class ChangeLog(Extension, QObject,):
self._change_logs = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog"))
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
def getChangeLogs(self):

View File

@ -1,6 +1,6 @@
{
"source_version": "15.04",
"target_version": 3,
"target_version": "4.5",
"translation": {
"machine_nozzle_size": "nozzle_size",

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser # For reading the legacy profile INI files.
@ -6,6 +6,7 @@ import io
import json # For reading the Dictionary of Doom.
import math # For mathematical operations included in the Dictionary of Doom.
import os.path # For concatenating the path to the plugin and the relative path to the Dictionary of Doom.
from typing import Dict
from UM.Application import Application # To get the machine manager to create the new profile in.
from UM.Logger import Logger # Logging errors.
@ -33,10 +34,11 @@ class LegacyProfileReader(ProfileReader):
# \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):
def prepareDefaults(self, json: Dict[str, Dict[str, str]]) -> Dict[str, str]:
defaults = {}
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]
if "defaults" in json:
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]
return defaults
## Prepares the local variables that can be used in evaluation of computing
@ -80,11 +82,10 @@ class LegacyProfileReader(ProfileReader):
Logger.log("i", "Importing legacy profile from file " + file_name + ".")
container_registry = ContainerRegistry.getInstance()
profile_id = container_registry.uniqueName("Imported Legacy Profile")
profile = InstanceContainer(profile_id) # Create an empty profile.
parser = configparser.ConfigParser(interpolation = None)
input_parser = configparser.ConfigParser(interpolation = None)
try:
parser.read([file_name]) # Parse the INI file.
input_parser.read([file_name]) # Parse the INI file.
except Exception as e:
Logger.log("e", "Unable to open legacy profile %s: %s", file_name, str(e))
return None
@ -92,7 +93,7 @@ class LegacyProfileReader(ProfileReader):
# Legacy Cura saved the profile under the section "profile_N" where N is the ID of a machine, except when you export in which case it saves it in the section "profile".
# Since importing multiple machine profiles is out of scope, just import the first section we find.
section = ""
for found_section in parser.sections():
for found_section in input_parser.sections():
if found_section.startswith("profile"):
section = found_section
break
@ -110,15 +111,13 @@ class LegacyProfileReader(ProfileReader):
return None
defaults = self.prepareDefaults(dict_of_doom)
legacy_settings = self.prepareLocals(parser, section, defaults) #Gets the settings from the legacy profile.
legacy_settings = self.prepareLocals(input_parser, section, defaults) #Gets the settings from the legacy profile.
#Check the target version in the Dictionary of Doom with this application version.
if "target_version" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no target version. Is it the correct JSON file?")
return None
if InstanceContainer.Version != dict_of_doom["target_version"]:
Logger.log("e", "Dictionary of Doom of legacy profile reader (version %s) is not in sync with the current instance container version (version %s)!", dict_of_doom["target_version"], str(InstanceContainer.Version))
return None
# Serialised format into version 4.5. Do NOT upgrade this, let the version upgrader handle it.
output_parser = configparser.ConfigParser(interpolation = None)
output_parser.add_section("general")
output_parser.add_section("metadata")
output_parser.add_section("values")
if "translation" not in dict_of_doom:
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
@ -127,7 +126,7 @@ class LegacyProfileReader(ProfileReader):
quality_definition = current_printer_definition.getMetaDataEntry("quality_definition")
if not quality_definition:
quality_definition = current_printer_definition.getId()
profile.setDefinition(quality_definition)
output_parser["general"]["definition"] = quality_definition
for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations.
old_setting_expression = dict_of_doom["translation"][new_setting]
compiled = compile(old_setting_expression, new_setting, "eval")
@ -140,37 +139,34 @@ class LegacyProfileReader(ProfileReader):
definitions = current_printer_definition.findDefinitions(key = new_setting)
if definitions:
if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura.
profile.setProperty(new_setting, "value", new_value) # Store the setting in the profile!
output_parser["values"][new_setting] = str(new_value) # Store the setting in the profile!
if len(profile.getAllKeys()) == 0:
if len(output_parser["values"]) == 0:
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
profile.setMetaDataEntry("type", "profile")
# don't know what quality_type it is based on, so use "normal" by default
profile.setMetaDataEntry("quality_type", "normal")
profile.setName(profile_id)
profile.setDirty(True)
output_parser["general"]["version"] = "4"
output_parser["general"]["name"] = profile_id
output_parser["metadata"]["type"] = "quality_changes"
output_parser["metadata"]["quality_type"] = "normal" # Don't know what quality_type it is based on, so use "normal" by default.
output_parser["metadata"]["position"] = "0" # We only support single extrusion.
output_parser["metadata"]["setting_version"] = "5" # What the dictionary of doom is made for.
#Serialise and deserialise in order to perform the version upgrade.
parser = configparser.ConfigParser(interpolation = None)
data = profile.serialize()
parser.read_string(data)
parser["general"]["version"] = "1"
if parser.has_section("values"):
parser["settings"] = parser["values"]
del parser["values"]
# Serialise in order to perform the version upgrade.
stream = io.StringIO()
parser.write(stream)
output_parser.write(stream)
data = stream.getvalue()
profile.deserialize(data)
# The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition
# again.
profile.setDefinition(quality_definition)
profile = InstanceContainer(profile_id)
profile.deserialize(data) # Also performs the version upgrade.
profile.setDirty(True)
#We need to return one extruder stack and one global stack.
global_container_id = container_registry.uniqueName("Global Imported Legacy Profile")
# We duplicate the extruder profile into the global stack.
# This may introduce some settings that are global in the extruder stack and some settings that are per-extruder in the global stack.
# We don't care about that. The engine will ignore them anyway.
global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile.
del global_profile.getMetaData()["position"] # Has no position because it's global.
global_profile.setDirty(True)
profile_definition = "fdmprinter"

View File

@ -0,0 +1,190 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser # An input for some functions we're testing.
import os.path # To find the integration test .ini files.
import pytest # To register tests with.
import unittest.mock # To mock the application, plug-in and container registry out.
import UM.Application # To mock the application out.
import UM.PluginRegistry # To mock the plug-in 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 LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
from LegacyProfileReader import LegacyProfileReader # The module we're testing.
@pytest.fixture
def legacy_profile_reader():
return LegacyProfileReader()
test_prepareDefaultsData = [
{
"defaults":
{
"foo": "bar"
},
"cheese": "delicious"
},
{
"cat": "fluffy",
"dog": "floofy"
}
]
@pytest.mark.parametrize("input", test_prepareDefaultsData)
def test_prepareDefaults(legacy_profile_reader, input):
output = legacy_profile_reader.prepareDefaults(input)
if "defaults" in input:
assert input["defaults"] == output
else:
assert output == {}
test_prepareLocalsData = [
( # Ordinary case.
{ # Parser data.
"profile":
{
"layer_height": "0.2",
"infill_density": "30"
}
},
{ # Defaults.
"layer_height": "0.1",
"infill_density": "20",
"line_width": "0.4"
}
),
( # Empty data.
{ # Parser data.
"profile":
{
}
},
{ # Defaults.
}
),
( # All defaults.
{ # Parser data.
"profile":
{
}
},
{ # Defaults.
"foo": "bar",
"boo": "far"
}
),
( # Multiple config sections.
{ # Parser data.
"some_other_name":
{
"foo": "bar"
},
"profile":
{
"foo": "baz" #Not the same as in some_other_name
}
},
{ # Defaults.
"foo": "bla"
}
)
]
@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsData)
def test_prepareLocals(legacy_profile_reader, parser_data, defaults):
parser = configparser.ConfigParser()
parser.read_dict(parser_data)
output = legacy_profile_reader.prepareLocals(parser, "profile", defaults)
assert set(defaults.keys()) <= set(output.keys()) # All defaults must be in there.
assert set(parser_data["profile"]) <= set(output.keys()) # All overwritten values must be in there.
for key in output:
if key in parser_data["profile"]:
assert output[key] == parser_data["profile"][key] # If overwritten, must be the overwritten value.
else:
assert output[key] == defaults[key] # Otherwise must be equal to the default.
test_prepareLocalsNoSectionErrorData = [
( # Section does not exist.
{ # Parser data.
"some_other_name":
{
"foo": "bar"
},
},
{ # Defaults.
"foo": "baz"
}
)
]
## Test cases where a key error is expected.
@pytest.mark.parametrize("parser_data, defaults", test_prepareLocalsNoSectionErrorData)
def test_prepareLocalsNoSectionError(legacy_profile_reader, parser_data, defaults):
parser = configparser.ConfigParser()
parser.read_dict(parser_data)
with pytest.raises(configparser.NoSectionError):
legacy_profile_reader.prepareLocals(parser, "profile", defaults)
intercepted_data = ""
@pytest.mark.parametrize("file_name", ["normal_case.ini"])
def test_read(legacy_profile_reader, file_name):
# Mock out all dependencies. Quite a lot!
global_stack = unittest.mock.MagicMock()
global_stack.getProperty = unittest.mock.MagicMock(return_value = 1) # For machine_extruder_count setting.
def getMetaDataEntry(key, default_value = ""):
if key == "quality_definition":
return "mocked_quality_definition"
if key == "has_machine_quality":
return "True"
global_stack.definition.getMetaDataEntry = getMetaDataEntry
global_stack.definition.getId = unittest.mock.MagicMock(return_value = "mocked_global_definition")
application = unittest.mock.MagicMock()
application.getGlobalContainerStack = unittest.mock.MagicMock(return_value = global_stack)
application_getInstance = unittest.mock.MagicMock(return_value = application)
container_registry = unittest.mock.MagicMock()
container_registry_getInstance = unittest.mock.MagicMock(return_value = container_registry)
container_registry.uniqueName = unittest.mock.MagicMock(return_value = "Imported Legacy Profile")
container_registry.findDefinitionContainers = unittest.mock.MagicMock(return_value = [global_stack.definition])
UM.Settings.InstanceContainer.setContainerRegistry(container_registry)
plugin_registry = unittest.mock.MagicMock()
plugin_registry_getInstance = unittest.mock.MagicMock(return_value = plugin_registry)
plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__))
# Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader.
def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
global intercepted_data
intercepted_data = data
parser = configparser.ConfigParser()
parser.read_string(data)
self._metadata["position"] = parser["metadata"]["position"]
def duplicate(self, new_id, new_name):
self._metadata["id"] = new_id
self._metadata["name"] = new_name
return self
with unittest.mock.patch.object(UM.Application.Application, "getInstance", application_getInstance):
with unittest.mock.patch.object(UM.Settings.ContainerRegistry.ContainerRegistry, "getInstance", container_registry_getInstance):
with unittest.mock.patch.object(UM.PluginRegistry.PluginRegistry, "getInstance", plugin_registry_getInstance):
with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "deserialize", deserialize):
with unittest.mock.patch.object(UM.Settings.InstanceContainer.InstanceContainer, "duplicate", duplicate):
result = legacy_profile_reader.read(os.path.join(os.path.dirname(__file__), file_name))
assert len(result) == 1
# Let's see what's inside the actual output file that we generated.
parser = configparser.ConfigParser()
parser.read_string(intercepted_data)
assert parser["general"]["definition"] == "mocked_quality_definition"
assert parser["general"]["version"] == "4" # Yes, before we upgraded.
assert parser["general"]["name"] == "Imported Legacy Profile" # Because we overwrote uniqueName.
assert parser["metadata"]["type"] == "quality_changes"
assert parser["metadata"]["quality_type"] == "normal"
assert parser["metadata"]["position"] == "0"
assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.

View File

@ -0,0 +1,7 @@
[profile]
foo = bar
boo = far
fill_overlap = 3
[alterations]
some = values

View File

@ -32,7 +32,8 @@ class PostProcessingPlugin(QObject, Extension):
def __init__(self, parent = None) -> None:
QObject.__init__(self, parent)
Extension.__init__(self)
self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
self.setMenuName(i18n_catalog.i18nc("@item:inmenu", "Post Processing"))
self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Modify G-Code"), self.showPopup)
self._view = None
# Loaded scripts are all scripts that can be used

View File

@ -12,7 +12,7 @@ from UM.Qt.ListModel import ListModel
from .ConfigsModel import ConfigsModel
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed.
class PackagesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
@ -70,7 +70,7 @@ class PackagesModel(ListModel):
# Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier
# to process.
link_list = package['data']['links'] if 'links' in package['data'] else []
link_list = package["data"]["links"] if "links" in package["data"] else []
links_dict = {d["title"]: d["url"] for d in link_list}
if "author_id" not in package["author"] or "display_name" not in package["author"]:

View File

@ -172,18 +172,18 @@ class Toolbox(QObject, Extension):
self._cloud_api_version = self._getCloudAPIVersion()
self._cloud_api_root = self._getCloudAPIRoot()
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
cloud_api_root=self._cloud_api_root,
cloud_api_version=self._cloud_api_version,
sdk_version=self._sdk_version
cloud_api_root = self._cloud_api_root,
cloud_api_version = self._cloud_api_version,
sdk_version = self._sdk_version
)
self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url=self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url=self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url=self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url)),
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url=self._api_url))
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url = self._api_url)),
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".format(base_url = self._api_url))
}
# Get the API root for the packages API depending on Cura version settings.
@ -209,11 +209,11 @@ class Toolbox(QObject, Extension):
# Get the packages version depending on Cura version settings.
def _getSDKVersion(self) -> Union[int, str]:
if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion
return self._application.getAPIVersion().getMajor()
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
return self._plugin_registry.APIVersion
return self._application.getAPIVersion().getMajor()
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
return self._plugin_registry.APIVersion
return self._application.getAPIVersion().getMajor()
return cura.CuraVersion.CuraSDKVersion # type: ignore
@pyqtSlot()
@ -299,7 +299,7 @@ class Toolbox(QObject, Extension):
for plugin_id in old_plugin_ids:
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
Logger.log('i', 'Found a plugin that was installed with the old plugin browser: %s', plugin_id)
Logger.log("i", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
old_metadata = self._plugin_registry.getMetaData(plugin_id)
new_metadata = self._convertPluginMetadata(old_metadata)
@ -511,7 +511,10 @@ class Toolbox(QObject, Extension):
# version, we also need to check if the current one has a lower SDK version. If so, this package should also
# be upgradable.
elif remote_version == local_version:
can_upgrade = local_package.get("sdk_version", 0) < remote_package.get("sdk_version", 0)
# First read sdk_version_semver. If that doesn't exist, read just sdk_version (old version system).
remote_sdk_version = Version(remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0)))
local_sdk_version = Version(local_package.get("sdk_version_semver", local_package.get("sdk_version", 0)))
can_upgrade = local_sdk_version < remote_sdk_version
return can_upgrade

View File

@ -78,7 +78,7 @@
"machine_extruder_start_code":
{
"label": "Extruder Start G-Code",
"description": "Start g-code to execute whenever turning the extruder on.",
"description": "Start g-code to execute when switching to this extruder.",
"type": "str",
"default_value": "",
"settable_per_mesh": false,
@ -124,7 +124,7 @@
"machine_extruder_end_code":
{
"label": "Extruder End G-Code",
"description": "End g-code to execute whenever turning the extruder off.",
"description": "End g-code to execute when switching away from this extruder.",
"type": "str",
"default_value": "",
"settable_per_mesh": false,

View File

@ -2406,7 +2406,7 @@
"switch_extruder_retraction_amount":
{
"label": "Nozzle Switch Retraction Distance",
"description": "The amount of retraction: Set at 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
"description": "The amount of retraction when switching extruders. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
"type": "float",
"unit": "mm",
"enabled": "retraction_enable",
@ -4139,6 +4139,20 @@
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false
},
"minimum_support_area":
{
"label": "Minimum Support Area",
"description": "Minimum area size for support polygons. Polygons which have an area smaller than this value will not be generated.",
"unit": "mm²",
"type": "float",
"default_value": 0.0,
"minimum_value": "0",
"enabled": "support_enable",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": true,
"fabricate_enabled": true,
"intermediate_enabled": true
},
"support_interface_enable":
{
"label": "Enable Support Interface",
@ -4378,6 +4392,50 @@
}
}
},
"minimum_interface_area":
{
"label": "Minimum Support Interface Area",
"description": "Minimum area size for support interface polygons. Polygons which have an area smaller than this value will not be generated.",
"unit": "mm²",
"type": "float",
"default_value": 1.0,
"minimum_value": "0",
"minimum_value_warning": "minimum_support_area",
"limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_interface_enable and support_enable",
"settable_per_mesh": true,
"children":
{
"minimum_roof_area":
{
"label": "Minimum Support Roof Area",
"description": "Minimum area size for the roofs of the support. Polygons which have an area smaller than this value will not be generated.",
"unit": "mm²",
"type": "float",
"default_value": 1.0,
"value": "extruderValue(support_roof_extruder_nr, 'minimum_interface_area')",
"minimum_value": "0",
"minimum_value_warning": "minimum_support_area",
"limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and support_enable",
"settable_per_mesh": true
},
"minimum_bottom_area":
{
"label": "Minimum Support Floor Area",
"description": "Minimum area size for the floors of the support. Polygons which have an area smaller than this value will not be generated.",
"unit": "mm²",
"type": "float",
"default_value": 1.0,
"value": "extruderValue(support_bottom_extruder_nr, 'minimum_interface_area')",
"minimum_value": "0",
"minimum_value_warning": "minimum_support_area",
"limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and support_enable",
"settable_per_mesh": true
}
}
},
"support_interface_offset":
{
"label": "Support Interface Horizontal Expansion",
@ -4398,7 +4456,6 @@
"description": "Amount of offset applied to the roofs of the support.",
"unit": "mm",
"type": "float",
"minimum_value": "0",
"default_value": 0.0,
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",
@ -4413,7 +4470,6 @@
"description": "Amount of offset applied to the floors of the support.",
"unit": "mm",
"type": "float",
"minimum_value": "0",
"default_value": 0.0,
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_offset')",
"maximum_value": "extruderValue(support_extruder_nr, 'support_offset')",

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fast
weight = -1
material = fabtotum_abs
material = generic_abs
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = high
weight = 1
material = fabtotum_abs
material = generic_abs
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = normal
weight = 0
material = fabtotum_abs
material = generic_abs
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fast
weight = -1
material = fabtotum_nylon
material = generic_nylon
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = high
weight = 1
material = fabtotum_nylon
material = generic_nylon
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = normal
weight = 0
material = fabtotum_nylon
material = generic_nylon
[values]
adhesion_type = raft

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fast
weight = -1
material = fabtotum_pla
material = generic_pla
[values]
adhesion_type = skirt

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = high
weight = 1
material = fabtotum_pla
material = generic_pla
[values]
adhesion_type = skirt

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = normal
weight = 0
material = fabtotum_pla
material = generic_pla
[values]
adhesion_type = skirt

View File

@ -6,7 +6,7 @@ name = Fast Quality
[metadata]
type = quality
setting_version = 5
material = fabtotum_tpu
material = generic_tpu
quality_type = fast
weight = -1

View File

@ -6,7 +6,7 @@ name = High Quality
[metadata]
type = quality
setting_version = 5
material = fabtotum_tpu
material = generic_tpu
quality_type = high
weight = 1

View File

@ -6,7 +6,7 @@ name = Normal Quality
[metadata]
type = quality
setting_version = 5
material = fabtotum_TPU
material = generic_tpu
quality_type = normal
weight = 0

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fast
weight = 1
material = zyyx_pro_flex
material = generic_tpu
[values]
layer_height = 0.3

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fine
weight = 3
material = zyyx_pro_flex
material = generic_tpu
[values]
layer_height = 0.12

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = normal
weight = 2
material = zyyx_pro_flex
material = generic_tpu
[values]
layer_height = 0.2

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fast
weight = 1
material = zyyx_pro_pla
material = generic_pla
[values]
layer_height = 0.3

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = fine
weight = 3
material = zyyx_pro_pla
material = generic_pla
[values]
layer_height = 0.1

View File

@ -8,7 +8,7 @@ setting_version = 5
type = quality
quality_type = normal
weight = 2
material = zyyx_pro_pla
material = generic_pla
[values]
layer_height = 0.2