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

This commit is contained in:
Tim Kuipers 2016-09-05 14:39:11 +02:00
commit 99a44fa1d2
22 changed files with 164 additions and 91 deletions

View File

@ -280,7 +280,7 @@ class BuildVolume(SceneNode):
self._height = self._global_container_stack.getProperty("machine_height", "value") self._height = self._global_container_stack.getProperty("machine_height", "value")
rebuild_me = True rebuild_me = True
if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings: if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence":
self._updateDisallowedAreas() self._updateDisallowedAreas()
rebuild_me = True rebuild_me = True
@ -383,6 +383,10 @@ class BuildVolume(SceneNode):
def _getBedAdhesionSize(self, container_stack): def _getBedAdhesionSize(self, container_stack):
skirt_size = 0.0 skirt_size = 0.0
# If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
return 0.1 # Return a very small value, so we do draw disallowed area's near the edges.
adhesion_type = container_stack.getProperty("adhesion_type", "value") adhesion_type = container_stack.getProperty("adhesion_type", "value")
if adhesion_type == "skirt": if adhesion_type == "skirt":
skirt_distance = container_stack.getProperty("skirt_gap", "value") skirt_distance = container_stack.getProperty("skirt_gap", "value")

View File

@ -56,6 +56,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._global_stack and self._node: if self._global_stack and self._node:
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32))) hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
hull = self._add2DAdhesionMargin(hull)
return hull return hull
## Get the convex hull of the node with the full head size ## Get the convex hull of the node with the full head size
@ -229,17 +230,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
machine_head_coords = numpy.array( machine_head_coords = numpy.array(
self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), self._global_stack.getProperty("machine_head_with_fans_polygon", "value"),
numpy.float32) numpy.float32)
head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions
if adhesion_type == "raft": if adhesion_type == "raft":
extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value") - head_y_size) extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value"))
elif adhesion_type == "brim": elif adhesion_type == "brim":
extra_margin = max(0, self._global_stack.getProperty("brim_width", "value") - head_y_size) extra_margin = max(0, self._global_stack.getProperty("brim_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value"))
elif adhesion_type == "skirt": elif adhesion_type == "skirt":
extra_margin = max( extra_margin = max(
0, self._global_stack.getProperty("skirt_gap", "value") + 0, self._global_stack.getProperty("skirt_gap", "value") +
self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value") - self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value"))
head_y_size)
# adjust head_and_fans with extra margin # adjust head_and_fans with extra margin
if extra_margin > 0: if extra_margin > 0:
# In Cura 2.2+, there is a function to create this circle-like polygon. # In Cura 2.2+, there is a function to create this circle-like polygon.
@ -288,4 +288,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
_affected_settings = [ _affected_settings = [
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
"raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence", "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"] "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]

View File

@ -23,6 +23,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from cura.SetParentOperation import SetParentOperation from cura.SetParentOperation import SetParentOperation
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
@ -36,7 +37,6 @@ from . import BuildVolume
from . import CameraAnimation from . import CameraAnimation
from . import PrintInformation from . import PrintInformation
from . import CuraActions from . import CuraActions
from . import MultiMaterialDecorator
from . import ZOffsetDecorator from . import ZOffsetDecorator
from . import CuraSplashScreen from . import CuraSplashScreen
from . import CameraImageProvider from . import CameraImageProvider
@ -180,6 +180,8 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addContainer(empty_material_container) ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container._id = "empty_quality" empty_quality_container._id = "empty_quality"
empty_quality_container.setName("Not supported")
empty_quality_container.addMetaDataEntry("quality_type", "normal")
empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.addMetaDataEntry("type", "quality")
ContainerRegistry.getInstance().addContainer(empty_quality_container) ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container = copy.deepcopy(empty_container)
@ -700,8 +702,7 @@ class CuraApplication(QtApplication):
op = GroupedOperation() op = GroupedOperation()
for node in nodes: for node in nodes:
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
op.addOperation(SetTransformOperation(node, Vector(0,0,0))) op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0)))
op.push() op.push()
## Reset all transformations on nodes with mesh data. ## Reset all transformations on nodes with mesh data.
@ -724,7 +725,8 @@ class CuraApplication(QtApplication):
for node in nodes: for node in nodes:
# Ensure that the object is above the build platform # Ensure that the object is above the build platform
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
op.addOperation(SetTransformOperation(node, Vector(0,0,0), Quaternion(), Vector(1, 1, 1)))
op.addOperation(SetTransformOperation(node, Vector(0, node.getMeshData().getCenterPosition().y, 0), Quaternion(), Vector(1, 1, 1)))
op.push() op.push()
@ -792,8 +794,6 @@ class CuraApplication(QtApplication):
except Exception as e: except Exception as e:
Logger.log("d", "mergeSelected: Exception:", e) Logger.log("d", "mergeSelected: Exception:", e)
return return
multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator()
group_node.addDecorator(multi_material_decorator)
# Compute the center of the objects when their origins are aligned. # Compute the center of the objects when their origins are aligned.
object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren()] object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren()]

View File

@ -1,11 +0,0 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class MultiMaterialDecorator(SceneNodeDecorator):
def __init__(self):
super().__init__()
def isMultiMaterial(self):
return True
def __deepcopy__(self, memo):
return MultiMaterialDecorator()

View File

@ -102,11 +102,11 @@ class PlatformPhysics:
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection. # Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
head_hull = node.callDecoration("getConvexHullHead") head_hull = node.callDecoration("getConvexHullHead")
if head_hull: if head_hull:
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead")) overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
if not overlap: if not overlap:
other_head_hull = other_node.callDecoration("getConvexHullHead") other_head_hull = other_node.callDecoration("getConvexHullHead")
if other_head_hull: if other_head_hull:
overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull) overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
else: else:
own_convex_hull = node.callDecoration("getConvexHull") own_convex_hull = node.callDecoration("getConvexHull")
other_convex_hull = other_node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull")

View File

@ -423,8 +423,8 @@ class ContainerManager(QObject):
# stack and clear the user settings. # stack and clear the user settings.
# #
# \return \type{bool} True if the operation was successfully, False if not. # \return \type{bool} True if the operation was successfully, False if not.
@pyqtSlot(result = bool) @pyqtSlot(str, result = bool)
def createQualityChanges(self): def createQualityChanges(self, base_name):
global_stack = UM.Application.getInstance().getGlobalContainerStack() global_stack = UM.Application.getInstance().getGlobalContainerStack()
if not global_stack: if not global_stack:
return False return False
@ -435,8 +435,9 @@ class ContainerManager(QObject):
return False return False
self._machine_manager.blurSettings.emit() self._machine_manager.blurSettings.emit()
if base_name is None or base_name == "":
unique_name = self._container_registry.uniqueName(active_quality_name) base_name = active_quality_name
unique_name = self._container_registry.uniqueName(base_name)
# Go through the active stacks and create quality_changes containers from the user containers. # Go through the active stacks and create quality_changes containers from the user containers.
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
@ -540,8 +541,8 @@ class ContainerManager(QObject):
# \param quality_name The name of the quality to duplicate. # \param quality_name The name of the quality to duplicate.
# #
# \return A string containing the name of the duplicated containers, or an empty string if it failed. # \return A string containing the name of the duplicated containers, or an empty string if it failed.
@pyqtSlot(str, result = str) @pyqtSlot(str, str, result = str)
def duplicateQualityOrQualityChanges(self, quality_name): def duplicateQualityOrQualityChanges(self, quality_name, base_name):
global_stack = UM.Application.getInstance().getGlobalContainerStack() global_stack = UM.Application.getInstance().getGlobalContainerStack()
if not global_stack or not quality_name: if not global_stack or not quality_name:
return "" return ""
@ -551,7 +552,10 @@ class ContainerManager(QObject):
UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name) UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name)
return "" return ""
new_name = self._container_registry.uniqueName(quality_name) if base_name is None:
base_name = quality_name
new_name = self._container_registry.uniqueName(base_name)
container_type = containers[0].getMetaDataEntry("type") container_type = containers[0].getMetaDataEntry("type")
if container_type == "quality": if container_type == "quality":

View File

@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QMessageBox
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message
from UM.Settings.SettingRelation import RelationType from UM.Settings.SettingRelation import RelationType
import UM.Settings import UM.Settings
@ -547,6 +547,7 @@ class MachineManager(QObject):
preferred_material = None preferred_material = None
if old_material: if old_material:
preferred_material_name = old_material.getName() preferred_material_name = old_material.getName()
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id) self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id)
else: else:
Logger.log("w", "While trying to set the active variant, no variant was found to replace.") Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
@ -614,9 +615,16 @@ class MachineManager(QObject):
stack_quality_changes = self._empty_quality_changes_container stack_quality_changes = self._empty_quality_changes_container
old_quality = stack.findContainer(type = "quality") old_quality = stack.findContainer(type = "quality")
old_quality.nameChanged.disconnect(self._onQualityNameChanged) if old_quality:
old_quality.nameChanged.disconnect(self._onQualityNameChanged)
else:
Logger.log("w", "Could not find old quality while changing active quality.")
old_changes = stack.findContainer(type = "quality_changes") old_changes = stack.findContainer(type = "quality_changes")
old_changes.nameChanged.disconnect(self._onQualityNameChanged) if old_changes:
old_changes.nameChanged.disconnect(self._onQualityNameChanged)
else:
Logger.log("w", "Could not find old quality_changes while changing active quality.")
stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality) stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality)
stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes) stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes)
@ -799,15 +807,15 @@ class MachineManager(QObject):
if containers: if containers:
return containers[0] return containers[0]
if "name" in search_criteria or "id" in search_criteria: containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if "variant" in search_criteria or "id" in search_criteria:
# If a material by this name can not be found, try a wider set of search criteria # If a material by this name can not be found, try a wider set of search criteria
search_criteria.pop("name", None) search_criteria.pop("variant", None)
search_criteria.pop("id", None) search_criteria.pop("id", None)
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if containers: if containers:
return containers[0] return containers[0]
Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
return self._empty_material_container return self._empty_material_container
def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None): def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None):
@ -848,10 +856,9 @@ class MachineManager(QObject):
containers = container_registry.findInstanceContainers(**search_criteria) containers = container_registry.findInstanceContainers(**search_criteria)
if containers: if containers:
return containers[0] return containers[0]
# We still weren't able to find a quality for this specific material. # We still weren't able to find a quality for this specific material.
# Try to find qualities for a generic version of the material. # Try to find qualities for a generic version of the material.
material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic" } material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"): if definition.getMetaDataEntry("has_machine_quality"):
if material_container: if material_container:
material_search_criteria["definition"] = material_container.getDefinition().id material_search_criteria["definition"] = material_container.getDefinition().id
@ -865,7 +872,6 @@ class MachineManager(QObject):
material_search_criteria["variant"] = variant_container.id material_search_criteria["variant"] = variant_container.id
else: else:
material_search_criteria["definition"] = "fdmprinter" material_search_criteria["definition"] = "fdmprinter"
material_containers = container_registry.findInstanceContainers(**material_search_criteria) material_containers = container_registry.findInstanceContainers(**material_search_criteria)
if material_containers: if material_containers:
search_criteria["material"] = material_containers[0].getId() search_criteria["material"] = material_containers[0].getId()
@ -883,6 +889,9 @@ class MachineManager(QObject):
if containers: if containers:
return containers[0] return containers[0]
# Notify user that we were unable to find a matching quality
message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination, using an empty one instead."))
message.show()
return self._empty_quality_container return self._empty_quality_container
## Finds a quality-changes container to use if any other container ## Finds a quality-changes container to use if any other container

View File

@ -1,7 +1,7 @@
[2.3.0] [2.3.0]
*Speed improvements *Speed improvements
The first thing you will notice is the speed. STL loading is now 10 to 20 times faster, layer time is significantly faster and slicing speed is slightly improved. The first thing you will notice is the speed. STL loading is now 10 to 20 times faster, layerview is significantly faster and slicing speed is slightly improved.
*Multi Extrusion Support *Multi Extrusion Support
Machines with multiple extruders are now supported. If youve got the Ultimaker Original with the dual extrusion upgrade kit, weve got you covered. Machines with multiple extruders are now supported. If youve got the Ultimaker Original with the dual extrusion upgrade kit, weve got you covered.
@ -59,9 +59,6 @@ Set the travel speed of the initial layer(s) to reduce risk of extruder pulling
*Support Bottoms *Support Bottoms
This new feature duplicates the Support Roofs feature in the places where the support rests on the model. This new feature duplicates the Support Roofs feature in the places where the support rests on the model.
*Auto Temperature
Automatically change the temperature of a layer based on the average flow of material in the layer.
*Bug fixes & minor changes *Bug fixes & minor changes
Deleting grouped objects works as intended again. Deleting grouped objects works as intended again.
Duplicating groups works as intended again. Duplicating groups works as intended again.

View File

@ -206,7 +206,14 @@ class StartSliceJob(Job):
# Use resolvement value if available, or take the value # Use resolvement value if available, or take the value
resolved_value = stack.getProperty(key, "resolve") resolved_value = stack.getProperty(key, "resolve")
if resolved_value is not None: if resolved_value is not None:
settings[key] = resolved_value # There is a resolvement value. Check if we need to use it.
user_container = stack.findContainer({"type": "user"})
quality_changes_container = stack.findContainer({"type": "quality_changes"})
if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"):
# Normal case
settings[key] = stack.getProperty(key, "value")
else:
settings[key] = resolved_value
else: else:
# Normal case # Normal case
settings[key] = stack.getProperty(key, "value") settings[key] = stack.getProperty(key, "value")

View File

@ -26,7 +26,11 @@ class CuraProfileReader(ProfileReader):
# not be read or didn't contain a valid profile, \code None \endcode is # not be read or didn't contain a valid profile, \code None \endcode is
# returned. # returned.
def read(self, file_name): def read(self, file_name):
archive = zipfile.ZipFile(file_name, "r") try:
archive = zipfile.ZipFile(file_name, "r")
except Exception:
# zipfile doesn't give proper exceptions, so we can only catch broad ones
return []
results = [] results = []
for profile_id in archive.namelist(): for profile_id in archive.namelist():
# Create an empty profile. # Create an empty profile.

View File

@ -89,17 +89,17 @@ class GCodeWriter(MeshWriter):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix) prefix_length = len(prefix)
container_with_profile = stack.findContainer({"type": "quality"}) container_with_profile = stack.findContainer({"type": "quality_changes"})
if not container_with_profile: if not container_with_profile:
Logger.log("e", "No valid quality profile found, not writing settings to GCode!") Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
return "" return ""
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile) flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
serialized = flat_global_container.serialize() serialized = flat_global_container.serialize()
data = {"global_quality": serialized} data = {"global_quality": serialized}
for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
extruder_quality = extruder.findContainer({"type": "quality"}) extruder_quality = extruder.findContainer({"type": "quality_changes"})
if not extruder_quality: if not extruder_quality:
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
continue continue

View File

@ -86,7 +86,7 @@ class LayerView(View):
if not self._ghost_shader: if not self._ghost_shader:
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 72)) self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 64))
for node in DepthFirstIterator(scene.getRoot()): for node in DepthFirstIterator(scene.getRoot()):
# We do not want to render ConvexHullNode as it conflicts with the bottom layers. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
@ -98,10 +98,10 @@ class LayerView(View):
if node.getMeshData() and node.isVisible(): if node.getMeshData() and node.isVisible():
renderer.queueNode(node, renderer.queueNode(node,
shader = self._ghost_shader, shader = self._ghost_shader,
state_setup_callback = lambda gl: gl.glDepthMask(gl.GL_FALSE), type = RenderBatch.RenderType.Transparent )
state_teardown_callback = lambda gl: gl.glDepthMask(gl.GL_TRUE)
)
for node in DepthFirstIterator(scene.getRoot()):
if type(node) is SceneNode:
if node.getMeshData() and node.isVisible(): if node.getMeshData() and node.isVisible():
layer_data = node.callDecoration("getLayerData") layer_data = node.callDecoration("getLayerData")
if not layer_data: if not layer_data:

View File

@ -47,7 +47,9 @@ class SolidView(View):
if global_container_stack: if global_container_stack:
if Preferences.getInstance().getValue("view/show_overhang"): if Preferences.getInstance().getValue("view/show_overhang"):
angle = global_container_stack.getProperty("support_angle", "value") angle = global_container_stack.getProperty("support_angle", "value")
if angle is not None and global_container_stack.getProperty("support_angle", "validationState") == ValidatorState.Valid: # 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]:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle))) self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
else: else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.

View File

@ -87,7 +87,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
super().setName(new_name) super().setName(new_name)
basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is none, this is a basefile. basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile.
# Update the basefile as well, this is actually what we're trying to do # Update the basefile as well, this is actually what we're trying to do
# Update all containers that share GUID and basefile # Update all containers that share GUID and basefile
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile)
@ -275,7 +275,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
# The XML material profile can have specific settings for machines. # The XML material profile can have specific settings for machines.
# Some machines share profiles, so they are only created once. # Some machines share profiles, so they are only created once.
# This function duplicates those elements so that each machine tag only has one identifier. # This function duplicates those elements so that each machine tag only has one identifier.
def _flattenMachinesXML(self, element): def _expandMachinesXML(self, element):
settings_element = element.find("./um:settings", self.__namespaces) settings_element = element.find("./um:settings", self.__namespaces)
machines = settings_element.iterfind("./um:machine", self.__namespaces) machines = settings_element.iterfind("./um:machine", self.__namespaces)
machines_to_add = [] machines_to_add = []
@ -309,7 +309,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
def _mergeXML(self, first, second): def _mergeXML(self, first, second):
result = copy.deepcopy(first) result = copy.deepcopy(first)
self._combineElement(self._flattenMachinesXML(result), self._flattenMachinesXML(second)) self._combineElement(self._expandMachinesXML(result), self._expandMachinesXML(second))
return result return result
def _createKey(self, element): def _createKey(self, element):
@ -339,7 +339,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
key = self._createKey(element) key = self._createKey(element)
if len(element): # Check if element has children. if len(element): # Check if element has children.
try: try:
if "setting " in key: if "setting" in element.tag and not "settings" in element.tag:
# Setting can have points in it. In that case, delete all values and override them. # Setting can have points in it. In that case, delete all values and override them.
for child in list(mapping[key]): for child in list(mapping[key]):
mapping[key].remove(child) mapping[key].remove(child)

View File

@ -3310,6 +3310,8 @@
"default_value": 200, "default_value": 200,
"minimum_value_warning": "-1000", "minimum_value_warning": "-1000",
"maximum_value_warning": "1000", "maximum_value_warning": "1000",
"maximum_value": "machine_width - 0.5 * prime_tower_size",
"minimum_value": "0.5 * prime_tower_size",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3323,6 +3325,8 @@
"default_value": 200, "default_value": 200,
"minimum_value_warning": "-1000", "minimum_value_warning": "-1000",
"maximum_value_warning": "1000", "maximum_value_warning": "1000",
"maximum_value": "machine_depth - 0.5 * prime_tower_size",
"minimum_value": "0.5 * prime_tower_size",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },

View File

@ -28,7 +28,9 @@ Generic CPE+ profile. Serves as an example file, data in this file is not correc
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/> <machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
<setting key="hardware compatible">yes</setting> <setting key="hardware compatible">yes</setting>
<hotend id="0.25 mm" /> <hotend id="0.25 mm">
<setting key="hardware compatible">no</setting>
</hotend>
<hotend id="0.4 mm" /> <hotend id="0.4 mm" />
<hotend id="0.6 mm" /> <hotend id="0.6 mm" />
<hotend id="0.8 mm" /> <hotend id="0.8 mm" />

View File

@ -31,7 +31,9 @@ Generic TPU 95A profile. Serves as an example file, data in this file is not cor
<hotend id="0.25 mm" /> <hotend id="0.25 mm" />
<hotend id="0.4 mm" /> <hotend id="0.4 mm" />
<hotend id="0.6 mm" /> <hotend id="0.6 mm" />
<hotend id="0.8 mm" /> <hotend id="0.8 mm">
<setting key="hardware compatible">no</setting>
</hotend>
</machine> </machine>
</settings> </settings>
</fdmmaterial> </fdmmaterial>

View File

@ -3,7 +3,6 @@
Automatically generated PLA profile. Data in this file may not be not correct. Automatically generated PLA profile. Data in this file may not be not correct.
--> -->
<fdmmaterial xmlns="http://www.ultimaker.com/material"> <fdmmaterial xmlns="http://www.ultimaker.com/material">
<inherits>generic_pla</inherits>
<metadata> <metadata>
<name> <name>
<brand>Ultimaker</brand> <brand>Ultimaker</brand>
@ -38,14 +37,14 @@ Automatically generated PLA profile. Data in this file may not be not correct.
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Go"/> <machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Go"/>
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended"/> <machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended"/>
<setting key="processing temperature graph"> <setting key="processing temperature graph">
<point flow="2" temperature="20"/> <point flow="2" temperature="180"/>
<point flow="10" temperature="20"/> <point flow="10" temperature="230"/>
</setting> </setting>
</machine> </machine>
<machine> <machine>
<machine_identifier manufacturer="Ultimaker" product="Ultimaker Original"/> <machine_identifier manufacturer="Ultimaker" product="Ultimaker Original"/>
<setting key="standby temperature">180</setting> <setting key="standby temperature">150</setting>
</machine> </machine>
</settings> </settings>
</fdmmaterial> </fdmmaterial>

View File

@ -463,7 +463,7 @@ UM.MainWindow
target: Cura.Actions.addProfile target: Cura.Actions.addProfile
onTriggered: onTriggered:
{ {
Cura.ContainerManager.createQualityChanges(); Cura.ContainerManager.createQualityChanges(null);
preferences.setPage(4); preferences.setPage(4);
preferences.show(); preferences.show();

View File

@ -61,6 +61,10 @@ UM.ManagementPage
return -1; return -1;
} }
function canCreateProfile() {
return base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) && Cura.MachineManager.hasUserSettings;
}
buttons: [ buttons: [
Button Button
{ {
@ -69,26 +73,39 @@ UM.ManagementPage
enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false; enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false;
onClicked: Cura.MachineManager.setActiveQuality(base.currentItem.id) onClicked: Cura.MachineManager.setActiveQuality(base.currentItem.id)
}, },
// Create button
Button Button
{ {
text: base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) && Cura.MachineManager.hasUserSettings ? catalog.i18nc("@label", "Create") : catalog.i18nc("@label", "Duplicate") text: catalog.i18nc("@label", "Create")
enabled: base.canCreateProfile()
visible: base.canCreateProfile()
iconName: "list-add"; iconName: "list-add";
onClicked: onClicked:
{ {
var selectedContainer; newNameDialog.object = base.currentItem != null ? base.currentItem.name : "";
if (base.currentItem.id == Cura.MachineManager.activeQualityId && Cura.MachineManager.hasUserSettings) { newNameDialog.open();
selectedContainer = Cura.ContainerManager.createQualityChanges(); newNameDialog.selectText();
} else {
selectedContainer = Cura.ContainerManager.duplicateQualityOrQualityChanges(base.currentItem.name);
}
base.selectContainer(selectedContainer);
renameDialog.removeWhenRejected = true;
renameDialog.open();
renameDialog.selectText();
} }
}, },
// Duplicate button
Button
{
text: catalog.i18nc("@label", "Duplicate")
enabled: ! base.canCreateProfile()
visible: ! base.canCreateProfile()
iconName: "list-add";
onClicked:
{
newDuplicateNameDialog.object = base.currentItem.name;
newDuplicateNameDialog.open();
newDuplicateNameDialog.selectText();
}
},
Button Button
{ {
text: catalog.i18nc("@action:button", "Remove"); text: catalog.i18nc("@action:button", "Remove");
@ -103,7 +120,6 @@ UM.ManagementPage
enabled: base.currentItem != null ? !base.currentItem.readOnly : false; enabled: base.currentItem != null ? !base.currentItem.readOnly : false;
onClicked: onClicked:
{ {
renameDialog.removeWhenRejected = false;
renameDialog.open(); renameDialog.open();
renameDialog.selectText(); renameDialog.selectText();
} }
@ -128,7 +144,6 @@ UM.ManagementPage
signal showProfileNameDialog() signal showProfileNameDialog()
onShowProfileNameDialog: onShowProfileNameDialog:
{ {
renameDialog.removeWhenRejected = true;
renameDialog.open(); renameDialog.open();
renameDialog.selectText(); renameDialog.selectText();
} }
@ -145,7 +160,7 @@ UM.ManagementPage
Label { Label {
id: profileName id: profileName
text: base.currentItem ? base.currentItem.name : "" text: base.currentItem ? base.currentItem.name: ""
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large")
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
@ -249,24 +264,44 @@ UM.ManagementPage
objectList.currentIndex = -1 //Reset selection. objectList.currentIndex = -1 //Reset selection.
} }
} }
UM.RenameDialog UM.RenameDialog
{ {
id: renameDialog; id: renameDialog;
object: base.currentItem != null ? base.currentItem.name : "" object: base.currentItem != null ? base.currentItem.name : ""
property bool removeWhenRejected: false
onAccepted: onAccepted:
{ {
Cura.ContainerManager.renameQualityChanges(base.currentItem.name, newName) Cura.ContainerManager.renameQualityChanges(base.currentItem.name, newName)
objectList.currentIndex = -1 //Reset selection. objectList.currentIndex = -1 //Reset selection.
} }
onRejected: }
// Dialog to request a name when creating a new profile
UM.RenameDialog
{
id: newNameDialog;
object: "<new name>";
onAccepted:
{ {
if(removeWhenRejected) var selectedContainer = Cura.ContainerManager.createQualityChanges(newName);
{ base.selectContainer(selectedContainer);
Cura.ContainerManager.removeQualityChanges(base.currentItem.name) objectList.currentIndex = -1 //Reset selection.
}
} }
} }
// Dialog to request a name when duplicating a new profile
UM.RenameDialog
{
id: newDuplicateNameDialog;
object: "<new name>";
onAccepted:
{
var selectedContainer = Cura.ContainerManager.duplicateQualityOrQualityChanges(base.currentItem.name, newName);
base.selectContainer(selectedContainer);
objectList.currentIndex = -1 //Reset selection.
}
}
MessageDialog MessageDialog
{ {
id: messageDialog id: messageDialog

View File

@ -277,7 +277,7 @@ Column
height: UM.Theme.getSize("setting_control").height height: UM.Theme.getSize("setting_control").height
tooltip: Cura.MachineManager.activeQualityName tooltip: Cura.MachineManager.activeQualityName
style: UM.Theme.styles.sidebar_header_button style: UM.Theme.styles.sidebar_header_button
property var valueWarning: Cura.MachineManager.activeQualityId == "empty_quality"
menu: ProfileMenu { } menu: ProfileMenu { }
UM.SimpleButton UM.SimpleButton

View File

@ -11,7 +11,22 @@ QtObject {
property Component sidebar_header_button: Component { property Component sidebar_header_button: Component {
ButtonStyle { ButtonStyle {
background: Rectangle { background: Rectangle {
color: control.enabled ? Theme.getColor("setting_control") : Theme.getColor("setting_control_disabled") color:
{
if(control.enabled)
{
if(control.valueWarning)
{
return Theme.getColor("setting_validation_warning");
} else
{
return Theme.getColor("setting_control");
}
} else {
return Theme.getColor("setting_control_disabled");
}
}
border.width: Theme.getSize("default_lining").width border.width: Theme.getSize("default_lining").width
border.color: !control.enabled ? Theme.getColor("setting_control_disabled_border") : border.color: !control.enabled ? Theme.getColor("setting_control_disabled_border") :
control.hovered ? Theme.getColor("setting_control_border_highlight") : Theme.getColor("setting_control_border") control.hovered ? Theme.getColor("setting_control_border_highlight") : Theme.getColor("setting_control_border")