mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-05-19 17:19:07 +08:00
Merge branch 'master' of https://github.com/Ultimaker/Cura into first_layer_line_width
This commit is contained in:
commit
ea35fdfd30
8
.gitignore
vendored
8
.gitignore
vendored
@ -11,6 +11,10 @@ resources/firmware
|
|||||||
resources/materials
|
resources/materials
|
||||||
LC_MESSAGES
|
LC_MESSAGES
|
||||||
.cache
|
.cache
|
||||||
|
*.qmlc
|
||||||
|
|
||||||
|
#MacOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Editors and IDEs.
|
# Editors and IDEs.
|
||||||
*kdev*
|
*kdev*
|
||||||
@ -26,9 +30,6 @@ cura.desktop
|
|||||||
.pydevproject
|
.pydevproject
|
||||||
.settings
|
.settings
|
||||||
|
|
||||||
# Debian packaging
|
|
||||||
debian*
|
|
||||||
|
|
||||||
#Externally located plug-ins.
|
#Externally located plug-ins.
|
||||||
plugins/Doodle3D-cura-plugin
|
plugins/Doodle3D-cura-plugin
|
||||||
plugins/GodMode
|
plugins/GodMode
|
||||||
@ -37,6 +38,7 @@ plugins/X3GWriter
|
|||||||
plugins/FlatProfileExporter
|
plugins/FlatProfileExporter
|
||||||
plugins/ProfileFlattener
|
plugins/ProfileFlattener
|
||||||
plugins/cura-god-mode-plugin
|
plugins/cura-god-mode-plugin
|
||||||
|
plugins/cura-big-flame-graph
|
||||||
|
|
||||||
#Build stuff
|
#Build stuff
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
9
Jenkinsfile
vendored
9
Jenkinsfile
vendored
@ -14,9 +14,14 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
|
|||||||
dir('build') {
|
dir('build') {
|
||||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
||||||
stage('Build') {
|
stage('Build') {
|
||||||
|
def branch = env.BRANCH_NAME
|
||||||
|
if(!(branch =~ /^2.\d+$/)) {
|
||||||
|
branch = "master"
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
|
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
|
||||||
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/master")
|
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
||||||
cmake("..", "-DCMAKE_PREFIX_PATH=${env.CURA_ENVIRONMENT_PATH} -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=${uranium_dir}")
|
cmake("..", "-DCMAKE_PREFIX_PATH=${env.CURA_ENVIRONMENT_PATH}/${branch} -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=${uranium_dir}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
||||||
|
11
README.md
11
README.md
@ -14,10 +14,11 @@ For crashes and similar issues, please attach the following information:
|
|||||||
|
|
||||||
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
||||||
* The Cura GUI log file, located at
|
* The Cura GUI log file, located at
|
||||||
* $User/AppData/Local/cura/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 (OSX)
|
* $User/Library/Application Support/cura/`<Cura version>`/cura.log (OSX)
|
||||||
* $USER/.local/share/cura (Ubuntu/Linux)
|
* $USER/.local/share/cura/`<Cura version>`/cura.log (Ubuntu/Linux)
|
||||||
* The Cura Engine log, using Help -> Show Engine Log
|
|
||||||
|
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
@ -44,13 +45,13 @@ Please checkout [cura-build](https://github.com/Ultimaker/cura-build)
|
|||||||
|
|
||||||
Third party plugins
|
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.
|
* [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.
|
* [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.
|
* [X3G Writer](https://github.com/Ghostkeeper/X3GWriter): Adds support for exporting X3G files.
|
||||||
* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model.
|
* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model.
|
||||||
* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura.
|
* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura.
|
||||||
* [WirelessPrinting Plugin](https://github.com/probonopd/WirelessPrinting): Print wirelessly from Cura to your 3D printer connected to an ESP8266 module.
|
* [WirelessPrinting Plugin](https://github.com/probonopd/WirelessPrinting): Print wirelessly from Cura to your 3D printer connected to an ESP8266 module.
|
||||||
|
* [Electric Print Cost Calculator Plugin](https://github.com/zoff99/ElectricPrintCostCalculator): Calculate the electric costs of a print.
|
||||||
|
|
||||||
Making profiles for other printers
|
Making profiles for other printers
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
@ -104,7 +104,6 @@ class BuildVolume(SceneNode):
|
|||||||
# but it does not update the disallowed areas after material change
|
# but it does not update the disallowed areas after material change
|
||||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
|
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
|
||||||
|
|
||||||
|
|
||||||
def _onSceneChanged(self, source):
|
def _onSceneChanged(self, source):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
@ -433,7 +432,8 @@ class BuildVolume(SceneNode):
|
|||||||
self._global_container_stack.getProperty("raft_interface_thickness", "value") +
|
self._global_container_stack.getProperty("raft_interface_thickness", "value") +
|
||||||
self._global_container_stack.getProperty("raft_surface_layers", "value") *
|
self._global_container_stack.getProperty("raft_surface_layers", "value") *
|
||||||
self._global_container_stack.getProperty("raft_surface_thickness", "value") +
|
self._global_container_stack.getProperty("raft_surface_thickness", "value") +
|
||||||
self._global_container_stack.getProperty("raft_airgap", "value"))
|
self._global_container_stack.getProperty("raft_airgap", "value") -
|
||||||
|
self._global_container_stack.getProperty("layer_0_z_overlap", "value"))
|
||||||
|
|
||||||
# Rounding errors do not matter, we check if raft_thickness has changed at all
|
# Rounding errors do not matter, we check if raft_thickness has changed at all
|
||||||
if old_raft_thickness != self._raft_thickness:
|
if old_raft_thickness != self._raft_thickness:
|
||||||
@ -562,7 +562,7 @@ class BuildVolume(SceneNode):
|
|||||||
used_extruders = [self._global_container_stack]
|
used_extruders = [self._global_container_stack]
|
||||||
|
|
||||||
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
|
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
|
||||||
prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
|
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
|
||||||
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
||||||
|
|
||||||
#Check if prime positions intersect with disallowed areas.
|
#Check if prime positions intersect with disallowed areas.
|
||||||
@ -636,7 +636,7 @@ class BuildVolume(SceneNode):
|
|||||||
result[extruder.getId()] = []
|
result[extruder.getId()] = []
|
||||||
|
|
||||||
#Currently, the only normally printed object is the prime tower.
|
#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")
|
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
||||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
@ -658,7 +658,7 @@ class BuildVolume(SceneNode):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Computes the disallowed areas for the prime locations.
|
## Computes the disallowed areas for the prime blobs.
|
||||||
#
|
#
|
||||||
# These are special because they are not subject to things like brim or
|
# These are special because they are not subject to things like brim or
|
||||||
# travel avoidance. They do get a dilute with the border size though
|
# travel avoidance. They do get a dilute with the border size though
|
||||||
@ -669,17 +669,18 @@ class BuildVolume(SceneNode):
|
|||||||
# \param used_extruders The extruder stacks to generate disallowed areas
|
# \param used_extruders The extruder stacks to generate disallowed areas
|
||||||
# for.
|
# for.
|
||||||
# \return A dictionary with for each used extruder ID the prime areas.
|
# \return A dictionary with for each used extruder ID the prime areas.
|
||||||
def _computeDisallowedAreasPrime(self, border_size, used_extruders):
|
def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders):
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
|
prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value")
|
||||||
prime_x = extruder.getProperty("extruder_prime_pos_x", "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
|
#Ignore extruder prime position if it is not set or if blob is disabled
|
||||||
if prime_x == 0 and prime_y == 0:
|
if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled:
|
||||||
result[extruder.getId()] = []
|
result[extruder.getId()] = []
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -715,6 +716,11 @@ class BuildVolume(SceneNode):
|
|||||||
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
||||||
machine_disallowed_polygons.append(polygon)
|
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 = {}
|
result = {}
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
extruder_id = extruder.getId()
|
extruder_id = extruder.getId()
|
||||||
@ -724,6 +730,8 @@ class BuildVolume(SceneNode):
|
|||||||
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
|
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||||
if offset_y is None:
|
if offset_y is None:
|
||||||
offset_y = 0
|
offset_y = 0
|
||||||
|
else:
|
||||||
|
offset_y = -offset_y
|
||||||
result[extruder_id] = []
|
result[extruder_id] = []
|
||||||
|
|
||||||
for polygon in machine_disallowed_polygons:
|
for polygon in machine_disallowed_polygons:
|
||||||
@ -734,10 +742,13 @@ class BuildVolume(SceneNode):
|
|||||||
right_unreachable_border = 0
|
right_unreachable_border = 0
|
||||||
top_unreachable_border = 0
|
top_unreachable_border = 0
|
||||||
bottom_unreachable_border = 0
|
bottom_unreachable_border = 0
|
||||||
|
|
||||||
|
# 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.
|
#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():
|
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||||
other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value")
|
other_offset_y = -other_extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||||
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
|
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
|
||||||
right_unreachable_border = max(right_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)
|
top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y)
|
||||||
@ -869,7 +880,7 @@ class BuildVolume(SceneNode):
|
|||||||
else:
|
else:
|
||||||
extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value")
|
extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value")
|
||||||
|
|
||||||
if extruder_index == "-1": # If extruder index is -1 use global instead
|
if str(extruder_index) == "-1": # If extruder index is -1 use global instead
|
||||||
stack = self._global_container_stack
|
stack = self._global_container_stack
|
||||||
else:
|
else:
|
||||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
||||||
@ -950,9 +961,9 @@ class BuildVolume(SceneNode):
|
|||||||
return max(min(value, max_value), min_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"]
|
||||||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
|
_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"]
|
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
|
||||||
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"]
|
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
|
||||||
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
||||||
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
_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"]
|
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
|
||||||
|
@ -257,7 +257,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||||||
# \return New Polygon instance that is offset with everything that
|
# \return New Polygon instance that is offset with everything that
|
||||||
# influences the collision area.
|
# influences the collision area.
|
||||||
def _offsetHull(self, convex_hull):
|
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
|
mold_width = 0
|
||||||
if self._getSettingProperty("mold_enabled", "value"):
|
if self._getSettingProperty("mold_enabled", "value"):
|
||||||
mold_width = self._getSettingProperty("mold_width", "value")
|
mold_width = self._getSettingProperty("mold_width", "value")
|
||||||
@ -298,7 +302,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||||
def _getSettingProperty(self, setting_key, property="value"):
|
def _getSettingProperty(self, setting_key, property = "value"):
|
||||||
per_mesh_stack = self._node.callDecoration("getStack")
|
per_mesh_stack = self._node.callDecoration("getStack")
|
||||||
if per_mesh_stack:
|
if per_mesh_stack:
|
||||||
return per_mesh_stack.getProperty(setting_key, property)
|
return per_mesh_stack.getProperty(setting_key, property)
|
||||||
@ -314,10 +318,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"]
|
extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"]
|
||||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||||
return extruder_stack.getProperty(setting_key, property)
|
return extruder_stack.getProperty(setting_key, property)
|
||||||
else: #Limit_to_extruder is set. Use that one.
|
else: #Limit_to_extruder is set. The global stack handles this then.
|
||||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
return self._global_stack.getProperty(setting_key, property)
|
||||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
|
||||||
return stack.getProperty(setting_key, property)
|
|
||||||
|
|
||||||
## Returns true if node is a descendant or the same as the root node.
|
## Returns true if node is a descendant or the same as the root node.
|
||||||
def __isDescendant(self, root, node):
|
def __isDescendant(self, root, node):
|
||||||
@ -328,11 +330,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||||||
return self.__isDescendant(root, node.getParent())
|
return self.__isDescendant(root, node.getParent())
|
||||||
|
|
||||||
_affected_settings = [
|
_affected_settings = [
|
||||||
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
|
"adhesion_type", "raft_margin", "print_sequence",
|
||||||
"raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
|
|
||||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
||||||
|
|
||||||
## Settings that change the convex hull.
|
## Settings that change the convex hull.
|
||||||
#
|
#
|
||||||
# If these settings change, the convex hull should be recalculated.
|
# 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"}
|
||||||
|
@ -2,6 +2,9 @@ import sys
|
|||||||
import platform
|
import platform
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import faulthandler
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
|
||||||
@ -91,6 +94,17 @@ def show(exception_type, value, tb):
|
|||||||
crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}"
|
crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}"
|
||||||
crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace)
|
crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace)
|
||||||
|
|
||||||
|
tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
|
||||||
|
os.close(tmp_file_fd)
|
||||||
|
with open(tmp_file_path, "w") as f:
|
||||||
|
faulthandler.dump_traceback(f, all_threads=True)
|
||||||
|
with open(tmp_file_path, "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
msg = "-------------------------\n"
|
||||||
|
msg += data
|
||||||
|
crash_info += "\n\n" + msg
|
||||||
|
|
||||||
textarea.setText(crash_info)
|
textarea.setText(crash_info)
|
||||||
|
|
||||||
buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog)
|
buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog)
|
||||||
|
@ -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.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QLocalServer
|
from PyQt5.QtNetwork import QLocalServer
|
||||||
from PyQt5.QtNetwork import QLocalSocket
|
from PyQt5.QtNetwork import QLocalSocket
|
||||||
|
|
||||||
@ -25,7 +26,6 @@ from UM.Settings.Validator import Validator
|
|||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||||
from UM.Platform import Platform
|
|
||||||
from UM.Decorators import deprecated
|
from UM.Decorators import deprecated
|
||||||
|
|
||||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||||
@ -47,6 +47,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from cura.Settings.MachineNameValidator import MachineNameValidator
|
from cura.Settings.MachineNameValidator import MachineNameValidator
|
||||||
from cura.Settings.ProfilesModel import ProfilesModel
|
from cura.Settings.ProfilesModel import ProfilesModel
|
||||||
|
from cura.Settings.MaterialsModel import MaterialsModel
|
||||||
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
||||||
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||||
from cura.Settings.UserProfilesModel import UserProfilesModel
|
from cura.Settings.UserProfilesModel import UserProfilesModel
|
||||||
@ -62,6 +63,7 @@ from . import CameraImageProvider
|
|||||||
from . import MachineActionManager
|
from . import MachineActionManager
|
||||||
|
|
||||||
from cura.Settings.MachineManager import MachineManager
|
from cura.Settings.MachineManager import MachineManager
|
||||||
|
from cura.Settings.MaterialManager import MaterialManager
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Settings.UserChangesModel import UserChangesModel
|
from cura.Settings.UserChangesModel import UserChangesModel
|
||||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||||
@ -99,6 +101,11 @@ if not MYPY:
|
|||||||
|
|
||||||
|
|
||||||
class CuraApplication(QtApplication):
|
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 = 2
|
||||||
|
|
||||||
class ResourceTypes:
|
class ResourceTypes:
|
||||||
QmlFiles = Resources.UserType + 1
|
QmlFiles = Resources.UserType + 1
|
||||||
Firmware = Resources.UserType + 2
|
Firmware = Resources.UserType + 2
|
||||||
@ -133,13 +140,14 @@ class CuraApplication(QtApplication):
|
|||||||
# From which stack the setting would inherit if not defined per object (handled in the engine)
|
# From which stack the setting would inherit if not defined per object (handled in the engine)
|
||||||
# AND for settings which are not settable_per_mesh:
|
# AND for settings which are not settable_per_mesh:
|
||||||
# which extruder is the only extruder this setting is obtained from
|
# which extruder is the only extruder this setting is obtained from
|
||||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1")
|
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1", depends_on = "value")
|
||||||
|
|
||||||
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
||||||
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
|
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
|
||||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
|
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
|
||||||
|
|
||||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||||
|
SettingDefinition.addSettingType("optional_extruder", None, str, None)
|
||||||
|
|
||||||
SettingDefinition.addSettingType("[int]", None, str, None)
|
SettingDefinition.addSettingType("[int]", None, str, None)
|
||||||
|
|
||||||
@ -169,11 +177,12 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
|
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
|
||||||
{
|
{
|
||||||
("quality", InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
("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"),
|
("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
|
||||||
("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
|
("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
|
||||||
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||||
("user", InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
("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"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -182,6 +191,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||||
self._machine_manager = None # This is initialized on demand.
|
self._machine_manager = None # This is initialized on demand.
|
||||||
|
self._material_manager = None
|
||||||
self._setting_inheritance_manager = None
|
self._setting_inheritance_manager = None
|
||||||
|
|
||||||
self._additional_components = {} # Components to add to certain areas in the interface
|
self._additional_components = {} # Components to add to certain areas in the interface
|
||||||
@ -257,11 +267,14 @@ class CuraApplication(QtApplication):
|
|||||||
with ContainerRegistry.getInstance().lockFile():
|
with ContainerRegistry.getInstance().lockFile():
|
||||||
ContainerRegistry.getInstance().load()
|
ContainerRegistry.getInstance().load()
|
||||||
|
|
||||||
|
# set the setting version for Preferences
|
||||||
|
Preferences.getInstance().addPreference("metadata/setting_version", CuraApplication.SettingVersion)
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/categories_expanded", "")
|
Preferences.getInstance().addPreference("cura/categories_expanded", "")
|
||||||
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
|
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
|
||||||
Preferences.getInstance().addPreference("view/center_on_select", False)
|
Preferences.getInstance().addPreference("view/center_on_select", True)
|
||||||
Preferences.getInstance().addPreference("mesh/scale_to_fit", False)
|
Preferences.getInstance().addPreference("mesh/scale_to_fit", False)
|
||||||
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
||||||
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
|
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
|
||||||
@ -294,6 +307,7 @@ class CuraApplication(QtApplication):
|
|||||||
z_seam_y
|
z_seam_y
|
||||||
infill
|
infill
|
||||||
infill_sparse_density
|
infill_sparse_density
|
||||||
|
gradual_infill_steps
|
||||||
material
|
material
|
||||||
material_print_temperature
|
material_print_temperature
|
||||||
material_bed_temperature
|
material_bed_temperature
|
||||||
@ -314,7 +328,6 @@ class CuraApplication(QtApplication):
|
|||||||
support_enable
|
support_enable
|
||||||
support_extruder_nr
|
support_extruder_nr
|
||||||
support_type
|
support_type
|
||||||
support_interface_density
|
|
||||||
platform_adhesion
|
platform_adhesion
|
||||||
adhesion_type
|
adhesion_type
|
||||||
adhesion_extruder_nr
|
adhesion_extruder_nr
|
||||||
@ -331,6 +344,7 @@ class CuraApplication(QtApplication):
|
|||||||
blackmagic
|
blackmagic
|
||||||
print_sequence
|
print_sequence
|
||||||
infill_mesh
|
infill_mesh
|
||||||
|
cutting_mesh
|
||||||
experimental
|
experimental
|
||||||
""".replace("\n", ";").replace(" ", ""))
|
""".replace("\n", ";").replace(" ", ""))
|
||||||
|
|
||||||
@ -340,6 +354,8 @@ class CuraApplication(QtApplication):
|
|||||||
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
|
|
||||||
|
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
|
||||||
@ -481,7 +497,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
self._plugin_registry.loadPlugins()
|
self._plugin_registry.loadPlugins()
|
||||||
|
|
||||||
if self.getBackend() == None:
|
if self.getBackend() is None:
|
||||||
raise RuntimeError("Could not load the backend plugin!")
|
raise RuntimeError("Could not load the backend plugin!")
|
||||||
|
|
||||||
self._plugins_loaded = True
|
self._plugins_loaded = True
|
||||||
@ -618,7 +634,9 @@ class CuraApplication(QtApplication):
|
|||||||
camera.lookAt(Vector(0, 0, 0))
|
camera.lookAt(Vector(0, 0, 0))
|
||||||
controller.getScene().setActiveCamera("3d")
|
controller.getScene().setActiveCamera("3d")
|
||||||
|
|
||||||
self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))
|
camera_tool = self.getController().getTool("CameraTool")
|
||||||
|
camera_tool.setOrigin(Vector(0, 100, 0))
|
||||||
|
camera_tool.setZoomRange(0.1, 200000)
|
||||||
|
|
||||||
self._camera_animation = CameraAnimation.CameraAnimation()
|
self._camera_animation = CameraAnimation.CameraAnimation()
|
||||||
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
||||||
@ -628,6 +646,7 @@ class CuraApplication(QtApplication):
|
|||||||
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
||||||
ExtruderManager.getInstance()
|
ExtruderManager.getInstance()
|
||||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||||
|
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
||||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
||||||
self.getSettingInheritanceManager)
|
self.getSettingInheritanceManager)
|
||||||
|
|
||||||
@ -653,6 +672,11 @@ class CuraApplication(QtApplication):
|
|||||||
self._machine_manager = MachineManager.createMachineManager()
|
self._machine_manager = MachineManager.createMachineManager()
|
||||||
return self._machine_manager
|
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):
|
def getSettingInheritanceManager(self, *args):
|
||||||
if self._setting_inheritance_manager is None:
|
if self._setting_inheritance_manager is None:
|
||||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||||
@ -696,6 +720,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||||
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
||||||
|
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
|
||||||
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
||||||
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
||||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||||
@ -739,8 +764,7 @@ class CuraApplication(QtApplication):
|
|||||||
# Default
|
# Default
|
||||||
self.getController().setActiveTool("TranslateTool")
|
self.getController().setActiveTool("TranslateTool")
|
||||||
|
|
||||||
# Hack: QVector bindings are broken on PyQt 5.7.1 on Windows. This disables it being called at all.
|
if Preferences.getInstance().getValue("view/center_on_select"):
|
||||||
if Preferences.getInstance().getValue("view/center_on_select") and not Platform.isWindows():
|
|
||||||
self._center_after_select = True
|
self._center_after_select = True
|
||||||
else:
|
else:
|
||||||
if self.getController().getActiveTool():
|
if self.getController().getActiveTool():
|
||||||
|
@ -69,7 +69,7 @@ class LayerDataBuilder(MeshBuilder):
|
|||||||
|
|
||||||
vertex_offset = 0
|
vertex_offset = 0
|
||||||
index_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)
|
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices)
|
||||||
self._element_counts[layer] = data.elementCount
|
self._element_counts[layer] = data.elementCount
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from UM.Math.Color import Color
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import numpy
|
import numpy
|
||||||
@ -16,8 +18,9 @@ class LayerPolygon:
|
|||||||
MoveCombingType = 8
|
MoveCombingType = 8
|
||||||
MoveRetractionType = 9
|
MoveRetractionType = 9
|
||||||
SupportInterfaceType = 10
|
SupportInterfaceType = 10
|
||||||
|
__number_of_types = 11
|
||||||
|
|
||||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType)
|
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||||
|
|
||||||
## LayerPolygon, used in ProcessSlicedLayersJob
|
## LayerPolygon, used in ProcessSlicedLayersJob
|
||||||
# \param extruder
|
# \param extruder
|
||||||
@ -28,6 +31,9 @@ class LayerPolygon:
|
|||||||
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses):
|
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses):
|
||||||
self._extruder = extruder
|
self._extruder = extruder
|
||||||
self._types = line_types
|
self._types = line_types
|
||||||
|
for i in range(len(self._types)):
|
||||||
|
if self._types[i] >= self.__number_of_types: #Got faulty line data from the engine.
|
||||||
|
self._types[i] = self.NoneType
|
||||||
self._data = data
|
self._data = data
|
||||||
self._line_widths = line_widths
|
self._line_widths = line_widths
|
||||||
self._line_thicknesses = line_thicknesses
|
self._line_thicknesses = line_thicknesses
|
||||||
@ -39,8 +45,8 @@ class LayerPolygon:
|
|||||||
|
|
||||||
self._jump_mask = self.__jump_map[self._types]
|
self._jump_mask = self.__jump_map[self._types]
|
||||||
self._jump_count = numpy.sum(self._jump_mask)
|
self._jump_count = numpy.sum(self._jump_mask)
|
||||||
self._mesh_line_count = len(self._types)-self._jump_count
|
self._mesh_line_count = len(self._types) - self._jump_count
|
||||||
self._vertex_count = self._mesh_line_count + numpy.sum( self._types[1:] == self._types[:-1])
|
self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
|
||||||
|
|
||||||
# Buffering the colors shouldn't be necessary as it is not
|
# Buffering the colors shouldn't be necessary as it is not
|
||||||
# re-used and can save alot of memory usage.
|
# re-used and can save alot of memory usage.
|
||||||
|
@ -23,8 +23,8 @@ class PlatformPhysicsOperation(Operation):
|
|||||||
def mergeWith(self, other):
|
def mergeWith(self, other):
|
||||||
group = GroupedOperation()
|
group = GroupedOperation()
|
||||||
|
|
||||||
group.addOperation(self)
|
|
||||||
group.addOperation(other)
|
group.addOperation(other)
|
||||||
|
group.addOperation(self)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ catalog = i18nCatalog("cura")
|
|||||||
# - This triggers a new slice with the current settings - this is the "current settings pass".
|
# - This triggers a new slice with the current settings - this is the "current settings pass".
|
||||||
# - When the slice is done, we update the current print time and material amount.
|
# - When the slice is done, we update the current print time and material amount.
|
||||||
# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
|
# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
|
||||||
# - When that is done, we update the minimum print time and start the final slice pass, the "high quality settings pass".
|
# - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass".
|
||||||
# - When the high quality pass is done, we update the maximum print time.
|
# - When the Extra Fine pass is done, we update the maximum print time.
|
||||||
#
|
#
|
||||||
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
|
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
|
||||||
# This job name is requested by the JobSpecs qml file.
|
# This job name is requested by the JobSpecs qml file.
|
||||||
@ -52,6 +52,19 @@ class PrintInformation(QObject):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._current_print_time = Duration(None, self)
|
self._current_print_time = Duration(None, self)
|
||||||
|
self._print_times_per_feature = {
|
||||||
|
"none": Duration(None, self),
|
||||||
|
"inset_0": Duration(None, self),
|
||||||
|
"inset_x": Duration(None, self),
|
||||||
|
"skin": Duration(None, self),
|
||||||
|
"support": Duration(None, self),
|
||||||
|
"skirt": Duration(None, self),
|
||||||
|
"infill": Duration(None, self),
|
||||||
|
"support_infill": Duration(None, self),
|
||||||
|
"travel": Duration(None, self),
|
||||||
|
"retract": Duration(None, self),
|
||||||
|
"support_interface": Duration(None, self)
|
||||||
|
}
|
||||||
|
|
||||||
self._material_lengths = []
|
self._material_lengths = []
|
||||||
self._material_weights = []
|
self._material_weights = []
|
||||||
@ -93,6 +106,10 @@ class PrintInformation(QObject):
|
|||||||
def currentPrintTime(self):
|
def currentPrintTime(self):
|
||||||
return self._current_print_time
|
return self._current_print_time
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantMap", notify = currentPrintTimeChanged)
|
||||||
|
def printTimesPerFeature(self):
|
||||||
|
return self._print_times_per_feature
|
||||||
|
|
||||||
materialLengthsChanged = pyqtSignal()
|
materialLengthsChanged = pyqtSignal()
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = materialLengthsChanged)
|
@pyqtProperty("QVariantList", notify = materialLengthsChanged)
|
||||||
@ -111,11 +128,15 @@ class PrintInformation(QObject):
|
|||||||
def materialCosts(self):
|
def materialCosts(self):
|
||||||
return self._material_costs
|
return self._material_costs
|
||||||
|
|
||||||
def _onPrintDurationMessage(self, total_time, material_amounts):
|
def _onPrintDurationMessage(self, time_per_feature, material_amounts):
|
||||||
if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values.
|
total_time = 0
|
||||||
|
for feature, time in time_per_feature.items():
|
||||||
|
if time != time: # Check for NaN. Engine can sometimes give us weird values.
|
||||||
|
self._print_times_per_feature[feature].setDuration(0)
|
||||||
Logger.log("w", "Received NaN for print duration message")
|
Logger.log("w", "Received NaN for print duration message")
|
||||||
self._current_print_time.setDuration(0)
|
continue
|
||||||
else:
|
total_time += time
|
||||||
|
self._print_times_per_feature[feature].setDuration(time)
|
||||||
self._current_print_time.setDuration(total_time)
|
self._current_print_time.setDuration(total_time)
|
||||||
|
|
||||||
self.currentPrintTimeChanged.emit()
|
self.currentPrintTimeChanged.emit()
|
||||||
|
@ -3,13 +3,18 @@
|
|||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtSignal, QUrl
|
||||||
|
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from enum import IntEnum # For the connection state tracking.
|
from enum import IntEnum # For the connection state tracking.
|
||||||
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Signal import signalemitter
|
from UM.Signal import signalemitter
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
i18n_catalog = i18nCatalog("cura")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
@ -57,6 +62,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||||||
|
|
||||||
self._camera_active = False
|
self._camera_active = False
|
||||||
|
|
||||||
|
self._monitor_view_qml_path = ""
|
||||||
|
self._monitor_component = None
|
||||||
|
self._monitor_item = None
|
||||||
|
self._qml_context = None
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
@ -111,6 +121,32 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||||||
# Signal to be emitted when some drastic change occurs in the remaining time (not when the time just passes on normally).
|
# Signal to be emitted when some drastic change occurs in the remaining time (not when the time just passes on normally).
|
||||||
preheatBedRemainingTimeChanged = pyqtSignal()
|
preheatBedRemainingTimeChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(QObject, constant=True)
|
||||||
|
def monitorItem(self):
|
||||||
|
# Note that we specifically only check if the monitor component is created.
|
||||||
|
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
||||||
|
# create the item (and fail) every time.
|
||||||
|
if not self._monitor_component:
|
||||||
|
self._createMonitorViewFromQML()
|
||||||
|
|
||||||
|
return self._monitor_item
|
||||||
|
|
||||||
|
def _createMonitorViewFromQML(self):
|
||||||
|
path = QUrl.fromLocalFile(self._monitor_view_qml_path)
|
||||||
|
|
||||||
|
# Because of garbage collection we need to keep this referenced by python.
|
||||||
|
self._monitor_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._monitor_item = self._monitor_component.create(self._qml_context)
|
||||||
|
if self._monitor_item is None:
|
||||||
|
Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
|
||||||
|
Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())
|
||||||
|
|
||||||
@pyqtProperty(str, notify=printerTypeChanged)
|
@pyqtProperty(str, notify=printerTypeChanged)
|
||||||
def printerType(self):
|
def printerType(self):
|
||||||
return self._printer_type
|
return self._printer_type
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# This collects a lot of quality and quality changes related code which was split between ContainerManager
|
# This collects a lot of quality and quality changes related code which was split between ContainerManager
|
||||||
# and the MachineManager and really needs to usable from both.
|
# and the MachineManager and really needs to usable from both.
|
||||||
from typing import List
|
from typing import List, Optional, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
@ -11,6 +11,10 @@ from UM.Settings.DefinitionContainer import DefinitionContainer
|
|||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainerInterface
|
||||||
|
|
||||||
class QualityManager:
|
class QualityManager:
|
||||||
|
|
||||||
@ -27,12 +31,12 @@ class QualityManager:
|
|||||||
## Find a quality by name for a specific machine definition and materials.
|
## Find a quality by name for a specific machine definition and materials.
|
||||||
#
|
#
|
||||||
# \param quality_name
|
# \param quality_name
|
||||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
# \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
|
||||||
# specified then the currently selected machine definition is used.
|
# specified then the currently selected machine definition is used.
|
||||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
|
||||||
# the current set of selected materials is used.
|
# the current set of selected materials is used.
|
||||||
# \return the matching quality container \type{ContainerInstance}
|
# \return the matching quality container \type{InstanceContainer}
|
||||||
def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
|
def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]:
|
||||||
criteria = {"type": "quality", "name": quality_name}
|
criteria = {"type": "quality", "name": quality_name}
|
||||||
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
||||||
|
|
||||||
@ -46,15 +50,20 @@ class QualityManager:
|
|||||||
## Find a quality changes container by name.
|
## Find a quality changes container by name.
|
||||||
#
|
#
|
||||||
# \param quality_changes_name \type{str} the name of the quality changes container.
|
# \param quality_changes_name \type{str} the name of the quality changes container.
|
||||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
# \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
|
||||||
# specified then the currently selected machine definition is used.
|
# specified then the currently selected machine definition is used..
|
||||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
# \return the matching quality changes containers \type{List[InstanceContainer]}
|
||||||
# the current set of selected materials is used.
|
def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
|
||||||
# \return the matching quality changes containers \type{List[ContainerInstance]}
|
if not machine_definition:
|
||||||
def findQualityChangesByName(self, quality_changes_name, machine_definition=None):
|
global_stack = Application.getGlobalContainerStack()
|
||||||
criteria = {"type": "quality_changes", "name": quality_changes_name}
|
if not global_stack:
|
||||||
result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
|
return [] #No stack, so no current definition could be found, so there are no quality changes either.
|
||||||
|
machine_definition = global_stack.definition
|
||||||
|
|
||||||
|
result = self.findAllQualityChangesForMachine(machine_definition)
|
||||||
|
for extruder in self.findAllExtruderDefinitionsForMachine(machine_definition):
|
||||||
|
result.extend(self.findAllQualityChangesForExtruder(extruder))
|
||||||
|
result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Fetch the list of available quality types for this combination of machine definition and materials.
|
## Fetch the list of available quality types for this combination of machine definition and materials.
|
||||||
@ -62,7 +71,7 @@ class QualityManager:
|
|||||||
# \param machine_definition \type{DefinitionContainer}
|
# \param machine_definition \type{DefinitionContainer}
|
||||||
# \param material_containers \type{List[InstanceContainer]}
|
# \param material_containers \type{List[InstanceContainer]}
|
||||||
# \return \type{List[str]}
|
# \return \type{List[str]}
|
||||||
def findAllQualityTypesForMachineAndMaterials(self, machine_definition, material_containers):
|
def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
|
||||||
# Determine the common set of quality types which can be
|
# Determine the common set of quality types which can be
|
||||||
# applied to all of the materials for this machine.
|
# applied to all of the materials for this machine.
|
||||||
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
|
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
|
||||||
@ -76,9 +85,9 @@ class QualityManager:
|
|||||||
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
|
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
|
||||||
#
|
#
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||||
# \param material \type{ContainerInstance} the material.
|
# \param material \type{InstanceContainer} the material.
|
||||||
# \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities.
|
# \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
|
||||||
def __fetchQualityTypeDictForMaterial(self, machine_definition, material):
|
def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
|
||||||
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
|
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
|
||||||
quality_type_dict = {}
|
quality_type_dict = {}
|
||||||
for quality in qualities:
|
for quality in qualities:
|
||||||
@ -88,35 +97,35 @@ class QualityManager:
|
|||||||
## Find a quality container by quality type.
|
## Find a quality container by quality type.
|
||||||
#
|
#
|
||||||
# \param quality_type \type{str} the name of the quality type to search for.
|
# \param quality_type \type{str} the name of the quality type to search for.
|
||||||
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
|
# \param machine_definition (Optional) \type{InstanceContainer} If nothing is
|
||||||
# specified then the currently selected machine definition is used.
|
# specified then the currently selected machine definition is used.
|
||||||
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
|
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
|
||||||
# the current set of selected materials is used.
|
# the current set of selected materials is used.
|
||||||
# \return the matching quality container \type{ContainerInstance}
|
# \return the matching quality container \type{InstanceContainer}
|
||||||
def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None, **kwargs):
|
def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer:
|
||||||
criteria = kwargs
|
criteria = kwargs
|
||||||
criteria["type"] = "quality"
|
criteria["type"] = "quality"
|
||||||
if quality_type:
|
if quality_type:
|
||||||
criteria["quality_type"] = quality_type
|
criteria["quality_type"] = quality_type
|
||||||
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
||||||
|
|
||||||
# Fall back to using generic materials and qualities if nothing could be found.
|
# Fall back to using generic materials and qualities if nothing could be found.
|
||||||
if not result and material_containers and len(material_containers) == 1:
|
if not result and material_containers and len(material_containers) == 1:
|
||||||
basic_materials = self._getBasicMaterials(material_containers[0])
|
basic_materials = self._getBasicMaterials(material_containers[0])
|
||||||
|
if basic_materials:
|
||||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||||
|
|
||||||
return result[0] if result else None
|
return result[0] if result else None
|
||||||
|
|
||||||
## Find all suitable qualities for a combination of machine and material.
|
## Find all suitable qualities for a combination of machine and material.
|
||||||
#
|
#
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||||
# \param material_container \type{ContainerInstance} the material.
|
# \param material_container \type{InstanceContainer} the material.
|
||||||
# \return \type{List[ContainerInstance]} the list of suitable qualities.
|
# \return \type{List[InstanceContainer]} the list of suitable qualities.
|
||||||
def findAllQualitiesForMachineMaterial(self, machine_definition, material_container):
|
def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
|
||||||
criteria = {"type": "quality" }
|
criteria = {"type": "quality" }
|
||||||
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
|
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
|
||||||
if not result:
|
if not result:
|
||||||
basic_materials = self._getBasicMaterials(material_container)
|
basic_materials = self._getBasicMaterials(material_container)
|
||||||
|
if basic_materials:
|
||||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -125,7 +134,7 @@ class QualityManager:
|
|||||||
#
|
#
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
||||||
# \return \type{List[InstanceContainer]} the list of quality changes
|
# \return \type{List[InstanceContainer]} the list of quality changes
|
||||||
def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]:
|
def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
|
||||||
if machine_definition.getMetaDataEntry("has_machine_quality"):
|
if machine_definition.getMetaDataEntry("has_machine_quality"):
|
||||||
definition_id = machine_definition.getId()
|
definition_id = machine_definition.getId()
|
||||||
else:
|
else:
|
||||||
@ -135,25 +144,37 @@ class QualityManager:
|
|||||||
quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
||||||
return quality_changes_list
|
return quality_changes_list
|
||||||
|
|
||||||
|
def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]:
|
||||||
|
filter_dict = { "machine": machine_definition.getId() }
|
||||||
|
return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict)
|
||||||
|
|
||||||
|
## Find all quality changes for a given extruder.
|
||||||
|
#
|
||||||
|
# \param extruder_definition The extruder to find the quality changes for.
|
||||||
|
# \return The list of quality changes for the given extruder.
|
||||||
|
def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
|
||||||
|
filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()}
|
||||||
|
return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
||||||
|
|
||||||
## Find all usable qualities for a machine and extruders.
|
## Find all usable qualities for a machine and extruders.
|
||||||
#
|
#
|
||||||
# Finds all of the qualities for this combination of machine and extruders.
|
# Finds all of the qualities for this combination of machine and extruders.
|
||||||
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
|
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
|
||||||
# then only one of then is returned (at random).
|
# then only one of then is returned (at random).
|
||||||
#
|
#
|
||||||
# \param global_container_stack \type{ContainerStack} the global machine definition
|
# \param global_container_stack \type{GlobalStack} the global machine definition
|
||||||
# \param extruder_stacks \type{List[ContainerStack]} the list of extruder stacks
|
# \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
|
||||||
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
|
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
|
||||||
# return come from the first extruder in the given list of extruders.
|
# return come from the first extruder in the given list of extruders.
|
||||||
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack, extruder_stacks):
|
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
|
||||||
global_machine_definition = global_container_stack.getBottom()
|
global_machine_definition = global_container_stack.getBottom()
|
||||||
|
|
||||||
if extruder_stacks:
|
if extruder_stacks:
|
||||||
# Multi-extruder machine detected.
|
# Multi-extruder machine detected.
|
||||||
materials = [stack.findContainer(type="material") for stack in extruder_stacks]
|
materials = [stack.material for stack in extruder_stacks]
|
||||||
else:
|
else:
|
||||||
# Machine with one extruder.
|
# Machine with one extruder.
|
||||||
materials = [global_container_stack.findContainer(type="material")]
|
materials = [global_container_stack.material]
|
||||||
|
|
||||||
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
|
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
|
||||||
|
|
||||||
@ -170,14 +191,13 @@ class QualityManager:
|
|||||||
# This tries to find a generic or basic version of the given material.
|
# This tries to find a generic or basic version of the given material.
|
||||||
# \param material_container \type{InstanceContainer} the material
|
# \param material_container \type{InstanceContainer} the material
|
||||||
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
|
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
|
||||||
def _getBasicMaterials(self, material_container):
|
def _getBasicMaterials(self, material_container: InstanceContainer):
|
||||||
base_material = material_container.getMetaDataEntry("material")
|
base_material = material_container.getMetaDataEntry("material")
|
||||||
material_container_definition = material_container.getDefinition()
|
material_container_definition = material_container.getDefinition()
|
||||||
if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
|
if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
|
||||||
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
|
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
|
||||||
else:
|
else:
|
||||||
definition_id = "fdmprinter"
|
definition_id = "fdmprinter"
|
||||||
|
|
||||||
if base_material:
|
if base_material:
|
||||||
# There is a basic material specified
|
# There is a basic material specified
|
||||||
criteria = { "type": "material", "name": base_material, "definition": definition_id }
|
criteria = { "type": "material", "name": base_material, "definition": definition_id }
|
||||||
@ -192,7 +212,7 @@ class QualityManager:
|
|||||||
def _getFilteredContainers(self, **kwargs):
|
def _getFilteredContainers(self, **kwargs):
|
||||||
return self._getFilteredContainersForStack(None, None, **kwargs)
|
return self._getFilteredContainersForStack(None, None, **kwargs)
|
||||||
|
|
||||||
def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
|
def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs):
|
||||||
# Fill in any default values.
|
# Fill in any default values.
|
||||||
if machine_definition is None:
|
if machine_definition is None:
|
||||||
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
||||||
@ -200,9 +220,14 @@ class QualityManager:
|
|||||||
if quality_definition_id is not None:
|
if quality_definition_id is not None:
|
||||||
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
|
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
|
||||||
|
|
||||||
|
# for convenience
|
||||||
if material_containers is None:
|
if material_containers is None:
|
||||||
|
material_containers = []
|
||||||
|
|
||||||
|
if not material_containers:
|
||||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||||
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
|
if active_stacks:
|
||||||
|
material_containers = [stack.material for stack in active_stacks]
|
||||||
|
|
||||||
criteria = kwargs
|
criteria = kwargs
|
||||||
filter_by_material = False
|
filter_by_material = False
|
||||||
@ -218,25 +243,23 @@ class QualityManager:
|
|||||||
criteria["definition"] = "fdmprinter"
|
criteria["definition"] = "fdmprinter"
|
||||||
|
|
||||||
# Stick the material IDs in a set
|
# Stick the material IDs in a set
|
||||||
if material_containers is None or len(material_containers) == 0:
|
|
||||||
filter_by_material = False
|
|
||||||
else:
|
|
||||||
material_ids = set()
|
material_ids = set()
|
||||||
|
|
||||||
for material_instance in material_containers:
|
for material_instance in material_containers:
|
||||||
if material_instance is not None:
|
if material_instance is not None:
|
||||||
# Add the parent material too.
|
# Add the parent material too.
|
||||||
for basic_material in self._getBasicMaterials(material_instance):
|
for basic_material in self._getBasicMaterials(material_instance):
|
||||||
material_ids.add(basic_material.getId())
|
material_ids.add(basic_material.getId())
|
||||||
material_ids.add(material_instance.getId())
|
material_ids.add(material_instance.getId())
|
||||||
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for container in containers:
|
for container in containers:
|
||||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
||||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and not "global_quality" in kwargs:
|
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs:
|
||||||
continue
|
continue
|
||||||
result.append(container)
|
result.append(container)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Get the parent machine definition of a machine definition.
|
## Get the parent machine definition of a machine definition.
|
||||||
@ -245,7 +268,7 @@ class QualityManager:
|
|||||||
# an extruder definition.
|
# an extruder definition.
|
||||||
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
|
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
|
||||||
# definition doesn't have a parent then it is simply returned.
|
# definition doesn't have a parent then it is simply returned.
|
||||||
def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer:
|
def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
machine_entry = machine_definition.getMetaDataEntry("machine")
|
||||||
@ -274,8 +297,8 @@ class QualityManager:
|
|||||||
#
|
#
|
||||||
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
||||||
# an extruder definition.
|
# an extruder definition.
|
||||||
# \return \type{DefinitionContainer}
|
# \return \type{DefinitionContainerInterface}
|
||||||
def getWholeMachineDefinition(self, machine_definition):
|
def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
|
||||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
machine_entry = machine_definition.getMetaDataEntry("machine")
|
||||||
if machine_entry is None:
|
if machine_entry is None:
|
||||||
# This already is a 'global' machine definition.
|
# This already is a 'global' machine definition.
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import urllib
|
import urllib
|
||||||
|
import uuid
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl, QVariant
|
from PyQt5.QtCore import QObject, QUrl, QVariant
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.SaveFile import SaveFile
|
from UM.SaveFile import SaveFile
|
||||||
@ -216,23 +218,85 @@ class ContainerManager(QObject):
|
|||||||
entries = entry_name.split("/")
|
entries = entry_name.split("/")
|
||||||
entry_name = entries.pop()
|
entry_name = entries.pop()
|
||||||
|
|
||||||
|
sub_item_changed = False
|
||||||
if entries:
|
if entries:
|
||||||
root_name = entries.pop(0)
|
root_name = entries.pop(0)
|
||||||
root = container.getMetaDataEntry(root_name)
|
root = container.getMetaDataEntry(root_name)
|
||||||
|
|
||||||
item = root
|
item = root
|
||||||
for entry in entries:
|
for _ in range(len(entries)):
|
||||||
item = item.get(entries.pop(0), { })
|
item = item.get(entries.pop(0), { })
|
||||||
|
|
||||||
|
if item[entry_name] != entry_value:
|
||||||
|
sub_item_changed = True
|
||||||
item[entry_name] = entry_value
|
item[entry_name] = entry_value
|
||||||
|
|
||||||
entry_name = root_name
|
entry_name = root_name
|
||||||
entry_value = root
|
entry_value = root
|
||||||
|
|
||||||
container.setMetaDataEntry(entry_name, entry_value)
|
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
|
return True
|
||||||
|
|
||||||
|
## 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
|
||||||
|
# happens for material containers.
|
||||||
|
#
|
||||||
|
# \param container_id \type{str} The ID of the container to change.
|
||||||
|
# \param setting_key \type{str} The key of the setting.
|
||||||
|
# \param property_name \type{str} The name of the property, eg "value".
|
||||||
|
# \param property_value \type{str} The new value of the property.
|
||||||
|
#
|
||||||
|
# \return True if successful, False if not.
|
||||||
|
@pyqtSlot(str, str, str, str, result = bool)
|
||||||
|
def setContainerProperty(self, container_id, setting_key, property_name, property_value):
|
||||||
|
containers = self._container_registry.findContainers(None, id = container_id)
|
||||||
|
if not containers:
|
||||||
|
Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
container = containers[0]
|
||||||
|
|
||||||
|
if container.isReadOnly():
|
||||||
|
Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
container.setProperty(setting_key, property_name, property_value)
|
||||||
|
|
||||||
|
basefile = container.getMetaDataEntry("base_file", container_id)
|
||||||
|
for sibbling_container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
|
||||||
|
if sibbling_container != container:
|
||||||
|
sibbling_container.setProperty(setting_key, property_name, property_value)
|
||||||
|
|
||||||
|
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.
|
## Set the name of the specified container.
|
||||||
@pyqtSlot(str, str, result = bool)
|
@pyqtSlot(str, str, result = bool)
|
||||||
def setContainerName(self, container_id, new_name):
|
def setContainerName(self, container_id, new_name):
|
||||||
@ -525,7 +589,7 @@ class ContainerManager(QObject):
|
|||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if not global_stack or not quality_name:
|
if not global_stack or not quality_name:
|
||||||
return ""
|
return ""
|
||||||
machine_definition = global_stack.getBottom()
|
machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
|
||||||
|
|
||||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
|
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
|
||||||
containers_found = True
|
containers_found = True
|
||||||
@ -671,6 +735,9 @@ class ContainerManager(QObject):
|
|||||||
|
|
||||||
return new_change_instances
|
return new_change_instances
|
||||||
|
|
||||||
|
## Create a duplicate of a material, which has the same GUID and base_file metadata
|
||||||
|
#
|
||||||
|
# \return \type{str} the id of the newly created container.
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def duplicateMaterial(self, material_id: str) -> str:
|
def duplicateMaterial(self, material_id: str) -> str:
|
||||||
containers = self._container_registry.findInstanceContainers(id=material_id)
|
containers = self._container_registry.findInstanceContainers(id=material_id)
|
||||||
@ -693,6 +760,117 @@ class ContainerManager(QObject):
|
|||||||
duplicated_container.deserialize(f.read())
|
duplicated_container.deserialize(f.read())
|
||||||
duplicated_container.setDirty(True)
|
duplicated_container.setDirty(True)
|
||||||
self._container_registry.addContainer(duplicated_container)
|
self._container_registry.addContainer(duplicated_container)
|
||||||
|
return self._getMaterialContainerIdForActiveMachine(new_id)
|
||||||
|
|
||||||
|
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
|
||||||
|
#
|
||||||
|
# \return \type{str} the id of the newly created container.
|
||||||
|
@pyqtSlot(result = str)
|
||||||
|
def createMaterial(self) -> str:
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
Application.getInstance().saveSettings()
|
||||||
|
|
||||||
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
|
||||||
|
containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter)
|
||||||
|
if not containers:
|
||||||
|
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
base_file = containers[0].getMetaDataEntry("base_file")
|
||||||
|
containers = self._container_registry.findInstanceContainers(id = base_file)
|
||||||
|
if not containers:
|
||||||
|
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Create a new ID & container to hold the data.
|
||||||
|
new_id = self._container_registry.uniqueName("custom_material")
|
||||||
|
container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file
|
||||||
|
duplicated_container = container_type(new_id)
|
||||||
|
|
||||||
|
# Instead of duplicating we load the data from the basefile again.
|
||||||
|
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
|
||||||
|
# are also correctly created.
|
||||||
|
with open(containers[0].getPath(), encoding="utf-8") as f:
|
||||||
|
duplicated_container.deserialize(f.read())
|
||||||
|
|
||||||
|
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
|
||||||
|
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
|
||||||
|
# We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
|
||||||
|
# TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
|
||||||
|
duplicated_container.setMetaDataEntry("material", "PLA")
|
||||||
|
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
|
||||||
|
|
||||||
|
self._container_registry.addContainer(duplicated_container)
|
||||||
|
return self._getMaterialContainerIdForActiveMachine(new_id)
|
||||||
|
|
||||||
|
## Find the id of a material container based on the new material
|
||||||
|
# Utilty function that is shared between duplicateMaterial and createMaterial
|
||||||
|
#
|
||||||
|
# \param base_file \type{str} the id of the created container.
|
||||||
|
def _getMaterialContainerIdForActiveMachine(self, base_file):
|
||||||
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
return base_file
|
||||||
|
|
||||||
|
has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
|
||||||
|
has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
|
||||||
|
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
|
||||||
|
if has_machine_materials or has_variant_materials:
|
||||||
|
if has_variants:
|
||||||
|
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
|
||||||
|
else:
|
||||||
|
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
|
||||||
|
|
||||||
|
if materials:
|
||||||
|
return materials[0].getId()
|
||||||
|
|
||||||
|
Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file)
|
||||||
|
return "" # do not activate a new material if a container can not be found
|
||||||
|
|
||||||
|
return base_file
|
||||||
|
|
||||||
|
## Get a list of materials that have the same GUID as the reference material
|
||||||
|
#
|
||||||
|
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
||||||
|
# \return \type{list} a list of names of materials with the same GUID
|
||||||
|
@pyqtSlot(str, result = "QStringList")
|
||||||
|
def getLinkedMaterials(self, material_id: str):
|
||||||
|
containers = self._container_registry.findInstanceContainers(id=material_id)
|
||||||
|
if not containers:
|
||||||
|
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
|
||||||
|
return []
|
||||||
|
|
||||||
|
material_container = containers[0]
|
||||||
|
material_base_file = material_container.getMetaDataEntry("base_file", "")
|
||||||
|
material_guid = material_container.getMetaDataEntry("GUID", "")
|
||||||
|
if not material_guid:
|
||||||
|
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
|
||||||
|
return []
|
||||||
|
|
||||||
|
containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid)
|
||||||
|
linked_material_names = []
|
||||||
|
for container in containers:
|
||||||
|
if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId():
|
||||||
|
continue
|
||||||
|
|
||||||
|
linked_material_names.append(container.getName())
|
||||||
|
return linked_material_names
|
||||||
|
|
||||||
|
## Unlink a material from all other materials by creating a new GUID
|
||||||
|
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def unlinkMaterial(self, material_id: str):
|
||||||
|
containers = self._container_registry.findInstanceContainers(id=material_id)
|
||||||
|
if not containers:
|
||||||
|
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
|
||||||
|
|
||||||
|
|
||||||
## Get the singleton instance for this class.
|
## Get the singleton instance for this class.
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -815,6 +993,9 @@ class ContainerManager(QObject):
|
|||||||
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
|
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
|
||||||
else:
|
else:
|
||||||
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
|
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
return quality_changes
|
return quality_changes
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
@ -22,6 +25,8 @@ from . import GlobalStack
|
|||||||
from .ContainerManager import ContainerManager
|
from .ContainerManager import ContainerManager
|
||||||
from .ExtruderManager import ExtruderManager
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
@ -41,6 +46,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
if type(container) == ContainerStack:
|
if type(container) == ContainerStack:
|
||||||
container = self._convertContainerStack(container)
|
container = self._convertContainerStack(container)
|
||||||
|
|
||||||
|
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
||||||
|
#Check against setting version of the definition.
|
||||||
|
required_setting_version = CuraApplication.SettingVersion
|
||||||
|
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
||||||
|
if required_setting_version != actual_setting_version:
|
||||||
|
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
|
||||||
|
return #Don't add.
|
||||||
|
|
||||||
super().addContainer(container)
|
super().addContainer(container)
|
||||||
|
|
||||||
## Create a name that is not empty and unique
|
## Create a name that is not empty and unique
|
||||||
@ -189,8 +202,12 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
new_name = self.uniqueName(name_seed)
|
new_name = self.uniqueName(name_seed)
|
||||||
if type(profile_or_list) is not list:
|
if type(profile_or_list) is not list:
|
||||||
profile = profile_or_list
|
profile = profile_or_list
|
||||||
self._configureProfile(profile, name_seed, new_name)
|
|
||||||
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
|
result = self._configureProfile(profile, name_seed, new_name)
|
||||||
|
if result is not None:
|
||||||
|
return {"status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result)}
|
||||||
|
|
||||||
|
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName())}
|
||||||
else:
|
else:
|
||||||
profile_index = -1
|
profile_index = -1
|
||||||
global_profile = None
|
global_profile = None
|
||||||
@ -220,7 +237,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
global_profile = profile
|
global_profile = profile
|
||||||
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
|
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
|
||||||
|
|
||||||
self._configureProfile(profile, profile_id, new_name)
|
result = self._configureProfile(profile, profile_id, new_name)
|
||||||
|
if result is not None:
|
||||||
|
return {"status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result)}
|
||||||
|
|
||||||
profile_index += 1
|
profile_index += 1
|
||||||
|
|
||||||
@ -234,7 +253,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
super().load()
|
super().load()
|
||||||
self._fixupExtruders()
|
self._fixupExtruders()
|
||||||
|
|
||||||
def _configureProfile(self, profile, id_seed, new_name):
|
## Update an imported profile to match the current machine configuration.
|
||||||
|
#
|
||||||
|
# \param profile The profile to configure.
|
||||||
|
# \param id_seed The base ID for the profile. May be changed so it does not conflict with existing containers.
|
||||||
|
# \param new_name The new name for the profile.
|
||||||
|
#
|
||||||
|
# \return None if configuring was successful or an error message if an error occurred.
|
||||||
|
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
|
||||||
profile.setReadOnly(False)
|
profile.setReadOnly(False)
|
||||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||||
|
|
||||||
@ -247,15 +273,36 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
else:
|
else:
|
||||||
profile.addMetaDataEntry("type", "quality_changes")
|
profile.addMetaDataEntry("type", "quality_changes")
|
||||||
|
|
||||||
|
quality_type = profile.getMetaDataEntry("quality_type")
|
||||||
|
if not quality_type:
|
||||||
|
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||||
|
|
||||||
|
quality_type_criteria = {"quality_type": quality_type}
|
||||||
if self._machineHasOwnQualities():
|
if self._machineHasOwnQualities():
|
||||||
profile.setDefinition(self._activeQualityDefinition())
|
profile.setDefinition(self._activeQualityDefinition())
|
||||||
if self._machineHasOwnMaterials():
|
if self._machineHasOwnMaterials():
|
||||||
profile.addMetaDataEntry("material", self._activeMaterialId())
|
active_material_id = self._activeMaterialId()
|
||||||
|
if active_material_id: # only update if there is an active material
|
||||||
|
profile.addMetaDataEntry("material", active_material_id)
|
||||||
|
quality_type_criteria["material"] = active_material_id
|
||||||
|
|
||||||
|
quality_type_criteria["definition"] = profile.getDefinition().getId()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
|
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
|
||||||
|
quality_type_criteria["definition"] = "fdmprinter"
|
||||||
|
|
||||||
|
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
||||||
|
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
||||||
|
# successfully imported but then fail to show up.
|
||||||
|
qualities = self.findInstanceContainers(**quality_type_criteria)
|
||||||
|
if not qualities:
|
||||||
|
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
||||||
|
|
||||||
ContainerRegistry.getInstance().addContainer(profile)
|
ContainerRegistry.getInstance().addContainer(profile)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
## Gets a list of profile writer plugins
|
## Gets a list of profile writer plugins
|
||||||
# \return List of tuples of (plugin_id, meta_data).
|
# \return List of tuples of (plugin_id, meta_data).
|
||||||
def _getIOPlugins(self, io_type):
|
def _getIOPlugins(self, io_type):
|
||||||
|
@ -5,7 +5,8 @@ import os.path
|
|||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
||||||
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
@ -65,8 +66,8 @@ class CuraContainerStack(ContainerStack):
|
|||||||
## Set the quality changes container.
|
## Set the quality changes container.
|
||||||
#
|
#
|
||||||
# \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
# \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
def setQualityChanges(self, new_quality_changes: InstanceContainer) -> None:
|
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes)
|
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the quality changes container by an ID.
|
## Set the quality changes container by an ID.
|
||||||
#
|
#
|
||||||
@ -92,8 +93,8 @@ class CuraContainerStack(ContainerStack):
|
|||||||
## Set the quality container.
|
## Set the quality container.
|
||||||
#
|
#
|
||||||
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||||
def setQuality(self, new_quality: InstanceContainer) -> None:
|
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Quality, new_quality)
|
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the quality container by an ID.
|
## Set the quality container by an ID.
|
||||||
#
|
#
|
||||||
@ -130,8 +131,8 @@ class CuraContainerStack(ContainerStack):
|
|||||||
## Set the material container.
|
## Set the material container.
|
||||||
#
|
#
|
||||||
# \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
# \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
def setMaterial(self, new_material: InstanceContainer) -> None:
|
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Material, new_material)
|
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the material container by an ID.
|
## Set the material container by an ID.
|
||||||
#
|
#
|
||||||
@ -249,10 +250,18 @@ class CuraContainerStack(ContainerStack):
|
|||||||
## Get the definition container.
|
## Get the definition container.
|
||||||
#
|
#
|
||||||
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged)
|
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
|
||||||
def definition(self) -> DefinitionContainer:
|
def definition(self) -> DefinitionContainer:
|
||||||
return self._containers[_ContainerIndexes.Definition]
|
return self._containers[_ContainerIndexes.Definition]
|
||||||
|
|
||||||
|
@override(ContainerStack)
|
||||||
|
def getBottom(self) -> "DefinitionContainer":
|
||||||
|
return self.definition
|
||||||
|
|
||||||
|
@override(ContainerStack)
|
||||||
|
def getTop(self) -> "InstanceContainer":
|
||||||
|
return self.userChanges
|
||||||
|
|
||||||
## Check whether the specified setting has a 'user' value.
|
## Check whether the specified setting has a 'user' value.
|
||||||
#
|
#
|
||||||
# A user value here is defined as the setting having a value in either
|
# A user value here is defined as the setting having a value in either
|
||||||
|
@ -9,7 +9,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||||||
|
|
||||||
from .GlobalStack import GlobalStack
|
from .GlobalStack import GlobalStack
|
||||||
from .ExtruderStack import ExtruderStack
|
from .ExtruderStack import ExtruderStack
|
||||||
from .CuraContainerStack import CuraContainerStack
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@ -31,6 +30,11 @@ class CuraStackBuilder:
|
|||||||
|
|
||||||
machine_definition = definitions[0]
|
machine_definition = definitions[0]
|
||||||
name = registry.createUniqueName("machine", "", name, machine_definition.name)
|
name = registry.createUniqueName("machine", "", name, machine_definition.name)
|
||||||
|
# Make sure the new name does not collide with any definition or (quality) profile
|
||||||
|
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
|
||||||
|
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
|
||||||
|
if registry.findContainers(id = name):
|
||||||
|
name = registry.uniqueName(name)
|
||||||
|
|
||||||
new_global_stack = cls.createGlobalStack(
|
new_global_stack = cls.createGlobalStack(
|
||||||
new_stack_id = name,
|
new_stack_id = name,
|
||||||
@ -76,6 +80,8 @@ class CuraStackBuilder:
|
|||||||
user_container = InstanceContainer(new_stack_id + "_user")
|
user_container = InstanceContainer(new_stack_id + "_user")
|
||||||
user_container.addMetaDataEntry("type", "user")
|
user_container.addMetaDataEntry("type", "user")
|
||||||
user_container.addMetaDataEntry("extruder", new_stack_id)
|
user_container.addMetaDataEntry("extruder", new_stack_id)
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
user_container.setDefinition(machine_definition)
|
user_container.setDefinition(machine_definition)
|
||||||
|
|
||||||
stack.setUserChanges(user_container)
|
stack.setUserChanges(user_container)
|
||||||
@ -124,6 +130,8 @@ class CuraStackBuilder:
|
|||||||
user_container = InstanceContainer(new_stack_id + "_user")
|
user_container = InstanceContainer(new_stack_id + "_user")
|
||||||
user_container.addMetaDataEntry("type", "user")
|
user_container.addMetaDataEntry("type", "user")
|
||||||
user_container.addMetaDataEntry("machine", new_stack_id)
|
user_container.addMetaDataEntry("machine", new_stack_id)
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
user_container.setDefinition(definition)
|
user_container.setDefinition(definition)
|
||||||
|
|
||||||
stack.setUserChanges(user_container)
|
stack.setUserChanges(user_container)
|
||||||
|
@ -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.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
|
||||||
@ -15,8 +15,13 @@ from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers
|
|||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||||
from typing import Optional, List
|
from typing import Optional, List, TYPE_CHECKING, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
|
||||||
## Manages all existing extruder stacks.
|
## Manages all existing extruder stacks.
|
||||||
#
|
#
|
||||||
@ -35,7 +40,7 @@ class ExtruderManager(QObject):
|
|||||||
## Registers listeners and such to listen to changes to the extruders.
|
## Registers listeners and such to listen to changes to the extruders.
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
|
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||||
self._active_extruder_index = 0
|
self._active_extruder_index = 0
|
||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = []
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
||||||
@ -69,11 +74,13 @@ class ExtruderManager(QObject):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@pyqtProperty("QVariantMap", notify=extrudersChanged)
|
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||||
def extruderIds(self):
|
def extruderIds(self):
|
||||||
map = {}
|
map = {}
|
||||||
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
|
global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
|
||||||
map[position] = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][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
|
return map
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
@ -81,7 +88,7 @@ class ExtruderManager(QObject):
|
|||||||
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
|
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
|
||||||
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
|
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
|
||||||
if extruder.getId() == id:
|
if extruder.getId() == id:
|
||||||
return extruder.findContainer(type = "quality_changes").getId()
|
return extruder.qualityChanges.getId()
|
||||||
|
|
||||||
## The instance of the singleton pattern.
|
## The instance of the singleton pattern.
|
||||||
#
|
#
|
||||||
@ -145,13 +152,14 @@ class ExtruderManager(QObject):
|
|||||||
selected_nodes.append(node)
|
selected_nodes.append(node)
|
||||||
|
|
||||||
# Then, figure out which nodes are used by those selected nodes.
|
# Then, figure out which nodes are used by those selected nodes.
|
||||||
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
||||||
for node in selected_nodes:
|
for node in selected_nodes:
|
||||||
extruder = node.callDecoration("getActiveExtruder")
|
extruder = node.callDecoration("getActiveExtruder")
|
||||||
if extruder:
|
if extruder:
|
||||||
object_extruders.add(extruder)
|
object_extruders.add(extruder)
|
||||||
else:
|
elif current_extruder_trains:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
object_extruders.add(current_extruder_trains["0"].getId())
|
||||||
object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId())
|
|
||||||
|
|
||||||
self._selected_object_extruders = list(object_extruders)
|
self._selected_object_extruders = list(object_extruders)
|
||||||
|
|
||||||
@ -165,7 +173,7 @@ class ExtruderManager(QObject):
|
|||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = []
|
||||||
self.selectedObjectExtrudersChanged.emit()
|
self.selectedObjectExtrudersChanged.emit()
|
||||||
|
|
||||||
def getActiveExtruderStack(self) -> ContainerStack:
|
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
@ -175,7 +183,7 @@ class ExtruderManager(QObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
## Get an extruder stack by index
|
## Get an extruder stack by index
|
||||||
def getExtruderStack(self, index):
|
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
if global_container_stack.getId() in self._extruder_trains:
|
if global_container_stack.getId() in self._extruder_trains:
|
||||||
@ -184,7 +192,7 @@ class ExtruderManager(QObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
## Get all extruder stacks
|
## Get all extruder stacks
|
||||||
def getExtruderStacks(self):
|
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
||||||
result = []
|
result = []
|
||||||
for i in range(self.extruderCount):
|
for i in range(self.extruderCount):
|
||||||
result.append(self.getExtruderStack(i))
|
result.append(self.getExtruderStack(i))
|
||||||
@ -196,7 +204,7 @@ class ExtruderManager(QObject):
|
|||||||
# \param machine_definition The machine definition to add the extruders for.
|
# \param machine_definition The machine definition to add the extruders for.
|
||||||
# \param machine_id The machine_id to add the extruders for.
|
# \param machine_id The machine_id to add the extruders for.
|
||||||
@deprecated("Use CuraStackBuilder", "2.6")
|
@deprecated("Use CuraStackBuilder", "2.6")
|
||||||
def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None:
|
def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None:
|
||||||
changed = False
|
changed = False
|
||||||
machine_definition_id = machine_definition.getId()
|
machine_definition_id = machine_definition.getId()
|
||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
@ -230,6 +238,13 @@ class ExtruderManager(QObject):
|
|||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
self._extruder_trains[machine_id] = {}
|
self._extruder_trains[machine_id] = {}
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
# do not register if an extruder has already been registered at the position on this machine
|
||||||
|
if any(item.getId() == extruder_train.getId() for item in self._extruder_trains[machine_id].values()):
|
||||||
|
Logger.log("w", "Extruder [%s] has already been registered on machine [%s], not doing anything",
|
||||||
|
extruder_train.getId(), machine_id)
|
||||||
|
return
|
||||||
|
|
||||||
if extruder_train:
|
if extruder_train:
|
||||||
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||||
changed = True
|
changed = True
|
||||||
@ -249,7 +264,7 @@ class ExtruderManager(QObject):
|
|||||||
# \param position The position of this extruder train in the extruder slots of the machine.
|
# \param position The position of this extruder train in the extruder slots of the machine.
|
||||||
# \param machine_id The id of the "global" stack this extruder is linked to.
|
# \param machine_id The id of the "global" stack this extruder is linked to.
|
||||||
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
|
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
|
||||||
def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer,
|
def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface,
|
||||||
position, machine_id: str) -> None:
|
position, machine_id: str) -> None:
|
||||||
# Cache some things.
|
# Cache some things.
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
@ -296,9 +311,9 @@ class ExtruderManager(QObject):
|
|||||||
if preferred_material_id:
|
if preferred_material_id:
|
||||||
global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||||
if global_stack:
|
if global_stack:
|
||||||
approximate_material_diameter = round(global_stack[0].getProperty("material_diameter", "value"))
|
approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value")))
|
||||||
else:
|
else:
|
||||||
approximate_material_diameter = round(machine_definition.getProperty("material_diameter", "value"))
|
approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value")))
|
||||||
|
|
||||||
search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter}
|
search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter}
|
||||||
if machine_definition.getMetaDataEntry("has_machine_materials"):
|
if machine_definition.getMetaDataEntry("has_machine_materials"):
|
||||||
@ -357,6 +372,8 @@ class ExtruderManager(QObject):
|
|||||||
user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
|
user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
|
||||||
user_profile.addMetaDataEntry("type", "user")
|
user_profile.addMetaDataEntry("type", "user")
|
||||||
user_profile.addMetaDataEntry("extruder", extruder_stack_id)
|
user_profile.addMetaDataEntry("extruder", extruder_stack_id)
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
user_profile.setDefinition(machine_definition)
|
user_profile.setDefinition(machine_definition)
|
||||||
container_registry.addContainer(user_profile)
|
container_registry.addContainer(user_profile)
|
||||||
container_stack.addContainer(user_profile)
|
container_stack.addContainer(user_profile)
|
||||||
@ -396,7 +413,7 @@ class ExtruderManager(QObject):
|
|||||||
# list.
|
# list.
|
||||||
#
|
#
|
||||||
# \return A list of extruder stacks.
|
# \return A list of extruder stacks.
|
||||||
def getUsedExtruderStacks(self):
|
def getUsedExtruderStacks(self) -> List["ContainerStack"]:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
@ -450,17 +467,17 @@ class ExtruderManager(QObject):
|
|||||||
## Removes the container stack and user profile for the extruders for a specific machine.
|
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||||
#
|
#
|
||||||
# \param machine_id The machine to remove the extruders for.
|
# \param machine_id The machine to remove the extruders for.
|
||||||
def removeMachineExtruders(self, machine_id):
|
def removeMachineExtruders(self, machine_id: str):
|
||||||
for extruder in self.getMachineExtruders(machine_id):
|
for extruder in self.getMachineExtruders(machine_id):
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
|
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
||||||
for container in containers:
|
|
||||||
ContainerRegistry.getInstance().removeContainer(container.getId())
|
|
||||||
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
||||||
|
if machine_id in self._extruder_trains:
|
||||||
|
del self._extruder_trains[machine_id]
|
||||||
|
|
||||||
## Returns extruders for a specific machine.
|
## Returns extruders for a specific machine.
|
||||||
#
|
#
|
||||||
# \param machine_id The machine to get the extruders of.
|
# \param machine_id The machine to get the extruders of.
|
||||||
def getMachineExtruders(self, machine_id):
|
def getMachineExtruders(self, machine_id: str):
|
||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
return []
|
return []
|
||||||
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
||||||
@ -469,7 +486,7 @@ class ExtruderManager(QObject):
|
|||||||
#
|
#
|
||||||
# The first element is the global container stack, followed by any extruder stacks.
|
# The first element is the global container stack, followed by any extruder stacks.
|
||||||
# \return \type{List[ContainerStack]}
|
# \return \type{List[ContainerStack]}
|
||||||
def getActiveGlobalAndExtruderStacks(self):
|
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return None
|
return None
|
||||||
@ -481,7 +498,7 @@ class ExtruderManager(QObject):
|
|||||||
## Returns the list of active extruder stacks.
|
## Returns the list of active extruder stacks.
|
||||||
#
|
#
|
||||||
# \return \type{List[ContainerStack]} a list of
|
# \return \type{List[ContainerStack]} a list of
|
||||||
def getActiveExtruderStacks(self):
|
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@ -509,7 +526,7 @@ class ExtruderManager(QObject):
|
|||||||
#
|
#
|
||||||
# This is exposed to SettingFunction so it can be used in value functions.
|
# This is exposed to SettingFunction so it can be used in value functions.
|
||||||
#
|
#
|
||||||
# \param key The key of the setting to retieve values for.
|
# \param key The key of the setting to retrieve values for.
|
||||||
#
|
#
|
||||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||||
# If no extruder has the value, the list will contain the global value.
|
# If no extruder has the value, the list will contain the global value.
|
||||||
@ -519,6 +536,10 @@ class ExtruderManager(QObject):
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||||
|
# only include values from extruders that are "active" for the current machine instance
|
||||||
|
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
|
||||||
|
continue
|
||||||
|
|
||||||
value = extruder.getRawProperty(key, "value")
|
value = extruder.getRawProperty(key, "value")
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
|
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
from .CuraContainerStack import CuraContainerStack
|
from .CuraContainerStack import CuraContainerStack
|
||||||
from .ExtruderManager import ExtruderManager
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
## Represents an Extruder and its related containers.
|
## Represents an Extruder and its related containers.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
@ -38,6 +37,10 @@ class ExtruderStack(CuraContainerStack):
|
|||||||
# For backward compatibility: Register the extruder with the Extruder Manager
|
# For backward compatibility: Register the extruder with the Extruder Manager
|
||||||
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
||||||
|
|
||||||
|
@override(ContainerStack)
|
||||||
|
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||||
|
return super().getNextStack()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getLoadingPriority(cls) -> int:
|
def getLoadingPriority(cls) -> int:
|
||||||
return 3
|
return 3
|
||||||
@ -59,6 +62,13 @@ class ExtruderStack(CuraContainerStack):
|
|||||||
if not super().getProperty(key, "settable_per_extruder"):
|
if not super().getProperty(key, "settable_per_extruder"):
|
||||||
return self.getNextStack().getProperty(key, property_name)
|
return self.getNextStack().getProperty(key, property_name)
|
||||||
|
|
||||||
|
limit_to_extruder = super().getProperty(key, "limit_to_extruder")
|
||||||
|
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
|
||||||
|
if str(limit_to_extruder) in self.getNextStack().extruders:
|
||||||
|
result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
|
||||||
return super().getProperty(key, property_name)
|
return super().getProperty(key, property_name)
|
||||||
|
|
||||||
@override(CuraContainerStack)
|
@override(CuraContainerStack)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, pyqtSlot
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
import UM.Qt.ListModel
|
import UM.Qt.ListModel
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
import UM.FlameProfiler
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack #To listen to changes on the extruders.
|
||||||
|
from cura.Settings.MachineManager import MachineManager #To listen to changes on the extruders of the currently active machine.
|
||||||
|
|
||||||
## Model that holds extruders.
|
## Model that holds extruders.
|
||||||
#
|
#
|
||||||
@ -58,19 +61,21 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
self.addRoleName(self.MaterialRole, "material")
|
self.addRoleName(self.MaterialRole, "material")
|
||||||
self.addRoleName(self.VariantRole, "variant")
|
self.addRoleName(self.VariantRole, "variant")
|
||||||
|
|
||||||
|
self._update_extruder_timer = QTimer()
|
||||||
|
self._update_extruder_timer.setInterval(100)
|
||||||
|
self._update_extruder_timer.setSingleShot(True)
|
||||||
|
self._update_extruder_timer.timeout.connect(self.__updateExtruders)
|
||||||
|
|
||||||
self._add_global = False
|
self._add_global = False
|
||||||
self._simple_names = False
|
self._simple_names = False
|
||||||
|
|
||||||
self._active_extruder_stack = None
|
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
|
||||||
|
self._add_optional_extruder = False
|
||||||
|
|
||||||
#Listen to changes.
|
#Listen to changes.
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders)
|
Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders.
|
||||||
manager = ExtruderManager.getInstance()
|
ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder.
|
||||||
|
self._extrudersChanged() #Also calls _updateExtruders.
|
||||||
self._updateExtruders()
|
|
||||||
|
|
||||||
manager.activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
|
||||||
self._onActiveExtruderChanged()
|
|
||||||
|
|
||||||
def setAddGlobal(self, add):
|
def setAddGlobal(self, add):
|
||||||
if add != self._add_global:
|
if add != self._add_global:
|
||||||
@ -84,6 +89,18 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
def addGlobal(self):
|
def addGlobal(self):
|
||||||
return self._add_global
|
return self._add_global
|
||||||
|
|
||||||
|
addOptionalExtruderChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def setAddOptionalExtruder(self, add_optional_extruder):
|
||||||
|
if add_optional_extruder != self._add_optional_extruder:
|
||||||
|
self._add_optional_extruder = add_optional_extruder
|
||||||
|
self.addOptionalExtruderChanged.emit()
|
||||||
|
self._updateExtruders()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, fset = setAddOptionalExtruder, notify = addOptionalExtruderChanged)
|
||||||
|
def addOptionalExtruder(self):
|
||||||
|
return self._add_optional_extruder
|
||||||
|
|
||||||
## Set the simpleNames property.
|
## Set the simpleNames property.
|
||||||
def setSimpleNames(self, simple_names):
|
def setSimpleNames(self, simple_names):
|
||||||
if simple_names != self._simple_names:
|
if simple_names != self._simple_names:
|
||||||
@ -99,40 +116,59 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
def simpleNames(self):
|
def simpleNames(self):
|
||||||
return self._simple_names
|
return self._simple_names
|
||||||
|
|
||||||
def _onActiveExtruderChanged(self):
|
## Links to the stack-changed signal of the new extruders when an extruder
|
||||||
manager = ExtruderManager.getInstance()
|
# is swapped out or added in the current machine.
|
||||||
active_extruder_stack = manager.getActiveExtruderStack()
|
#
|
||||||
if self._active_extruder_stack != active_extruder_stack:
|
# \param machine_id The machine for which the extruders changed. This is
|
||||||
if self._active_extruder_stack:
|
# filled by the ExtruderManager.extrudersChanged signal when coming from
|
||||||
self._active_extruder_stack.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
# that signal. Application.globalContainerStackChanged doesn't fill this
|
||||||
|
# signal; it's assumed to be the current printer in that case.
|
||||||
|
def _extrudersChanged(self, machine_id = None):
|
||||||
|
if machine_id is not None:
|
||||||
|
if Application.getInstance().getGlobalContainerStack() is None:
|
||||||
|
return #No machine, don't need to update the current machine's extruders.
|
||||||
|
if machine_id != Application.getInstance().getGlobalContainerStack().getId():
|
||||||
|
return #Not the current machine.
|
||||||
|
#Unlink from old extruders.
|
||||||
|
for extruder in self._active_machine_extruders:
|
||||||
|
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||||
|
|
||||||
if active_extruder_stack:
|
#Link to new extruders.
|
||||||
# Update the model when the material container is changed
|
self._active_machine_extruders = []
|
||||||
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
|
extruder_manager = ExtruderManager.getInstance()
|
||||||
self._active_extruder_stack = active_extruder_stack
|
for extruder in extruder_manager.getExtruderStacks():
|
||||||
|
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||||
|
self._active_machine_extruders.append(extruder)
|
||||||
|
|
||||||
|
self._updateExtruders() #Since the new extruders may have different properties, update our own model.
|
||||||
|
|
||||||
def _onExtruderStackContainersChanged(self, container):
|
def _onExtruderStackContainersChanged(self, container):
|
||||||
|
# Update when there is an empty container or material change
|
||||||
|
if container.getMetaDataEntry("type") == "material" or container.getMetaDataEntry("type") is None:
|
||||||
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
|
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
|
||||||
self._updateExtruders()
|
self._updateExtruders()
|
||||||
|
|
||||||
|
|
||||||
modelChanged = pyqtSignal()
|
modelChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def _updateExtruders(self):
|
||||||
|
self._update_extruder_timer.start()
|
||||||
|
|
||||||
## Update the list of extruders.
|
## Update the list of extruders.
|
||||||
#
|
#
|
||||||
# This should be called whenever the list of extruders changes.
|
# This should be called whenever the list of extruders changes.
|
||||||
def _updateExtruders(self):
|
@UM.FlameProfiler.profile
|
||||||
|
def __updateExtruders(self):
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
if self.rowCount() != 0:
|
if self.rowCount() != 0:
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
if self._add_global:
|
if self._add_global:
|
||||||
material = global_container_stack.findContainer({ "type": "material" })
|
material = global_container_stack.material
|
||||||
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
|
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
|
||||||
item = {
|
item = {
|
||||||
"id": global_container_stack.getId(),
|
"id": global_container_stack.getId(),
|
||||||
@ -147,9 +183,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
manager = ExtruderManager.getInstance()
|
manager = ExtruderManager.getInstance()
|
||||||
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
||||||
extruder_name = extruder.getName()
|
|
||||||
material = extruder.findContainer({ "type": "material" })
|
|
||||||
variant = extruder.findContainer({"type": "variant"})
|
|
||||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||||
try:
|
try:
|
||||||
position = int(position)
|
position = int(position)
|
||||||
@ -157,6 +190,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
position = -1
|
position = -1
|
||||||
if position >= machine_extruder_count:
|
if position >= machine_extruder_count:
|
||||||
continue
|
continue
|
||||||
|
extruder_name = extruder.getName()
|
||||||
|
material = extruder.material
|
||||||
|
variant = extruder.variant
|
||||||
|
|
||||||
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0]
|
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0]
|
||||||
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
|
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
|
||||||
@ -174,5 +210,16 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
items.sort(key = lambda i: i["index"])
|
items.sort(key = lambda i: i["index"])
|
||||||
|
# We need optional extruder to be last, so add it after we do sorting.
|
||||||
|
# This way we can simply intrepret the -1 of the index as the last item (which it now always is)
|
||||||
|
if self._add_optional_extruder:
|
||||||
|
item = {
|
||||||
|
"id": "",
|
||||||
|
"name": "Not overridden",
|
||||||
|
"color": "#ffffff",
|
||||||
|
"index": -1,
|
||||||
|
"definition": ""
|
||||||
|
}
|
||||||
|
items.append(item)
|
||||||
self.setItems(items)
|
self.setItems(items)
|
||||||
self.modelChanged.emit()
|
self.modelChanged.emit()
|
||||||
|
66
cura/Settings/GlobalStack.py
Normal file → Executable file
66
cura/Settings/GlobalStack.py
Normal file → Executable file
@ -1,19 +1,17 @@
|
|||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
|
||||||
from UM.Settings.SettingInstance import InstanceState
|
from UM.Settings.SettingInstance import InstanceState
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Logger import Logger
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
from .CuraContainerStack import CuraContainerStack
|
from .CuraContainerStack import CuraContainerStack
|
||||||
@ -26,7 +24,7 @@ class GlobalStack(CuraContainerStack):
|
|||||||
|
|
||||||
self.addMetaDataEntry("type", "machine") # For backward compatibility
|
self.addMetaDataEntry("type", "machine") # For backward compatibility
|
||||||
|
|
||||||
self._extruders = []
|
self._extruders = {}
|
||||||
|
|
||||||
# This property is used to track which settings we are calculating the "resolve" for
|
# This property is used to track which settings we are calculating the "resolve" for
|
||||||
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
||||||
@ -36,14 +34,25 @@ class GlobalStack(CuraContainerStack):
|
|||||||
## Get the list of extruders of this stack.
|
## Get the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
# \return The extruders registered with this stack.
|
# \return The extruders registered with this stack.
|
||||||
@pyqtProperty("QVariantList")
|
@pyqtProperty("QVariantMap")
|
||||||
def extruders(self) -> list:
|
def extruders(self) -> Dict[str, "ExtruderStack"]:
|
||||||
return self._extruders
|
return self._extruders
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getLoadingPriority(cls) -> int:
|
def getLoadingPriority(cls) -> int:
|
||||||
return 2
|
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.
|
## Add an extruder to the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
# \param extruder The extruder to add.
|
# \param extruder The extruder to add.
|
||||||
@ -53,9 +62,18 @@ class GlobalStack(CuraContainerStack):
|
|||||||
def addExtruder(self, extruder: ContainerStack) -> None:
|
def addExtruder(self, extruder: ContainerStack) -> None:
|
||||||
extruder_count = self.getProperty("machine_extruder_count", "value")
|
extruder_count = self.getProperty("machine_extruder_count", "value")
|
||||||
if extruder_count and len(self._extruders) + 1 > extruder_count:
|
if extruder_count and len(self._extruders) + 1 > extruder_count:
|
||||||
raise Exceptions.TooManyExtrudersError("Tried to add extruder to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count))
|
Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData())))
|
||||||
|
return
|
||||||
|
|
||||||
self._extruders.append(extruder)
|
position = extruder.getMetaDataEntry("position")
|
||||||
|
if position is None:
|
||||||
|
Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if any(item.getId() == extruder.id for item in self._extruders.values()):
|
||||||
|
Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self._id)
|
||||||
|
return
|
||||||
|
self._extruders[position] = extruder
|
||||||
|
|
||||||
## Overridden from ContainerStack
|
## Overridden from ContainerStack
|
||||||
#
|
#
|
||||||
@ -73,6 +91,7 @@ class GlobalStack(CuraContainerStack):
|
|||||||
if not self.definition.findDefinitions(key = key):
|
if not self.definition.findDefinitions(key = key):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Handle the "resolve" property.
|
||||||
if self._shouldResolve(key, property_name):
|
if self._shouldResolve(key, property_name):
|
||||||
self._resolving_settings.add(key)
|
self._resolving_settings.add(key)
|
||||||
resolve = super().getProperty(key, "resolve")
|
resolve = super().getProperty(key, "resolve")
|
||||||
@ -80,6 +99,16 @@ class GlobalStack(CuraContainerStack):
|
|||||||
if resolve is not None:
|
if resolve is not None:
|
||||||
return resolve
|
return resolve
|
||||||
|
|
||||||
|
# Handle the "limit_to_extruder" property.
|
||||||
|
limit_to_extruder = super().getProperty(key, "limit_to_extruder")
|
||||||
|
if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
|
||||||
|
if super().getProperty(key, "settable_per_extruder"):
|
||||||
|
result = self._extruders[str(limit_to_extruder)].getProperty(key, property_name)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Setting {setting} has limit_to_extruder but is not settable per extruder!", setting = key)
|
||||||
|
|
||||||
return super().getProperty(key, property_name)
|
return super().getProperty(key, property_name)
|
||||||
|
|
||||||
## Overridden from ContainerStack
|
## Overridden from ContainerStack
|
||||||
@ -89,6 +118,21 @@ class GlobalStack(CuraContainerStack):
|
|||||||
def setNextStack(self, next_stack: ContainerStack) -> None:
|
def setNextStack(self, next_stack: ContainerStack) -> None:
|
||||||
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
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:
|
# protected:
|
||||||
|
|
||||||
# Determine whether or not we should try to get the "resolve" property instead of the
|
# Determine whether or not we should try to get the "resolve" property instead of the
|
||||||
|
@ -16,16 +16,14 @@ from UM.Decorators import deprecated
|
|||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.SettingDefinition import SettingDefinition
|
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Signal import postponeSignals, CompressTechnique
|
||||||
from UM.Signal import postponeSignals
|
import UM.FlameProfiler
|
||||||
|
|
||||||
from cura.QualityManager import QualityManager
|
from cura.QualityManager import QualityManager
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from .GlobalStack import GlobalStack
|
|
||||||
from .CuraStackBuilder import CuraStackBuilder
|
from .CuraStackBuilder import CuraStackBuilder
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
@ -35,15 +33,28 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class MachineManager(QObject):
|
class MachineManager(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._active_container_stack = None # type: ContainerStack
|
self._active_container_stack = None # type: CuraContainerStack
|
||||||
self._global_container_stack = None # type: ContainerStack
|
self._global_container_stack = None # type: GlobalStack
|
||||||
|
|
||||||
|
self._error_check_timer = QTimer()
|
||||||
|
self._error_check_timer.setInterval(250)
|
||||||
|
self._error_check_timer.setSingleShot(True)
|
||||||
|
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
|
||||||
|
|
||||||
|
self._instance_container_timer = QTimer()
|
||||||
|
self._instance_container_timer.setInterval(250)
|
||||||
|
self._instance_container_timer.setSingleShot(True)
|
||||||
|
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged)
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||||
## When the global container is changed, active material probably needs to be updated.
|
## When the global container is changed, active material probably needs to be updated.
|
||||||
@ -81,7 +92,7 @@ class MachineManager(QObject):
|
|||||||
self._printer_output_devices = []
|
self._printer_output_devices = []
|
||||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(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.
|
# An active machine was saved, so restore it.
|
||||||
self.setActiveMachine(active_machine_id)
|
self.setActiveMachine(active_machine_id)
|
||||||
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
@ -94,11 +105,6 @@ class MachineManager(QObject):
|
|||||||
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
||||||
"The selected material is incompatible with the selected machine or configuration."))
|
"The selected material is incompatible with the selected machine or configuration."))
|
||||||
|
|
||||||
self._error_check_timer = QTimer()
|
|
||||||
self._error_check_timer.setInterval(250)
|
|
||||||
self._error_check_timer.setSingleShot(True)
|
|
||||||
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
|
|
||||||
|
|
||||||
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
|
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
|
||||||
activeMaterialChanged = pyqtSignal()
|
activeMaterialChanged = pyqtSignal()
|
||||||
activeVariantChanged = pyqtSignal()
|
activeVariantChanged = pyqtSignal()
|
||||||
@ -134,7 +140,7 @@ class MachineManager(QObject):
|
|||||||
return self._printer_output_devices
|
return self._printer_output_devices
|
||||||
|
|
||||||
@pyqtProperty(int, constant=True)
|
@pyqtProperty(int, constant=True)
|
||||||
def totalNumberOfSettings(self):
|
def totalNumberOfSettings(self) -> int:
|
||||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||||
|
|
||||||
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
||||||
@ -158,7 +164,7 @@ class MachineManager(QObject):
|
|||||||
else:
|
else:
|
||||||
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
|
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
|
||||||
|
|
||||||
def _onMaterialIdChanged(self, index, material_id):
|
def _onMaterialIdChanged(self, index: Union[str, int], material_id: str):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -215,6 +221,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
if old_index is not None:
|
if old_index is not None:
|
||||||
extruder_manager.setActiveExtruderIndex(old_index)
|
extruder_manager.setActiveExtruderIndex(old_index)
|
||||||
|
self._auto_materials_changed = {} #Processed all of them now.
|
||||||
|
|
||||||
def _autoUpdateHotends(self):
|
def _autoUpdateHotends(self):
|
||||||
extruder_manager = ExtruderManager.getInstance()
|
extruder_manager = ExtruderManager.getInstance()
|
||||||
@ -231,6 +238,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
if old_index is not None:
|
if old_index is not None:
|
||||||
extruder_manager.setActiveExtruderIndex(old_index)
|
extruder_manager.setActiveExtruderIndex(old_index)
|
||||||
|
self._auto_hotends_changed = {} #Processed all of them now.
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self):
|
def _onGlobalContainerChanged(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
@ -290,8 +298,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
quality = self._global_container_stack.quality
|
quality = self._global_container_stack.quality
|
||||||
quality.nameChanged.connect(self._onQualityNameChanged)
|
quality.nameChanged.connect(self._onQualityNameChanged)
|
||||||
|
self._error_check_timer.start()
|
||||||
self._updateStacksHaveErrors()
|
|
||||||
|
|
||||||
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
||||||
def _updateStacksHaveErrors(self):
|
def _updateStacksHaveErrors(self):
|
||||||
@ -308,23 +315,23 @@ class MachineManager(QObject):
|
|||||||
if not self._active_container_stack:
|
if not self._active_container_stack:
|
||||||
self._active_container_stack = self._global_container_stack
|
self._active_container_stack = self._global_container_stack
|
||||||
|
|
||||||
self._updateStacksHaveErrors()
|
self._error_check_timer.start()
|
||||||
|
|
||||||
if old_active_container_stack != self._active_container_stack:
|
if old_active_container_stack != self._active_container_stack:
|
||||||
# Many methods and properties related to the active quality actually depend
|
# Many methods and properties related to the active quality actually depend
|
||||||
# on _active_container_stack. If it changes, then the properties change.
|
# on _active_container_stack. If it changes, then the properties change.
|
||||||
self.activeQualityChanged.emit()
|
self.activeQualityChanged.emit()
|
||||||
|
|
||||||
def _onInstanceContainersChanged(self, container):
|
def __onInstanceContainersChanged(self):
|
||||||
container_type = container.getMetaDataEntry("type")
|
self.activeQualityChanged.emit()
|
||||||
|
|
||||||
self.activeVariantChanged.emit()
|
self.activeVariantChanged.emit()
|
||||||
self.activeMaterialChanged.emit()
|
self.activeMaterialChanged.emit()
|
||||||
self.activeQualityChanged.emit()
|
self._error_check_timer.start()
|
||||||
|
|
||||||
self._updateStacksHaveErrors()
|
def _onInstanceContainersChanged(self, container):
|
||||||
|
self._instance_container_timer.start()
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key: str, property_name: str):
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||||
self.activeStackValueChanged.emit()
|
self.activeStackValueChanged.emit()
|
||||||
@ -408,7 +415,7 @@ class MachineManager(QObject):
|
|||||||
## Delete a user setting from the global stack and all extruder stacks.
|
## Delete a user setting from the global stack and all extruder stacks.
|
||||||
# \param key \type{str} the name of the key to delete
|
# \param key \type{str} the name of the key to delete
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def clearUserSettingAllCurrentStacks(self, key):
|
def clearUserSettingAllCurrentStacks(self, key: str):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -464,8 +471,8 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty("QObject", notify = globalContainerChanged)
|
@pyqtProperty(QObject, notify = globalContainerChanged)
|
||||||
def activeMachine(self) -> GlobalStack:
|
def activeMachine(self) -> "GlobalStack":
|
||||||
return self._global_container_stack
|
return self._global_container_stack
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeStackChanged)
|
@pyqtProperty(str, notify = activeStackChanged)
|
||||||
@ -525,6 +532,22 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantMap", notify = activeVariantChanged)
|
||||||
|
def allActiveVariantIds(self):
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
|
variant_container = stack.variant
|
||||||
|
if not variant_container:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result[stack.getId()] = variant_container.getId()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
||||||
def allActiveMaterialIds(self):
|
def allActiveMaterialIds(self):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
@ -548,7 +571,7 @@ class MachineManager(QObject):
|
|||||||
# \return The layer height of the currently active quality profile. If
|
# \return The layer height of the currently active quality profile. If
|
||||||
# there is no quality profile, this returns 0.
|
# there is no quality profile, this returns 0.
|
||||||
@pyqtProperty(float, notify=activeQualityChanged)
|
@pyqtProperty(float, notify=activeQualityChanged)
|
||||||
def activeQualityLayerHeight(self):
|
def activeQualityLayerHeight(self) -> float:
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -565,7 +588,7 @@ class MachineManager(QObject):
|
|||||||
value = value(self._global_container_stack)
|
value = value(self._global_container_stack)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return 0 #No quality profile.
|
return 0 # No quality profile.
|
||||||
|
|
||||||
## Get the Material ID associated with the currently active material
|
## Get the Material ID associated with the currently active material
|
||||||
# \returns MaterialID (string) if found, empty string otherwise
|
# \returns MaterialID (string) if found, empty string otherwise
|
||||||
@ -587,7 +610,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def activeQualityName(self):
|
def activeQualityName(self) -> str:
|
||||||
if self._active_container_stack and self._global_container_stack:
|
if self._active_container_stack and self._global_container_stack:
|
||||||
quality = self._global_container_stack.qualityChanges
|
quality = self._global_container_stack.qualityChanges
|
||||||
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
@ -598,7 +621,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def activeQualityId(self):
|
def activeQualityId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.qualityChanges
|
quality = self._active_container_stack.qualityChanges
|
||||||
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
@ -609,7 +632,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def globalQualityId(self):
|
def globalQualityId(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
quality = self._global_container_stack.qualityChanges
|
quality = self._global_container_stack.qualityChanges
|
||||||
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
@ -620,7 +643,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeQualityChanged)
|
@pyqtProperty(str, notify = activeQualityChanged)
|
||||||
def activeQualityType(self):
|
def activeQualityType(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.quality
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
@ -628,7 +651,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = activeQualityChanged)
|
@pyqtProperty(bool, notify = activeQualityChanged)
|
||||||
def isActiveQualitySupported(self):
|
def isActiveQualitySupported(self) -> bool:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.quality
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
@ -642,7 +665,7 @@ class MachineManager(QObject):
|
|||||||
# \todo Ideally, this method would be named activeQualityId(), and the other one
|
# \todo Ideally, this method would be named activeQualityId(), and the other one
|
||||||
# would be named something like activeQualityOrQualityChanges() for consistency
|
# would be named something like activeQualityOrQualityChanges() for consistency
|
||||||
@pyqtProperty(str, notify = activeQualityChanged)
|
@pyqtProperty(str, notify = activeQualityChanged)
|
||||||
def activeQualityContainerId(self):
|
def activeQualityContainerId(self) -> str:
|
||||||
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
|
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
quality = self._active_container_stack.quality
|
quality = self._active_container_stack.quality
|
||||||
@ -651,7 +674,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeQualityChanged)
|
@pyqtProperty(str, notify = activeQualityChanged)
|
||||||
def activeQualityChangesId(self):
|
def activeQualityChangesId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
changes = self._active_container_stack.qualityChanges
|
changes = self._active_container_stack.qualityChanges
|
||||||
if changes and changes.getId() != "empty":
|
if changes and changes.getId() != "empty":
|
||||||
@ -660,7 +683,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
## Check if a container is read_only
|
## Check if a container is read_only
|
||||||
@pyqtSlot(str, result = bool)
|
@pyqtSlot(str, result = bool)
|
||||||
def isReadOnly(self, container_id) -> bool:
|
def isReadOnly(self, container_id: str) -> bool:
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
return True
|
return True
|
||||||
@ -668,7 +691,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def copyValueToExtruders(self, key):
|
def copyValueToExtruders(self, key: str):
|
||||||
if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
|
if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -682,8 +705,8 @@ class MachineManager(QObject):
|
|||||||
## Set the active material by switching out a container
|
## Set the active material by switching out a container
|
||||||
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveMaterial(self, material_id):
|
def setActiveMaterial(self, material_id: str):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
return
|
return
|
||||||
@ -698,7 +721,7 @@ class MachineManager(QObject):
|
|||||||
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if old_quality_changes and old_quality_changes.getId() == "empty_quality_changes":
|
if old_quality_changes and isinstance(old_quality_changes, type(self._empty_quality_changes_container)):
|
||||||
old_quality_changes = None
|
old_quality_changes = None
|
||||||
|
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
@ -731,11 +754,12 @@ class MachineManager(QObject):
|
|||||||
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
|
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
|
||||||
quality_manager.getWholeMachineDefinition(machine_definition),
|
quality_manager.getWholeMachineDefinition(machine_definition),
|
||||||
[material_container])
|
[material_container])
|
||||||
if not candidate_quality or candidate_quality.getId() == "empty_quality":
|
|
||||||
|
if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)):
|
||||||
|
Logger.log("d", "Attempting to find fallback quality")
|
||||||
# Fall back to a quality (which must be compatible with all other extruders)
|
# Fall back to a quality (which must be compatible with all other extruders)
|
||||||
new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
|
new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
|
||||||
self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
|
self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
|
||||||
|
|
||||||
if new_qualities:
|
if new_qualities:
|
||||||
new_quality_id = new_qualities[0].getId() # Just pick the first available one
|
new_quality_id = new_qualities[0].getId() # Just pick the first available one
|
||||||
else:
|
else:
|
||||||
@ -747,8 +771,8 @@ class MachineManager(QObject):
|
|||||||
self.setActiveQuality(new_quality_id)
|
self.setActiveQuality(new_quality_id)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveVariant(self, variant_id):
|
def setActiveVariant(self, variant_id: str):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
return
|
return
|
||||||
@ -757,10 +781,9 @@ class MachineManager(QObject):
|
|||||||
old_material = self._active_container_stack.material
|
old_material = self._active_container_stack.material
|
||||||
if old_variant:
|
if old_variant:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
variant_index = self._active_container_stack.getContainerIndex(old_variant)
|
self._active_container_stack.variant = containers[0]
|
||||||
self._active_container_stack.replaceContainer(variant_index, containers[0])
|
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
|
||||||
Logger.log("d", "Active variant changed")
|
preferred_material_name = None
|
||||||
preferred_material = None
|
|
||||||
if old_material:
|
if old_material:
|
||||||
preferred_material_name = old_material.getName()
|
preferred_material_name = old_material.getName()
|
||||||
|
|
||||||
@ -771,8 +794,8 @@ class MachineManager(QObject):
|
|||||||
## set the active quality
|
## set the active quality
|
||||||
# \param quality_id The quality_id of either a quality or a quality_changes
|
# \param quality_id The quality_id of either a quality or a quality_changes
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setActiveQuality(self, quality_id):
|
def setActiveQuality(self, quality_id: str):
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
|
||||||
@ -808,8 +831,8 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
name_changed_connect_stacks.append(stack_quality)
|
name_changed_connect_stacks.append(stack_quality)
|
||||||
name_changed_connect_stacks.append(stack_quality_changes)
|
name_changed_connect_stacks.append(stack_quality_changes)
|
||||||
self._replaceQualityOrQualityChangesInStack(stack, stack_quality)
|
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True)
|
||||||
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes)
|
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True)
|
||||||
|
|
||||||
# Send emits that are postponed in replaceContainer.
|
# Send emits that are postponed in replaceContainer.
|
||||||
# Here the stacks are finished replacing and every value can be resolved based on the current state.
|
# Here the stacks are finished replacing and every value can be resolved based on the current state.
|
||||||
@ -829,7 +852,8 @@ class MachineManager(QObject):
|
|||||||
#
|
#
|
||||||
# \param quality_name \type{str} the name of the quality.
|
# \param quality_name \type{str} the name of the quality.
|
||||||
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
||||||
def determineQualityAndQualityChangesForQualityType(self, quality_type):
|
@UM.FlameProfiler.profile
|
||||||
|
def determineQualityAndQualityChangesForQualityType(self, quality_type: str):
|
||||||
quality_manager = QualityManager.getInstance()
|
quality_manager = QualityManager.getInstance()
|
||||||
result = []
|
result = []
|
||||||
empty_quality_changes = self._empty_quality_changes_container
|
empty_quality_changes = self._empty_quality_changes_container
|
||||||
@ -866,7 +890,7 @@ class MachineManager(QObject):
|
|||||||
#
|
#
|
||||||
# \param quality_changes_name \type{str} the name of the quality changes.
|
# \param quality_changes_name \type{str} the name of the quality changes.
|
||||||
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
|
||||||
def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name):
|
def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name: str):
|
||||||
result = []
|
result = []
|
||||||
quality_manager = QualityManager.getInstance()
|
quality_manager = QualityManager.getInstance()
|
||||||
|
|
||||||
@ -922,18 +946,18 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
|
def _replaceQualityOrQualityChangesInStack(self, stack: "CuraContainerStack", container: "InstanceContainer", postpone_emit = False):
|
||||||
# Disconnect the signal handling from the old container.
|
# Disconnect the signal handling from the old container.
|
||||||
container_type = container.getMetaDataEntry("type")
|
container_type = container.getMetaDataEntry("type")
|
||||||
if container_type == "quality":
|
if container_type == "quality":
|
||||||
stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
|
stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
stack.setQuality(container)
|
stack.setQuality(container, postpone_emit = postpone_emit)
|
||||||
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
||||||
elif container_type == "quality_changes" or container_type is None:
|
elif container_type == "quality_changes" or container_type is None:
|
||||||
# If the container is an empty container, we need to change the quality_changes.
|
# If the container is an empty container, we need to change the quality_changes.
|
||||||
# Quality can never be set to empty.
|
# Quality can never be set to empty.
|
||||||
stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
|
stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
stack.setQualityChanges(container)
|
stack.setQualityChanges(container, postpone_emit = postpone_emit)
|
||||||
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
||||||
self._onQualityNameChanged()
|
self._onQualityNameChanged()
|
||||||
|
|
||||||
@ -941,7 +965,7 @@ class MachineManager(QObject):
|
|||||||
Application.getInstance().discardOrKeepProfileChanges()
|
Application.getInstance().discardOrKeepProfileChanges()
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeVariantName(self):
|
def activeVariantName(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.variant
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
@ -950,7 +974,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeVariantId(self):
|
def activeVariantId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.variant
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
@ -959,7 +983,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
def activeDefinitionId(self):
|
def activeDefinitionId(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
definition = self._global_container_stack.getBottom()
|
definition = self._global_container_stack.getBottom()
|
||||||
if definition:
|
if definition:
|
||||||
@ -968,7 +992,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify=globalContainerChanged)
|
@pyqtProperty(str, notify=globalContainerChanged)
|
||||||
def activeDefinitionName(self):
|
def activeDefinitionName(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
definition = self._global_container_stack.getBottom()
|
definition = self._global_container_stack.getBottom()
|
||||||
if definition:
|
if definition:
|
||||||
@ -980,7 +1004,7 @@ class MachineManager(QObject):
|
|||||||
# \returns DefinitionID (string) if found, empty string otherwise
|
# \returns DefinitionID (string) if found, empty string otherwise
|
||||||
# \sa getQualityDefinitionId
|
# \sa getQualityDefinitionId
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
def activeQualityDefinitionId(self):
|
def activeQualityDefinitionId(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self.getQualityDefinitionId(self._global_container_stack.getBottom())
|
return self.getQualityDefinitionId(self._global_container_stack.getBottom())
|
||||||
return ""
|
return ""
|
||||||
@ -989,14 +1013,14 @@ class MachineManager(QObject):
|
|||||||
# This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from
|
# This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from
|
||||||
# \param definition (DefinitionContainer) machine definition
|
# \param definition (DefinitionContainer) machine definition
|
||||||
# \returns DefinitionID (string) if found, empty string otherwise
|
# \returns DefinitionID (string) if found, empty string otherwise
|
||||||
def getQualityDefinitionId(self, definition):
|
def getQualityDefinitionId(self, definition: "DefinitionContainer") -> str:
|
||||||
return QualityManager.getInstance().getParentMachineDefinition(definition).getId()
|
return QualityManager.getInstance().getParentMachineDefinition(definition).getId()
|
||||||
|
|
||||||
## Get the Variant ID to use to select quality profiles for the currently active variant
|
## Get the Variant ID to use to select quality profiles for the currently active variant
|
||||||
# \returns VariantID (string) if found, empty string otherwise
|
# \returns VariantID (string) if found, empty string otherwise
|
||||||
# \sa getQualityVariantId
|
# \sa getQualityVariantId
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeQualityVariantId(self):
|
def activeQualityVariantId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.variant
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
@ -1007,9 +1031,9 @@ class MachineManager(QObject):
|
|||||||
# This is normally the id of the variant itself, but machines can specify a different definition
|
# This is normally the id of the variant itself, but machines can specify a different definition
|
||||||
# to inherit qualities from, which has consequences for the variant to use as well
|
# to inherit qualities from, which has consequences for the variant to use as well
|
||||||
# \param definition (DefinitionContainer) machine definition
|
# \param definition (DefinitionContainer) machine definition
|
||||||
# \param variant (DefinitionContainer) variant definition
|
# \param variant (InstanceContainer) variant definition
|
||||||
# \returns VariantID (string) if found, empty string otherwise
|
# \returns VariantID (string) if found, empty string otherwise
|
||||||
def getQualityVariantId(self, definition, variant):
|
def getQualityVariantId(self, definition: "DefinitionContainer", variant: "InstanceContainer") -> str:
|
||||||
variant_id = variant.getId()
|
variant_id = variant.getId()
|
||||||
definition_id = definition.getId()
|
definition_id = definition.getId()
|
||||||
quality_definition_id = self.getQualityDefinitionId(definition)
|
quality_definition_id = self.getQualityDefinitionId(definition)
|
||||||
@ -1021,7 +1045,7 @@ class MachineManager(QObject):
|
|||||||
## Gets how the active definition calls variants
|
## Gets how the active definition calls variants
|
||||||
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
def activeDefinitionVariantsName(self):
|
def activeDefinitionVariantsName(self) -> str:
|
||||||
fallback_title = catalog.i18nc("@label", "Nozzle")
|
fallback_title = catalog.i18nc("@label", "Nozzle")
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title)
|
return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title)
|
||||||
@ -1029,7 +1053,7 @@ class MachineManager(QObject):
|
|||||||
return fallback_title
|
return fallback_title
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def renameMachine(self, machine_id, new_name):
|
def renameMachine(self, machine_id: str, new_name: str):
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||||
if containers:
|
if containers:
|
||||||
new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName())
|
new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName())
|
||||||
@ -1037,32 +1061,32 @@ class MachineManager(QObject):
|
|||||||
self.globalContainerChanged.emit()
|
self.globalContainerChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def removeMachine(self, machine_id):
|
def removeMachine(self, machine_id: str):
|
||||||
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
|
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
|
||||||
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
|
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
|
||||||
|
|
||||||
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
# activate a new machine before removing a machine because this is safer
|
||||||
|
if activate_new_machine:
|
||||||
|
machine_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||||
|
other_machine_stacks = [s for s in machine_stacks if s.getId() != machine_id]
|
||||||
|
if other_machine_stacks:
|
||||||
|
Application.getInstance().setGlobalContainerStack(other_machine_stacks[0])
|
||||||
|
|
||||||
|
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id)
|
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id)
|
||||||
for container in containers:
|
for container in containers:
|
||||||
ContainerRegistry.getInstance().removeContainer(container.getId())
|
ContainerRegistry.getInstance().removeContainer(container.getId())
|
||||||
ContainerRegistry.getInstance().removeContainer(machine_id)
|
ContainerRegistry.getInstance().removeContainer(machine_id)
|
||||||
|
|
||||||
if activate_new_machine:
|
|
||||||
stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
|
||||||
if stacks:
|
|
||||||
Application.getInstance().setGlobalContainerStack(stacks[0])
|
|
||||||
|
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
def hasMaterials(self):
|
def hasMaterials(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
|
return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
def hasVariants(self):
|
def hasVariants(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
|
return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
|
||||||
|
|
||||||
@ -1071,7 +1095,7 @@ class MachineManager(QObject):
|
|||||||
## Property to indicate if a machine has "specialized" material profiles.
|
## Property to indicate if a machine has "specialized" material profiles.
|
||||||
# Some machines have their own material profiles that "override" the default catch all profiles.
|
# Some machines have their own material profiles that "override" the default catch all profiles.
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
def filterMaterialsByMachine(self):
|
def filterMaterialsByMachine(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
|
return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
|
||||||
|
|
||||||
@ -1080,7 +1104,7 @@ class MachineManager(QObject):
|
|||||||
## Property to indicate if a machine has "specialized" quality profiles.
|
## Property to indicate if a machine has "specialized" quality profiles.
|
||||||
# Some machines have their own quality profiles that "override" the default catch all profiles.
|
# Some machines have their own quality profiles that "override" the default catch all profiles.
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
def filterQualityByMachine(self):
|
def filterQualityByMachine(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
|
return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
|
||||||
return False
|
return False
|
||||||
@ -1089,7 +1113,7 @@ class MachineManager(QObject):
|
|||||||
# \param machine_id string machine id to get the definition ID of
|
# \param machine_id string machine id to get the definition ID of
|
||||||
# \returns DefinitionID (string) if found, None otherwise
|
# \returns DefinitionID (string) if found, None otherwise
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def getDefinitionByMachineId(self, machine_id):
|
def getDefinitionByMachineId(self, machine_id: str) -> str:
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id)
|
containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id)
|
||||||
if containers:
|
if containers:
|
||||||
return containers[0].getBottom().getId()
|
return containers[0].getBottom().getId()
|
||||||
@ -1098,27 +1122,12 @@ class MachineManager(QObject):
|
|||||||
def createMachineManager(engine=None, script_engine=None):
|
def createMachineManager(engine=None, script_engine=None):
|
||||||
return MachineManager()
|
return MachineManager()
|
||||||
|
|
||||||
def _updateVariantContainer(self, definition: "DefinitionContainer"):
|
@deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")
|
||||||
if not definition.getMetaDataEntry("has_variants"):
|
|
||||||
return self._empty_variant_container
|
|
||||||
machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(definition)
|
|
||||||
containers = []
|
|
||||||
preferred_variant = definition.getMetaDataEntry("preferred_variant")
|
|
||||||
if preferred_variant:
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant)
|
|
||||||
if not containers:
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id)
|
|
||||||
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
return self._empty_variant_container
|
|
||||||
|
|
||||||
def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None):
|
def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None):
|
||||||
if not definition.getMetaDataEntry("has_materials"):
|
if not definition.getMetaDataEntry("has_materials"):
|
||||||
return self._empty_material_container
|
return self._empty_material_container
|
||||||
|
|
||||||
approximate_material_diameter = round(stack.getProperty("material_diameter", "value"))
|
approximate_material_diameter = str(round(stack.getProperty("material_diameter", "value")))
|
||||||
search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter }
|
search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter }
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_machine_materials"):
|
if definition.getMetaDataEntry("has_machine_materials"):
|
||||||
@ -1151,110 +1160,6 @@ class MachineManager(QObject):
|
|||||||
Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
|
Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
|
||||||
return self._empty_material_container
|
return self._empty_material_container
|
||||||
|
|
||||||
def _updateQualityContainer(self, definition: "DefinitionContainer", variant_container: "ContainerStack", material_container = None, preferred_quality_name: Optional[str] = None):
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
|
||||||
search_criteria = { "type": "quality" }
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
search_criteria["definition"] = self.getQualityDefinitionId(definition)
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_materials") and material_container:
|
|
||||||
search_criteria["material"] = material_container.id
|
|
||||||
else:
|
|
||||||
search_criteria["definition"] = "fdmprinter"
|
|
||||||
|
|
||||||
if preferred_quality_name and preferred_quality_name != "empty":
|
|
||||||
search_criteria["name"] = preferred_quality_name
|
|
||||||
else:
|
|
||||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
|
||||||
if preferred_quality:
|
|
||||||
search_criteria["id"] = preferred_quality
|
|
||||||
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
if "material" in search_criteria:
|
|
||||||
# First check if we can solve our material not found problem by checking if we can find quality containers
|
|
||||||
# that are assigned to the parents of this material profile.
|
|
||||||
try:
|
|
||||||
inherited_files = material_container.getInheritedFiles()
|
|
||||||
except AttributeError: # Material_container does not support inheritance.
|
|
||||||
inherited_files = []
|
|
||||||
|
|
||||||
if inherited_files:
|
|
||||||
for inherited_file in inherited_files:
|
|
||||||
# Extract the ID from the path we used to load the file.
|
|
||||||
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
# We still weren't able to find a quality for this specific material.
|
|
||||||
# Try to find qualities for a generic version of the material.
|
|
||||||
material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
|
|
||||||
if definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
if material_container:
|
|
||||||
material_search_criteria["definition"] = material_container.getDefinition().id
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_variants"):
|
|
||||||
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
|
|
||||||
else:
|
|
||||||
material_search_criteria["definition"] = self.getQualityDefinitionId(definition)
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_variants") and variant_container:
|
|
||||||
material_search_criteria["variant"] = self.getQualityVariantId(definition, variant_container)
|
|
||||||
else:
|
|
||||||
material_search_criteria["definition"] = "fdmprinter"
|
|
||||||
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
|
|
||||||
# Try all materials to see if there is a quality profile available.
|
|
||||||
for material_container in material_containers:
|
|
||||||
search_criteria["material"] = material_container.getId()
|
|
||||||
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
if "name" in search_criteria or "id" in search_criteria:
|
|
||||||
# If a quality by this name can not be found, try a wider set of search criteria
|
|
||||||
search_criteria.pop("name", None)
|
|
||||||
search_criteria.pop("id", None)
|
|
||||||
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
# Notify user that we were unable to find a matching quality
|
|
||||||
message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination. Default settings will be used instead."))
|
|
||||||
message.show()
|
|
||||||
return self._empty_quality_container
|
|
||||||
|
|
||||||
## Finds a quality-changes container to use if any other container
|
|
||||||
# changes.
|
|
||||||
#
|
|
||||||
# \param quality_type The quality type to find a quality-changes for.
|
|
||||||
# \param preferred_quality_changes_name The name of the quality-changes to
|
|
||||||
# pick, if any such quality-changes profile is available.
|
|
||||||
def _updateQualityChangesContainer(self, quality_type, preferred_quality_changes_name = None):
|
|
||||||
container_registry = ContainerRegistry.getInstance() # Cache.
|
|
||||||
search_criteria = { "type": "quality_changes" }
|
|
||||||
|
|
||||||
search_criteria["quality"] = quality_type
|
|
||||||
if preferred_quality_changes_name:
|
|
||||||
search_criteria["name"] = preferred_quality_changes_name
|
|
||||||
|
|
||||||
# Try to search with the name in the criteria first, since we prefer to have the correct name.
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers: # Found one!
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
if "name" in search_criteria:
|
|
||||||
del search_criteria["name"] # Not found, then drop the name requirement (if we had one) and search again.
|
|
||||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
return self._empty_quality_changes_container # Didn't find anything with the required quality_type.
|
|
||||||
|
|
||||||
def _onMachineNameChanged(self):
|
def _onMachineNameChanged(self):
|
||||||
self.globalContainerChanged.emit()
|
self.globalContainerChanged.emit()
|
||||||
|
|
||||||
|
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()
|
@ -40,6 +40,6 @@ class QualityAndUserProfilesModel(ProfilesModel):
|
|||||||
|
|
||||||
# Filter the quality_change by the list of available quality_types
|
# Filter the quality_change by the list of available quality_types
|
||||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
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
|
return quality_list + filtered_quality_changes
|
||||||
|
@ -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.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||||
@ -35,7 +35,7 @@ class SettingInheritanceManager(QObject):
|
|||||||
## Get the keys of all children settings with an override.
|
## Get the keys of all children settings with an override.
|
||||||
@pyqtSlot(str, result = "QStringList")
|
@pyqtSlot(str, result = "QStringList")
|
||||||
def getChildrenKeysWithOverride(self, key):
|
def getChildrenKeysWithOverride(self, key):
|
||||||
definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
|
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
Logger.log("w", "Could not find definition for key [%s]", key)
|
Logger.log("w", "Could not find definition for key [%s]", key)
|
||||||
return []
|
return []
|
||||||
@ -55,7 +55,7 @@ class SettingInheritanceManager(QObject):
|
|||||||
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
|
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
Logger.log("w", "Could not find definition for key [%s] (2)", key)
|
Logger.log("w", "Could not find definition for key [%s] (2)", key)
|
||||||
return []
|
return []
|
||||||
@ -93,7 +93,7 @@ class SettingInheritanceManager(QObject):
|
|||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key, property_name):
|
||||||
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
||||||
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
definitions = self._global_container_stack.definition.findDefinitions(key = key)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -198,6 +198,10 @@ class SettingInheritanceManager(QObject):
|
|||||||
def _update(self):
|
def _update(self):
|
||||||
self._settings_with_inheritance_warning = [] # Reset previous data.
|
self._settings_with_inheritance_warning = [] # Reset previous data.
|
||||||
|
|
||||||
|
# Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late.
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
|
|
||||||
# Check all setting keys that we know of and see if they are overridden.
|
# Check all setting keys that we know of and see if they are overridden.
|
||||||
for setting_key in self._global_container_stack.getAllKeys():
|
for setting_key in self._global_container_stack.getAllKeys():
|
||||||
override = self._settingIsOverwritingInheritance(setting_key)
|
override = self._settingIsOverwritingInheritance(setting_key)
|
||||||
@ -205,7 +209,7 @@ class SettingInheritanceManager(QObject):
|
|||||||
self._settings_with_inheritance_warning.append(setting_key)
|
self._settings_with_inheritance_warning.append(setting_key)
|
||||||
|
|
||||||
# Check all the categories if any of their children have their inheritance overwritten.
|
# Check all the categories if any of their children have their inheritance overwritten.
|
||||||
for category in self._global_container_stack.getBottom().findDefinitions(type = "category"):
|
for category in self._global_container_stack.definition.findDefinitions(type = "category"):
|
||||||
if self._recursiveCheck(category):
|
if self._recursiveCheck(category):
|
||||||
self._settings_with_inheritance_warning.append(category.key)
|
self._settings_with_inheritance_warning.append(category.key)
|
||||||
|
|
||||||
|
@ -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.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
@ -33,4 +33,9 @@ class UserProfilesModel(ProfilesModel):
|
|||||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
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]
|
||||||
|
|
||||||
|
#Only display the global quality changes.
|
||||||
|
#Otherwise you get multiple copies of every quality changes profile.
|
||||||
|
#The actual profile switching goes by profile name (not ID), and as long as the names are consistent, switching to any of the profiles will cause all stacks to switch.
|
||||||
|
filtered_quality_changes = list(filter(lambda quality_changes: quality_changes.getMetaDataEntry("extruder") is None, filtered_quality_changes))
|
||||||
|
|
||||||
return filtered_quality_changes
|
return filtered_quality_changes
|
||||||
|
@ -3,6 +3,7 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
|||||||
## A decorator that stores the amount an object has been moved below the platform.
|
## A decorator that stores the amount an object has been moved below the platform.
|
||||||
class ZOffsetDecorator(SceneNodeDecorator):
|
class ZOffsetDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self._z_offset = 0
|
self._z_offset = 0
|
||||||
|
|
||||||
def setZOffset(self, offset):
|
def setZOffset(self, offset):
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
import faulthandler
|
||||||
|
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
|
|
||||||
@ -53,12 +54,14 @@ import Arcus #@UnusedImport
|
|||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
import cura.Settings.CuraContainerRegistry
|
import cura.Settings.CuraContainerRegistry
|
||||||
|
|
||||||
if Platform.isWindows() and hasattr(sys, "frozen"):
|
if hasattr(sys, "frozen"):
|
||||||
dirpath = os.path.expanduser("~/AppData/Local/cura/")
|
dirpath = os.path.expanduser("~/AppData/Local/cura/")
|
||||||
os.makedirs(dirpath, exist_ok = True)
|
os.makedirs(dirpath, exist_ok = True)
|
||||||
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
|
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
|
||||||
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
|
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
|
||||||
|
|
||||||
|
faulthandler.enable()
|
||||||
|
|
||||||
# Force an instance of CuraContainerRegistry to be created and reused later.
|
# Force an instance of CuraContainerRegistry to be created and reused later.
|
||||||
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
|
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
|
||||||
|
|
||||||
|
590
plugins/3MFReader/ThreeMFWorkspaceReader.py
Normal file → Executable file
590
plugins/3MFReader/ThreeMFWorkspaceReader.py
Normal file → Executable file
@ -1,3 +1,6 @@
|
|||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
@ -15,7 +18,10 @@ from .WorkspaceDialog import WorkspaceDialog
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
from configparser import ConfigParser
|
||||||
import zipfile
|
import zipfile
|
||||||
import io
|
import io
|
||||||
import configparser
|
import configparser
|
||||||
@ -31,10 +37,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
self._dialog = WorkspaceDialog()
|
self._dialog = WorkspaceDialog()
|
||||||
self._3mf_mesh_reader = None
|
self._3mf_mesh_reader = None
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
|
|
||||||
|
# suffixes registered with the MineTypes don't start with a dot '.'
|
||||||
|
self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
|
||||||
self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
|
self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
|
||||||
self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
|
self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
|
||||||
self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
|
self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
|
||||||
|
self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix
|
||||||
|
self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix
|
||||||
|
|
||||||
|
# Certain instance container types are ignored because we make the assumption that only we make those types
|
||||||
|
# of containers. They are:
|
||||||
|
# - quality
|
||||||
|
# - variant
|
||||||
|
self._ignored_instance_container_types = {"quality", "variant"}
|
||||||
|
|
||||||
self._resolve_strategies = {}
|
self._resolve_strategies = {}
|
||||||
|
|
||||||
@ -47,6 +63,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
||||||
return self._id_mapping[old_id]
|
return self._id_mapping[old_id]
|
||||||
|
|
||||||
|
## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files.
|
||||||
|
#
|
||||||
|
# In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg".
|
||||||
|
#
|
||||||
|
def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list):
|
||||||
|
archive = zipfile.ZipFile(project_file_name, "r")
|
||||||
|
|
||||||
|
global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)]
|
||||||
|
extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)]
|
||||||
|
|
||||||
|
# separate container stack files and extruder stack files
|
||||||
|
files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)]
|
||||||
|
for file_name in files_to_determine:
|
||||||
|
# FIXME: HACK!
|
||||||
|
# We need to know the type of the stack file, but we can only know it if we deserialize it.
|
||||||
|
# The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
|
||||||
|
# Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
|
||||||
|
serialized = archive.open(file_name).read().decode("utf-8")
|
||||||
|
stack_config = ConfigParser()
|
||||||
|
stack_config.read_string(serialized)
|
||||||
|
|
||||||
|
# sanity check
|
||||||
|
if not stack_config.has_option("metadata", "type"):
|
||||||
|
Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stack_type = stack_config.get("metadata", "type")
|
||||||
|
if stack_type == "extruder_train":
|
||||||
|
extruder_stack_file_list.append(file_name)
|
||||||
|
elif stack_type == "machine":
|
||||||
|
global_stack_file_list.append(file_name)
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Unknown container stack type '%s' from %s in %s",
|
||||||
|
stack_type, file_name, project_file_name)
|
||||||
|
|
||||||
|
if len(global_stack_file_list) != 1:
|
||||||
|
raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list))
|
||||||
|
|
||||||
|
return global_stack_file_list[0], extruder_stack_file_list
|
||||||
|
|
||||||
|
## read some info so we can make decisions
|
||||||
|
# \param file_name
|
||||||
|
# \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
|
||||||
def preRead(self, file_name, show_dialog=True, *args, **kwargs):
|
def preRead(self, file_name, show_dialog=True, *args, **kwargs):
|
||||||
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
|
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
|
||||||
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
|
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
|
||||||
@ -59,51 +118,52 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
machine_type = ""
|
machine_type = ""
|
||||||
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
|
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
|
||||||
|
|
||||||
num_extruders = 0
|
|
||||||
# Check if there are any conflicts, so we can ask the user.
|
# Check if there are any conflicts, so we can ask the user.
|
||||||
archive = zipfile.ZipFile(file_name, "r")
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
||||||
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
|
|
||||||
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
|
|
||||||
machine_conflict = False
|
|
||||||
quality_changes_conflict = False
|
|
||||||
for container_stack_file in container_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.
|
|
||||||
id_list = self._getContainerIdListFromSerialized(serialized)
|
|
||||||
for index, container_id in enumerate(id_list):
|
|
||||||
if stacks[0].getContainer(index).getId() != container_id:
|
|
||||||
machine_conflict = True
|
|
||||||
Job.yieldThread()
|
|
||||||
|
|
||||||
|
# A few lists of containers in this project files.
|
||||||
|
# When loading the global stack file, it may be associated with those containers, which may or may not be
|
||||||
|
# in Cura already, so we need to provide them as alternative search lists.
|
||||||
|
definition_container_list = []
|
||||||
|
instance_container_list = []
|
||||||
|
material_container_list = []
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read definition containers
|
||||||
|
#
|
||||||
|
machine_definition_container_count = 0
|
||||||
|
extruder_definition_container_count = 0
|
||||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||||
for definition_container_file in definition_container_files:
|
for each_definition_container_file in definition_container_files:
|
||||||
container_id = self._stripFileToId(definition_container_file)
|
container_id = self._stripFileToId(each_definition_container_file)
|
||||||
definitions = self._container_registry.findDefinitionContainers(id=container_id)
|
definitions = self._container_registry.findDefinitionContainers(id=container_id)
|
||||||
|
|
||||||
if not definitions:
|
if not definitions:
|
||||||
definition_container = DefinitionContainer(container_id)
|
definition_container = DefinitionContainer(container_id)
|
||||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
|
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
definition_container = definitions[0]
|
definition_container = definitions[0]
|
||||||
|
definition_container_list.append(definition_container)
|
||||||
|
|
||||||
if definition_container.getMetaDataEntry("type") != "extruder":
|
definition_container_type = definition_container.getMetaDataEntry("type")
|
||||||
|
if definition_container_type == "machine":
|
||||||
machine_type = definition_container.getName()
|
machine_type = definition_container.getName()
|
||||||
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
|
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
|
||||||
|
|
||||||
|
machine_definition_container_count += 1
|
||||||
|
elif definition_container_type == "extruder":
|
||||||
|
extruder_definition_container_count += 1
|
||||||
else:
|
else:
|
||||||
num_extruders += 1
|
Logger.log("w", "Unknown definition container type %s for %s",
|
||||||
|
definition_container_type, each_definition_container_file)
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
# sanity check
|
||||||
if num_extruders == 0:
|
if machine_definition_container_count != 1:
|
||||||
num_extruders = 1 # No extruder stacks found, which means there is one extruder
|
msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
|
||||||
|
Logger.log("e", msg)
|
||||||
extruders = num_extruders * [""]
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
material_labels = []
|
material_labels = []
|
||||||
material_conflict = False
|
material_conflict = False
|
||||||
@ -119,18 +179,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
|
if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
|
||||||
material_conflict = True
|
material_conflict = True
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
# Check if any quality_changes instance container is in conflict.
|
# Check if any quality_changes instance container is in conflict.
|
||||||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||||
quality_name = ""
|
quality_name = ""
|
||||||
quality_type = ""
|
quality_type = ""
|
||||||
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||||
|
num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
|
||||||
num_user_settings = 0
|
num_user_settings = 0
|
||||||
for instance_container_file in instance_container_files:
|
quality_changes_conflict = False
|
||||||
container_id = self._stripFileToId(instance_container_file)
|
definition_changes_conflict = False
|
||||||
|
|
||||||
|
for each_instance_container_file in instance_container_files:
|
||||||
|
container_id = self._stripFileToId(each_instance_container_file)
|
||||||
instance_container = InstanceContainer(container_id)
|
instance_container = InstanceContainer(container_id)
|
||||||
|
|
||||||
# Deserialize InstanceContainer by converting read data from bytes to string
|
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
|
||||||
|
instance_container_list.append(instance_container)
|
||||||
|
|
||||||
container_type = instance_container.getMetaDataEntry("type")
|
container_type = instance_container.getMetaDataEntry("type")
|
||||||
if container_type == "quality_changes":
|
if container_type == "quality_changes":
|
||||||
quality_name = instance_container.getName()
|
quality_name = instance_container.getName()
|
||||||
@ -141,16 +208,41 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
# Check if there really is a conflict by comparing the values
|
# Check if there really is a conflict by comparing the values
|
||||||
if quality_changes[0] != instance_container:
|
if quality_changes[0] != instance_container:
|
||||||
quality_changes_conflict = True
|
quality_changes_conflict = True
|
||||||
elif container_type == "quality":
|
elif container_type == "definition_changes":
|
||||||
# If the quality name is not set (either by quality or changes, set it now)
|
definition_name = instance_container.getName()
|
||||||
# Quality changes should always override this (as they are "on top")
|
num_settings_overriden_by_definition_changes += len(instance_container._instances)
|
||||||
if quality_name == "":
|
definition_changes = self._container_registry.findDefinitionContainers(id = container_id)
|
||||||
quality_name = instance_container.getName()
|
if definition_changes:
|
||||||
quality_type = instance_container.getName()
|
if definition_changes[0] != instance_container:
|
||||||
|
definition_changes_conflict = True
|
||||||
elif container_type == "user":
|
elif container_type == "user":
|
||||||
num_user_settings += len(instance_container._instances)
|
num_user_settings += len(instance_container._instances)
|
||||||
|
elif container_type in self._ignored_instance_container_types:
|
||||||
|
# Ignore certain instance container types
|
||||||
|
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
|
||||||
|
continue
|
||||||
|
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
id_list = self._getContainerIdListFromSerialized(serialized)
|
||||||
|
for index, container_id in enumerate(id_list):
|
||||||
|
if stacks[0].getContainer(index).getId() != container_id:
|
||||||
|
machine_conflict = True
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
num_visible_settings = 0
|
num_visible_settings = 0
|
||||||
try:
|
try:
|
||||||
temp_preferences = Preferences()
|
temp_preferences = Preferences()
|
||||||
@ -171,9 +263,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
if not show_dialog:
|
if not show_dialog:
|
||||||
return WorkspaceReader.PreReadResult.accepted
|
return WorkspaceReader.PreReadResult.accepted
|
||||||
|
|
||||||
|
# prepare data for the dialog
|
||||||
|
num_extruders = extruder_definition_container_count
|
||||||
|
if num_extruders == 0:
|
||||||
|
num_extruders = 1 # No extruder stacks found, which means there is one extruder
|
||||||
|
|
||||||
|
extruders = num_extruders * [""]
|
||||||
|
|
||||||
# Show the dialog, informing the user what is about to happen.
|
# Show the dialog, informing the user what is about to happen.
|
||||||
self._dialog.setMachineConflict(machine_conflict)
|
self._dialog.setMachineConflict(machine_conflict)
|
||||||
self._dialog.setQualityChangesConflict(quality_changes_conflict)
|
self._dialog.setQualityChangesConflict(quality_changes_conflict)
|
||||||
|
self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
|
||||||
self._dialog.setMaterialConflict(material_conflict)
|
self._dialog.setMaterialConflict(material_conflict)
|
||||||
self._dialog.setNumVisibleSettings(num_visible_settings)
|
self._dialog.setNumVisibleSettings(num_visible_settings)
|
||||||
self._dialog.setQualityName(quality_name)
|
self._dialog.setQualityName(quality_name)
|
||||||
@ -196,9 +296,47 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
return WorkspaceReader.PreReadResult.cancelled
|
return WorkspaceReader.PreReadResult.cancelled
|
||||||
|
|
||||||
self._resolve_strategies = self._dialog.getResult()
|
self._resolve_strategies = self._dialog.getResult()
|
||||||
|
#
|
||||||
|
# There can be 3 resolve strategies coming from the dialog:
|
||||||
|
# - 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.
|
||||||
|
#
|
||||||
|
# Default values
|
||||||
|
for k, v in self._resolve_strategies.items():
|
||||||
|
if v is None:
|
||||||
|
self._resolve_strategies[k] = "new"
|
||||||
|
|
||||||
return WorkspaceReader.PreReadResult.accepted
|
return WorkspaceReader.PreReadResult.accepted
|
||||||
|
|
||||||
|
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
|
||||||
|
def _overrideExtruderStack(self, global_stack, extruder_file_content):
|
||||||
|
# get extruder position first
|
||||||
|
extruder_config = configparser.ConfigParser()
|
||||||
|
extruder_config.read_string(extruder_file_content)
|
||||||
|
if not extruder_config.has_option("metadata", "position"):
|
||||||
|
msg = "Could not find 'metadata/position' in extruder stack file"
|
||||||
|
Logger.log("e", "Could not find 'metadata/position' in extruder stack file")
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
extruder_position = extruder_config.get("metadata", "position")
|
||||||
|
|
||||||
|
extruder_stack = global_stack.extruders[extruder_position]
|
||||||
|
|
||||||
|
# override the given extruder stack
|
||||||
|
extruder_stack.deserialize(extruder_file_content)
|
||||||
|
|
||||||
|
# return the new ExtruderStack
|
||||||
|
return extruder_stack
|
||||||
|
|
||||||
|
## Read the project file
|
||||||
|
# Add all the definitions / materials / quality changes that do not exist yet. Then it loads
|
||||||
|
# all the stacks into the container registry. In some cases it will reuse the container for the global stack.
|
||||||
|
# It handles old style project files containing .stack.cfg as well as new style project files
|
||||||
|
# containing global.cfg / extruder.cfg
|
||||||
|
#
|
||||||
|
# \param file_name
|
||||||
def read(self, file_name):
|
def read(self, file_name):
|
||||||
archive = zipfile.ZipFile(file_name, "r")
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
|
|
||||||
@ -232,6 +370,35 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
# We do this so that if something goes wrong, it's easier to clean up.
|
# We do this so that if something goes wrong, it's easier to clean up.
|
||||||
containers_to_add = []
|
containers_to_add = []
|
||||||
|
|
||||||
|
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
|
||||||
|
|
||||||
|
global_stack = None
|
||||||
|
extruder_stacks = []
|
||||||
|
extruder_stacks_added = []
|
||||||
|
container_stacks_added = []
|
||||||
|
|
||||||
|
containers_added = []
|
||||||
|
|
||||||
|
global_stack_id_original = self._stripFileToId(global_stack_file)
|
||||||
|
global_stack_id_new = global_stack_id_original
|
||||||
|
global_stack_need_rename = False
|
||||||
|
|
||||||
|
extruder_stack_id_map = {} # new and old ExtruderStack IDs map
|
||||||
|
if self._resolve_strategies["machine"] == "new":
|
||||||
|
# We need a new id if the id already exists
|
||||||
|
if self._container_registry.findContainerStacks(id = global_stack_id_original):
|
||||||
|
global_stack_id_new = self.getNewId(global_stack_id_original)
|
||||||
|
global_stack_need_rename = True
|
||||||
|
|
||||||
|
for each_extruder_stack_file in extruder_stack_files:
|
||||||
|
old_container_id = self._stripFileToId(each_extruder_stack_file)
|
||||||
|
new_container_id = old_container_id
|
||||||
|
if self._container_registry.findContainerStacks(id = old_container_id):
|
||||||
|
# get a new name for this extruder
|
||||||
|
new_container_id = self.getNewId(old_container_id)
|
||||||
|
|
||||||
|
extruder_stack_id_map[old_container_id] = new_container_id
|
||||||
|
|
||||||
# TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
|
# TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
|
||||||
# TODO: cases that the container loaded is the same (most notable in materials & definitions).
|
# TODO: cases that the container loaded is the same (most notable in materials & definitions).
|
||||||
# TODO: It might be possible that we need to add smarter checking in the future.
|
# TODO: It might be possible that we need to add smarter checking in the future.
|
||||||
@ -240,7 +407,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||||
for definition_container_file in definition_container_files:
|
for definition_container_file in definition_container_files:
|
||||||
container_id = self._stripFileToId(definition_container_file)
|
container_id = self._stripFileToId(definition_container_file)
|
||||||
definitions = self._container_registry.findDefinitionContainers(id=container_id)
|
definitions = self._container_registry.findDefinitionContainers(id = container_id)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
definition_container = DefinitionContainer(container_id)
|
definition_container = DefinitionContainer(container_id)
|
||||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
|
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
|
||||||
@ -257,21 +424,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
|
||||||
for material_container_file in material_container_files:
|
for material_container_file in material_container_files:
|
||||||
container_id = self._stripFileToId(material_container_file)
|
container_id = self._stripFileToId(material_container_file)
|
||||||
materials = self._container_registry.findInstanceContainers(id=container_id)
|
materials = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
|
|
||||||
if not materials:
|
if not materials:
|
||||||
material_container = xml_material_profile(container_id)
|
material_container = xml_material_profile(container_id)
|
||||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
containers_to_add.append(material_container)
|
containers_to_add.append(material_container)
|
||||||
else:
|
else:
|
||||||
if not materials[0].isReadOnly(): # Only create new materials if they are not read only.
|
material_container = materials[0]
|
||||||
|
if not material_container.isReadOnly(): # Only create new materials if they are not read only.
|
||||||
if self._resolve_strategies["material"] == "override":
|
if self._resolve_strategies["material"] == "override":
|
||||||
materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
elif self._resolve_strategies["material"] == "new":
|
elif self._resolve_strategies["material"] == "new":
|
||||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||||
# auto created & added.
|
# auto created & added.
|
||||||
material_container = xml_material_profile(self.getNewId(container_id))
|
material_container = xml_material_profile(self.getNewId(container_id))
|
||||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||||
containers_to_add.append(material_container)
|
containers_to_add.append(material_container)
|
||||||
|
|
||||||
material_containers.append(material_container)
|
material_containers.append(material_container)
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
@ -279,99 +449,155 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
# Get quality_changes and user profiles saved in the workspace
|
# Get quality_changes and user profiles saved in the workspace
|
||||||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||||
user_instance_containers = []
|
user_instance_containers = []
|
||||||
quality_changes_instance_containers = []
|
quality_and_definition_changes_instance_containers = []
|
||||||
for instance_container_file in instance_container_files:
|
for instance_container_file in instance_container_files:
|
||||||
container_id = self._stripFileToId(instance_container_file)
|
container_id = self._stripFileToId(instance_container_file)
|
||||||
|
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
||||||
|
|
||||||
|
# HACK! we ignore "quality" and "variant" instance containers!
|
||||||
|
parser = configparser.ConfigParser()
|
||||||
|
parser.read_string(serialized)
|
||||||
|
if not parser.has_option("metadata", "type"):
|
||||||
|
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
|
||||||
|
continue
|
||||||
|
if parser.get("metadata", "type") in self._ignored_instance_container_types:
|
||||||
|
continue
|
||||||
|
|
||||||
instance_container = InstanceContainer(container_id)
|
instance_container = InstanceContainer(container_id)
|
||||||
|
|
||||||
# Deserialize InstanceContainer by converting read data from bytes to string
|
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
instance_container.deserialize(serialized)
|
||||||
container_type = instance_container.getMetaDataEntry("type")
|
container_type = instance_container.getMetaDataEntry("type")
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
if container_type == "user":
|
|
||||||
|
#
|
||||||
|
# IMPORTANT:
|
||||||
|
# If an instance container (or maybe other type of container) exists, and user chooses "Create New",
|
||||||
|
# we need to rename this container and all references to it, and changing those references are VERY
|
||||||
|
# HARD.
|
||||||
|
#
|
||||||
|
if container_type in self._ignored_instance_container_types:
|
||||||
|
# Ignore certain instance container types
|
||||||
|
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
|
||||||
|
continue
|
||||||
|
elif container_type == "user":
|
||||||
# Check if quality changes already exists.
|
# Check if quality changes already exists.
|
||||||
user_containers = self._container_registry.findInstanceContainers(id=container_id)
|
user_containers = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
if not user_containers:
|
if not user_containers:
|
||||||
containers_to_add.append(instance_container)
|
containers_to_add.append(instance_container)
|
||||||
else:
|
else:
|
||||||
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
|
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
|
||||||
user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
instance_container = user_containers[0]
|
||||||
|
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
instance_container.setDirty(True)
|
||||||
elif self._resolve_strategies["machine"] == "new":
|
elif self._resolve_strategies["machine"] == "new":
|
||||||
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
|
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
|
||||||
extruder_id = instance_container.getMetaDataEntry("extruder", None)
|
old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
|
||||||
if extruder_id:
|
if old_extruder_id:
|
||||||
new_id = self.getNewId(extruder_id) + "_current_settings"
|
new_extruder_id = extruder_stack_id_map[old_extruder_id]
|
||||||
|
new_id = new_extruder_id + "_current_settings"
|
||||||
instance_container._id = new_id
|
instance_container._id = new_id
|
||||||
instance_container.setName(new_id)
|
instance_container.setName(new_id)
|
||||||
instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
|
instance_container.setMetaDataEntry("extruder", new_extruder_id)
|
||||||
containers_to_add.append(instance_container)
|
containers_to_add.append(instance_container)
|
||||||
|
|
||||||
machine_id = instance_container.getMetaDataEntry("machine", None)
|
machine_id = instance_container.getMetaDataEntry("machine", None)
|
||||||
if machine_id:
|
if machine_id:
|
||||||
new_id = self.getNewId(machine_id) + "_current_settings"
|
new_machine_id = self.getNewId(machine_id)
|
||||||
|
new_id = new_machine_id + "_current_settings"
|
||||||
instance_container._id = new_id
|
instance_container._id = new_id
|
||||||
instance_container.setName(new_id)
|
instance_container.setName(new_id)
|
||||||
instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
|
instance_container.setMetaDataEntry("machine", new_machine_id)
|
||||||
containers_to_add.append(instance_container)
|
containers_to_add.append(instance_container)
|
||||||
user_instance_containers.append(instance_container)
|
user_instance_containers.append(instance_container)
|
||||||
elif container_type == "quality_changes":
|
elif container_type in ("quality_changes", "definition_changes"):
|
||||||
# Check if quality changes already exists.
|
# Check if quality changes already exists.
|
||||||
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
|
changes_containers = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
if not quality_changes:
|
if not changes_containers:
|
||||||
|
# no existing containers with the same ID, so we can safely add the new one
|
||||||
containers_to_add.append(instance_container)
|
containers_to_add.append(instance_container)
|
||||||
else:
|
else:
|
||||||
if self._resolve_strategies["quality_changes"] == "override":
|
# we have found existing container with the same ID, so we need to resolve according to the
|
||||||
quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
# selected strategy.
|
||||||
elif self._resolve_strategies["quality_changes"] is None:
|
if self._resolve_strategies[container_type] == "override":
|
||||||
|
instance_container = changes_containers[0]
|
||||||
|
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||||
|
instance_container.setDirty(True)
|
||||||
|
|
||||||
|
elif self._resolve_strategies[container_type] == "new":
|
||||||
|
# TODO: how should we handle the case "new" for quality_changes and definition_changes?
|
||||||
|
|
||||||
|
instance_container.setName(self._container_registry.uniqueName(instance_container.getName()))
|
||||||
|
new_changes_container_id = self.getNewId(instance_container.getId())
|
||||||
|
instance_container._id = new_changes_container_id
|
||||||
|
|
||||||
|
# TODO: we don't know the following is correct or not, need to verify
|
||||||
|
# AND REFACTOR!!!
|
||||||
|
if self._resolve_strategies["machine"] == "new":
|
||||||
|
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
|
||||||
|
old_extruder_id = instance_container.getMetaDataEntry("extruder", None)
|
||||||
|
if old_extruder_id:
|
||||||
|
new_extruder_id = extruder_stack_id_map[old_extruder_id]
|
||||||
|
instance_container.setMetaDataEntry("extruder", new_extruder_id)
|
||||||
|
|
||||||
|
machine_id = instance_container.getMetaDataEntry("machine", None)
|
||||||
|
if machine_id:
|
||||||
|
new_machine_id = self.getNewId(machine_id)
|
||||||
|
instance_container.setMetaDataEntry("machine", new_machine_id)
|
||||||
|
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
|
||||||
|
elif self._resolve_strategies[container_type] is None:
|
||||||
# The ID already exists, but nothing in the values changed, so do nothing.
|
# The ID already exists, but nothing in the values changed, so do nothing.
|
||||||
pass
|
pass
|
||||||
quality_changes_instance_containers.append(instance_container)
|
quality_and_definition_changes_instance_containers.append(instance_container)
|
||||||
else:
|
else:
|
||||||
continue
|
existing_container = self._container_registry.findInstanceContainers(id = container_id)
|
||||||
|
if not existing_container:
|
||||||
|
containers_to_add.append(instance_container)
|
||||||
|
if global_stack_need_rename:
|
||||||
|
if instance_container.getMetaDataEntry("machine"):
|
||||||
|
instance_container.setMetaDataEntry("machine", global_stack_id_new)
|
||||||
|
|
||||||
# Add all the containers right before we try to add / serialize the stack
|
# Add all the containers right before we try to add / serialize the stack
|
||||||
for container in containers_to_add:
|
for container in containers_to_add:
|
||||||
self._container_registry.addContainer(container)
|
self._container_registry.addContainer(container)
|
||||||
container.setDirty(True)
|
container.setDirty(True)
|
||||||
|
containers_added.append(container)
|
||||||
|
|
||||||
# Get the stack(s) saved in the workspace.
|
# Get the stack(s) saved in the workspace.
|
||||||
Logger.log("d", "Workspace loading is checking stacks containers...")
|
Logger.log("d", "Workspace loading is checking stacks containers...")
|
||||||
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
|
|
||||||
global_stack = None
|
|
||||||
extruder_stacks = []
|
|
||||||
container_stacks_added = []
|
|
||||||
try:
|
|
||||||
for container_stack_file in container_stack_files:
|
|
||||||
container_id = self._stripFileToId(container_stack_file)
|
|
||||||
|
|
||||||
|
# --
|
||||||
|
# load global stack file
|
||||||
|
try:
|
||||||
# Check if a stack by this ID already exists;
|
# Check if a stack by this ID already exists;
|
||||||
container_stacks = self._container_registry.findContainerStacks(id=container_id)
|
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
|
||||||
if container_stacks:
|
if container_stacks:
|
||||||
stack = container_stacks[0]
|
stack = container_stacks[0]
|
||||||
|
|
||||||
if self._resolve_strategies["machine"] == "override":
|
if self._resolve_strategies["machine"] == "override":
|
||||||
# TODO: HACK
|
# TODO: HACK
|
||||||
# There is a machine, check if it has authenticationd data. If so, keep that data.
|
# 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_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
|
||||||
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
|
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
|
||||||
container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||||
if network_authentication_id:
|
if network_authentication_id:
|
||||||
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
|
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
|
||||||
if network_authentication_key:
|
if network_authentication_key:
|
||||||
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
|
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
|
||||||
elif self._resolve_strategies["machine"] == "new":
|
elif self._resolve_strategies["machine"] == "new":
|
||||||
new_id = self.getNewId(container_id)
|
stack = GlobalStack(global_stack_id_new)
|
||||||
stack = ContainerStack(new_id)
|
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||||
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
|
||||||
|
|
||||||
# Ensure a unique ID and name
|
# Ensure a unique ID and name
|
||||||
stack._id = new_id
|
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
|
# 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.
|
# bound machine also needs to change.
|
||||||
if stack.getMetaDataEntry("machine", None):
|
if stack.getMetaDataEntry("machine", None):
|
||||||
stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))
|
stack.setMetaDataEntry("machine", global_stack_id_new)
|
||||||
|
|
||||||
if stack.getMetaDataEntry("type") != "extruder_train":
|
|
||||||
# Only machines need a new name, stacks may be non-unique
|
# Only machines need a new name, stacks may be non-unique
|
||||||
stack.setName(self._container_registry.uniqueName(stack.getName()))
|
stack.setName(self._container_registry.uniqueName(stack.getName()))
|
||||||
container_stacks_added.append(stack)
|
container_stacks_added.append(stack)
|
||||||
@ -379,82 +605,203 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
else:
|
else:
|
||||||
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
|
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
|
||||||
else:
|
else:
|
||||||
stack = ContainerStack(container_id)
|
# no existing container stack, so we create a new one
|
||||||
|
stack = GlobalStack(global_stack_id_new)
|
||||||
# Deserialize stack by converting read data from bytes to string
|
# Deserialize stack by converting read data from bytes to string
|
||||||
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
|
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||||
container_stacks_added.append(stack)
|
container_stacks_added.append(stack)
|
||||||
self._container_registry.addContainer(stack)
|
self._container_registry.addContainer(stack)
|
||||||
|
containers_added.append(stack)
|
||||||
|
|
||||||
if stack.getMetaDataEntry("type") == "extruder_train":
|
|
||||||
extruder_stacks.append(stack)
|
|
||||||
else:
|
|
||||||
global_stack = stack
|
global_stack = stack
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
except:
|
except:
|
||||||
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
||||||
# Something went really wrong. Try to remove any data that we added.
|
# Something went really wrong. Try to remove any data that we added.
|
||||||
for container in containers_to_add:
|
for container in containers_added:
|
||||||
self._container_registry.getInstance().removeContainer(container.getId())
|
self._container_registry.removeContainer(container.getId())
|
||||||
|
return
|
||||||
|
|
||||||
for container in container_stacks_added:
|
# --
|
||||||
self._container_registry.getInstance().removeContainer(container.getId())
|
# load extruder stack files
|
||||||
|
try:
|
||||||
|
for index, extruder_stack_file in enumerate(extruder_stack_files):
|
||||||
|
container_id = self._stripFileToId(extruder_stack_file)
|
||||||
|
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
|
||||||
|
|
||||||
return None
|
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":
|
||||||
|
# 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":
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
stack.deserialize(extruder_file_content)
|
||||||
|
|
||||||
|
# Ensure a unique ID and name
|
||||||
|
stack._id = new_id
|
||||||
|
|
||||||
|
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"]))
|
||||||
|
|
||||||
|
extruder_stacks.append(stack)
|
||||||
|
except:
|
||||||
|
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
||||||
|
# Something went really wrong. Try to remove any data that we added.
|
||||||
|
for container in containers_added:
|
||||||
|
self._container_registry.removeContainer(container.getId())
|
||||||
|
return
|
||||||
|
|
||||||
|
#
|
||||||
|
# Replacing the old containers if resolve is "new".
|
||||||
|
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
|
||||||
|
# MUST get updated too.
|
||||||
|
#
|
||||||
if self._resolve_strategies["machine"] == "new":
|
if self._resolve_strategies["machine"] == "new":
|
||||||
# A new machine was made, but it was serialized with the wrong user container. Fix that now.
|
# A new machine was made, but it was serialized with the wrong user container. Fix that now.
|
||||||
for container in user_instance_containers:
|
for container in user_instance_containers:
|
||||||
|
# replacing the container ID for user instance containers for the extruders
|
||||||
extruder_id = container.getMetaDataEntry("extruder", None)
|
extruder_id = container.getMetaDataEntry("extruder", None)
|
||||||
if extruder_id:
|
if extruder_id:
|
||||||
for extruder in extruder_stacks:
|
for extruder in extruder_stacks:
|
||||||
if extruder.getId() == extruder_id:
|
if extruder.getId() == extruder_id:
|
||||||
extruder.replaceContainer(0, container)
|
extruder.userChanges = container
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# replacing the container ID for user instance containers for the machine
|
||||||
machine_id = container.getMetaDataEntry("machine", None)
|
machine_id = container.getMetaDataEntry("machine", None)
|
||||||
if machine_id:
|
if machine_id:
|
||||||
if global_stack.getId() == machine_id:
|
if global_stack.getId() == machine_id:
|
||||||
global_stack.replaceContainer(0, container)
|
global_stack.userChanges = container
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._resolve_strategies["quality_changes"] == "new":
|
for changes_container_type in ("quality_changes", "definition_changes"):
|
||||||
|
if self._resolve_strategies[changes_container_type] == "new":
|
||||||
# Quality changes needs to get a new ID, added to registry and to the right stacks
|
# Quality changes needs to get a new ID, added to registry and to the right stacks
|
||||||
for container in quality_changes_instance_containers:
|
for each_changes_container in quality_and_definition_changes_instance_containers:
|
||||||
old_id = container.getId()
|
# NOTE: The renaming and giving new IDs are possibly redundant because they are done in the
|
||||||
container.setName(self._container_registry.uniqueName(container.getName()))
|
# instance container loading part.
|
||||||
# We're not really supposed to change the ID in normal cases, but this is an exception.
|
new_id = each_changes_container.getId()
|
||||||
container._id = self.getNewId(container.getId())
|
|
||||||
|
|
||||||
# The container was not added yet, as it didn't have an unique ID. It does now, so add it.
|
# Find the old (current) changes container in the global stack
|
||||||
self._container_registry.addContainer(container)
|
if changes_container_type == "quality_changes":
|
||||||
|
old_container = global_stack.qualityChanges
|
||||||
|
elif changes_container_type == "definition_changes":
|
||||||
|
old_container = global_stack.definitionChanges
|
||||||
|
|
||||||
# Replace the quality changes container
|
# sanity checks
|
||||||
old_container = global_stack.findContainer({"type": "quality_changes"})
|
# NOTE: The following cases SHOULD NOT happen!!!!
|
||||||
if old_container.getId() == old_id:
|
if not old_container:
|
||||||
quality_changes_index = global_stack.getContainerIndex(old_container)
|
Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
|
||||||
global_stack.replaceContainer(quality_changes_index, container)
|
changes_container_type, global_stack.getId())
|
||||||
|
|
||||||
|
# Replace the quality/definition changes container if it's in the GlobalStack
|
||||||
|
# NOTE: we can get an empty container here, but the IDs will not match,
|
||||||
|
# so this comparison is fine.
|
||||||
|
if self._id_mapping.get(old_container.getId()) == new_id:
|
||||||
|
if changes_container_type == "quality_changes":
|
||||||
|
global_stack.qualityChanges = each_changes_container
|
||||||
|
elif changes_container_type == "definition_changes":
|
||||||
|
global_stack.definitionChanges = each_changes_container
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for stack in extruder_stacks:
|
# Replace the quality/definition changes container if it's in one of the ExtruderStacks
|
||||||
old_container = stack.findContainer({"type": "quality_changes"})
|
for each_extruder_stack in extruder_stacks:
|
||||||
if old_container.getId() == old_id:
|
changes_container = None
|
||||||
quality_changes_index = stack.getContainerIndex(old_container)
|
if changes_container_type == "quality_changes":
|
||||||
stack.replaceContainer(quality_changes_index, container)
|
changes_container = each_extruder_stack.qualityChanges
|
||||||
|
elif changes_container_type == "definition_changes":
|
||||||
|
changes_container = each_extruder_stack.definitionChanges
|
||||||
|
|
||||||
|
# sanity checks
|
||||||
|
# NOTE: The following cases SHOULD NOT happen!!!!
|
||||||
|
if not changes_container:
|
||||||
|
Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
|
||||||
|
changes_container_type, each_extruder_stack.getId())
|
||||||
|
|
||||||
|
# NOTE: we can get an empty container here, but the IDs will not match,
|
||||||
|
# so this comparison is fine.
|
||||||
|
if self._id_mapping.get(changes_container.getId()) == new_id:
|
||||||
|
if changes_container_type == "quality_changes":
|
||||||
|
each_extruder_stack.qualityChanges = each_changes_container
|
||||||
|
elif changes_container_type == "definition_changes":
|
||||||
|
each_extruder_stack.definitionChanges = each_changes_container
|
||||||
|
|
||||||
if self._resolve_strategies["material"] == "new":
|
if self._resolve_strategies["material"] == "new":
|
||||||
for material in material_containers:
|
for each_material in material_containers:
|
||||||
old_material = global_stack.findContainer({"type": "material"})
|
old_material = global_stack.material
|
||||||
if old_material.getId() in self._id_mapping:
|
|
||||||
material_index = global_stack.getContainerIndex(old_material)
|
# check if the old material container has been renamed to this material container ID
|
||||||
global_stack.replaceContainer(material_index, material)
|
# if the container hasn't been renamed, we do nothing.
|
||||||
|
new_id = self._id_mapping.get(old_material.getId())
|
||||||
|
if new_id is None or new_id != each_material.getId():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for stack in extruder_stacks:
|
|
||||||
old_material = stack.findContainer({"type": "material"})
|
|
||||||
if old_material.getId() in self._id_mapping:
|
if old_material.getId() in self._id_mapping:
|
||||||
material_index = stack.getContainerIndex(old_material)
|
global_stack.material = each_material
|
||||||
stack.replaceContainer(material_index, material)
|
|
||||||
|
for each_extruder_stack in extruder_stacks:
|
||||||
|
old_material = each_extruder_stack.material
|
||||||
|
|
||||||
|
# check if the old material container has been renamed to this material container ID
|
||||||
|
# if the container hasn't been renamed, we do nothing.
|
||||||
|
new_id = self._id_mapping.get(old_material.getId())
|
||||||
|
if new_id is None or new_id != each_material.getId():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if old_material.getId() in self._id_mapping:
|
||||||
|
each_extruder_stack.material = each_material
|
||||||
|
|
||||||
|
if extruder_stacks:
|
||||||
for stack in extruder_stacks:
|
for stack in extruder_stacks:
|
||||||
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
|
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
|
||||||
else:
|
else:
|
||||||
@ -463,9 +810,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
|
|
||||||
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
|
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
|
||||||
|
|
||||||
# Notify everything/one that is to notify about changes.
|
if self._resolve_strategies["machine"] == "new":
|
||||||
global_stack.containersChanged.emit(global_stack.getTop())
|
|
||||||
|
|
||||||
for stack in extruder_stacks:
|
for stack in extruder_stacks:
|
||||||
stack.setNextStack(global_stack)
|
stack.setNextStack(global_stack)
|
||||||
stack.containersChanged.emit(stack.getTop())
|
stack.containersChanged.emit(stack.getTop())
|
||||||
@ -473,6 +818,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
# Actually change the active machine.
|
# Actually change the active machine.
|
||||||
Application.getInstance().setGlobalContainerStack(global_stack)
|
Application.getInstance().setGlobalContainerStack(global_stack)
|
||||||
|
|
||||||
|
# Notify everything/one that is to notify about changes.
|
||||||
|
global_stack.containersChanged.emit(global_stack.getTop())
|
||||||
|
|
||||||
# Load all the nodes / meshdata of the workspace
|
# Load all the nodes / meshdata of the workspace
|
||||||
nodes = self._3mf_mesh_reader.read(file_name)
|
nodes = self._3mf_mesh_reader.read(file_name)
|
||||||
if nodes is None:
|
if nodes is None:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
|
from PyQt5.QtCore import QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
@ -29,11 +29,13 @@ class WorkspaceDialog(QObject):
|
|||||||
self._default_strategy = "override"
|
self._default_strategy = "override"
|
||||||
self._result = {"machine": self._default_strategy,
|
self._result = {"machine": self._default_strategy,
|
||||||
"quality_changes": self._default_strategy,
|
"quality_changes": self._default_strategy,
|
||||||
|
"definition_changes": self._default_strategy,
|
||||||
"material": self._default_strategy}
|
"material": self._default_strategy}
|
||||||
self._visible = False
|
self._visible = False
|
||||||
self.showDialogSignal.connect(self.__show)
|
self.showDialogSignal.connect(self.__show)
|
||||||
|
|
||||||
self._has_quality_changes_conflict = False
|
self._has_quality_changes_conflict = False
|
||||||
|
self._has_definition_changes_conflict = False
|
||||||
self._has_machine_conflict = False
|
self._has_machine_conflict = False
|
||||||
self._has_material_conflict = False
|
self._has_material_conflict = False
|
||||||
self._num_visible_settings = 0
|
self._num_visible_settings = 0
|
||||||
@ -51,6 +53,7 @@ class WorkspaceDialog(QObject):
|
|||||||
|
|
||||||
machineConflictChanged = pyqtSignal()
|
machineConflictChanged = pyqtSignal()
|
||||||
qualityChangesConflictChanged = pyqtSignal()
|
qualityChangesConflictChanged = pyqtSignal()
|
||||||
|
definitionChangesConflictChanged = pyqtSignal()
|
||||||
materialConflictChanged = pyqtSignal()
|
materialConflictChanged = pyqtSignal()
|
||||||
numVisibleSettingsChanged = pyqtSignal()
|
numVisibleSettingsChanged = pyqtSignal()
|
||||||
activeModeChanged = pyqtSignal()
|
activeModeChanged = pyqtSignal()
|
||||||
@ -185,6 +188,10 @@ class WorkspaceDialog(QObject):
|
|||||||
def qualityChangesConflict(self):
|
def qualityChangesConflict(self):
|
||||||
return self._has_quality_changes_conflict
|
return self._has_quality_changes_conflict
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=definitionChangesConflictChanged)
|
||||||
|
def definitionChangesConflict(self):
|
||||||
|
return self._has_definition_changes_conflict
|
||||||
|
|
||||||
@pyqtProperty(bool, notify=materialConflictChanged)
|
@pyqtProperty(bool, notify=materialConflictChanged)
|
||||||
def materialConflict(self):
|
def materialConflict(self):
|
||||||
return self._has_material_conflict
|
return self._has_material_conflict
|
||||||
@ -214,11 +221,18 @@ class WorkspaceDialog(QObject):
|
|||||||
self._has_quality_changes_conflict = quality_changes_conflict
|
self._has_quality_changes_conflict = quality_changes_conflict
|
||||||
self.qualityChangesConflictChanged.emit()
|
self.qualityChangesConflictChanged.emit()
|
||||||
|
|
||||||
|
def setDefinitionChangesConflict(self, definition_changes_conflict):
|
||||||
|
if self._has_definition_changes_conflict != definition_changes_conflict:
|
||||||
|
self._has_definition_changes_conflict = definition_changes_conflict
|
||||||
|
self.definitionChangesConflictChanged.emit()
|
||||||
|
|
||||||
def getResult(self):
|
def getResult(self):
|
||||||
if "machine" in self._result and not self._has_machine_conflict:
|
if "machine" in self._result and not self._has_machine_conflict:
|
||||||
self._result["machine"] = None
|
self._result["machine"] = None
|
||||||
if "quality_changes" in self._result and not self._has_quality_changes_conflict:
|
if "quality_changes" in self._result and not self._has_quality_changes_conflict:
|
||||||
self._result["quality_changes"] = None
|
self._result["quality_changes"] = None
|
||||||
|
if "definition_changes" in self._result and not self._has_definition_changes_conflict:
|
||||||
|
self._result["definition_changes"] = None
|
||||||
if "material" in self._result and not self._has_material_conflict:
|
if "material" in self._result and not self._has_material_conflict:
|
||||||
self._result["material"] = None
|
self._result["material"] = None
|
||||||
return self._result
|
return self._result
|
||||||
@ -240,6 +254,7 @@ class WorkspaceDialog(QObject):
|
|||||||
# Reset the result
|
# Reset the result
|
||||||
self._result = {"machine": self._default_strategy,
|
self._result = {"machine": self._default_strategy,
|
||||||
"quality_changes": self._default_strategy,
|
"quality_changes": self._default_strategy,
|
||||||
|
"definition_changes": self._default_strategy,
|
||||||
"material": self._default_strategy}
|
"material": self._default_strategy}
|
||||||
self._visible = True
|
self._visible = True
|
||||||
self.showDialogSignal.emit()
|
self.showDialogSignal.emit()
|
||||||
|
@ -12,15 +12,12 @@ UM.Dialog
|
|||||||
{
|
{
|
||||||
title: catalog.i18nc("@title:window", "Open Project")
|
title: catalog.i18nc("@title:window", "Open Project")
|
||||||
|
|
||||||
width: 550 * Screen.devicePixelRatio
|
width: 500
|
||||||
minimumWidth: 550 * Screen.devicePixelRatio
|
height: 400
|
||||||
maximumWidth: minimumWidth
|
|
||||||
|
property int comboboxHeight: 15
|
||||||
|
property int spacerHeight: 10
|
||||||
|
|
||||||
height: 400 * Screen.devicePixelRatio
|
|
||||||
minimumHeight: 400 * Screen.devicePixelRatio
|
|
||||||
maximumHeight: minimumHeight
|
|
||||||
property int comboboxHeight: 15 * Screen.devicePixelRatio
|
|
||||||
property int spacerHeight: 10 * Screen.devicePixelRatio
|
|
||||||
onClosing: manager.notifyClosed()
|
onClosing: manager.notifyClosed()
|
||||||
onVisibleChanged:
|
onVisibleChanged:
|
||||||
{
|
{
|
||||||
@ -34,7 +31,7 @@ UM.Dialog
|
|||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 20 * Screen.devicePixelRatio
|
anchors.margins: 20
|
||||||
|
|
||||||
UM.I18nCatalog
|
UM.I18nCatalog
|
||||||
{
|
{
|
||||||
@ -376,7 +373,6 @@ UM.Dialog
|
|||||||
enabled: true
|
enabled: true
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: ok_button.left
|
anchors.right: ok_button.left
|
||||||
anchors.bottomMargin: - 0.5 * height
|
|
||||||
anchors.rightMargin:2
|
anchors.rightMargin:2
|
||||||
}
|
}
|
||||||
Button
|
Button
|
||||||
@ -384,7 +380,6 @@ UM.Dialog
|
|||||||
id: ok_button
|
id: ok_button
|
||||||
text: catalog.i18nc("@action:button","Open");
|
text: catalog.i18nc("@action:button","Open");
|
||||||
onClicked: { manager.closeBackend(); manager.onOkButtonClicked() }
|
onClicked: { manager.closeBackend(); manager.onOkButtonClicked() }
|
||||||
anchors.bottomMargin: - 0.5 * height
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,13 @@ from UM.Platform import Platform
|
|||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData() -> Dict:
|
def getMetaData() -> Dict:
|
||||||
# Workarround for osx not supporting double file extensions correclty.
|
# Workarround for osx not supporting double file extensions correctly.
|
||||||
if Platform.isOSX():
|
if Platform.isOSX():
|
||||||
workspace_extension = "3mf"
|
workspace_extension = "3mf"
|
||||||
else:
|
else:
|
||||||
workspace_extension = "curaproject.3mf"
|
workspace_extension = "curaproject.3mf"
|
||||||
|
|
||||||
metaData = {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||||
metaData["mesh_reader"] = [
|
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"
|
||||||
|
}
|
@ -7,6 +7,7 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|||||||
import zipfile
|
import zipfile
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import copy
|
import copy
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
||||||
@ -48,6 +49,16 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|||||||
Preferences.getInstance().writeToFile(preferences_string)
|
Preferences.getInstance().writeToFile(preferences_string)
|
||||||
archive.writestr(preferences_file, preferences_string.getvalue())
|
archive.writestr(preferences_file, preferences_string.getvalue())
|
||||||
|
|
||||||
|
# Save Cura version
|
||||||
|
version_file = zipfile.ZipInfo("Cura/version.ini")
|
||||||
|
version_config_parser = configparser.ConfigParser()
|
||||||
|
version_config_parser.add_section("versions")
|
||||||
|
version_config_parser.set("versions", "cura_version", Application.getStaticVersion())
|
||||||
|
|
||||||
|
version_file_string = StringIO()
|
||||||
|
version_config_parser.write(version_file_string)
|
||||||
|
archive.writestr(version_file, version_file_string.getvalue())
|
||||||
|
|
||||||
# Close the archive & reset states.
|
# Close the archive & reset states.
|
||||||
archive.close()
|
archive.close()
|
||||||
mesh_writer.setStoreArchive(False)
|
mesh_writer.setStoreArchive(False)
|
||||||
|
@ -10,19 +10,18 @@ except ImportError:
|
|||||||
from . import ThreeMFWorkspaceWriter
|
from . import ThreeMFWorkspaceWriter
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Platform import Platform
|
||||||
|
|
||||||
i18n_catalog = i18nCatalog("uranium")
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
metaData = {
|
# Workarround for osx not supporting double file extensions correctly.
|
||||||
"plugin": {
|
if Platform.isOSX():
|
||||||
"name": i18n_catalog.i18nc("@label", "3MF Writer"),
|
workspace_extension = "3mf"
|
||||||
"author": "Ultimaker",
|
else:
|
||||||
"version": "1.0",
|
workspace_extension = "curaproject.3mf"
|
||||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
|
|
||||||
"api": 3
|
metaData = {}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if "3MFWriter.ThreeMFWriter" in sys.modules:
|
if "3MFWriter.ThreeMFWriter" in sys.modules:
|
||||||
metaData["mesh_writer"] = {
|
metaData["mesh_writer"] = {
|
||||||
@ -35,7 +34,7 @@ def getMetaData():
|
|||||||
}
|
}
|
||||||
metaData["workspace_writer"] = {
|
metaData["workspace_writer"] = {
|
||||||
"output": [{
|
"output": [{
|
||||||
"extension": "curaproject.3mf",
|
"extension": workspace_extension,
|
||||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||||
"mime_type": "application/x-curaproject+xml",
|
"mime_type": "application/x-curaproject+xml",
|
||||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||||
|
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")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "extension": AutoSave.AutoSave() }
|
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"
|
||||||
|
}
|
@ -1,3 +1,75 @@
|
|||||||
|
[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 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.
|
||||||
|
|
||||||
|
*Multi-extrusion support for custom FDM printers
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
*Support meshes
|
||||||
|
It’s now possible to load an extra model that will be used as a support structure.
|
||||||
|
|
||||||
|
*Mold
|
||||||
|
This is a bit of an experimental improvement. Users can use it to print a mold from a 3D model, which can be cast afterwards with the material that you would like your model to have.
|
||||||
|
|
||||||
|
*Towers for tiny overhangs
|
||||||
|
We’ve added a new support option allowing users to achieve more reliable results by creating towers to support even the smallest overhangs.
|
||||||
|
|
||||||
|
*Cutting meshes
|
||||||
|
Easily transform any model into a dual-extrusion print by applying a pattern for the second extruder. All areas of the original model, which also fall inside the pattern model, will be printed by the extruder selected for the pattern.
|
||||||
|
|
||||||
|
*Extruder per model selection via the context menu or extruder buttons
|
||||||
|
You can now select the necessary extruder in the right-click menu or extruder buttons. This is a quicker and more user-friendly process. The material color for each extruder will also be represented in the extruder icons.
|
||||||
|
|
||||||
|
*Custom toggle
|
||||||
|
We have made the interface a little bit cleaner and more user-friendly for switching from Recommended to Custom mode.
|
||||||
|
|
||||||
|
*Plugin installer
|
||||||
|
It used to be fairly tricky to install new plugins. We have now added a button to select and install new plugins with ease – you will find it in Preferences.
|
||||||
|
|
||||||
|
*Project-based menu
|
||||||
|
It’s a lot simpler to save and open files, and Cura will know if it’s a project, model, or gcode.
|
||||||
|
|
||||||
|
*Theme picker
|
||||||
|
If you have a custom theme, you can now apply it more easily in the preferences screen.
|
||||||
|
|
||||||
|
*Time estimates per feature
|
||||||
|
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.
|
||||||
|
|
||||||
|
*Invert the direction of camera zoom
|
||||||
|
We’ve added an option to invert mouse direction for a better user experience.
|
||||||
|
|
||||||
|
*Olsson block upgrade
|
||||||
|
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 allows users to send prints to OctoPrint. Thanks to Aldo Hoeben for this feature.
|
||||||
|
|
||||||
|
*Bug fixes
|
||||||
|
- Post Processing plugin
|
||||||
|
- Font rendering
|
||||||
|
- Progress bar
|
||||||
|
- Support Bottom Distance issues
|
||||||
|
|
||||||
|
*3rd party printers
|
||||||
|
- MAKEIT
|
||||||
|
- Alya
|
||||||
|
- Peopoly Moai
|
||||||
|
- Rigid3D Zero
|
||||||
|
- 3D maker
|
||||||
|
|
||||||
[2.5.0]
|
[2.5.0]
|
||||||
*Improved speed
|
*Improved speed
|
||||||
We’ve made changing printers, profiles, materials, and print cores even faster. 3MF processing is also much faster now. Opening a 3MF file now takes one tenth of the time.
|
We’ve made changing printers, profiles, materials, and print cores even faster. 3MF processing is also much faster now. Opening a 3MF file now takes one tenth of the time.
|
||||||
@ -79,7 +151,7 @@ The initial and final printing temperatures reduce the amount of oozing during P
|
|||||||
Initial and final printing temperature settings have been tuned for higher quality results. For all materials the initial print temperature is 5 degrees above the default value.
|
Initial and final printing temperature settings have been tuned for higher quality results. For all materials the initial print temperature is 5 degrees above the default value.
|
||||||
|
|
||||||
*Printing temperature of the materials
|
*Printing temperature of the materials
|
||||||
The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Normal Quality profile.
|
The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Fine profile.
|
||||||
|
|
||||||
*Improved PLA-PVA layer adhesion
|
*Improved PLA-PVA layer adhesion
|
||||||
The PVA jerk and acceleration have been optimized to improve the layer adhesion between PVA and PLA.
|
The PVA jerk and acceleration have been optimized to improve the layer adhesion between PVA and PLA.
|
||||||
|
@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
|
|||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return {"extension": ChangeLog.ChangeLog()}
|
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"
|
||||||
|
}
|
@ -90,9 +90,21 @@ message GCodeLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for the extruder
|
message PrintTimeMaterialEstimates { // The print time for each feature and material estimates for the extruder
|
||||||
float time = 1; // Total time estimate
|
// Time estimate in each feature
|
||||||
repeated MaterialEstimates materialEstimates = 2; // materialEstimates data
|
float time_none = 1;
|
||||||
|
float time_inset_0 = 2;
|
||||||
|
float time_inset_x = 3;
|
||||||
|
float time_skin = 4;
|
||||||
|
float time_support = 5;
|
||||||
|
float time_skirt = 6;
|
||||||
|
float time_infill = 7;
|
||||||
|
float time_support_infill = 8;
|
||||||
|
float time_travel = 9;
|
||||||
|
float time_retract = 10;
|
||||||
|
float time_support_interface = 11;
|
||||||
|
|
||||||
|
repeated MaterialEstimates materialEstimates = 12; // materialEstimates data
|
||||||
}
|
}
|
||||||
|
|
||||||
message MaterialEstimates {
|
message MaterialEstimates {
|
||||||
|
@ -187,7 +187,19 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
|
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.printDurationMessage.emit(0, [0])
|
self.printDurationMessage.emit({
|
||||||
|
"none": 0,
|
||||||
|
"inset_0": 0,
|
||||||
|
"inset_x": 0,
|
||||||
|
"skin": 0,
|
||||||
|
"support": 0,
|
||||||
|
"skirt": 0,
|
||||||
|
"infill": 0,
|
||||||
|
"support_infill": 0,
|
||||||
|
"travel": 0,
|
||||||
|
"retract": 0,
|
||||||
|
"support_interface": 0
|
||||||
|
}, [0])
|
||||||
|
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
self._stored_optimized_layer_data = []
|
self._stored_optimized_layer_data = []
|
||||||
@ -273,9 +285,15 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
if not extruders:
|
if not extruders:
|
||||||
error_keys = self._global_container_stack.getErrorKeys()
|
error_keys = self._global_container_stack.getErrorKeys()
|
||||||
error_labels = set()
|
error_labels = set()
|
||||||
definition_container = self._global_container_stack.getBottom()
|
|
||||||
for key in error_keys:
|
for key in error_keys:
|
||||||
error_labels.add(definition_container.findDefinitions(key = key)[0].label)
|
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
|
||||||
|
definitions = stack.getBottom().findDefinitions(key = key)
|
||||||
|
if definitions:
|
||||||
|
break #Found it! No need to continue search.
|
||||||
|
else: #No stack has a definition for this setting.
|
||||||
|
Logger.log("w", "When checking settings for errors, unable to find definition for key: {key}".format(key = key))
|
||||||
|
continue
|
||||||
|
error_labels.add(definitions[0].label)
|
||||||
|
|
||||||
error_labels = ", ".join(error_labels)
|
error_labels = ", ".join(error_labels)
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)))
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)))
|
||||||
@ -475,13 +493,26 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
## Called when a print time message is received from the engine.
|
## Called when a print time message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuff message containing the print time and
|
# \param message The protobuf message containing the print time per feature and
|
||||||
# material amount per extruder
|
# material amount per extruder
|
||||||
def _onPrintTimeMaterialEstimates(self, message):
|
def _onPrintTimeMaterialEstimates(self, message):
|
||||||
material_amounts = []
|
material_amounts = []
|
||||||
for index in range(message.repeatedMessageCount("materialEstimates")):
|
for index in range(message.repeatedMessageCount("materialEstimates")):
|
||||||
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
||||||
self.printDurationMessage.emit(message.time, material_amounts)
|
feature_times = {
|
||||||
|
"none": message.time_none,
|
||||||
|
"inset_0": message.time_inset_0,
|
||||||
|
"inset_x": message.time_inset_x,
|
||||||
|
"skin": message.time_skin,
|
||||||
|
"support": message.time_support,
|
||||||
|
"skirt": message.time_skirt,
|
||||||
|
"infill": message.time_infill,
|
||||||
|
"support_infill": message.time_support_infill,
|
||||||
|
"travel": message.time_travel,
|
||||||
|
"retract": message.time_retract,
|
||||||
|
"support_interface": message.time_support_interface
|
||||||
|
}
|
||||||
|
self.printDurationMessage.emit(feature_times, material_amounts)
|
||||||
|
|
||||||
## Creates a new socket connection.
|
## Creates a new socket connection.
|
||||||
def _createSocket(self):
|
def _createSocket(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Copyright (c) 2016 Ultimaker B.V.
|
#Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
#Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
@ -31,6 +31,9 @@ catalog = i18nCatalog("cura")
|
|||||||
#
|
#
|
||||||
# \param color_code html color code, i.e. "#FF0000" -> red
|
# \param color_code html color code, i.e. "#FF0000" -> red
|
||||||
def colorCodeToRGBA(color_code):
|
def colorCodeToRGBA(color_code):
|
||||||
|
if color_code is None:
|
||||||
|
Logger.log("w", "Unable to convert color code, returning default")
|
||||||
|
return [0, 0, 0, 1]
|
||||||
return [
|
return [
|
||||||
int(color_code[1:3], 16) / 255,
|
int(color_code[1:3], 16) / 255,
|
||||||
int(color_code[3:5], 16) / 255,
|
int(color_code[3:5], 16) / 255,
|
||||||
@ -170,19 +173,14 @@ class ProcessSlicedLayersJob(Job):
|
|||||||
if extruders:
|
if extruders:
|
||||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
material = extruder.findContainer({"type": "material"})
|
|
||||||
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
|
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
|
||||||
color_code = material.getMetaDataEntry("color_code")
|
color_code = extruder.material.getMetaDataEntry("color_code", default="#e0e000")
|
||||||
color = colorCodeToRGBA(color_code)
|
color = colorCodeToRGBA(color_code)
|
||||||
material_color_map[position, :] = color
|
material_color_map[position, :] = color
|
||||||
else:
|
else:
|
||||||
# Single extruder via global stack.
|
# Single extruder via global stack.
|
||||||
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
||||||
material = global_container_stack.findContainer({"type": "material"})
|
color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
|
||||||
color_code = "#e0e000"
|
|
||||||
if material:
|
|
||||||
if material.getMetaDataEntry("color_code") is not None:
|
|
||||||
color_code = material.getMetaDataEntry("color_code")
|
|
||||||
color = colorCodeToRGBA(color_code)
|
color = colorCodeToRGBA(color_code)
|
||||||
material_color_map[0, :] = color
|
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.
|
## Job class that builds up the message of scene data to send to CuraEngine.
|
||||||
class StartSliceJob(Job):
|
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):
|
def __init__(self, slice_message):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@ -132,7 +140,8 @@ class StartSliceJob(Job):
|
|||||||
temp_list = []
|
temp_list = []
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
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)
|
temp_list.append(node)
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
@ -149,8 +158,13 @@ class StartSliceJob(Job):
|
|||||||
self._buildGlobalSettingsMessage(stack)
|
self._buildGlobalSettingsMessage(stack)
|
||||||
self._buildGlobalInheritsStackMessage(stack)
|
self._buildGlobalInheritsStackMessage(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()):
|
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||||
self._buildExtruderMessage(extruder_stack)
|
self._buildExtruderMessage(extruder_stack)
|
||||||
|
else:
|
||||||
|
self._buildExtruderMessageFromGlobalStack(stack)
|
||||||
|
|
||||||
for group in object_groups:
|
for group in object_groups:
|
||||||
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
||||||
@ -212,7 +226,7 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
for key in stack.getAllKeys():
|
for key in stack.getAllKeys():
|
||||||
# Do not send settings that are not settable_per_extruder.
|
# 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
|
continue
|
||||||
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
||||||
setting.name = key
|
setting.name = key
|
||||||
@ -223,6 +237,19 @@ class StartSliceJob(Job):
|
|||||||
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
||||||
Job.yieldThread()
|
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.
|
## Sends all global settings to the engine.
|
||||||
#
|
#
|
||||||
# The settings are taken from the global stack. This does not include any
|
# 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")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "backend": CuraEngineBackend.CuraEngineBackend() }
|
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():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"profile_reader": [
|
||||||
{
|
{
|
||||||
"extension": "curaprofile",
|
"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():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"profile_writer": [
|
||||||
{
|
{
|
||||||
"extension": "curaprofile",
|
"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"
|
||||||
|
}
|
@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader):
|
|||||||
# TODO: Consider moving settings to the start?
|
# TODO: Consider moving settings to the start?
|
||||||
serialized = "" # Will be filled with the serialized profile.
|
serialized = "" # Will be filled with the serialized profile.
|
||||||
try:
|
try:
|
||||||
with open(file_name) as f:
|
with open(file_name, "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith(prefix):
|
if line.startswith(prefix):
|
||||||
# Remove the prefix and the newline from the line and add it to the rest.
|
# Remove the prefix and the newline from the line and add it to the rest.
|
||||||
@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
serialized = unescapeGcodeComment(serialized)
|
serialized = unescapeGcodeComment(serialized)
|
||||||
Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized)))
|
|
||||||
|
|
||||||
|
# serialized data can be invalid JSON
|
||||||
|
try:
|
||||||
json_data = json.loads(serialized)
|
json_data = json.loads(serialized)
|
||||||
|
except Exception as e:
|
||||||
|
Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e)
|
||||||
|
return None
|
||||||
|
|
||||||
profiles = []
|
profiles = []
|
||||||
global_profile = readQualityProfileFromString(json_data["global_quality"])
|
global_profile = readQualityProfileFromString(json_data["global_quality"])
|
||||||
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"profile_reader": [
|
||||||
{
|
{
|
||||||
"extension": "gcode",
|
"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():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"mesh_reader": [
|
||||||
{
|
{
|
||||||
"extension": "gcode",
|
"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.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Mesh.MeshWriter import MeshWriter
|
from UM.Mesh.MeshWriter import MeshWriter
|
||||||
@ -85,6 +85,7 @@ class GCodeWriter(MeshWriter):
|
|||||||
|
|
||||||
for key in instance_container1.getAllKeys():
|
for key in instance_container1.getAllKeys():
|
||||||
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
|
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
|
||||||
|
|
||||||
return flat_container
|
return flat_container
|
||||||
|
|
||||||
|
|
||||||
@ -100,26 +101,32 @@ class GCodeWriter(MeshWriter):
|
|||||||
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
||||||
prefix_length = len(prefix)
|
prefix_length = len(prefix)
|
||||||
|
|
||||||
container_with_profile = stack.findContainer({"type": "quality_changes"})
|
container_with_profile = stack.qualityChanges
|
||||||
if not container_with_profile:
|
if not container_with_profile:
|
||||||
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
|
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
||||||
|
# If the quality changes is not set, we need to set type manually
|
||||||
|
if flat_global_container.getMetaDataEntry("type", None) is None:
|
||||||
|
flat_global_container.addMetaDataEntry("type", "quality_changes")
|
||||||
|
|
||||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||||
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
|
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()
|
serialized = flat_global_container.serialize()
|
||||||
data = {"global_quality": serialized}
|
data = {"global_quality": serialized}
|
||||||
|
|
||||||
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
|
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
|
||||||
extruder_quality = extruder.findContainer({"type": "quality_changes"})
|
extruder_quality = extruder.qualityChanges
|
||||||
if not extruder_quality:
|
if not extruder_quality:
|
||||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||||
continue
|
continue
|
||||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
|
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
|
||||||
|
# If the quality changes is not set, we need to set type manually
|
||||||
|
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
|
||||||
|
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
|
||||||
|
|
||||||
# Ensure that extruder is set. (Can happen if we have empty quality changes).
|
# Ensure that extruder is set. (Can happen if we have empty quality changes).
|
||||||
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
|
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
|
||||||
@ -127,7 +134,7 @@ class GCodeWriter(MeshWriter):
|
|||||||
|
|
||||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||||
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
|
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()
|
extruder_serialized = flat_extruder_quality.serialize()
|
||||||
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
||||||
|
|
||||||
|
@ -8,13 +8,7 @@ catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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": {
|
"mesh_writer": {
|
||||||
"output": [{
|
"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():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"mesh_reader": [
|
||||||
{
|
{
|
||||||
"extension": "jpg",
|
"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
|
start = 0
|
||||||
end = 0
|
end = 0
|
||||||
element_counts = layer_data.getElementCounts()
|
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:
|
if layer > self._layer_view._current_layer_num:
|
||||||
break
|
break
|
||||||
if self._layer_view._minimum_layer_num > layer:
|
if self._layer_view._minimum_layer_num > layer:
|
||||||
start += counts
|
start += element_counts[layer]
|
||||||
end += counts
|
end += element_counts[layer]
|
||||||
|
|
||||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
# 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))
|
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
|
Item
|
||||||
{
|
{
|
||||||
|
id: base
|
||||||
width: {
|
width: {
|
||||||
if (UM.LayerView.compatibilityMode) {
|
if (UM.LayerView.compatibilityMode) {
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
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)
|
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
|
id: layerViewMenu
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@ -34,6 +39,11 @@ Item
|
|||||||
height: parent.height
|
height: parent.height
|
||||||
z: slider.z - 1
|
z: slider.z - 1
|
||||||
color: UM.Theme.getColor("tool_panel_background")
|
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 {
|
ColumnLayout {
|
||||||
id: view_settings
|
id: view_settings
|
||||||
@ -522,27 +532,20 @@ Item
|
|||||||
target: Qt.point(0, slider.activeHandle.y + slider.activeHandle.height / 2)
|
target: Qt.point(0, slider.activeHandle.y + slider.activeHandle.height / 2)
|
||||||
arrowSize: UM.Theme.getSize("default_arrow").width
|
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
|
width: valueLabel.width + UM.Theme.getSize("default_margin").width
|
||||||
Behavior on height { NumberAnimation { duration: 50; } }
|
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
|
visible: slider.layersVisible
|
||||||
|
|
||||||
UM.PointingRectangle
|
|
||||||
{
|
|
||||||
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)
|
MouseArea //Catch all mouse events (so scene doesnt handle them)
|
||||||
{
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TextField
|
TextField
|
||||||
{
|
{
|
||||||
|
@ -9,13 +9,6 @@ catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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": {
|
"view": {
|
||||||
"name": catalog.i18nc("@item:inlistbox", "Layers"),
|
"name": catalog.i18nc("@item:inlistbox", "Layers"),
|
||||||
"view_panel": "LayerView.qml",
|
"view_panel": "LayerView.qml",
|
||||||
|
@ -130,9 +130,9 @@ geometry41core =
|
|||||||
// fixed size for movements
|
// fixed size for movements
|
||||||
size_x = 0.05;
|
size_x = 0.05;
|
||||||
} else {
|
} 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_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));
|
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():
|
def getMetaData():
|
||||||
return {
|
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": [
|
"profile_reader": [
|
||||||
{
|
{
|
||||||
"extension": "ini",
|
"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"
|
||||||
|
}
|
@ -14,7 +14,7 @@ from UM.Settings.DefinitionContainer import DefinitionContainer
|
|||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
import UM.i18n
|
import UM.i18n
|
||||||
@ -69,7 +69,8 @@ class MachineSettingsAction(MachineAction):
|
|||||||
self._container_index = container_index
|
self._container_index = container_index
|
||||||
self.containerIndexChanged.emit()
|
self.containerIndexChanged.emit()
|
||||||
|
|
||||||
# Disable autoslicing while the machineaction is showing
|
# Disable auto-slicing while the MachineAction is showing
|
||||||
|
if self._backend: # This sometimes triggers before backend is loaded.
|
||||||
self._backend.disableTimer()
|
self._backend.disableTimer()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@ -99,6 +100,7 @@ class MachineSettingsAction(MachineAction):
|
|||||||
definition = container_stack.getBottom()
|
definition = container_stack.getBottom()
|
||||||
definition_changes_container.setDefinition(definition)
|
definition_changes_container.setDefinition(definition)
|
||||||
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||||
|
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
|
|
||||||
self._container_registry.addContainer(definition_changes_container)
|
self._container_registry.addContainer(definition_changes_container)
|
||||||
container_stack.definitionChanges = definition_changes_container
|
container_stack.definitionChanges = definition_changes_container
|
||||||
@ -153,7 +155,7 @@ class MachineSettingsAction(MachineAction):
|
|||||||
if machine_manager.hasMaterials:
|
if machine_manager.hasMaterials:
|
||||||
extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
|
extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
|
||||||
if machine_manager.hasVariants:
|
if machine_manager.hasVariants:
|
||||||
extruder_variant_id = machine_manager.activeVariantIds[0]
|
extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]]
|
||||||
|
|
||||||
# Copy any settable_per_extruder setting value from the extruders to the global stack
|
# Copy any settable_per_extruder setting value from the extruders to the global stack
|
||||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
@ -251,7 +253,7 @@ class MachineSettingsAction(MachineAction):
|
|||||||
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
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"
|
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||||
|
|
||||||
material_container = self._global_container_stack.findContainer({"type": "material"})
|
material_container = self._global_container_stack.material
|
||||||
material_index = self._global_container_stack.getContainerIndex(material_container)
|
material_index = self._global_container_stack.getContainerIndex(material_container)
|
||||||
|
|
||||||
if has_materials:
|
if has_materials:
|
||||||
@ -272,7 +274,6 @@ class MachineSettingsAction(MachineAction):
|
|||||||
if "has_materials" in self._global_container_stack.getMetaData():
|
if "has_materials" in self._global_container_stack.getMetaData():
|
||||||
self._global_container_stack.removeMetaDataEntry("has_materials")
|
self._global_container_stack.removeMetaDataEntry("has_materials")
|
||||||
|
|
||||||
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
self._global_container_stack.replaceContainer(material_index, empty_material)
|
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.emit()
|
Application.getInstance().globalContainerStackChanged.emit()
|
||||||
|
@ -16,20 +16,14 @@ Cura.MachineAction
|
|||||||
property var extrudersModel: Cura.ExtrudersModel{}
|
property var extrudersModel: Cura.ExtrudersModel{}
|
||||||
property int extruderTabsCount: 0
|
property int extruderTabsCount: 0
|
||||||
|
|
||||||
Component.onCompleted:
|
Connections
|
||||||
{
|
{
|
||||||
// Populate extruder tabs after a short delay, because otherwise the tabs that are added when
|
target: base.extrudersModel
|
||||||
// the dialog is created are stuck.
|
onModelChanged:
|
||||||
extruderTabsCountDelay.start();
|
{
|
||||||
|
var extruderCount = base.extrudersModel.rowCount();
|
||||||
|
base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer
|
|
||||||
{
|
|
||||||
id: extruderTabsCountDelay
|
|
||||||
repeat: false
|
|
||||||
interval: 1
|
|
||||||
|
|
||||||
onTriggered: base.extruderTabsCount = (machineExtruderCountProvider.properties.value > 1) ? parseInt(machineExtruderCountProvider.properties.value) : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
@ -347,7 +341,6 @@ Cura.MachineAction
|
|||||||
sourceComponent: numericTextFieldWithUnit
|
sourceComponent: numericTextFieldWithUnit
|
||||||
property var propertyProvider: gantryHeightProvider
|
property var propertyProvider: gantryHeightProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||||
@ -375,14 +368,9 @@ Cura.MachineAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentIndex: machineExtruderCountProvider.properties.value - 1
|
currentIndex: machineExtruderCountProvider.properties.value - 1
|
||||||
Component.onCompleted:
|
|
||||||
{
|
|
||||||
manager.setMachineExtruderCount(1);
|
|
||||||
}
|
|
||||||
onActivated:
|
onActivated:
|
||||||
{
|
{
|
||||||
manager.setMachineExtruderCount(index + 1);
|
manager.setMachineExtruderCount(index + 1);
|
||||||
base.extruderTabsCount = (index > 0) ? index + 1 : 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +384,6 @@ Cura.MachineAction
|
|||||||
sourceComponent: numericTextFieldWithUnit
|
sourceComponent: numericTextFieldWithUnit
|
||||||
property var propertyProvider: materialDiameterProvider
|
property var propertyProvider: materialDiameterProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: false
|
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
@ -410,7 +397,6 @@ Cura.MachineAction
|
|||||||
sourceComponent: numericTextFieldWithUnit
|
sourceComponent: numericTextFieldWithUnit
|
||||||
property var propertyProvider: machineNozzleSizeProvider
|
property var propertyProvider: machineNozzleSizeProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -561,7 +547,6 @@ Cura.MachineAction
|
|||||||
sourceComponent: numericTextFieldWithUnit
|
sourceComponent: numericTextFieldWithUnit
|
||||||
property var propertyProvider: extruderNozzleSizeProvider
|
property var propertyProvider: extruderNozzleSizeProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
@ -575,6 +560,7 @@ Cura.MachineAction
|
|||||||
property var propertyProvider: extruderOffsetXProvider
|
property var propertyProvider: extruderOffsetXProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: true
|
property bool forceUpdateOnChange: true
|
||||||
|
property bool allowNegative: true
|
||||||
}
|
}
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
@ -587,6 +573,7 @@ Cura.MachineAction
|
|||||||
property var propertyProvider: extruderOffsetYProvider
|
property var propertyProvider: extruderOffsetYProvider
|
||||||
property string unit: catalog.i18nc("@label", "mm")
|
property string unit: catalog.i18nc("@label", "mm")
|
||||||
property bool forceUpdateOnChange: true
|
property bool forceUpdateOnChange: true
|
||||||
|
property bool allowNegative: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,17 +653,21 @@ Cura.MachineAction
|
|||||||
Item {
|
Item {
|
||||||
height: textField.height
|
height: textField.height
|
||||||
width: textField.width
|
width: textField.width
|
||||||
|
|
||||||
|
property bool _allowNegative: (typeof(allowNegative) === 'undefined') ? false : allowNegative
|
||||||
|
property bool _forceUpdateOnChange: (typeof(forceUpdateOnChange) === 'undefined') ? false: forceUpdateOnChange
|
||||||
|
|
||||||
TextField
|
TextField
|
||||||
{
|
{
|
||||||
id: textField
|
id: textField
|
||||||
text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
|
text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
|
||||||
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
|
validator: RegExpValidator { regExp: _allowNegative ? /-?[0-9\.]{0,6}/ : /[0-9\.]{0,6}/ }
|
||||||
onEditingFinished:
|
onEditingFinished:
|
||||||
{
|
{
|
||||||
if (propertyProvider && text != propertyProvider.properties.value)
|
if (propertyProvider && text != propertyProvider.properties.value)
|
||||||
{
|
{
|
||||||
propertyProvider.setPropertyValue("value", text);
|
propertyProvider.setPropertyValue("value", text);
|
||||||
if(forceUpdateOnChange)
|
if(_forceUpdateOnChange)
|
||||||
{
|
{
|
||||||
var extruderIndex = ExtruderManager.activeExtruderIndex;
|
var extruderIndex = ExtruderManager.activeExtruderIndex;
|
||||||
manager.forceUpdate();
|
manager.forceUpdate();
|
||||||
|
@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
|
|||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "machine_action": MachineSettingsAction.MachineSettingsAction() }
|
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"
|
||||||
|
}
|
@ -63,7 +63,8 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||||||
stack_nr = -1
|
stack_nr = -1
|
||||||
stack = None
|
stack = None
|
||||||
# Check from what stack we should copy the raw property of the setting from.
|
# Check from what stack we should copy the raw property of the setting from.
|
||||||
if definition.limit_to_extruder != "-1" and self._stack.getProperty("machine_extruder_count", "value") > 1:
|
if self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
|
if definition.limit_to_extruder != "-1":
|
||||||
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
||||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
||||||
|
|
||||||
@ -74,6 +75,8 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||||||
# Use the found stack number to get the right stack to copy the value from.
|
# Use the found stack number to get the right stack to copy the value from.
|
||||||
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
||||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||||
|
else:
|
||||||
|
stack = self._stack
|
||||||
|
|
||||||
# Use the raw property to set the value (so the inheritance doesn't break)
|
# Use the raw property to set the value (so the inheritance doesn't break)
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
|
@ -94,6 +94,8 @@ Item {
|
|||||||
return settingComboBox
|
return settingComboBox
|
||||||
case "extruder":
|
case "extruder":
|
||||||
return settingExtruder
|
return settingExtruder
|
||||||
|
case "optional_extruder":
|
||||||
|
return settingOptionalExtruder
|
||||||
case "bool":
|
case "bool":
|
||||||
return settingCheckBox
|
return settingCheckBox
|
||||||
case "str":
|
case "str":
|
||||||
@ -141,14 +143,6 @@ Item {
|
|||||||
storeIndex: 0
|
storeIndex: 0
|
||||||
removeUnusedValue: false
|
removeUnusedValue: false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the extruder by which the object needs to be printed is changed, ensure that the
|
|
||||||
// display is also notified of the fact.
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: extruderSelector
|
|
||||||
onActivated: provider.forcePropertiesChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,6 +344,13 @@ Item {
|
|||||||
Cura.SettingExtruder { }
|
Cura.SettingExtruder { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component
|
||||||
|
{
|
||||||
|
id: settingOptionalExtruder
|
||||||
|
|
||||||
|
Cura.SettingOptionalExtruder { }
|
||||||
|
}
|
||||||
|
|
||||||
Component
|
Component
|
||||||
{
|
{
|
||||||
id: settingCheckBox;
|
id: settingCheckBox;
|
||||||
|
@ -10,13 +10,6 @@ i18n_catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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": {
|
"tool": {
|
||||||
"name": i18n_catalog.i18nc("@label", "Per Model Settings"),
|
"name": i18n_catalog.i18nc("@label", "Per Model Settings"),
|
||||||
"description": i18n_catalog.i18nc("@info:tooltip", "Configure 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) 2015 Ultimaker B.V.
|
||||||
# Copyright (c) 2013 David Braam
|
# Copyright (c) 2013 David Braam
|
||||||
# Uranium is released under the terms of the AGPLv3 or higher.
|
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
|
||||||
catalog = i18nCatalog("cura")
|
|
||||||
|
|
||||||
from . import RemovableDrivePlugin
|
from . import RemovableDrivePlugin
|
||||||
|
|
||||||
import string
|
import string
|
||||||
import ctypes # type: ignore
|
import ctypes
|
||||||
from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work
|
from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
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
|
# WinAPI Constants that we need
|
||||||
# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
|
# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
|
||||||
DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]
|
DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]
|
||||||
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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):
|
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.
|
# Copyright (c) 2015 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from UM.Extension import Extension
|
from UM.Extension import Extension
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Platform import Platform
|
|
||||||
|
import time
|
||||||
|
|
||||||
from UM.Qt.Duration import DurationFormat
|
from UM.Qt.Duration import DurationFormat
|
||||||
from UM.Job import Job
|
|
||||||
|
from .SliceInfoJob import SliceInfoJob
|
||||||
|
|
||||||
import platform
|
import platform
|
||||||
import math
|
import math
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import ssl
|
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
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.
|
## 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
|
# 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).
|
# no model files are being sent (Just a SHA256 hash of the model).
|
||||||
class SliceInfo(Extension):
|
class SliceInfo(Extension):
|
||||||
info_url = "https://stats.youmagine.com/curastats/slice"
|
info_url = "http://stats.ultimaker.com/api/cura"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -87,59 +55,137 @@ class SliceInfo(Extension):
|
|||||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||||
return # Do nothing, user does not want to send data
|
return # Do nothing, user does not want to send data
|
||||||
|
|
||||||
# Listing all files placed on the buildplate
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
modelhashes = []
|
print_information = Application.getInstance().getPrintInformation()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
active_mode = Preferences.getInstance().getValue("cura/active_mode")
|
||||||
|
if active_mode == 0:
|
||||||
|
data["active_mode"] = "recommended"
|
||||||
|
else:
|
||||||
|
data["active_mode"] = "custom"
|
||||||
|
|
||||||
|
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()):
|
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
modelhashes.append(node.getMeshData().getHash())
|
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)
|
||||||
|
|
||||||
# Creating md5sums and formatting them as discussed on JIRA
|
model_settings = dict()
|
||||||
modelhash_formatted = ",".join(modelhashes)
|
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"))
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
# 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")
|
||||||
|
|
||||||
# Get total material used (in mm^3)
|
model_settings["wall_line_count"] = model_stack.getProperty("wall_line_count", "value")
|
||||||
print_information = Application.getInstance().getPrintInformation()
|
model_settings["retraction_enable"] = model_stack.getProperty("retraction_enable", "value")
|
||||||
material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
|
|
||||||
|
|
||||||
# Send material per extruder
|
# Infill settings
|
||||||
material_used = [str(math.pi * material_radius * material_radius * material_length) for material_length in print_information.materialLengths]
|
model_settings["infill_sparse_density"] = model_stack.getProperty("infill_sparse_density", "value")
|
||||||
material_used = ",".join(material_used)
|
model_settings["infill_pattern"] = model_stack.getProperty("infill_pattern", "value")
|
||||||
|
model_settings["gradual_infill_steps"] = model_stack.getProperty("gradual_infill_steps", "value")
|
||||||
|
|
||||||
containers = { "": global_container_stack.serialize() }
|
model["model_settings"] = model_settings
|
||||||
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)
|
|
||||||
|
|
||||||
# Bundle the collected data
|
data["models"].append(model)
|
||||||
submitted_data = {
|
|
||||||
"processor": platform.processor(),
|
print_times = print_information.printTimesPerFeature
|
||||||
"machine": platform.machine(),
|
data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)),
|
||||||
"platform": platform.platform(),
|
"support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)),
|
||||||
"settings": json.dumps(containers), # bundle of containers with their serialized contents
|
"infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)),
|
||||||
"version": Application.getInstance().getVersion(),
|
"total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))}
|
||||||
"modelhash": modelhash_formatted,
|
|
||||||
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
|
print_settings = dict()
|
||||||
"filament": material_used,
|
print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value")
|
||||||
"language": Preferences.getInstance().getValue("general/language"),
|
|
||||||
}
|
# 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
|
# Convert data to bytes
|
||||||
submitted_data = urllib.parse.urlencode(submitted_data)
|
binary_data = json.dumps(data).encode("utf-8")
|
||||||
binary_data = submitted_data.encode("utf-8")
|
|
||||||
|
|
||||||
# Sending slice info non-blocking
|
# Sending slice info non-blocking
|
||||||
reportJob = SliceInfoJob(self.info_url, binary_data)
|
reportJob = SliceInfoJob(self.info_url, binary_data)
|
||||||
reportJob.start()
|
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
|
# 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.
|
# (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():
|
def getMetaData():
|
||||||
return {
|
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):
|
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.Resources import Resources
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Preferences import Preferences
|
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.Settings.Validator import ValidatorState
|
||||||
from UM.Math.Color import Color
|
from UM.Math.Color import Color
|
||||||
from UM.View.GL.OpenGL import OpenGL
|
from UM.View.GL.OpenGL import OpenGL
|
||||||
@ -118,7 +118,7 @@ class SolidView(View):
|
|||||||
else:
|
else:
|
||||||
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
|
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
|
||||||
if node.callDecoration("isGroup") and Selection.isSelected(node):
|
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):
|
def endRendering(self):
|
||||||
pass
|
pass
|
||||||
|
@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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": {
|
"view": {
|
||||||
"name": i18n_catalog.i18nc("@item:inmenu", "Solid"),
|
"name": i18n_catalog.i18nc("@item:inmenu", "Solid"),
|
||||||
"weight": 0
|
"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"
|
||||||
|
}
|
@ -12,7 +12,6 @@ Cura.MachineAction
|
|||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
property var selectedPrinter: null
|
property var selectedPrinter: null
|
||||||
property bool completeProperties: true
|
property bool completeProperties: true
|
||||||
property var connectingToPrinter: null
|
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
@ -33,9 +32,8 @@ Cura.MachineAction
|
|||||||
if(base.selectedPrinter && base.completeProperties)
|
if(base.selectedPrinter && base.completeProperties)
|
||||||
{
|
{
|
||||||
var printerKey = base.selectedPrinter.getKey()
|
var printerKey = base.selectedPrinter.getKey()
|
||||||
if(connectingToPrinter != printerKey) {
|
if(manager.getStoredKey() != printerKey)
|
||||||
// prevent an infinite loop
|
{
|
||||||
connectingToPrinter = printerKey;
|
|
||||||
manager.setKey(printerKey);
|
manager.setKey(printerKey);
|
||||||
completed();
|
completed();
|
||||||
}
|
}
|
||||||
|
34
plugins/UM3NetworkPrinting/MonitorItem.qml
Normal file
34
plugins/UM3NetworkPrinting/MonitorItem.qml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import QtQuick 2.2
|
||||||
|
|
||||||
|
|
||||||
|
import UM 1.3 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
Component
|
||||||
|
{
|
||||||
|
Image
|
||||||
|
{
|
||||||
|
id: cameraImage
|
||||||
|
width: sourceSize.width
|
||||||
|
height: sourceSize.height * width / sourceSize.width
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onVisibleChanged:
|
||||||
|
{
|
||||||
|
if(visible)
|
||||||
|
{
|
||||||
|
OutputDevice.startCamera()
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
OutputDevice.stopCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
source:
|
||||||
|
{
|
||||||
|
if(OutputDevice.cameraImage)
|
||||||
|
{
|
||||||
|
return OutputDevice.cameraImage;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -178,6 +178,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
self._last_command = ""
|
self._last_command = ""
|
||||||
|
|
||||||
self._compressing_print = False
|
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")
|
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
|
||||||
if printer_type.startswith("9511"):
|
if printer_type.startswith("9511"):
|
||||||
@ -187,9 +188,18 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
else:
|
else:
|
||||||
self._updatePrinterType("unknown")
|
self._updatePrinterType("unknown")
|
||||||
|
|
||||||
|
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||||
|
|
||||||
def _onNetworkAccesibleChanged(self, accessible):
|
def _onNetworkAccesibleChanged(self, accessible):
|
||||||
Logger.log("d", "Network accessible state changed to: %s", 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):
|
def _onAuthenticationTimer(self):
|
||||||
self._authentication_counter += 1
|
self._authentication_counter += 1
|
||||||
self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100)
|
self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100)
|
||||||
@ -306,8 +316,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
def _stopCamera(self):
|
def _stopCamera(self):
|
||||||
self._camera_timer.stop()
|
self._camera_timer.stop()
|
||||||
if self._image_reply:
|
if self._image_reply:
|
||||||
|
try:
|
||||||
self._image_reply.abort()
|
self._image_reply.abort()
|
||||||
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
|
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_reply = None
|
||||||
self._image_request = None
|
self._image_request = None
|
||||||
|
|
||||||
@ -612,7 +625,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
# is ignored.
|
# is ignored.
|
||||||
# \param kwargs Keyword arguments.
|
# \param kwargs Keyword arguments.
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
|
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(
|
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)
|
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()
|
self._error_message.show()
|
||||||
@ -638,7 +651,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
|
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
|
||||||
Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
|
Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
|
||||||
self._error_message = Message(
|
self._error_message = Message(
|
||||||
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1)))
|
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1)))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
return
|
return
|
||||||
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
|
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
|
||||||
|
@ -6,15 +6,7 @@ from UM.i18n import i18nCatalog
|
|||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
|
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"
|
||||||
|
}
|
@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
values = winreg.EnumValue(key, i)
|
values = winreg.EnumValue(key, i)
|
||||||
if not only_list_usb or "USBSER" in values[0]:
|
if not only_list_usb or "USBSER" or "VCP" in values[0]:
|
||||||
base_list += [values[1]]
|
base_list += [values[1]]
|
||||||
i += 1
|
i += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -8,14 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
|||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
return {
|
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):
|
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"
|
||||||
|
}
|
65
plugins/UltimakerMachineActions/UM2UpgradeSelection.py
Normal file
65
plugins/UltimakerMachineActions/UM2UpgradeSelection.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from cura.MachineAction import MachineAction
|
||||||
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Util import parseBool
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
import UM.Settings.InstanceContainer
|
||||||
|
|
||||||
|
|
||||||
|
## The Ultimaker 2 can have a few revisions & upgrades.
|
||||||
|
class UM2UpgradeSelection(MachineAction):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
||||||
|
self._qml_url = "UM2UpgradeSelectionMachineAction.qml"
|
||||||
|
|
||||||
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
|
def _reset(self):
|
||||||
|
self.hasVariantsChanged.emit()
|
||||||
|
|
||||||
|
hasVariantsChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = hasVariantsChanged)
|
||||||
|
def hasVariants(self):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def setHasVariants(self, has_variants = True):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
variant_container = global_container_stack.variant
|
||||||
|
variant_index = global_container_stack.getContainerIndex(variant_container)
|
||||||
|
|
||||||
|
if has_variants:
|
||||||
|
if "has_variants" in global_container_stack.getMetaData():
|
||||||
|
global_container_stack.setMetaDataEntry("has_variants", True)
|
||||||
|
else:
|
||||||
|
global_container_stack.addMetaDataEntry("has_variants", True)
|
||||||
|
|
||||||
|
# Set the variant container to a sane default
|
||||||
|
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
if type(variant_container) == type(empty_container):
|
||||||
|
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
|
||||||
|
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
global_container_stack.variant = containers[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_variants" in global_container_stack.getMetaData():
|
||||||
|
global_container_stack.removeMetaDataEntry("has_variants")
|
||||||
|
|
||||||
|
# Set the variant container to an empty variant
|
||||||
|
global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
|
||||||
|
Application.getInstance().globalContainerStackChanged.emit()
|
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