mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-06 13:34:01 -06:00
Merge branch 'master' into python_type_hinting
This commit is contained in:
commit
d4619da358
132 changed files with 42584 additions and 952 deletions
|
@ -11,6 +11,7 @@ from UM.Application import Application
|
|||
from UM.Resources import Resources
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Polygon import Polygon
|
||||
|
@ -23,6 +24,7 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
import numpy
|
||||
import copy
|
||||
import math
|
||||
|
||||
# Setting for clearance around the prime
|
||||
PRIME_CLEARANCE = 6.5
|
||||
|
@ -43,6 +45,7 @@ class BuildVolume(SceneNode):
|
|||
self._width = 0
|
||||
self._height = 0
|
||||
self._depth = 0
|
||||
self._shape = ""
|
||||
|
||||
self._shader = None
|
||||
|
||||
|
@ -137,6 +140,9 @@ class BuildVolume(SceneNode):
|
|||
def setDepth(self, depth):
|
||||
if depth: self._depth = depth
|
||||
|
||||
def setShape(self, shape):
|
||||
if shape: self._shape = shape
|
||||
|
||||
def getDisallowedAreas(self):
|
||||
return self._disallowed_areas
|
||||
|
||||
|
@ -175,27 +181,70 @@ class BuildVolume(SceneNode):
|
|||
min_d = -self._depth / 2
|
||||
max_d = self._depth / 2
|
||||
|
||||
mb = MeshBuilder()
|
||||
z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting
|
||||
|
||||
# Outline 'cube' of the build volume
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
if self._shape != "elliptic":
|
||||
# Outline 'cube' of the build volume
|
||||
mb = MeshBuilder()
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
|
||||
|
||||
mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
|
||||
|
||||
self.setMeshData(mb.build())
|
||||
self.setMeshData(mb.build())
|
||||
|
||||
mb = MeshBuilder()
|
||||
# Build plate grid mesh
|
||||
mb = MeshBuilder()
|
||||
mb.addQuad(
|
||||
Vector(min_w, min_h - z_fight_distance, min_d),
|
||||
Vector(max_w, min_h - z_fight_distance, min_d),
|
||||
Vector(max_w, min_h - z_fight_distance, max_d),
|
||||
Vector(min_w, min_h - z_fight_distance, max_d)
|
||||
)
|
||||
|
||||
for n in range(0, 6):
|
||||
v = mb.getVertex(n)
|
||||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||
self._grid_mesh = mb.build()
|
||||
|
||||
else:
|
||||
# Bottom and top 'ellipse' of the build volume
|
||||
aspect = 1.0
|
||||
scale_matrix = Matrix()
|
||||
if self._width != 0:
|
||||
# Scale circular meshes by aspect ratio if width != height
|
||||
aspect = self._height / self._width
|
||||
scale_matrix.compose(scale = Vector(1, 1, aspect))
|
||||
mb = MeshBuilder()
|
||||
mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self.VolumeOutlineColor)
|
||||
mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self.VolumeOutlineColor)
|
||||
self.setMeshData(mb.build().getTransformed(scale_matrix))
|
||||
|
||||
# Build plate grid mesh
|
||||
mb = MeshBuilder()
|
||||
mb.addVertex(0, min_h - z_fight_distance, 0)
|
||||
mb.addArc(max_w, Vector.Unit_Y, center = Vector(0, min_h - z_fight_distance, 0))
|
||||
sections = mb.getVertexCount() - 1 # Center point is not an arc section
|
||||
indices = []
|
||||
for n in range(0, sections - 1):
|
||||
indices.append([0, n + 2, n + 1])
|
||||
mb.addIndices(numpy.asarray(indices, dtype = numpy.int32))
|
||||
mb.calculateNormals()
|
||||
|
||||
for n in range(0, mb.getVertexCount()):
|
||||
v = mb.getVertex(n)
|
||||
mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
|
||||
self._grid_mesh = mb.build().getTransformed(scale_matrix)
|
||||
|
||||
# Indication of the machine origin
|
||||
if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
|
||||
|
@ -203,6 +252,7 @@ class BuildVolume(SceneNode):
|
|||
else:
|
||||
origin = Vector(min_w, min_h, max_d)
|
||||
|
||||
mb = MeshBuilder()
|
||||
mb.addCube(
|
||||
width = self._origin_line_length,
|
||||
height = self._origin_line_width,
|
||||
|
@ -226,19 +276,6 @@ class BuildVolume(SceneNode):
|
|||
)
|
||||
self._origin_mesh = mb.build()
|
||||
|
||||
mb = MeshBuilder()
|
||||
mb.addQuad(
|
||||
Vector(min_w, min_h - 0.2, min_d),
|
||||
Vector(max_w, min_h - 0.2, min_d),
|
||||
Vector(max_w, min_h - 0.2, max_d),
|
||||
Vector(min_w, min_h - 0.2, max_d)
|
||||
)
|
||||
|
||||
for n in range(0, 6):
|
||||
v = mb.getVertex(n)
|
||||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||
self._grid_mesh = mb.build()
|
||||
|
||||
disallowed_area_height = 0.1
|
||||
disallowed_area_size = 0
|
||||
if self._disallowed_areas:
|
||||
|
@ -351,6 +388,7 @@ class BuildVolume(SceneNode):
|
|||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
self._build_volume_message.hide()
|
||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||
|
||||
self._updateDisallowedAreas()
|
||||
self._updateRaftThickness()
|
||||
|
@ -410,6 +448,13 @@ class BuildVolume(SceneNode):
|
|||
used_extruders = extruder_manager.getUsedExtruderStacks()
|
||||
disallowed_border_size = self._getEdgeDisallowedSize()
|
||||
|
||||
if not used_extruders:
|
||||
# If no extruder is used, assume that the active extruder is used (else nothing is drawn)
|
||||
if extruder_manager.getActiveExtruderStack():
|
||||
used_extruders = [extruder_manager.getActiveExtruderStack()]
|
||||
else:
|
||||
used_extruders = [self._global_container_stack]
|
||||
|
||||
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
|
||||
prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
|
||||
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
||||
|
@ -440,10 +485,7 @@ class BuildVolume(SceneNode):
|
|||
if collision:
|
||||
break
|
||||
|
||||
|
||||
if not collision:
|
||||
#Prime areas are valid. Add as normal.
|
||||
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
||||
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
||||
|
||||
nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value")
|
||||
for area in nozzle_disallowed_areas:
|
||||
|
@ -582,34 +624,79 @@ class BuildVolume(SceneNode):
|
|||
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
|
||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||
if border_size - left_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border],
|
||||
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size + right_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
||||
[half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size + bottom_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border],
|
||||
[-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size - top_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
||||
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
||||
], numpy.float32)))
|
||||
|
||||
if self._shape != "elliptic":
|
||||
if border_size - left_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border],
|
||||
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size + right_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
||||
[half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size + bottom_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[half_machine_width, half_machine_depth],
|
||||
[half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border],
|
||||
[-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border]
|
||||
], numpy.float32)))
|
||||
if border_size - top_unreachable_border > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border],
|
||||
[half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border]
|
||||
], numpy.float32)))
|
||||
else:
|
||||
sections = 32
|
||||
arc_vertex = [0, half_machine_depth - border_size]
|
||||
for i in range(0, sections):
|
||||
quadrant = math.floor(4 * i / sections)
|
||||
vertices = []
|
||||
if quadrant == 0:
|
||||
vertices.append([-half_machine_width, half_machine_depth])
|
||||
elif quadrant == 1:
|
||||
vertices.append([-half_machine_width, -half_machine_depth])
|
||||
elif quadrant == 2:
|
||||
vertices.append([half_machine_width, -half_machine_depth])
|
||||
elif quadrant == 3:
|
||||
vertices.append([half_machine_width, half_machine_depth])
|
||||
vertices.append(arc_vertex)
|
||||
|
||||
angle = 2 * math.pi * (i + 1) / sections
|
||||
arc_vertex = [-(half_machine_width - border_size) * math.sin(angle), (half_machine_depth - border_size) * math.cos(angle)]
|
||||
vertices.append(arc_vertex)
|
||||
|
||||
result[extruder_id].append(Polygon(numpy.array(vertices, numpy.float32)))
|
||||
|
||||
if border_size > 0:
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[-half_machine_width + border_size, 0]
|
||||
], numpy.float32)))
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[-half_machine_width, half_machine_depth],
|
||||
[ half_machine_width, half_machine_depth],
|
||||
[ 0, half_machine_depth - border_size]
|
||||
], numpy.float32)))
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[ half_machine_width, half_machine_depth],
|
||||
[ half_machine_width, -half_machine_depth],
|
||||
[ half_machine_width - border_size, 0]
|
||||
], numpy.float32)))
|
||||
result[extruder_id].append(Polygon(numpy.array([
|
||||
[ half_machine_width,-half_machine_depth],
|
||||
[-half_machine_width,-half_machine_depth],
|
||||
[ 0, -half_machine_depth + border_size]
|
||||
], numpy.float32)))
|
||||
|
||||
return result
|
||||
|
||||
|
@ -683,7 +770,7 @@ class BuildVolume(SceneNode):
|
|||
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
|
||||
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
|
||||
bed_adhesion_size = skirt_distance + (skirt_line_count * self._getSettingFromAdhesionExtruder("skirt_brim_line_width"))
|
||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
if len(ExtruderManager.getInstance().getUsedExtruderStacks()) > 1:
|
||||
adhesion_extruder_nr = int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
extruder_values = ExtruderManager.getInstance().getAllExtruderValues("skirt_brim_line_width")
|
||||
del extruder_values[adhesion_extruder_nr] # Remove the value of the adhesion extruder nr.
|
||||
|
@ -716,15 +803,16 @@ class BuildVolume(SceneNode):
|
|||
|
||||
move_from_wall_radius = 0 # Moves that start from outer wall.
|
||||
move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
|
||||
avoid_enabled_per_extruder = self._getSettingFromAllExtruders(("travel_avoid_other_parts"))
|
||||
avoid_distance_per_extruder = self._getSettingFromAllExtruders("travel_avoid_distance")
|
||||
for index, avoid_other_parts_enabled in enumerate(avoid_enabled_per_extruder): #For each extruder (or just global).
|
||||
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
|
||||
avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts","value") for stack in used_extruders]
|
||||
travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
|
||||
for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): #For each extruder (or just global).
|
||||
if avoid_other_parts_enabled:
|
||||
move_from_wall_radius = max(move_from_wall_radius, avoid_distance_per_extruder[index]) #Index of the same extruder.
|
||||
move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
|
||||
|
||||
#Now combine our different pieces of data to get the final border size.
|
||||
#Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
|
||||
#Support expansion is added to farthest shield distance, since the shields go around support.
|
||||
# Now combine our different pieces of data to get the final border size.
|
||||
# Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
|
||||
# Support expansion is added to farthest shield distance, since the shields go around support.
|
||||
border_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size)
|
||||
return border_size
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@ import sys
|
|||
import platform
|
||||
import traceback
|
||||
import webbrowser
|
||||
import urllib
|
||||
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication
|
||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QVBoxLayout, QLabel, QTextEdit
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -22,9 +24,7 @@ fatal_exception_types = [
|
|||
]
|
||||
|
||||
def show(exception_type, value, tb):
|
||||
debug_mode = False
|
||||
if QCoreApplication.instance():
|
||||
debug_mode = QCoreApplication.instance().getCommandLineOption("debug-mode", False)
|
||||
debug_mode = True
|
||||
|
||||
Logger.log("c", "An uncaught exception has occurred!")
|
||||
for line in traceback.format_exception(exception_type, value, tb):
|
||||
|
@ -38,14 +38,41 @@ def show(exception_type, value, tb):
|
|||
if not application:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
dialog = QDialog()
|
||||
dialog.setMinimumWidth(640)
|
||||
dialog.setMinimumHeight(640)
|
||||
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
label = QLabel(dialog)
|
||||
pixmap = QPixmap()
|
||||
|
||||
try:
|
||||
data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
|
||||
pixmap.loadFromData(data)
|
||||
except:
|
||||
try:
|
||||
from UM.Resources import Resources
|
||||
path = Resources.getPath(Resources.Images, "kitten.jpg")
|
||||
pixmap.load(path)
|
||||
except:
|
||||
pass
|
||||
|
||||
pixmap = pixmap.scaled(150, 150)
|
||||
label.setPixmap(pixmap)
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label)
|
||||
label.setText(catalog.i18nc("@label", "<p>A fatal exception has occurred that we could not recover from!</p><p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>"))
|
||||
|
||||
label = QLabel(dialog)
|
||||
layout.addWidget(label)
|
||||
|
||||
#label.setScaledContents(True)
|
||||
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
|
||||
<p>We hope this picture of a kitten helps you recover from the shock.</p>
|
||||
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
|
||||
"""))
|
||||
|
||||
textarea = QTextEdit(dialog)
|
||||
layout.addWidget(textarea)
|
||||
|
|
|
@ -7,6 +7,7 @@ from UM.Scene.Camera import Camera
|
|||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Quaternion import Quaternion
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.ToolHandle import ToolHandle
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
@ -61,43 +62,12 @@ from PyQt5.QtGui import QColor, QIcon
|
|||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import numpy
|
||||
import copy
|
||||
import urllib
|
||||
import urllib.parse
|
||||
import os
|
||||
import time
|
||||
|
||||
CONFIG_LOCK_FILENAME = "cura.lock"
|
||||
|
||||
## Contextmanager to create a lock file and remove it afterwards.
|
||||
@contextmanager
|
||||
def lockFile(filename):
|
||||
try:
|
||||
with open(filename, 'w') as lock_file:
|
||||
lock_file.write("Lock file - Cura is currently writing")
|
||||
except:
|
||||
Logger.log("e", "Could not create lock file [%s]" % filename)
|
||||
yield
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
except:
|
||||
Logger.log("e", "Could not delete lock file [%s]" % filename)
|
||||
|
||||
|
||||
## Wait for a lock file to disappear
|
||||
# the maximum allowable age is settable; if the file is too old, it will be ignored too
|
||||
def waitFileDisappear(filename, max_age_seconds=10, msg=""):
|
||||
now = time.time()
|
||||
while os.path.exists(filename) and now < os.path.getmtime(filename) + max_age_seconds and now > os.path.getmtime(filename):
|
||||
if msg:
|
||||
Logger.log("d", msg)
|
||||
time.sleep(1)
|
||||
now = time.time()
|
||||
|
||||
|
||||
numpy.seterr(all="ignore")
|
||||
|
@ -145,7 +115,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
||||
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
|
||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None)
|
||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
|
||||
|
||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||
|
||||
|
@ -175,6 +145,7 @@ class CuraApplication(QtApplication):
|
|||
{
|
||||
("quality", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("machine_stack", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
|
||||
("extruder_train", UM.Settings.ContainerStack.ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
|
||||
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||
("user", UM.Settings.InstanceContainer.InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
||||
}
|
||||
|
@ -250,10 +221,8 @@ class CuraApplication(QtApplication):
|
|||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||
|
||||
# Set the filename to create if cura is writing in the config dir.
|
||||
self._config_lock_filename = os.path.join(Resources.getConfigStoragePath(), CONFIG_LOCK_FILENAME)
|
||||
self.waitConfigLockFile()
|
||||
ContainerRegistry.getInstance().load()
|
||||
with ContainerRegistry.getInstance().lockFile():
|
||||
ContainerRegistry.getInstance().load()
|
||||
|
||||
Preferences.getInstance().addPreference("cura/active_mode", "simple")
|
||||
Preferences.getInstance().addPreference("cura/recent_files", "")
|
||||
|
@ -262,7 +231,8 @@ class CuraApplication(QtApplication):
|
|||
Preferences.getInstance().addPreference("view/center_on_select", True)
|
||||
Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
|
||||
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
||||
|
||||
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
|
||||
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
|
||||
for key in [
|
||||
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
||||
"dialog_profile_path",
|
||||
|
@ -279,6 +249,8 @@ class CuraApplication(QtApplication):
|
|||
shell
|
||||
wall_thickness
|
||||
top_bottom_thickness
|
||||
z_seam_x
|
||||
z_seam_y
|
||||
infill
|
||||
infill_sparse_density
|
||||
material
|
||||
|
@ -333,14 +305,6 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self._recent_files.append(QUrl.fromLocalFile(f))
|
||||
|
||||
def getContainerRegistry(self):
|
||||
return CuraContainerRegistry.getInstance()
|
||||
## Lock file check: if (another) Cura is writing in the Config dir.
|
||||
# one may not be able to read a valid set of files while writing. Not entirely fool-proof,
|
||||
# but works when you start Cura shortly after shutting down.
|
||||
def waitConfigLockFile(self):
|
||||
waitFileDisappear(self._config_lock_filename, max_age_seconds=10, msg="Waiting for Cura to finish writing in the config dir...")
|
||||
|
||||
def _onEngineCreated(self):
|
||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
|
||||
|
@ -368,11 +332,8 @@ class CuraApplication(QtApplication):
|
|||
if not self._started: # Do not do saving during application start
|
||||
return
|
||||
|
||||
self.waitConfigLockFile()
|
||||
|
||||
# When starting Cura, we check for the lockFile which is created and deleted here
|
||||
with lockFile(self._config_lock_filename):
|
||||
|
||||
# Lock file for "more" atomically loading and saving to/from config dir.
|
||||
with ContainerRegistry.getInstance().lockFile():
|
||||
for instance in ContainerRegistry.getInstance().findInstanceContainers():
|
||||
if not instance.isDirty():
|
||||
continue
|
||||
|
@ -397,6 +358,8 @@ class CuraApplication(QtApplication):
|
|||
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
||||
elif instance_type == "variant":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||
elif instance_type == "definition_changes":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
||||
|
||||
if path:
|
||||
instance.setPath(path)
|
||||
|
@ -461,7 +424,6 @@ class CuraApplication(QtApplication):
|
|||
def addCommandLineOptions(self, parser):
|
||||
super().addCommandLineOptions(parser)
|
||||
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
|
||||
parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.")
|
||||
|
||||
def run(self):
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||
|
@ -651,7 +613,7 @@ class CuraApplication(QtApplication):
|
|||
if not scene_bounding_box:
|
||||
scene_bounding_box = AxisAlignedBox.Null
|
||||
|
||||
if repr(self._scene_bounding_box) != repr(scene_bounding_box):
|
||||
if repr(self._scene_bounding_box) != repr(scene_bounding_box) and scene_bounding_box.isValid():
|
||||
self._scene_bounding_box = scene_bounding_box
|
||||
self.sceneBoundingBoxChanged.emit()
|
||||
|
||||
|
@ -753,6 +715,8 @@ 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)
|
||||
if not node.isSelectable():
|
||||
continue # i.e. node with layer data
|
||||
Selection.add(node)
|
||||
|
||||
## Delete all nodes containing mesh data in the scene.
|
||||
|
@ -792,6 +756,8 @@ 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)
|
||||
if not node.isSelectable():
|
||||
continue # i.e. node with layer data
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
|
@ -818,6 +784,8 @@ 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)
|
||||
if not node.isSelectable():
|
||||
continue # i.e. node with layer data
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
|
@ -901,10 +869,13 @@ class CuraApplication(QtApplication):
|
|||
|
||||
# Compute the center of the objects
|
||||
object_centers = []
|
||||
# Forget about the translation that the original objects have
|
||||
zero_translation = Matrix(data=numpy.zeros(3))
|
||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||
orientation = node.getOrientation().toMatrix()
|
||||
rotated_mesh = mesh.getTransformed(orientation)
|
||||
center = rotated_mesh.getCenterPosition().scale(node.getScale())
|
||||
transformation = node.getLocalTransformation()
|
||||
transformation.setTranslation(zero_translation)
|
||||
transformed_mesh = mesh.getTransformed(transformation)
|
||||
center = transformed_mesh.getCenterPosition()
|
||||
object_centers.append(center)
|
||||
if object_centers and len(object_centers) > 0:
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
|
@ -916,12 +887,13 @@ class CuraApplication(QtApplication):
|
|||
|
||||
# Move each node to the same position.
|
||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||
orientation = node.getOrientation().toMatrix()
|
||||
rotated_mesh = mesh.getTransformed(orientation)
|
||||
transformation = node.getLocalTransformation()
|
||||
transformation.setTranslation(zero_translation)
|
||||
transformed_mesh = mesh.getTransformed(transformation)
|
||||
|
||||
# Align the object around its zero position
|
||||
# and also apply the offset to center it inside the group.
|
||||
node.setPosition(-rotated_mesh.getZeroPosition().scale(node.getScale()) - offset)
|
||||
node.setPosition(-transformed_mesh.getZeroPosition() - offset)
|
||||
|
||||
# Use the previously found center of the group bounding box as the new location of the group
|
||||
group_node.setPosition(group_node.getBoundingBox().center)
|
||||
|
@ -1007,7 +979,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def _reloadMeshFinished(self, job):
|
||||
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
|
||||
mesh_data = job.getResult().getMeshData()
|
||||
mesh_data = job.getResult()[0].getMeshData()
|
||||
if mesh_data:
|
||||
job._node.setMeshData(mesh_data)
|
||||
else:
|
||||
|
|
|
@ -6,13 +6,21 @@ from PyQt5.QtQml import QQmlComponent, QQmlContext
|
|||
|
||||
from UM.PluginObject import PluginObject
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
|
||||
import os
|
||||
|
||||
|
||||
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
|
||||
# updating the firmware, connecting with remote devices or doing bed leveling. A machine action can also have a
|
||||
# qml, which should contain a "Cura.MachineAction" item. When activated, the item will be displayed in a dialog
|
||||
# and this object will be added as "manager" (so all pyqtSlot() functions can be called by calling manager.func())
|
||||
class MachineAction(QObject, PluginObject):
|
||||
|
||||
## Create a new Machine action.
|
||||
# \param key unique key of the machine action
|
||||
# \param label Human readable label used to identify the machine action.
|
||||
def __init__(self, key, label = ""):
|
||||
super().__init__()
|
||||
self._key = key
|
||||
|
@ -63,13 +71,16 @@ class MachineAction(QObject, PluginObject):
|
|||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
## Protected helper to create a view object based on provided QML.
|
||||
def _createViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(
|
||||
os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._context.setContextProperty("manager", self)
|
||||
self._view = self._component.create(self._context)
|
||||
if self._view is None:
|
||||
Logger.log("c", "QQmlComponent status %s", self._component.status())
|
||||
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def displayItem(self):
|
||||
|
|
|
@ -49,7 +49,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._printer_state = ""
|
||||
self._printer_type = "unknown"
|
||||
|
||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False):
|
||||
self._camera_active = False
|
||||
|
||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
## Signals
|
||||
|
@ -136,6 +138,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
@pyqtSlot()
|
||||
def startCamera(self):
|
||||
self._camera_active = True
|
||||
self._startCamera()
|
||||
|
||||
def _startCamera(self):
|
||||
|
@ -143,6 +146,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
@pyqtSlot()
|
||||
def stopCamera(self):
|
||||
self._camera_active = False
|
||||
self._stopCamera()
|
||||
|
||||
def _stopCamera(self):
|
||||
|
|
|
@ -155,6 +155,18 @@ class ExtruderManager(QObject):
|
|||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
||||
def registerExtruder(self, extruder_train, machine_id):
|
||||
changed = False
|
||||
|
||||
if machine_id not in self._extruder_trains:
|
||||
self._extruder_trains[machine_id] = {}
|
||||
changed = True
|
||||
if extruder_train:
|
||||
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||
changed = True
|
||||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
||||
## Creates a container stack for an extruder train.
|
||||
#
|
||||
# The container stack has an extruder definition at the bottom, which is
|
||||
|
|
|
@ -141,8 +141,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
||||
extruder_name = extruder.getName()
|
||||
material = extruder.findContainer({ "type": "material" })
|
||||
if material and not self._simple_names:
|
||||
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
|
||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||
try:
|
||||
position = int(position)
|
||||
|
|
|
@ -13,6 +13,7 @@ from UM.Message import Message
|
|||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingDefinition import SettingDefinition
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
|
||||
|
@ -39,6 +40,10 @@ class MachineManager(QObject):
|
|||
self.globalContainerChanged.connect(self.activeQualityChanged)
|
||||
|
||||
self._stacks_have_errors = None
|
||||
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0]
|
||||
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0]
|
||||
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0]
|
||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0]
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||
|
@ -53,11 +58,6 @@ class MachineManager(QObject):
|
|||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
|
||||
self.activeStackChanged.connect(self.activeStackValueChanged)
|
||||
|
||||
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
|
||||
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
||||
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
|
||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
|
||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||
|
||||
self._global_event_keys = set()
|
||||
|
@ -114,7 +114,11 @@ class MachineManager(QObject):
|
|||
def printerOutputDevices(self):
|
||||
return self._printer_output_devices
|
||||
|
||||
def _onHotendIdChanged(self, index: Union[str,int], hotend_id: str) -> None:
|
||||
@pyqtProperty(int, constant=True)
|
||||
def totalNumberOfSettings(self):
|
||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||
|
||||
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
|
@ -251,6 +255,7 @@ class MachineManager(QObject):
|
|||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
||||
quality.nameChanged.connect(self._onQualityNameChanged)
|
||||
|
||||
self._updateStacksHaveErrors()
|
||||
|
||||
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
||||
def _updateStacksHaveErrors(self):
|
||||
|
@ -282,7 +287,7 @@ class MachineManager(QObject):
|
|||
|
||||
def _onInstanceContainersChanged(self, container):
|
||||
container_type = container.getMetaDataEntry("type")
|
||||
|
||||
|
||||
if container_type == "material":
|
||||
self.activeMaterialChanged.emit()
|
||||
elif container_type == "variant":
|
||||
|
@ -290,6 +295,8 @@ class MachineManager(QObject):
|
|||
elif container_type == "quality":
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
self._updateStacksHaveErrors()
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||
|
@ -302,6 +309,16 @@ class MachineManager(QObject):
|
|||
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
||||
else:
|
||||
changed_validation_state = self._global_container_stack.getProperty(key, property_name)
|
||||
|
||||
if changed_validation_state is None:
|
||||
# Setting is not validated. This can happen if there is only a setting definition.
|
||||
# We do need to validate it, because a setting defintions value can be set by a function, which could
|
||||
# be an invalid setting.
|
||||
definition = self._active_container_stack.getSettingDefinition(key)
|
||||
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||
if validator_type:
|
||||
validator = validator_type(key)
|
||||
changed_validation_state = validator(self._active_container_stack)
|
||||
if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
||||
self._stacks_have_errors = True
|
||||
self.stacksValidationChanged.emit()
|
||||
|
@ -311,6 +328,7 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(str)
|
||||
def setActiveMachine(self, stack_id: str) -> None:
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
|
||||
if containers:
|
||||
Application.getInstance().setGlobalContainerStack(containers[0])
|
||||
|
@ -476,6 +494,16 @@ class MachineManager(QObject):
|
|||
|
||||
return ""
|
||||
|
||||
@pyqtProperty("QVariantList", notify = activeMaterialChanged)
|
||||
def activeMaterialNames(self):
|
||||
result = []
|
||||
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
material_container = stack.findContainer(type="material")
|
||||
if material_container and material_container != self._empty_material_container:
|
||||
result.append(material_container.getName())
|
||||
return result
|
||||
|
||||
@pyqtProperty(str, notify=activeMaterialChanged)
|
||||
def activeMaterialId(self) -> str:
|
||||
if self._active_container_stack:
|
||||
|
@ -867,7 +895,8 @@ class MachineManager(QObject):
|
|||
if old_container:
|
||||
old_container.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old "+ container.getMetaDataEntry("type") + " while changing active " + container.getMetaDataEntry("type") + ".")
|
||||
Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId())
|
||||
return
|
||||
|
||||
# Swap in the new container into the stack.
|
||||
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
|
||||
|
@ -878,7 +907,7 @@ class MachineManager(QObject):
|
|||
def _askUserToKeepOrClearCurrentSettings(self):
|
||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
||||
# In Simple Mode we assume the user always wants to keep the (limited) current settings
|
||||
details_text = catalog.i18nc("@label", "You made changes to the following setting(s):")
|
||||
details_text = catalog.i18nc("@label", "You made changes to the following setting(s)/override(s):")
|
||||
|
||||
# user changes in global stack
|
||||
details_list = [setting.definition.label for setting in self._global_container_stack.getTop().findInstances(**{})]
|
||||
|
@ -893,14 +922,19 @@ class MachineManager(QObject):
|
|||
# Format to output string
|
||||
details = "\n ".join([details_text, ] + details_list)
|
||||
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"),
|
||||
catalog.i18nc("@label",
|
||||
"Do you want to transfer your changed settings to this profile?"),
|
||||
catalog.i18nc("@label",
|
||||
"If you transfer your settings they will override settings in the profile."),
|
||||
details,
|
||||
buttons=QMessageBox.Yes + QMessageBox.No, icon=QMessageBox.Question,
|
||||
callback=self._keepUserSettingsDialogCallback)
|
||||
num_changed_settings = len(details_list)
|
||||
Application.getInstance().messageBox(
|
||||
catalog.i18nc("@window:title", "Switched profiles"),
|
||||
catalog.i18nc(
|
||||
"@label",
|
||||
"Do you want to transfer your %d changed setting(s)/override(s) to this profile?") % num_changed_settings,
|
||||
catalog.i18nc(
|
||||
"@label",
|
||||
"If you transfer your settings they will override settings in the profile. If you don't transfer these settings, they will be lost."),
|
||||
details,
|
||||
buttons=QMessageBox.Yes + QMessageBox.No,
|
||||
icon=QMessageBox.Question,
|
||||
callback=self._keepUserSettingsDialogCallback)
|
||||
|
||||
def _keepUserSettingsDialogCallback(self, button):
|
||||
if button == QMessageBox.Yes:
|
||||
|
@ -1170,8 +1204,9 @@ class MachineManager(QObject):
|
|||
else:
|
||||
material_search_criteria["definition"] = "fdmprinter"
|
||||
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
|
||||
if material_containers:
|
||||
search_criteria["material"] = material_containers[0].getId()
|
||||
# Try all materials to see if there is a quality profile available.
|
||||
for material_container in material_containers:
|
||||
search_criteria["material"] = material_container.getId()
|
||||
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
|
|
|
@ -8,7 +8,7 @@ class MaterialSettingsVisibilityHandler(SettingVisibilityHandler):
|
|||
super().__init__(parent = parent, *args, **kwargs)
|
||||
|
||||
material_settings = set([
|
||||
"material_print_temperature",
|
||||
"default_material_print_temperature",
|
||||
"material_bed_temperature",
|
||||
"material_standby_temperature",
|
||||
"cool_fan_speed",
|
||||
|
|
|
@ -9,6 +9,10 @@ import UM.Logger
|
|||
import UM.Qt
|
||||
from UM.Application import Application
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
import os
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
|
||||
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||
KeyRole = Qt.UserRole + 1
|
||||
|
@ -28,6 +32,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
self._extruder_definition_id = None
|
||||
self._quality_id = None
|
||||
self._material_id = None
|
||||
self._i18n_catalog = None
|
||||
|
||||
self.addRoleName(self.KeyRole, "key")
|
||||
self.addRoleName(self.LabelRole, "label")
|
||||
|
@ -117,6 +122,18 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
|
||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
|
||||
definition = quality_container.getDefinition()
|
||||
|
||||
# Check if the definition container has a translation file.
|
||||
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
|
||||
catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
|
||||
if catalog.hasTranslationLoaded():
|
||||
self._i18n_catalog = catalog
|
||||
|
||||
for file_name in quality_container.getDefinition().getInheritedFiles():
|
||||
catalog = i18nCatalog(os.path.basename(file_name))
|
||||
if catalog.hasTranslationLoaded():
|
||||
self._i18n_catalog = catalog
|
||||
|
||||
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
||||
|
||||
|
@ -167,6 +184,8 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
for definition in definition_container.findDefinitions():
|
||||
if definition.type == "category":
|
||||
current_category = definition.label
|
||||
if self._i18n_catalog:
|
||||
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
|
||||
continue
|
||||
|
||||
profile_value = None
|
||||
|
@ -177,6 +196,12 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
profile_value_source = container.getMetaDataEntry("type")
|
||||
profile_value = new_value
|
||||
|
||||
# Global tab should use resolve (if there is one)
|
||||
if not self._extruder_id:
|
||||
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
|
||||
if resolve_value is not None and profile_value is not None:
|
||||
profile_value = resolve_value
|
||||
|
||||
user_value = None
|
||||
if not self._extruder_id:
|
||||
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
|
||||
|
@ -198,9 +223,14 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
if self._extruder_id == "" and settable_per_extruder:
|
||||
continue
|
||||
|
||||
|
||||
label = definition.label
|
||||
if self._i18n_catalog:
|
||||
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
||||
|
||||
items.append({
|
||||
"key": definition.key,
|
||||
"label": definition.label,
|
||||
"label": label,
|
||||
"unit": definition.unit,
|
||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||
"profile_value_source": profile_value_source,
|
||||
|
@ -208,4 +238,4 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|||
"category": current_category
|
||||
})
|
||||
|
||||
self.setItems(items)
|
||||
self.setItems(items)
|
||||
|
|
|
@ -89,7 +89,7 @@ class SettingInheritanceManager(QObject):
|
|||
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if property_name == "value" and self._global_container_stack:
|
||||
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||
if not definitions:
|
||||
return
|
||||
|
@ -171,7 +171,16 @@ class SettingInheritanceManager(QObject):
|
|||
continue
|
||||
if value is not None:
|
||||
# If a setting doesn't use any keys, it won't change it's value, so treat it as if it's a fixed value
|
||||
has_setting_function = isinstance(value, SettingFunction) and len(value.getUsedSettingKeys()) > 0
|
||||
has_setting_function = isinstance(value, SettingFunction)
|
||||
if has_setting_function:
|
||||
for setting_key in value.getUsedSettingKeys():
|
||||
if setting_key in self._active_container_stack.getAllKeys():
|
||||
break # We found an actual setting. So has_setting_function can remain true
|
||||
else:
|
||||
# All of the setting_keys turned out to not be setting keys at all!
|
||||
# This can happen due enum keys also being marked as settings.
|
||||
has_setting_function = False
|
||||
|
||||
if has_setting_function is False:
|
||||
has_non_function_value = True
|
||||
continue
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue