Merge master into material marketplace

This commit is contained in:
Lipu Fei 2018-05-01 11:56:34 +02:00
commit 9a5fb47a6e
228 changed files with 125261 additions and 4020 deletions

View file

@ -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 = []

View file

@ -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):

View file

@ -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 extruders 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 movebefore printing the brim/skirtis 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 users 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 were 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

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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()

View file

@ -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.

View file

@ -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())

View 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()
}
}
}
}

View file

@ -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

View 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"
}

View file

@ -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)

View file

@ -4,7 +4,7 @@
from . import SupportEraser
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {

View file

@ -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

View file

@ -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'
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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"}