Merge branch 'master' into feature_xmlmaterials_cura_settings

This commit is contained in:
fieldOfView 2018-03-23 14:57:31 +01:00
commit 251e24fedc
23 changed files with 382 additions and 23 deletions

View file

@ -85,7 +85,7 @@ class CrashHandler:
dialog = QDialog() dialog = QDialog()
dialog.setMinimumWidth(500) dialog.setMinimumWidth(500)
dialog.setMinimumHeight(170) dialog.setMinimumHeight(170)
dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't startup")) dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura can't start"))
dialog.finished.connect(self._closeEarlyCrashDialog) dialog.finished.connect(self._closeEarlyCrashDialog)
layout = QVBoxLayout(dialog) layout = QVBoxLayout(dialog)

View file

@ -1288,7 +1288,7 @@ class CuraApplication(QtApplication):
has_merged_nodes = False has_merged_nodes = False
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, CuraSceneNode) or not node.getMeshData() : if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
if node.getName() == 'MergedMesh': if node.getName() == "MergedMesh":
has_merged_nodes = True has_merged_nodes = True
continue continue
@ -1380,7 +1380,7 @@ class CuraApplication(QtApplication):
# Use the previously found center of the group bounding box as the new location of the group # Use the previously found center of the group bounding box as the new location of the group
group_node.setPosition(group_node.getBoundingBox().center) group_node.setPosition(group_node.getBoundingBox().center)
group_node.setName("MergedMesh") # add a specific name to destinguis this node group_node.setName("MergedMesh") # add a specific name to distinguish this node
## Updates origin position of all merged meshes ## Updates origin position of all merged meshes
@ -1625,8 +1625,13 @@ class CuraApplication(QtApplication):
node.setName(os.path.basename(filename)) node.setName(os.path.basename(filename))
self.getBuildVolume().checkBoundsAndUpdate(node) self.getBuildVolume().checkBoundsAndUpdate(node)
extension = os.path.splitext(filename)[1] is_non_sliceable = False
if extension.lower() in self._non_sliceable_extensions: filename_lower = filename.lower()
for extension in self._non_sliceable_extensions:
if filename_lower.endswith(extension):
is_non_sliceable = True
break
if is_non_sliceable:
self.callLater(lambda: self.getController().setActiveView("SimulationView")) self.callLater(lambda: self.getController().setActiveView("SimulationView"))
block_slicing_decorator = BlockSlicingDecorator() block_slicing_decorator = BlockSlicingDecorator()

View file

@ -597,6 +597,18 @@ class MachineManager(QObject):
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
## Copy the value of all manually changed settings of the current extruder to all other extruders.
@pyqtSlot()
def copyAllValuesToExtruders(self):
extruder_stacks = list(self._global_container_stack.extruders.values())
for extruder_stack in extruder_stacks:
if extruder_stack != self._active_container_stack:
for key in self._active_container_stack.userChanges.getAllKeys():
new_value = self._active_container_stack.getProperty(key, "value")
# check if the value has to be replaced
extruder_stack.userChanges.setProperty(key, "value", new_value)
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self) -> str: def activeVariantName(self) -> str:
if self._active_container_stack: if self._active_container_stack:

View file

@ -119,6 +119,7 @@ class CuraEngineBackend(QObject, Backend):
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
self._slice_start_time = None self._slice_start_time = None
self._is_disabled = False
Preferences.getInstance().addPreference("general/auto_slice", True) Preferences.getInstance().addPreference("general/auto_slice", True)
@ -405,6 +406,7 @@ class CuraEngineBackend(QObject, Backend):
# - decorator isBlockSlicing is found (used in g-code reader) # - decorator isBlockSlicing is found (used in g-code reader)
def determineAutoSlicing(self): def determineAutoSlicing(self):
enable_timer = True enable_timer = True
self._is_disabled = False
if not Preferences.getInstance().getValue("general/auto_slice"): if not Preferences.getInstance().getValue("general/auto_slice"):
enable_timer = False enable_timer = False
@ -412,6 +414,7 @@ class CuraEngineBackend(QObject, Backend):
if node.callDecoration("isBlockSlicing"): if node.callDecoration("isBlockSlicing"):
enable_timer = False enable_timer = False
self.backendStateChange.emit(BackendState.Disabled) self.backendStateChange.emit(BackendState.Disabled)
self._is_disabled = True
gcode_list = node.callDecoration("getGCodeList") gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None: if gcode_list is not None:
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list
@ -545,6 +548,10 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.stop() self._change_timer.stop()
def _onStackErrorCheckFinished(self): def _onStackErrorCheckFinished(self):
self.determineAutoSlicing()
if self._is_disabled:
return
if not self._slicing and self._build_plates_to_be_sliced: if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing() self.needsSlicing()
self._onChanged() self._onChanged()

View file

@ -0,0 +1,33 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import gzip
import tempfile
from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing.
from typing import List
from UM.Logger import Logger
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
from UM.PluginRegistry import PluginRegistry
from UM.Scene.SceneNode import SceneNode #For typing.
## A file reader that reads gzipped g-code.
#
# If you're zipping g-code, you might as well use gzip!
class GCodeGzReader(MeshReader):
def __init__(self):
super().__init__()
self._supported_extensions = [".gcode.gz"]
def read(self, file_name):
with open(file_name, "rb") as file:
file_data = file.read()
uncompressed_gcode = gzip.decompress(file_data)
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(uncompressed_gcode)
PluginRegistry.getInstance().getPluginObject("GCodeReader").preRead(temp_file.name)
result = PluginRegistry.getInstance().getPluginObject("GCodeReader").read(temp_file.name)
return result

View file

@ -0,0 +1,21 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import GCodeGzReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"mesh_reader": [
{
"extension": "gcode.gz",
"description": i18n_catalog.i18nc("@item:inlistbox", "Compressed G-code File")
}
]
}
def register(app):
app.addNonSliceableExtension(".gcode.gz")
return { "mesh_reader": GCodeGzReader.GCodeGzReader() }

View file

@ -0,0 +1,8 @@
{
"name": "Compressed G-code Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Reads g-code from a compressed archive.",
"api": 4,
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,116 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Extension import Extension
from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.PluginRegistry import PluginRegistry
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
catalog = i18nCatalog("cura")
class ModelChecker(QObject, Extension):
## Signal that gets emitted when anything changed that we need to check.
onChanged = pyqtSignal()
def __init__(self):
super().__init__()
self._button_view = None
self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question.
lifetime = 0,
title = catalog.i18nc("@info:title", "Model Checker Warning"))
Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)
## Pass-through to allow UM.Signal to connect with a pyqtSignal.
def _onChanged(self, _):
self.onChanged.emit()
## Called when plug-ins are initialized.
#
# This makes sure that we listen to changes of the material and that the
# button is created that indicates warnings with the current set-up.
def _pluginsInitialized(self):
Application.getInstance().getMachineManager().rootMaterialChanged.connect(self.onChanged)
self._createView()
def checkObjectsForShrinkage(self):
shrinkage_threshold = 0.5 #From what shrinkage percentage a warning will be issued about the model size.
warning_size_xy = 150 #The horizontal size of a model that would be too large when dealing with shrinking materials.
warning_size_z = 100 #The vertical size of a model that would be too large when dealing with shrinking materials.
material_shrinkage = self._getMaterialShrinkage()
warning_nodes = []
# Check node material shrinkage and bounding box size
for node in self.sliceableNodes():
node_extruder_position = node.callDecoration("getActiveExtruderPosition")
if material_shrinkage[node_extruder_position] > shrinkage_threshold:
bbox = node.getBoundingBox()
if bbox.width >= warning_size_xy or bbox.depth >= warning_size_xy or bbox.height >= warning_size_z:
warning_nodes.append(node)
self._caution_message.setText(catalog.i18nc(
"@info:status",
"Some models may not be printed optimal due to object size and chosen material for models: {model_names}.\n"
"Tips that may be useful to improve the print quality:\n"
"1) Use rounded corners\n"
"2) Turn the fan off (only if the are no tiny details on the model)\n"
"3) Use a different material").format(model_names = ", ".join([n.getName() for n in warning_nodes])))
return len(warning_nodes) > 0
def sliceableNodes(self):
# Add all sliceable scene nodes to check
scene = Application.getInstance().getController().getScene()
for node in DepthFirstIterator(scene.getRoot()):
if node.callDecoration("isSliceable"):
yield node
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
def _createView(self):
Logger.log("d", "Creating model checker view.")
# Create the plugin dialog component
path = os.path.join(PluginRegistry.getInstance().getPluginPath("ModelChecker"), "ModelChecker.qml")
self._button_view = Application.getInstance().createQmlComponent(path, {"manager": self})
# The qml is only the button
Application.getInstance().addAdditionalComponent("jobSpecsButton", self._button_view)
Logger.log("d", "Model checker view created.")
@pyqtProperty(bool, notify = onChanged)
def runChecks(self):
danger_shrinkage = self.checkObjectsForShrinkage()
return any((danger_shrinkage, )) #If any of the checks fail, show the warning button.
@pyqtSlot()
def showWarnings(self):
self._caution_message.show()
def _getMaterialShrinkage(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None:
return {}
material_shrinkage = {}
# Get all shrinkage values of materials used
for extruder_position, extruder in global_container_stack.extruders.items():
shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value")
if shrinkage is None:
shrinkage = 0
material_shrinkage[extruder_position] = shrinkage
return material_shrinkage

View file

@ -0,0 +1,43 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import UM 1.2 as UM
import Cura 1.0 as Cura
Button
{
id: modelCheckerButton
UM.I18nCatalog{id: catalog; name:"cura"}
visible: manager.runChecks
tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.")
onClicked: manager.showWarnings()
width: UM.Theme.getSize("save_button_specs_icons").width
height: UM.Theme.getSize("save_button_specs_icons").height
style: ButtonStyle
{
background: Item
{
UM.RecolorImage
{
width: UM.Theme.getSize("save_button_specs_icons").width;
height: UM.Theme.getSize("save_button_specs_icons").height;
sourceSize.width: width;
sourceSize.height: width;
color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene");
source: "model_checker.svg"
}
}
}
}

View file

@ -0,0 +1,14 @@
# Copyright (c) 2018 Ultimaker B.V.
# This example is released under the terms of the AGPLv3 or higher.
from . import ModelChecker
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {}
def register(app):
return { "extension": ModelChecker.ModelChecker() }

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
<polygon fill="#000000" points="19 11 30 8 30 24 19 27" />
<path d="M10,19 C5.581722,19 2,15.418278 2,11 C2,6.581722 5.581722,3 10,3 C14.418278,3 18,6.581722 18,11 C18,15.418278 14.418278,19 10,19 Z M10,17 C13.3137085,17 16,14.3137085 16,11 C16,7.6862915 13.3137085,5 10,5 C6.6862915,5 4,7.6862915 4,11 C4,14.3137085 6.6862915,17 10,17 Z" fill="#000000" />
<polygon fill="#000000" points="4.2 15 6 16.8 1.8 21 0 19.2" />
<path d="M18.7333454,8.81666365 C18.2107269,6.71940704 16.9524304,4.91317986 15.248379,3.68790525 L18,3 L30,6 L18.7333454,8.81666365 Z M17,16.6573343 L17,27 L6,24 L6,19.0644804 C7.20495897,19.6632939 8.56315852,20 10,20 C12.8272661,20 15.3500445,18.6963331 17,16.6573343 Z" fill="#000000" />
</svg>

After

Width:  |  Height:  |  Size: 877 B

View file

@ -0,0 +1,8 @@
{
"name": "Model Checker",
"author": "Ultimaker B.V.",
"version": "0.1",
"api": 4,
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}

View file

@ -1,11 +1,10 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the LGPLv3 or higher. # PluginBrowser is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from UM.Application import Application from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Qt.Bindings.PluginsModel import PluginsModel from UM.Qt.Bindings.PluginsModel import PluginsModel
@ -20,7 +19,6 @@ import os
import tempfile import tempfile
import platform import platform
import zipfile import zipfile
import shutil
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
@ -44,7 +42,7 @@ class PluginBrowser(QObject, Extension):
self._plugins_metadata = [] self._plugins_metadata = []
self._plugins_model = None self._plugins_model = None
# Can be 'installed' or 'availble' # Can be 'installed' or 'available'
self._view = "available" self._view = "available"
self._restart_required = False self._restart_required = False

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// PluginBrowser is released under the terms of the LGPLv3 or higher. // PluginBrowser is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.2
@ -129,6 +129,18 @@ Component {
return catalog.i18nc("@action:button", "Install"); return catalog.i18nc("@action:button", "Install");
} }
} }
enabled:
{
if ( manager.isDownloading )
{
return pluginList.activePlugin == model ? true : false
}
else
{
return true
}
}
opacity: enabled ? 1.0 : 0.5
visible: model.external && ((model.status !== "installed") || model.can_upgrade) visible: model.external && ((model.status !== "installed") || model.can_upgrade)
style: ButtonStyle { style: ButtonStyle {
background: Rectangle { background: Rectangle {

View file

@ -74,7 +74,7 @@ class SimulationView(View):
self._global_container_stack = None self._global_container_stack = None
self._proxy = SimulationViewProxy() self._proxy = SimulationViewProxy()
self._controller.getScene().sceneChanged.connect(self._onSceneChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._resetSettings() self._resetSettings()
self._legend_items = None self._legend_items = None
@ -160,10 +160,10 @@ class SimulationView(View):
def _onSceneChanged(self, node): def _onSceneChanged(self, node):
if node.getMeshData() is None: if node.getMeshData() is None:
self.resetLayerData() self.resetLayerData()
else:
self.setActivity(False) self.setActivity(False)
self.calculateMaxLayers() self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num) self.calculateMaxPathsOnLayer(self._current_layer_num)
def isBusy(self): def isBusy(self):
return self._busy return self._busy

View file

@ -127,10 +127,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._sending_job.send("") #No specifically selected printer. self._sending_job.send("") #No specifically selected printer.
is_job_sent = self._sending_job.send(None) is_job_sent = self._sending_job.send(None)
# Notify the UI that a switch to the print monitor should happen
if is_job_sent:
Application.getInstance().getController().setActiveStage("MonitorStage")
def _spawnPrinterSelectionDialog(self): def _spawnPrinterSelectionDialog(self):
if self._printer_selection_dialog is None: if self._printer_selection_dialog is None:
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml") path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
@ -242,6 +238,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if new_progress > self._progress_message.getProgress(): if new_progress > self._progress_message.getProgress():
self._progress_message.show() # Ensure that the message is visible. self._progress_message.show() # Ensure that the message is visible.
self._progress_message.setProgress(bytes_sent / bytes_total * 100) self._progress_message.setProgress(bytes_sent / bytes_total * 100)
# If successfully sent:
if bytes_sent == bytes_total:
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the
# monitor tab.
self._success_message = Message(
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
lifetime=5, dismissable=True,
title=i18n_catalog.i18nc("@info:title", "Data Sent"))
self._success_message.addAction("View", i18n_catalog.i18nc("@action:button", "View in Montior"), icon=None,
description="")
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
self._success_message.show()
else: else:
self._progress_message.setProgress(0) self._progress_message.setProgress(0)
self._progress_message.hide() self._progress_message.hide()
@ -260,6 +269,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._latest_reply_handler.disconnect() self._latest_reply_handler.disconnect()
self._latest_reply_handler = None self._latest_reply_handler = None
def _successMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
if action_id == "View":
Application.getInstance().getController().setActiveStage("MonitorStage")
@pyqtSlot() @pyqtSlot()
def openPrintJobControlPanel(self) -> None: def openPrintJobControlPanel(self) -> None:

View file

@ -1032,7 +1032,8 @@ class XmlMaterialProfile(InstanceContainer):
"retraction amount": "retraction_amount", "retraction amount": "retraction_amount",
"retraction speed": "retraction_speed", "retraction speed": "retraction_speed",
"adhesion tendency": "material_adhesion_tendency", "adhesion tendency": "material_adhesion_tendency",
"surface energy": "material_surface_energy" "surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage",
} }
__unmapped_settings = [ __unmapped_settings = [
"hardware compatible", "hardware compatible",

View file

@ -2097,6 +2097,19 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
"material_shrinkage_percentage":
{
"label": "Shrinkage Ratio",
"description": "Shrinkage ratio in percentage.",
"unit": "%",
"type": "float",
"default_value": 0,
"minimum_value": "0",
"maximum_value": "100",
"enabled": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_flow": "material_flow":
{ {
"label": "Flow", "label": "Flow",

View file

@ -194,7 +194,7 @@ msgstr "Vorbereiten"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23 #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
msgctxt "@action:button Preceded by 'Ready to'." msgctxt "@action:button Preceded by 'Ready to'."
msgid "Save to Removable Drive" msgid "Save to Removable Drive"
msgstr "Speichern auf Wechseldatenträger" msgstr "Speichern auf Datenträger"
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24 #: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24
#, python-brace-format #, python-brace-format

View file

@ -115,15 +115,50 @@ Item {
} }
} }
Row {
id: additionalComponentsRow
anchors.top: jobNameRow.bottom
anchors.right: parent.right
}
Label Label
{ {
id: boundingSpec id: boundingSpec
anchors.top: jobNameRow.bottom anchors.top: jobNameRow.bottom
anchors.right: parent.right anchors.right: additionalComponentsRow.left
anchors.rightMargin:
{
if (additionalComponentsRow.width > 0)
{
return UM.Theme.getSize("default_margin").width
}
else
{
return 0;
}
}
height: UM.Theme.getSize("jobspecs_line").height height: UM.Theme.getSize("jobspecs_line").height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("small") font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_scene") color: UM.Theme.getColor("text_scene")
text: CuraApplication.getSceneBoundingBoxString text: CuraApplication.getSceneBoundingBoxString
} }
Component.onCompleted: {
base.addAdditionalComponents("jobSpecsButton")
}
Connections {
target: CuraApplication
onAdditionalComponentsChanged: base.addAdditionalComponents("jobSpecsButton")
}
function addAdditionalComponents (areaId) {
if(areaId == "jobSpecsButton") {
for (var component in CuraApplication.additionalComponents["jobSpecsButton"]) {
CuraApplication.additionalComponents["jobSpecsButton"][component].parent = additionalComponentsRow
}
}
}
} }

View file

@ -21,7 +21,10 @@ Column
{ {
// FIXME For now the model should be removed and then created again, otherwise changes in the printer don't automatically update the UI // FIXME For now the model should be removed and then created again, otherwise changes in the printer don't automatically update the UI
configurationList.model = [] configurationList.model = []
configurationList.model = outputDevice.uniqueConfigurations if(outputDevice)
{
configurationList.model = outputDevice.uniqueConfigurations
}
} }
Label Label

View file

@ -533,6 +533,15 @@ Item
onTriggered: Cura.MachineManager.copyValueToExtruders(contextMenu.key) onTriggered: Cura.MachineManager.copyValueToExtruders(contextMenu.key)
} }
MenuItem
{
//: Settings context menu action
text: catalog.i18nc("@action:menu", "Copy all changed values to all extruders")
visible: machineExtruderCount.properties.value > 1
enabled: contextMenu.provider != undefined
onTriggered: Cura.MachineManager.copyAllValuesToExtruders()
}
MenuSeparator MenuSeparator
{ {
visible: machineExtruderCount.properties.value > 1 visible: machineExtruderCount.properties.value > 1

View file

@ -411,6 +411,8 @@
"save_button_save_to_button": [0.3, 2.7], "save_button_save_to_button": [0.3, 2.7],
"save_button_specs_icons": [1.4, 1.4], "save_button_specs_icons": [1.4, 1.4],
"job_specs_button": [2.7, 2.7],
"monitor_preheat_temperature_control": [4.5, 2.0], "monitor_preheat_temperature_control": [4.5, 2.0],
"modal_window_minimum": [60.0, 45], "modal_window_minimum": [60.0, 45],