Merge remote-tracking branch 'upstream/master' into mb-group-walls

This commit is contained in:
Mark Burton 2017-07-27 16:43:25 +01:00
commit 9fc39a19c9
441 changed files with 15069 additions and 3108 deletions

3
.gitignore vendored
View File

@ -30,9 +30,6 @@ cura.desktop
.pydevproject
.settings
# Debian packaging
debian*
#Externally located plug-ins.
plugins/Doodle3D-cura-plugin
plugins/GodMode

View File

@ -14,10 +14,12 @@ For crashes and similar issues, please attach the following information:
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
* The Cura GUI log file, located at
* $User/AppData/Roaming/cura/`<Cura version>`/cura.log (Windows)
* %APPDATA%\cura\\`<Cura version>`\cura.log (Windows), or usually C:\Users\\`<your username>`\AppData\Roaming\cura\\`<Cura version>`\cura.log
* $User/Library/Application Support/cura/`<Cura version>`/cura.log (OSX)
* $USER/.local/share/cura/`<Cura version>`/cura.log (Ubuntu/Linux)
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
Dependencies
------------
@ -43,7 +45,6 @@ Please checkout [cura-build](https://github.com/Ultimaker/cura-build)
Third party plugins
-------------
* [Print Cost Calculator](https://github.com/nallath/PrintCostCalculator): Calculates weight and monetary cost of your print.
* [Post Processing Plugin](https://github.com/nallath/PostProcessingPlugin): Allows for post-processing scripts to run on g-code.
* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin): Simple scale tool for imperial to metric.
* [X3G Writer](https://github.com/Ghostkeeper/X3GWriter): Adds support for exporting X3G files.
@ -62,8 +63,30 @@ There are two ways of doing it. You can either use the generator [here](http://q
* Set your machine's dimensions with machine_width, machine_depth, and machine_height
* If your printer's origin is in the center of the bed, set machine_center_is_zero to true.
* Set your print head dimensions with the machine_head_shape parameters
* Set the nozzle offset with machine_nozzle_offset_x_1 and machine_nozzle_offset_y1
* Set the nozzle offset with machine_nozzle_offset_x and machine_nozzle_offset_y
* Set the start and end gcode in machine_start_gcode and machine_end_gcode
* If your printer has a heated bed, set visible to true under material_bed_temperature
Once you are done, put the profile you have made into resources/definitions, or in definitions in your cura profile folder.
Translating Cura
----------------
If you'd like to contribute a translation of Cura, please first look for [any existing translation](https://github.com/Ultimaker/Cura/tree/master/resources/i18n). If your language is already there in the source code but not in Cura's interface, it may be partially translated.
There are four files that need to be translated for Cura:
1. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/cura.pot
2. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/fdmextruder.def.json.pot
3. https://github.com/Ultimaker/Cura/blob/master/resources/i18n/fdmprinter.def.json.pot (This one is the most work.)
4. https://github.com/Ultimaker/Uranium/blob/master/resources/i18n/uranium.pot
Copy these files and rename them to `*.po` (remove the `t`). Then create the actual translations by filling in the empty `msgstr` entries. These are gettext files, which are plain text so you can open them with any text editor such as Notepad or GEdit, but it is probably easier with a specialised tool such as [POEdit](https://poedit.net/) or [Virtaal](http://virtaal.translatehouse.org/).
Do not hestiate to ask us about a translation or the meaning of some text via Github Issues.
Once the translation is complete, it's probably best to test them in Cura. Use your favourite software to convert the .po file to a .mo file (such as [GetText](https://www.gnu.org/software/gettext/)). Then put the .mo files in the `.../resources/i18n/<language code>/LC_MESSAGES` folder in your Cura installation. Then find your Cura configuration file (next to the log as described above, except on Linux where it is located in `~/.config/cura`) and change the language preference to the name of the folder you just created. Then start Cura. If working correctly, your Cura should now be translated.
To submit your translation, ideally you would make two pull requests where all `*.po` files are located in that same `<language code>` folder in the resources of both the Cura and Uranium repositories. Put `cura.po`, `fdmprinter.def.json.po` and `fdmextruder.def.json.po` in the Cura repository, and put `uranium.po` in the Uranium repository. Then submit the pull requests to Github. For people with less experience with Git, you can also e-mail the translations to the e-mail address listed at the top of the [cura.pot](https://github.com/Ultimaker/Cura/blob/master/resources/i18n/cura.pot) file as the `Report-Msgid-Bugs-To` entry and we'll make sure it gets checked and included.
After the translation is submitted, the Cura maintainers will check for its completeness and check whether it is consistent. We will take special care to look for common mistakes, such as translating mark-up `<message>` code and such. We are often not fluent in every language, so we expect the translator and the international users to make corrections where necessary. Of course, there will always be some mistakes in every translation.
When the next Cura release comes around, some of the texts will have changed and some new texts will have been added. Around the time when the beta is released we will invoke a string freeze, meaning that no developer is allowed to make changes to the texts. Then we will update the translation template `.pot` files and ask all our translators to update their translations. If you are unable to update the translation in time for the actual release, we will remove the language from the drop-down menu in the Preferences window. The translation stays in Cura however, so that someone might pick it up again later and update it with the newest texts. Also, users who had previously selected the language can still continue Cura in their language but English text will appear among the original text.

View File

@ -104,7 +104,6 @@ class BuildVolume(SceneNode):
# but it does not update the disallowed areas after material change
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
def _onSceneChanged(self, source):
if self._global_container_stack:
self._change_timer.start()
@ -528,6 +527,10 @@ class BuildVolume(SceneNode):
self._updateExtraZClearance()
rebuild_me = True
if setting_key in self._limit_to_extruder_settings:
self._updateDisallowedAreas()
rebuild_me = True
if rebuild_me:
self.rebuild()
@ -637,7 +640,7 @@ class BuildVolume(SceneNode):
result[extruder.getId()] = []
#Currently, the only normally printed object is the prime tower.
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable") == True:
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable"):
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
machine_width = self._global_container_stack.getProperty("machine_width", "value")
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
@ -678,7 +681,7 @@ class BuildVolume(SceneNode):
for extruder in used_extruders:
prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value")
prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
prime_y = - extruder.getProperty("extruder_prime_pos_y", "value")
prime_y = -extruder.getProperty("extruder_prime_pos_y", "value")
#Ignore extruder prime position if it is not set or if blob is disabled
if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled:
@ -717,6 +720,11 @@ class BuildVolume(SceneNode):
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
machine_disallowed_polygons.append(polygon)
# For certain machines we don't need to compute disallowed areas for each nozzle.
# So we check here and only do the nozzle offsetting if needed.
nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry(
"nozzle_offsetting_for_disallowed_areas", True)
result = {}
for extruder in used_extruders:
extruder_id = extruder.getId()
@ -736,14 +744,22 @@ class BuildVolume(SceneNode):
right_unreachable_border = 0
top_unreachable_border = 0
bottom_unreachable_border = 0
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value")
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x)
top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y)
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
# Only do nozzle offsetting if needed
if nozzle_offsetting_for_disallowed_areas:
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
if other_offset_x is None:
other_offset_x = 0
other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value")
if other_offset_y is None:
other_offset_y = 0
other_offset_y = -other_offset_y
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x)
top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y)
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
@ -892,6 +908,7 @@ class BuildVolume(SceneNode):
if not self._global_container_stack:
return 0
container_stack = self._global_container_stack
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
# If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
@ -901,21 +918,19 @@ class BuildVolume(SceneNode):
if adhesion_type == "skirt":
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
bed_adhesion_size = skirt_distance + (skirt_line_count * self._getSettingFromAdhesionExtruder("skirt_brim_line_width"))
if len(ExtruderManager.getInstance().getUsedExtruderStacks()) > 1:
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
for value in extruder_values:
bed_adhesion_size += value
bed_adhesion_size = skirt_distance + (skirt_line_count * self._getSettingFromAdhesionExtruder("skirt_brim_line_width")) * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0
if len(used_extruders) > 1:
for extruder_stack in used_extruders:
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
#We don't create an additional line for the extruder we're printing the skirt with.
bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0
elif adhesion_type == "brim":
bed_adhesion_size = self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("skirt_brim_line_width")
bed_adhesion_size = self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
for value in extruder_values:
bed_adhesion_size += value
for extruder_stack in used_extruders:
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
#We don't create an additional line for the extruder we're printing the brim with.
bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0
elif adhesion_type == "raft":
bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin")
elif adhesion_type == "none":
@ -935,7 +950,6 @@ class BuildVolume(SceneNode):
move_from_wall_radius = 0 # Moves that start from outer wall.
move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts","value") for stack in used_extruders]
travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): #For each extruder (or just global).
@ -951,7 +965,7 @@ class BuildVolume(SceneNode):
def _clamp(self, value, min_value, max_value):
return max(min(value, max_value), min_value)
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"]
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "initial_layer_line_width_factor"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
@ -959,3 +973,4 @@ class BuildVolume(SceneNode):
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
_limit_to_extruder_settings = ["wall_extruder_nr", "wall_0_extruder_nr", "wall_x_extruder_nr", "top_bottom_extruder_nr", "infill_extruder_nr", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "adhesion_extruder_nr"]

View File

@ -257,7 +257,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
# \return New Polygon instance that is offset with everything that
# influences the collision area.
def _offsetHull(self, convex_hull):
horizontal_expansion = self._getSettingProperty("xy_offset", "value")
horizontal_expansion = max(
self._getSettingProperty("xy_offset", "value"),
self._getSettingProperty("xy_offset_layer_0", "value")
)
mold_width = 0
if self._getSettingProperty("mold_enabled", "value"):
mold_width = self._getSettingProperty("mold_width", "value")
@ -332,4 +336,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Settings that change the convex hull.
#
# If these settings change, the convex hull should be recalculated.
_influencing_settings = {"xy_offset", "mold_enabled", "mold_width"}
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width"}

View File

@ -1,5 +1,6 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
@ -25,7 +26,6 @@ from UM.Settings.Validator import Validator
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Platform import Platform
from UM.Decorators import deprecated
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
@ -47,6 +47,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from cura.Settings.MachineNameValidator import MachineNameValidator
from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.MaterialsModel import MaterialsModel
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.UserProfilesModel import UserProfilesModel
@ -62,6 +63,7 @@ from . import CameraImageProvider
from . import MachineActionManager
from cura.Settings.MachineManager import MachineManager
from cura.Settings.MaterialManager import MaterialManager
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel
@ -102,7 +104,7 @@ class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 1
SettingVersion = 2
class ResourceTypes:
QmlFiles = Resources.UserType + 1
@ -117,6 +119,12 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes)
# FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
# Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
# will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
# Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@ -176,9 +184,9 @@ class CuraApplication(QtApplication):
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
{
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
}
@ -189,6 +197,7 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager()
self._machine_manager = None # This is initialized on demand.
self._material_manager = None
self._setting_inheritance_manager = None
self._additional_components = {} # Components to add to certain areas in the interface
@ -264,33 +273,38 @@ class CuraApplication(QtApplication):
with ContainerRegistry.getInstance().lockFile():
ContainerRegistry.getInstance().load()
Preferences.getInstance().addPreference("cura/active_mode", "simple")
# set the setting version for Preferences
preferences = Preferences.getInstance()
preferences.addPreference("metadata/setting_version", 0)
preferences.setValue("metadata/setting_version", self.SettingVersion) #Don't make it equal to the default so that the setting version always gets written to the file.
Preferences.getInstance().addPreference("cura/categories_expanded", "")
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
Preferences.getInstance().addPreference("view/center_on_select", True)
Preferences.getInstance().addPreference("mesh/scale_to_fit", False)
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
Preferences.getInstance().addPreference("cura/choice_on_profile_override", "always_ask")
Preferences.getInstance().addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/active_mode", "simple")
Preferences.getInstance().addPreference("cura/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}")
preferences.addPreference("cura/categories_expanded", "")
preferences.addPreference("cura/jobname_prefix", True)
preferences.addPreference("view/center_on_select", True)
preferences.addPreference("mesh/scale_to_fit", False)
preferences.addPreference("mesh/scale_tiny_meshes", True)
preferences.addPreference("cura/dialog_on_project_save", True)
preferences.addPreference("cura/asked_dialog_on_project_save", False)
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
Preferences.getInstance().addPreference("view/invert_zoom", False)
preferences.addPreference("cura/currency", "")
preferences.addPreference("cura/material_settings", "{}")
preferences.addPreference("view/invert_zoom", False)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",
"dialog_material_path"]:
Preferences.getInstance().addPreference("local_file/%s" % key, os.path.expanduser("~/"))
preferences.addPreference("local_file/%s" % key, os.path.expanduser("~/"))
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
preferences.setDefault("local_file/last_used_type", "text/x-gcode")
Preferences.getInstance().setDefault("general/visible_settings", """
preferences.setDefault("general/visible_settings", """
machine_settings
resolution
layer_height
@ -640,6 +654,7 @@ class CuraApplication(QtApplication):
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
ExtruderManager.getInstance()
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
self.getSettingInheritanceManager)
@ -665,6 +680,11 @@ class CuraApplication(QtApplication):
self._machine_manager = MachineManager.createMachineManager()
return self._machine_manager
def getMaterialManager(self, *args):
if self._material_manager is None:
self._material_manager = MaterialManager.createMaterialManager()
return self._material_manager
def getSettingInheritanceManager(self, *args):
if self._setting_inheritance_manager is None:
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
@ -708,6 +728,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
@ -791,7 +812,7 @@ class CuraApplication(QtApplication):
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivity(self, node = None):
count = 0

View File

@ -69,7 +69,7 @@ class LayerDataBuilder(MeshBuilder):
vertex_offset = 0
index_offset = 0
for layer, data in self._layers.items():
for layer, data in sorted(self._layers.items()):
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices)
self._element_counts[layer] = data.elementCount

View File

@ -65,6 +65,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._monitor_view_qml_path = ""
self._monitor_component = None
self._monitor_item = None
self._control_view_qml_path = ""
self._control_component = None
self._control_item = None
self._qml_context = None
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
@ -131,6 +136,29 @@ class PrinterOutputDevice(QObject, OutputDevice):
return self._monitor_item
@pyqtProperty(QObject, constant=True)
def controlItem(self):
if not self._control_component:
self._createControlViewFromQML()
return self._control_item
def _createControlViewFromQML(self):
path = QUrl.fromLocalFile(self._control_view_qml_path)
# Because of garbage collection we need to keep this referenced by python.
self._control_component = QQmlComponent(Application.getInstance()._engine, path)
# Check if the context was already requested before (Printer output device might have multiple items in the future)
if self._qml_context is None:
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._qml_context.setContextProperty("OutputDevice", self)
self._control_item = self._control_component.create(self._qml_context)
if self._control_item is None:
Logger.log("e", "QQmlComponent status %s", self._control_component.status())
Logger.log("e", "QQmlComponent error string %s", self._control_component.errorString())
def _createMonitorViewFromQML(self):
path = QUrl.fromLocalFile(self._monitor_view_qml_path)

View File

@ -218,24 +218,29 @@ class ContainerManager(QObject):
entries = entry_name.split("/")
entry_name = entries.pop()
sub_item_changed = False
if entries:
root_name = entries.pop(0)
root = container.getMetaDataEntry(root_name)
item = root
for entry in entries:
for _ in range(len(entries)):
item = item.get(entries.pop(0), { })
if item[entry_name] != entry_value:
sub_item_changed = True
item[entry_name] = entry_value
entry_name = root_name
entry_value = root
container.setMetaDataEntry(entry_name, entry_value)
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
container.metaDataChanged.emit(container)
return True
## Set a setting property value of the specified container.
## Set a setting property of the specified container.
#
# This will set the specified property of the specified setting of the container
# and all containers that share the same base_file (if any). The latter only
@ -269,6 +274,29 @@ class ContainerManager(QObject):
return True
## Get a setting property of the specified container.
#
# This will get the specified property of the specified setting of the
# specified container.
#
# \param container_id The ID of the container to get the setting property
# of.
# \param setting_key The key of the setting to get the property of.
# \param property_name The property to obtain.
# \return The value of the specified property. The type of this property
# value depends on the type of the property. For instance, the "value"
# property of an integer setting will be a Python int, but the "value"
# property of an enum setting will be a Python str.
@pyqtSlot(str, str, str, result = QVariant)
def getContainerProperty(self, container_id: str, setting_key: str, property_name: str):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could not get properties of container %s because it was not found.", container_id)
return ""
container = containers[0]
return container.getProperty(setting_key, property_name)
## Set the name of the specified container.
@pyqtSlot(str, str, result = bool)
def setContainerName(self, container_id, new_name):

View File

@ -282,7 +282,7 @@ class CuraContainerRegistry(ContainerRegistry):
profile.setDefinition(self._activeQualityDefinition())
if self._machineHasOwnMaterials():
active_material_id = self._activeMaterialId()
if active_material_id: # only update if there is an active material
if active_material_id and active_material_id != "empty": # only update if there is an active material
profile.addMetaDataEntry("material", active_material_id)
quality_type_criteria["material"] = active_material_id
@ -340,10 +340,8 @@ class CuraContainerRegistry(ContainerRegistry):
# \return the ID of the active material or the empty string
def _activeMaterialId(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
material = global_container_stack.findContainer({"type": "material"})
if material:
return material.getId()
if global_container_stack and global_container_stack.material:
return global_container_stack.material.getId()
return ""
## Returns true if the current machien requires its own quality profiles

View File

@ -47,6 +47,9 @@ class CuraContainerStack(ContainerStack):
self.containersChanged.connect(self._onContainersChanged)
import cura.CuraApplication #Here to prevent circular imports.
self.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
pyqtContainersChanged = pyqtSignal()

View File

@ -78,8 +78,9 @@ class ExtruderManager(QObject):
def extruderIds(self):
map = {}
global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
for position in self._extruder_trains[global_stack_id]:
map[position] = self._extruder_trains[global_stack_id][position].getId()
if global_stack_id in self._extruder_trains:
for position in self._extruder_trains[global_stack_id]:
map[position] = self._extruder_trains[global_stack_id][position].getId()
return map
@pyqtSlot(str, result = str)
@ -433,19 +434,40 @@ class ExtruderManager(QObject):
extruder_stack_id = self.extruderIds["0"]
used_extruder_stack_ids.add(extruder_stack_id)
#Get whether any of them use support.
per_mesh_stack = mesh.callDecoration("getStack")
if per_mesh_stack:
support_enabled |= per_mesh_stack.getProperty("support_enable", "value")
support_bottom_enabled |= per_mesh_stack.getProperty("support_bottom_enable", "value")
support_roof_enabled |= per_mesh_stack.getProperty("support_roof_enable", "value")
else: #Take the setting from the build extruder stack.
extruder_stack = container_registry.findContainerStacks(id = extruder_stack_id)[0]
support_enabled |= extruder_stack.getProperty("support_enable", "value")
support_bottom_enabled |= extruder_stack.getProperty("support_bottom_enable", "value")
support_roof_enabled |= extruder_stack.getProperty("support_roof_enable", "value")
# Get whether any of them use support.
stack_to_use = mesh.callDecoration("getStack") # if there is a per-mesh stack, we use it
if not stack_to_use:
# if there is no per-mesh stack, we use the build extruder for this mesh
stack_to_use = container_registry.findContainerStacks(id = extruder_stack_id)[0]
#The support extruders.
support_enabled |= stack_to_use.getProperty("support_enable", "value")
support_bottom_enabled |= stack_to_use.getProperty("support_bottom_enable", "value")
support_roof_enabled |= stack_to_use.getProperty("support_roof_enable", "value")
# Check limit to extruders
limit_to_extruder_feature_list = ["wall_extruder_nr",
"wall_0_extruder_nr",
"wall_x_extruder_nr",
"roofing_extruder_nr",
"top_bottom_extruder_nr",
"infill_extruder_nr",
]
wall_extruder_nr = None
for extruder_nr_feature_name in limit_to_extruder_feature_list:
extruder_nr = int(global_stack.getProperty(extruder_nr_feature_name, "value"))
if extruder_nr == -1:
# outer and inner wall extruder numbers should first inherit from the wall extruder number
if extruder_nr_feature_name in ["wall_0_extruder_nr", "wall_x_extruder_nr"]:
extruder_nr = wall_extruder_nr
else:
extruder_nr = 0
used_extruder_stack_ids.add(self.extruderIds[str(extruder_nr)])
if extruder_nr_feature_name == "wall_extruder_nr":
wall_extruder_nr = extruder_nr
# Check support extruders
if support_enabled:
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_infill_extruder_nr", "value"))])
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_extruder_nr_layer_0", "value"))])

View File

@ -20,11 +20,13 @@ if TYPE_CHECKING:
#
#
class ExtruderStack(CuraContainerStack):
def __init__(self, container_id, *args, **kwargs):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
self.propertiesChanged.connect(self._onPropertiesChanged)
## Overridden from ContainerStack
#
# This will set the next stack and ensure that we register this stack as an extruder.
@ -85,6 +87,22 @@ class ExtruderStack(CuraContainerStack):
if stacks:
self.setNextStack(stacks[0])
def _onPropertiesChanged(self, key, properties):
# When there is a setting that is not settable per extruder that depends on a value from a setting that is,
# we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate
# something changed for those settings.
definitions = self.getNextStack().definition.findDefinitions(key = key)
if definitions:
has_global_dependencies = False
for relation in definitions[0].relations:
if not getattr(relation.target, "settable_per_extruder", True):
has_global_dependencies = True
break
if has_global_dependencies:
self.getNextStack().propertiesChanged.emit(key, properties)
extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack",
comment = "Cura Extruder Stack",

View File

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Any, Dict
from typing import Any, Dict, Optional
from PyQt5.QtCore import pyqtProperty
@ -42,6 +42,17 @@ class GlobalStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 2
def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]:
configuration_type = None
try:
parser = self._readAndValidateSerialized(serialized)
configuration_type = parser["metadata"].get("type")
if configuration_type == "machine":
configuration_type = "machine_stack"
except Exception as e:
Logger.log("e", "Could not get configuration type: %s", e)
return configuration_type
## Add an extruder to the list of extruders of this stack.
#
# \param extruder The extruder to add.
@ -107,6 +118,21 @@ class GlobalStack(CuraContainerStack):
def setNextStack(self, next_stack: ContainerStack) -> None:
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
## Gets the approximate filament diameter that the machine requires.
#
# The approximate material diameter is the material diameter rounded to
# the nearest millimetre.
#
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer, as a string.
@pyqtProperty(str)
def approximateMaterialDiameter(self) -> str:
material_diameter = self.definition.getProperty("material_diameter", "value")
if material_diameter is None:
return "-1"
return str(round(float(material_diameter))) #Round, then convert back to string.
# protected:
# Determine whether or not we should try to get the "resolve" property instead of the

View File

@ -11,6 +11,7 @@ from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.Message import Message
from UM.Decorators import deprecated
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
@ -90,8 +91,10 @@ class MachineManager(QObject):
self._printer_output_devices = []
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
# There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged()
if active_machine_id != "":
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id):
# An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id)
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
@ -156,7 +159,7 @@ class MachineManager(QObject):
if str(index) == extruder.getMetaDataEntry("position"):
matching_extruder = extruder
break
if matching_extruder and matching_extruder.findContainer({"type": "variant"}).getName() != hotend_id:
if matching_extruder and matching_extruder.variant.getName() != hotend_id:
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
self._auto_hotends_changed[str(index)] = containers[0].getId()
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
@ -180,11 +183,10 @@ class MachineManager(QObject):
matching_extruder = extruder
break
if matching_extruder and matching_extruder.findContainer({"type": "material"}).getMetaDataEntry("GUID") != material_id:
if matching_extruder and matching_extruder.material.getMetaDataEntry("GUID") != material_id:
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
variant_container = matching_extruder.findContainer({"type": "variant"})
if self._global_container_stack.getBottom().getMetaDataEntry("has_variants") and variant_container:
variant_id = self.getQualityVariantId(self._global_container_stack.getBottom(), variant_container)
if self._global_container_stack.getBottom().getMetaDataEntry("has_variants") and matching_extruder.variant:
variant_id = self.getQualityVariantId(self._global_container_stack.getBottom(), matching_extruder.variant)
for container in containers:
if container.getMetaDataEntry("variant") == variant_id:
self._auto_materials_changed[str(index)] = container.getId()
@ -305,6 +307,7 @@ class MachineManager(QObject):
self._stacks_have_errors = self._checkStacksHaveErrors()
if old_stacks_have_errors != self._stacks_have_errors:
self.stacksValidationChanged.emit()
Application.getInstance().stacksValidationFinished.emit()
def _onActiveExtruderStackChanged(self):
self.blurSettings.emit() # Ensure no-one has focus.
@ -506,9 +509,8 @@ class MachineManager(QObject):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
variant_container = stack.findContainer({"type": "variant"})
if variant_container and variant_container != self._empty_variant_container:
result.append(variant_container.getId())
if stack.variant and stack.variant != self._empty_variant_container:
result.append(stack.variant.getId())
return result
@ -1121,6 +1123,7 @@ class MachineManager(QObject):
def createMachineManager(engine=None, script_engine=None):
return MachineManager()
@deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")
def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None):
if not definition.getMetaDataEntry("has_materials"):
return self._empty_material_container

View File

@ -0,0 +1,57 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
from cura.Settings.ContainerManager import ContainerManager
from UM.Logger import Logger
from UM.Message import Message #To create a warning message about material diameter.
from UM.i18n import i18nCatalog #Translated strings.
catalog = i18nCatalog("cura")
## Handles material-related data, processing requests to change them and
# providing data for the GUI.
#
# TODO: Move material-related managing over from the machine manager to here.
class MaterialManager(QObject):
## Creates the global values for the material manager to use.
def __init__(self, parent = None):
super().__init__(parent)
#Material diameter changed warning message.
self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
"The selected material diameter causes the material to become incompatible with the current printer."))
self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
## Creates an instance of the MaterialManager.
#
# This should only be called by PyQt to create the singleton instance of
# this class.
@staticmethod
def createMaterialManager(engine = None, script_engine = None):
return MaterialManager()
@pyqtSlot(str, str)
def showMaterialWarningMessage(self, material_id, previous_diameter):
self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
self._material_diameter_warning_message.material_id = material_id
self._material_diameter_warning_message.show()
## Called when clicking "undo" on the warning dialogue for disappeared
# materials.
#
# This executes the undo action, restoring the material diameter.
#
# \param button The identifier of the button that was pressed.
def _materialWarningMessageAction(self, message, button):
if button == "Undo":
container_manager = ContainerManager.getInstance()
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
approximate_previous_diameter = str(round(float(self._material_diameter_warning_message.previous_diameter)))
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "approximate_diameter", approximate_previous_diameter)
container_manager.setContainerProperty(self._material_diameter_warning_message.material_id, "material_diameter", "value", self._material_diameter_warning_message.previous_diameter);
message.hide()
else:
Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))

View File

@ -0,0 +1,21 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
## A model that shows a list of currently valid materials.
class MaterialsModel(InstanceContainersModel):
def __init__(self, parent = None):
super().__init__(parent)
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
## Called when the metadata of the container was changed.
#
# This makes sure that we only update when it was a material that changed.
#
# \param container The container whose metadata was changed.
def _onContainerMetaDataChanged(self, container):
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
self._update()

View File

@ -107,9 +107,9 @@ class ProfilesModel(InstanceContainersModel):
continue
#Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
skip_until_container = global_container_stack.findContainer({"type": "material"})
skip_until_container = global_container_stack.material
if not skip_until_container: #No material in stack.
skip_until_container = global_container_stack.findContainer({"type": "variant"})
skip_until_container = global_container_stack.variant
if not skip_until_container: #No variant in stack.
skip_until_container = global_container_stack.getBottom()
item["layer_height"] = str(global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId())) + unit #Fall through to the currently loaded material.

View File

@ -40,6 +40,6 @@ class QualityAndUserProfilesModel(ProfilesModel):
# Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set]
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is None]
return quality_list + filtered_quality_changes

View File

@ -26,8 +26,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
super().__init__()
self._stack = ContainerStack(stack_id = id(self))
self._stack.setDirty(False) # This stack does not need to be saved.
self._instance = InstanceContainer(container_id = "SettingOverrideInstanceContainer")
self._stack.addContainer(self._instance)
self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer"))
if ExtruderManager.getInstance().extruderCount > 1:
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
@ -46,13 +45,14 @@ class SettingOverrideDecorator(SceneNodeDecorator):
## Create a fresh decorator object
deep_copy = SettingOverrideDecorator()
## Copy the instance
deep_copy._instance = copy.deepcopy(self._instance, memo)
instance_container = copy.deepcopy(self._stack.getContainer(0), memo)
## Set the copied instance as the first (and only) instance container of the stack.
deep_copy._stack.replaceContainer(0, instance_container)
# Properly set the right extruder on the copy
deep_copy.setActiveExtruder(self._extruder_stack)
## Set the copied instance as the first (and only) instance container of the stack.
deep_copy._stack.replaceContainer(0, deep_copy._instance)
return deep_copy
## Gets the currently active extruder to print this object with.

View File

@ -56,6 +56,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._id_mapping = {}
# In Cura 2.5 and 2.6, the empty profiles used to have those long names
self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
def getNewId(self, old_id):
@ -129,6 +132,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container_list = []
material_container_list = []
resolve_strategy_keys = ["machine", "material", "quality_changes"]
self._resolve_strategies = {k: None for k in resolve_strategy_keys}
containers_found_dict = {k: False for k in resolve_strategy_keys}
#
# Read definition containers
#
@ -176,8 +183,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
container_id = self._stripFileToId(material_container_file)
materials = self._container_registry.findInstanceContainers(id=container_id)
material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
material_conflict = True
if materials:
containers_found_dict["material"] = True
if not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
material_conflict = True
Job.yieldThread()
# Check if any quality_changes instance container is in conflict.
@ -205,6 +214,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Check if quality changes already exists.
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
if quality_changes:
containers_found_dict["quality_changes"] = True
# Check if there really is a conflict by comparing the values
if quality_changes[0] != instance_container:
quality_changes_conflict = True
@ -227,21 +237,61 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Load ContainerStack files and ExtruderStack files
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
file_name, cura_file_names)
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
machine_conflict = False
for container_stack_file in [global_stack_file] + extruder_stack_files:
container_id = self._stripFileToId(container_stack_file)
serialized = archive.open(container_stack_file).read().decode("utf-8")
if machine_name == "":
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id = container_id)
if stacks:
# Check if there are any changes at all in any of the container stacks.
# Because there can be cases as follows:
# - the global stack exists but some/all of the extruder stacks DON'T exist
# - the global stack DOESN'T exist but some/all of the extruder stacks exist
# To simplify this, only check if the global stack exists or not
container_id = self._stripFileToId(global_stack_file)
serialized = archive.open(global_stack_file).read().decode("utf-8")
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id = container_id)
if stacks:
global_stack = stacks[0]
containers_found_dict["machine"] = True
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
# take into account the old empty container IDs
container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
if global_stack.getContainer(index).getId() != container_id:
machine_conflict = True
break
Job.yieldThread()
# if the global stack is found, we check if there are conflicts in the extruder stacks
if containers_found_dict["machine"] and not machine_conflict:
for extruder_stack_file in extruder_stack_files:
container_id = self._stripFileToId(extruder_stack_file)
serialized = archive.open(extruder_stack_file).read().decode("utf-8")
parser = configparser.ConfigParser()
parser.read_string(serialized)
# The check should be done for the extruder stack that's associated with the existing global stack,
# and those extruder stacks may have different IDs.
# So we check according to the positions
position = str(parser["metadata"]["position"])
if position not in global_stack.extruders:
# The extruder position defined in the project doesn't exist in this global stack.
# We can say that it is a machine conflict, but it is very hard to override the machine in this
# case because we need to override the existing extruders and add the non-existing extruders.
#
# HACK:
# To make this simple, we simply say that there is no machine conflict and create a new machine
# by default.
machine_conflict = False
break
existing_extruder_stack = global_stack.extruders[position]
# check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
if stacks[0].getContainer(index).getId() != container_id:
# take into account the old empty container IDs
container_id = self._old_empty_profile_id_dict.get(container_id, container_id)
if existing_extruder_stack.getContainer(index).getId() != container_id:
machine_conflict = True
Job.yieldThread()
break
num_visible_settings = 0
try:
@ -301,13 +351,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# - new: create a new container
# - override: override the existing container
# - None: There is no conflict, which means containers with the same IDs may or may not be there already.
# If they are there, there is no conflict between the them.
# In this case, you can either create a new one, or safely override the existing one.
# If there is an existing container, there is no conflict between the them, and default to "override"
# If there is no existing container, default to "new"
#
# Default values
for k, v in self._resolve_strategies.items():
if v is None:
self._resolve_strategies[k] = "new"
for key, strategy in self._resolve_strategies.items():
if key not in containers_found_dict or strategy is not None:
continue
self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new"
return WorkspaceReader.PreReadResult.accepted
@ -571,47 +622,43 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# --
# load global stack file
try:
# Check if a stack by this ID already exists;
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
if container_stacks:
if self._resolve_strategies["machine"] == "override":
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
# TODO: HACK
# There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
elif self._resolve_strategies["machine"] == "new":
stack = GlobalStack(global_stack_id_new)
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
# HACK
# There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
# Ensure a unique ID and name
stack._id = global_stack_id_new
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
# bound machine also needs to change.
if stack.getMetaDataEntry("machine", None):
stack.setMetaDataEntry("machine", global_stack_id_new)
# Only machines need a new name, stacks may be non-unique
stack.setName(self._container_registry.uniqueName(stack.getName()))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
else:
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
else:
# no existing container stack, so we create a new one
elif self._resolve_strategies["machine"] == "new":
# create a new global stack
stack = GlobalStack(global_stack_id_new)
# Deserialize stack by converting read data from bytes to string
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
# Ensure a unique ID and name
stack._id = global_stack_id_new
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
# bound machine also needs to change.
if stack.getMetaDataEntry("machine", None):
stack.setMetaDataEntry("machine", global_stack_id_new)
# Only machines need a new name, stacks may be non-unique
stack.setName(global_stack_id_new)
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
containers_added.append(stack)
else:
Logger.log("e", "Resolve strategy of %s for machine is not supported",
self._resolve_strategies["machine"])
global_stack = stack
Job.yieldThread()
@ -625,73 +672,40 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# --
# load extruder stack files
try:
for index, extruder_stack_file in enumerate(extruder_stack_files):
for extruder_stack_file in extruder_stack_files:
container_id = self._stripFileToId(extruder_stack_file)
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
container_stacks = self._container_registry.findContainerStacks(id = container_id)
if container_stacks:
# this container stack already exists, try to resolve
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
# deserialize new extruder stack over the current ones
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
if self._resolve_strategies["machine"] == "override":
# NOTE: This is the same code as those in the lower part
# deserialize new extruder stack over the current ones
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
elif self._resolve_strategies["machine"] == "new":
new_id = extruder_stack_id_map[container_id]
stack = ExtruderStack(new_id)
elif self._resolve_strategies["machine"] == "new":
# create a new extruder stack from this one
new_id = extruder_stack_id_map[container_id]
stack = ExtruderStack(new_id)
# HACK: the global stack can have a new name, so we need to make sure that this extruder stack
# references to the new name instead of the old one. Normally, this can be done after
# deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
# also does addExtruder() to its machine stack, so we have to make sure that it's pointing
# to the right machine BEFORE deserialization.
extruder_config = configparser.ConfigParser()
extruder_config.read_string(extruder_file_content)
extruder_config.set("metadata", "machine", global_stack_id_new)
tmp_string_io = io.StringIO()
extruder_config.write(tmp_string_io)
extruder_file_content = tmp_string_io.getvalue()
# HACK: the global stack can have a new name, so we need to make sure that this extruder stack
# references to the new name instead of the old one. Normally, this can be done after
# deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
# also does addExtruder() to its machine stack, so we have to make sure that it's pointing
# to the right machine BEFORE deserialization.
extruder_config = configparser.ConfigParser()
extruder_config.read_string(extruder_file_content)
extruder_config.set("metadata", "machine", global_stack_id_new)
tmp_string_io = io.StringIO()
extruder_config.write(tmp_string_io)
extruder_file_content = tmp_string_io.getvalue()
stack.deserialize(extruder_file_content)
stack.deserialize(extruder_file_content)
# Ensure a unique ID and name
stack._id = new_id
# Ensure a unique ID and name
stack._id = new_id
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
containers_added.append(stack)
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
containers_added.append(stack)
else:
# No extruder stack with the same ID can be found
if self._resolve_strategies["machine"] == "override":
# deserialize new extruder stack over the current ones
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
elif self._resolve_strategies["machine"] == "new":
# container not found, create a new one
stack = ExtruderStack(container_id)
# HACK: the global stack can have a new name, so we need to make sure that this extruder stack
# references to the new name instead of the old one. Normally, this can be done after
# deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
# also does addExtruder() to its machine stack, so we have to make sure that it's pointing
# to the right machine BEFORE deserialization.
extruder_config = configparser.ConfigParser()
extruder_config.read_string(extruder_file_content)
extruder_config.set("metadata", "machine", global_stack_id_new)
tmp_string_io = io.StringIO()
extruder_config.write(tmp_string_io)
extruder_file_content = tmp_string_io.getvalue()
stack.deserialize(extruder_file_content)
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
containers_added.append(stack)
else:
Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))
Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"])
extruder_stacks.append(stack)
except:
@ -849,6 +863,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
container_list = container_string.split(",")
container_ids = [container_id for container_id in container_list if container_id != ""]
# HACK: there used to be 6 containers numbering from 0 to 5 in a stack,
# now we have 7: index 5 becomes "definition_changes"
if len(container_ids) == 6:
# Hack; We used to not save the definition changes. Fix this.
container_ids.insert(5, "empty")
return container_ids
def _getMachineNameFromSerializedStack(self, serialized):
@ -861,5 +881,3 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
for entry in metadata:
return entry.text
pass

View File

@ -87,18 +87,18 @@ UM.Dialog
{
text: catalog.i18nc("@action:label", "Printer settings")
font.bold: true
width: parent.width / 3
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: parent.width / 3
width: (parent.width / 3) | 0
}
UM.TooltipArea
{
id: machineResolveTooltip
width: parent.width / 3
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: manager.machineConflict
text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?")
@ -122,12 +122,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Type")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: manager.machineType
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
@ -138,12 +138,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Name")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: manager.machineName
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
@ -160,18 +160,18 @@ UM.Dialog
{
text: catalog.i18nc("@action:label", "Profile settings")
font.bold: true
width: parent.width / 3
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: parent.width / 3
width: (parent.width / 3) | 0
}
UM.TooltipArea
{
id: qualityChangesResolveTooltip
width: parent.width / 3
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: manager.qualityChangesConflict
text: catalog.i18nc("@info:tooltip", "How should the conflict in the profile be resolved?")
@ -195,12 +195,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Name")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: manager.qualityName
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
Row
@ -210,12 +210,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Not in profile")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
width: parent.width / 3
width: (parent.width / 3) | 0
}
visible: manager.numUserSettings != 0
}
@ -226,12 +226,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Derivative from")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
width: parent.width / 3
width: (parent.width / 3) | 0
}
visible: manager.numSettingsOverridenByQualityChanges != 0
}
@ -248,18 +248,18 @@ UM.Dialog
{
text: catalog.i18nc("@action:label", "Material settings")
font.bold: true
width: parent.width / 3
width: (parent.width / 3) | 0
}
Item
{
// spacer
height: spacerHeight
width: parent.width / 3
width: (parent.width / 3) | 0
}
UM.TooltipArea
{
id: materialResolveTooltip
width: parent.width / 3
width: (parent.width / 3) | 0
height: visible ? comboboxHeight : 0
visible: manager.materialConflict
text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?")
@ -287,12 +287,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Name")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: modelData
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
}
@ -315,12 +315,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Mode")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: manager.activeMode
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
Row
@ -330,12 +330,12 @@ UM.Dialog
Label
{
text: catalog.i18nc("@action:label", "Visible settings:")
width: parent.width / 3
width: (parent.width / 3) | 0
}
Label
{
text: catalog.i18nc("@action:label", "%1 out of %2" ).arg(manager.numVisibleSettings).arg(manager.totalNumberOfSettings)
width: parent.width / 3
width: (parent.width / 3) | 0
}
}
Item // Spacer

View File

@ -22,15 +22,7 @@ def getMetaData() -> Dict:
else:
workspace_extension = "curaproject.3mf"
metaData = {
"plugin": {
"name": catalog.i18nc("@label", "3MF Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."),
"api": 3
}
}
metaData = {}
if "3MFReader.ThreeMFReader" in sys.modules:
metaData["mesh_reader"] = [
{

View File

@ -0,0 +1,8 @@
{
"name": "3MF Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for reading 3MF files.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -69,7 +69,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
# \param archive The archive to write to.
@staticmethod
def _writeContainerToArchive(container, archive):
if type(container) == type(ContainerRegistry.getInstance().getEmptyInstanceContainer()):
if isinstance(container, type(ContainerRegistry.getInstance().getEmptyInstanceContainer())):
return # Empty file, do nothing.
file_suffix = ContainerRegistry.getMimeTypeForContainer(type(container)).preferredSuffix
@ -87,14 +87,9 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
file_in_archive = zipfile.ZipInfo(file_name)
# For some reason we have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
if type(container) == ContainerStack and (container.getMetaDataEntry("network_authentication_id") or container.getMetaDataEntry("network_authentication_key")):
# TODO: Hack
# Create a shallow copy of the container, so we can filter out the network auth (if any)
container_copy = copy.deepcopy(container)
container_copy.removeMetaDataEntry("network_authentication_id")
container_copy.removeMetaDataEntry("network_authentication_key")
serialized_data = container_copy.serialize()
else:
serialized_data = container.serialize()
# Do not include the network authentication keys
ignore_keys = ["network_authentication_id", "network_authentication_key"]
serialized_data = container.serialize(ignored_metadata_keys = ignore_keys)
archive.writestr(file_in_archive, serialized_data)

View File

@ -21,15 +21,7 @@ def getMetaData():
else:
workspace_extension = "curaproject.3mf"
metaData = {
"plugin": {
"name": i18n_catalog.i18nc("@label", "3MF Writer"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
"api": 3
}
}
metaData = {}
if "3MFWriter.ThreeMFWriter" in sys.modules:
metaData["mesh_writer"] = {

View File

@ -0,0 +1,8 @@
{
"name": "3MF Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for writing 3MF files.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Auto Save"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Automatically saves Preferences, Machines and Profiles after changes."),
"api": 3
},
}
return {}
def register(app):
return { "extension": AutoSave.AutoSave() }

View File

@ -0,0 +1,8 @@
{
"name": "Auto Save",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Automatically saves Preferences, Machines and Profiles after changes.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -11,8 +11,8 @@ import UM 1.1 as UM
UM.Dialog
{
id: base
minimumWidth: UM.Theme.getSize("modal_window_minimum").width * 0.75
minimumHeight: UM.Theme.getSize("modal_window_minimum").height * 0.75
minimumWidth: (UM.Theme.getSize("modal_window_minimum").width * 0.75) | 0
minimumHeight: (UM.Theme.getSize("modal_window_minimum").height * 0.75) | 0
width: minimumWidth
height: minimumHeight
title: catalog.i18nc("@label", "Changelog")

View File

@ -1,6 +1,10 @@
[2.6.1]
*New profiles
The Polypropylene material is added and supported with the Ultimaker 3. Support for CPE+ and PC with 0.8mm nozzles is added as well.
[2.6.0]
*Cura versions
Cura 2.6 beta has local version folders, which means the new version wont overwrite the existing configuration and profiles from older versions, but can create a new folder instead. You can now safely check out new beta versions and, if necessary, start up an older version without the danger of losing your profiles.
Cura 2.6 has local version folders, which means the new version wont overwrite the existing configuration and profiles from older versions, but can create a new folder instead. You can now safely check out new beta versions and, if necessary, start up an older version without the danger of losing your profiles.
*Better support adhesion
Weve added extra support settings to allow the creation of improved support profiles with better PVA/PLA adhesion. The Support Interface settings, such as speed and density, are now split up into Support Roof and Support Floor settings.
@ -9,7 +13,7 @@ Weve added extra support settings to allow the creation of improved support p
Custom third-party printers and Ultimaker modifications now have multi-extrusion support. Thanks to Aldo Hoeben for this feature.
*Model auto-arrange
Weve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If theres not enough build plate space or the model is placed beyond the build plate, you can rectify this by selecting Arrange all models in the context menu or by pressing Command+R (MacOS) or Ctrl+R (Windows and Linux). Cura 2.6 beta will then find a better solution for model positioning.
Weve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If theres not enough build plate space or the model is placed beyond the build plate, you can rectify this by selecting Arrange all models in the context menu or by pressing Command+R (MacOS) or Ctrl+R (Windows and Linux). Cura 2.6 will then find a better solution for model positioning.
*Gradual infill
You can now find the Gradual Infill button in Recommended mode. This setting makes the infill concentrated near the top of the model so that we can save time and material for the lower parts of the model. This functionality is especially useful when printing with flexible materials.
@ -42,27 +46,16 @@ Its a lot simpler to save and open files, and Cura will know if its a proj
If you have a custom theme, you can now apply it more easily in the preferences screen.
*Time estimates per feature
<<<<<<< HEAD
You can hover over the print time estimate in the lower right corner to see how the printing time is divided over the printing features (walls, infill, etc.).
=======
You can hover over the print time estimate in the lower right corner to see how the printing time is divided over the printing features (walls, infill, etc.). Thanks to 14bitVoid for this feature.
>>>>>>> 2.6
*Invert the direction of camera zoom
Weve added an option to invert mouse direction for a better user experience.
*Olsson block upgrade
<<<<<<< HEAD
Ultimaker 2 users can now specify if they have the Olsson block installed on their machine.
*OctoPrint plugin
Cura 2.6 beta allows users to send prints to OctoPrint.
=======
Ultimaker 2 users can now specify if they have the Olsson block installed on their machine. Thanks to Aldo Hoeben for this feature.
*OctoPrint plugin
Cura 2.6 beta allows users to send prints to OctoPrint. Thanks to Aldo Hoeben for this feature.
>>>>>>> 2.6
Cura 2.6 allows users to send prints to OctoPrint. Thanks to Aldo Hoeben for this feature.
*Bug fixes
- Post Processing plugin

View File

@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Changelog"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Shows changes since latest checked version."),
"api": 3
}
}
return {}
def register(app):
return {"extension": ChangeLog.ChangeLog()}

View File

@ -0,0 +1,8 @@
{
"name": "Changelog",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Shows changes since latest checked version.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -76,7 +76,14 @@ class CuraEngineBackend(QObject, Backend):
self._scene = Application.getInstance().getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for when to (re)start slicing:
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
# - auto-slicing is started with a timer
# - whenever there is a value change, we start the timer
# - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the
# auto-slicing timer when that error check is finished
# If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer,
# and only wait for the error check to be finished to start the auto-slicing timer again.
#
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
@ -85,6 +92,12 @@ class CuraEngineBackend(QObject, Backend):
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
# A flag indicating if an error check was scheduled
# If so, we will stop the auto-slice timer and start upon the error check
self._is_error_check_scheduled = False
# Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
@ -426,11 +439,21 @@ class CuraEngineBackend(QObject, Backend):
self._clearLayerData()
## A setting has changed, so check if we must reslice.
#
# \param instance The setting instance that has changed.
# \param property The property of the setting instance that has changed.
# \param instance The setting instance that has changed.
# \param property The property of the setting instance that has changed.
def _onSettingChanged(self, instance, property):
if property == "value": # Only reslice if the value has changed.
if property == "value": # Only reslice if the value has changed.
self.needsSlicing()
self._onChanged()
elif property == "validationState":
if self._use_timer:
self._is_error_check_scheduled = True
self._change_timer.stop()
def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
if self._need_slicing:
self.needsSlicing()
self._onChanged()
@ -525,7 +548,12 @@ class CuraEngineBackend(QObject, Backend):
def _onChanged(self, *args, **kwargs):
self.needsSlicing()
if self._use_timer:
self._change_timer.start()
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual
if self._is_error_check_scheduled:
self._change_timer.stop()
else:
self._change_timer.start()
## Called when the back-end connects to the front-end.
def _onBackendConnected(self):
@ -591,9 +619,9 @@ class CuraEngineBackend(QObject, Backend):
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged)
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingChanged)
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -601,9 +629,8 @@ class CuraEngineBackend(QObject, Backend):
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._global_container_stack.containersChanged.connect(self._onChanged)
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
self._onActiveExtruderChanged()
self._onChanged()
@ -612,9 +639,8 @@ class CuraEngineBackend(QObject, Backend):
# Connect all extruders of the active machine. This might cause a few connects that have already happend,
# but that shouldn't cause issues as only new / unique connections are added.
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
if self._active_extruder_stack:
self._active_extruder_stack.containersChanged.disconnect(self._onChanged)

View File

@ -1,5 +1,5 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
#Copyright (c) 2017 Ultimaker B.V.
#Cura is released under the terms of the AGPLv3 or higher.
import gc
@ -24,6 +24,7 @@ from cura import LayerPolygon
import numpy
from time import time
from cura.Settings.ExtrudersModel import ExtrudersModel
catalog = i18nCatalog("cura")
@ -173,19 +174,18 @@ class ProcessSlicedLayersJob(Job):
if extruders:
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
for extruder in extruders:
material = extruder.findContainer({"type": "material"})
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
color_code = material.getMetaDataEntry("color_code", default="#e0e000")
try:
default_color = ExtrudersModel.defaultColors[position]
except IndexError:
default_color = "#e0e000"
color_code = extruder.material.getMetaDataEntry("color_code", default=default_color)
color = colorCodeToRGBA(color_code)
material_color_map[position, :] = color
else:
# Single extruder via global stack.
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
material = global_container_stack.findContainer({"type": "material"})
color_code = "#e0e000"
if material:
if material.getMetaDataEntry("color_code") is not None:
color_code = material.getMetaDataEntry("color_code")
color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
color = colorCodeToRGBA(color_code)
material_color_map[0, :] = color

View File

@ -44,6 +44,14 @@ class GcodeStartEndFormatter(Formatter):
## Job class that builds up the message of scene data to send to CuraEngine.
class StartSliceJob(Job):
## Meshes that are sent to the engine regardless of being outside of the
# build volume.
#
# If these settings are True for any mesh, the build volume is ignored.
# Note that Support Mesh is not in here because it actually generates
# g-code in the volume of the mesh.
_not_printed_mesh_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
def __init__(self, slice_message):
super().__init__()
@ -132,7 +140,8 @@ class StartSliceJob(Job):
temp_list = []
for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
if not getattr(node, "_outside_buildarea", False):
if not getattr(node, "_outside_buildarea", False)\
or (node.callDecoration("getStack") and any(node.callDecoration("getStack").getProperty(setting, "value") for setting in self._not_printed_mesh_settings)):
temp_list.append(node)
Job.yieldThread()
@ -149,8 +158,13 @@ class StartSliceJob(Job):
self._buildGlobalSettingsMessage(stack)
self._buildGlobalInheritsStackMessage(stack)
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
# Only add extruder stacks if there are multiple extruders
# Single extruder machines only use the global stack to store setting values
if stack.getProperty("machine_extruder_count", "value") > 1:
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
else:
self._buildExtruderMessageFromGlobalStack(stack)
for group in object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists")
@ -212,7 +226,7 @@ class StartSliceJob(Job):
for key in stack.getAllKeys():
# Do not send settings that are not settable_per_extruder.
if stack.getProperty(key, "settable_per_extruder") == False:
if not stack.getProperty(key, "settable_per_extruder"):
continue
setting = message.getMessage("settings").addRepeatedMessage("settings")
setting.name = key
@ -223,6 +237,19 @@ class StartSliceJob(Job):
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread()
## Create extruder message from global stack
def _buildExtruderMessageFromGlobalStack(self, stack):
message = self._slice_message.addRepeatedMessage("extruders")
for key in stack.getAllKeys():
# Do not send settings that are not settable_per_extruder.
if not stack.getProperty(key, "settable_per_extruder"):
continue
setting = message.getMessage("settings").addRepeatedMessage("settings")
setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread()
## Sends all global settings to the engine.
#
# The settings are taken from the global stack. This does not include any

View File

@ -8,14 +8,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "CuraEngine Backend"),
"author": "Ultimaker",
"description": catalog.i18nc("@info:whatsthis", "Provides the link to the CuraEngine slicing backend."),
"api": 3
}
}
return {}
def register(app):
return { "backend": CuraEngineBackend.CuraEngineBackend() }

View File

@ -0,0 +1,8 @@
{
"name": "CuraEngine Backend",
"author": "Ultimaker B.V.",
"description": "Provides the link to the CuraEngine slicing backend.",
"api": 4,
"version": "1.0.0",
"i18n-catalog": "cura"
}

View File

@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Cura Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing Cura profiles."),
"api": 3
},
"profile_reader": [
{
"extension": "curaprofile",

View File

@ -0,0 +1,8 @@
{
"name": "Cura Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing Cura profiles.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Cura Profile Writer"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for exporting Cura profiles."),
"api": 3
},
"profile_writer": [
{
"extension": "curaprofile",

View File

@ -0,0 +1,8 @@
{
"name": "Cura Profile Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for exporting Cura profiles.",
"api": 4,
"i18n-catalog":"cura"
}

View File

@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "GCode Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from g-code files."),
"api": 3
},
"profile_reader": [
{
"extension": "gcode",

View File

@ -0,0 +1,8 @@
{
"name": "GCode Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing profiles from g-code files.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "G-code Reader"),
"author": "Victor Larchenko",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Allows loading and displaying G-code files."),
"api": 3
},
"mesh_reader": [
{
"extension": "gcode",

View File

@ -0,0 +1,8 @@
{
"name": "G-code Reader",
"author": "Victor Larchenko",
"version": "1.0.0",
"description": "Allows loading and displaying G-code files.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshWriter import MeshWriter
@ -113,7 +113,7 @@ class GCodeWriter(MeshWriter):
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.findContainer({"type": "quality"}).getMetaDataEntry("quality_type", "normal"))
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
serialized = flat_global_container.serialize()
data = {"global_quality": serialized}
@ -134,7 +134,7 @@ class GCodeWriter(MeshWriter):
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.findContainer({"type": "quality"}).getMetaDataEntry("quality_type", "normal"))
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)

View File

@ -8,13 +8,7 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "GCode Writer"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Writes GCode to a file."),
"api": 3
},
"mesh_writer": {
"output": [{

View File

@ -0,0 +1,8 @@
{
"name": "GCode Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Writes GCode to a file.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "Image Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Enables ability to generate printable geometry from 2D image files."),
"api": 3
},
"mesh_reader": [
{
"extension": "jpg",

View File

@ -0,0 +1,8 @@
{
"name": "Image Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Enables ability to generate printable geometry from 2D image files.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -82,12 +82,12 @@ class LayerPass(RenderPass):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
for layer, counts in element_counts.items():
for layer in sorted(element_counts.keys()):
if layer > self._layer_view._current_layer_num:
break
if self._layer_view._minimum_layer_num > layer:
start += counts
end += counts
start += element_counts[layer]
end += element_counts[layer]
# This uses glDrawRangeElements internally to only draw a certain range of lines.
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))

View File

@ -11,6 +11,7 @@ import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.LayerView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
@ -25,8 +26,12 @@ Item
return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
property var buttonTarget: {
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y);
}
Rectangle {
UM.PointingRectangle {
id: layerViewMenu
anchors.left: parent.left
anchors.top: parent.top
@ -34,6 +39,11 @@ Item
height: parent.height
z: slider.z - 1
color: UM.Theme.getColor("tool_panel_background")
borderWidth: UM.Theme.getSize("default_lining").width
borderColor: UM.Theme.getColor("lining")
target: parent.buttonTarget
arrowSize: UM.Theme.getSize("default_arrow").width
ColumnLayout {
id: view_settings
@ -132,6 +142,7 @@ Item
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
color: UM.Theme.getColor("text")
visible: UM.LayerView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
@ -522,26 +533,19 @@ Item
target: Qt.point(0, slider.activeHandle.y + slider.activeHandle.height / 2)
arrowSize: UM.Theme.getSize("default_arrow").width
height: (Math.floor(UM.Theme.getSize("slider_handle").height + UM.Theme.getSize("default_margin").height) / 2) * 2 // Make sure height has an integer middle so drawing a pointy border is easier
height: UM.Theme.getSize("slider_handle").height + UM.Theme.getSize("default_margin").height
width: valueLabel.width + UM.Theme.getSize("default_margin").width
Behavior on height { NumberAnimation { duration: 50; } }
color: UM.Theme.getColor("lining");
color: UM.Theme.getColor("tool_panel_background")
borderColor: UM.Theme.getColor("lining")
borderWidth: UM.Theme.getSize("default_lining").width
visible: slider.layersVisible
UM.PointingRectangle
MouseArea //Catch all mouse events (so scene doesnt handle them)
{
color: UM.Theme.getColor("tool_panel_background")
target: Qt.point(0, height / 2 + UM.Theme.getSize("default_lining").width)
arrowSize: UM.Theme.getSize("default_arrow").width
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_lining").width
MouseArea //Catch all mouse events (so scene doesnt handle them)
{
anchors.fill: parent
}
}
TextField

View File

@ -9,13 +9,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Layer View"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides the Layer view."),
"api": 3
},
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layers"),
"view_panel": "LayerView.qml",

View File

@ -130,9 +130,9 @@ geometry41core =
// fixed size for movements
size_x = 0.05;
} else {
size_x = v_line_dim[0].x / 2 + 0.01; // radius, and make it nicely overlapping
size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping
}
size_y = v_line_dim[0].y / 2 + 0.01;
size_y = v_line_dim[1].y / 2 + 0.01;
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));

View File

@ -0,0 +1,8 @@
{
"name": "Layer View",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides the Layer view.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Legacy Cura Profile Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from legacy Cura versions."),
"api": 3
},
"profile_reader": [
{
"extension": "ini",

View File

@ -0,0 +1,8 @@
{
"name": "Legacy Cura Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing profiles from legacy Cura versions.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -30,7 +30,6 @@ class MachineSettingsAction(MachineAction):
self._global_container_stack = None
self._container_index = 0
self._extruder_container_index = 0
self._container_registry = ContainerRegistry.getInstance()
self._container_registry.containerAdded.connect(self._onContainerAdded)
@ -38,6 +37,8 @@ class MachineSettingsAction(MachineAction):
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self._empty_container = self._container_registry.getEmptyInstanceContainer()
self._backend = Application.getInstance().getBackend()
def _onContainerAdded(self, container):
@ -48,8 +49,8 @@ class MachineSettingsAction(MachineAction):
def _onContainerRemoved(self, container):
# Remove definition_changes containers when a stack is removed
if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
definition_changes_container = container.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = container.definitionChanges
if definition_changes_container == self._empty_container:
return
self._container_registry.removeContainer(definition_changes_container.getId())
@ -59,8 +60,8 @@ class MachineSettingsAction(MachineAction):
return
# Make sure there is a definition_changes container to store the machine settings
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = self._global_container_stack.definitionChanges
if definition_changes_container == self._empty_container:
definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings")
# Notify the UI in which container to store the machine settings data
@ -85,16 +86,10 @@ class MachineSettingsAction(MachineAction):
return
# Make sure there is a definition_changes container to store the machine settings
definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = extruder_container_stack.definitionChanges
if definition_changes_container == self._empty_container:
definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings")
# Notify the UI in which container to store the machine settings data
container_index = extruder_container_stack.getContainerIndex(definition_changes_container)
if container_index != self._extruder_container_index:
self._extruder_container_index = container_index
self.extruderContainerIndexChanged.emit()
def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None):
definition_changes_container = InstanceContainer(container_name)
definition = container_stack.getBottom()
@ -113,13 +108,6 @@ class MachineSettingsAction(MachineAction):
def containerIndex(self):
return self._container_index
extruderContainerIndexChanged = pyqtSignal()
@pyqtProperty(int, notify = extruderContainerIndexChanged)
def extruderContainerIndex(self):
return self._extruder_container_index
def _onGlobalContainerChanged(self):
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
@ -140,8 +128,8 @@ class MachineSettingsAction(MachineAction):
machine_manager = Application.getInstance().getMachineManager()
extruder_manager = ExtruderManager.getInstance()
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
if not self._global_container_stack or not definition_changes_container:
definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_container:
return
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
@ -250,30 +238,115 @@ class MachineSettingsAction(MachineAction):
return
definition = self._global_container_stack.getBottom()
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
# In other words: only continue for the UM2 (extended), but not for the UM2+
return
material_container = self._global_container_stack.material
material_index = self._global_container_stack.getContainerIndex(material_container)
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
if has_materials:
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
self._global_container_stack.addMetaDataEntry("has_materials", True)
material_container = self._global_container_stack.material
# Set the material container to a sane default
if material_container.getId() == "empty_material":
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"}
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
self._global_container_stack.replaceContainer(material_index, containers[0])
if has_materials:
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.removeMetaDataEntry("has_materials")
self._global_container_stack.addMetaDataEntry("has_materials", True)
self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
# Set the material container to a sane default
if material_container == self._empty_container:
search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material")}
materials = self._container_registry.findInstanceContainers(**search_criteria)
if materials:
self._global_container_stack.material = materials[0]
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.removeMetaDataEntry("has_materials")
Application.getInstance().globalContainerStackChanged.emit()
self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit()
@pyqtSlot()
def updateMaterialForDiameter(self):
# Updates the material container to a material that matches the material diameter set for the printer
if not self._global_container_stack:
return
if not self._global_container_stack.getMetaDataEntry("has_materials", False):
return
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
if machine_extruder_count > 1:
material = ExtruderManager.getInstance().getActiveExtruderStack().material
else:
material = self._global_container_stack.material
material_diameter = material.getProperty("material_diameter", "value")
if not material_diameter: # in case of "empty" material
material_diameter = 0
material_approximate_diameter = str(round(material_diameter))
definition_changes = self._global_container_stack.definitionChanges
machine_diameter = definition_changes.getProperty("material_diameter", "value")
if not machine_diameter:
machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
machine_approximate_diameter = str(round(machine_diameter))
if material_approximate_diameter != machine_approximate_diameter:
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
if machine_extruder_count > 1:
stacks = ExtruderManager.getInstance().getExtruderStacks()
else:
stacks = [self._global_container_stack]
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
materials_definition = self._global_container_stack.definition.getId()
has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
else:
materials_definition = "fdmprinter"
has_material_variants = False
for stack in stacks:
old_material = stack.material
search_criteria = {
"type": "material",
"approximate_diameter": machine_approximate_diameter,
"material": old_material.getMetaDataEntry("material", "value"),
"supplier": old_material.getMetaDataEntry("supplier", "value"),
"color_name": old_material.getMetaDataEntry("color_name", "value"),
"definition": materials_definition
}
if has_material_variants:
search_criteria["variant"] = stack.variant.getId()
if old_material == self._empty_container:
search_criteria.pop("material", None)
search_criteria.pop("supplier", None)
search_criteria.pop("definition", None)
search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials:
# Same material with new diameter is not found, search for generic version of the same material type
search_criteria.pop("supplier", None)
search_criteria["color_name"] = "Generic"
materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials:
# Generic material with new diameter is not found, search for preferred material
search_criteria.pop("color_name", None)
search_criteria.pop("material", None)
search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials:
# Preferrd material with new diameter is not found, search for any material
search_criteria.pop("id", None)
materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials:
# Just use empty material as a final fallback
materials = [self._empty_container]
Logger.log("i", "Selecting new material: %s" % materials[0].getId())
stack.material = materials[0]

File diff suppressed because it is too large Load Diff

View File

@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Machine Settings action"),
"author": "fieldOfView",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides a way to change machine settings (such as build volume, nozzle size, etc)"),
"api": 3
}
}
return {}
def register(app):
return { "machine_action": MachineSettingsAction.MachineSettingsAction() }

View File

@ -0,0 +1,8 @@
{
"name": "Machine Settings action",
"author": "fieldOfView",
"version": "1.0.0",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc)",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -22,7 +22,7 @@ Button {
UM.RecolorImage
{
anchors.verticalCenter: parent.verticalCenter
height: label.height / 2
height: (label.height / 2) | 0
width: height
source: control.checked ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right");
color: control.hovered ? palette.highlight : palette.buttonText

View File

@ -110,10 +110,10 @@ Item {
Button
{
width: UM.Theme.getSize("setting").height / 2;
height: UM.Theme.getSize("setting").height;
width: (UM.Theme.getSize("setting").height / 2) | 0
height: UM.Theme.getSize("setting").height
onClicked: addedSettingsModel.setVisible(model.key, false);
onClicked: addedSettingsModel.setVisible(model.key, false)
style: ButtonStyle
{

View File

@ -10,13 +10,6 @@ i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "Per Model Settings Tool"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides the Per Model Settings."),
"api": 3
},
"tool": {
"name": i18n_catalog.i18nc("@label", "Per Model Settings"),
"description": i18n_catalog.i18nc("@info:tooltip", "Configure Per Model Settings"),

View File

@ -0,0 +1,8 @@
{
"name": "Per Model Settings Tool",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides the Per Model Settings.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -0,0 +1,249 @@
# Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the AGPLv3 or higher.
from UM.Extension import Extension
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.PluginRegistry import PluginRegistry
from UM.Application import Application
from UM.Version import Version
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtQml import QQmlComponent, QQmlContext
import json
import os
import tempfile
i18n_catalog = i18nCatalog("cura")
class PluginBrowser(QObject, Extension):
def __init__(self, parent = None):
super().__init__(parent)
self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins)
self._api_version = 1
self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
self._plugin_list_request = None
self._download_plugin_request = None
self._download_plugin_reply = None
self._network_manager = None
self._plugins_metadata = []
self._plugins_model = None
self._qml_component = None
self._qml_context = None
self._dialog = None
self._download_progress = 0
self._is_downloading = False
self._request_header = [b"User-Agent", str.encode("%s - %s" % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion()))]
# Installed plugins are really installed after reboot. In order to prevent the user from downloading the
# same file over and over again, we keep track of the upgraded plugins.
self._newly_installed_plugin_ids = []
pluginsMetadataChanged = pyqtSignal()
onDownloadProgressChanged = pyqtSignal()
onIsDownloadingChanged = pyqtSignal()
@pyqtProperty(bool, notify = onIsDownloadingChanged)
def isDownloading(self):
return self._is_downloading
def browsePlugins(self):
self._createNetworkManager()
self.requestPluginList()
if not self._dialog:
self._createDialog()
self._dialog.show()
@pyqtSlot()
def requestPluginList(self):
Logger.log("i", "Requesting plugin list")
url = QUrl(self._api_url + "plugins")
self._plugin_list_request = QNetworkRequest(url)
self._plugin_list_request.setRawHeader(*self._request_header)
self._network_manager.get(self._plugin_list_request)
def _createDialog(self):
Logger.log("d", "PluginBrowser")
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml"))
self._qml_component = QQmlComponent(Application.getInstance()._engine, path)
# We need access to engine (although technically we can't)
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._qml_context.setContextProperty("manager", self)
self._dialog = self._qml_component.create(self._qml_context)
if self._dialog is None:
Logger.log("e", "QQmlComponent status %s", self._qml_component.status())
Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString())
def setIsDownloading(self, is_downloading):
if self._is_downloading != is_downloading:
self._is_downloading = is_downloading
self.onIsDownloadingChanged.emit()
def _onDownloadPluginProgress(self, bytes_sent, bytes_total):
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
self.setDownloadProgress(new_progress)
if new_progress == 100.0:
self.setIsDownloading(False)
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
# must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curaplugin", delete = False)
location = self._temp_plugin_file.name
# write first and close, otherwise on Windows, it cannot read the file
self._temp_plugin_file.write(self._download_plugin_reply.readAll())
self._temp_plugin_file.close()
# open as read
if not location.startswith("/"):
location = "/" + location # Ensure that it starts with a /, as otherwise it doesn't work on windows.
result = PluginRegistry.getInstance().installPlugin("file://" + location)
self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit()
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
self._temp_plugin_file.close() # Plugin was installed, delete temp file
@pyqtProperty(int, notify = onDownloadProgressChanged)
def downloadProgress(self):
return self._download_progress
def setDownloadProgress(self, progress):
if progress != self._download_progress:
self._download_progress = progress
self.onDownloadProgressChanged.emit()
@pyqtSlot(str)
def downloadAndInstallPlugin(self, url):
Logger.log("i", "Attempting to download & install plugin from %s", url)
url = QUrl(url)
self._download_plugin_request = QNetworkRequest(url)
self._download_plugin_request.setRawHeader(*self._request_header)
self._download_plugin_reply = self._network_manager.get(self._download_plugin_request)
self.setDownloadProgress(0)
self.setIsDownloading(True)
self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress)
@pyqtSlot()
def cancelDownload(self):
Logger.log("i", "user cancelled the download of a plugin")
self._download_plugin_reply.abort()
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply = None
self._download_plugin_request = None
self.setDownloadProgress(0)
self.setIsDownloading(False)
@pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self):
if self._plugins_model is None:
self._plugins_model = ListModel()
self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description")
self._plugins_model.addRoleName(Qt.UserRole + 4, "author")
self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed")
self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location")
self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade")
else:
self._plugins_model.clear()
items = []
for metadata in self._plugins_metadata:
items.append({
"name": metadata["label"],
"version": metadata["version"],
"short_description": metadata["short_description"],
"author": metadata["author"],
"already_installed": self._checkAlreadyInstalled(metadata["id"]),
"file_location": metadata["file_location"],
"can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"])
})
self._plugins_model.setItems(items)
return self._plugins_model
def _checkCanUpgrade(self, id, version):
plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id)
if metadata != {}:
if id in self._newly_installed_plugin_ids:
return False # We already updated this plugin.
current_version = Version(metadata["plugin"]["version"])
new_version = Version(version)
if new_version > current_version:
return True
return False
def _checkAlreadyInstalled(self, id):
plugin_registry = PluginRegistry.getInstance()
metadata = plugin_registry.getMetaData(id)
if metadata != {}:
return True
else:
if id in self._newly_installed_plugin_ids:
return True # We already installed this plugin, but the registry just doesn't know it yet.
return False
def _onRequestFinished(self, reply):
reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError:
Logger.log("w", "Got a timeout.")
# Reset everything.
self.setDownloadProgress(0)
self.setIsDownloading(False)
if self._download_plugin_reply:
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply.abort()
self._download_plugin_reply = None
return
elif reply.error() == QNetworkReply.HostNotFoundError:
Logger.log("w", "Unable to reach server.")
return
if reply.operation() == QNetworkAccessManager.GetOperation:
if reply_url == self._api_url + "plugins":
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
self._plugins_metadata = json_data
self.pluginsMetadataChanged.emit()
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
return
else:
# Ignore any operation that is not a get operation
pass
def _onNetworkAccesibleChanged(self, accessible):
if accessible == 0:
self.setDownloadProgress(0)
self.setIsDownloading(False)
if self._download_plugin_reply:
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply.abort()
self._download_plugin_reply = None
def _createNetworkManager(self):
if self._network_manager:
self._network_manager.finished.disconnect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)

View File

@ -0,0 +1,182 @@
import UM 1.1 as UM
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.1
UM.Dialog
{
id: base
title: catalog.i18nc("@title:window", "Find & Update plugins")
width: 600
height: 450
Item
{
anchors.fill: parent
Item
{
id: topBar
height: childrenRect.height;
width: parent.width
Label
{
id: introText
text: catalog.i18nc("@label", "Here you can find a list of Third Party plugins.")
width: parent.width
height: 30
}
Button
{
id: refresh
text: catalog.i18nc("@action:button", "Refresh")
onClicked: manager.requestPluginList()
anchors.right: parent.right
enabled: !manager.isDownloading
}
}
ScrollView
{
width: parent.width
anchors.top: topBar.bottom
anchors.bottom: bottomBar.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
frameVisible: true
ListView
{
id: pluginList
model: manager.pluginsModel
anchors.fill: parent
property var activePlugin
delegate: pluginDelegate
}
}
Item
{
id: bottomBar
width: parent.width
height: closeButton.height
anchors.bottom:parent.bottom
anchors.left: parent.left
ProgressBar
{
id: progressbar
anchors.bottom: parent.bottom
minimumValue: 0;
maximumValue: 100
anchors.left:parent.left
anchors.right: closeButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
value: manager.isDownloading ? manager.downloadProgress : 0
}
Button
{
id: closeButton
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
onClicked:
{
if (manager.isDownloading)
{
manager.cancelDownload()
}
base.close();
}
anchors.bottom: parent.bottom
anchors.right: parent.right
}
}
Item
{
SystemPalette { id: palette }
Component
{
id: pluginDelegate
Rectangle
{
width: pluginList.width;
height: texts.height;
color: index % 2 ? palette.base : palette.alternateBase
Column
{
id: texts
width: parent.width
height: childrenRect.height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: downloadButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
Label
{
text: "<b>" + model.name + "</b> - " + model.author
width: contentWidth
height: contentHeight + UM.Theme.getSize("default_margin").height
verticalAlignment: Text.AlignVCenter
}
Label
{
text: model.short_description
width: parent.width
height: contentHeight + UM.Theme.getSize("default_margin").height
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
}
}
Button
{
id: downloadButton
text:
{
if (manager.isDownloading && pluginList.activePlugin == model)
{
return catalog.i18nc("@action:button", "Cancel");
}
else if (model.already_installed)
{
if (model.can_upgrade)
{
return catalog.i18nc("@action:button", "Upgrade");
}
return catalog.i18nc("@action:button", "Installed");
}
return catalog.i18nc("@action:button", "Download");
}
onClicked:
{
if(!manager.isDownloading)
{
pluginList.activePlugin = model;
manager.downloadAndInstallPlugin(model.file_location);
}
else
{
manager.cancelDownload();
}
}
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
enabled:
{
if (manager.isDownloading)
{
return (pluginList.activePlugin == model);
}
else
{
return (!model.already_installed || model.can_upgrade);
}
}
}
}
}
}
UM.I18nCatalog { id: catalog; name:"cura" }
}
}

View File

@ -0,0 +1,12 @@
# Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the AGPLv3 or higher.
from . import PluginBrowser
def getMetaData():
return {}
def register(app):
return {"extension": PluginBrowser.PluginBrowser()}

View File

@ -0,0 +1,7 @@
{
"name": "Plugin Browser",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 4,
"description": "Find, manage and install new plugins."
}

View File

@ -1,19 +1,18 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2013 David Braam
# Uranium is released under the terms of the AGPLv3 or higher.
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from . import RemovableDrivePlugin
import string
import ctypes # type: ignore
from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work
import ctypes
from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader.
ctypes.windll.kernel32.SetErrorMode(1)
# WinAPI Constants that we need
# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]

View File

@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Removable Drive Output Device Plugin"),
"author": "Ultimaker B.V.",
"description": catalog.i18nc("@info:whatsthis", "Provides removable drive hotplugging and writing support."),
"version": "1.0",
"api": 3
}
}
def register(app):

View File

@ -0,0 +1,8 @@
{
"name": "Removable Drive Output Device Plugin",
"author": "Ultimaker B.V.",
"description": "Provides removable drive hotplugging and writing support.",
"version": "1.0.0",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -1,69 +1,37 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Any
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Extension import Extension
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Platform import Platform
import time
from UM.Qt.Duration import DurationFormat
from UM.Job import Job
from .SliceInfoJob import SliceInfoJob
import platform
import math
import urllib.request
import urllib.parse
import ssl
import hashlib
import json
catalog = i18nCatalog("cura")
class SliceInfoJob(Job):
data = None # type: Any
url = None # type: str
def __init__(self, url, data):
super().__init__()
self.url = url
self.data = data
def run(self):
if not self.url or not self.data:
Logger.log("e", "URL or DATA for sending slice info was not set!")
return
# Submit data
kwoptions = {"data" : self.data,
"timeout" : 5
}
if Platform.isOSX():
kwoptions["context"] = ssl._create_unverified_context()
Logger.log("d", "Sending anonymous slice info to [%s]...", self.url)
try:
f = urllib.request.urlopen(self.url, **kwoptions)
Logger.log("i", "Sent anonymous slice info.")
f.close()
except urllib.error.HTTPError as http_exception:
Logger.log("e", "An HTTP error occurred while trying to send slice information: %s" % http_exception)
except Exception as e: # We don't want any exception to cause problems
Logger.log("e", "An exception occurred while trying to send slice information: %s" % e)
## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
# The data is only sent when the user in question gave permission to do so. All data is anonymous and
# no model files are being sent (Just a SHA256 hash of the model).
class SliceInfo(Extension):
info_url = "https://stats.youmagine.com/curastats/slice"
info_url = "https://stats.ultimaker.com/api/cura"
def __init__(self):
super().__init__()
@ -72,7 +40,7 @@ class SliceInfo(Extension):
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False)
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in the preferences."), lifetime = 0, dismissable = False)
self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()
@ -85,61 +53,139 @@ class SliceInfo(Extension):
try:
if not Preferences.getInstance().getValue("info/send_slice_info"):
Logger.log("d", "'info/send_slice_info' is turned off.")
return # Do nothing, user does not want to send data
# Listing all files placed on the buildplate
modelhashes = []
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
if node.callDecoration("isSliceable"):
modelhashes.append(node.getMeshData().getHash())
# Creating md5sums and formatting them as discussed on JIRA
modelhash_formatted = ",".join(modelhashes)
return # Do nothing, user does not want to send data
global_container_stack = Application.getInstance().getGlobalContainerStack()
# Get total material used (in mm^3)
print_information = Application.getInstance().getPrintInformation()
material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
# Send material per extruder
material_used = [str(math.pi * material_radius * material_radius * material_length) for material_length in print_information.materialLengths]
material_used = ",".join(material_used)
data = dict() # The data that we're going to submit.
data["time_stamp"] = time.time()
data["schema_version"] = 0
data["cura_version"] = Application.getInstance().getVersion()
containers = { "": global_container_stack.serialize() }
for container in global_container_stack.getContainers():
container_id = container.getId()
try:
container_serialized = container.serialize()
except NotImplementedError:
Logger.log("w", "Container %s could not be serialized!", container_id)
continue
if container_serialized:
containers[container_id] = container_serialized
else:
Logger.log("i", "No data found in %s to be serialized!", container_id)
active_mode = Preferences.getInstance().getValue("cura/active_mode")
if active_mode == 0:
data["active_mode"] = "recommended"
else:
data["active_mode"] = "custom"
# Bundle the collected data
submitted_data = {
"processor": platform.processor(),
"machine": platform.machine(),
"platform": platform.platform(),
"settings": json.dumps(containers), # bundle of containers with their serialized contents
"version": Application.getInstance().getVersion(),
"modelhash": modelhash_formatted,
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
"filament": material_used,
"language": Preferences.getInstance().getValue("general/language"),
}
data["machine_settings_changed_by_user"] = global_container_stack.definitionChanges.getId() != "empty"
data["language"] = Preferences.getInstance().getValue("general/language")
data["os"] = {"type": platform.system(), "version": platform.version()}
data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")}
data["extruders"] = []
extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
if not extruders:
extruders = [global_container_stack]
for extruder in extruders:
extruder_dict = dict()
extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder
extruder_dict["material"] = {"GUID": extruder.material.getMetaData().get("GUID", ""),
"type": extruder.material.getMetaData().get("material", ""),
"brand": extruder.material.getMetaData().get("brand", "")
}
extruder_dict["material_used"] = print_information.materialLengths[int(extruder.getMetaDataEntry("position", "0"))]
extruder_dict["variant"] = extruder.variant.getName()
extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value")
extruder_settings = dict()
extruder_settings["wall_line_count"] = extruder.getProperty("wall_line_count", "value")
extruder_settings["retraction_enable"] = extruder.getProperty("retraction_enable", "value")
extruder_settings["infill_sparse_density"] = extruder.getProperty("infill_sparse_density", "value")
extruder_settings["infill_pattern"] = extruder.getProperty("infill_pattern", "value")
extruder_settings["gradual_infill_steps"] = extruder.getProperty("gradual_infill_steps", "value")
extruder_settings["default_material_print_temperature"] = extruder.getProperty("default_material_print_temperature", "value")
extruder_settings["material_print_temperature"] = extruder.getProperty("material_print_temperature", "value")
extruder_dict["extruder_settings"] = extruder_settings
data["extruders"].append(extruder_dict)
data["quality_profile"] = global_container_stack.quality.getMetaData().get("quality_type")
data["models"] = []
# Listing all files placed on the build plate
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
if node.callDecoration("isSliceable"):
model = dict()
model["hash"] = node.getMeshData().getHash()
bounding_box = node.getBoundingBox()
model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
"y": bounding_box.minimum.y,
"z": bounding_box.minimum.z},
"maximum": {"x": bounding_box.maximum.x,
"y": bounding_box.maximum.y,
"z": bounding_box.maximum.z}}
model["transformation"] = {"data": str(node.getWorldTransformation().getData()).replace("\n", "")}
extruder_position = node.callDecoration("getActiveExtruderPosition")
model["extruder"] = 0 if extruder_position is None else int(extruder_position)
model_settings = dict()
model_stack = node.callDecoration("getStack")
if model_stack:
model_settings["support_enabled"] = model_stack.getProperty("support_enable", "value")
model_settings["support_extruder_nr"] = int(model_stack.getProperty("support_extruder_nr", "value"))
# Mesh modifiers;
model_settings["infill_mesh"] = model_stack.getProperty("infill_mesh", "value")
model_settings["cutting_mesh"] = model_stack.getProperty("cutting_mesh", "value")
model_settings["support_mesh"] = model_stack.getProperty("support_mesh", "value")
model_settings["anti_overhang_mesh"] = model_stack.getProperty("anti_overhang_mesh", "value")
model_settings["wall_line_count"] = model_stack.getProperty("wall_line_count", "value")
model_settings["retraction_enable"] = model_stack.getProperty("retraction_enable", "value")
# Infill settings
model_settings["infill_sparse_density"] = model_stack.getProperty("infill_sparse_density", "value")
model_settings["infill_pattern"] = model_stack.getProperty("infill_pattern", "value")
model_settings["gradual_infill_steps"] = model_stack.getProperty("gradual_infill_steps", "value")
model["model_settings"] = model_settings
data["models"].append(model)
print_times = print_information.printTimesPerFeature
data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)),
"support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)),
"infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)),
"total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))}
print_settings = dict()
print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value")
# Support settings
print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value")
print_settings["support_extruder_nr"] = int(global_container_stack.getProperty("support_extruder_nr", "value"))
# Platform adhesion settings
print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value")
# Shell settings
print_settings["wall_line_count"] = global_container_stack.getProperty("wall_line_count", "value")
print_settings["retraction_enable"] = global_container_stack.getProperty("retraction_enable", "value")
# Prime tower settings
print_settings["prime_tower_enable"] = global_container_stack.getProperty("prime_tower_enable", "value")
# Infill settings
print_settings["infill_sparse_density"] = global_container_stack.getProperty("infill_sparse_density", "value")
print_settings["infill_pattern"] = global_container_stack.getProperty("infill_pattern", "value")
print_settings["gradual_infill_steps"] = global_container_stack.getProperty("gradual_infill_steps", "value")
print_settings["print_sequence"] = global_container_stack.getProperty("print_sequence", "value")
data["print_settings"] = print_settings
# Convert data to bytes
submitted_data = urllib.parse.urlencode(submitted_data)
binary_data = submitted_data.encode("utf-8")
binary_data = json.dumps(data).encode("utf-8")
# Sending slice info non-blocking
reportJob = SliceInfoJob(self.info_url, binary_data)
reportJob.start()
except Exception as e:
except Exception:
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course.
Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.

View File

@ -0,0 +1,38 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Job import Job
from UM.Logger import Logger
from UM.Platform import Platform
import ssl
import urllib.request
import urllib.error
class SliceInfoJob(Job):
def __init__(self, url, data):
super().__init__()
self._url = url
self._data = data
def run(self):
if not self._url or not self._data:
Logger.log("e", "URL or DATA for sending slice info was not set!")
return
# Submit data
kwoptions = {"data" : self._data, "timeout" : 5}
if Platform.isOSX():
kwoptions["context"] = ssl._create_unverified_context()
Logger.log("i", "Sending anonymous slice info to [%s]...", self._url)
try:
f = urllib.request.urlopen(self._url, **kwoptions)
Logger.log("i", "Sent anonymous slice info.")
f.close()
except urllib.error.HTTPError:
Logger.logException("e", "An HTTP error occurred while trying to send slice information")
except Exception: # We don't want any exception to cause problems
Logger.logException("e", "An exception occurred while trying to send slice information")

View File

@ -6,13 +6,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Slice info"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Submits anonymous slice info. Can be disabled through preferences."),
"api": 3
}
}
def register(app):

View File

@ -0,0 +1,8 @@
{
"name": "Slice info",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -7,7 +7,7 @@ from UM.Scene.Selection import Selection
from UM.Resources import Resources
from UM.Application import Application
from UM.Preferences import Preferences
from UM.View.Renderer import Renderer
from UM.View.RenderBatch import RenderBatch
from UM.Settings.Validator import ValidatorState
from UM.Math.Color import Color
from UM.View.GL.OpenGL import OpenGL
@ -118,7 +118,7 @@ class SolidView(View):
else:
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
if node.callDecoration("isGroup") and Selection.isSelected(node):
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines)
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop)
def endRendering(self):
pass

View File

@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "Solid View"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides a normal solid mesh view."),
"api": 3
},
"view": {
"name": i18n_catalog.i18nc("@item:inmenu", "Solid"),
"weight": 0

View File

@ -0,0 +1,8 @@
{
"name": "Solid View",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a normal solid mesh view.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -114,7 +114,7 @@ Cura.MachineAction
Column
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
spacing: UM.Theme.getSize("default_margin").height
ScrollView
@ -193,14 +193,14 @@ Cura.MachineAction
wrapMode: Text.WordWrap
//: Tips label
//TODO: get actual link from webteam
text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network-printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting");
text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting");
onLinkActivated: Qt.openUrlExternally(link)
}
}
Column
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
visible: base.selectedPrinter ? true : false
spacing: UM.Theme.getSize("default_margin").height
Label
@ -218,13 +218,13 @@ Cura.MachineAction
columns: 2
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Type")
}
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text:
{
@ -249,25 +249,25 @@ Cura.MachineAction
}
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware version")
}
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : ""
}
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Address")
}
Label
{
width: parent.width * 0.5
width: (parent.width * 0.5) | 0
wrapMode: Text.WordWrap
text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
}

View File

@ -179,7 +179,6 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._compressing_print = False
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
if printer_type.startswith("9511"):
self._updatePrinterType("ultimaker3_extended")
@ -188,9 +187,18 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
else:
self._updatePrinterType("unknown")
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
def _onNetworkAccesibleChanged(self, accessible):
Logger.log("d", "Network accessible state changed to: %s", accessible)
## Triggered when the output device manager changes devices.
#
# This is how we can detect that our device is no longer active now.
def _onOutputDevicesChanged(self):
if self.getId() not in Application.getInstance().getOutputDeviceManager().getOutputDeviceIds():
self.stopCamera()
def _onAuthenticationTimer(self):
self._authentication_counter += 1
self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100)
@ -305,15 +313,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
return True
def _stopCamera(self):
self._camera_timer.stop()
if self._image_reply:
try:
self._image_reply.abort()
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
except RuntimeError:
pass # It can happen that the wrapped c++ object is already deleted.
self._image_reply = None
self._image_request = None
if self._camera_timer.isActive():
self._camera_timer.stop()
if self._image_reply:
try:
self._image_reply.abort()
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
except RuntimeError:
pass # It can happen that the wrapped c++ object is already deleted.
self._image_reply = None
self._image_request = None
def _startCamera(self):
if self._use_stream:
@ -616,7 +625,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
# is ignored.
# \param kwargs Keyword arguments.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
if self._printer_state != "idle":
if self._printer_state not in ["idle", ""]:
self._error_message = Message(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._printer_state)
self._error_message.show()
@ -636,7 +645,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
# Only check for mistakes if there is material length information.
if print_information.materialLengths:
# Check if print cores / materials are loaded at all. Any failure in these results in an Error.
# Check if PrintCores / materials are loaded at all. Any failure in these results in an Error.
for index in range(0, self._num_extruders):
if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
@ -668,7 +677,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if variant:
if variant.getName() != core_name:
Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName())
warnings.append(i18n_catalog.i18nc("@label", "Different print core (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"})
if material:
@ -690,7 +699,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
is_offset_calibrated = True
if not is_offset_calibrated:
warnings.append(i18n_catalog.i18nc("@label", "Print core {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1))
warnings.append(i18n_catalog.i18nc("@label", "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1))
else:
Logger.log("w", "There was no material usage found. No check to match used material with machine is done.")
@ -1167,7 +1176,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
i18n_catalog.i18nc("@label",
"Would you like to use your current printer configuration in Cura?"),
i18n_catalog.i18nc("@label",
"The print cores and/or materials on your printer differ from those within your current project. For the best result, always slice for the print cores and materials that are inserted in your printer."),
"The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."),
buttons=QMessageBox.Yes + QMessageBox.No,
icon=QMessageBox.Question,
callback=callback

View File

@ -6,15 +6,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": "UM3 Network Connection",
"author": "Ultimaker",
"description": catalog.i18nc("@info:whatsthis", "Manages network connections to Ultimaker 3 printers"),
"version": "1.0",
"api": 3
}
}
return {}
def register(app):
return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}

View File

@ -0,0 +1,8 @@
{
"name": "UM3 Network Connection",
"author": "Ultimaker B.V.",
"description": "Manages network connections to Ultimaker 3 printers",
"version": "1.0.0",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -12,7 +12,9 @@ UM.Dialog
id: base;
width: 500 * Screen.devicePixelRatio;
minimumWidth: 500 * Screen.devicePixelRatio;
height: 100 * Screen.devicePixelRatio;
minimumHeight: 100 * Screen.devicePixelRatio;
visible: true;
modality: Qt.ApplicationModal;

View File

@ -622,6 +622,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._sendCommand("M140 S0")
self._sendCommand("M104 S0")
self._sendCommand("M107")
self.homeHead()
self.homeBed()
self._sendCommand("M84")
self._is_printing = False
self._is_paused = False

View File

@ -19,6 +19,7 @@ import platform
import glob
import time
import os.path
import serial.tools.list_ports
from UM.Extension import Extension
from PyQt5.QtQml import QQmlComponent, QQmlContext
@ -252,24 +253,13 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
# \param only_list_usb If true, only usb ports are listed
def getSerialPortList(self, only_list_usb = False):
base_list = []
if platform.system() == "Windows":
import winreg # type: ignore @UnresolvedImport
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
i = 0
while True:
values = winreg.EnumValue(key, i)
if not only_list_usb or "USBSER" or "VCP" in values[0]:
base_list += [values[1]]
i += 1
except Exception as e:
pass
else:
if only_list_usb:
base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.usb*") + glob.glob("/dev/tty.wchusb*") + glob.glob("/dev/cu.wchusb*")
base_list = filter(lambda s: "Bluetooth" not in s, base_list) # Filter because mac sometimes puts them in the list
else:
base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/tty.wchusb*") + glob.glob("/dev/cu.wchusb*") + glob.glob("/dev/rfcomm*") + glob.glob("/dev/serial/by-id/*")
for port in serial.tools.list_ports.comports():
if not isinstance(port, tuple):
port = (port.device, port.description, port.hwid)
if only_list_usb and not port[2].startswith("USB"):
continue
base_list += [port[0]]
return list(base_list)
_instance = None # type: "USBPrinterOutputDeviceManager"

View File

@ -8,14 +8,6 @@ i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"type": "extension",
"plugin": {
"name": i18n_catalog.i18nc("@label", "USB printing"),
"author": "Ultimaker",
"version": "1.0",
"api": 3,
"description": i18n_catalog.i18nc("@info:whatsthis","Accepts G-Code and sends them to a printer. Plugin can also update firmware.")
}
}
def register(app):

View File

@ -0,0 +1,8 @@
{
"name": "USB printing",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 4,
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"i18n-catalog": "cura"
}

View File

@ -13,8 +13,8 @@ Cura.MachineAction
{
id: checkupMachineAction
anchors.fill: parent;
property int leftRow: checkupMachineAction.width * 0.40
property int rightRow: checkupMachineAction.width * 0.60
property int leftRow: (checkupMachineAction.width * 0.40) | 0
property int rightRow: (checkupMachineAction.width * 0.60) | 0
property bool heatupHotendStarted: false
property bool heatupBedStarted: false
property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0
@ -166,7 +166,7 @@ Cura.MachineAction
Label
{
id: nozzleTempStatus
width: checkupMachineAction.rightRow * 0.4
width: (checkupMachineAction.rightRow * 0.4) | 0
anchors.top: nozzleTempLabel.top
anchors.left: nozzleTempLabel.right
wrapMode: Text.WordWrap
@ -176,7 +176,7 @@ Cura.MachineAction
Item
{
id: nozzleTempButton
width: checkupMachineAction.rightRow * 0.3
width: (checkupMachineAction.rightRow * 0.3) | 0
height: childrenRect.height
anchors.top: nozzleTempLabel.top
anchors.left: bedTempStatus.right
@ -205,7 +205,7 @@ Cura.MachineAction
anchors.top: nozzleTempLabel.top
anchors.left: nozzleTempButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: checkupMachineAction.rightRow * 0.2
width: (checkupMachineAction.rightRow * 0.2) | 0
wrapMode: Text.WordWrap
text: manager.hotendTemperature + "°C"
font.bold: true
@ -227,7 +227,7 @@ Cura.MachineAction
Label
{
id: bedTempStatus
width: checkupMachineAction.rightRow * 0.4
width: (checkupMachineAction.rightRow * 0.4) | 0
anchors.top: bedTempLabel.top
anchors.left: bedTempLabel.right
wrapMode: Text.WordWrap
@ -237,7 +237,7 @@ Cura.MachineAction
Item
{
id: bedTempButton
width: checkupMachineAction.rightRow * 0.3
width: (checkupMachineAction.rightRow * 0.3) | 0
height: childrenRect.height
anchors.top: bedTempLabel.top
anchors.left: bedTempStatus.right
@ -263,7 +263,7 @@ Cura.MachineAction
Label
{
id: bedTemp
width: checkupMachineAction.rightRow * 0.2
width: (checkupMachineAction.rightRow * 0.2) | 0
anchors.top: bedTempLabel.top
anchors.left: bedTempButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width

View File

@ -36,8 +36,8 @@ class UMOUpgradeSelection(MachineAction):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
# Make sure there is a definition_changes container to store the machine settings
definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = global_container_stack.definitionChanges
if definition_changes_container == ContainerRegistry.getInstance().getEmptyInstanceContainer():
definition_changes_container = self._createDefinitionChangesContainer(global_container_stack)
definition_changes_container.setProperty("machine_heated_bed", "value", heated_bed)
@ -51,7 +51,7 @@ class UMOUpgradeSelection(MachineAction):
definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().addContainer(definition_changes_container)
ContainerRegistry.getInstance().addContainer(definition_changes_container)
global_container_stack.definitionChanges = definition_changes_container
return definition_changes_container

View File

@ -12,13 +12,6 @@ catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Ultimaker machine actions"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)"),
"api": 3
}
}
def register(app):

View File

@ -0,0 +1,8 @@
{
"name": "Ultimaker machine actions",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -10,13 +10,6 @@ upgrade = VersionUpgrade21to22.VersionUpgrade21to22()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.1 to 2.2"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.1 to Cura 2.2."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("profile", 1000000): ("quality", 2000000, upgrade.upgradeProfile),

View File

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 2.1 to 2.2",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -10,13 +10,6 @@ upgrade = VersionUpgrade.VersionUpgrade22to24()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.2 to 2.4"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.2 to Cura 2.4."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("machine_instance", 2000000): ("machine_stack", 3000000, upgrade.upgradeMachineInstance),

View File

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 2.2 to 2.4",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -5,7 +5,6 @@ import configparser #To parse the files we need to upgrade and write the new fil
import io #To serialise configparser output to a string.
from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
_removed_settings = { #Settings that were removed in 2.5.
"start_layers_at_same_position",
@ -62,8 +61,13 @@ class VersionUpgrade25to26(VersionUpgrade):
parser["general"]["visible_settings"] = ";".join(new_visible_settings)
#Change the version number in the file.
if parser.has_section("general"): #It better have!
parser["general"]["version"] = "5"
if "general" not in parser:
parser["general"] = {}
parser.set("general", "version", "4")
if "metadata" not in parser:
parser["metadata"] = {}
parser.set("metadata", "setting_version", "1")
#Re-serialise the file.
output = io.StringIO()
@ -91,11 +95,9 @@ class VersionUpgrade25to26(VersionUpgrade):
if not parser.has_section(each_section):
parser.add_section(each_section)
# Change the version number in the file.
parser["metadata"]["setting_version"] = str(CuraApplication.SettingVersion)
# Update version
# Update version numbers
parser["general"]["version"] = "2"
parser["metadata"]["setting_version"] = "1"
#Re-serialise the file.
output = io.StringIO()

View File

@ -10,13 +10,6 @@ upgrade = VersionUpgrade25to26.VersionUpgrade25to26()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.5 to 2.6"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.5 to Cura 2.6."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("preferences", 4000000): ("preferences", 4000001, upgrade.upgradePreferences),

View File

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 2.5 to 2.6",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -0,0 +1,172 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
import io #To serialise configparser output to a string.
from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
# a dict of renamed quality profiles: <old_id> : <new_id>
_renamed_quality_profiles = {
"um3_aa0.4_PVA_Not_Supported_Quality": "um3_aa0.4_PVA_Fast_Print",
"um3_aa0.8_CPEP_Not_Supported_Quality": "um3_aa0.8_CPEP_Fast_Print",
"um3_aa0.8_CPEP_Not_Supported_Superdraft_Quality": "um3_aa0.8_CPEP_Superdraft_Print",
"um3_aa0.8_CPEP_Not_Supported_Verydraft_Quality": "um3_aa0.8_CPEP_Verydraft_Print",
"um3_aa0.8_PC_Not_Supported_Quality": "um3_aa0.8_PC_Fast_Print",
"um3_aa0.8_PC_Not_Supported_Superdraft_Quality": "um3_aa0.8_PC_Superdraft_Print",
"um3_aa0.8_PC_Not_Supported_Verydraft_Quality": "um3_aa0.8_PC_Verydraft_Print",
"um3_aa0.8_PVA_Not_Supported_Quality": "um3_aa0.8_PVA_Fast_Print",
"um3_aa0.8_PVA_Not_Supported_Superdraft_Quality": "um3_aa0.8_PVA_Superdraft_Print",
"um3_bb0.4_ABS_Not_Supported_Quality": "um3_bb0.4_ABS_Fast_print",
"um3_bb0.4_ABS_Not_Supported_Superdraft_Quality": "um3_bb0.4_ABS_Superdraft_Print",
"um3_bb0.4_CPE_Not_Supported_Quality": "um3_bb0.4_CPE_Fast_Print",
"um3_bb0.4_CPE_Not_Supported_Superdraft_Quality": "um3_bb0.4_CPE_Superdraft_Print",
"um3_bb0.4_CPEP_Not_Supported_Quality": "um3_bb0.4_CPEP_Fast_Print",
"um3_bb0.4_CPEP_Not_Supported_Superdraft_Quality": "um3_bb0.4_CPEP_Superdraft_Print",
"um3_bb0.4_Nylon_Not_Supported_Quality": "um3_bb0.4_Nylon_Fast_Print",
"um3_bb0.4_Nylon_Not_Supported_Superdraft_Quality": "um3_bb0.4_Nylon_Superdraft_Print",
"um3_bb0.4_PC_Not_Supported_Quality": "um3_bb0.4_PC_Fast_Print",
"um3_bb0.4_PLA_Not_Supported_Quality": "um3_bb0.4_PLA_Fast_Print",
"um3_bb0.4_PLA_Not_Supported_Superdraft_Quality": "um3_bb0.4_PLA_Superdraft_Print",
"um3_bb0.4_TPU_Not_Supported_Quality": "um3_bb0.4_TPU_Fast_Print",
"um3_bb0.4_TPU_Not_Supported_Superdraft_Quality": "um3_bb0.4_TPU_Superdraft_Print",
"um3_bb0.8_ABS_Not_Supported_Quality": "um3_bb0.8_ABS_Fast_Print",
"um3_bb0.8_ABS_Not_Supported_Superdraft_Quality": "um3_bb0.8_ABS_Superdraft_Print",
"um3_bb0.8_CPE_Not_Supported_Quality": "um3_bb0.8_CPE_Fast_Print",
"um3_bb0.8_CPE_Not_Supported_Superdraft_Quality": "um3_bb0.8_CPE_Superdraft_Print",
"um3_bb0.8_CPEP_Not_Supported_Quality": "um3_bb0.um3_bb0.8_CPEP_Fast_Print",
"um3_bb0.8_CPEP_Not_Supported_Superdraft_Quality": "um3_bb0.8_CPEP_Superdraft_Print",
"um3_bb0.8_Nylon_Not_Supported_Quality": "um3_bb0.8_Nylon_Fast_Print",
"um3_bb0.8_Nylon_Not_Supported_Superdraft_Quality": "um3_bb0.8_Nylon_Superdraft_Print",
"um3_bb0.8_PC_Not_Supported_Quality": "um3_bb0.8_PC_Fast_Print",
"um3_bb0.8_PC_Not_Supported_Superdraft_Quality": "um3_bb0.8_PC_Superdraft_Print",
"um3_bb0.8_PLA_Not_Supported_Quality": "um3_bb0.8_PLA_Fast_Print",
"um3_bb0.8_PLA_Not_Supported_Superdraft_Quality": "um3_bb0.8_PLA_Superdraft_Print",
"um3_bb0.8_TPU_Not_Supported_Quality": "um3_bb0.8_TPU_Fast_print",
"um3_bb0.8_TPU_Not_Supported_Superdraft_Quality": "um3_bb0.8_TPU_Superdraft_Print",
}
## A collection of functions that convert the configuration of the user in Cura
# 2.6 to a configuration for Cura 2.7.
#
# All of these methods are essentially stateless.
class VersionUpgrade26to27(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 2.6 format.
#
# Since the format may change, this is implemented for the 2.6 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
# \param serialised The serialised form of a CFG file.
# \return The version number stored in the CFG file.
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 2.6 to 2.7.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
def upgradePreferences(self, serialised, filename):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
# Update version numbers
if "general" not in parser:
parser["general"] = {}
parser["general"]["version"] = "4"
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "2"
#Renamed setting value for g-code flavour.
if "values" in parser and "machine_gcode_flavor" in parser["values"]:
if parser["values"]["machine_gcode_flavor"] == "RepRap (Volumatric)":
parser["values"]["machine_gcode_flavor"] = "RepRap (Volumetric)"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades a container file other than a container stack file from version 2.6 to 2.7.
#
# \param serialised The serialised form of a container file.
# \param filename The name of the file to upgrade.
def upgradeOtherContainer(self, serialised, filename):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
# Update version numbers
if "general" not in parser:
parser["general"] = {}
parser["general"]["version"] = "2"
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "2"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades a container stack from version 2.6 to 2.7.
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
def upgradeStack(self, serialised, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
# Update IDs of the renamed quality profiles
if parser.has_section("containers"):
key_list = [key for key in parser["containers"].keys()]
for key in key_list:
container_id = parser.get("containers", key)
new_id = _renamed_quality_profiles.get(container_id)
if new_id is not None:
parser.set("containers", key, new_id)
for each_section in ("general", "metadata"):
if not parser.has_section(each_section):
parser.add_section(each_section)
# Update version numbers
if "general" not in parser:
parser["general"] = {}
parser["general"]["version"] = "3"
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "2"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]

Some files were not shown because too many files have changed in this diff Show More