mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-05 21:13:58 -06:00
Merge branch '2.3' of github.com:Ultimaker/Cura into 2.3
This commit is contained in:
commit
84fb59a0f3
34 changed files with 318 additions and 96 deletions
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
"visible": false
|
||||
},
|
||||
"overrides": {
|
||||
"machine_max_feedrate_e": {
|
||||
"default_value": 45
|
||||
},
|
||||
"material_print_temperature": {
|
||||
"minimum_value": "0"
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2backplate.png",
|
||||
"platform_offset": [9, 0, 0],
|
||||
"has_materials": false,
|
||||
"supported_actions":["UpgradeFirmware"]
|
||||
},
|
||||
"overrides": {
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
"default_value": 185
|
||||
},
|
||||
"prime_tower_position_y": {
|
||||
"default_value": 175
|
||||
"default_value": 160
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue