mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-08 14:34:01 -06:00
Merge branch 'master' into feature_model_check
This commit is contained in:
commit
9848c07a0e
75 changed files with 1478 additions and 615 deletions
|
@ -280,6 +280,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# Check if any quality_changes instance container is in conflict.
|
||||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||
quality_name = ""
|
||||
custom_quality_name = ""
|
||||
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||
num_user_settings = 0
|
||||
quality_changes_conflict = False
|
||||
|
@ -292,7 +293,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
container_id = self._stripFileToId(instance_container_file_name)
|
||||
|
||||
serialized = archive.open(instance_container_file_name).read().decode("utf-8")
|
||||
serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name)
|
||||
|
||||
# Qualities and variants don't have upgrades, so don't upgrade them
|
||||
parser = ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
container_type = parser["metadata"]["type"]
|
||||
if container_type not in ("quality", "variant"):
|
||||
serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name)
|
||||
|
||||
parser = ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
container_info = ContainerInfo(instance_container_file_name, serialized, parser)
|
||||
|
@ -309,7 +317,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
position = parser["metadata"]["position"]
|
||||
self._machine_info.quality_changes_info.extruder_info_dict[position] = container_info
|
||||
|
||||
quality_name = parser["general"]["name"]
|
||||
custom_quality_name = parser["general"]["name"]
|
||||
values = parser["values"] if parser.has_section("values") else dict()
|
||||
num_settings_overriden_by_quality_changes += len(values)
|
||||
# Check if quality changes already exists.
|
||||
|
@ -473,6 +481,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
extruders = num_extruders * [""]
|
||||
|
||||
quality_name = custom_quality_name if custom_quality_name else quality_name
|
||||
|
||||
self._machine_info.container_id = global_stack_id
|
||||
self._machine_info.name = machine_name
|
||||
self._machine_info.definition_id = machine_definition_id
|
||||
|
|
|
@ -224,7 +224,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
||||
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
||||
num_objects = self._numObjects()
|
||||
num_objects = self._numObjectsPerBuildPlate()
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
|
||||
|
||||
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
||||
self._scene.gcode_dict[build_plate_to_be_sliced] = []
|
||||
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
||||
|
@ -232,9 +236,6 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.slice()
|
||||
return
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
|
||||
|
||||
if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
|
||||
Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
|
||||
|
||||
|
@ -426,7 +427,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
return False
|
||||
|
||||
## Return a dict with number of objects per build plate
|
||||
def _numObjects(self):
|
||||
def _numObjectsPerBuildPlate(self):
|
||||
num_objects = defaultdict(int)
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
# Only count sliceable objects
|
||||
|
@ -453,7 +454,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
source_build_plate_number = source.callDecoration("getBuildPlateNumber")
|
||||
if source == self._scene.getRoot():
|
||||
# we got the root node
|
||||
num_objects = self._numObjects()
|
||||
num_objects = self._numObjectsPerBuildPlate()
|
||||
for build_plate_number in list(self._last_num_objects.keys()) + list(num_objects.keys()):
|
||||
if build_plate_number not in self._last_num_objects or num_objects[build_plate_number] != self._last_num_objects[build_plate_number]:
|
||||
self._last_num_objects[build_plate_number] = num_objects[build_plate_number]
|
||||
|
@ -604,7 +605,12 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
# See if we need to process the sliced layers job.
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
|
||||
if (
|
||||
self._layer_view_active and
|
||||
(self._process_layers_job is None or not self._process_layers_job.isRunning()) and
|
||||
active_build_plate == self._start_slice_job_build_plate and
|
||||
active_build_plate not in self._build_plates_to_be_sliced):
|
||||
|
||||
self._startProcessSlicedLayersJob(active_build_plate)
|
||||
# self._onActiveViewChanged()
|
||||
self._start_slice_job_build_plate = None
|
||||
|
@ -733,7 +739,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# There is data and we're not slicing at the moment
|
||||
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
||||
# TODO: what build plate I am slicing
|
||||
if active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job:
|
||||
if (active_build_plate in self._stored_optimized_layer_data and
|
||||
not self._slicing and
|
||||
not self._process_layers_job and
|
||||
active_build_plate not in self._build_plates_to_be_sliced):
|
||||
|
||||
self._startProcessSlicedLayersJob(active_build_plate)
|
||||
else:
|
||||
self._layer_view_active = False
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re # For escaping characters in the settings.
|
||||
import json
|
||||
import copy
|
||||
|
||||
from UM.Mesh.MeshWriter import MeshWriter
|
||||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
|
||||
import re #For escaping characters in the settings.
|
||||
import json
|
||||
import copy
|
||||
|
||||
## Writes g-code to a file.
|
||||
#
|
||||
|
@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._application = Application.getInstance()
|
||||
|
||||
## Writes the g-code for the entire scene to a stream.
|
||||
#
|
||||
# Note that even though the function accepts a collection of nodes, the
|
||||
|
@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter):
|
|||
|
||||
return flat_container
|
||||
|
||||
|
||||
## Serialises a container stack to prepare it for writing at the end of the
|
||||
# g-code.
|
||||
#
|
||||
|
@ -104,15 +105,20 @@ class GCodeWriter(MeshWriter):
|
|||
# \param settings A container stack to serialise.
|
||||
# \return A serialised string of the settings.
|
||||
def _serialiseSettings(self, stack):
|
||||
container_registry = self._application.getContainerRegistry()
|
||||
quality_manager = self._application.getQualityManager()
|
||||
|
||||
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
||||
prefix_length = len(prefix)
|
||||
|
||||
quality_type = stack.quality.getMetaDataEntry("quality_type")
|
||||
container_with_profile = stack.qualityChanges
|
||||
if container_with_profile.getId() == "empty_quality_changes":
|
||||
Logger.log("e", "No valid quality profile found, not writing settings to g-code!")
|
||||
return ""
|
||||
# If the global quality changes is empty, create a new one
|
||||
quality_name = container_registry.uniqueName(stack.quality.getName())
|
||||
container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_global_container.getMetaDataEntry("type", None) is None:
|
||||
flat_global_container.addMetaDataEntry("type", "quality_changes")
|
||||
|
@ -121,41 +127,47 @@ class GCodeWriter(MeshWriter):
|
|||
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
|
||||
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
|
||||
|
||||
# Change the default defintion
|
||||
default_machine_definition = "fdmprinter"
|
||||
if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")):
|
||||
default_machine_definition = stack.getMetaDataEntry("quality_definition")
|
||||
if not default_machine_definition:
|
||||
default_machine_definition = stack.definition.getId()
|
||||
flat_global_container.setMetaDataEntry("definition", default_machine_definition)
|
||||
# Get the machine definition ID for quality profiles
|
||||
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
|
||||
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
|
||||
|
||||
serialized = flat_global_container.serialize()
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")):
|
||||
all_setting_keys = set(flat_global_container.getAllKeys())
|
||||
for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
|
||||
extruder_quality = extruder.qualityChanges
|
||||
if extruder_quality.getId() == "empty_quality_changes":
|
||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||
continue
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
|
||||
# Same story, if quality changes is empty, create a new one
|
||||
quality_name = container_registry.uniqueName(stack.quality.getName())
|
||||
extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
|
||||
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
|
||||
|
||||
# Ensure that extruder is set. (Can happen if we have empty quality changes).
|
||||
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
|
||||
if flat_extruder_quality.getMetaDataEntry("position", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position"))
|
||||
|
||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
|
||||
|
||||
# Change the default defintion
|
||||
flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition)
|
||||
# Change the default definition
|
||||
flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)
|
||||
|
||||
extruder_serialized = flat_extruder_quality.serialize()
|
||||
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
||||
|
||||
all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
|
||||
|
||||
# Check if there is any profiles
|
||||
if not all_setting_keys:
|
||||
Logger.log("i", "No custom settings found, not writing settings to g-code.")
|
||||
return ""
|
||||
|
||||
json_string = json.dumps(data)
|
||||
|
||||
# Escape characters that have a special meaning in g-code comments.
|
||||
|
@ -169,5 +181,5 @@ class GCodeWriter(MeshWriter):
|
|||
|
||||
# Lines have 80 characters, so the payload of each line is 80 - prefix.
|
||||
for pos in range(0, len(escaped_string), 80 - prefix_length):
|
||||
result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
|
||||
result += prefix + escaped_string[pos: pos + 80 - prefix_length] + "\n"
|
||||
return result
|
||||
|
|
|
@ -48,7 +48,11 @@ class MonitorStage(CuraStage):
|
|||
new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
|
||||
if new_output_device != self._printer_output_device:
|
||||
if self._printer_output_device:
|
||||
self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
|
||||
try:
|
||||
self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
|
||||
except TypeError:
|
||||
# Ignore stupid "Not connected" errors.
|
||||
pass
|
||||
|
||||
self._printer_output_device = new_output_device
|
||||
|
||||
|
|
|
@ -163,7 +163,16 @@ Item {
|
|||
id: addedSettingsModel;
|
||||
containerId: Cura.MachineManager.activeDefinitionId
|
||||
expanded: [ "*" ]
|
||||
exclude: {
|
||||
filter:
|
||||
{
|
||||
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
|
||||
{
|
||||
return {"settable_per_meshgroup": true};
|
||||
}
|
||||
return {"settable_per_mesh": true};
|
||||
}
|
||||
exclude:
|
||||
{
|
||||
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
|
||||
|
||||
if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
|
||||
|
@ -375,7 +384,6 @@ Item {
|
|||
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
|
||||
width: screenScaleFactor * 360
|
||||
|
||||
property string labelFilter: ""
|
||||
property var additional_excluded_settings
|
||||
|
||||
onVisibilityChanged:
|
||||
|
@ -386,11 +394,33 @@ Item {
|
|||
// Set skip setting, it will prevent from resetting selected mesh_type
|
||||
contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
|
||||
listview.model.forceUpdate()
|
||||
|
||||
updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilter()
|
||||
{
|
||||
var new_filter = {};
|
||||
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
|
||||
{
|
||||
new_filter["settable_per_meshgroup"] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_filter["settable_per_mesh"] = true;
|
||||
}
|
||||
|
||||
if(filterInput.text != "")
|
||||
{
|
||||
new_filter["i18n_label"] = "*" + filterInput.text;
|
||||
}
|
||||
|
||||
listview.model.filter = new_filter;
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: filter
|
||||
id: filterInput
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
|
@ -401,17 +431,7 @@ Item {
|
|||
|
||||
placeholderText: catalog.i18nc("@label:textbox", "Filter...");
|
||||
|
||||
onTextChanged:
|
||||
{
|
||||
if(text != "")
|
||||
{
|
||||
listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
|
||||
}
|
||||
else
|
||||
{
|
||||
listview.model.filter = {"settable_per_mesh": true}
|
||||
}
|
||||
}
|
||||
onTextChanged: settingPickDialog.updateFilter()
|
||||
}
|
||||
|
||||
CheckBox
|
||||
|
@ -437,7 +457,7 @@ Item {
|
|||
|
||||
anchors
|
||||
{
|
||||
top: filter.bottom;
|
||||
top: filterInput.bottom;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
bottom: parent.bottom;
|
||||
|
@ -449,10 +469,6 @@ Item {
|
|||
{
|
||||
id: definitionsModel;
|
||||
containerId: Cura.MachineManager.activeDefinitionId
|
||||
filter:
|
||||
{
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
|
||||
expanded: [ "*" ]
|
||||
exclude:
|
||||
|
@ -484,6 +500,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: settingPickDialog.updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,6 +524,16 @@ Item {
|
|||
storeIndex: 0
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: printSequencePropertyProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "print_sequence"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
}
|
||||
|
||||
SystemPalette { id: palette; }
|
||||
|
||||
Component
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os.path
|
||||
|
@ -7,7 +7,7 @@ from UM.Application import Application
|
|||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.FileHandler.WriteFileJob import WriteFileJob
|
||||
from UM.Mesh.MeshWriter import MeshWriter
|
||||
from UM.FileHandler.FileWriter import FileWriter #To check against the write modes (text vs. binary).
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from UM.OutputDevice import OutputDeviceError
|
||||
|
@ -39,7 +39,7 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
# MIME types available to the currently active machine?
|
||||
#
|
||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
|
||||
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
|
||||
filter_by_machine = True # This plugin is intended to be used by machine (regardless of what it was told to do)
|
||||
if self._writing:
|
||||
raise OutputDeviceError.DeviceBusyError()
|
||||
|
||||
|
@ -56,19 +56,21 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")]
|
||||
|
||||
# Take the intersection between file_formats and machine_file_formats.
|
||||
file_formats = list(filter(lambda file_format: file_format["mime_type"] in machine_file_formats, file_formats))
|
||||
format_by_mimetype = {format["mime_type"]: format for format in file_formats}
|
||||
file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
|
||||
|
||||
if len(file_formats) == 0:
|
||||
Logger.log("e", "There are no file formats available to write with!")
|
||||
raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("There are no file formats available to write with!"))
|
||||
raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "There are no file formats available to write with!"))
|
||||
preferred_format = file_formats[0]
|
||||
|
||||
# Just take the first file format available.
|
||||
if file_handler is not None:
|
||||
writer = file_handler.getWriterByMimeType(file_formats[0]["mime_type"])
|
||||
writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
|
||||
else:
|
||||
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"])
|
||||
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
|
||||
|
||||
extension = file_formats[0]["extension"]
|
||||
extension = preferred_format["extension"]
|
||||
|
||||
if file_name is None:
|
||||
file_name = self._automaticFileName(nodes)
|
||||
|
@ -80,8 +82,11 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
try:
|
||||
Logger.log("d", "Writing to %s", file_name)
|
||||
# Using buffering greatly reduces the write time for many lines of gcode
|
||||
self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
|
||||
job = WriteFileJob(writer, self._stream, nodes, MeshWriter.OutputMode.TextMode)
|
||||
if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
|
||||
self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
|
||||
else: #Binary mode.
|
||||
self._stream = open(file_name, "wb", buffering = 1)
|
||||
job = WriteFileJob(writer, self._stream, nodes, preferred_format["mode"])
|
||||
job.setFileName(file_name)
|
||||
job.progress.connect(self._onProgress)
|
||||
job.finished.connect(self._onFinished)
|
||||
|
|
|
@ -158,7 +158,9 @@ class SimulationView(View):
|
|||
return self._nozzle_node
|
||||
|
||||
def _onSceneChanged(self, node):
|
||||
if node.getMeshData() is not None:
|
||||
if node.getMeshData() is None:
|
||||
self.resetLayerData()
|
||||
else:
|
||||
self.setActivity(False)
|
||||
self.calculateMaxLayers()
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
|
|
@ -146,7 +146,7 @@ class SliceInfo(Extension):
|
|||
model_stack = node.callDecoration("getStack")
|
||||
if model_stack:
|
||||
model_settings["support_enabled"] = model_stack.getProperty("support_enable", "value")
|
||||
model_settings["support_extruder_nr"] = int(model_stack.getProperty("support_extruder_nr", "value"))
|
||||
model_settings["support_extruder_nr"] = int(model_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
|
||||
# Mesh modifiers;
|
||||
model_settings["infill_mesh"] = model_stack.getProperty("infill_mesh", "value")
|
||||
|
@ -177,7 +177,7 @@ class SliceInfo(Extension):
|
|||
|
||||
# Support settings
|
||||
print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value")
|
||||
print_settings["support_extruder_nr"] = int(global_container_stack.getProperty("support_extruder_nr", "value"))
|
||||
print_settings["support_extruder_nr"] = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
|
||||
# Platform adhesion settings
|
||||
print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value")
|
||||
|
|
|
@ -62,7 +62,7 @@ class SolidView(View):
|
|||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
|
||||
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
|
||||
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
|
||||
|
||||
if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
|
||||
|
@ -89,7 +89,7 @@ class SolidView(View):
|
|||
# Use the support extruder instead of the active extruder if this is a support_mesh
|
||||
if per_mesh_stack:
|
||||
if per_mesh_stack.getProperty("support_mesh", "value"):
|
||||
extruder_index = int(global_container_stack.getProperty("support_extruder_nr", "value"))
|
||||
extruder_index = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
|
||||
try:
|
||||
material_color = self._extruders_model.getItem(extruder_index)["color"]
|
||||
|
|
|
@ -1,39 +1,103 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Tool import Tool
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Tool import Tool
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, MouseEvent
|
||||
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
from cura.PickingPass import PickingPass
|
||||
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from cura.Operations.SetParentOperation import SetParentOperation
|
||||
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from UM.Scene.GroupDecorator import GroupDecorator
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
|
||||
class SupportEraser(Tool):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._shortcut_key = Qt.Key_G
|
||||
self._controller = Application.getInstance().getController()
|
||||
self._controller = self.getController()
|
||||
|
||||
self._selection_pass = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
||||
|
||||
# Note: if the selection is cleared with this tool active, there is no way to switch to
|
||||
# another tool than to reselect an object (by clicking it) because the tool buttons in the
|
||||
# toolbar will have been disabled. That is why we need to ignore the first press event
|
||||
# after the selection has been cleared.
|
||||
Selection.selectionChanged.connect(self._onSelectionChanged)
|
||||
self._had_selection = False
|
||||
self._skip_press = False
|
||||
|
||||
self._had_selection_timer = QTimer()
|
||||
self._had_selection_timer.setInterval(0)
|
||||
self._had_selection_timer.setSingleShot(True)
|
||||
self._had_selection_timer.timeout.connect(self._selectionChangeDelay)
|
||||
|
||||
def event(self, event):
|
||||
super().event(event)
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||
|
||||
if event.type == Event.ToolActivateEvent:
|
||||
if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
|
||||
if ctrl_is_active:
|
||||
self._controller.setActiveTool("TranslateTool")
|
||||
return
|
||||
|
||||
# Load the remover mesh:
|
||||
self._createEraserMesh()
|
||||
if self._skip_press:
|
||||
# The selection was previously cleared, do not add/remove an anti-support mesh but
|
||||
# use this click for selection and reactivating this tool only.
|
||||
self._skip_press = False
|
||||
return
|
||||
|
||||
# After we load the mesh, deactivate the tool again:
|
||||
self.getController().setActiveTool(None)
|
||||
if self._selection_pass is None:
|
||||
# The selection renderpass is used to identify objects in the current view
|
||||
self._selection_pass = Application.getInstance().getRenderer().getRenderPass("selection")
|
||||
picked_node = self._controller.getScene().findObject(self._selection_pass.getIdAtPosition(event.x, event.y))
|
||||
if not picked_node:
|
||||
# There is no slicable object at the picked location
|
||||
return
|
||||
|
||||
def _createEraserMesh(self):
|
||||
node_stack = picked_node.callDecoration("getStack")
|
||||
if node_stack:
|
||||
if node_stack.getProperty("anti_overhang_mesh", "value"):
|
||||
self._removeEraserMesh(picked_node)
|
||||
return
|
||||
|
||||
elif node_stack.getProperty("support_mesh", "value") or node_stack.getProperty("infill_mesh", "value") or node_stack.getProperty("cutting_mesh", "value"):
|
||||
# Only "normal" meshes can have anti_overhang_meshes added to them
|
||||
return
|
||||
|
||||
# Create a pass for picking a world-space location from the mouse location
|
||||
active_camera = self._controller.getScene().getActiveCamera()
|
||||
picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight())
|
||||
picking_pass.render()
|
||||
|
||||
picked_position = picking_pass.getPickedPosition(event.x, event.y)
|
||||
|
||||
# Add the anti_overhang_mesh cube at the picked location
|
||||
self._createEraserMesh(picked_node, picked_position)
|
||||
|
||||
def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
|
||||
node = CuraSceneNode()
|
||||
|
||||
node.setName("Eraser")
|
||||
|
@ -41,9 +105,7 @@ class SupportEraser(Tool):
|
|||
mesh = MeshBuilder()
|
||||
mesh.addCube(10,10,10)
|
||||
node.setMeshData(mesh.build())
|
||||
# Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
|
||||
move_vector = Vector(0, 5, 0)
|
||||
node.setPosition(move_vector)
|
||||
node.setPosition(position)
|
||||
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
|
||||
|
@ -51,21 +113,60 @@ class SupportEraser(Tool):
|
|||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
node.addDecorator(SliceableObjectDecorator())
|
||||
|
||||
stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
||||
if not stack:
|
||||
node.addDecorator(SettingOverrideDecorator())
|
||||
stack = node.callDecoration("getStack")
|
||||
|
||||
stack = node.callDecoration("getStack") # created by SettingOverrideDecorator
|
||||
settings = stack.getTop()
|
||||
|
||||
if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
|
||||
definition = stack.getSettingDefinition("anti_overhang_mesh")
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
new_instance.setProperty("value", True)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
definition = stack.getSettingDefinition("anti_overhang_mesh")
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
new_instance.setProperty("value", True)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
|
||||
scene = self._controller.getScene()
|
||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
||||
root = self._controller.getScene().getRoot()
|
||||
|
||||
op = GroupedOperation()
|
||||
# First add the node to the scene, so it gets the expected transform
|
||||
op.addOperation(AddSceneNodeOperation(node, root))
|
||||
op.addOperation(SetParentOperation(node, parent))
|
||||
op.push()
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
def _removeEraserMesh(self, node: CuraSceneNode):
|
||||
parent = node.getParent()
|
||||
if parent == self._controller.getScene().getRoot():
|
||||
parent = None
|
||||
|
||||
op = RemoveSceneNodeOperation(node)
|
||||
op.push()
|
||||
|
||||
if parent and not Selection.isSelected(parent):
|
||||
Selection.add(parent)
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
def _updateEnabled(self):
|
||||
plugin_enabled = False
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
|
||||
|
||||
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
||||
|
||||
def _onSelectionChanged(self):
|
||||
# When selection is passed from one object to another object, first the selection is cleared
|
||||
# and then it is set to the new object. We are only interested in the change from no selection
|
||||
# to a selection or vice-versa, not in a change from one object to another. A timer is used to
|
||||
# "merge" a possible clear/select action in a single frame
|
||||
if Selection.hasSelection() != self._had_selection:
|
||||
self._had_selection_timer.start()
|
||||
|
||||
def _selectionChangeDelay(self):
|
||||
has_selection = Selection.hasSelection()
|
||||
if not has_selection and self._had_selection:
|
||||
self._skip_press = True
|
||||
else:
|
||||
self._skip_press = False
|
||||
|
||||
self._had_selection = has_selection
|
||||
|
|
|
@ -216,7 +216,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"]
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
|
|
|
@ -13,6 +13,7 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
|
|||
def __init__(self, output_device):
|
||||
super().__init__(output_device)
|
||||
self.can_pre_heat_bed = False
|
||||
self.can_pre_heat_hotends = False
|
||||
self.can_control_manually = False
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
|
|
|
@ -147,6 +147,10 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
return ""
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def existsKey(self, key) -> bool:
|
||||
return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
||||
|
||||
@pyqtSlot()
|
||||
def loadConfigurationFromPrinter(self):
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
|
|
|
@ -5,6 +5,7 @@ import QtQuick 2.2
|
|||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
|
@ -33,15 +34,33 @@ Cura.MachineAction
|
|||
{
|
||||
var printerKey = base.selectedDevice.key
|
||||
var printerName = base.selectedDevice.name // TODO To change when the groups have a name
|
||||
if(manager.getStoredKey() != printerKey)
|
||||
if (manager.getStoredKey() != printerKey)
|
||||
{
|
||||
manager.setKey(printerKey)
|
||||
manager.setGroupName(printerName) // TODO To change when the groups have a name
|
||||
completed()
|
||||
// Check if there is another instance with the same key
|
||||
if (!manager.existsKey(printerKey))
|
||||
{
|
||||
manager.setKey(printerKey)
|
||||
manager.setGroupName(printerName) // TODO To change when the groups have a name
|
||||
completed()
|
||||
}
|
||||
else
|
||||
{
|
||||
existingConnectionDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
{
|
||||
id: existingConnectionDialog
|
||||
title: catalog.i18nc("@window:title", "Existing Connection")
|
||||
icon: StandardIcon.Information
|
||||
text: catalog.i18nc("@message:text", "This printer/group is already added to Cura. Please select another printer/group.")
|
||||
standardButtons: StandardButton.Ok
|
||||
modality: Qt.ApplicationModal
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent;
|
||||
|
|
|
@ -82,6 +82,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self._zero_conf_browser.cancel()
|
||||
self._zero_conf_browser = None # Force the old ServiceBrowser to be destroyed.
|
||||
|
||||
for instance_name in list(self._discovered_devices):
|
||||
self._onRemoveDevice(instance_name)
|
||||
|
||||
self._zero_conf = Zeroconf()
|
||||
self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
|
||||
[self._appendServiceChangedRequest])
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
||||
class USBPrinterOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
||||
super().__init__(output_device)
|
||||
|
||||
self._preheat_bed_timer = QTimer()
|
||||
self._preheat_bed_timer.setSingleShot(True)
|
||||
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
|
||||
self._preheat_printer = None
|
||||
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
self._output_device.sendCommand("G91")
|
||||
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
|
||||
self._output_device.sendCommand("G90")
|
||||
|
||||
def homeHead(self, printer):
|
||||
self._output_device.sendCommand("G28 X")
|
||||
self._output_device.sendCommand("G28 Y")
|
||||
|
||||
def homeBed(self, printer):
|
||||
self._output_device.sendCommand("G28 Z")
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
if state == "pause":
|
||||
self._output_device.pausePrint()
|
||||
job.updateState("paused")
|
||||
elif state == "print":
|
||||
self._output_device.resumePrint()
|
||||
job.updateState("printing")
|
||||
elif state == "abort":
|
||||
self._output_device.cancelPrint()
|
||||
pass
|
||||
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
|
||||
try:
|
||||
temperature = round(temperature) # The API doesn't allow floating point.
|
||||
duration = round(duration)
|
||||
except ValueError:
|
||||
return # Got invalid values, can't pre-heat.
|
||||
|
||||
self.setTargetBedTemperature(printer, temperature=temperature)
|
||||
self._preheat_bed_timer.setInterval(duration * 1000)
|
||||
self._preheat_bed_timer.start()
|
||||
self._preheat_printer = printer
|
||||
printer.updateIsPreheating(True)
|
||||
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
|
||||
self.preheatBed(printer, temperature=0, duration=0)
|
||||
self._preheat_bed_timer.stop()
|
||||
printer.updateIsPreheating(False)
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
self._output_device.sendCommand("M140 S%s" % temperature)
|
||||
|
||||
def _onPreheatBedTimerFinished(self):
|
||||
self.setTargetBedTemperature(self._preheat_printer, 0)
|
||||
self._preheat_printer.updateIsPreheating(False)
|
|
@ -10,9 +10,9 @@ from UM.PluginRegistry import PluginRegistry
|
|||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
||||
|
||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
from .USBPrinterOutputController import USBPrinterOutputController
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||
|
@ -240,7 +240,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
||||
# Ensure that a printer is created.
|
||||
self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)]
|
||||
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
|
||||
self._printers[0].updateName(container_stack.getName())
|
||||
self.setConnectionState(ConnectionState.connected)
|
||||
self._update_thread.start()
|
||||
|
@ -372,7 +372,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
elapsed_time = int(time() - self._print_start_time)
|
||||
print_job = self._printers[0].activePrintJob
|
||||
if print_job is None:
|
||||
print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
|
||||
print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
|
||||
print_job.updateState("printing")
|
||||
self._printers[0].updateActivePrintJob(print_job)
|
||||
|
||||
|
|
|
@ -153,6 +153,10 @@ class VersionUpgrade26to27(VersionUpgrade):
|
|||
if new_id is not None:
|
||||
parser.set("containers", key, new_id)
|
||||
|
||||
if "6" not in parser["containers"]:
|
||||
parser["containers"]["6"] = parser["containers"]["5"]
|
||||
parser["containers"]["5"] = "empty"
|
||||
|
||||
for each_section in ("general", "metadata"):
|
||||
if not parser.has_section(each_section):
|
||||
parser.add_section(each_section)
|
||||
|
|
|
@ -208,14 +208,9 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
machine_variant_map = {}
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
|
||||
root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile.
|
||||
material_group = material_manager.getMaterialGroup(root_material_id)
|
||||
|
||||
all_containers = []
|
||||
for node in [material_group.root_material_node] + material_group.derived_material_node_list:
|
||||
all_containers.append(node.getContainer())
|
||||
all_containers = registry.findInstanceContainers(base_file = root_material_id)
|
||||
|
||||
for container in all_containers:
|
||||
definition_id = container.getMetaDataEntry("definition")
|
||||
|
@ -242,7 +237,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
|
||||
for definition_id, container in machine_container_map.items():
|
||||
definition_id = container.getMetaDataEntry("definition")
|
||||
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = definition_id)[0]
|
||||
definition_metadata = registry.findDefinitionContainersMetadata(id = definition_id)[0]
|
||||
|
||||
product = definition_id
|
||||
for product_name, product_id_list in product_id_map.items():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue