mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-07-11 01:22:12 +08:00
Merge remote-tracking branch 'upstream/master' into mb-group-walls
This commit is contained in:
commit
9fc39a19c9
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,9 +30,6 @@ cura.desktop
|
||||
.pydevproject
|
||||
.settings
|
||||
|
||||
# Debian packaging
|
||||
debian*
|
||||
|
||||
#Externally located plug-ins.
|
||||
plugins/Doodle3D-cura-plugin
|
||||
plugins/GodMode
|
||||
|
29
README.md
29
README.md
@ -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.
|
@ -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"]
|
||||
|
@ -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"}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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"))])
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
57
cura/Settings/MaterialManager.py
Normal file
57
cura/Settings/MaterialManager.py
Normal 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))
|
21
cura/Settings/MaterialsModel.py
Normal file
21
cura/Settings/MaterialsModel.py
Normal 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()
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"] = [
|
||||
{
|
||||
|
8
plugins/3MFReader/plugin.json
Normal file
8
plugins/3MFReader/plugin.json
Normal 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"
|
||||
}
|
@ -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)
|
||||
|
@ -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"] = {
|
||||
|
8
plugins/3MFWriter/plugin.json
Normal file
8
plugins/3MFWriter/plugin.json
Normal 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"
|
||||
}
|
@ -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() }
|
||||
|
8
plugins/AutoSave/plugin.json
Normal file
8
plugins/AutoSave/plugin.json
Normal 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"
|
||||
}
|
@ -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")
|
||||
|
@ -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 won’t 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 won’t 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
|
||||
We’ve 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 @@ We’ve 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
|
||||
We’ve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If there’s 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.
|
||||
We’ve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If there’s 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 @@ It’s a lot simpler to save and open files, and Cura will know if it’s 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
|
||||
We’ve 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
|
||||
|
@ -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()}
|
||||
|
8
plugins/ChangeLogPlugin/plugin.json
Normal file
8
plugins/ChangeLogPlugin/plugin.json
Normal 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"
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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() }
|
||||
|
8
plugins/CuraEngineBackend/plugin.json
Normal file
8
plugins/CuraEngineBackend/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/CuraProfileReader/plugin.json
Normal file
8
plugins/CuraProfileReader/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/CuraProfileWriter/plugin.json
Normal file
8
plugins/CuraProfileWriter/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/GCodeProfileReader/plugin.json
Normal file
8
plugins/GCodeProfileReader/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/GCodeReader/plugin.json
Normal file
8
plugins/GCodeReader/plugin.json
Normal 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"
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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": [{
|
||||
|
8
plugins/GCodeWriter/plugin.json
Normal file
8
plugins/GCodeWriter/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/ImageReader/plugin.json
Normal file
8
plugins/ImageReader/plugin.json
Normal 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"
|
||||
}
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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));
|
||||
|
8
plugins/LayerView/plugin.json
Normal file
8
plugins/LayerView/plugin.json
Normal 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"
|
||||
}
|
@ -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",
|
||||
|
8
plugins/LegacyProfileReader/plugin.json
Normal file
8
plugins/LegacyProfileReader/plugin.json
Normal 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"
|
||||
}
|
@ -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
@ -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() }
|
||||
|
8
plugins/MachineSettingsAction/plugin.json
Normal file
8
plugins/MachineSettingsAction/plugin.json
Normal 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"
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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"),
|
||||
|
8
plugins/PerObjectSettingsTool/plugin.json
Normal file
8
plugins/PerObjectSettingsTool/plugin.json
Normal 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"
|
||||
}
|
249
plugins/PluginBrowser/PluginBrowser.py
Normal file
249
plugins/PluginBrowser/PluginBrowser.py
Normal 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)
|
182
plugins/PluginBrowser/PluginBrowser.qml
Normal file
182
plugins/PluginBrowser/PluginBrowser.qml
Normal 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" }
|
||||
}
|
||||
}
|
12
plugins/PluginBrowser/__init__.py
Normal file
12
plugins/PluginBrowser/__init__.py
Normal 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()}
|
7
plugins/PluginBrowser/plugin.json
Normal file
7
plugins/PluginBrowser/plugin.json
Normal 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."
|
||||
}
|
@ -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]
|
||||
|
@ -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):
|
||||
|
8
plugins/RemovableDriveOutputDevice/plugin.json
Normal file
8
plugins/RemovableDriveOutputDevice/plugin.json
Normal 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"
|
||||
}
|
@ -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.
|
||||
|
38
plugins/SliceInfoPlugin/SliceInfoJob.py
Normal file
38
plugins/SliceInfoPlugin/SliceInfoJob.py
Normal 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")
|
@ -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):
|
||||
|
8
plugins/SliceInfoPlugin/plugin.json
Normal file
8
plugins/SliceInfoPlugin/plugin.json
Normal 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"
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
8
plugins/SolidView/plugin.json
Normal file
8
plugins/SolidView/plugin.json
Normal 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"
|
||||
}
|
@ -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 : ""
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()}
|
8
plugins/UM3NetworkPrinting/plugin.json
Normal file
8
plugins/UM3NetworkPrinting/plugin.json
Normal 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"
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
8
plugins/USBPrinting/plugin.json
Normal file
8
plugins/USBPrinting/plugin.json
Normal 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"
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
8
plugins/UltimakerMachineActions/plugin.json
Normal file
8
plugins/UltimakerMachineActions/plugin.json
Normal 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"
|
||||
}
|
@ -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),
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
Normal 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"
|
||||
}
|
@ -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),
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
Normal 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"
|
||||
}
|
@ -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()
|
||||
|
@ -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),
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
Normal 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"
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user