Merge branch '2.3' of github.com:Ultimaker/Cura into 2.3

This commit is contained in:
Tim Kuipers 2016-09-08 16:19:06 +02:00
commit 84fb59a0f3
34 changed files with 318 additions and 96 deletions

View file

@ -67,6 +67,9 @@ class BuildVolume(SceneNode):
self._disallowed_areas = []
self._disallowed_area_mesh = None
self._prime_tower_area = None
self._prime_tower_area_mesh = None
self.setCalculateBoundingBox(False)
self._volume_aabb = None
@ -82,6 +85,8 @@ class BuildVolume(SceneNode):
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self._onActiveExtruderStackChanged()
self._has_errors = False
def setWidth(self, width):
if width: self._width = width
@ -110,6 +115,10 @@ class BuildVolume(SceneNode):
if self._disallowed_area_mesh:
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
if self._prime_tower_area_mesh:
renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True,
backface_cull=True, sort=-8)
return True
## Recalculates the build volume & disallowed areas.
@ -184,6 +193,24 @@ class BuildVolume(SceneNode):
else:
self._disallowed_area_mesh = None
if self._prime_tower_area:
mb = MeshBuilder()
color = Color(1.0, 0.0, 0.0, 0.5)
points = self._prime_tower_area.getPoints()
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
self._clamp(points[0][1], min_d, max_d))
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
self._clamp(points[0][1], min_d, max_d))
for point in points:
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
self._clamp(point[1], min_d, max_d))
mb.addFace(first, previous_point, new_point, color=color)
previous_point = new_point
self._prime_tower_area_mesh = mb.build()
else:
self._prime_tower_area_mesh = None
self._volume_aabb = AxisAlignedBox(
minimum = Vector(min_w, min_h - 1.0, min_d),
maximum = Vector(max_w, max_h - self._raft_thickness, max_d))
@ -291,24 +318,34 @@ class BuildVolume(SceneNode):
if rebuild_me:
self.rebuild()
def hasErrors(self):
return self._has_errors
def _updateDisallowedAreas(self):
if not self._global_container_stack:
return
self._has_errors = False # Reset.
disallowed_areas = copy.deepcopy(
self._global_container_stack.getProperty("machine_disallowed_areas", "value"))
areas = []
machine_width = self._global_container_stack.getProperty("machine_width", "value")
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
self._prime_tower_area = None
# Add prime tower location as disallowed area.
if self._global_container_stack.getProperty("prime_tower_enable", "value") == True:
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2
prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2
disallowed_areas.append([
'''disallowed_areas.append([
[prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
[prime_tower_x, prime_tower_y - prime_tower_size],
[prime_tower_x, prime_tower_y],
[prime_tower_x - prime_tower_size, prime_tower_y],
])'''
self._prime_tower_area = Polygon([
[prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
[prime_tower_x, prime_tower_y - prime_tower_size],
[prime_tower_x, prime_tower_y],
@ -344,6 +381,9 @@ class BuildVolume(SceneNode):
areas.append(poly)
if self._prime_tower_area:
self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size)))
# Add the skirt areas around the borders of the build plate.
if bed_adhesion_size > 0:
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
@ -377,6 +417,19 @@ class BuildVolume(SceneNode):
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
], numpy.float32)))
# Check if the prime tower area intersects with any of the other areas.
# If this is the case, keep the polygon seperate, so it can be drawn in red.
# If not, add it back to disallowed area's, so it's rendered as normal.
collision = False
if self._prime_tower_area:
for area in areas:
if self._prime_tower_area.intersectsPolygon(area) is not None:
collision = True
break
if not collision:
areas.append(self._prime_tower_area)
self._prime_tower_area = None
self._has_errors = collision
self._disallowed_areas = areas
## Convenience function to calculate the size of the bed adhesion in directions x, y.

View file

@ -322,6 +322,7 @@ class CuraApplication(QtApplication):
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
if path:
instance.setPath(path)
with SaveFile(path, "wt", -1, "utf-8") as f:
f.write(data)
@ -346,6 +347,7 @@ class CuraApplication(QtApplication):
elif stack_type == "extruder_train":
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
if path:
stack.setPath(path)
with SaveFile(path, "wt", -1, "utf-8") as f:
f.write(data)
@ -693,17 +695,17 @@ class CuraApplication(QtApplication):
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
if nodes:
op = GroupedOperation()
for node in nodes:
# Ensure that the object is above the build platform
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0)))
op.push()
## Reset all transformations on nodes with mesh data.
## Reset all transformations on nodes with mesh data.
@pyqtSlot()
def resetAll(self):
Logger.log("i", "Resetting all scene transformations")
@ -719,15 +721,17 @@ class CuraApplication(QtApplication):
if nodes:
op = GroupedOperation()
for node in nodes:
# Ensure that the object is above the build platform
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
op.addOperation(SetTransformOperation(node, Vector(0, node.getMeshData().getCenterPosition().y, 0), Quaternion(), Vector(1, 1, 1)))
center_y = 0
if node.callDecoration("isGroup"):
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
else:
center_y = node.getMeshData().getCenterPosition().y
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push()
## Reload all mesh data on the screen from file.
@pyqtSlot()
def reloadAll(self):

View file

@ -16,7 +16,7 @@ class LayerPolygon:
MoveRetractionType = 9
SupportInterfaceType = 10
__jump_map = numpy.logical_or( numpy.arange(11) == NoneType, numpy.arange(11) >= MoveRetractionType )
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType)
def __init__(self, mesh, extruder, line_types, data, line_widths):
self._mesh = mesh
@ -42,7 +42,7 @@ class LayerPolygon:
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
# Should be generated in better way, not hardcoded.
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0], dtype=numpy.bool)
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool)
self._build_cache_line_mesh_mask = None
self._build_cache_needed_points = None

View file

@ -15,6 +15,9 @@ from cura.ConvexHullDecorator import ConvexHullDecorator
from . import PlatformPhysicsOperation
from . import ZOffsetDecorator
import random # used for list shuffling
class PlatformPhysics:
def __init__(self, controller, volume):
super().__init__()
@ -48,7 +51,12 @@ class PlatformPhysics:
# same direction.
transformed_nodes = []
for node in BreadthFirstIterator(root):
group_nodes = []
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
nodes = list(BreadthFirstIterator(root))
random.shuffle(nodes)
for node in nodes:
if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
continue
@ -69,6 +77,9 @@ class PlatformPhysics:
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
node._outside_buildarea = True
if node.callDecoration("isGroup"):
group_nodes.append(node) # Keep list of affected group_nodes
# Move it downwards if bottom is above platform
move_vector = Vector()
if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
@ -102,7 +113,6 @@ class PlatformPhysics:
continue # Other node is already moving, wait for next pass.
overlap = (0, 0) # Start loop with no overlap
move_vector = move_vector.set(x=overlap[0] * self._move_factor, z=overlap[1] * self._move_factor)
current_overlap_checks = 0
# Continue to check the overlap until we no longer find one.
while overlap and current_overlap_checks < self._max_overlap_checks:
@ -144,7 +154,6 @@ class PlatformPhysics:
overlap = convex_hull.intersectsPolygon(area)
if overlap is None:
continue
node._outside_buildarea = True
if not Vector.Null.equals(move_vector, epsilon=1e-5):
@ -152,6 +161,12 @@ class PlatformPhysics:
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push()
# Group nodes should override the _outside_buildarea property of their children.
for group_node in group_nodes:
for child_node in group_node.getAllChildren():
child_node._outside_buildarea = group_node._outside_buildarea
def _onToolOperationStarted(self, tool):
self._enabled = False

View file

@ -167,6 +167,19 @@ class ContainerManager(QObject):
return True
@pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name):
containers = self._container_registry.findContainers(None, id=container_id)
if not containers:
UM.Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
return False
result = containers[0].getMetaDataEntry(entry_name)
if result:
return result
else:
return ""
## Set a metadata entry of the specified container.
#
# This will set the specified entry of the container's metadata to the specified
@ -254,6 +267,10 @@ class ContainerManager(QObject):
return True
return False
@pyqtSlot(str, result = str)
def makeUniqueName(self, original_name):
return self._container_registry.uniqueName(original_name)
## Get a list of string that can be used as name filters for a Qt File Dialog
#
# This will go through the list of available container types and generate a list of strings
@ -573,6 +590,28 @@ class ContainerManager(QObject):
return new_name
@pyqtSlot(str, result = str)
def duplicateMaterial(self, material_id):
containers = self._container_registry.findInstanceContainers(id=material_id)
if not containers:
UM.Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
return ""
# Ensure all settings are saved.
UM.Application.getInstance().saveSettings()
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName(material_id)
container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer
duplicated_container = container_type(new_id)
# Instead of duplicating we load the data from the basefile again.
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
# are also correctly created.
with open(containers[0].getPath(), encoding="utf-8") as f:
duplicated_container.deserialize(f.read())
self._container_registry.addContainer(duplicated_container)
# Factory function, used by QML
@staticmethod
def createContainerManager(engine, js_engine):

View file

@ -71,7 +71,7 @@ class ThreeMFReader(MeshReader):
rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0))
# TODO: We currently do not check for normals and simply recalculate them.
mesh_builder.calculateNormals()
mesh_builder.calculateNormals(flip = True)
mesh_builder.setFileName(file_name)
node.setMeshData(mesh_builder.build().getTransformed(rotation))
node.setSelectable(True)

View file

@ -78,6 +78,10 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.SettingError)
return
if Application.getInstance().getBuildVolume().hasErrors():
self.setResult(StartJobResult.SettingError)
return
# Don't slice if there is a per object setting with an error value.
for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is not SceneNode or not node.isSelectable():

View file

@ -1,8 +1,8 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser
import os.path
from UM import PluginRegistry
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.ProfileReader import ProfileReader
@ -27,22 +27,76 @@ class CuraProfileReader(ProfileReader):
# returned.
def read(self, file_name):
try:
archive = zipfile.ZipFile(file_name, "r")
except Exception:
# zipfile doesn't give proper exceptions, so we can only catch broad ones
with zipfile.ZipFile(file_name, "r") as archive:
results = []
for profile_id in archive.namelist():
with archive.open(profile_id) as f:
serialized = f.read()
profile = self._loadProfile(serialized.decode("utf-8"), profile_id)
if profile is not None:
results.append(profile)
return results
except zipfile.BadZipFile:
# It must be an older profile from Cura 2.1.
with open(file_name, encoding="utf-8") as fhandle:
serialized = fhandle.read()
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
## Convert a profile from an old Cura to this Cura if needed.
#
# \param serialized \type{str} The profile data to convert in the serialized on-disk format.
# \param profile_id \type{str} The name of the profile.
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
def _upgradeProfile(self, serialized, profile_id):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialized)
if not "general" in parser:
Logger.log("w", "Missing required section 'general'.")
return None
if not "version" in parser["general"]:
Logger.log("w", "Missing required 'version' property")
return None
version = int(parser["general"]["version"])
if InstanceContainer.Version != version:
name = parser["general"]["name"]
return self._upgradeProfileVersion(serialized, name, version)
else:
return [(serialized, profile_id)]
## Load a profile from a serialized string.
#
# \param serialized \type{str} The profile data to read.
# \param profile_id \type{str} The name of the profile.
# \return \type{InstanceContainer|None}
def _loadProfile(self, serialized, profile_id):
# Create an empty profile.
profile = InstanceContainer(profile_id)
profile.addMetaDataEntry("type", "quality_changes")
try:
profile.deserialize(serialized)
except Exception as e: # Parsing error. This is not a (valid) Cura profile then.
Logger.log("e", "Error while trying to parse profile: %s", str(e))
return None
return profile
## Upgrade a serialized profile to the current profile format.
#
# \param serialized \type{str} The profile data to convert.
# \param profile_id \type{str} The name of the profile.
# \param source_version \type{int} The profile version of 'serialized'.
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
def _upgradeProfileVersion(self, serialized, profile_id, source_version):
converter_plugins = PluginRegistry.getInstance().getAllMetaData(filter={"version_upgrade": {} }, active_only=True)
source_format = ("profile", source_version)
profile_convert_funcs = [plugin["version_upgrade"][source_format][2] for plugin in converter_plugins
if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version]
if not profile_convert_funcs:
return []
results = []
for profile_id in archive.namelist():
# Create an empty profile.
profile = InstanceContainer(profile_id)
profile.addMetaDataEntry("type", "quality_changes")
serialized = ""
with archive.open(profile_id) as f:
serialized = f.read()
try:
profile.deserialize(serialized.decode("utf-8") )
except Exception as e: # Parsing error. This is not a (valid) Cura profile then.
Logger.log("e", "Error while trying to parse profile: %s", str(e))
continue
results.append(profile)
return results
filenames, outputs = profile_convert_funcs[0](serialized, profile_id)
return list(zip(outputs, filenames))

View file

@ -66,7 +66,10 @@ class GCodeWriter(MeshWriter):
## Create a new container with container 2 as base and container 1 written over it.
def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
flat_container = InstanceContainer(instance_container2.getName())
flat_container.setDefinition(instance_container2.getDefinition())
if instance_container1.getDefinition():
flat_container.setDefinition(instance_container1.getDefinition())
else:
flat_container.setDefinition(instance_container2.getDefinition())
flat_container.setMetaData(instance_container2.getMetaData())
for key in instance_container2.getAllKeys():

View file

@ -70,7 +70,8 @@
"magic_spiralize": "spiralize",
"prime_tower_enable": "wipe_tower",
"prime_tower_size": "math.sqrt(float(wipe_tower_volume) / float(layer_height))",
"ooze_shield_enabled": "ooze_shield"
"ooze_shield_enabled": "ooze_shield",
"skin_overlap": "fill_overlap"
},
"defaults": {

View file

@ -69,7 +69,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
else:
stack = UM.Application.getInstance().getGlobalContainerStack()
new_instance.setProperty("value", stack.getProperty(item, "value"))
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
new_instance.resetState() # Ensure that the state is not seen as a user state.
settings.addInstance(new_instance)
visibility_changed = True

View file

@ -7,6 +7,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Application import Application
from UM.Preferences import Preferences
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager
## This tool allows the user to add & change settings per node in the scene.
@ -71,11 +72,20 @@ 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
# Ensure that all extruder data is reset
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())
default_stack_id = global_container_stack.getId()
else:
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
if default_stack:
default_stack_id = default_stack.getId()
else: default_stack_id = global_container_stack.getId()
root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
node.callDecoration("setActiveExtruder", default_stack_id)
self._updateEnabled()
def _updateEnabled(self):

View file

@ -49,7 +49,7 @@ class RemovableDrivePlugin(OutputDevicePlugin):
message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(device.getName()))
message.show()
else:
message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Maybe it is still in use?").format(device.getName()))
message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(device.getName()))
message.show()
return result

View file

@ -65,7 +65,7 @@ class SliceInfo(Extension):
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False)
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False)
self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()

View file

@ -13,6 +13,7 @@ from UM.Settings.Validator import ValidatorState
from UM.View.GL.OpenGL import OpenGL
import cura.Settings
from cura.Settings.ExtruderManager import ExtruderManager
import math
@ -45,8 +46,18 @@ class SolidView(View):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
if multi_extrusion:
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr)
if not support_angle_stack:
support_angle_stack = global_container_stack
else:
support_angle_stack = global_container_stack
if Preferences.getInstance().getValue("view/show_overhang"):
angle = global_container_stack.getProperty("support_angle", "value")
angle = support_angle_stack.getProperty("support_angle", "value")
# Make sure the overhang angle is valid before passing it to the shader
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]:
@ -56,7 +67,6 @@ class SolidView(View):
else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
for node in DepthFirstIterator(scene.getRoot()):
if not node.render(renderer):

View file

@ -140,7 +140,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(catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected."))
self._error_message.show()
Logger.log("d", "Printer is busy or not connected, aborting print")
self.writeError.emit(self)

View file

@ -105,9 +105,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
@pyqtSlot(str)
def updateAllFirmware(self, file_name):
file_name = file_name.replace("file://", "") # File dialogs prepend the path with file://, which we don't need / want
if file_name.startswith("file://"):
file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want
if not self._usb_output_devices:
Message(i18n_catalog.i18nc("@info", "Cannot update firmware, there were no connected printers found.")).show()
Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected.")).show()
return
for printer_connection in self._usb_output_devices:

View file

@ -31,7 +31,7 @@ class UMOUpgradeSelection(MachineAction):
if variant:
if variant.getId() == "empty_variant":
variant_index = global_container_stack.getContainerIndex(variant)
self._createVariant(global_container_stack, variant_index)
variant = self._createVariant(global_container_stack, variant_index)
variant.setProperty("machine_heated_bed", "value", heated_bed)
self.heatedBedChanged.emit()
@ -41,4 +41,5 @@ class UMOUpgradeSelection(MachineAction):
new_variant.addMetaDataEntry("type", "variant")
new_variant.setDefinition(global_container_stack.getBottom())
UM.Settings.ContainerRegistry.getInstance().addContainer(new_variant)
global_container_stack.replaceContainer(variant_index, new_variant)
global_container_stack.replaceContainer(variant_index, new_variant)
return new_variant

View file

@ -279,6 +279,8 @@ class VersionUpgrade21to22(VersionUpgrade):
elif key in _setting_name_translations:
del settings[key]
settings[_setting_name_translations[key]] = value
if "infill_overlap" in settings: # New setting, added in 2.3
settings["skin_overlap"] = settings["infill_overlap"]
return settings
## Translates a setting name for the change from Cura 2.1 to 2.2.

View file

@ -10,6 +10,7 @@
"manufacturer": "Ultimaker",
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
"visible": false,
"has_materials": true,
"preferred_material": "*generic_pla*",
"preferred_quality": "*normal*",
"machine_extruder_trains":
@ -403,7 +404,7 @@
"description": "The maximum speed of the filament.",
"unit": "mm/s",
"type": "float",
"default_value": 25,
"default_value": 299792458000,
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
@ -1161,7 +1162,7 @@
"default_value": 25,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"maximum_value_warning": "100",
"maximum_value_warning": "25",
"enabled": "retraction_enable",
"settable_per_mesh": false,
"settable_per_extruder": true,
@ -1174,7 +1175,7 @@
"default_value": 25,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"maximum_value_warning": "100",
"maximum_value_warning": "25",
"enabled": "retraction_enable",
"value": "retraction_speed",
"settable_per_mesh": false,
@ -1188,7 +1189,7 @@
"default_value": 25,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_e",
"maximum_value_warning": "100",
"maximum_value_warning": "25",
"enabled": "retraction_enable",
"value": "retraction_speed",
"settable_per_mesh": false,
@ -3207,6 +3208,7 @@
"type": "category",
"icon": "category_dual",
"description": "Settings used for printing with multiple extruders.",
"enabled": "machine_extruder_count > 1",
"children":
{
"adhesion_extruder_nr":
@ -3284,6 +3286,7 @@
"default_value": 15,
"value": "15 if prime_tower_enable else 0",
"minimum_value": "0",
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
"maximum_value_warning": "20",
"settable_per_mesh": false,
"settable_per_extruder": false

View file

@ -9,6 +9,9 @@
"visible": false
},
"overrides": {
"machine_max_feedrate_e": {
"default_value": 45
},
"material_print_temperature": {
"minimum_value": "0"
},

View file

@ -14,6 +14,7 @@
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"platform_offset": [9, 0, 0],
"has_materials": false,
"supported_actions":["UpgradeFirmware"]
},
"overrides": {

View file

@ -78,7 +78,7 @@
"default_value": 185
},
"prime_tower_position_y": {
"default_value": 175
"default_value": 160
}
}
}

View file

@ -14,11 +14,10 @@ Generic Nylon profile. Serves as an example file, data in this file is not corre
<color_code>#3DF266</color_code>
</metadata>
<properties>
<density>1.19</density>
<density>1.14</density>
<diameter>2.85</diameter>
</properties>
<settings>
<setting key="hardware compatible">no</setting><!-- This material is not supported on most printers due to high temperatures -->
<setting key="print temperature">250</setting>
<setting key="heated bed temperature">60</setting>
<setting key="standby temperature">175</setting>

View file

@ -14,7 +14,7 @@ Generic PC profile. Serves as an example file, data in this file is not correct.
<color_code>#F29030</color_code>
</metadata>
<properties>
<density>1.18</density>
<density>1.19</density>
<diameter>2.85</diameter>
</properties>
<settings>

View file

@ -14,7 +14,7 @@ Generic TPU 95A profile. Serves as an example file, data in this file is not cor
<color_code>#B22744</color_code>
</metadata>
<properties>
<density>1.19</density>
<density>1.22</density>
<diameter>2.85</diameter>
</properties>
<settings>

View file

@ -464,12 +464,11 @@ UM.MainWindow
target: Cura.Actions.addProfile
onTriggered:
{
Cura.ContainerManager.createQualityChanges(null);
preferences.setPage(4);
preferences.show();
// Show the renameDialog after a very short delay so the preference page has time to initiate
showProfileNameDialogTimer.start();
// Create a new profile after a very short delay so the preference page has time to initiate
createProfileTimer.start();
}
}
@ -516,11 +515,11 @@ UM.MainWindow
Timer
{
id: showProfileNameDialogTimer
id: createProfileTimer
repeat: false
interval: 1
onTriggered: preferences.getCurrentItem().showProfileNameDialog()
onTriggered: preferences.getCurrentItem().createProfile()
}
// BlurSettings is a way to force the focus away from any of the setting items.

View file

@ -115,7 +115,7 @@ TabView
width: base.secondColumnWidth;
value: properties.density;
decimals: 2
suffix: "g/cm"
suffix: "g/cm³"
stepSize: 0.01
readOnly: !base.editingEnabled;
@ -128,7 +128,7 @@ TabView
width: base.secondColumnWidth;
value: properties.diameter;
decimals: 2
suffix: "mm³"
suffix: "mm"
stepSize: 0.01
readOnly: !base.editingEnabled;

View file

@ -129,30 +129,24 @@ UM.ManagementPage
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
},
/* // apparently visible does not work on OS X
Button
// apparently visible does not work on OS X
/*Button
{
text: catalog.i18nc("@action:button", "Duplicate");
iconName: "list-add";
enabled: base.currentItem != null
onClicked:
{
var material_id = Cura.ContainerManager.duplicateContainer(base.currentItem.id)
var base_file = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "base_file")
// We need to copy the base container instead of the specific variant.
var material_id = base_file == "" ? Cura.ContainerManager.duplicateMaterial(base.currentItem.id): Cura.ContainerManager.duplicateMaterial(base_file)
if(material_id == "")
{
return
}
if(Cura.MachineManager.filterQualityByMachine)
{
var quality_id = Cura.ContainerManager.duplicateContainer(Cura.MachineManager.activeQualityId)
Cura.ContainerManager.setContainerMetaDataEntry(quality_id, "material", material_id)
Cura.MachineManager.setActiveQuality(quality_id)
}
Cura.MachineManager.setActiveMaterial(material_id)
}
visible: false;
},
*/
Button

View file

@ -84,7 +84,7 @@ UM.ManagementPage
onClicked:
{
newNameDialog.object = base.currentItem != null ? base.currentItem.name : "";
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : "";
newNameDialog.open();
newNameDialog.selectText();
}
@ -100,7 +100,7 @@ UM.ManagementPage
onClicked:
{
newDuplicateNameDialog.object = base.currentItem.name;
newDuplicateNameDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
newDuplicateNameDialog.open();
newDuplicateNameDialog.selectText();
}
@ -141,11 +141,12 @@ UM.ManagementPage
scrollviewCaption: catalog.i18nc("@label %1 is printer name","Printer: %1").arg(Cura.MachineManager.activeMachineName)
signal showProfileNameDialog()
onShowProfileNameDialog:
signal createProfile()
onCreateProfile:
{
renameDialog.open();
renameDialog.selectText();
newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : "";
newNameDialog.open();
newNameDialog.selectText();
}
signal selectContainer(string name)
@ -267,6 +268,7 @@ UM.ManagementPage
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Rename Profile")
id: renameDialog;
object: base.currentItem != null ? base.currentItem.name : ""
onAccepted:
@ -279,6 +281,7 @@ UM.ManagementPage
// Dialog to request a name when creating a new profile
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Create Profile")
id: newNameDialog;
object: "<new name>";
onAccepted:
@ -292,6 +295,7 @@ UM.ManagementPage
// Dialog to request a name when duplicating a new profile
UM.RenameDialog
{
title: catalog.i18nc("@title:window", "Duplicate Profile")
id: newDuplicateNameDialog;
object: "<new name>";
onAccepted:

View file

@ -138,7 +138,7 @@ Item {
{
id: linkedSettingIcon;
visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && !definition.settable_per_extruder && base.showLinkedSettingIcon
visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && (!definition.settable_per_extruder || definition.global_inherits_stack != "-1") && base.showLinkedSettingIcon
height: parent.height;
width: height;

View file

@ -40,7 +40,7 @@ ScrollView
id: delegate
width: UM.Theme.getSize("sidebar").width;
height: provider.properties.enabled == "True" ? UM.Theme.getSize("section").height : 0
height: provider.properties.enabled == "True" ? UM.Theme.getSize("section").height : - contents.spacing
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }

View file

@ -199,18 +199,32 @@ QtObject {
property Component progressbar: Component{
ProgressBarStyle {
background:Rectangle {
background: Rectangle {
implicitWidth: Theme.getSize("message").width - (Theme.getSize("default_margin").width * 2)
implicitHeight: Theme.getSize("progressbar").height
radius: Theme.getSize("progressbar_radius").width
color: Theme.getColor("progressbar_background")
color: control.hasOwnProperty("backgroundColor") ? control.backgroundColor : Theme.getColor("progressbar_background")
}
progress: Rectangle {
color: control.indeterminate ? "transparent" : Theme.getColor("progressbar_control")
color:
{
if(control.indeterminate)
{
return "transparent";
}
else if(control.hasOwnProperty("controlColor"))
{
return control.controlColor;
}
else
{
return Theme.getColor("progressbar_control");
}
}
radius: Theme.getSize("progressbar_radius").width
Rectangle{
radius: Theme.getSize("progressbar_radius").width
color: Theme.getColor("progressbar_control")
color: control.hasOwnProperty("controlColor") ? control.controlColor : Theme.getColor("progressbar_control")
width: Theme.getSize("progressbar_control").width
height: Theme.getSize("progressbar_control").height
visible: control.indeterminate

View file

@ -159,9 +159,17 @@
"tooltip": [12, 169, 227, 255],
"tooltip_text": [255, 255, 255, 255],
"message_background": [255, 255, 255, 255],
"message_text": [32, 166, 219, 255],
"message_dismiss": [127, 127, 127, 255],
"message_background": [24, 41, 77, 255],
"message_text": [255, 255, 255, 255],
"message_border": [24, 41, 77, 255],
"message_button": [255, 255, 255, 255],
"message_button_hover": [12, 169, 227, 255],
"message_button_active": [32, 166, 219, 255],
"message_button_text": [24, 41, 77, 255],
"message_button_text_hover": [255, 255, 255, 255],
"message_button_text_active": [255, 255, 255, 255],
"message_progressbar_background": [255, 255, 255, 255],
"message_progressbar_control": [12, 169, 227, 255],
"tool_panel_background": [255, 255, 255, 255],