Merge branch 'master' of https://github.com/Ultimaker/Cura into layerview_dev

This commit is contained in:
Johan K 2016-07-22 10:21:41 +02:00
commit c6790981d6
37 changed files with 835 additions and 316 deletions

View file

@ -111,6 +111,8 @@ class ThreeMFReader(MeshReader):
if len(objects) > 1:
group_decorator = GroupDecorator()
result.addDecorator(group_decorator)
elif len(objects) == 1:
result = result.getChildren()[0] # Only one object found, return that.
except Exception as e:
Logger.log("e", "exception occured in 3mf reader: %s", e)

View file

@ -1,6 +1,11 @@
[2.1.3]
*Material Profiles
New material profiles for CPE+, PC, Nylon and TPU for the Ultimaker 2+ family.
[2.1.2]
Cura has been completely reengineered from the ground up for an even more seamless integration between hardware, software and materials. Together with its intuitive new user interface, its now also ready for any future developments. For the beginner Cura makes 3D printing incredibly easy, and for more advanced users, there are over 140 new customisable settings.
Cura has been completely reengineered from the ground up for an even more seamless integration between hardware, software and materials. Together with its intuitive new user interface, its now also ready for any future developments. For the beginner Cura makes 3D printing incredibly easy, and for more advanced users, there are over 200 customizable settings.
*Select Multiple Objects
You now have the freedom to select and manipulate multiple objects at the same time.
@ -27,22 +32,61 @@ An optimized 64-bit Windows Cura version is now available. This allows you to lo
Cura allows you to set a number of lines/layers instead of millimeters. The engine automatically calculates the right settings.
*Per-Object Settings
You can now override individual settings for different objects in advanced mode.
Per-object settings allow you to override individual profile settings per object.
*Fuzzy Skin
Prints the outer walls with a jittering motion to give your object a diffused finish.
*Engine Features
*Wire Printing
The object is printed with a mid-air / net-like structure, following the mesh surface. The build plate will move up and down during diagonal segments. Though not visible in layer view, you can view the result in other software, such as Repetier Host or http://chilipeppr.com/tinyg.
*Line Width
Line width settings added per feature: Global, Walls, Top/Bottom, Infill, Skirt, Support.
* Conical Support
An experimental filament, cost-reduction feature, for support.
*Pattern Settings
Pattern settings improved per feature: Top/Bottom, Infill, Support.
*Shell
*Alternate Skin Rotation
Helps to combat the pillowing problem on top layers.
*Alternate Extra Wall
For better infill adhesion.
*Horizontal Expansion
Allows to compensate model x,y-size to get a 1:1 result.
*Travel
*Avoid Printed Parts
When moving to the next part to print, avoid collisions between the nozzle and other parts which are already printed.
*Support
*Stair Step Height
Sets the balance between sturdy and hard to remove support. By setting steps of the stair-like bottom of the support resting on the model.
*ZigZag
A new, infill type thats easily breakable, introduced specially for support.
*Support Roofs
A new sub-feature to reduce scars the support leaves on overhangs.
*Support Towers
Specialized support for tiny overhang areas.
*ZigZag infill
A new, infill type thats easily breakable, introduced specially for support.
*Special Modes
* Avoid Printed Parts
While combing, the print head moves around printed parts, avoiding collisions with the nozzle and a part thats already printed.
*Surface Mode
This mode will print the surface of the mesh instead of the enclosed volume. This used to be called Only follow mesh surface. In addition to the surface mode and normal, a both mode has now been added. This ensures all closed volumes are printed as normal and all loose geometry as single walls.
*Experimental Features
*Conical Support
An experimental filament, cost-reduction feature, for support.
*Draft Shield
Prints a protective wall at a set distance around the object that prevents air from hitting the print, reducing warping.
*Fuzzy Skin
Prints the outer walls with a jittering motion to give your object a diffuse finish.
*Wire Printing
The object is printed with a mid-air / net-like structure, following the mesh surface. The build plate will move up and down during diagonal segments. Though not visible in layer view, you can view the result in other software, such as Repetier Host or http://chilipeppr.com/tinyg.

View file

@ -22,6 +22,7 @@ from . import StartSliceJob
import os
import sys
from time import time
from PyQt5.QtCore import QTimer
@ -91,6 +92,7 @@ class CuraEngineBackend(Backend):
self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers.
self._backend_log_max_lines = 200 # Maximal count of lines to buffer
self._error_message = None #Pop-up message that shows errors.
self.backendQuit.connect(self._onBackendQuit)
@ -100,6 +102,8 @@ class CuraEngineBackend(Backend):
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
self._slice_start_time = None
## Called when closing the application.
#
# This function should terminate the engine process.
@ -128,6 +132,7 @@ class CuraEngineBackend(Backend):
## Perform a slice of the scene.
def slice(self):
self._slice_start_time = time()
if not self._enabled or not self._global_container_stack: #We shouldn't be slicing.
# try again in a short time
self._change_timer.start()
@ -217,6 +222,7 @@ class CuraEngineBackend(Backend):
# Preparation completed, send it to the backend.
self._socket.sendMessage(job.getSliceMessage())
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
## Listener for when the scene has changed.
#
@ -286,7 +292,7 @@ class CuraEngineBackend(Backend):
self.processingProgress.emit(1.0)
self._slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
self._process_layers_job.start()

View file

@ -9,6 +9,7 @@ from UM.Mesh.MeshData import MeshData
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Math.Vector import Vector
@ -17,7 +18,7 @@ from cura import LayerDataDecorator
from cura import LayerPolygon
import numpy
from time import time
catalog = i18nCatalog("cura")
@ -45,6 +46,7 @@ class ProcessSlicedLayersJob(Job):
if len(self._layers) == 0:
return
start_time = time()
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
self._progress = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
self._progress.show()
@ -155,7 +157,10 @@ class ProcessSlicedLayersJob(Job):
new_node.addDecorator(decorator)
new_node.setMeshData(mesh)
new_node.setParent(self._scene.getRoot()) # Note: After this we can no longer abort!
# Set build volume as parent, the build volume can move as a result of raft settings.
# It makes sense to set the build volume as parent: the print is actually printed on it.
new_node_parent = Application.getInstance().getBuildVolume()
new_node.setParent(new_node_parent) # Note: After this we can no longer abort!
settings = Application.getInstance().getGlobalContainerStack()
if not settings.getProperty("machine_center_is_zero", "value"):
@ -174,6 +179,8 @@ class ProcessSlicedLayersJob(Job):
# Clear the unparsed layers. This saves us a bunch of memory if the Job does not get destroyed.
self._layers = None
Logger.log("d", "Processing layers took %s seconds", time() - start_time)
def _onActiveViewChanged(self):
if self.isRunning():
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":

View file

@ -129,7 +129,7 @@ class StartSliceJob(Job):
self._buildGlobalSettingsMessage(stack)
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getBottom().getId()):
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
for group in object_groups:
@ -174,10 +174,17 @@ class StartSliceJob(Job):
def _buildExtruderMessage(self, stack):
message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position"))
material_instance_container = stack.findContainer({"type": "material"})
for key in stack.getAllKeys():
setting = message.getMessage("settings").addRepeatedMessage("settings")
setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
if key == "material_guid" and material_instance_container:
# Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
setting.value = str(material_instance_container.getMetaDataEntry("GUID", "")).encode("utf-8")
else:
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread()
## Sends all global settings to the engine.

View file

@ -3,7 +3,6 @@
import os.path
from UM.Application import Application #To get the machine manager to create the new profile in.
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer #The new profile to make.
from cura.ProfileReader import ProfileReader

View file

@ -1,10 +1,9 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os
import re #Regular expressions for parsing escape characters in the settings.
import json
from UM.Application import Application #To get the machine manager to create the new profile in.
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Logger import Logger
from UM.i18n import i18nCatalog
@ -22,7 +21,7 @@ class GCodeProfileReader(ProfileReader):
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 2
version = 3
## Dictionary that defines how characters are escaped when embedded in
# g-code.
@ -66,21 +65,37 @@ class GCodeProfileReader(ProfileReader):
Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e))
return None
# Un-escape the serialized profile.
pattern = re.compile("|".join(GCodeProfileReader.escape_characters.keys()))
# Perform the replacement with a regular expression.
serialized = pattern.sub(lambda m: GCodeProfileReader.escape_characters[re.escape(m.group(0))], serialized)
serialized = unescapeGcodeComment(serialized)
Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized)))
# Create an empty profile - the id and name will be changed by the ContainerRegistry
profile = InstanceContainer("")
try:
profile.deserialize(serialized)
except Exception as e: # Not a valid g-code file.
Logger.log("e", "Unable to serialise the profile: %s", str(e))
return None
json_data = json.loads(serialized)
profile.addMetaDataEntry("type", "quality")
profile_strings = [json_data["global_quality"]]
profile_strings.extend(json_data.get("extruder_quality", []))
return profile
return [readQualityProfileFromString(profile_string) for profile_string in profile_strings]
## Unescape a string which has been escaped for use in a gcode comment.
#
# \param string The string to unescape.
# \return \type{str} The unscaped string.
def unescapeGcodeComment(string):
# Un-escape the serialized profile.
pattern = re.compile("|".join(GCodeProfileReader.escape_characters.keys()))
# Perform the replacement with a regular expression.
return pattern.sub(lambda m: GCodeProfileReader.escape_characters[re.escape(m.group(0))], string)
## Read in a profile from a serialized string.
#
# \param profile_string The profile data in serialized form.
# \return \type{Profile} the resulting Profile object or None if it could not be read.
def readQualityProfileFromString(profile_string):
# Create an empty profile - the id and name will be changed by the ContainerRegistry
profile = InstanceContainer("")
try:
profile.deserialize(profile_string)
except Exception as e: # Not a valid g-code file.
Logger.log("e", "Unable to serialise the profile: %s", str(e))
return None
return profile

View file

@ -4,8 +4,13 @@
from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer #To create a complete setting profile to store in the g-code.
import UM.Settings.ContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
import re #For escaping characters in the settings.
import json
## Writes g-code to a file.
#
@ -23,7 +28,7 @@ class GCodeWriter(MeshWriter):
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 2
version = 3
## Dictionary that defines how characters are escaped when embedded in
# g-code.
@ -64,25 +69,49 @@ class GCodeWriter(MeshWriter):
#
# \param settings A container stack to serialise.
# \return A serialised string of the settings.
def _serialiseSettings(self, settings):
def _serialiseSettings(self, stack):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
global_stack = Application.getInstance().getGlobalContainerStack()
container_with_profile = global_stack.findContainer({"type": "quality"})
serialized = container_with_profile.serialize()
container_with_profile = stack.findContainer({"type": "quality"})
machine_manager = CuraApplication.getInstance().getMachineManager()
# Duplicate the current quality profile and update it with any user settings.
flat_quality_id = machine_manager.duplicateContainer(container_with_profile.getId())
flat_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = flat_quality_id)[0]
user_settings = stack.getTop()
for key in user_settings.getAllKeys():
flat_quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
serialized = flat_quality.serialize()
data = {"global_quality": serialized}
manager = ExtruderManager.getInstance()
for extruder in manager.getMachineExtruders(stack.getId()):
extruder_quality = extruder.findContainer({"type": "quality"})
flat_extruder_quality_id = machine_manager.duplicateContainer(extruder_quality.getId())
flat_extruder_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=flat_extruder_quality_id)[0]
extruder_user_settings = extruder.getTop()
for key in extruder_user_settings.getAllKeys():
flat_extruder_quality.setProperty(key, "value", extruder_user_settings.getProperty(key, "value"))
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)
json_string = json.dumps(data)
# Escape characters that have a special meaning in g-code comments.
pattern = re.compile("|".join(GCodeWriter.escape_characters.keys()))
# Perform the replacement with a regular expression.
serialized = pattern.sub(lambda m: GCodeWriter.escape_characters[re.escape(m.group(0))], serialized)
escaped_string = pattern.sub(lambda m: GCodeWriter.escape_characters[re.escape(m.group(0))], json_string)
# Introduce line breaks so that each comment is no longer than 80 characters. Prepend each line with the prefix.
result = ""
# Lines have 80 characters, so the payload of each line is 80 - prefix.
for pos in range(0, len(serialized), 80 - prefix_length):
result += prefix + serialized[pos : pos + 80 - prefix_length] + "\n"
serialized = result
return serialized
for pos in range(0, len(escaped_string), 80 - prefix_length):
result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
return result

View file

@ -3,10 +3,12 @@
from UM.Tool import Tool
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Application import Application
from UM.Preferences import Preferences
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## This tool allows the user to add & change settings per node in the scene.
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
class PerObjectSettingsTool(Tool):
@ -69,6 +71,11 @@ class PerObjectSettingsTool(Tool):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
if not self._multi_extrusion:
# Ensure that all extruder data is reset
root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
node.callDecoration("setActiveExtruder", global_container_stack.getId())
self._updateEnabled()
def _updateEnabled(self):

View file

@ -59,7 +59,7 @@ class SliceInfo(Extension):
material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
# TODO: Send material per extruder instead of mashing it on a pile
material_used = math.pi * material_radius * material_radius * sum(print_information.materialAmounts) #Volume of all materials used
material_used = math.pi * material_radius * material_radius * sum(print_information.materialLengths) #Volume of all materials used
# Get model information (bounding boxes, hashes and transformation matrix)
models_info = []
@ -93,7 +93,6 @@ class SliceInfo(Extension):
"printtime": print_information.currentPrintTime.getDisplayString(),
"filament": material_used,
"language": Preferences.getInstance().getValue("general/language"),
"materials_profiles ": {}
}
for container in global_container_stack.getContainers():
container_id = container.getId()

View file

@ -8,14 +8,12 @@ import time
import queue
import re
import functools
import os.path
from UM.Application import Application
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
from UM.Message import Message
from PyQt5.QtQml import QQmlComponent, QQmlContext
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal
from UM.i18n import i18nCatalog
@ -137,7 +135,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# \param gcode_list List with gcode (strings).
def printGCode(self, gcode_list):
if self._progress or self._connection_state != ConnectionState.connected:
self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
self._error_message = Message(catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
self._error_message.show()
Logger.log("d", "Printer is busy or not connected, aborting print")
self.writeError.emit(self)
@ -504,6 +502,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# It will be normalized (based on max_progress) to range 0 - 100
def setProgress(self, progress, max_progress = 100):
self._progress = (progress / max_progress) * 100 # Convert to scale of 0-100
if self._progress == 100:
# Printing is done, reset progress
self._gcode_position = 0
self.setProgress(0)
self._is_printing = False
self._is_paused = False
self._updateJobState("ready")
self.progressChanged.emit()
## Cancel the current print. Printer connection wil continue to listen.

View file

@ -197,7 +197,6 @@ Cura.MachineAction
Button
{
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
//
onClicked:
{
if (checkupMachineAction.heatupHotendStarted)

View file

@ -42,7 +42,7 @@ Cura.MachineAction
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@label", "Self-built heated bed")
text: catalog.i18nc("@label", "Heated bed (official kit or self-built)")
checked: manager.hasHeatedBed
onClicked: manager.hasHeatedBed ? manager.removeHeatedBed() : manager.addHeatedBed()
}

View file

@ -47,11 +47,11 @@ class MachineInstance:
raise UM.VersionUpgrade.InvalidVersionException("The version of this machine instance is wrong. It must be 1.")
self._type_name = config.get("general", "type")
self._variant_name = config.get("general", "variant", fallback = "empty")
self._variant_name = config.get("general", "variant", fallback = "empty_variant")
self._name = config.get("general", "name", fallback = "")
self._key = config.get("general", "key", fallback = None)
self._active_profile_name = config.get("general", "active_profile", fallback = "empty")
self._active_material_name = config.get("general", "material", fallback = "empty")
self._active_profile_name = config.get("general", "active_profile", fallback = "empty_quality")
self._active_material_name = config.get("general", "material", fallback = "empty_material")
self._machine_setting_overrides = {}
for key, value in config["machine_settings"].items():

View file

@ -18,7 +18,11 @@ _printer_translations = {
_profile_translations = {
"PLA": "generic_pla",
"ABS": "generic_abs",
"CPE": "generic_cpe"
"CPE": "generic_cpe",
"Low Quality": "low",
"Normal Quality": "normal",
"High Quality": "high",
"Ulti Quality": "high" #This one doesn't have an equivalent. Map it to high.
}
## How to translate setting names from the old version to the new.