mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-18 20:28:01 -06:00
Merge master into material marketplace
This commit is contained in:
commit
9a5fb47a6e
228 changed files with 125261 additions and 4020 deletions
|
@ -16,6 +16,7 @@ from UM.Application import Application
|
|||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
@ -303,7 +304,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
containers_found_dict["quality_changes"] = True
|
||||
# Check if there really is a conflict by comparing the values
|
||||
instance_container = InstanceContainer(container_id)
|
||||
instance_container.deserialize(serialized, file_name = instance_container_file_name)
|
||||
try:
|
||||
instance_container.deserialize(serialized, file_name = instance_container_file_name)
|
||||
except ContainerFormatError:
|
||||
Logger.logException("e", "Failed to deserialize InstanceContainer %s from project file %s",
|
||||
instance_container_file_name, file_name)
|
||||
return ThreeMFWorkspaceReader.PreReadResult.failed
|
||||
if quality_changes[0] != instance_container:
|
||||
quality_changes_conflict = True
|
||||
elif container_type == "quality":
|
||||
|
@ -610,8 +616,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
|
||||
if not definitions:
|
||||
definition_container = DefinitionContainer(container_id)
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
|
||||
file_name = definition_container_file)
|
||||
try:
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
|
||||
file_name = definition_container_file)
|
||||
except ContainerFormatError:
|
||||
# We cannot just skip the definition file because everything else later will just break if the
|
||||
# machine definition cannot be found.
|
||||
Logger.logException("e", "Failed to deserialize definition file %s in project file %s",
|
||||
definition_container_file, file_name)
|
||||
definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
|
||||
self._container_registry.addContainer(definition_container)
|
||||
Job.yieldThread()
|
||||
|
||||
|
@ -650,8 +663,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
if to_deserialize_material:
|
||||
material_container = xml_material_profile(container_id)
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = container_id + "." + self._material_container_suffix)
|
||||
try:
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = container_id + "." + self._material_container_suffix)
|
||||
except ContainerFormatError:
|
||||
Logger.logException("e", "Failed to deserialize material file %s in project file %s",
|
||||
material_container_file, file_name)
|
||||
continue
|
||||
if need_new_name:
|
||||
new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
|
||||
material_container.setName(new_name)
|
||||
|
@ -675,7 +693,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# To solve this, we schedule _updateActiveMachine() for later so it will have the latest data.
|
||||
self._updateActiveMachine(global_stack)
|
||||
|
||||
# Load all the nodes / meshdata of the workspace
|
||||
# Load all the nodes / mesh data of the workspace
|
||||
nodes = self._3mf_mesh_reader.read(file_name)
|
||||
if nodes is None:
|
||||
nodes = []
|
||||
|
|
|
@ -23,9 +23,9 @@ class ChangeLog(Extension, QObject,):
|
|||
self._changelog_context = None
|
||||
version_string = Application.getInstance().getVersion()
|
||||
if version_string is not "master":
|
||||
self._version = Version(version_string)
|
||||
self._current_app_version = Version(version_string)
|
||||
else:
|
||||
self._version = None
|
||||
self._current_app_version = None
|
||||
|
||||
self._change_logs = None
|
||||
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
|
@ -76,7 +76,7 @@ class ChangeLog(Extension, QObject,):
|
|||
self._change_logs[open_version][open_header].append(line)
|
||||
|
||||
def _onEngineCreated(self):
|
||||
if not self._version:
|
||||
if not self._current_app_version:
|
||||
return #We're on dev branch.
|
||||
|
||||
if Preferences.getInstance().getValue("general/latest_version_changelog_shown") == "master":
|
||||
|
@ -91,7 +91,7 @@ class ChangeLog(Extension, QObject,):
|
|||
if not Application.getInstance().getGlobalContainerStack():
|
||||
return
|
||||
|
||||
if self._version > latest_version_shown:
|
||||
if self._current_app_version > latest_version_shown:
|
||||
self.showChangelog()
|
||||
|
||||
def showChangelog(self):
|
||||
|
|
|
@ -1,94 +1,122 @@
|
|||
[3.3.0]
|
||||
|
||||
*Profile for the Ultimaker S5
|
||||
New printer profile for the Ultimaker S5, our latest 3D printer.
|
||||
|
||||
*Profile for Tough PLA
|
||||
New material profile for Tough PLA, a material that prints with the convenience of PLA but with toughness and impact strength similar to ABS.
|
||||
|
||||
*Configuration/sync button
|
||||
This new feature for network connected printers allows you to easily synchronize with all available configurations in your Cura Connect group. The name of the Host of the group is automatically pulled from the API, and network printers and local printers are now separated in the list for clarity.
|
||||
Configuration and synchronization button now available for Ultimaker network-connected printers to easily synchronize all possible available configurations in your Cura Connect group. The name of the group host is automatically pulled from the API, and network printers and local printers are separated in the list for clarity.
|
||||
|
||||
*Single extrusion mode Ultimaker 3
|
||||
Single extrusion mode allows to disable the unused extruder. This has the following positive effects:
|
||||
- Printing profiles are optimized to print with a single extruder, increasing print quality.
|
||||
- Global settings, like build plate temperature, are set for the active extruder.
|
||||
- The ‘print one at a time’ feature is now available.
|
||||
*Setting visibility preset
|
||||
Presets guide you to find the most important settings incrementally. A small menu is located next to the search bar to easily access these new setting visibility presets. Contributed by fieldOfView.
|
||||
|
||||
*Plugin browser updates
|
||||
The plugin browser has been updated to look and feel similar to the other UI elements. The author name is clickable, which opens email for support. The plugins can now be uninstalled with an uninstall button.
|
||||
*Print/save hotkey
|
||||
Send a print to the queue using Ctrl + P (Windows/Linux) or Cmd + P (Mac). If no printer is present on the network, it will save to file instead.
|
||||
|
||||
*Block support - fieldOfView
|
||||
This new feature allows you to easily add small cubic areas which prevent support material from being generated. Select the ‘block support’ tool and click the model to place a cube. This cube can be scaled, rotated and moved with the adjustment tools to modify it.
|
||||
*3D model assistant
|
||||
Models sliced to print with ABS, PC, PP or CPE+ that have a larger footprint than 150 x 150 x 150 mm will notify the user with an icon and popup of how they can achieve the best possible print quality and reliability.
|
||||
|
||||
*Setting visibility preset - fieldOfView
|
||||
A convenient way to select a preset of settings to be visible. These presets guide you to find the most important Cura settings in an incremental way. A small menu is located next to the ‘search bar’ to easily access these new setting visibility presets.
|
||||
*Refactored machine manager
|
||||
Refactored machine manager resulted in less manager classes. Changing settings, materials, variants and machines is now clearer. This results in an overall speed up when making changes.
|
||||
|
||||
*Model assistant
|
||||
This feature provides useful information to the user based on their model. For now, it informs the user when printing models with a footprint larger than 15x15x10cm, printed with ABS, PC, PP or CPE+, that they may want to make changes to the model such as filleting sharp edges.
|
||||
*Multiply models faster
|
||||
Significant speed increase when multiplying models.
|
||||
|
||||
*Auto slicing disabled by default
|
||||
The auto slice tool is now disabled by default. Users can still enable the feature in the user preferences dialog.
|
||||
|
||||
*Updated fonts
|
||||
Default font changed to NotoSans to increase readability and consistency with Cura Connect.
|
||||
|
||||
*Plugin browser look and feel
|
||||
The plugin browser has been updated with a better look and feel to bring it in line with other UI elements. The author name is clickable, which opens email for support. Plugins can now be uninstalled with an uninstall button.
|
||||
|
||||
*Show tooltip for unavailable profile
|
||||
Tooltips have been added to incompatible settings, to give explanations why they are incompatible.
|
||||
|
||||
*Empty material slots Ultimaker 3
|
||||
When a material is not loaded in the Ultimaker 3, it is now displayed as ‘Empty’ rather than ‘Unknown’.
|
||||
|
||||
*Send over network confirmation
|
||||
When a print job is sent to a networked printer, a popup will confirm the job was sent, with a button to redirect the user to the monitor in Cura Connect.
|
||||
|
||||
*Post processing scripts
|
||||
Fixed an issue where post processing scripts could be lost between sessions. Post processing scripts are now persistent between sessions.
|
||||
|
||||
*Single extrusion mode
|
||||
Disable an extruder on a dual extrusion printer, so you are not limited by the other extruder’s parameters. To disable an extruder, right click it in the right panel, and select ‘Disable extruder’ to disable it. Re-enable by right clicking and selecting ‘enable extruder’. Printing profiles are optimized for the active extruder, as well as global settings, such as build plate temperature, to achieve better print quality. Using single extrusion mode also makes the ‘print one at a time’ feature available for the Ultimaker 3 and Ultimaker S5.
|
||||
|
||||
*New UFP extension
|
||||
UFP (Ultimaker format package) is a new file extension that contains compressed gcode and a preview thumbnail. Using this extension enables a model preview (similar to the solid view) on the Ultimaker S5 touchscreen and in Cura Connect.
|
||||
|
||||
*Compressed Gcode
|
||||
Gcode saved from Ultimaker Cura using the Ultimaker 3 profile is compressed (using gzip) to save space on printers.
|
||||
|
||||
*Circular prime tower
|
||||
The prime tower shape has changed from rectangular to circular. This shape should increase the adhesion to the build plate, overall strength, and prevent delamination of the layers.
|
||||
Prime towers are now circular, resulting in a less jerky print head action, a more robust structure, better layer adhesion, and better build plate adhesion compared to square prime towers, reducing the chance of prime tower failure mid-print.
|
||||
|
||||
*Connected infill lines
|
||||
Grid and triangular infill lines are now connected. This strengthens the internal structure of the infill and results in a more constant flow of filament while printing.
|
||||
Grid and triangular infill patterns now have connected lines for a more constant flow, better model rigidity, and reduced impact on the quality of the outer wall.
|
||||
|
||||
*Support blocker - fieldOfView
|
||||
Generate a cube mesh to prevent support material generation in specific areas of a model. Each cube can be scaled, rotated, and moved with the standard adjustment tools to fit your requirements. When the support blocker tool is selected, single click in the area you want to block support to generate a mesh. If it is positioned by accident, click it again to remove it.
|
||||
|
||||
*Real bridging - smartavionics
|
||||
This new experimental feature adds bridge detection which adjusts the print speed, flow and fan speed to enhance print quality on bridging parts.
|
||||
|
||||
*Initial layer flow
|
||||
The flow of the initial layer can now be adjusted separately. The new setting is found in the ‘material’ category.
|
||||
|
||||
*Initial travel move retraction - smartavionics
|
||||
The initial travel move–before printing the brim/skirt–is now retracted. This reduces the chance of the prime blob dragging into the printed part.
|
||||
New experimental feature that detects bridges, adjusting the print speed, slow and fan speed to enhance print quality on bridging parts.
|
||||
|
||||
*Updated CuraEngine executable - thopiekar
|
||||
The CuraEngine executable now contains a dedicated icon, author information and a license.
|
||||
|
||||
*Removed unnecessary retractions in spiralize - smartavionics
|
||||
This feature removes retractions on layer change in spiralize mode, improving model quality.
|
||||
*Use RapidJSON and ClipperLib from system libraries
|
||||
Application updated to use verified copies of libraries, reducing maintenance time keeping them up to date (the operating system is now responsible), as well as reducing the amount of code shipped (as necessary code is already on the user’s system).
|
||||
|
||||
*Improve travel paths - smartavionics
|
||||
This feature optimizes the travel paths in a print, reducing print times and oozing of the material.
|
||||
*Initial layer flow
|
||||
New setting in the ‘material’ category where the initial layer flow can be adjusted.
|
||||
|
||||
*Refactor machine manager
|
||||
The refactor of the machine manager resulted in fewer manager classes and made changing settings, materials, variants and machines more streamlined. This results in a significant overall improvement in speed when changing any of the aforementioned properties.
|
||||
*Initial travel move retraction - smartavionics
|
||||
Retraction has been added to the initial travel move, reducing the chance of prime blobs getting dragged into parts before brim/skirts are printed.
|
||||
|
||||
*Speed up ‘multiply model’
|
||||
The viewport frame rate is significantly increased when working with multiple models.
|
||||
*Unnecessary retractions in spiralize - smartavionics
|
||||
Removes retractions on layer change in spiralize mode, improving model quality.
|
||||
|
||||
*New font: Noto Sans
|
||||
The font of Ultimaker Cura has been changed to Noto Sans. This improves readability and consistency with Cura Connect. The font update also fixes some rendering issues on macOS.
|
||||
*Faster travel paths - smartavionics
|
||||
Until now, the path order optimizer worked on the basis that the shortest possible route could be taken from any one point to another. When combing is used, any route may longer, due to the need to route around obstacles. Now, it can use the combed distances to give more consistent results.
|
||||
|
||||
*Plugin updates - Pheneeny
|
||||
Three new plugins were added to Ultimaker Cura; Scalable extra prime, Print temperature offset and Enclosure fan.
|
||||
Also Alexander Roessler made a new NGC writer plugin so you can export files in NGC format.
|
||||
*New plugins - Pheneeny
|
||||
Three new plugins have been added to Ultimaker Cura: Scalable extra prime, Print temperature offset, and Enclosure fan.
|
||||
|
||||
*Pre-heat extruders - fieldOfView
|
||||
This new feature allows to preheat the extruders in the printer monitor.
|
||||
|
||||
*Persistent post-processing scripts
|
||||
Scripts are no longer erased after restarting Ultimaker Cura.
|
||||
*Renamed TweakAtZ to ‘ChangeAtZ’
|
||||
This script has been renamed to be more consistent with other scripts.
|
||||
|
||||
*GZ Reader
|
||||
By default, G-code for Ultimaker 3 machines is now saved as gzipped G-Code.
|
||||
*Import XML material profile checks
|
||||
XML material profile files are now checked before import in Ultimaker Cura to avoid potential issues. Contributed by fieldOfView.
|
||||
|
||||
*Print preview image
|
||||
Adds a preview image of the gcode to the slice information. This can be shown in Cura Connect.
|
||||
*Bug fixes
|
||||
- Slice engine crash default temperature 0. Fixed an issue where the slicing engine could crash when slicing with a material at 0°C.
|
||||
- Network printer reconnect. Fixed an issue where the user could not connect to the printer after losing connection.
|
||||
- Pause at height ‘redo layers’ broken. Fixed an issue where setting ‘pause at height redo layers’ to 1 or more would cause failed prints.
|
||||
- Reset icon fix. Fixed an issue where manually reverting a default print profile value instead of using the reset button would cause the reset icon to remain.
|
||||
- Infill density for all extruders. The infill density in the recommended mode now applies to all extruders instead of extruder 1.
|
||||
- Polypropylene 0.25mm print profile. Fixed the maximum number of allowed extrusions for all 0.25mm Polypropylene profile prints.
|
||||
- SolidWorks plugin. Replaced comtypes module with win32com to fix issues.
|
||||
- Font rendering issues. Fixed font rendering issues on Max OSX.
|
||||
- Slice engine avoids broken wall segments. Fixed an issue where narrow walls created broken line segments by testing for such situations and using slightly narrow lines in those cases.
|
||||
|
||||
*Use RapidJSON and ClipperLib from system libraries
|
||||
This reduces both the amount of trouble required to keep these libraries up to date (the operating system is now responsible) as well as reducing the amount of code we’re shipping.
|
||||
*Third party printers
|
||||
|
||||
*TweakAtZ renamed to ChangeAtZ
|
||||
The TweakAtZ script has been renamed to ChangeAtZ to be more consistent with the rest of the scripts.
|
||||
|
||||
*XML material profile improvements
|
||||
XML material profiles are now checked before importing into Ultimaker Cura. ALso, XML material profiles can now contain Ultimaker Cura-specific properties (by fieldOfView).
|
||||
|
||||
*Ultimaker 3 network status improvements
|
||||
- When a material is not loaded in the Ultimaker 3, Cura now displays it as ‘Empty’ rather than ‘Unknown’.
|
||||
- When an Ultimaker 3 is in maintenance mode, Cura now displays it as ‘Unavailable’ rather than ‘Unknown’.
|
||||
|
||||
*Bug Fixes
|
||||
- Cura Engine no longer crashes when slicing with a material at 0°C.
|
||||
- Cura reconnects to networked printers after connectivity loss.
|
||||
- Pause at height ‘redo layers’ broken no longer results in failed prints.
|
||||
- Setting reset icon no longer remains visible after resetting.
|
||||
- The infill density in the recommended mode now applies to all extruders instead of extruder 1.
|
||||
- The maximum number of allowed extrusions for all 0.25mm Polypropylene profile prints has been fixed.
|
||||
- FABtotum TPU profiles. Added third-party material profiles for TPU. Contributed by krios-fabteam.
|
||||
- Dagoma profiles. Updated printer profiles contributed by dagoma3d.
|
||||
- uBuild profile. Updated printer profiles contributed by uBuild-3D.
|
||||
- Cartesio printer updates. Updated profiles contributed by maukcc.
|
||||
- Printrbot Simple Maker's Kit 1405. Profiles contributed by timur-tabi.
|
||||
- Added SeeMeCNC. Profiles contributed by pouncingiguana.
|
||||
- Velleman Vertex. Updated printer and quality profiles contributed by velbn.
|
||||
- gMax 1.5. Profiles contributed by gordo3di.
|
||||
|
||||
[3.2.1]
|
||||
*Bug fixes
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import configparser
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||
from cura.ReaderWriters.ProfileReader import ProfileReader
|
||||
|
||||
|
@ -77,7 +78,10 @@ class CuraProfileReader(ProfileReader):
|
|||
profile.addMetaDataEntry("type", "quality_changes")
|
||||
try:
|
||||
profile.deserialize(serialized)
|
||||
except Exception as e: # Parsing error. This is not a (valid) Cura profile then.
|
||||
except ContainerFormatError as e:
|
||||
Logger.log("e", "Error in the format of a container: %s", str(e))
|
||||
return None
|
||||
except Exception as e:
|
||||
Logger.log("e", "Error while trying to parse profile: %s", str(e))
|
||||
return None
|
||||
return profile
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re #Regular expressions for parsing escape characters in the settings.
|
||||
import json
|
||||
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -113,6 +114,9 @@ def readQualityProfileFromString(profile_string):
|
|||
profile = InstanceContainer("")
|
||||
try:
|
||||
profile.deserialize(profile_string)
|
||||
except ContainerFormatError as e:
|
||||
Logger.log("e", "Corrupt profile in this g-code file: %s", str(e))
|
||||
return None
|
||||
except Exception as e: # Not a valid g-code file.
|
||||
Logger.log("e", "Unable to serialise the profile: %s", str(e))
|
||||
return None
|
||||
|
|
|
@ -362,12 +362,14 @@ class FlavorParser:
|
|||
else:
|
||||
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
|
||||
|
||||
# When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder
|
||||
# When the layer change is reached, the polygon is computed so we have just one layer per extruder
|
||||
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||
try:
|
||||
layer_number = int(line[len(self._layer_keyword):])
|
||||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
# Start the new layer at the end position of the last layer
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
|
||||
# When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior
|
||||
# as in ProcessSlicedLayersJob
|
||||
|
@ -406,7 +408,11 @@ class FlavorParser:
|
|||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
|
||||
# When changing tool, store the end point of the previous path, then process the code and finally
|
||||
# add another point with the new position of the head.
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_position = self.processTCode(T, line, current_position, current_path)
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
|
|
|
@ -31,9 +31,10 @@ class ModelChecker(QObject, Extension):
|
|||
|
||||
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, _):
|
||||
def _onChanged(self, *args, **kwargs):
|
||||
self.onChanged.emit()
|
||||
|
||||
## Called when plug-ins are initialized.
|
||||
|
@ -53,7 +54,6 @@ class ModelChecker(QObject, Extension):
|
|||
# has not done yet.
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack is None:
|
||||
Application.getInstance().callLater(lambda: self.onChanged.emit())
|
||||
return False
|
||||
|
||||
material_shrinkage = self._getMaterialShrinkage()
|
||||
|
|
|
@ -208,7 +208,7 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
for script_str in scripts_list_strs.split("\n"): #Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
||||
if not script_str: #There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
||||
continue
|
||||
script_str = script_str.replace("\\n", "\n").replace("\\\\", "\\") #Unescape escape sequences.
|
||||
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") #Unescape escape sequences.
|
||||
script_parser = configparser.ConfigParser(interpolation = None)
|
||||
script_parser.optionxform = str #Don't transform the setting keys as they are case-sensitive.
|
||||
script_parser.read_string(script_str)
|
||||
|
@ -241,7 +241,7 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
parser.write(serialized)
|
||||
serialized.seek(0)
|
||||
script_str = serialized.read()
|
||||
script_str = script_str.replace("\\", "\\\\").replace("\n", "\\n") #Escape newlines because configparser sees those as section delimiters.
|
||||
script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") #Escape newlines because configparser sees those as section delimiters.
|
||||
script_list_strs.append(script_str)
|
||||
|
||||
script_list_strs = "\n".join(script_list_strs) #ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# Copyright (c) 2015 Jaime van Kessel
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
# Setting stuff import
|
||||
from UM.Application import Application
|
||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
|
@ -39,8 +39,12 @@ class Script:
|
|||
self._definition = definitions[0]
|
||||
else:
|
||||
self._definition = DefinitionContainer(setting_data["key"])
|
||||
self._definition.deserialize(json.dumps(setting_data))
|
||||
ContainerRegistry.getInstance().addContainer(self._definition)
|
||||
try:
|
||||
self._definition.deserialize(json.dumps(setting_data))
|
||||
ContainerRegistry.getInstance().addContainer(self._definition)
|
||||
except ContainerFormatError:
|
||||
self._definition = None
|
||||
return
|
||||
self._stack.addContainer(self._definition)
|
||||
self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
|
||||
self._instance.setDefinition(self._definition.getId())
|
||||
|
|
151
plugins/SliceInfoPlugin/MoreInfoWindow.qml
Normal file
151
plugins/SliceInfoPlugin/MoreInfoWindow.qml
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: baseDialog
|
||||
title: catalog.i18nc("@title:window", "More information on anonymous data collection")
|
||||
visible: false
|
||||
|
||||
minimumWidth: 500 * screenScaleFactor
|
||||
minimumHeight: 400 * screenScaleFactor
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
|
||||
property bool allowSendData: true // for saving the user's choice
|
||||
|
||||
onAccepted: manager.setSendSliceInfo(allowSendData)
|
||||
|
||||
onVisibilityChanged:
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info");
|
||||
if (baseDialog.allowSendData)
|
||||
{
|
||||
allowSendButton.checked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
dontSendButton.checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: textRow
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: radioButtonsRow.top
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: headerText
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
TextArea
|
||||
{
|
||||
id: exampleData
|
||||
anchors
|
||||
{
|
||||
top: headerText.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: manager.getExampleData()
|
||||
readOnly: true
|
||||
textFormat: TextEdit.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: radioButtonsRow
|
||||
width: parent.width
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
ExclusiveGroup { id: group }
|
||||
|
||||
RadioButton
|
||||
{
|
||||
id: dontSendButton
|
||||
text: catalog.i18nc("@text:window", "I don't want to send these data")
|
||||
exclusiveGroup: group
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.allowSendData = !checked;
|
||||
}
|
||||
}
|
||||
RadioButton
|
||||
{
|
||||
id: allowSendButton
|
||||
text: catalog.i18nc("@text:window", "Allow sending these data to Ultimaker and help us improve Cura")
|
||||
exclusiveGroup: group
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.allowSendData = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: buttonRow
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@action:button", "OK")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.accepted()
|
||||
baseDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@action:button", "Cancel")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.rejected()
|
||||
baseDialog.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
|
@ -11,18 +15,11 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
|
||||
import time
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
|
||||
from .SliceInfoJob import SliceInfoJob
|
||||
|
||||
import platform
|
||||
import math
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -30,15 +27,19 @@ catalog = i18nCatalog("cura")
|
|||
## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
|
||||
# The data is only sent when the user in question gave permission to do so. All data is anonymous and
|
||||
# no model files are being sent (Just a SHA256 hash of the model).
|
||||
class SliceInfo(Extension):
|
||||
class SliceInfo(QObject, Extension):
|
||||
info_url = "https://stats.ultimaker.com/api/cura"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, parent = None):
|
||||
QObject.__init__(self, parent)
|
||||
Extension.__init__(self)
|
||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
|
||||
Preferences.getInstance().addPreference("info/send_slice_info", True)
|
||||
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
|
||||
|
||||
self._more_info_dialog = None
|
||||
self._example_data_content = None
|
||||
|
||||
if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
|
||||
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
|
||||
lifetime = 0,
|
||||
|
@ -47,32 +48,64 @@ class SliceInfo(Extension):
|
|||
|
||||
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
|
||||
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
|
||||
self.send_slice_info_message.addAction("Disable", name = catalog.i18nc("@action:button", "Disable"), icon = None,
|
||||
description = catalog.i18nc("@action:tooltip", "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."), button_style = Message.ActionButtonStyle.LINK)
|
||||
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
|
||||
description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
|
||||
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
|
||||
self.send_slice_info_message.show()
|
||||
|
||||
Application.getInstance().initializationFinished.connect(self._onAppInitialized)
|
||||
|
||||
def _onAppInitialized(self):
|
||||
if self._more_info_dialog is None:
|
||||
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
|
||||
|
||||
## Perform action based on user input.
|
||||
# Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
|
||||
def messageActionTriggered(self, message_id, action_id):
|
||||
Preferences.getInstance().setValue("info/asked_send_slice_info", True)
|
||||
if action_id == "Disable":
|
||||
Preferences.getInstance().addPreference("info/send_slice_info", False)
|
||||
if action_id == "MoreInfo":
|
||||
self.showMoreInfoDialog()
|
||||
self.send_slice_info_message.hide()
|
||||
|
||||
def showMoreInfoDialog(self):
|
||||
if self._more_info_dialog is None:
|
||||
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
|
||||
self._more_info_dialog.open()
|
||||
|
||||
def _createDialog(self, qml_name):
|
||||
Logger.log("d", "Creating dialog [%s]", qml_name)
|
||||
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
|
||||
dialog = Application.getInstance().createQmlComponent(file_path, {"manager": self})
|
||||
return dialog
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getExampleData(self) -> str:
|
||||
if self._example_data_content is None:
|
||||
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "example_data.json")
|
||||
with open(file_path, "r", encoding = "utf-8") as f:
|
||||
self._example_data_content = f.read()
|
||||
return self._example_data_content
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setSendSliceInfo(self, enabled: bool):
|
||||
Preferences.getInstance().setValue("info/send_slice_info", enabled)
|
||||
|
||||
def _onWriteStarted(self, output_device):
|
||||
try:
|
||||
if not Preferences.getInstance().getValue("info/send_slice_info"):
|
||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||
return # Do nothing, user does not want to send data
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
print_information = Application.getInstance().getPrintInformation()
|
||||
application = Application.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
print_information = application.getPrintInformation()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
data = dict() # The data that we're going to submit.
|
||||
data["time_stamp"] = time.time()
|
||||
data["schema_version"] = 0
|
||||
data["cura_version"] = Application.getInstance().getVersion()
|
||||
data["cura_version"] = application.getVersion()
|
||||
|
||||
active_mode = Preferences.getInstance().getValue("cura/active_mode")
|
||||
if active_mode == 0:
|
||||
|
@ -80,7 +113,7 @@ class SliceInfo(Extension):
|
|||
else:
|
||||
data["active_mode"] = "custom"
|
||||
|
||||
definition_changes = global_container_stack.definitionChanges
|
||||
definition_changes = global_stack.definitionChanges
|
||||
machine_settings_changed_by_user = False
|
||||
if definition_changes.getId() != "empty":
|
||||
# Now a definition_changes container will always be created for a stack,
|
||||
|
@ -92,16 +125,17 @@ class SliceInfo(Extension):
|
|||
data["language"] = Preferences.getInstance().getValue("general/language")
|
||||
data["os"] = {"type": platform.system(), "version": platform.version()}
|
||||
|
||||
data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")}
|
||||
data["active_machine"] = {"definition_id": global_stack.definition.getId(),
|
||||
"manufacturer": global_stack.definition.getMetaDataEntry("manufacturer", "")}
|
||||
|
||||
# add extruder specific data to slice info
|
||||
data["extruders"] = []
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
|
||||
extruders = list(global_stack.extruders.values())
|
||||
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
|
||||
|
||||
for extruder in extruders:
|
||||
extruder_dict = dict()
|
||||
extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder
|
||||
extruder_dict["active"] = machine_manager.activeStack == extruder
|
||||
extruder_dict["material"] = {"GUID": extruder.material.getMetaData().get("GUID", ""),
|
||||
"type": extruder.material.getMetaData().get("material", ""),
|
||||
"brand": extruder.material.getMetaData().get("brand", "")
|
||||
|
@ -123,11 +157,11 @@ class SliceInfo(Extension):
|
|||
extruder_dict["extruder_settings"] = extruder_settings
|
||||
data["extruders"].append(extruder_dict)
|
||||
|
||||
data["quality_profile"] = global_container_stack.quality.getMetaData().get("quality_type")
|
||||
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
|
||||
|
||||
data["models"] = []
|
||||
# Listing all files placed on the build plate
|
||||
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
||||
if node.callDecoration("isSliceable"):
|
||||
model = dict()
|
||||
model["hash"] = node.getMeshData().getHash()
|
||||
|
@ -173,28 +207,28 @@ class SliceInfo(Extension):
|
|||
"total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))}
|
||||
|
||||
print_settings = dict()
|
||||
print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value")
|
||||
print_settings["layer_height"] = global_stack.getProperty("layer_height", "value")
|
||||
|
||||
# Support settings
|
||||
print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value")
|
||||
print_settings["support_extruder_nr"] = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
print_settings["support_enabled"] = global_stack.getProperty("support_enable", "value")
|
||||
print_settings["support_extruder_nr"] = int(global_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
|
||||
# Platform adhesion settings
|
||||
print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value")
|
||||
print_settings["adhesion_type"] = global_stack.getProperty("adhesion_type", "value")
|
||||
|
||||
# Shell settings
|
||||
print_settings["wall_line_count"] = global_container_stack.getProperty("wall_line_count", "value")
|
||||
print_settings["retraction_enable"] = global_container_stack.getProperty("retraction_enable", "value")
|
||||
print_settings["wall_line_count"] = global_stack.getProperty("wall_line_count", "value")
|
||||
print_settings["retraction_enable"] = global_stack.getProperty("retraction_enable", "value")
|
||||
|
||||
# Prime tower settings
|
||||
print_settings["prime_tower_enable"] = global_container_stack.getProperty("prime_tower_enable", "value")
|
||||
print_settings["prime_tower_enable"] = global_stack.getProperty("prime_tower_enable", "value")
|
||||
|
||||
# Infill settings
|
||||
print_settings["infill_sparse_density"] = global_container_stack.getProperty("infill_sparse_density", "value")
|
||||
print_settings["infill_pattern"] = global_container_stack.getProperty("infill_pattern", "value")
|
||||
print_settings["gradual_infill_steps"] = global_container_stack.getProperty("gradual_infill_steps", "value")
|
||||
print_settings["infill_sparse_density"] = global_stack.getProperty("infill_sparse_density", "value")
|
||||
print_settings["infill_pattern"] = global_stack.getProperty("infill_pattern", "value")
|
||||
print_settings["gradual_infill_steps"] = global_stack.getProperty("gradual_infill_steps", "value")
|
||||
|
||||
print_settings["print_sequence"] = global_container_stack.getProperty("print_sequence", "value")
|
||||
print_settings["print_sequence"] = global_stack.getProperty("print_sequence", "value")
|
||||
|
||||
data["print_settings"] = print_settings
|
||||
|
||||
|
|
113
plugins/SliceInfoPlugin/example_data.json
Normal file
113
plugins/SliceInfoPlugin/example_data.json
Normal file
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"time_stamp": 1523973715.486928,
|
||||
"schema_version": 0,
|
||||
"cura_version": "3.3",
|
||||
"active_mode": "custom",
|
||||
"machine_settings_changed_by_user": true,
|
||||
"language": "en_US",
|
||||
"os": {
|
||||
"type": "Linux",
|
||||
"version": "#43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018"
|
||||
},
|
||||
"active_machine": {
|
||||
"definition_id": "ultimaker3",
|
||||
"manufacturer": "Ultimaker B.V."
|
||||
},
|
||||
"extruders": [
|
||||
{
|
||||
"active": true,
|
||||
"material": {
|
||||
"GUID": "506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9",
|
||||
"type": "PLA",
|
||||
"brand": "Generic"
|
||||
},
|
||||
"material_used": 0.84,
|
||||
"variant": "AA 0.4",
|
||||
"nozzle_size": 0.4,
|
||||
"extruder_settings": {
|
||||
"wall_line_count": 3,
|
||||
"retraction_enable": true,
|
||||
"infill_sparse_density": 30,
|
||||
"infill_pattern": "triangles",
|
||||
"gradual_infill_steps": 0,
|
||||
"default_material_print_temperature": 200,
|
||||
"material_print_temperature": 200
|
||||
}
|
||||
},
|
||||
{
|
||||
"active": false,
|
||||
"material": {
|
||||
"GUID": "86a89ceb-4159-47f6-ab97-e9953803d70f",
|
||||
"type": "PVA",
|
||||
"brand": "Generic"
|
||||
},
|
||||
"material_used": 0.5,
|
||||
"variant": "BB 0.4",
|
||||
"nozzle_size": 0.4,
|
||||
"extruder_settings": {
|
||||
"wall_line_count": 3,
|
||||
"retraction_enable": true,
|
||||
"infill_sparse_density": 20,
|
||||
"infill_pattern": "triangles",
|
||||
"gradual_infill_steps": 0,
|
||||
"default_material_print_temperature": 215,
|
||||
"material_print_temperature": 220
|
||||
}
|
||||
}
|
||||
],
|
||||
"quality_profile": "fast",
|
||||
"models": [
|
||||
{
|
||||
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
|
||||
"bounding_box": {
|
||||
"minimum": {
|
||||
"x": -10.0,
|
||||
"y": 0.0,
|
||||
"z": -5.0
|
||||
},
|
||||
"maximum": {
|
||||
"x": 9.999999046325684,
|
||||
"y": 40.0,
|
||||
"z": 5.0
|
||||
}
|
||||
},
|
||||
"transformation": {
|
||||
"data": "[[ 1. 0. 0. 0.] [ 0. 1. 0. 20.] [ 0. 0. 1. 0.] [ 0. 0. 0. 1.]]"
|
||||
},
|
||||
"extruder": 0,
|
||||
"model_settings": {
|
||||
"support_enabled": true,
|
||||
"support_extruder_nr": 1,
|
||||
"infill_mesh": false,
|
||||
"cutting_mesh": false,
|
||||
"support_mesh": false,
|
||||
"anti_overhang_mesh": false,
|
||||
"wall_line_count": 3,
|
||||
"retraction_enable": true,
|
||||
"infill_sparse_density": 30,
|
||||
"infill_pattern": "triangles",
|
||||
"gradual_infill_steps": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"print_times": {
|
||||
"travel": 187,
|
||||
"support": 825,
|
||||
"infill": 351,
|
||||
"total": 7234
|
||||
},
|
||||
"print_settings": {
|
||||
"layer_height": 0.15,
|
||||
"support_enabled": true,
|
||||
"support_extruder_nr": 1,
|
||||
"adhesion_type": "brim",
|
||||
"wall_line_count": 3,
|
||||
"retraction_enable": true,
|
||||
"prime_tower_enable": true,
|
||||
"infill_sparse_density": 20,
|
||||
"infill_pattern": "triangles",
|
||||
"gradual_infill_steps": 0,
|
||||
"print_sequence": "all_at_once"
|
||||
},
|
||||
"output_to": "LocalFileOutputDevice"
|
||||
}
|
|
@ -19,8 +19,10 @@ from cura.Scene.CuraSceneNode import CuraSceneNode
|
|||
|
||||
from cura.PickingPass import PickingPass
|
||||
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from cura.Operations.SetParentOperation import SetParentOperation
|
||||
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
|
@ -56,7 +58,7 @@ class SupportEraser(Tool):
|
|||
modifiers = QApplication.keyboardModifiers()
|
||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||
|
||||
if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
|
||||
if event.type == Event.MousePressEvent and MouseEvent.LeftButton in event.buttons and self._controller.getToolsEnabled():
|
||||
if ctrl_is_active:
|
||||
self._controller.setActiveTool("TranslateTool")
|
||||
return
|
||||
|
@ -117,7 +119,10 @@ class SupportEraser(Tool):
|
|||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
|
||||
op = AddSceneNodeOperation(node, parent)
|
||||
op = GroupedOperation()
|
||||
# First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent
|
||||
op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot()))
|
||||
op.addOperation(SetParentOperation(node, parent))
|
||||
op.push()
|
||||
node.setPosition(position, CuraSceneNode.TransformSpace.World)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from . import SupportEraser
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
|
|
|
@ -50,6 +50,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._number_of_extruders = 2
|
||||
|
||||
self._dummy_lambdas = set()
|
||||
|
||||
self._print_jobs = []
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||
|
@ -64,6 +66,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._authentication_state = AuthState.Authenticated
|
||||
|
||||
self._error_message = None
|
||||
self._write_job_progress_message = None
|
||||
self._progress_message = None
|
||||
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
@ -179,16 +182,33 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
|
||||
|
||||
self._write_job_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"), use_inactivity_timer = False)
|
||||
self._write_job_progress_message.show()
|
||||
|
||||
self._dummy_lambdas = (target_printer, preferred_format, stream)
|
||||
job.finished.connect(self._sendPrintJobWaitOnWriteJobFinished)
|
||||
|
||||
job.start()
|
||||
|
||||
yield True #Return that we had success!
|
||||
yield #To prevent having to catch the StopIteration exception.
|
||||
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
def _sendPrintJobWaitOnWriteJobFinished(self, job):
|
||||
self._write_job_progress_message.hide()
|
||||
|
||||
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, description = "")
|
||||
self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
|
||||
self._progress_message.show()
|
||||
|
||||
job.start()
|
||||
|
||||
parts = []
|
||||
|
||||
target_printer, preferred_format, stream = self._dummy_lambdas
|
||||
|
||||
# If a specific printer was selected, it should be printed with that machine.
|
||||
if target_printer:
|
||||
target_printer = self._printer_uuid_to_unique_name_mapping[target_printer]
|
||||
|
@ -199,8 +219,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
||||
|
||||
while not job.isFinished():
|
||||
sleep(0.1)
|
||||
output = stream.getvalue() #Either str or bytes depending on the output mode.
|
||||
if isinstance(stream, io.StringIO):
|
||||
output = output.encode("utf-8")
|
||||
|
@ -209,9 +227,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
|
||||
|
||||
yield True #Return that we had success!
|
||||
yield #To prevent having to catch the StopIteration exception.
|
||||
|
||||
@pyqtProperty(QObject, notify=activePrinterChanged)
|
||||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||
return self._active_printer
|
||||
|
|
|
@ -249,13 +249,19 @@ Cura.MachineAction
|
|||
{
|
||||
if(base.selectedDevice)
|
||||
{
|
||||
if(base.selectedDevice.printerType == "ultimaker3")
|
||||
if (base.selectedDevice.printerType == "ultimaker3")
|
||||
{
|
||||
return catalog.i18nc("@label", "Ultimaker 3")
|
||||
} else if(base.selectedDevice.printerType == "ultimaker3_extended")
|
||||
return "Ultimaker 3";
|
||||
}
|
||||
else if (base.selectedDevice.printerType == "ultimaker3_extended")
|
||||
{
|
||||
return catalog.i18nc("@label", "Ultimaker 3 Extended")
|
||||
} else
|
||||
return "Ultimaker 3 Extended";
|
||||
}
|
||||
else if (base.selectedDevice.printerType == "ultimaker_s5")
|
||||
{
|
||||
return "Ultimaker S5";
|
||||
}
|
||||
else
|
||||
{
|
||||
return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2015 Ultimaker B.V.
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
|
@ -48,7 +48,7 @@ UM.Dialog
|
|||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
height: 50 * screenScaleFactord
|
||||
height: 50 * screenScaleFactor
|
||||
Label
|
||||
{
|
||||
id: manualPrinterSelectionLabel
|
||||
|
|
|
@ -104,6 +104,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if self._is_printing:
|
||||
return # Aleady printing
|
||||
|
||||
# cancel any ongoing preheat timer before starting a print
|
||||
self._printers[0].getController().stopPreheatTimers()
|
||||
|
||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
# find the G-code for the active build plate to print
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import configparser #To parse preference files.
|
||||
import io #To serialise the preference files afterwards.
|
||||
import os
|
||||
import urllib.parse
|
||||
import re
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
|
||||
|
||||
|
@ -118,6 +120,12 @@ class VersionUpgrade27to30(VersionUpgrade):
|
|||
if not parser.has_section("general"):
|
||||
parser.add_section("general")
|
||||
|
||||
# Clean up the filename
|
||||
file_base_name = os.path.basename(filename)
|
||||
file_base_name = urllib.parse.unquote_plus(file_base_name)
|
||||
|
||||
um2_pattern = re.compile(r"^ultimaker[^a-zA-Z\\d\\s:]2_.*$")
|
||||
|
||||
# The ultimaker 2 family
|
||||
ultimaker2_prefix_list = ["ultimaker2_extended_",
|
||||
"ultimaker2_go_",
|
||||
|
@ -127,9 +135,8 @@ class VersionUpgrade27to30(VersionUpgrade):
|
|||
"ultimaker2_plus_"]
|
||||
|
||||
# set machine definition to "ultimaker2" for the custom quality profiles that can be for the ultimaker 2 family
|
||||
file_base_name = os.path.basename(filename)
|
||||
is_ultimaker2_family = False
|
||||
if not any(file_base_name.startswith(ep) for ep in exclude_prefix_list):
|
||||
is_ultimaker2_family = um2_pattern.match(file_base_name) is not None
|
||||
if not is_ultimaker2_family and not any(file_base_name.startswith(ep) for ep in exclude_prefix_list):
|
||||
is_ultimaker2_family = any(file_base_name.startswith(ep) for ep in ultimaker2_prefix_list)
|
||||
|
||||
# ultimaker2 family quality profiles used to set as "fdmprinter" profiles
|
||||
|
|
|
@ -85,6 +85,34 @@ class VersionUpgrade32to33(VersionUpgrade):
|
|||
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades a preferences file from version 3.2 to 3.3.
|
||||
#
|
||||
# \param serialised The serialised form of a preferences file.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradePreferences(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
# Update version numbers
|
||||
if "general" not in parser:
|
||||
parser["general"] = {}
|
||||
parser["general"]["version"] = "6"
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "4"
|
||||
|
||||
# The auto_slice preference changed its default value to "disabled" so if there is no value in previous versions,
|
||||
# then it means the desired value is auto_slice "enabled"
|
||||
if "auto_slice" not in parser["general"]:
|
||||
parser["general"]["auto_slice"] = "True"
|
||||
elif parser["general"]["auto_slice"] == "False": # If the value is False, then remove the entry
|
||||
del parser["general"]["auto_slice"]
|
||||
|
||||
# Re-serialise the file.
|
||||
output = io.StringIO()
|
||||
parser.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
## Upgrades a container stack from version 3.2 to 3.3.
|
||||
#
|
||||
# \param serialised The serialised form of a container stack.
|
||||
|
|
|
@ -9,6 +9,8 @@ def getMetaData():
|
|||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 5000004): ("preferences", 6000004, upgrade.upgradePreferences),
|
||||
|
||||
("machine_stack", 3000004): ("machine_stack", 4000004, upgrade.upgradeStack),
|
||||
("extruder_train", 3000004): ("extruder_train", 4000004, upgrade.upgradeStack),
|
||||
|
||||
|
@ -18,6 +20,10 @@ def getMetaData():
|
|||
("variant", 2000004): ("variant", 3000004, upgrade.upgradeVariants)
|
||||
},
|
||||
"sources": {
|
||||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"machine_stack": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue