mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-12 01:07:52 -06:00
Merge remote-tracking branch 'origin/master' into CL-1266_cloud_association_for_manual_ips
This commit is contained in:
commit
49cb3de562
1047 changed files with 52357 additions and 5122 deletions
|
@ -17,6 +17,7 @@ if(CURA_DEBUGMODE)
|
|||
set(_cura_debugmode "ON")
|
||||
endif()
|
||||
|
||||
set(CURA_APP_NAME "cura" CACHE STRING "Short name of Cura, used for configuration folder")
|
||||
set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura")
|
||||
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
|
||||
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
||||
|
|
19
contributing.md
Normal file
19
contributing.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
Submitting bug reports
|
||||
----------------------
|
||||
Please submit bug reports for all of Cura and CuraEngine to the [Cura repository](https://github.com/Ultimaker/Cura/issues). There will be a template there to fill in. Depending on the type of issue, we will usually ask for the [Cura log](Logging Issues) or a project file.
|
||||
|
||||
If a bug report would contain private information, such as a proprietary 3D model, you may also e-mail us. Ask for contact information in the issue.
|
||||
|
||||
Bugs related to supporting certain types of printers can usually not be solved by the Cura maintainers, since we don't have access to every 3D printer model in the world either. We have to rely on external contributors to fix this. If it's something simple and obvious, such as a mistake in the start g-code, then we can directly fix it for you, but e.g. issues with USB cable connectivity are impossible for us to debug.
|
||||
|
||||
Requesting features
|
||||
-------------------
|
||||
The issue template in the Cura repository does not apply to feature requests. You can ignore it.
|
||||
|
||||
When requesting a feature, please describe clearly what you need and why you think this is valuable to users or what problem it solves.
|
||||
|
||||
Making pull requests
|
||||
--------------------
|
||||
If you want to propose a change to Cura's source code, please create a pull request in the appropriate repository (being [Cura](https://github.com/Ultimaker/Cura), [Uranium](https://github.com/Ultimaker/Uranium), [CuraEngine](https://github.com/Ultimaker/CuraEngine), [fdm_materials](https://github.com/Ultimaker/fdm_materials), [libArcus](https://github.com/Ultimaker/libArcus), [cura-build](https://github.com/Ultimaker/cura-build), [cura-build-environment](https://github.com/Ultimaker/cura-build-environment), [libSavitar](https://github.com/Ultimaker/libSavitar), [libCharon](https://github.com/Ultimaker/libCharon) or [cura-binary-data](https://github.com/Ultimaker/cura-binary-data)) and if your change requires changes on multiple of these repositories, please link them together so that we know to merge them together.
|
||||
|
||||
Some of these repositories will have automated tests running when you create a pull request, indicated by green check marks or red crosses in the Github web page. If you see a red cross, that means that a test has failed. If the test doesn't fail on the Master branch but does fail on your branch, that indicates that you've probably made a mistake and you need to do that. Click on the cross for more details, or run the test locally by running `cmake . && ctest --verbose`.
|
|
@ -4,19 +4,31 @@
|
|||
# ---------
|
||||
# General constants used in Cura
|
||||
# ---------
|
||||
DEFAULT_CURA_APP_NAME = "cura"
|
||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||
DEFAULT_CURA_VERSION = "master"
|
||||
DEFAULT_CURA_BUILD_TYPE = ""
|
||||
DEFAULT_CURA_DEBUG_MODE = False
|
||||
DEFAULT_CURA_SDK_VERSION = "6.0.0"
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraAppName # type: ignore
|
||||
if CuraAppName == "":
|
||||
CuraAppName = DEFAULT_CURA_APP_NAME
|
||||
except ImportError:
|
||||
CuraAppName = DEFAULT_CURA_APP_NAME
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraAppDisplayName # type: ignore
|
||||
if CuraAppDisplayName == "":
|
||||
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
||||
except ImportError:
|
||||
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraVersion # type: ignore
|
||||
if CuraVersion == "":
|
||||
CuraVersion = DEFAULT_CURA_VERSION
|
||||
except ImportError:
|
||||
CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value]
|
||||
|
||||
|
@ -32,5 +44,7 @@ except ImportError:
|
|||
|
||||
try:
|
||||
from cura.CuraVersion import CuraSDKVersion # type: ignore
|
||||
if CuraSDKVersion == "":
|
||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
||||
except ImportError:
|
||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
||||
|
|
|
@ -217,11 +217,6 @@ class Arrange:
|
|||
prio_slice = self._priority[min_y:max_y, min_x:max_x]
|
||||
prio_slice[new_occupied] = 999
|
||||
|
||||
# If you want to see how the rasterized arranger build plate looks like, uncomment this code
|
||||
# numpy.set_printoptions(linewidth=500, edgeitems=200)
|
||||
# print(self._occupied.shape)
|
||||
# print(self._occupied)
|
||||
|
||||
@property
|
||||
def isEmpty(self):
|
||||
return self._is_empty
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
|
@ -48,7 +48,6 @@ class ArrangeArray:
|
|||
return self._count
|
||||
|
||||
def get(self, index):
|
||||
print(self._arrange)
|
||||
return self._arrange[index]
|
||||
|
||||
def getFirstEmpty(self):
|
||||
|
|
|
@ -740,13 +740,17 @@ class BuildVolume(SceneNode):
|
|||
prime_tower_collision = False
|
||||
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
|
||||
for extruder_id in prime_tower_areas:
|
||||
for prime_tower_area in prime_tower_areas[extruder_id]:
|
||||
for i_area, prime_tower_area in enumerate(prime_tower_areas[extruder_id]):
|
||||
for area in result_areas[extruder_id]:
|
||||
if prime_tower_area.intersectsPolygon(area) is not None:
|
||||
prime_tower_collision = True
|
||||
break
|
||||
if prime_tower_collision: #Already found a collision.
|
||||
break
|
||||
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
|
||||
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
|
||||
prime_tower_areas[extruder_id][i_area] = prime_tower_area.getMinkowskiHull(
|
||||
Polygon.approximatedCircle(disallowed_border_size))
|
||||
if not prime_tower_collision:
|
||||
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||
|
@ -786,6 +790,16 @@ class BuildVolume(SceneNode):
|
|||
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
|
||||
prime_tower_y = prime_tower_y + machine_depth / 2
|
||||
|
||||
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
|
||||
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
|
||||
brim_size = (
|
||||
extruder.getProperty("brim_line_count", "value") *
|
||||
extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
|
||||
extruder.getProperty("initial_layer_line_width_factor", "value")
|
||||
)
|
||||
prime_tower_x -= brim_size
|
||||
prime_tower_y += brim_size
|
||||
|
||||
if self._global_container_stack.getProperty("prime_tower_circular", "value"):
|
||||
radius = prime_tower_size / 2
|
||||
prime_tower_area = Polygon.approximatedCircle(radius)
|
||||
|
@ -1025,7 +1039,9 @@ class BuildVolume(SceneNode):
|
|||
# We don't create an additional line for the extruder we're printing the skirt with.
|
||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
||||
|
||||
elif adhesion_type == "brim":
|
||||
elif (adhesion_type == "brim" or
|
||||
(self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and
|
||||
self._global_container_stack.getProperty("adhesion_type", "value") != "raft")):
|
||||
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
|
||||
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
|
||||
|
||||
|
@ -1084,7 +1100,7 @@ class BuildVolume(SceneNode):
|
|||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
|
||||
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
|
||||
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
|
||||
_tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
||||
_tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]
|
||||
_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", "travel_avoid_supports"]
|
||||
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List
|
||||
|
||||
import numpy
|
||||
|
||||
|
@ -38,10 +38,8 @@ from UM.Settings.Validator import Validator
|
|||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||
from UM.Decorators import deprecated
|
||||
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||
|
||||
|
@ -136,7 +134,7 @@ class CuraApplication(QtApplication):
|
|||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||
# changes of the settings.
|
||||
SettingVersion = 6
|
||||
SettingVersion = 7
|
||||
|
||||
Created = False
|
||||
|
||||
|
@ -156,7 +154,7 @@ class CuraApplication(QtApplication):
|
|||
Q_ENUMS(ResourceTypes)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(name = "cura",
|
||||
super().__init__(name = ApplicationMetadata.CuraAppName,
|
||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||
version = ApplicationMetadata.CuraVersion,
|
||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||
|
@ -309,7 +307,8 @@ class CuraApplication(QtApplication):
|
|||
super().initialize()
|
||||
|
||||
self.__sendCommandToSingleInstance()
|
||||
self.__initializeSettingDefinitionsAndFunctions()
|
||||
self._initializeSettingDefinitions()
|
||||
self._initializeSettingFunctions()
|
||||
self.__addAllResourcesAndContainerResources()
|
||||
self.__addAllEmptyContainers()
|
||||
self.__setLatestResouceVersionsForVersionUpgrade()
|
||||
|
@ -338,31 +337,40 @@ class CuraApplication(QtApplication):
|
|||
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
|
||||
Resources.addSearchPath(resource_path)
|
||||
|
||||
# Adds custom property types, settings types, and extra operators (functions) that need to be registered in
|
||||
# SettingDefinition and SettingFunction.
|
||||
def __initializeSettingDefinitionsAndFunctions(self):
|
||||
self._cura_formula_functions = CuraFormulaFunctions(self)
|
||||
|
||||
@classmethod
|
||||
def _initializeSettingDefinitions(cls):
|
||||
# Need to do this before ContainerRegistry tries to load the machines
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default=True,
|
||||
read_only=True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default=True,
|
||||
read_only=True)
|
||||
# this setting can be changed for each group in one-at-a-time mode
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default=True,
|
||||
read_only=True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default=True,
|
||||
read_only=True)
|
||||
|
||||
# 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:
|
||||
# which extruder is the only extruder this setting is obtained from
|
||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1", depends_on = "value")
|
||||
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:
|
||||
# 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("optional_extruder", None, str, None)
|
||||
SettingDefinition.addSettingType("[int]", None, str, None)
|
||||
|
||||
|
||||
# Adds custom property types, settings types, and extra operators (functions) that need to be registered in
|
||||
# SettingDefinition and SettingFunction.
|
||||
def _initializeSettingFunctions(self):
|
||||
self._cura_formula_functions = CuraFormulaFunctions(self)
|
||||
|
||||
SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder)
|
||||
SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
|
||||
SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
|
||||
|
@ -435,6 +443,7 @@ class CuraApplication(QtApplication):
|
|||
def startSplashWindowPhase(self) -> None:
|
||||
super().startSplashWindowPhase()
|
||||
|
||||
if not self.getIsHeadLess():
|
||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||
|
||||
self.setRequiredPlugins([
|
||||
|
@ -666,7 +675,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
## Handle loading of all plugin types (and the backend explicitly)
|
||||
# \sa PluginRegistry
|
||||
def _loadPlugins(self):
|
||||
def _loadPlugins(self) -> None:
|
||||
self._plugin_registry.addType("profile_reader", self._addProfileReader)
|
||||
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
|
||||
|
||||
|
@ -1107,88 +1116,6 @@ class CuraApplication(QtApplication):
|
|||
self._platform_activity = True if count > 0 else False
|
||||
self.activityChanged.emit()
|
||||
|
||||
# Remove all selected objects from the scene.
|
||||
@pyqtSlot()
|
||||
@deprecated("Moved to CuraActions", "2.6")
|
||||
def deleteSelection(self):
|
||||
if not self.getController().getToolsEnabled():
|
||||
return
|
||||
removed_group_nodes = []
|
||||
op = GroupedOperation()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
for node in nodes:
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
group_node = node.getParent()
|
||||
if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
|
||||
remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
|
||||
if len(remaining_nodes_in_group) == 1:
|
||||
removed_group_nodes.append(group_node)
|
||||
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
op.push()
|
||||
|
||||
## Remove an object from the scene.
|
||||
# Note that this only removes an object if it is selected.
|
||||
@pyqtSlot("quint64")
|
||||
@deprecated("Use deleteSelection instead", "2.6")
|
||||
def deleteObject(self, object_id):
|
||||
if not self.getController().getToolsEnabled():
|
||||
return
|
||||
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
|
||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
if node:
|
||||
op = GroupedOperation()
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
|
||||
group_node = node.getParent()
|
||||
if group_node:
|
||||
# Note that at this point the node has not yet been deleted
|
||||
if len(group_node.getChildren()) <= 2 and group_node.callDecoration("isGroup"):
|
||||
op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
|
||||
op.push()
|
||||
|
||||
## Create a number of copies of existing object.
|
||||
# \param object_id
|
||||
# \param count number of copies
|
||||
# \param min_offset minimum offset to other objects.
|
||||
@pyqtSlot("quint64", int)
|
||||
@deprecated("Use CuraActions::multiplySelection", "2.6")
|
||||
def multiplyObject(self, object_id, count, min_offset = 8):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
if not node:
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
while node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
node = node.getParent()
|
||||
|
||||
job = MultiplyObjectsJob([node], count, min_offset)
|
||||
job.start()
|
||||
return
|
||||
|
||||
## Center object on platform.
|
||||
@pyqtSlot("quint64")
|
||||
@deprecated("Use CuraActions::centerSelection", "2.6")
|
||||
def centerObject(self, object_id):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
if not node:
|
||||
return
|
||||
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
node = node.getParent()
|
||||
|
||||
if node:
|
||||
op = SetTransformOperation(node, Vector())
|
||||
op.push()
|
||||
|
||||
## Select all nodes containing mesh data in the scene.
|
||||
@pyqtSlot()
|
||||
def selectAll(self):
|
||||
|
@ -1268,61 +1195,74 @@ class CuraApplication(QtApplication):
|
|||
|
||||
## Arrange all objects.
|
||||
@pyqtSlot()
|
||||
def arrangeObjectsToAllBuildPlates(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
def arrangeObjectsToAllBuildPlates(self) -> None:
|
||||
nodes_to_arrange = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, SceneNode):
|
||||
continue
|
||||
|
||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||
continue # Node that doesnt have a mesh and is not a group.
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||
|
||||
parent_node = node.getParent()
|
||||
if parent_node and parent_node.callDecoration("isGroup"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
|
||||
|
||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
||||
continue # i.e. node with layer data
|
||||
|
||||
bounding_box = node.getBoundingBox()
|
||||
# Skip nodes that are too big
|
||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||
nodes.append(node)
|
||||
job = ArrangeObjectsAllBuildPlatesJob(nodes)
|
||||
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
||||
nodes_to_arrange.append(node)
|
||||
job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
|
||||
job.start()
|
||||
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
|
||||
|
||||
# Single build plate
|
||||
@pyqtSlot()
|
||||
def arrangeAll(self):
|
||||
nodes = []
|
||||
def arrangeAll(self) -> None:
|
||||
nodes_to_arrange = []
|
||||
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, SceneNode):
|
||||
continue
|
||||
|
||||
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
||||
continue # Node that doesnt have a mesh and is not a group.
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
|
||||
parent_node = node.getParent()
|
||||
if parent_node and parent_node.callDecoration("isGroup"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||
|
||||
if not node.isSelectable():
|
||||
continue # i.e. node with layer data
|
||||
|
||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
||||
continue # i.e. node with layer data
|
||||
|
||||
if node.callDecoration("getBuildPlateNumber") == active_build_plate:
|
||||
# Skip nodes that are too big
|
||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||
nodes.append(node)
|
||||
self.arrange(nodes, fixed_nodes = [])
|
||||
bounding_box = node.getBoundingBox()
|
||||
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
||||
nodes_to_arrange.append(node)
|
||||
self.arrange(nodes_to_arrange, fixed_nodes = [])
|
||||
|
||||
## Arrange a set of nodes given a set of fixed nodes
|
||||
# \param nodes nodes that we have to place
|
||||
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes
|
||||
def arrange(self, nodes, fixed_nodes):
|
||||
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
|
||||
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
|
||||
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
|
||||
job.start()
|
||||
|
||||
## Reload all mesh data on the screen from file.
|
||||
@pyqtSlot()
|
||||
def reloadAll(self):
|
||||
def reloadAll(self) -> None:
|
||||
Logger.log("i", "Reloading all loaded mesh data.")
|
||||
nodes = []
|
||||
has_merged_nodes = False
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
|
||||
if node.getName() == "MergedMesh":
|
||||
has_merged_nodes = True
|
||||
|
@ -1337,7 +1277,7 @@ class CuraApplication(QtApplication):
|
|||
file_name = node.getMeshData().getFileName()
|
||||
if file_name:
|
||||
job = ReadMeshJob(file_name)
|
||||
job._node = node
|
||||
job._node = node # type: ignore
|
||||
job.finished.connect(self._reloadMeshFinished)
|
||||
if has_merged_nodes:
|
||||
job.finished.connect(self.updateOriginOfMergedMeshes)
|
||||
|
@ -1346,20 +1286,8 @@ class CuraApplication(QtApplication):
|
|||
else:
|
||||
Logger.log("w", "Unable to reload data because we don't have a filename.")
|
||||
|
||||
|
||||
## Get logging data of the backend engine
|
||||
# \returns \type{string} Logging data
|
||||
@pyqtSlot(result = str)
|
||||
def getEngineLog(self):
|
||||
log = ""
|
||||
|
||||
for entry in self.getBackend().getLog():
|
||||
log += entry.decode()
|
||||
|
||||
return log
|
||||
|
||||
@pyqtSlot("QStringList")
|
||||
def setExpandedCategories(self, categories):
|
||||
def setExpandedCategories(self, categories: List[str]) -> None:
|
||||
categories = list(set(categories))
|
||||
categories.sort()
|
||||
joined = ";".join(categories)
|
||||
|
@ -1370,7 +1298,7 @@ class CuraApplication(QtApplication):
|
|||
expandedCategoriesChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty("QStringList", notify = expandedCategoriesChanged)
|
||||
def expandedCategories(self):
|
||||
def expandedCategories(self) -> List[str]:
|
||||
return self.getPreferences().getValue("cura/categories_expanded").split(";")
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -1420,13 +1348,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
|
||||
## Updates origin position of all merged meshes
|
||||
# \param jobNode \type{Job} empty object which passed which is required by JobQueue
|
||||
def updateOriginOfMergedMeshes(self, jobNode):
|
||||
def updateOriginOfMergedMeshes(self, _):
|
||||
group_nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
|
||||
|
||||
#checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
|
||||
# Checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
|
||||
for decorator in node.getDecorators():
|
||||
if isinstance(decorator, GroupDecorator):
|
||||
group_nodes.append(node)
|
||||
|
@ -1470,7 +1397,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
|
||||
@pyqtSlot()
|
||||
def groupSelected(self):
|
||||
def groupSelected(self) -> None:
|
||||
# Create a group-node
|
||||
group_node = CuraSceneNode()
|
||||
group_decorator = GroupDecorator()
|
||||
|
@ -1486,7 +1413,8 @@ class CuraApplication(QtApplication):
|
|||
# Remove nodes that are directly parented to another selected node from the selection so they remain parented
|
||||
selected_nodes = Selection.getAllSelectedObjects().copy()
|
||||
for node in selected_nodes:
|
||||
if node.getParent() in selected_nodes and not node.getParent().callDecoration("isGroup"):
|
||||
parent = node.getParent()
|
||||
if parent is not None and parent in selected_nodes and not parent.callDecoration("isGroup"):
|
||||
Selection.remove(node)
|
||||
|
||||
# Move selected nodes into the group-node
|
||||
|
@ -1498,7 +1426,7 @@ class CuraApplication(QtApplication):
|
|||
Selection.add(group_node)
|
||||
|
||||
@pyqtSlot()
|
||||
def ungroupSelected(self):
|
||||
def ungroupSelected(self) -> None:
|
||||
selected_objects = Selection.getAllSelectedObjects().copy()
|
||||
for node in selected_objects:
|
||||
if node.callDecoration("isGroup"):
|
||||
|
@ -1521,7 +1449,7 @@ class CuraApplication(QtApplication):
|
|||
# Note: The group removes itself from the scene once all its children have left it,
|
||||
# see GroupDecorator._onChildrenChanged
|
||||
|
||||
def _createSplashScreen(self):
|
||||
def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
|
||||
if self._is_headless:
|
||||
return None
|
||||
return CuraSplashScreen.CuraSplashScreen()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
CuraAppName = "@CURA_APP_NAME@"
|
||||
CuraAppDisplayName = "@CURA_APP_DISPLAY_NAME@"
|
||||
CuraVersion = "@CURA_VERSION@"
|
||||
CuraBuildType = "@CURA_BUILDTYPE@"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, QTimer
|
||||
from PyQt5.QtCore import pyqtProperty, Qt, QTimer
|
||||
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
|
|
|
@ -7,43 +7,36 @@ from UM.Mesh.MeshBuilder import MeshBuilder
|
|||
from .LayerData import LayerData
|
||||
|
||||
import numpy
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
## Builder class for constructing a LayerData object
|
||||
class LayerDataBuilder(MeshBuilder):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._layers = {}
|
||||
self._element_counts = {}
|
||||
self._layers = {} # type: Dict[int, Layer]
|
||||
self._element_counts = {} # type: Dict[int, int]
|
||||
|
||||
def addLayer(self, layer):
|
||||
def addLayer(self, layer: int) -> None:
|
||||
if layer not in self._layers:
|
||||
self._layers[layer] = Layer(layer)
|
||||
|
||||
def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate):
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
def getLayer(self, layer: int) -> Optional[Layer]:
|
||||
return self._layers.get(layer)
|
||||
|
||||
p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate)
|
||||
self._layers[layer].polygons.append(p)
|
||||
|
||||
def getLayer(self, layer):
|
||||
if layer in self._layers:
|
||||
return self._layers[layer]
|
||||
|
||||
def getLayers(self):
|
||||
def getLayers(self) -> Dict[int, Layer]:
|
||||
return self._layers
|
||||
|
||||
def getElementCounts(self):
|
||||
def getElementCounts(self) -> Dict[int, int]:
|
||||
return self._element_counts
|
||||
|
||||
def setLayerHeight(self, layer, height):
|
||||
def setLayerHeight(self, layer: int, height: float) -> None:
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
self._layers[layer].setHeight(height)
|
||||
|
||||
def setLayerThickness(self, layer, thickness):
|
||||
def setLayerThickness(self, layer: int, thickness: float) -> None:
|
||||
if layer not in self._layers:
|
||||
self.addLayer(layer)
|
||||
|
||||
|
@ -71,7 +64,7 @@ class LayerDataBuilder(MeshBuilder):
|
|||
vertex_offset = 0
|
||||
index_offset = 0
|
||||
for layer, data in sorted(self._layers.items()):
|
||||
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
|
||||
vertex_offset, index_offset = data.build(vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
|
||||
self._element_counts[layer] = data.elementCount
|
||||
|
||||
self.addVertices(vertices)
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
|
||||
from cura.LayerData import LayerData
|
||||
|
||||
|
||||
## Simple decorator to indicate a scene node holds layer data.
|
||||
class LayerDataDecorator(SceneNodeDecorator):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._layer_data = None
|
||||
self._layer_data = None # type: Optional[LayerData]
|
||||
|
||||
def getLayerData(self):
|
||||
def getLayerData(self) -> Optional["LayerData"]:
|
||||
return self._layer_data
|
||||
|
||||
def setLayerData(self, layer_data):
|
||||
def setLayerData(self, layer_data: LayerData) -> None:
|
||||
self._layer_data = layer_data
|
||||
|
||||
def __deepcopy__(self, memo) -> "LayerDataDecorator":
|
||||
copied_decorator = LayerDataDecorator()
|
||||
copied_decorator._layer_data = self._layer_data
|
||||
return copied_decorator
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
import numpy
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
class LayerPolygon:
|
||||
NoneType = 0
|
||||
|
@ -18,22 +20,24 @@ class LayerPolygon:
|
|||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
SupportInterfaceType = 10
|
||||
__number_of_types = 11
|
||||
PrimeTower = 11
|
||||
__number_of_types = 12
|
||||
|
||||
__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
|
||||
# \param extruder
|
||||
# \param extruder The position of the extruder
|
||||
# \param line_types array with line_types
|
||||
# \param data new_points
|
||||
# \param line_widths array with line widths
|
||||
# \param line_thicknesses: array with type as index and thickness as value
|
||||
# \param line_feedrates array with line feedrates
|
||||
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates):
|
||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||
self._extruder = extruder
|
||||
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.
|
||||
Logger.log("w", "Found an unknown line type: %s", i)
|
||||
self._types[i] = self.NoneType
|
||||
self._data = data
|
||||
self._line_widths = line_widths
|
||||
|
@ -53,16 +57,16 @@ class LayerPolygon:
|
|||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save alot of memory usage.
|
||||
self._color_map = LayerPolygon.getColorMap()
|
||||
self._colors = self._color_map[self._types]
|
||||
self._colors = self._color_map[self._types] # type: numpy.ndarray
|
||||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool)
|
||||
|
||||
self._build_cache_line_mesh_mask = None
|
||||
self._build_cache_needed_points = None
|
||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||
|
||||
def buildCache(self):
|
||||
def buildCache(self) -> None:
|
||||
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
||||
self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype=bool)
|
||||
mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
|
||||
|
@ -90,10 +94,14 @@ class LayerPolygon:
|
|||
# \param extruders : vertex numpy array to be filled
|
||||
# \param line_types : vertex numpy array to be filled
|
||||
# \param indices : index numpy array to be filled
|
||||
def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
|
||||
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
|
||||
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
|
||||
self.buildCache()
|
||||
|
||||
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
|
||||
Logger.log("w", "Failed to build cache for layer polygon")
|
||||
return
|
||||
|
||||
line_mesh_mask = self._build_cache_line_mesh_mask
|
||||
needed_points_list = self._build_cache_needed_points
|
||||
|
||||
|
@ -236,7 +244,8 @@ class LayerPolygon:
|
|||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_support_interface").getRgbF() # SupportInterfaceType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_prime_tower").getRgbF()
|
||||
])
|
||||
|
||||
return cls.__color_map
|
||||
|
|
|
@ -219,7 +219,7 @@ class MaterialManager(QObject):
|
|||
|
||||
root_material_id = material_metadata["base_file"]
|
||||
definition = material_metadata["definition"]
|
||||
approximate_diameter = material_metadata["approximate_diameter"]
|
||||
approximate_diameter = str(material_metadata["approximate_diameter"])
|
||||
|
||||
if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||
self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {}
|
||||
|
@ -332,7 +332,6 @@ class MaterialManager(QObject):
|
|||
buildplate_node = nozzle_node.getChildNode(buildplate_name)
|
||||
|
||||
nodes_to_check = [buildplate_node, nozzle_node, machine_node, default_machine_node]
|
||||
|
||||
# Fallback mechanism of finding materials:
|
||||
# 1. buildplate-specific material
|
||||
# 2. nozzle-specific material
|
||||
|
@ -537,16 +536,40 @@ class MaterialManager(QObject):
|
|||
return
|
||||
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
# Sort all nodes with respect to the container ID lengths in the ascending order so the base material container
|
||||
# will be the first one to be removed. We need to do this to ensure that all containers get loaded & deleted.
|
||||
nodes_to_remove = sorted(nodes_to_remove, key = lambda x: len(x.getMetaDataEntry("id", "")))
|
||||
# Try to load all containers first. If there is any faulty ones, they will be put into the faulty container
|
||||
# list, so removeContainer() can ignore those ones.
|
||||
for node in nodes_to_remove:
|
||||
container_id = node.getMetaDataEntry("id", "")
|
||||
results = self._container_registry.findContainers(id = container_id)
|
||||
if not results:
|
||||
self._container_registry.addWrongContainerId(container_id)
|
||||
for node in nodes_to_remove:
|
||||
self._container_registry.removeContainer(node.getMetaDataEntry("id", ""))
|
||||
|
||||
#
|
||||
# Methods for GUI
|
||||
#
|
||||
@pyqtSlot("QVariant", result=bool)
|
||||
def canMaterialBeRemoved(self, material_node: "MaterialNode"):
|
||||
# Check if the material is active in any extruder train. In that case, the material shouldn't be removed!
|
||||
# In the future we might enable this again, but right now, it's causing a ton of issues if we do (since it
|
||||
# corrupts the configuration)
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
return False
|
||||
|
||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||
ids_to_remove = [node.getMetaDataEntry("id", "") for node in nodes_to_remove]
|
||||
|
||||
for extruder_stack in self._container_registry.findContainerStacks(type="extruder_train"):
|
||||
if extruder_stack.material.getId() in ids_to_remove:
|
||||
return False
|
||||
return True
|
||||
|
||||
#
|
||||
# Sets the new name for the given material.
|
||||
#
|
||||
@pyqtSlot("QVariant", str)
|
||||
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
|
@ -649,10 +672,9 @@ class MaterialManager(QObject):
|
|||
extruder_stack = machine_manager.activeStack
|
||||
|
||||
machine_definition = self._application.getGlobalContainerStack().definition
|
||||
preferred_material = machine_definition.getMetaDataEntry("preferred_material")
|
||||
root_material_id = machine_definition.getMetaDataEntry("preferred_material", default = "generic_pla")
|
||||
|
||||
approximate_diameter = str(extruder_stack.approximateMaterialDiameter)
|
||||
root_material_id = preferred_material if preferred_material else "generic_pla"
|
||||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ class BaseMaterialsModel(ListModel):
|
|||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
|
|
@ -209,6 +209,7 @@ class QualityManager(QObject):
|
|||
# (1) the machine-specific node
|
||||
# (2) the generic node
|
||||
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||
|
||||
# Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
|
||||
# qualities, we should not fall back to use the global qualities.
|
||||
has_extruder_specific_qualities = False
|
||||
|
@ -441,7 +442,8 @@ class QualityManager(QObject):
|
|||
quality_changes_group = quality_model_item["quality_changes_group"]
|
||||
if quality_changes_group is None:
|
||||
# create global quality changes only
|
||||
new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
|
||||
new_name = self._container_registry.uniqueName(quality_changes_name)
|
||||
new_quality_changes = self._createQualityChanges(quality_group.quality_type, new_name,
|
||||
global_stack, None)
|
||||
self._container_registry.addContainer(new_quality_changes)
|
||||
else:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
@ -9,29 +9,29 @@ from typing import Optional
|
|||
|
||||
import requests
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
|
||||
from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
|
||||
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
|
||||
# Class containing several helpers to deal with the authorization flow.
|
||||
## Class containing several helpers to deal with the authorization flow.
|
||||
class AuthorizationHelpers:
|
||||
def __init__(self, settings: "OAuth2Settings") -> None:
|
||||
self._settings = settings
|
||||
self._token_url = "{}/token".format(self._settings.OAUTH_SERVER_URL)
|
||||
|
||||
@property
|
||||
# The OAuth2 settings object.
|
||||
## The OAuth2 settings object.
|
||||
def settings(self) -> "OAuth2Settings":
|
||||
return self._settings
|
||||
|
||||
# Request the access token from the authorization server.
|
||||
## Request the access token from the authorization server.
|
||||
# \param authorization_code: The authorization code from the 1st step.
|
||||
# \param verification_code: The verification code needed for the PKCE extension.
|
||||
# \return: An AuthenticationResponse object.
|
||||
# \param verification_code: The verification code needed for the PKCE
|
||||
# extension.
|
||||
# \return An AuthenticationResponse object.
|
||||
def getAccessTokenUsingAuthorizationCode(self, authorization_code: str, verification_code: str) -> "AuthenticationResponse":
|
||||
data = {
|
||||
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
||||
|
@ -46,9 +46,9 @@ class AuthorizationHelpers:
|
|||
except requests.exceptions.ConnectionError:
|
||||
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
||||
|
||||
# Request the access token from the authorization server using a refresh token.
|
||||
## Request the access token from the authorization server using a refresh token.
|
||||
# \param refresh_token:
|
||||
# \return: An AuthenticationResponse object.
|
||||
# \return An AuthenticationResponse object.
|
||||
def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
|
||||
data = {
|
||||
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
||||
|
@ -63,9 +63,9 @@ class AuthorizationHelpers:
|
|||
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
||||
|
||||
@staticmethod
|
||||
# Parse the token response from the authorization server into an AuthenticationResponse object.
|
||||
## Parse the token response from the authorization server into an AuthenticationResponse object.
|
||||
# \param token_response: The JSON string data response from the authorization server.
|
||||
# \return: An AuthenticationResponse object.
|
||||
# \return An AuthenticationResponse object.
|
||||
def parseTokenResponse(token_response: requests.models.Response) -> "AuthenticationResponse":
|
||||
token_data = None
|
||||
|
||||
|
@ -75,7 +75,7 @@ class AuthorizationHelpers:
|
|||
Logger.log("w", "Could not parse token response data: %s", token_response.text)
|
||||
|
||||
if not token_data:
|
||||
return AuthenticationResponse(success=False, err_message="Could not read response.")
|
||||
return AuthenticationResponse(success = False, err_message = catalog.i18nc("@message", "Could not read response."))
|
||||
|
||||
if token_response.status_code not in (200, 201):
|
||||
return AuthenticationResponse(success = False, err_message = token_data["error_description"])
|
||||
|
@ -88,9 +88,9 @@ class AuthorizationHelpers:
|
|||
scope=token_data["scope"],
|
||||
received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
|
||||
|
||||
# Calls the authentication API endpoint to get the token data.
|
||||
## Calls the authentication API endpoint to get the token data.
|
||||
# \param access_token: The encoded JWT token.
|
||||
# \return: Dict containing some profile data.
|
||||
# \return Dict containing some profile data.
|
||||
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
|
||||
try:
|
||||
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
|
||||
|
@ -114,15 +114,15 @@ class AuthorizationHelpers:
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
# Generate a 16-character verification code.
|
||||
## Generate a 16-character verification code.
|
||||
# \param code_length: How long should the code be?
|
||||
def generateVerificationCode(code_length: int = 16) -> str:
|
||||
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
|
||||
|
||||
@staticmethod
|
||||
# Generates a base64 encoded sha512 encrypted version of a given string.
|
||||
## Generates a base64 encoded sha512 encrypted version of a given string.
|
||||
# \param verification_code:
|
||||
# \return: The encrypted code in base64 format.
|
||||
# \return The encrypted code in base64 format.
|
||||
def generateVerificationCodeChallenge(verification_code: str) -> str:
|
||||
encoded = sha512(verification_code.encode()).digest()
|
||||
return b64encode(encoded, altchars = b"_-").decode()
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, Callable, Tuple, Dict, Any, List, TYPE_CHECKING
|
||||
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from typing import Optional, Callable, Tuple, Dict, Any, List, TYPE_CHECKING
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from cura.OAuth2.Models import AuthenticationResponse, ResponseData, HTTP_STATUS
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.OAuth2.Models import ResponseStatus
|
||||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
# This handler handles all HTTP requests on the local web server.
|
||||
## This handler handles all HTTP requests on the local web server.
|
||||
# It also requests the access token for the 2nd stage of the OAuth flow.
|
||||
class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||
def __init__(self, request, client_address, server) -> None:
|
||||
|
@ -47,9 +49,9 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
|||
# This will cause the server to shut down, so we do it at the very end of the request handling.
|
||||
self.authorization_callback(token_response)
|
||||
|
||||
# Handler for the callback URL redirect.
|
||||
# \param query: Dict containing the HTTP query parameters.
|
||||
# \return: HTTP ResponseData containing a success page to show to the user.
|
||||
## Handler for the callback URL redirect.
|
||||
# \param query Dict containing the HTTP query parameters.
|
||||
# \return HTTP ResponseData containing a success page to show to the user.
|
||||
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
|
||||
code = self._queryGet(query, "code")
|
||||
if code and self.authorization_helpers is not None and self.verification_code is not None:
|
||||
|
@ -61,14 +63,14 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
|||
# Otherwise we show an error message (probably the user clicked "Deny" in the auth dialog).
|
||||
token_response = AuthenticationResponse(
|
||||
success = False,
|
||||
err_message="Please give the required permissions when authorizing this application."
|
||||
err_message = catalog.i18nc("@message", "Please give the required permissions when authorizing this application.")
|
||||
)
|
||||
|
||||
else:
|
||||
# We don't know what went wrong here, so instruct the user to check the logs.
|
||||
token_response = AuthenticationResponse(
|
||||
success = False,
|
||||
error_message="Something unexpected happened when trying to log in, please try again."
|
||||
error_message = catalog.i18nc("@message", "Something unexpected happened when trying to log in, please try again.")
|
||||
)
|
||||
if self.authorization_helpers is None:
|
||||
return ResponseData(), token_response
|
||||
|
@ -80,8 +82,8 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
|||
self.authorization_helpers.settings.AUTH_FAILED_REDIRECT
|
||||
), token_response
|
||||
|
||||
## Handle all other non-existing server calls.
|
||||
@staticmethod
|
||||
# Handle all other non-existing server calls.
|
||||
def _handleNotFound() -> ResponseData:
|
||||
return ResponseData(status = HTTP_STATUS["NOT_FOUND"], content_type = "text/html", data_stream = b"Not found.")
|
||||
|
||||
|
@ -95,7 +97,7 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
|||
def _sendData(self, data: bytes) -> None:
|
||||
self.wfile.write(data)
|
||||
|
||||
## Convenience helper for getting values from a pre-parsed query string
|
||||
@staticmethod
|
||||
# Convenience Helper for getting values from a pre-parsed query string
|
||||
def _queryGet(query_data: Dict[Any, List], key: str, default: Optional[str] = None) -> Optional[str]:
|
||||
return query_data.get(key, [default])[0]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from http.server import HTTPServer
|
||||
from typing import Callable, Any, TYPE_CHECKING
|
||||
|
||||
|
@ -8,19 +9,19 @@ if TYPE_CHECKING:
|
|||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
||||
|
||||
|
||||
# The authorization request callback handler server.
|
||||
## The authorization request callback handler server.
|
||||
# This subclass is needed to be able to pass some data to the request handler.
|
||||
# This cannot be done on the request handler directly as the HTTPServer creates an instance of the handler after
|
||||
# init.
|
||||
# This cannot be done on the request handler directly as the HTTPServer
|
||||
# creates an instance of the handler after init.
|
||||
class AuthorizationRequestServer(HTTPServer):
|
||||
# Set the authorization helpers instance on the request handler.
|
||||
## Set the authorization helpers instance on the request handler.
|
||||
def setAuthorizationHelpers(self, authorization_helpers: "AuthorizationHelpers") -> None:
|
||||
self.RequestHandlerClass.authorization_helpers = authorization_helpers # type: ignore
|
||||
|
||||
# Set the authorization callback on the request handler.
|
||||
## Set the authorization callback on the request handler.
|
||||
def setAuthorizationCallback(self, authorization_callback: Callable[["AuthenticationResponse"], Any]) -> None:
|
||||
self.RequestHandlerClass.authorization_callback = authorization_callback # type: ignore
|
||||
|
||||
# Set the verification code on the request handler.
|
||||
## Set the verification code on the request handler.
|
||||
def setVerificationCode(self, verification_code: str) -> None:
|
||||
self.RequestHandlerClass.verification_code = verification_code # type: ignore
|
||||
|
|
|
@ -25,12 +25,9 @@ if TYPE_CHECKING:
|
|||
from UM.Preferences import Preferences
|
||||
|
||||
|
||||
## The authorization service is responsible for handling the login flow,
|
||||
# storing user credentials and providing account information.
|
||||
class AuthorizationService:
|
||||
"""
|
||||
The authorization service is responsible for handling the login flow,
|
||||
storing user credentials and providing account information.
|
||||
"""
|
||||
|
||||
# Emit signal when authentication is completed.
|
||||
onAuthStateChanged = Signal()
|
||||
|
||||
|
@ -60,12 +57,13 @@ class AuthorizationService:
|
|||
if self._preferences:
|
||||
self._preferences.addPreference(self._settings.AUTH_DATA_PREFERENCE_KEY, "{}")
|
||||
|
||||
# Get the user profile as obtained from the JWT (JSON Web Token).
|
||||
## Get the user profile as obtained from the JWT (JSON Web Token).
|
||||
# If the JWT is not yet parsed, calling this will take care of that.
|
||||
# \return UserProfile if a user is logged in, None otherwise.
|
||||
# \sa _parseJWT
|
||||
def getUserProfile(self) -> Optional["UserProfile"]:
|
||||
if not self._user_profile:
|
||||
# If no user profile was stored locally, we try to get it from JWT.
|
||||
try:
|
||||
self._user_profile = self._parseJWT()
|
||||
except requests.exceptions.ConnectionError:
|
||||
|
@ -80,7 +78,7 @@ class AuthorizationService:
|
|||
|
||||
return self._user_profile
|
||||
|
||||
# Tries to parse the JWT (JSON Web Token) data, which it does if all the needed data is there.
|
||||
## Tries to parse the JWT (JSON Web Token) data, which it does if all the needed data is there.
|
||||
# \return UserProfile if it was able to parse, None otherwise.
|
||||
def _parseJWT(self) -> Optional["UserProfile"]:
|
||||
if not self._auth_data or self._auth_data.access_token is None:
|
||||
|
@ -100,7 +98,7 @@ class AuthorizationService:
|
|||
|
||||
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
||||
|
||||
# Get the access token as provided by the response data.
|
||||
## Get the access token as provided by the repsonse data.
|
||||
def getAccessToken(self) -> Optional[str]:
|
||||
if self._auth_data is None:
|
||||
Logger.log("d", "No auth data to retrieve the access_token from")
|
||||
|
@ -116,7 +114,7 @@ class AuthorizationService:
|
|||
|
||||
return self._auth_data.access_token if self._auth_data else None
|
||||
|
||||
# Try to refresh the access token. This should be used when it has expired.
|
||||
## Try to refresh the access token. This should be used when it has expired.
|
||||
def refreshAccessToken(self) -> None:
|
||||
if self._auth_data is None or self._auth_data.refresh_token is None:
|
||||
Logger.log("w", "Unable to refresh access token, since there is no refresh token.")
|
||||
|
@ -126,15 +124,15 @@ class AuthorizationService:
|
|||
self._storeAuthData(response)
|
||||
self.onAuthStateChanged.emit(logged_in = True)
|
||||
else:
|
||||
self.onAuthStateChanged(logged_in = False)
|
||||
self.onAuthStateChanged.emit(logged_in = False)
|
||||
|
||||
# Delete the authentication data that we have stored locally (eg; logout)
|
||||
## Delete the authentication data that we have stored locally (eg; logout)
|
||||
def deleteAuthData(self) -> None:
|
||||
if self._auth_data is not None:
|
||||
self._storeAuthData()
|
||||
self.onAuthStateChanged.emit(logged_in = False)
|
||||
|
||||
# Start the flow to become authenticated. This will start a new webbrowser tap, prompting the user to login.
|
||||
## Start the flow to become authenticated. This will start a new webbrowser tap, prompting the user to login.
|
||||
def startAuthorizationFlow(self) -> None:
|
||||
Logger.log("d", "Starting new OAuth2 flow...")
|
||||
|
||||
|
@ -161,7 +159,7 @@ class AuthorizationService:
|
|||
# Start a local web server to receive the callback URL on.
|
||||
self._server.start(verification_code)
|
||||
|
||||
# Callback method for the authentication flow.
|
||||
## Callback method for the authentication flow.
|
||||
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
|
||||
if auth_response.success:
|
||||
self._storeAuthData(auth_response)
|
||||
|
@ -170,7 +168,7 @@ class AuthorizationService:
|
|||
self.onAuthenticationError.emit(logged_in = False, error_message = auth_response.err_message)
|
||||
self._server.stop() # Stop the web server at all times.
|
||||
|
||||
# Load authentication data from preferences.
|
||||
## Load authentication data from preferences.
|
||||
def loadAuthDataFromPreferences(self) -> None:
|
||||
if self._preferences is None:
|
||||
Logger.log("e", "Unable to load authentication data, since no preference has been set!")
|
||||
|
@ -194,7 +192,7 @@ class AuthorizationService:
|
|||
except ValueError:
|
||||
Logger.logException("w", "Could not load auth data from preferences")
|
||||
|
||||
# Store authentication data in preferences.
|
||||
## Store authentication data in preferences.
|
||||
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
|
||||
if self._preferences is None:
|
||||
Logger.log("e", "Unable to save authentication data, since no preference has been set!")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import threading
|
||||
from typing import Optional, Callable, Any, TYPE_CHECKING
|
||||
|
||||
|
@ -14,12 +15,15 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class LocalAuthorizationServer:
|
||||
# The local LocalAuthorizationServer takes care of the oauth2 callbacks.
|
||||
# Once the flow is completed, this server should be closed down again by calling stop()
|
||||
# \param auth_helpers: An instance of the authorization helpers class.
|
||||
# \param auth_state_changed_callback: A callback function to be called when the authorization state changes.
|
||||
# \param daemon: Whether the server thread should be run in daemon mode. Note: Daemon threads are abruptly stopped
|
||||
# at shutdown. Their resources (e.g. open files) may never be released.
|
||||
## The local LocalAuthorizationServer takes care of the oauth2 callbacks.
|
||||
# Once the flow is completed, this server should be closed down again by
|
||||
# calling stop()
|
||||
# \param auth_helpers An instance of the authorization helpers class.
|
||||
# \param auth_state_changed_callback A callback function to be called when
|
||||
# the authorization state changes.
|
||||
# \param daemon Whether the server thread should be run in daemon mode.
|
||||
# Note: Daemon threads are abruptly stopped at shutdown. Their resources
|
||||
# (e.g. open files) may never be released.
|
||||
def __init__(self, auth_helpers: "AuthorizationHelpers",
|
||||
auth_state_changed_callback: Callable[["AuthenticationResponse"], Any],
|
||||
daemon: bool) -> None:
|
||||
|
@ -30,8 +34,8 @@ class LocalAuthorizationServer:
|
|||
self._auth_state_changed_callback = auth_state_changed_callback
|
||||
self._daemon = daemon
|
||||
|
||||
# Starts the local web server to handle the authorization callback.
|
||||
# \param verification_code: The verification code part of the OAuth2 client identification.
|
||||
## Starts the local web server to handle the authorization callback.
|
||||
# \param verification_code The verification code part of the OAuth2 client identification.
|
||||
def start(self, verification_code: str) -> None:
|
||||
if self._web_server:
|
||||
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
|
||||
|
@ -54,7 +58,7 @@ class LocalAuthorizationServer:
|
|||
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
|
||||
self._web_server_thread.start()
|
||||
|
||||
# Stops the web server if it was running. It also does some cleanup.
|
||||
## Stops the web server if it was running. It also does some cleanup.
|
||||
def stop(self) -> None:
|
||||
Logger.log("d", "Stopping local oauth2 web server...")
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
|
||||
|
@ -8,7 +8,7 @@ class BaseModel:
|
|||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
# OAuth OAuth2Settings data template.
|
||||
## OAuth OAuth2Settings data template.
|
||||
class OAuth2Settings(BaseModel):
|
||||
CALLBACK_PORT = None # type: Optional[int]
|
||||
OAUTH_SERVER_URL = None # type: Optional[str]
|
||||
|
@ -20,14 +20,14 @@ class OAuth2Settings(BaseModel):
|
|||
AUTH_FAILED_REDIRECT = "https://ultimaker.com" # type: str
|
||||
|
||||
|
||||
# User profile data template.
|
||||
## User profile data template.
|
||||
class UserProfile(BaseModel):
|
||||
user_id = None # type: Optional[str]
|
||||
username = None # type: Optional[str]
|
||||
profile_image_url = None # type: Optional[str]
|
||||
|
||||
|
||||
# Authentication data template.
|
||||
## Authentication data template.
|
||||
class AuthenticationResponse(BaseModel):
|
||||
"""Data comes from the token response with success flag and error message added."""
|
||||
success = True # type: bool
|
||||
|
@ -40,21 +40,20 @@ class AuthenticationResponse(BaseModel):
|
|||
received_at = None # type: Optional[str]
|
||||
|
||||
|
||||
# Response status template.
|
||||
## Response status template.
|
||||
class ResponseStatus(BaseModel):
|
||||
code = 200 # type: int
|
||||
message = "" # type: str
|
||||
|
||||
|
||||
# Response data template.
|
||||
## Response data template.
|
||||
class ResponseData(BaseModel):
|
||||
status = None # type: ResponseStatus
|
||||
data_stream = None # type: Optional[bytes]
|
||||
redirect_uri = None # type: Optional[str]
|
||||
content_type = "text/html" # type: str
|
||||
|
||||
|
||||
# Possible HTTP responses.
|
||||
## Possible HTTP responses.
|
||||
HTTP_STATUS = {
|
||||
"OK": ResponseStatus(code = 200, message = "OK"),
|
||||
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
|
|
@ -29,4 +29,4 @@ class PlatformPhysicsOperation(Operation):
|
|||
return group
|
||||
|
||||
def __repr__(self):
|
||||
return "PlatformPhysicsOperation(translation = {0})".format(self._translation)
|
||||
return "PlatformPhysicsOp.(trans.={0})".format(self._translation)
|
||||
|
|
|
@ -69,10 +69,8 @@ class PrintInformation(QObject):
|
|||
self._application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
|
||||
|
||||
self._onActiveMaterialsChanged()
|
||||
|
||||
self._material_amounts = [] # type: List[float]
|
||||
self._onActiveMaterialsChanged()
|
||||
|
||||
def initializeCuraMessagePrintTimeProperties(self) -> None:
|
||||
self._current_print_time = {} # type: Dict[int, Duration]
|
||||
|
@ -220,6 +218,7 @@ class PrintInformation(QObject):
|
|||
|
||||
material_guid = material.getMetaDataEntry("GUID")
|
||||
material_name = material.getName()
|
||||
|
||||
if material_guid in material_preference_values:
|
||||
material_values = material_preference_values[material_guid]
|
||||
|
||||
|
|
|
@ -40,7 +40,9 @@ class ConfigurationModel(QObject):
|
|||
return self._extruder_configurations
|
||||
|
||||
def setBuildplateConfiguration(self, buildplate_configuration: str) -> None:
|
||||
if self._buildplate_configuration != buildplate_configuration:
|
||||
self._buildplate_configuration = buildplate_configuration
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
||||
def buildplateConfiguration(self) -> str:
|
||||
|
|
|
@ -81,8 +81,8 @@ class GenericOutputController(PrinterOutputController):
|
|||
self._output_device.cancelPrint()
|
||||
pass
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
self._output_device.sendCommand("M140 S%s" % temperature)
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None:
|
||||
self._output_device.sendCommand("M140 S%s" % round(temperature)) # The API doesn't allow floating point.
|
||||
|
||||
def _onTargetBedTemperatureChanged(self) -> None:
|
||||
if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
|
||||
|
|
|
@ -310,11 +310,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
def _createNetworkManager(self) -> None:
|
||||
Logger.log("d", "Creating network manager")
|
||||
if self._manager:
|
||||
self._manager.finished.disconnect(self.__handleOnFinished)
|
||||
self._manager.finished.disconnect(self._handleOnFinished)
|
||||
self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
|
||||
|
||||
self._manager = QNetworkAccessManager()
|
||||
self._manager.finished.connect(self.__handleOnFinished)
|
||||
self._manager.finished.connect(self._handleOnFinished)
|
||||
self._last_manager_create_time = time()
|
||||
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
|
||||
|
||||
|
@ -325,7 +325,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
if on_finished is not None:
|
||||
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
|
||||
|
||||
def __handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||
def _handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||
# Due to garbage collection, we need to cache certain bits of post operations.
|
||||
# As we don't want to keep them around forever, delete them if we get a reply.
|
||||
if reply.operation() == QNetworkAccessManager.PostOperation:
|
||||
|
|
|
@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
|
||||
|
||||
class PrintJobOutputModel(QObject):
|
||||
stateChanged = pyqtSignal()
|
||||
timeTotalChanged = pyqtSignal()
|
||||
|
@ -44,7 +45,7 @@ class PrintJobOutputModel(QObject):
|
|||
@pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
|
||||
def compatibleMachineFamilies(self):
|
||||
# Hack; Some versions of cluster will return a family more than once...
|
||||
return set(self._compatible_machine_families)
|
||||
return list(set(self._compatible_machine_families))
|
||||
|
||||
def setCompatibleMachineFamilies(self, compatible_machine_families: List[str]) -> None:
|
||||
if self._compatible_machine_families != compatible_machine_families:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
@ -25,10 +25,10 @@ class PrinterOutputController:
|
|||
self.can_update_firmware = False
|
||||
self._output_device = output_device
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: float) -> None:
|
||||
Logger.log("w", "Set target hotend temperature not implemented in controller")
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None:
|
||||
Logger.log("w", "Set target bed temperature not implemented in controller")
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
|
||||
|
@ -22,7 +22,7 @@ class PrinterOutputModel(QObject):
|
|||
nameChanged = pyqtSignal()
|
||||
headPositionChanged = pyqtSignal()
|
||||
keyChanged = pyqtSignal()
|
||||
printerTypeChanged = pyqtSignal()
|
||||
typeChanged = pyqtSignal()
|
||||
buildplateChanged = pyqtSignal()
|
||||
cameraUrlChanged = pyqtSignal()
|
||||
configurationChanged = pyqtSignal()
|
||||
|
@ -30,8 +30,8 @@ class PrinterOutputModel(QObject):
|
|||
|
||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
|
||||
super().__init__(parent)
|
||||
self._bed_temperature = -1 # Use -1 for no heated bed.
|
||||
self._target_bed_temperature = 0
|
||||
self._bed_temperature = -1 # type: float # Use -1 for no heated bed.
|
||||
self._target_bed_temperature = 0 # type: float
|
||||
self._name = ""
|
||||
self._key = "" # Unique identifier
|
||||
self._controller = output_controller
|
||||
|
@ -73,7 +73,7 @@ class PrinterOutputModel(QObject):
|
|||
def isPreheating(self) -> bool:
|
||||
return self._is_preheating
|
||||
|
||||
@pyqtProperty(str, notify = printerTypeChanged)
|
||||
@pyqtProperty(str, notify = typeChanged)
|
||||
def type(self) -> str:
|
||||
return self._printer_type
|
||||
|
||||
|
@ -81,7 +81,7 @@ class PrinterOutputModel(QObject):
|
|||
if self._printer_type != printer_type:
|
||||
self._printer_type = printer_type
|
||||
self._printer_configuration.printerType = self._printer_type
|
||||
self.printerTypeChanged.emit()
|
||||
self.typeChanged.emit()
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = buildplateChanged)
|
||||
|
@ -179,7 +179,6 @@ class PrinterOutputModel(QObject):
|
|||
return self._name
|
||||
|
||||
def setName(self, name: str) -> None:
|
||||
self._setName(name)
|
||||
self.updateName(name)
|
||||
|
||||
def updateName(self, name: str) -> None:
|
||||
|
@ -188,19 +187,19 @@ class PrinterOutputModel(QObject):
|
|||
self.nameChanged.emit()
|
||||
|
||||
## Update the bed temperature. This only changes it locally.
|
||||
def updateBedTemperature(self, temperature: int) -> None:
|
||||
def updateBedTemperature(self, temperature: float) -> None:
|
||||
if self._bed_temperature != temperature:
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
|
||||
def updateTargetBedTemperature(self, temperature: int) -> None:
|
||||
def updateTargetBedTemperature(self, temperature: float) -> None:
|
||||
if self._target_bed_temperature != temperature:
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Set the target bed temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(int)
|
||||
def setTargetBedTemperature(self, temperature: int) -> None:
|
||||
@pyqtSlot(float)
|
||||
def setTargetBedTemperature(self, temperature: float) -> None:
|
||||
self._controller.setTargetBedTemperature(self, temperature)
|
||||
self.updateTargetBedTemperature(temperature)
|
||||
|
||||
|
@ -229,12 +228,12 @@ class PrinterOutputModel(QObject):
|
|||
def state(self) -> str:
|
||||
return self._printer_state
|
||||
|
||||
@pyqtProperty(int, notify=bedTemperatureChanged)
|
||||
def bedTemperature(self) -> int:
|
||||
@pyqtProperty(float, notify = bedTemperatureChanged)
|
||||
def bedTemperature(self) -> float:
|
||||
return self._bed_temperature
|
||||
|
||||
@pyqtProperty(int, notify=targetBedTemperatureChanged)
|
||||
def targetBedTemperature(self) -> int:
|
||||
@pyqtProperty(float, notify = targetBedTemperatureChanged)
|
||||
def targetBedTemperature(self) -> float:
|
||||
return self._target_bed_temperature
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
|
|
|
@ -112,21 +112,21 @@ class CuraSceneNode(SceneNode):
|
|||
|
||||
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
|
||||
def _calculateAABB(self) -> None:
|
||||
self._aabb = None
|
||||
if self._mesh_data:
|
||||
aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
|
||||
position = self.getWorldPosition()
|
||||
aabb = AxisAlignedBox(minimum = position, maximum = position)
|
||||
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||
|
||||
for child in self._children:
|
||||
if child.callDecoration("isNonPrintingMesh"):
|
||||
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
|
||||
continue
|
||||
if aabb is None:
|
||||
aabb = child.getBoundingBox()
|
||||
if not child._mesh_data:
|
||||
# Nodes without mesh data should not affect bounding boxes of their parents.
|
||||
continue
|
||||
if self._aabb is None:
|
||||
self._aabb = child.getBoundingBox()
|
||||
else:
|
||||
aabb = aabb + child.getBoundingBox()
|
||||
self._aabb = aabb
|
||||
self._aabb = self._aabb + child.getBoundingBox()
|
||||
|
||||
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
|
||||
def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
|
||||
|
|
|
@ -10,7 +10,7 @@ class GCodeListDecorator(SceneNodeDecorator):
|
|||
def getGCodeList(self) -> List[str]:
|
||||
return self._gcode_list
|
||||
|
||||
def setGCodeList(self, list: List[str]):
|
||||
def setGCodeList(self, list: List[str]) -> None:
|
||||
self._gcode_list = list
|
||||
|
||||
def __deepcopy__(self, memo) -> "GCodeListDecorator":
|
||||
|
|
|
@ -47,8 +47,10 @@ class ContainerManager(QObject):
|
|||
if ContainerManager.__instance is not None:
|
||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||
ContainerManager.__instance = self
|
||||
|
||||
try:
|
||||
super().__init__(parent = application)
|
||||
except TypeError:
|
||||
super().__init__()
|
||||
|
||||
self._application = application # type: CuraApplication
|
||||
self._plugin_registry = self._application.getPluginRegistry() # type: PluginRegistry
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
import re
|
||||
import configparser
|
||||
|
||||
from typing import cast, Dict, Optional
|
||||
from typing import Any, cast, Dict, Optional
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from UM.Decorators import override
|
||||
|
@ -267,7 +267,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
profile.setMetaDataEntry("position", "0")
|
||||
profile.setDirty(True)
|
||||
if idx == 0:
|
||||
# move all per-extruder settings to the first extruder's quality_changes
|
||||
# Move all per-extruder settings to the first extruder's quality_changes
|
||||
for qc_setting_key in global_profile.getAllKeys():
|
||||
settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
|
@ -327,6 +327,23 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
self._registerSingleExtrusionMachinesExtruderStacks()
|
||||
self._connectUpgradedExtruderStacksToMachines()
|
||||
|
||||
## Check if the metadata for a container is okay before adding it.
|
||||
#
|
||||
# This overrides the one from UM.Settings.ContainerRegistry because we
|
||||
# also require that the setting_version is correct.
|
||||
@override(ContainerRegistry)
|
||||
def _isMetadataValid(self, metadata: Optional[Dict[str, Any]]) -> bool:
|
||||
if metadata is None:
|
||||
return False
|
||||
if "setting_version" not in metadata:
|
||||
return False
|
||||
try:
|
||||
if int(metadata["setting_version"]) != cura.CuraApplication.CuraApplication.SettingVersion:
|
||||
return False
|
||||
except ValueError: #Not parsable as int.
|
||||
return False
|
||||
return True
|
||||
|
||||
## Update an imported profile to match the current machine configuration.
|
||||
#
|
||||
# \param profile The profile to configure.
|
||||
|
@ -386,30 +403,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
result.append( (plugin_id, meta_data) )
|
||||
return result
|
||||
|
||||
## Returns true if the current machine requires its own materials
|
||||
# \return True if the current machine requires its own materials
|
||||
def _machineHasOwnMaterials(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
return global_container_stack.getMetaDataEntry("has_materials", False)
|
||||
return False
|
||||
|
||||
## Gets the ID of the active material
|
||||
# \return the ID of the active material or the empty string
|
||||
def _activeMaterialId(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack and global_container_stack.material:
|
||||
return global_container_stack.material.getId()
|
||||
return ""
|
||||
|
||||
## Returns true if the current machine requires its own quality profiles
|
||||
# \return true if the current machine requires its own quality profiles
|
||||
def _machineHasOwnQualities(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
|
||||
return False
|
||||
|
||||
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
|
||||
def _convertContainerStack(self, container):
|
||||
assert type(container) == ContainerStack
|
||||
|
@ -521,7 +514,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
||||
|
||||
if machine.userChanges:
|
||||
# for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
|
||||
# For the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
|
||||
# container to the extruder stack.
|
||||
for user_setting_key in machine.userChanges.getAllKeys():
|
||||
settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
|
||||
|
@ -583,7 +576,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
|
||||
else:
|
||||
# if we still cannot find a quality changes container for the extruder, create a new one
|
||||
# If we still cannot find a quality changes container for the extruder, create a new one
|
||||
container_name = machine_quality_changes.getName()
|
||||
container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name)
|
||||
extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
|
||||
|
@ -601,7 +594,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
|
||||
machine_quality_changes.getName(), extruder_stack.getId())
|
||||
else:
|
||||
# move all per-extruder settings to the extruder's quality changes
|
||||
# Move all per-extruder settings to the extruder's quality changes
|
||||
for qc_setting_key in machine_quality_changes.getAllKeys():
|
||||
settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
|
@ -642,7 +635,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if qc_name not in qc_groups:
|
||||
qc_groups[qc_name] = []
|
||||
qc_groups[qc_name].append(qc)
|
||||
# try to find from the quality changes cura directory too
|
||||
# Try to find from the quality changes cura directory too
|
||||
quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
|
||||
if quality_changes_container:
|
||||
qc_groups[qc_name].append(quality_changes_container)
|
||||
|
@ -656,7 +649,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
else:
|
||||
qc_dict["global"] = qc
|
||||
if qc_dict["global"] is not None and len(qc_dict["extruders"]) == 1:
|
||||
# move per-extruder settings
|
||||
# Move per-extruder settings
|
||||
for qc_setting_key in qc_dict["global"].getAllKeys():
|
||||
settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
|
@ -690,17 +683,17 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
try:
|
||||
parser.read([file_path])
|
||||
except:
|
||||
# skip, it is not a valid stack file
|
||||
# Skip, it is not a valid stack file
|
||||
continue
|
||||
|
||||
if not parser.has_option("general", "name"):
|
||||
continue
|
||||
|
||||
if parser["general"]["name"] == name:
|
||||
# load the container
|
||||
# Load the container
|
||||
container_id = os.path.basename(file_path).replace(".inst.cfg", "")
|
||||
if self.findInstanceContainers(id = container_id):
|
||||
# this container is already in the registry, skip it
|
||||
# This container is already in the registry, skip it
|
||||
continue
|
||||
|
||||
instance_container = InstanceContainer(container_id)
|
||||
|
|
|
@ -125,7 +125,12 @@ class CuraStackBuilder:
|
|||
|
||||
extruder_definition_dict = global_stack.getMetaDataEntry("machine_extruder_trains")
|
||||
extruder_definition_id = extruder_definition_dict[str(extruder_position)]
|
||||
try:
|
||||
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
||||
except IndexError as e:
|
||||
# It still needs to break, but we want to know what extruder ID made it break.
|
||||
Logger.log("e", "Unable to find extruder with the id %s", extruder_definition_id)
|
||||
raise e
|
||||
|
||||
# get material container for extruders
|
||||
material_container = application.empty_material_container
|
||||
|
|
|
@ -264,7 +264,9 @@ class ExtruderManager(QObject):
|
|||
used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_roof_extruder_nr", "value")))])
|
||||
|
||||
# The platform adhesion extruder. Not used if using none.
|
||||
if global_stack.getProperty("adhesion_type", "value") != "none":
|
||||
if global_stack.getProperty("adhesion_type", "value") != "none" or (
|
||||
global_stack.getProperty("prime_tower_brim_enable", "value") and
|
||||
global_stack.getProperty("adhesion_type", "value") != 'raft'):
|
||||
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
if extruder_str_nr == "-1":
|
||||
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
||||
|
@ -301,12 +303,7 @@ class ExtruderManager(QObject):
|
|||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return []
|
||||
|
||||
result_tuple_list = sorted(list(global_stack.extruders.items()), key = lambda x: int(x[0]))
|
||||
result_list = [item[1] for item in result_tuple_list]
|
||||
|
||||
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
|
||||
return result_list[:machine_extruder_count]
|
||||
return global_stack.extruderList
|
||||
|
||||
def _globalContainerStackChanged(self) -> None:
|
||||
# If the global container changed, the machine changed and might have extruders that were not registered yet
|
||||
|
@ -341,7 +338,7 @@ class ExtruderManager(QObject):
|
|||
extruder_train.setNextStack(global_stack)
|
||||
extruders_changed = True
|
||||
|
||||
self._fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
self.fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
if extruders_changed:
|
||||
self.extrudersChanged.emit(global_stack_id)
|
||||
self.setActiveExtruderIndex(0)
|
||||
|
@ -349,7 +346,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
||||
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
||||
def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
|
||||
def fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
|
||||
extruder_stack_0 = global_stack.extruders.get("0")
|
||||
|
|
|
@ -133,6 +133,7 @@ class GlobalStack(CuraContainerStack):
|
|||
return
|
||||
|
||||
self._extruders[position] = extruder
|
||||
self.extrudersChanged.emit()
|
||||
Logger.log("i", "Extruder[%s] added to [%s] at position [%s]", extruder.id, self.id, position)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import collections
|
||||
import time
|
||||
import re
|
||||
import unicodedata
|
||||
from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional, cast
|
||||
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
@ -64,8 +63,6 @@ class MachineManager(QObject):
|
|||
|
||||
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
|
||||
|
||||
self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]]
|
||||
|
||||
self._instance_container_timer = QTimer() # type: QTimer
|
||||
self._instance_container_timer.setInterval(250)
|
||||
self._instance_container_timer.setSingleShot(True)
|
||||
|
@ -272,11 +269,6 @@ class MachineManager(QObject):
|
|||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
extruder_stack.containersChanged.connect(self._onContainersChanged)
|
||||
|
||||
if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
|
||||
for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
|
||||
self._application.callLater(func)
|
||||
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
|
||||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
|
||||
def _onActiveExtruderStackChanged(self) -> None:
|
||||
|
@ -365,12 +357,13 @@ class MachineManager(QObject):
|
|||
# Make sure that the default machine actions for this machine have been added
|
||||
self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
|
||||
|
||||
ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
if not global_stack.isValid():
|
||||
# Mark global stack as invalid
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
|
||||
return # We're done here
|
||||
ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder
|
||||
|
||||
self._global_container_stack = global_stack
|
||||
self._application.setGlobalContainerStack(global_stack)
|
||||
ExtruderManager.getInstance()._globalContainerStackChanged()
|
||||
|
@ -433,12 +426,12 @@ class MachineManager(QObject):
|
|||
if not self._global_container_stack:
|
||||
return False
|
||||
|
||||
if self._global_container_stack.getTop().findInstances():
|
||||
if self._global_container_stack.getTop().getNumInstances() != 0:
|
||||
return True
|
||||
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for stack in stacks:
|
||||
if stack.getTop().findInstances():
|
||||
if stack.getTop().getNumInstances() != 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
@ -448,10 +441,10 @@ class MachineManager(QObject):
|
|||
if not self._global_container_stack:
|
||||
return 0
|
||||
num_user_settings = 0
|
||||
num_user_settings += len(self._global_container_stack.getTop().findInstances())
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
num_user_settings += self._global_container_stack.getTop().getNumInstances()
|
||||
stacks = self._global_container_stack.extruderList
|
||||
for stack in stacks:
|
||||
num_user_settings += len(stack.getTop().findInstances())
|
||||
num_user_settings += stack.getTop().getNumInstances()
|
||||
return num_user_settings
|
||||
|
||||
## Delete a user setting from the global stack and all extruder stacks.
|
||||
|
@ -1381,7 +1374,6 @@ class MachineManager(QObject):
|
|||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self.switchPrinterType(configuration.printerType)
|
||||
|
||||
used_extruder_stack_list = ExtruderManager.getInstance().getUsedExtruderStacks()
|
||||
disabled_used_extruder_position_set = set()
|
||||
extruders_to_disable = set()
|
||||
|
||||
|
@ -1390,8 +1382,9 @@ class MachineManager(QObject):
|
|||
need_to_show_message = False
|
||||
|
||||
for extruder_configuration in configuration.extruderConfigurations:
|
||||
extruder_has_hotend = extruder_configuration.hotendID != ""
|
||||
extruder_has_material = extruder_configuration.material.guid != ""
|
||||
# We support "" or None, since the cloud uses None instead of empty strings
|
||||
extruder_has_hotend = extruder_configuration.hotendID and extruder_configuration.hotendID != ""
|
||||
extruder_has_material = extruder_configuration.material.guid and extruder_configuration.material.guid != ""
|
||||
|
||||
# If the machine doesn't have a hotend or material, disable this extruder
|
||||
if not extruder_has_hotend or not extruder_has_material:
|
||||
|
|
|
@ -17,6 +17,8 @@ except ImportError:
|
|||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
|
||||
if CuraCloudAPIVersion == "":
|
||||
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
|
||||
except ImportError:
|
||||
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
|
||||
|
||||
|
|
10
cura_app.py
10
cura_app.py
|
@ -9,6 +9,7 @@ import os
|
|||
import sys
|
||||
|
||||
from UM.Platform import Platform
|
||||
from cura.ApplicationMetadata import CuraAppName
|
||||
|
||||
parser = argparse.ArgumentParser(prog = "cura",
|
||||
add_help = False)
|
||||
|
@ -22,11 +23,14 @@ known_args = vars(parser.parse_known_args()[0])
|
|||
if not known_args["debug"]:
|
||||
def get_cura_dir_path():
|
||||
if Platform.isWindows():
|
||||
return os.path.expanduser("~/AppData/Roaming/cura")
|
||||
appdata_path = os.getenv("APPDATA")
|
||||
if not appdata_path: #Defensive against the environment variable missing (should never happen).
|
||||
appdata_path = "."
|
||||
return os.path.join(appdata_path, CuraAppName)
|
||||
elif Platform.isLinux():
|
||||
return os.path.expanduser("~/.local/share/cura")
|
||||
return os.path.expanduser("~/.local/share/" + CuraAppName)
|
||||
elif Platform.isOSX():
|
||||
return os.path.expanduser("~/Library/Logs/cura")
|
||||
return os.path.expanduser("~/Library/Logs/" + CuraAppName)
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
dirpath = get_cura_dir_path()
|
||||
|
|
|
@ -26,6 +26,7 @@ from UM.Preferences import Preferences
|
|||
|
||||
from cura.Machines.VariantType import VariantType
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
|
@ -781,6 +782,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
if not quality_changes_info.extruder_info_dict:
|
||||
container_info = ContainerInfo(None, None, None)
|
||||
quality_changes_info.extruder_info_dict["0"] = container_info
|
||||
# If the global stack we're "targeting" has never been active, but was updated from Cura 3.4,
|
||||
# it might not have it's extruders set properly.
|
||||
if not global_stack.extruders:
|
||||
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
extruder_stack = global_stack.extruders["0"]
|
||||
|
||||
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
|
||||
|
|
|
@ -1,3 +1,88 @@
|
|||
[4.0.0]
|
||||
*Updated user interface
|
||||
Ultimaker Cura is a very powerful tool with many features to support users’ needs. In the new UI, we present these features in a better, more intuitive way based on the workflow of our users. The Marketplace and user account control have been integrated into the main interface to easily access material profiles and plugins. Three stages are shown in the header to give a clear guidance of the flow. The stage menu is populated with collapsible panels that allow users to focus on the 3D view when needed, while still showing important information at the same time, such as slicing configuration and settings. Users can now easily go to the preview stage to examine the layer view after slicing the model, which previously was less obvious or hidden. The new UI also creates more distinction between recommended and custom mode. Novice users or users who are not interested in all the settings can easily prepare a file, relying on the strength of expert-configured print profiles. Experienced users who want greater control can configure over 300 settings to their needs.
|
||||
|
||||
*Redesigned "Add Printer" dialog
|
||||
Updated one of the first dialogs a new user is presented with. The layout is loosely modeled on the layout of the Ultimaker 3/Ultimaker S5 "Connect to Network" dialog, and adds some instructions and intention to the dialog. Contributed by fieldOfView.
|
||||
|
||||
*Updated custom mode panel
|
||||
Based on feedback from 4.0 beta, the custom mode panel is now resizable to make more settings visible. The set position will persist between sessions.
|
||||
|
||||
*Monitor tab
|
||||
Updated the monitor tab interface for better alignment with Cura Connect interface.
|
||||
|
||||
*Remote printing
|
||||
Use your Ultimaker S5 printer with an Ultimaker account to send and monitor print jobs from outside your local network. Requires firmware 5.2 (coming soon).
|
||||
|
||||
*User ratings for plugins
|
||||
With an Ultimaker account, users can now give feedback on their experience by rating their favourite plugins.
|
||||
|
||||
*Integrated backups
|
||||
‘Cura backups’ has been integrated into Ultimaker Cura and can be found in the ‘extensions’ menu. With this feature, users can use their Ultimaker account to backup their Ultimaker Cura configurations to the cloud for easy, convenient retrieval.
|
||||
|
||||
*Plugin versioning
|
||||
Newer plug-ins can't load in older versions if they use newer features, while old plug-ins may still load in newer versions.
|
||||
|
||||
*LAN and cloud printer icons
|
||||
Users can now quickly see if their printer is network or cloud enabled with new icons.
|
||||
|
||||
*Improved UI speed
|
||||
This version switches faster between extruders and printers. Your mileage may vary depending on your system specifications.
|
||||
|
||||
*Floats precision
|
||||
No settings in Ultimaker Cura require more than three digits of precision, so floats in setting input fields have been limited to three digits only. Contributed by fieldOfView.
|
||||
|
||||
*Minimum support area
|
||||
This feature allows set minimum area size for support and support interface polygons. Polygons which area are smaller than set value will not be generated. Contributed by vgribinchuk/Desktop Metal.
|
||||
|
||||
*Lazy Tree Support calculation
|
||||
In previous versions, 95% of Tree Support’s computation time was used to calculate the collision volumes to make sure that the branches avoid collisions with the meshes. Now it calculates these volumes only when necessary, reducing the computation time. Contributed by bjude.
|
||||
|
||||
*CPE and CPE+ comb retractions
|
||||
Changed all CPE and CPE+ profiles to travel up to 50 mm without retraction, decreasing blobs caused by combing long distances.
|
||||
|
||||
*Marketplace improvements
|
||||
Added optimizations to show a support site instead of an email address, increased the number of lines that are shown for the description, and show a 'website' link so people can order material directly.
|
||||
|
||||
*Arduino drivers silent install
|
||||
Previous versions stopped silent installation because the Arduino drivers packaged with Cura are not signed. Arduino drivers are now skipped when performing a silent install.
|
||||
|
||||
*New third-party definitions
|
||||
- Wanhao. Updated printer profiles to use new travel_speed macro (Contributed by forkineye).
|
||||
- JGAurora A1, A5 and Z-603S (Contributed by pinchies).
|
||||
- Alfawise U20 (Contributed by pinchies).
|
||||
- Cocoon Create ModelMaker (Contributed by pinchies).
|
||||
- Ender-3. Updates to the printer definition (Contributed by stelgenhof).
|
||||
|
||||
*Bug fixes
|
||||
- Fixed an issue which prevented slicing when per extruder settings were changed with a disabled extruder.
|
||||
- Improved handling of non-Ultimaker network connected printers within Ultimaker Cura. Contributed by fieldOfView
|
||||
- Fixed an issue where printing with the second extruder only would retract material unnecessarily.
|
||||
- Fixed an issue where outdated plugins remained partially activated.
|
||||
- Fixed an issue where combing was not working when tweaking Retraction minimum travel.
|
||||
- Fixed an oversized print head collision zone when using print one-at-a-time mode.
|
||||
- Due to inaccuracy of floats in very large prints, the position is reset again several times using "G92 E0" commands.
|
||||
- Improved update checker text for better readability.
|
||||
- Updated the implementation of 3MF in Ultimaker Cura for better consistency with 3MF consortium specifications.
|
||||
- Removed all final and initial print temperature offsets, and increased first layer print temperature to fix under-extrusion problems.
|
||||
- Holding shift and rotating a model on its axis for fine-grained rotations would sometimes pan the camera. This has now been fixed.
|
||||
- Added file type associations for .gcode and .g extensions.
|
||||
- Marked some more profiles as experimental.
|
||||
- Fixed an issue where duplicated PLA with a different label would replace the original PLA entry.
|
||||
- Updated which profile new materials are based when you create a brand new material. Contributed by fieldOfView.
|
||||
- Fixed adhesion type errors on startup.
|
||||
- Fixed an issue where system tray icons would remain when Ultimaker Cura is closed until mouse-over.
|
||||
- Added extra tooltip to give extra information about start/end g-codes.
|
||||
- Fixed an issue where clicking 'Create Account' would go to login instead of sign-up.
|
||||
- Fixed an issue where the legacy profile importer would generate corrupt profiles.
|
||||
- Fixed an issue where Ultimaker Cura could crash on start-up during the upgrading of your configuration to the newest version for some people.
|
||||
- Fixed an issue where Ultimaker Cura would crash after downloading plugin from Marketplace.
|
||||
- Ignores plugins folder when checking files for version upgrade. Start-up is now much faster if you've installed a lot of plugins or have used many versions of Ultimaker Cura.
|
||||
- Fixed an issue where the firmware checker shows up when there is no internet connection.
|
||||
- Fixed an issue where settings could not be made visible again after hiding all settings.
|
||||
- Fixed false configuration error for CC Red 0.6 core after a version upgrade.
|
||||
- Fixed an issue where a warning is issued when selecting a printer with no material loaded. The extruder will now be disabled instead.
|
||||
|
||||
[3.6.0]
|
||||
*Gyroid infill
|
||||
New infill pattern with enhanced strength properties. Gyroid infill is one of the strongest infill types for a given weight, has isotropic properties, and prints relatively fast with reduced material use and a fully connected part interior. Note: Slicing time can increase up to 40 seconds or more, depending on the model. Contributed by smartavionics.
|
||||
|
@ -943,7 +1028,7 @@ This release adds support for printers with elliptic buildplates. This feature h
|
|||
*AppImage for Linux
|
||||
The Linux distribution is now in AppImage format, which makes Cura easier to install.
|
||||
|
||||
*bugfixes
|
||||
*Bugfixes
|
||||
The user is now notified when a new version of Cura is available.
|
||||
When searching in the setting visibility preferences, the category for each setting is always displayed.
|
||||
3MF files are now saved and loaded correctly.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any, cast
|
||||
from typing import Any, cast, Dict, List, Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
|
@ -68,7 +68,7 @@ class DrivePluginExtension(QObject, Extension):
|
|||
|
||||
def showDriveWindow(self) -> None:
|
||||
if not self._drive_window:
|
||||
plugin_dir_path = cast(str, CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive"))
|
||||
plugin_dir_path = cast(str, CuraApplication.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())) # We know this plug-in exists because that's us, so this always returns str.
|
||||
path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
|
||||
self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
|
||||
self.refreshBackups()
|
||||
|
|
|
@ -29,7 +29,7 @@ message Object
|
|||
bytes normals = 3; //An array of 3 floats.
|
||||
bytes indices = 4; //An array of ints.
|
||||
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
|
||||
string name = 6;
|
||||
string name = 6; //Mesh name
|
||||
}
|
||||
|
||||
message Progress
|
||||
|
@ -58,6 +58,7 @@ message Polygon {
|
|||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
SupportInterfaceType = 10;
|
||||
PrimeTowerType = 11;
|
||||
}
|
||||
Type type = 1; // Type of move
|
||||
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
|
||||
|
@ -108,8 +109,9 @@ message PrintTimeMaterialEstimates { // The print time for each feature and mate
|
|||
float time_travel = 9;
|
||||
float time_retract = 10;
|
||||
float time_support_interface = 11;
|
||||
float time_prime_tower = 12;
|
||||
|
||||
repeated MaterialEstimates materialEstimates = 12; // materialEstimates data
|
||||
repeated MaterialEstimates materialEstimates = 13; // materialEstimates data
|
||||
}
|
||||
|
||||
message MaterialEstimates {
|
||||
|
|
|
@ -137,6 +137,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
extruder = polygon.extruder
|
||||
|
||||
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
|
||||
|
||||
line_types = line_types.reshape((-1,1))
|
||||
|
||||
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
|
||||
|
|
|
@ -326,6 +326,7 @@ class StartSliceJob(Job):
|
|||
|
||||
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
|
||||
result["print_temperature"] = result["material_print_temperature"]
|
||||
result["travel_speed"] = result["speed_travel"]
|
||||
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
|
||||
result["date"] = time.strftime("%d-%m-%Y")
|
||||
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
|
@ -13,8 +12,6 @@ from UM.Logger import Logger
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob
|
||||
from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
|
||||
|
||||
|
@ -53,6 +50,7 @@ class FirmwareUpdateChecker(Extension):
|
|||
|
||||
def _onContainerAdded(self, container):
|
||||
# Only take care when a new GlobalStack was added
|
||||
from cura.Settings.GlobalStack import GlobalStack # otherwise circular imports
|
||||
if isinstance(container, GlobalStack):
|
||||
self.checkFirmwareVersion(container, True)
|
||||
|
||||
|
@ -76,7 +74,7 @@ class FirmwareUpdateChecker(Extension):
|
|||
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(container_name))
|
||||
return
|
||||
|
||||
self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent,
|
||||
self._check_job = FirmwareUpdateCheckerJob(silent = silent,
|
||||
machine_name = container_name, metadata = metadata,
|
||||
callback = self._onActionTriggered)
|
||||
self._check_job.start()
|
||||
|
|
|
@ -25,15 +25,14 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
ZERO_VERSION = Version(STRING_ZERO_VERSION)
|
||||
EPSILON_VERSION = Version(STRING_EPSILON_VERSION)
|
||||
|
||||
def __init__(self, container, silent, machine_name, metadata, callback) -> None:
|
||||
def __init__(self, silent, machine_name, metadata, callback) -> None:
|
||||
super().__init__()
|
||||
self._container = container
|
||||
self.silent = silent
|
||||
self._callback = callback
|
||||
|
||||
self._machine_name = machine_name
|
||||
self._metadata = metadata
|
||||
self._lookups = None # type:Optional[FirmwareUpdateCheckerLookup]
|
||||
self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata)
|
||||
self._headers = {} # type:Dict[str, str] # Don't set headers yet.
|
||||
|
||||
def getUrlResponse(self, url: str) -> str:
|
||||
|
@ -45,7 +44,6 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
result = response.read().decode("utf-8")
|
||||
except URLError:
|
||||
Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url))
|
||||
|
||||
return result
|
||||
|
||||
def parseVersionResponse(self, response: str) -> Version:
|
||||
|
@ -70,9 +68,6 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
return max_version
|
||||
|
||||
def run(self):
|
||||
if self._lookups is None:
|
||||
self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata)
|
||||
|
||||
try:
|
||||
# Initialize a Preference that stores the last version checked for this printer.
|
||||
Application.getInstance().getPreferences().addPreference(
|
||||
|
@ -83,13 +78,10 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
application_version = Application.getInstance().getVersion()
|
||||
self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)}
|
||||
|
||||
# get machine name from the definition container
|
||||
machine_name = self._container.definition.getName()
|
||||
|
||||
# If it is not None, then we compare between the checked_version and the current_version
|
||||
machine_id = self._lookups.getMachineId()
|
||||
if machine_id is not None:
|
||||
Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name))
|
||||
Logger.log("i", "You have a(n) {0} in the printer list. Do firmware-check.".format(self._machine_name))
|
||||
|
||||
current_version = self.getCurrentVersion()
|
||||
|
||||
|
@ -105,18 +97,20 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
# If the checked_version is "", it's because is the first time we check firmware and in this case
|
||||
# we will not show the notification, but we will store it for the next time
|
||||
Application.getInstance().getPreferences().setValue(setting_key_str, current_version)
|
||||
Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version)
|
||||
Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s",
|
||||
self._machine_name, checked_version, current_version)
|
||||
|
||||
# The first time we want to store the current version, the notification will not be shown,
|
||||
# because the new version of Cura will be release before the firmware and we don't want to
|
||||
# notify the user when no new firmware version is available.
|
||||
if (checked_version != "") and (checked_version != current_version):
|
||||
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
|
||||
message = FirmwareUpdateCheckerMessage(machine_id, machine_name, self._lookups.getRedirectUserUrl())
|
||||
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
|
||||
self._lookups.getRedirectUserUrl())
|
||||
message.actionTriggered.connect(self._callback)
|
||||
message.show()
|
||||
else:
|
||||
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(machine_name))
|
||||
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(self._machine_name))
|
||||
|
||||
except Exception as e:
|
||||
Logger.log("w", "Failed to check for new version: %s", e)
|
||||
|
|
|
@ -18,7 +18,7 @@ class FirmwareUpdateCheckerLookup:
|
|||
self._machine_id = machine_json.get("id")
|
||||
self._machine_name = machine_name.lower() # Lower in-case upper-case chars are added to the original json.
|
||||
self._check_urls = [] # type:List[str]
|
||||
for check_url in machine_json.get("check_urls"):
|
||||
for check_url in machine_json.get("check_urls", []):
|
||||
self._check_urls.append(check_url)
|
||||
self._redirect_user = machine_json.get("update_url")
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import pytest
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from UM.Version import Version
|
||||
|
||||
import FirmwareUpdateChecker
|
||||
|
||||
json_data = \
|
||||
{
|
||||
"ned":
|
||||
{
|
||||
"id": 1,
|
||||
"name": "ned",
|
||||
"check_urls": [""],
|
||||
"update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
|
||||
"version_parser": "default"
|
||||
},
|
||||
"olivia":
|
||||
{
|
||||
"id": 3,
|
||||
"name": "olivia",
|
||||
"check_urls": [""],
|
||||
"update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
|
||||
"version_parser": "default"
|
||||
},
|
||||
"emmerson":
|
||||
{
|
||||
"id": 5,
|
||||
"name": "emmerson",
|
||||
"check_urls": [""],
|
||||
"update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware",
|
||||
"version_parser": "default"
|
||||
}
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize("name, id", [
|
||||
("ned" , 1),
|
||||
("olivia" , 3),
|
||||
("emmerson", 5),
|
||||
])
|
||||
def test_FirmwareUpdateCheckerLookup(id, name):
|
||||
lookup = FirmwareUpdateChecker.FirmwareUpdateCheckerLookup.FirmwareUpdateCheckerLookup(name, json_data.get(name))
|
||||
|
||||
assert lookup.getMachineName() == name
|
||||
assert lookup.getMachineId() == id
|
||||
assert len(lookup.getCheckUrls()) >= 1
|
||||
assert lookup.getRedirectUserUrl() is not None
|
||||
|
||||
@pytest.mark.parametrize("name, version", [
|
||||
("ned" , Version("5.1.2.3")),
|
||||
("olivia" , Version("4.3.2.1")),
|
||||
("emmerson", Version("6.7.8.1")),
|
||||
])
|
||||
def test_FirmwareUpdateCheckerJob_getCurrentVersion(name, version):
|
||||
machine_data = json_data.get(name)
|
||||
job = FirmwareUpdateChecker.FirmwareUpdateCheckerJob.FirmwareUpdateCheckerJob(False, name, machine_data, MagicMock)
|
||||
job.getUrlResponse = MagicMock(return_value = str(version)) # Pretend like we got a good response from the server
|
||||
assert job.getCurrentVersion() == version
|
0
plugins/FirmwareUpdateChecker/tests/__init__.py
Normal file
0
plugins/FirmwareUpdateChecker/tests/__init__.py
Normal file
|
@ -107,6 +107,8 @@ class FlavorParser:
|
|||
self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2])
|
||||
self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness)
|
||||
this_layer = self._layer_data_builder.getLayer(self._layer_number)
|
||||
if not this_layer:
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
count = len(path)
|
||||
|
|
|
@ -12,9 +12,6 @@ catalog = i18nCatalog("cura")
|
|||
from . import MarlinFlavorParser, RepRapFlavorParser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Class for loading and parsing G-code files
|
||||
class GCodeReader(MeshReader):
|
||||
_flavor_default = "Marlin"
|
||||
|
|
|
@ -123,7 +123,7 @@ UM.Dialog
|
|||
UM.TooltipArea {
|
||||
Layout.fillWidth:true
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","By default, white pixels represent high points on the mesh and black pixels represent low points on the mesh. Change this option to reverse the behavior such that black pixels represent high points on the mesh and white pixels represent low points on the mesh.")
|
||||
text: catalog.i18nc("@info:tooltip","For lithophanes dark pixels should correspond to thicker locations in order to block more light coming through. For height maps lighter pixels signify higher terrain, so lighter pixels should correspond to thicker locations in the generated 3D model.")
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
|
@ -134,9 +134,9 @@ UM.Dialog
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: image_color_invert
|
||||
objectName: "Image_Color_Invert"
|
||||
model: [ catalog.i18nc("@item:inlistbox","Lighter is higher"), catalog.i18nc("@item:inlistbox","Darker is higher") ]
|
||||
id: lighter_is_higher
|
||||
objectName: "Lighter_Is_Higher"
|
||||
model: [ catalog.i18nc("@item:inlistbox","Darker is higher"), catalog.i18nc("@item:inlistbox","Lighter is higher") ]
|
||||
width: 180 * screenScaleFactor
|
||||
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
|
||||
}
|
||||
|
|
|
@ -46,9 +46,9 @@ class ImageReader(MeshReader):
|
|||
|
||||
def _read(self, file_name):
|
||||
size = max(self._ui.getWidth(), self._ui.getDepth())
|
||||
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)
|
||||
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher)
|
||||
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher):
|
||||
scene_node = SceneNode()
|
||||
|
||||
mesh = MeshBuilder()
|
||||
|
@ -104,7 +104,7 @@ class ImageReader(MeshReader):
|
|||
|
||||
Job.yieldThread()
|
||||
|
||||
if image_color_invert:
|
||||
if not lighter_is_higher:
|
||||
height_data = 1 - height_data
|
||||
|
||||
for _ in range(0, blur_iterations):
|
||||
|
|
|
@ -30,10 +30,10 @@ class ImageReaderUI(QObject):
|
|||
self._width = self.default_width
|
||||
self._depth = self.default_depth
|
||||
|
||||
self.base_height = 1
|
||||
self.peak_height = 10
|
||||
self.base_height = 0.4
|
||||
self.peak_height = 2.5
|
||||
self.smoothing = 1
|
||||
self.image_color_invert = False;
|
||||
self.lighter_is_higher = False;
|
||||
|
||||
self._ui_lock = threading.Lock()
|
||||
self._cancelled = False
|
||||
|
@ -143,4 +143,4 @@ class ImageReaderUI(QObject):
|
|||
|
||||
@pyqtSlot(int)
|
||||
def onImageColorInvertChanged(self, value):
|
||||
self.image_color_invert = (value == 1)
|
||||
self.lighter_is_higher = (value == 1)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty, QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Extension import Extension
|
||||
|
@ -30,18 +30,22 @@ class ModelChecker(QObject, Extension):
|
|||
lifetime = 0,
|
||||
title = catalog.i18nc("@info:title", "3D Model Assistant"))
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(200)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self.onChanged)
|
||||
|
||||
Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onChanged)
|
||||
|
||||
## Pass-through to allow UM.Signal to connect with a pyqtSignal.
|
||||
def _onChanged(self, *args, **kwargs):
|
||||
# Ignore camera updates.
|
||||
if len(args) == 0:
|
||||
self.onChanged.emit()
|
||||
self._change_timer.start()
|
||||
return
|
||||
if not isinstance(args[0], Camera):
|
||||
self.onChanged.emit()
|
||||
self._change_timer.start()
|
||||
|
||||
## Called when plug-ins are initialized.
|
||||
#
|
||||
|
|
|
@ -112,7 +112,7 @@ class ChangeAtZ(Script):
|
|||
"e1_Change_speed":
|
||||
{
|
||||
"label": "Change Speed",
|
||||
"description": "Select if total speed (print and travel) has to be cahnged",
|
||||
"description": "Select if total speed (print and travel) has to be changed",
|
||||
"type": "bool",
|
||||
"default_value": false
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@ class DisplayFilenameAndLayerOnLCD(Script):
|
|||
name = self.getSettingValueByKey("name")
|
||||
else:
|
||||
name = Application.getInstance().getPrintInformation().jobName
|
||||
lcd_text = "M117 " + name + " layer: "
|
||||
lcd_text = "M117 " + name + " layer "
|
||||
i = 0
|
||||
for layer in data:
|
||||
display_text = lcd_text + str(i)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
@ -45,6 +45,22 @@ class FilamentChange(Script):
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 300.0
|
||||
},
|
||||
"x_position":
|
||||
{
|
||||
"label": "X Position",
|
||||
"description": "Extruder X position. The print head will move here for filament change.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0
|
||||
},
|
||||
"y_position":
|
||||
{
|
||||
"label": "Y Position",
|
||||
"description": "Extruder Y position. The print head will move here for filament change.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
@ -55,6 +71,8 @@ class FilamentChange(Script):
|
|||
layer_nums = self.getSettingValueByKey("layer_number")
|
||||
initial_retract = self.getSettingValueByKey("initial_retract")
|
||||
later_retract = self.getSettingValueByKey("later_retract")
|
||||
x_pos = self.getSettingValueByKey("x_position")
|
||||
y_pos = self.getSettingValueByKey("y_position")
|
||||
|
||||
color_change = "M600"
|
||||
|
||||
|
@ -64,6 +82,12 @@ class FilamentChange(Script):
|
|||
if later_retract is not None and later_retract > 0.:
|
||||
color_change = color_change + (" L%.2f" % later_retract)
|
||||
|
||||
if x_pos is not None:
|
||||
color_change = color_change + (" X%.2f" % x_pos)
|
||||
|
||||
if y_pos is not None:
|
||||
color_change = color_change + (" Y%.2f" % y_pos)
|
||||
|
||||
color_change = color_change + " ; Generated by FilamentChange plugin"
|
||||
|
||||
layer_targets = layer_nums.split(",")
|
||||
|
|
50
plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py
Normal file
50
plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Created by Wayne Porter
|
||||
|
||||
from ..Script import Script
|
||||
|
||||
class InsertAtLayerChange(Script):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Insert at layer change",
|
||||
"key": "InsertAtLayerChange",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings":
|
||||
{
|
||||
"insert_location":
|
||||
{
|
||||
"label": "When to insert",
|
||||
"description": "Whether to insert code before or after layer change.",
|
||||
"type": "enum",
|
||||
"options": {"before": "Before", "after": "After"},
|
||||
"default_value": "before"
|
||||
},
|
||||
"gcode_to_add":
|
||||
{
|
||||
"label": "GCODE to insert.",
|
||||
"description": "GCODE to add before or after layer change.",
|
||||
"type": "str",
|
||||
"default_value": ""
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n"
|
||||
for layer in data:
|
||||
# Check that a layer is being printed
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if ";LAYER:" in line:
|
||||
index = data.index(layer)
|
||||
if self.getSettingValueByKey("insert_location") == "before":
|
||||
layer = gcode_to_add + layer
|
||||
else:
|
||||
layer = layer + gcode_to_add
|
||||
|
||||
data[index] = layer
|
||||
break
|
||||
return data
|
95
plugins/PostProcessingPlugin/scripts/TimeLapse.py
Normal file
95
plugins/PostProcessingPlugin/scripts/TimeLapse.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
# Created by Wayne Porter
|
||||
|
||||
from ..Script import Script
|
||||
|
||||
class TimeLapse(Script):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Time Lapse",
|
||||
"key": "TimeLapse",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings":
|
||||
{
|
||||
"trigger_command":
|
||||
{
|
||||
"label": "Trigger camera command",
|
||||
"description": "Gcode command used to trigger camera.",
|
||||
"type": "str",
|
||||
"default_value": "M240"
|
||||
},
|
||||
"pause_length":
|
||||
{
|
||||
"label": "Pause length",
|
||||
"description": "How long to wait (in ms) after camera was triggered.",
|
||||
"type": "int",
|
||||
"default_value": 700,
|
||||
"minimum_value": 0,
|
||||
"unit": "ms"
|
||||
},
|
||||
"park_print_head":
|
||||
{
|
||||
"label": "Park Print Head",
|
||||
"description": "Park the print head out of the way. Assumes absolute positioning.",
|
||||
"type": "bool",
|
||||
"default_value": true
|
||||
},
|
||||
"head_park_x":
|
||||
{
|
||||
"label": "Park Print Head X",
|
||||
"description": "What X location does the head move to for photo.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"enabled": "park_print_head"
|
||||
},
|
||||
"head_park_y":
|
||||
{
|
||||
"label": "Park Print Head Y",
|
||||
"description": "What Y location does the head move to for photo.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 190,
|
||||
"enabled": "park_print_head"
|
||||
},
|
||||
"park_feed_rate":
|
||||
{
|
||||
"label": "Park Feed Rate",
|
||||
"description": "How fast does the head move to the park coordinates.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 9000,
|
||||
"enabled": "park_print_head"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
feed_rate = self.getSettingValueByKey("park_feed_rate")
|
||||
park_print_head = self.getSettingValueByKey("park_print_head")
|
||||
x_park = self.getSettingValueByKey("head_park_x")
|
||||
y_park = self.getSettingValueByKey("head_park_y")
|
||||
trigger_command = self.getSettingValueByKey("trigger_command")
|
||||
pause_length = self.getSettingValueByKey("pause_length")
|
||||
gcode_to_append = ";TimeLapse Begin\n"
|
||||
|
||||
if park_print_head:
|
||||
gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + ";Park print head\n"
|
||||
gcode_to_append += self.putValue(M = 400) + ";Wait for moves to finish\n"
|
||||
gcode_to_append += trigger_command + ";Snap Photo\n"
|
||||
gcode_to_append += self.putValue(G = 4, P = pause_length) + ";Wait for camera\n"
|
||||
gcode_to_append += ";TimeLapse End\n"
|
||||
for layer in data:
|
||||
# Check that a layer is being printed
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if ";LAYER:" in line:
|
||||
index = data.index(layer)
|
||||
layer += gcode_to_append
|
||||
|
||||
data[index] = layer
|
||||
break
|
||||
return data
|
|
@ -0,0 +1,46 @@
|
|||
# Cura PostProcessingPlugin
|
||||
# Author: Amanda de Castilho
|
||||
# Date: January 5,2019
|
||||
|
||||
# Description: This plugin overrides probing command and inserts code to ensure
|
||||
# previous probe measurements are loaded and bed leveling enabled
|
||||
# (searches for G29 and replaces it with M501 & M420 S1)
|
||||
# *** Assumes G29 is in the start code, will do nothing if it isn't ***
|
||||
|
||||
from ..Script import Script
|
||||
|
||||
class UsePreviousProbeMeasurements(Script):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Use Previous Probe Measurements",
|
||||
"key": "UsePreviousProbeMeasurements",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings":
|
||||
{
|
||||
"use_previous_measurements":
|
||||
{
|
||||
"label": "Use last measurement?",
|
||||
"description": "Selecting this will remove the G29 probing command and instead ensure previous measurements are loaded and enabled",
|
||||
"type": "bool",
|
||||
"default_value": false
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
text = "M501 ;load bed level data\nM420 S1 ;enable bed leveling"
|
||||
if self.getSettingValueByKey("use_previous_measurements"):
|
||||
for layer in data:
|
||||
layer_index = data.index(layer)
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("G29"):
|
||||
line_index = lines.index(line)
|
||||
lines[line_index] = text
|
||||
final_lines = "\n".join(lines)
|
||||
data[layer_index] = final_lines
|
||||
return data
|
|
@ -58,6 +58,7 @@ Item
|
|||
|
||||
Cura.ConfigurationMenu
|
||||
{
|
||||
id: printerSetup
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: itemRow.width - machineSelection.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width
|
||||
|
|
|
@ -50,7 +50,7 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
## View used to display g-code paths.
|
||||
class SimulationView(CuraView):
|
||||
# Must match SimulationView.qml
|
||||
# Must match SimulationViewMenuComponent.qml
|
||||
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
|
||||
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||
|
|
|
@ -49,12 +49,13 @@ fragment =
|
|||
// discard movements
|
||||
discard;
|
||||
}
|
||||
// support: 4, 5, 7, 10
|
||||
// support: 4, 5, 7, 10, 11 (prime tower)
|
||||
if ((u_show_helpers == 0) && (
|
||||
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
|
||||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
|
||||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
|
||||
((v_line_type >= 10.5) && (v_line_type <= 11.5))
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ geometry41core =
|
|||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {
|
||||
|
|
|
@ -45,19 +45,23 @@ fragment =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5))
|
||||
{ // actually, 8 and 9
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
// support: 4, 5, 7, 10
|
||||
// support: 4, 5, 7, 10, 11
|
||||
if ((u_show_helpers == 0) && (
|
||||
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
|
||||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
|
||||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
|
||||
)) {
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
|
||||
((v_line_type >= 10.5) && (v_line_type <= 11.5))
|
||||
))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
// skin: 1, 2, 3
|
||||
if ((u_show_skin == 0) && (
|
||||
(v_line_type >= 0.5) && (v_line_type <= 3.5)
|
||||
|
@ -65,7 +69,8 @@ fragment =
|
|||
discard;
|
||||
}
|
||||
// infill:
|
||||
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
|
||||
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5))
|
||||
{
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
@ -117,12 +122,13 @@ fragment41core =
|
|||
// discard movements
|
||||
discard;
|
||||
}
|
||||
// helpers: 4, 5, 7, 10
|
||||
// helpers: 4, 5, 7, 10, 11
|
||||
if ((u_show_helpers == 0) && (
|
||||
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
|
||||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
|
||||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
|
||||
((v_line_type >= 10.5) && (v_line_type <= 11.5))
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ Item
|
|||
anchors.top: tile.top
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
|
||||
packageData: model
|
||||
}
|
||||
|
||||
ToolboxCompatibilityChart
|
||||
|
|
|
@ -12,6 +12,7 @@ Column
|
|||
property bool installed: toolbox.isInstalled(model.id)
|
||||
property bool canUpdate: toolbox.canUpdate(model.id)
|
||||
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||
property var packageData
|
||||
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
spacing: UM.Theme.getSize("narrow_margin").height
|
||||
|
@ -66,6 +67,27 @@ Column
|
|||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
property var whereToBuyUrl:
|
||||
{
|
||||
var pg_name = "whereToBuy"
|
||||
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
|
||||
}
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Buy material spools</a>")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
visible: whereToBuyUrl != undefined
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: Qt.openUrlExternally(parent.whereToBuyUrl)
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxProgressButton
|
||||
{
|
||||
id: updateButton
|
||||
|
|
|
@ -81,7 +81,7 @@ Item
|
|||
}
|
||||
sourceSize.height: height
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../images/installed_check.svg"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ Rectangle
|
|||
right: parent.right
|
||||
}
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../images/installed_check.svg"
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ ScrollView
|
|||
}
|
||||
Rectangle
|
||||
{
|
||||
id: installedPlugins
|
||||
color: "transparent"
|
||||
width: parent.width
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
|
@ -74,6 +75,7 @@ ScrollView
|
|||
|
||||
Rectangle
|
||||
{
|
||||
id: installedMaterials
|
||||
color: "transparent"
|
||||
width: parent.width
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
|
|
|
@ -649,6 +649,7 @@ class Toolbox(QObject, Extension):
|
|||
Logger.log("w", "Received invalid JSON for %s.", response_type)
|
||||
break
|
||||
else:
|
||||
Logger.log("w", "Unable to connect with the server, we got a response code %s while trying to connect to %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
|
||||
self.setViewPage("errored")
|
||||
self.resetDownload()
|
||||
elif reply.operation() == QNetworkAccessManager.PutOperation:
|
||||
|
|
41
plugins/UFPReader/UFPReader.py
Normal file
41
plugins/UFPReader/UFPReader.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import cast
|
||||
|
||||
from Charon.VirtualFile import VirtualFile
|
||||
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from plugins.GCodeReader.GCodeReader import GCodeReader
|
||||
|
||||
|
||||
class UFPReader(MeshReader):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Ultimaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
self._supported_extensions = [".ufp"]
|
||||
|
||||
def _read(self, file_name: str) -> CuraSceneNode:
|
||||
# Open the file
|
||||
archive = VirtualFile()
|
||||
archive.open(file_name)
|
||||
# Get the gcode data from the file
|
||||
gcode_data = archive.getData("/3D/model.gcode")
|
||||
# Convert the bytes stream to string
|
||||
gcode_stream = gcode_data["/3D/model.gcode"].decode("utf-8")
|
||||
|
||||
# Open the GCodeReader to parse the data
|
||||
gcode_reader = cast(GCodeReader, PluginRegistry.getInstance().getPluginObject("GCodeReader"))
|
||||
gcode_reader.preReadFromStream(gcode_stream)
|
||||
return gcode_reader.readFromStream(gcode_stream)
|
26
plugins/UFPReader/__init__.py
Normal file
26
plugins/UFPReader/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import UFPReader
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"mesh_reader": [
|
||||
{
|
||||
"mime_type": "application/x-ufp",
|
||||
"extension": "ufp",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Ultimaker Format Package")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
app.addNonSliceableExtension(".ufp")
|
||||
return {"mesh_reader": UFPReader.UFPReader()}
|
||||
|
8
plugins/UFPReader/plugin.json
Normal file
8
plugins/UFPReader/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "UFP Reader",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||
"supported_sdk_versions": ["6.0.0"],
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -28,7 +28,7 @@ class UFPWriter(MeshWriter):
|
|||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Cura UFP File",
|
||||
comment = "Ultimaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
|
|
|
@ -210,7 +210,7 @@ Item
|
|||
|
||||
Label
|
||||
{
|
||||
text: "All jobs are printed."
|
||||
text: i18n.i18nc("@info", "All jobs are printed.")
|
||||
color: UM.Theme.getColor("monitor_text_primary")
|
||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||
}
|
||||
|
|
|
@ -50,7 +50,17 @@ Component
|
|||
MonitorCarousel
|
||||
{
|
||||
id: carousel
|
||||
printers: OutputDevice.receivedPrintJobs ? OutputDevice.printers : [null]
|
||||
printers:
|
||||
{
|
||||
// When printing over the cloud we don't recieve print jobs until there is one, so
|
||||
// unless there's at least one print job we'll be stuck with skeleton loading
|
||||
// indefinitely.
|
||||
if (Cura.MachineManager.activeMachineIsUsingCloudConnection || OutputDevice.receivedPrintJobs)
|
||||
{
|
||||
return OutputDevice.printers
|
||||
}
|
||||
return [null]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ from UM.Backend.Backend import BackendState
|
|||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
@ -82,8 +84,11 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._account = api_client.account
|
||||
|
||||
# We use the Cura Connect monitor tab to get most functionality right away.
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"../../resources/qml/MonitorStage.qml")
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
|
||||
# Trigger the printersChanged signal when the private signal is triggered.
|
||||
self.printersChanged.connect(self._clusterPrintersChanged)
|
||||
|
|
|
@ -11,8 +11,8 @@ I18N_CATALOG = i18nCatalog("cura")
|
|||
class CloudProgressMessage(Message):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
text = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
|
||||
title = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
|
||||
title = I18N_CATALOG.i18nc("@info:status", "Sending Print Job"),
|
||||
text = I18N_CATALOG.i18nc("@info:status", "Uploading via Ultimaker Cloud"),
|
||||
progress = -1,
|
||||
lifetime = 0,
|
||||
dismissable = False,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Any, cast, Tuple, Union, Optional, Dict, List
|
||||
|
@ -10,13 +10,13 @@ import os
|
|||
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.FileHandler.WriteFileJob import WriteFileJob # To call the file writer asynchronously.
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
|
@ -65,7 +65,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
||||
self._received_print_jobs = False # type: bool
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorStage.qml")
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
|
||||
# Trigger the printersChanged signal when the private signal is triggered
|
||||
self.printersChanged.connect(self._clusterPrintersChanged)
|
||||
|
@ -126,7 +130,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
def _spawnPrinterSelectionDialog(self):
|
||||
if self._printer_selection_dialog is None:
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/PrintWindow.qml")
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "PrintWindow.qml"
|
||||
)
|
||||
self._printer_selection_dialog = self._application.createQmlComponent(path, {"OutputDevice": self})
|
||||
if self._printer_selection_dialog is not None:
|
||||
self._printer_selection_dialog.show()
|
||||
|
@ -197,7 +205,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0,
|
||||
dismissable = False, progress = -1,
|
||||
title = i18n_catalog.i18nc("@info:title", "Sending Data"))
|
||||
self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None,
|
||||
self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = "",
|
||||
description = "")
|
||||
self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
|
||||
self._progress_message.show()
|
||||
|
@ -263,7 +271,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
|
||||
# timeout responses if this happens.
|
||||
self._last_response_time = time()
|
||||
if self._progress_message is not None and new_progress > self._progress_message.getProgress():
|
||||
if self._progress_message is not None and new_progress != self._progress_message.getProgress():
|
||||
self._progress_message.show() # Ensure that the message is visible.
|
||||
self._progress_message.setProgress(bytes_sent / bytes_total * 100)
|
||||
|
||||
|
@ -275,7 +283,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
|
||||
lifetime=5, dismissable=True,
|
||||
title=i18n_catalog.i18nc("@info:title", "Data Sent"))
|
||||
self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon=None,
|
||||
self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Monitor"), icon = "",
|
||||
description="")
|
||||
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
|
||||
self._success_message.show()
|
||||
|
@ -387,9 +395,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
newly_finished_jobs = [job for job in finished_jobs if job not in self._finished_jobs and job.owner == username]
|
||||
for job in newly_finished_jobs:
|
||||
if job.assignedPrinter:
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.".format(printer_name=job.assignedPrinter.name, job_name = job.name))
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.").format(printer_name=job.assignedPrinter.name, job_name = job.name)
|
||||
else:
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.".format(job_name = job.name))
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.").format(job_name = job.name)
|
||||
job_completed_message = Message(text=job_completed_text, title = i18n_catalog.i18nc("@info:status", "Print finished"))
|
||||
job_completed_message.show()
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
@ -12,10 +10,13 @@ from cura.PrinterOutputDevice import ConnectionType
|
|||
from cura.Settings.ContainerManager import ContainerManager
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
from PyQt5.QtCore import QTimer, QUrl
|
||||
|
@ -76,7 +77,11 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self.setIconName("print")
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorItem.qml")
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
|
||||
self._output_controller = LegacyUM3PrinterOutputController(self)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
|
@ -33,7 +33,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
|
|||
data = "{\"target\": \"%s\"}" % state
|
||||
self._output_device.put("print_job/state", data, on_finished=None)
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float):
|
||||
data = str(temperature)
|
||||
self._output_device.put("printer/bed/temperature/target", data, on_finished = self._onPutBedTemperatureCompleted)
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, TYPE_CHECKING, Set, Optional
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
# Absolute imports don't work in plugins
|
||||
from .Models import ClusterMaterial, LocalMaterial
|
||||
|
@ -86,8 +86,8 @@ class SendMaterialJob(Job):
|
|||
#
|
||||
# \param materials_to_send A set with id's of materials that must be sent.
|
||||
def _sendMaterials(self, materials_to_send: Set[str]) -> None:
|
||||
container_registry = Application.getInstance().getContainerRegistry()
|
||||
material_manager = Application.getInstance().getMaterialManager()
|
||||
container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
|
||||
for root_material_id in material_group_dict:
|
||||
|
@ -166,7 +166,7 @@ class SendMaterialJob(Job):
|
|||
# \return a dictionary of LocalMaterial objects by GUID
|
||||
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
|
||||
result = {} # type: Dict[str, LocalMaterial]
|
||||
material_manager = Application.getInstance().getMaterialManager()
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
|
||||
material_group_dict = material_manager.getAllMaterialGroups()
|
||||
|
||||
|
|
|
@ -14,12 +14,14 @@ from PyQt5.QtGui import QDesktopServices
|
|||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.Settings.GlobalStack import GlobalStack # typing
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.Version import Version
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
|
||||
from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
|
||||
|
@ -452,39 +454,20 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
def _onCloudFlowPossible(self) -> None:
|
||||
# Cloud flow is possible, so show the message
|
||||
if not self._start_cloud_flow_message:
|
||||
self._start_cloud_flow_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 0,
|
||||
image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
|
||||
"resources", "svg", "cloud-flow-start.svg")),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
|
||||
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
||||
option_state = False
|
||||
)
|
||||
self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "")
|
||||
self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain)
|
||||
self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted)
|
||||
self._createCloudFlowStartMessage()
|
||||
if self._start_cloud_flow_message and not self._start_cloud_flow_message.visible:
|
||||
self._start_cloud_flow_message.show()
|
||||
return
|
||||
|
||||
def _onCloudPrintingConfigured(self) -> None:
|
||||
if self._start_cloud_flow_message:
|
||||
# Hide the cloud flow start message if it was hanging around already
|
||||
# For example: if the user already had the browser openen and made the association themselves
|
||||
if self._start_cloud_flow_message and self._start_cloud_flow_message.visible:
|
||||
self._start_cloud_flow_message.hide()
|
||||
self._start_cloud_flow_message = None
|
||||
|
||||
# Show the successful pop-up
|
||||
if not self._start_cloud_flow_message:
|
||||
self._cloud_flow_complete_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 30,
|
||||
image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
|
||||
"resources", "svg", "cloud-flow-completed.svg")),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
|
||||
)
|
||||
# Don't show the review connection link if we're not on the local network
|
||||
if self._application.getMachineManager().activeMachineHasNetworkConnection:
|
||||
self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
|
||||
self._cloud_flow_complete_message.actionTriggered.connect(self._onReviewCloudConnection)
|
||||
# Cloud flow is complete, so show the message
|
||||
if not self._cloud_flow_complete_message:
|
||||
self._createCloudFlowCompleteMessage()
|
||||
if self._cloud_flow_complete_message and not self._cloud_flow_complete_message.visible:
|
||||
self._cloud_flow_complete_message.show()
|
||||
|
||||
# Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers
|
||||
|
@ -517,11 +500,40 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
return
|
||||
|
||||
def _onMachineSwitched(self) -> None:
|
||||
if self._start_cloud_flow_message is not None:
|
||||
# Hide any left over messages
|
||||
if self._start_cloud_flow_message is not None and self._start_cloud_flow_message.visible:
|
||||
self._start_cloud_flow_message.hide()
|
||||
self._start_cloud_flow_message = None
|
||||
if self._cloud_flow_complete_message is not None:
|
||||
if self._cloud_flow_complete_message is not None and self._cloud_flow_complete_message.visible:
|
||||
self._cloud_flow_complete_message.hide()
|
||||
self._cloud_flow_complete_message = None
|
||||
|
||||
# Check for cloud flow again with newly selected machine
|
||||
self.checkCloudFlowIsPossible()
|
||||
|
||||
def _createCloudFlowStartMessage(self):
|
||||
self._start_cloud_flow_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 0,
|
||||
image_source = QUrl.fromLocalFile(os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "svg", "cloud-flow-start.svg"
|
||||
)),
|
||||
image_caption = i18n_catalog.i18nc("@info:status Ultimaker Cloud is a brand name and shouldn't be translated.", "Connect to Ultimaker Cloud"),
|
||||
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
||||
option_state = False
|
||||
)
|
||||
self._start_cloud_flow_message.addAction("", i18n_catalog.i18nc("@action", "Get started"), "", "")
|
||||
self._start_cloud_flow_message.optionToggled.connect(self._onDontAskMeAgain)
|
||||
self._start_cloud_flow_message.actionTriggered.connect(self._onCloudFlowStarted)
|
||||
|
||||
def _createCloudFlowCompleteMessage(self):
|
||||
self._cloud_flow_complete_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 30,
|
||||
image_source = QUrl.fromLocalFile(os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "svg", "cloud-flow-completed.svg"
|
||||
)),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
|
||||
)
|
||||
self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
|
||||
self._cloud_flow_complete_message.actionTriggered.connect(self._onReviewCloudConnection)
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# Workaround for a race condition on certain systems where there
|
||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
||||
# first seems to prevent Sip from going into a state where it
|
||||
# tries to create PyQt objects on a non-main thread.
|
||||
import Arcus #@UnusedImport
|
||||
import Savitar #@UnusedImport
|
|
@ -18,7 +18,7 @@ class AutoDetectBaudJob(Job):
|
|||
def __init__(self, serial_port: int) -> None:
|
||||
super().__init__()
|
||||
self._serial_port = serial_port
|
||||
self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
|
||||
self._all_baud_rates = [115200, 250000, 500000, 230400, 57600, 38400, 19200, 9600]
|
||||
|
||||
def run(self) -> None:
|
||||
Logger.log("d", "Auto detect baud rate started.")
|
||||
|
@ -74,7 +74,7 @@ class AutoDetectBaudJob(Job):
|
|||
line = serial.readline()
|
||||
if b"ok" in line and b"T:" in line:
|
||||
successful_responses += 1
|
||||
if successful_responses >= 3:
|
||||
if successful_responses >= 1:
|
||||
self.setResult(baud_rate)
|
||||
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
|
||||
serial = self._serial_port, baud_rate = baud_rate, retry = retry, time_elapsed = time() - start_timeout_time))
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Mesh.MeshWriter import MeshWriter #To get the g-code output.
|
||||
from UM.PluginRegistry import PluginRegistry #To get the g-code output.
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
@ -15,10 +18,11 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
|||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
from .AvrFirmwareUpdater import AvrFirmwareUpdater
|
||||
|
||||
from io import StringIO #To write the g-code output.
|
||||
from queue import Queue
|
||||
from serial import Serial, SerialException, SerialTimeoutException
|
||||
from threading import Thread, Event
|
||||
from time import time
|
||||
from queue import Queue
|
||||
from typing import Union, Optional, List, cast
|
||||
|
||||
import re
|
||||
|
@ -49,7 +53,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._baud_rate = baud_rate
|
||||
|
||||
self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
|
||||
self._all_baud_rates = [115200, 250000, 500000, 230400, 57600, 38400, 19200, 9600]
|
||||
|
||||
# Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
|
||||
self._update_thread = Thread(target = self._update, daemon = True)
|
||||
|
@ -114,28 +118,29 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# \param kwargs Keyword arguments.
|
||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
|
||||
if self._is_printing:
|
||||
return # Aleady printing
|
||||
return # Already printing
|
||||
self.writeStarted.emit(self)
|
||||
# cancel any ongoing preheat timer before starting a print
|
||||
self._printers[0].getController().stopPreheatTimers()
|
||||
|
||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
# find the G-code for the active build plate to print
|
||||
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict")
|
||||
gcode_list = gcode_dict[active_build_plate_id]
|
||||
#Find the g-code to print.
|
||||
gcode_textio = StringIO()
|
||||
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||
success = gcode_writer.write(gcode_textio, None)
|
||||
if not success:
|
||||
return
|
||||
|
||||
self._printGCode(gcode_list)
|
||||
self._printGCode(gcode_textio.getvalue())
|
||||
|
||||
## Start a print based on a g-code.
|
||||
# \param gcode_list List with gcode (strings).
|
||||
def _printGCode(self, gcode_list: List[str]):
|
||||
# \param gcode The g-code to print.
|
||||
def _printGCode(self, gcode: str):
|
||||
self._gcode.clear()
|
||||
self._paused = False
|
||||
|
||||
for layer in gcode_list:
|
||||
self._gcode.extend(layer.split("\n"))
|
||||
self._gcode.extend(gcode.split("\n"))
|
||||
|
||||
# Reset line number. If this is not done, first line is sometimes ignored
|
||||
self._gcode.insert(0, "M110")
|
||||
|
@ -243,7 +248,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._last_temperature_request = time()
|
||||
|
||||
if re.search(b"[B|T\d*]: ?\d+\.?\d*", line): # Temperature message. 'T:' for extruder and 'B:' for bed
|
||||
extruder_temperature_matches = re.findall(b"T(\d*): ?(\d+\.?\d*) ?\/?(\d+\.?\d*)?", line)
|
||||
extruder_temperature_matches = re.findall(b"T(\d*): ?(\d+\.?\d*)\s*\/?(\d+\.?\d*)?", line)
|
||||
# Update all temperature values
|
||||
matched_extruder_nrs = []
|
||||
for match in extruder_temperature_matches:
|
||||
|
@ -265,7 +270,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if match[2]:
|
||||
extruder.updateTargetHotendTemperature(float(match[2]))
|
||||
|
||||
bed_temperature_matches = re.findall(b"B: ?(\d+\.?\d*) ?\/?(\d+\.?\d*) ?", line)
|
||||
bed_temperature_matches = re.findall(b"B: ?(\d+\.?\d*)\s*\/?(\d+\.?\d*)?", line)
|
||||
if bed_temperature_matches:
|
||||
match = bed_temperature_matches[0]
|
||||
if match[0]:
|
||||
|
|
|
@ -63,9 +63,9 @@ _RENAMED_MATERIAL_PROFILES = {
|
|||
## Upgrades configurations from the state they were in at version 3.4 to the
|
||||
# state they should be in at version 3.5.
|
||||
class VersionUpgrade34to35(VersionUpgrade):
|
||||
## Gets the version number from a CFG file in Uranium's 3.3 format.
|
||||
## Gets the version number from a CFG file in Uranium's 3.4 format.
|
||||
#
|
||||
# Since the format may change, this is implemented for the 3.3 format only
|
||||
# Since the format may change, this is implemented for the 3.4 format only
|
||||
# and needs to be included in the version upgrade system rather than
|
||||
# globally in Uranium.
|
||||
#
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import configparser
|
||||
import io
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
_renamed_quality_profiles = {
|
||||
"gmax15plus_pla_dual_normal": "gmax15plus_global_dual_normal",
|
||||
"gmax15plus_pla_dual_thick": "gmax15plus_global_dual_thick",
|
||||
"gmax15plus_pla_dual_thin": "gmax15plus_global_dual_thin",
|
||||
"gmax15plus_pla_dual_very_thick": "gmax15plus_global_dual_very_thick",
|
||||
"gmax15plus_pla_normal": "gmax15plus_global_normal",
|
||||
"gmax15plus_pla_thick": "gmax15plus_global_thick",
|
||||
"gmax15plus_pla_thin": "gmax15plus_global_thin",
|
||||
"gmax15plus_pla_very_thick": "gmax15plus_global_very_thick"
|
||||
} # type: Dict[str, str]
|
||||
|
||||
## Upgrades configurations from the state they were in at version 4.0 to the
|
||||
# state they should be in at version 4.1.
|
||||
class VersionUpgrade40to41(VersionUpgrade):
|
||||
## Gets the version number from a CFG file in Uranium's 4.0 format.
|
||||
#
|
||||
# Since the format may change, this is implemented for the 4.0 format only
|
||||
# and needs to be included in the version upgrade system rather than
|
||||
# globally in Uranium.
|
||||
#
|
||||
# \param serialised The serialised form of a CFG file.
|
||||
# \return The version number stored in the CFG file.
|
||||
# \raises ValueError The format of the version number in the file is
|
||||
# incorrect.
|
||||
# \raises KeyError The format of the file is incorrect.
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades instance containers to have the new version
|
||||
# number.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "7"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
## Upgrades Preferences to have the new version number.
|
||||
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "6"
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "7"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
## Upgrades stacks to have the new version number.
|
||||
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "7"
|
||||
|
||||
#Update the name of the quality profile.
|
||||
if parser["containers"]["4"] in _renamed_quality_profiles:
|
||||
parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]]
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
59
plugins/VersionUpgrade/VersionUpgrade40to41/__init__.py
Normal file
59
plugins/VersionUpgrade/VersionUpgrade40to41/__init__.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Any, Dict, TYPE_CHECKING
|
||||
|
||||
from . import VersionUpgrade40to41
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Application import Application
|
||||
|
||||
upgrade = VersionUpgrade40to41.VersionUpgrade40to41()
|
||||
|
||||
def getMetaData() -> Dict[str, Any]:
|
||||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 6000006): ("preferences", 6000007, upgrade.upgradePreferences),
|
||||
("machine_stack", 4000006): ("machine_stack", 4000007, upgrade.upgradeStack),
|
||||
("extruder_train", 4000006): ("extruder_train", 4000007, upgrade.upgradeStack),
|
||||
("definition_changes", 4000006): ("definition_changes", 4000007, upgrade.upgradeInstanceContainer),
|
||||
("quality_changes", 4000006): ("quality_changes", 4000007, upgrade.upgradeInstanceContainer),
|
||||
("quality", 4000006): ("quality", 4000007, upgrade.upgradeInstanceContainer),
|
||||
("user", 4000006): ("user", 4000007, upgrade.upgradeInstanceContainer),
|
||||
},
|
||||
"sources": {
|
||||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"machine_stack": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
},
|
||||
"extruder_train": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./extruders"}
|
||||
},
|
||||
"definition_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./definition_changes"}
|
||||
},
|
||||
"quality_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality_changes"}
|
||||
},
|
||||
"quality": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality"}
|
||||
},
|
||||
"user": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register(app: "Application") -> Dict[str, Any]:
|
||||
return { "version_upgrade": upgrade }
|
8
plugins/VersionUpgrade/VersionUpgrade40to41/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade40to41/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 4.0 to 4.1",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
|
||||
"api": "6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -17,6 +17,7 @@ from UM.View.RenderBatch import RenderBatch
|
|||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.ConvexHullNode import ConvexHullNode
|
||||
|
||||
from . import XRayPass
|
||||
|
||||
|
@ -41,6 +42,10 @@ class XRayView(View):
|
|||
self._xray_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("xray").getRgb()))
|
||||
|
||||
for node in BreadthFirstIterator(scene.getRoot()):
|
||||
# We do not want to render ConvexHullNode as it conflicts with the bottom of the X-Ray (z-fighting).
|
||||
if type(node) is ConvexHullNode:
|
||||
continue
|
||||
|
||||
if not node.render(renderer):
|
||||
if node.getMeshData() and node.isVisible():
|
||||
renderer.queueNode(node,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import copy
|
||||
|
@ -6,7 +6,7 @@ import io
|
|||
import json #To parse the product-to-id mapping file.
|
||||
import os.path #To find the product-to-id mapping.
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast, Set
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from UM.Resources import Resources
|
||||
|
@ -60,6 +60,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
def setMetaDataEntry(self, key, value, apply_to_all = True):
|
||||
registry = ContainerRegistry.getInstance()
|
||||
if registry.isReadOnly(self.getId()):
|
||||
Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId()))
|
||||
return
|
||||
|
||||
# Prevent recursion
|
||||
|
@ -119,7 +120,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
## Overridden from InstanceContainer
|
||||
# base file: common settings + supported machines
|
||||
# machine / variant combination: only changes for itself.
|
||||
def serialize(self, ignored_metadata_keys: Optional[set] = None):
|
||||
def serialize(self, ignored_metadata_keys: Optional[Set[str]] = None):
|
||||
registry = ContainerRegistry.getInstance()
|
||||
|
||||
base_file = self.getMetaDataEntry("base_file", "")
|
||||
|
@ -944,9 +945,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
|
||||
for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces):
|
||||
machine_compatibility = common_compatibility
|
||||
for entry in machine.iterfind("./um:setting", cls.__namespaces):
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
for entry in machine.iterfind("./um:setting[@key='hardware compatible']", cls.__namespaces):
|
||||
if entry.text is not None:
|
||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
|
||||
|
@ -1019,9 +1018,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
continue
|
||||
|
||||
hotend_compatibility = machine_compatibility
|
||||
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
for entry in hotend.iterfind("./um:setting[@key='hardware compatible']", cls.__namespaces):
|
||||
if entry.text is not None:
|
||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from .XmlMaterialProfile import XmlMaterialProfile
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ def getMetaData():
|
|||
"mimetype": "application/x-ultimaker-material-profile"
|
||||
},
|
||||
"version_upgrade": {
|
||||
("materials", 1000000): ("materials", 1000006, upgrader.upgradeMaterial),
|
||||
("materials", 1000000): ("materials", 1000007, upgrader.upgradeMaterial),
|
||||
},
|
||||
"sources": {
|
||||
"materials": {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"display_name": "3MF Reader",
|
||||
"description": "Provides support for reading 3MF files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -23,7 +23,7 @@
|
|||
"display_name": "3MF Writer",
|
||||
"description": "Provides support for writing 3MF files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -40,7 +40,7 @@
|
|||
"display_name": "Change Log",
|
||||
"description": "Shows changes since latest checked version.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"display_name": "Cura Backups",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"package_version": "1.2.0",
|
||||
"sdk_version": 6,
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"display_name": "CuraEngine Backend",
|
||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"display_name": "Cura Profile Reader",
|
||||
"description": "Provides support for importing Cura profiles.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -108,7 +108,7 @@
|
|||
"display_name": "Cura Profile Writer",
|
||||
"description": "Provides support for exporting Cura profiles.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -125,7 +125,7 @@
|
|||
"display_name": "Firmware Update Checker",
|
||||
"description": "Checks for firmware updates.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -142,7 +142,7 @@
|
|||
"display_name": "Firmware Updater",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -159,7 +159,7 @@
|
|||
"display_name": "Compressed G-code Reader",
|
||||
"description": "Reads g-code from a compressed archive.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -176,7 +176,7 @@
|
|||
"display_name": "Compressed G-code Writer",
|
||||
"description": "Writes g-code to a compressed archive.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -193,7 +193,7 @@
|
|||
"display_name": "G-Code Profile Reader",
|
||||
"description": "Provides support for importing profiles from g-code files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -210,7 +210,7 @@
|
|||
"display_name": "G-Code Reader",
|
||||
"description": "Allows loading and displaying G-code files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "VictorLarchenko",
|
||||
|
@ -227,7 +227,7 @@
|
|||
"display_name": "G-Code Writer",
|
||||
"description": "Writes g-code to a file.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -244,7 +244,7 @@
|
|||
"display_name": "Image Reader",
|
||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -261,7 +261,7 @@
|
|||
"display_name": "Legacy Cura Profile Reader",
|
||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -278,7 +278,7 @@
|
|||
"display_name": "Machine Settings Action",
|
||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "fieldOfView",
|
||||
|
@ -295,7 +295,7 @@
|
|||
"display_name": "Model Checker",
|
||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -312,7 +312,7 @@
|
|||
"display_name": "Monitor Stage",
|
||||
"description": "Provides a monitor stage in Cura.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -329,7 +329,7 @@
|
|||
"display_name": "Per-Object Settings Tool",
|
||||
"description": "Provides the per-model settings.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -346,7 +346,7 @@
|
|||
"display_name": "Post Processing",
|
||||
"description": "Extension that allows for user created scripts for post processing.",
|
||||
"package_version": "2.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -363,7 +363,7 @@
|
|||
"display_name": "Prepare Stage",
|
||||
"description": "Provides a prepare stage in Cura.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -380,7 +380,7 @@
|
|||
"display_name": "Preview Stage",
|
||||
"description": "Provides a preview stage in Cura.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -397,7 +397,7 @@
|
|||
"display_name": "Removable Drive Output Device",
|
||||
"description": "Provides removable drive hotplugging and writing support.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -414,7 +414,7 @@
|
|||
"display_name": "Simulation View",
|
||||
"description": "Provides the Simulation view.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -431,7 +431,7 @@
|
|||
"display_name": "Slice Info",
|
||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -448,7 +448,7 @@
|
|||
"display_name": "Solid View",
|
||||
"description": "Provides a normal solid mesh view.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -465,7 +465,7 @@
|
|||
"display_name": "Support Eraser Tool",
|
||||
"description": "Creates an eraser mesh to block the printing of support in certain places.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -482,7 +482,24 @@
|
|||
"display_name": "Toolbox",
|
||||
"description": "Find, manage and install new Cura packages.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UFPReader": {
|
||||
"package_info": {
|
||||
"package_id": "UFPReader",
|
||||
"package_type": "plugin",
|
||||
"display_name": "UFP Reader",
|
||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -499,7 +516,7 @@
|
|||
"display_name": "UFP Writer",
|
||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -516,7 +533,7 @@
|
|||
"display_name": "Ultimaker Machine Actions",
|
||||
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -533,7 +550,7 @@
|
|||
"display_name": "UM3 Network Printing",
|
||||
"description": "Manages network connections to Ultimaker 3 printers.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -550,7 +567,7 @@
|
|||
"display_name": "USB Printing",
|
||||
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
||||
"package_version": "1.0.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -567,7 +584,7 @@
|
|||
"display_name": "User Agreement",
|
||||
"description": "Ask the user once if he/she agrees with our license.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -584,7 +601,7 @@
|
|||
"display_name": "Version Upgrade 2.1 to 2.2",
|
||||
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -601,7 +618,7 @@
|
|||
"display_name": "Version Upgrade 2.2 to 2.4",
|
||||
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -618,7 +635,7 @@
|
|||
"display_name": "Version Upgrade 2.5 to 2.6",
|
||||
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -635,7 +652,7 @@
|
|||
"display_name": "Version Upgrade 2.6 to 2.7",
|
||||
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -652,7 +669,7 @@
|
|||
"display_name": "Version Upgrade 2.7 to 3.0",
|
||||
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -669,7 +686,7 @@
|
|||
"display_name": "Version Upgrade 3.0 to 3.1",
|
||||
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -686,7 +703,7 @@
|
|||
"display_name": "Version Upgrade 3.2 to 3.3",
|
||||
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -703,7 +720,7 @@
|
|||
"display_name": "Version Upgrade 3.3 to 3.4",
|
||||
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -720,7 +737,7 @@
|
|||
"display_name": "Version Upgrade 3.4 to 3.5",
|
||||
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -737,7 +754,24 @@
|
|||
"display_name": "Version Upgrade 3.5 to 4.0",
|
||||
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"VersionUpgrade40to41": {
|
||||
"package_info": {
|
||||
"package_id": "VersionUpgrade40to41",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Version Upgrade 4.0 to 4.1",
|
||||
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -754,7 +788,7 @@
|
|||
"display_name": "X3D Reader",
|
||||
"description": "Provides support for reading X3D files.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "SevaAlekseyev",
|
||||
|
@ -771,7 +805,7 @@
|
|||
"display_name": "XML Material Profiles",
|
||||
"description": "Provides capabilities to read and write XML-based material profiles.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -788,7 +822,7 @@
|
|||
"display_name": "X-Ray View",
|
||||
"description": "Provides the X-Ray view.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -805,7 +839,7 @@
|
|||
"display_name": "Generic ABS",
|
||||
"description": "The generic ABS profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -823,7 +857,7 @@
|
|||
"display_name": "Generic BAM",
|
||||
"description": "The generic BAM profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -841,7 +875,7 @@
|
|||
"display_name": "Generic CFF CPE",
|
||||
"description": "The generic CFF CPE profile which other profiles can be based upon.",
|
||||
"package_version": "1.1.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -859,7 +893,7 @@
|
|||
"display_name": "Generic CFF PA",
|
||||
"description": "The generic CFF PA profile which other profiles can be based upon.",
|
||||
"package_version": "1.1.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -877,7 +911,7 @@
|
|||
"display_name": "Generic CPE",
|
||||
"description": "The generic CPE profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -895,7 +929,7 @@
|
|||
"display_name": "Generic CPE+",
|
||||
"description": "The generic CPE+ profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -913,7 +947,7 @@
|
|||
"display_name": "Generic GFF CPE",
|
||||
"description": "The generic GFF CPE profile which other profiles can be based upon.",
|
||||
"package_version": "1.1.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -931,7 +965,7 @@
|
|||
"display_name": "Generic GFF PA",
|
||||
"description": "The generic GFF PA profile which other profiles can be based upon.",
|
||||
"package_version": "1.1.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -949,7 +983,7 @@
|
|||
"display_name": "Generic HIPS",
|
||||
"description": "The generic HIPS profile which other profiles can be based upon.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -967,7 +1001,7 @@
|
|||
"display_name": "Generic Nylon",
|
||||
"description": "The generic Nylon profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -985,7 +1019,7 @@
|
|||
"display_name": "Generic PC",
|
||||
"description": "The generic PC profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1003,7 +1037,7 @@
|
|||
"display_name": "Generic PETG",
|
||||
"description": "The generic PETG profile which other profiles can be based upon.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1021,7 +1055,7 @@
|
|||
"display_name": "Generic PLA",
|
||||
"description": "The generic PLA profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1039,7 +1073,7 @@
|
|||
"display_name": "Generic PP",
|
||||
"description": "The generic PP profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1057,7 +1091,7 @@
|
|||
"display_name": "Generic PVA",
|
||||
"description": "The generic PVA profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1075,7 +1109,7 @@
|
|||
"display_name": "Generic Tough PLA",
|
||||
"description": "The generic Tough PLA profile which other profiles can be based upon.",
|
||||
"package_version": "1.0.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1093,7 +1127,7 @@
|
|||
"display_name": "Generic TPU",
|
||||
"description": "The generic TPU profile which other profiles can be based upon.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://github.com/Ultimaker/fdm_materials",
|
||||
"author": {
|
||||
"author_id": "Generic",
|
||||
|
@ -1111,7 +1145,7 @@
|
|||
"display_name": "Dagoma Chromatik PLA",
|
||||
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://dagoma.fr/boutique/filaments.html",
|
||||
"author": {
|
||||
"author_id": "Dagoma",
|
||||
|
@ -1128,7 +1162,7 @@
|
|||
"display_name": "FABtotum ABS",
|
||||
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so it’s compatible with all versions of the FABtotum Personal fabricator.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
|
||||
"author": {
|
||||
"author_id": "FABtotum",
|
||||
|
@ -1145,7 +1179,7 @@
|
|||
"display_name": "FABtotum Nylon",
|
||||
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
|
||||
"author": {
|
||||
"author_id": "FABtotum",
|
||||
|
@ -1162,7 +1196,7 @@
|
|||
"display_name": "FABtotum PLA",
|
||||
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starch’s sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotum’s one is between 185° and 195°.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
|
||||
"author": {
|
||||
"author_id": "FABtotum",
|
||||
|
@ -1179,7 +1213,7 @@
|
|||
"display_name": "FABtotum TPU Shore 98A",
|
||||
"description": "",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
|
||||
"author": {
|
||||
"author_id": "FABtotum",
|
||||
|
@ -1196,7 +1230,7 @@
|
|||
"display_name": "Fiberlogy HD PLA",
|
||||
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS – better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
|
||||
"author": {
|
||||
"author_id": "Fiberlogy",
|
||||
|
@ -1213,7 +1247,7 @@
|
|||
"display_name": "Filo3D PLA",
|
||||
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://dagoma.fr",
|
||||
"author": {
|
||||
"author_id": "Dagoma",
|
||||
|
@ -1230,7 +1264,7 @@
|
|||
"display_name": "IMADE3D JellyBOX PETG",
|
||||
"description": "",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://shop.imade3d.com/filament.html",
|
||||
"author": {
|
||||
"author_id": "IMADE3D",
|
||||
|
@ -1247,7 +1281,7 @@
|
|||
"display_name": "IMADE3D JellyBOX PLA",
|
||||
"description": "",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://shop.imade3d.com/filament.html",
|
||||
"author": {
|
||||
"author_id": "IMADE3D",
|
||||
|
@ -1264,7 +1298,7 @@
|
|||
"display_name": "Octofiber PLA",
|
||||
"description": "PLA material from Octofiber.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
|
||||
"author": {
|
||||
"author_id": "Octofiber",
|
||||
|
@ -1281,7 +1315,7 @@
|
|||
"display_name": "PolyFlex™ PLA",
|
||||
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://www.polymaker.com/shop/polyflex/",
|
||||
"author": {
|
||||
"author_id": "Polymaker",
|
||||
|
@ -1298,7 +1332,7 @@
|
|||
"display_name": "PolyMax™ PLA",
|
||||
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://www.polymaker.com/shop/polymax/",
|
||||
"author": {
|
||||
"author_id": "Polymaker",
|
||||
|
@ -1315,7 +1349,7 @@
|
|||
"display_name": "PolyPlus™ PLA True Colour",
|
||||
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
|
||||
"author": {
|
||||
"author_id": "Polymaker",
|
||||
|
@ -1332,7 +1366,7 @@
|
|||
"display_name": "PolyWood™ PLA",
|
||||
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "http://www.polymaker.com/shop/polywood/",
|
||||
"author": {
|
||||
"author_id": "Polymaker",
|
||||
|
@ -1349,7 +1383,7 @@
|
|||
"display_name": "Ultimaker ABS",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/abs",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1368,7 +1402,7 @@
|
|||
"display_name": "Ultimaker Breakaway",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/breakaway",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1387,7 +1421,7 @@
|
|||
"display_name": "Ultimaker CPE",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/abs",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1406,7 +1440,7 @@
|
|||
"display_name": "Ultimaker CPE+",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/cpe",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1425,7 +1459,7 @@
|
|||
"display_name": "Ultimaker Nylon",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/abs",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1444,7 +1478,7 @@
|
|||
"display_name": "Ultimaker PC",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/pc",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1463,7 +1497,7 @@
|
|||
"display_name": "Ultimaker PLA",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/abs",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1482,7 +1516,7 @@
|
|||
"display_name": "Ultimaker PP",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/pp",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1501,7 +1535,7 @@
|
|||
"display_name": "Ultimaker PVA",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/abs",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1520,7 +1554,7 @@
|
|||
"display_name": "Ultimaker TPU 95A",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.2.2",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/tpu-95a",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1539,7 +1573,7 @@
|
|||
"display_name": "Ultimaker Tough PLA",
|
||||
"description": "Example package for material and quality profiles for Ultimaker materials.",
|
||||
"package_version": "1.0.3",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://ultimaker.com/products/materials/tough-pla",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
@ -1558,7 +1592,7 @@
|
|||
"display_name": "Vertex Delta ABS",
|
||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://vertex3dprinter.eu",
|
||||
"author": {
|
||||
"author_id": "Velleman",
|
||||
|
@ -1575,7 +1609,7 @@
|
|||
"display_name": "Vertex Delta PET",
|
||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://vertex3dprinter.eu",
|
||||
"author": {
|
||||
"author_id": "Velleman",
|
||||
|
@ -1592,7 +1626,7 @@
|
|||
"display_name": "Vertex Delta PLA",
|
||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://vertex3dprinter.eu",
|
||||
"author": {
|
||||
"author_id": "Velleman",
|
||||
|
@ -1609,7 +1643,7 @@
|
|||
"display_name": "Vertex Delta TPU",
|
||||
"description": "ABS material and quality files for the Delta Vertex K8800.",
|
||||
"package_version": "1.0.1",
|
||||
"sdk_version": "6.0",
|
||||
"sdk_version": "6.0.0",
|
||||
"website": "https://vertex3dprinter.eu",
|
||||
"author": {
|
||||
"author_id": "Velleman",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"default_value": "Alfawise U20"
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
|
||||
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 Y60.0 Z0 E9.0 F1000.0;intro line\nG1 Y100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "; -- END GCODE --\nM104 S0 ;turn off nozzle heater\nM140 S0 ;turn off bed heater\nG91 ;set to relative positioning\nG1 E-10 F300 ;retract the filament slightly\nG90 ;set to absolute positioning\nG28 X0 ;move to the X-axis origin (Home)\nG0 Y280 F600 ;bring the bed to the front for easy print removal\nM84 ;turn off stepper motors\n; -- end of END GCODE --"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue