mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-23 06:33:55 -06:00
Merge remote-tracking branch 'um/master' into gcode-keywords
This commit is contained in:
commit
e47ca7a68d
379 changed files with 147832 additions and 59891 deletions
270
plugins/CuraEngineBackend/CuraEngineBackend.py
Normal file → Executable file
270
plugins/CuraEngineBackend/CuraEngineBackend.py
Normal file → Executable file
|
@ -14,13 +14,10 @@ from UM.Settings.Validator import ValidatorState #To find if a setting is in an
|
|||
from UM.Platform import Platform
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
import cura.Settings
|
||||
|
||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from . import ProcessSlicedLayersJob
|
||||
from . import ProcessGCodeJob
|
||||
from . import StartSliceJob
|
||||
|
||||
import os
|
||||
|
@ -34,13 +31,14 @@ import Arcus
|
|||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
class CuraEngineBackend(Backend):
|
||||
class CuraEngineBackend(QObject, Backend):
|
||||
## Starts the back-end plug-in.
|
||||
#
|
||||
# This registers all the signal listeners and prepares for communication
|
||||
# with the back-end in general.
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# CuraEngineBackend is exposed to qml as well.
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
# Find out where the engine is located, and how it is called.
|
||||
# This depends on how Cura is packaged and which OS we are running on.
|
||||
executable_name = "CuraEngine"
|
||||
|
@ -68,11 +66,6 @@ class CuraEngineBackend(Backend):
|
|||
default_engine_location = os.path.abspath(default_engine_location)
|
||||
Preferences.getInstance().addPreference("backend/location", default_engine_location)
|
||||
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._pause_slicing = False
|
||||
|
||||
# Workaround to disable layer view processing if layer view is not active.
|
||||
self._layer_view_active = False
|
||||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||
|
@ -80,23 +73,18 @@ class CuraEngineBackend(Backend):
|
|||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
# Triggers for when to (re)start slicing:
|
||||
self._global_container_stack = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
self._active_extruder_stack = None
|
||||
cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
self._onActiveExtruderChanged()
|
||||
|
||||
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
||||
# This timer will group them up, and only slice for the last setting changed signal.
|
||||
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(500)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self.slice)
|
||||
|
||||
# Listeners for receiving messages from the back-end.
|
||||
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
||||
self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
|
||||
|
@ -109,12 +97,16 @@ class CuraEngineBackend(Backend):
|
|||
self._start_slice_job = None
|
||||
self._slicing = False # Are we currently slicing?
|
||||
self._restart = False # Back-end is currently restarting?
|
||||
self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around.
|
||||
self._tool_active = False # If a tool is active, some tasks do not have to do anything
|
||||
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
|
||||
self._need_slicing = False
|
||||
self._engine_is_fresh = True # Is the newly started engine used before or not?
|
||||
|
||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
||||
self._error_message = None # Pop-up message that shows errors.
|
||||
self._last_num_objects = 0 # Count number of objects to see if there is something changed
|
||||
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
|
||||
|
||||
self.backendQuit.connect(self._onBackendQuit)
|
||||
self.backendConnected.connect(self._onBackendConnected)
|
||||
|
@ -125,9 +117,22 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
self._slice_start_time = None
|
||||
|
||||
## Called when closing the application.
|
||||
Preferences.getInstance().addPreference("general/auto_slice", True)
|
||||
|
||||
self._use_timer = False
|
||||
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
||||
# This timer will group them up, and only slice for the last setting changed signal.
|
||||
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.setInterval(500)
|
||||
self.determineAutoSlicing()
|
||||
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
## Terminate the engine process.
|
||||
#
|
||||
# This function should terminate the engine process.
|
||||
# Called when closing the application.
|
||||
def close(self):
|
||||
# Terminate CuraEngine if it is still running at this point
|
||||
self._terminate()
|
||||
|
@ -151,24 +156,12 @@ class CuraEngineBackend(Backend):
|
|||
## Emitted when the slicing process is aborted forcefully.
|
||||
slicingCancelled = Signal()
|
||||
|
||||
## Perform a slice of the scene.
|
||||
def slice(self):
|
||||
Logger.log("d", "Starting slice job...")
|
||||
if self._pause_slicing:
|
||||
return
|
||||
self._slice_start_time = time()
|
||||
if not self._enabled or not self._global_container_stack: # We shouldn't be slicing.
|
||||
# try again in a short time
|
||||
self._change_timer.start()
|
||||
return
|
||||
|
||||
self.printDurationMessage.emit(0, [0])
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
@pyqtSlot()
|
||||
def stopSlicing(self):
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
if self._slicing: # We were already slicing. Stop the old job.
|
||||
self._terminate()
|
||||
self._createSocket()
|
||||
|
||||
if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
|
||||
self._process_layers_job.abort()
|
||||
|
@ -177,6 +170,33 @@ class CuraEngineBackend(Backend):
|
|||
if self._error_message:
|
||||
self._error_message.hide()
|
||||
|
||||
## Manually triggers a reslice
|
||||
@pyqtSlot()
|
||||
def forceSlice(self):
|
||||
if self._use_timer:
|
||||
self._change_timer.start()
|
||||
else:
|
||||
self.slice()
|
||||
|
||||
## Perform a slice of the scene.
|
||||
def slice(self):
|
||||
self._slice_start_time = time()
|
||||
if not self._need_slicing:
|
||||
self.processingProgress.emit(1.0)
|
||||
self.backendStateChange.emit(BackendState.Done)
|
||||
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
|
||||
return
|
||||
|
||||
self.printDurationMessage.emit(0, [0])
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
|
||||
if self._process is None:
|
||||
self._createSocket()
|
||||
self.stopSlicing()
|
||||
self._engine_is_fresh = False # Yes we're going to use the engine
|
||||
|
||||
self.processingProgress.emit(0.0)
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
|
@ -189,21 +209,10 @@ class CuraEngineBackend(Backend):
|
|||
self._start_slice_job.start()
|
||||
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
|
||||
|
||||
|
||||
def pauseSlicing(self):
|
||||
self.close()
|
||||
self._pause_slicing = True
|
||||
self.backendStateChange.emit(BackendState.Disabled)
|
||||
|
||||
def continueSlicing(self):
|
||||
if self._pause_slicing:
|
||||
self._pause_slicing = False
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
## Terminate the engine process.
|
||||
# Start the engine process by calling _createSocket()
|
||||
def _terminate(self):
|
||||
self._slicing = False
|
||||
self._restart = True
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = []
|
||||
if self._start_slice_job is not None:
|
||||
|
@ -225,9 +234,6 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
|
||||
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
|
||||
else:
|
||||
# Process is none, but something did went wrong here. Try and re-create the socket
|
||||
self._createSocket()
|
||||
|
||||
## Event handler to call when the job to initiate the slicing process is
|
||||
# completed.
|
||||
|
@ -249,7 +255,7 @@ class CuraEngineBackend(Backend):
|
|||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
if Application.getInstance().platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is incompatible with the selected machine or configuration."))
|
||||
self._error_message.show()
|
||||
|
@ -259,7 +265,7 @@ class CuraEngineBackend(Backend):
|
|||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.SettingError:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
if Application.getInstance().platformActivity:
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
error_keys = []
|
||||
for extruder in extruders:
|
||||
|
@ -280,7 +286,7 @@ class CuraEngineBackend(Backend):
|
|||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
if Application.getInstance().platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
|
@ -288,7 +294,7 @@ class CuraEngineBackend(Backend):
|
|||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
if Application.getInstance().platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
|
@ -303,6 +309,33 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
|
||||
|
||||
## Determine enable or disable auto slicing. Return True for enable timer and False otherwise.
|
||||
# It disables when
|
||||
# - preference auto slice is off
|
||||
# - decorator isBlockSlicing is found (used in g-code reader)
|
||||
def determineAutoSlicing(self):
|
||||
enable_timer = True
|
||||
|
||||
if not Preferences.getInstance().getValue("general/auto_slice"):
|
||||
enable_timer = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if node.callDecoration("isBlockSlicing"):
|
||||
enable_timer = False
|
||||
self.backendStateChange.emit(BackendState.Disabled)
|
||||
gcode_list = node.callDecoration("getGCodeList")
|
||||
if gcode_list is not None:
|
||||
self._scene.gcode_list = gcode_list
|
||||
|
||||
if self._use_timer == enable_timer:
|
||||
return self._use_timer
|
||||
if enable_timer:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
self.enableTimer()
|
||||
return True
|
||||
else:
|
||||
self.disableTimer()
|
||||
return False
|
||||
|
||||
## Listener for when the scene has changed.
|
||||
#
|
||||
# This should start a slice if the scene is now ready to slice.
|
||||
|
@ -312,28 +345,33 @@ class CuraEngineBackend(Backend):
|
|||
if type(source) is not SceneNode:
|
||||
return
|
||||
|
||||
if source is self._scene.getRoot():
|
||||
return
|
||||
|
||||
should_pause = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if node.callDecoration("isBlockSlicing"):
|
||||
should_pause = True
|
||||
gcode_list = node.callDecoration("getGCodeList")
|
||||
if gcode_list is not None:
|
||||
self._scene.gcode_list = gcode_list
|
||||
|
||||
if should_pause:
|
||||
self.pauseSlicing()
|
||||
else:
|
||||
self.continueSlicing()
|
||||
|
||||
if source.getMeshData() is None:
|
||||
return
|
||||
|
||||
if source.getMeshData().getVertices() is None:
|
||||
root_scene_nodes_changed = False
|
||||
if source == self._scene.getRoot():
|
||||
num_objects = 0
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
# Only count sliceable objects
|
||||
if node.callDecoration("isSliceable"):
|
||||
num_objects += 1
|
||||
if num_objects != self._last_num_objects:
|
||||
self._last_num_objects = num_objects
|
||||
root_scene_nodes_changed = True
|
||||
else:
|
||||
return
|
||||
|
||||
if not source.callDecoration("isGroup") and not root_scene_nodes_changed:
|
||||
if source.getMeshData() is None:
|
||||
return
|
||||
if source.getMeshData().getVertices() is None:
|
||||
return
|
||||
|
||||
if self._tool_active:
|
||||
# do it later, each source only has to be done once
|
||||
if source not in self._postponed_scene_change_sources:
|
||||
self._postponed_scene_change_sources.append(source)
|
||||
return
|
||||
|
||||
self.needsSlicing()
|
||||
self.stopSlicing()
|
||||
self._onChanged()
|
||||
|
||||
## Called when an error occurs in the socket connection towards the engine.
|
||||
|
@ -348,16 +386,34 @@ class CuraEngineBackend(Backend):
|
|||
return
|
||||
|
||||
self._terminate()
|
||||
self._createSocket()
|
||||
|
||||
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
|
||||
Logger.log("w", "A socket error caused the connection to be reset")
|
||||
|
||||
## Remove old layer data (if any)
|
||||
def _clearLayerData(self):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if node.callDecoration("getLayerData"):
|
||||
node.getParent().removeChild(node)
|
||||
break
|
||||
|
||||
## Convenient function: set need_slicing, emit state and clear layer data
|
||||
def needsSlicing(self):
|
||||
self._need_slicing = True
|
||||
self.processingProgress.emit(0.0)
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
if not self._use_timer:
|
||||
# With manually having to slice, we want to clear the old invalid layer data.
|
||||
self._clearLayerData()
|
||||
|
||||
## A setting has changed, so check if we must reslice.
|
||||
#
|
||||
# \param instance The setting instance that has changed.
|
||||
# \param property The property of the setting instance that has changed.
|
||||
def _onSettingChanged(self, instance, property):
|
||||
if property == "value": # Only reslice if the value has changed.
|
||||
self.needsSlicing()
|
||||
self._onChanged()
|
||||
|
||||
## Called when a sliced layer data message is received from the engine.
|
||||
|
@ -397,6 +453,7 @@ class CuraEngineBackend(Backend):
|
|||
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
|
||||
|
||||
self._slicing = False
|
||||
self._need_slicing = False
|
||||
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
||||
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data)
|
||||
|
@ -430,22 +487,21 @@ class CuraEngineBackend(Backend):
|
|||
## Creates a new socket connection.
|
||||
def _createSocket(self):
|
||||
super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")))
|
||||
|
||||
## Manually triggers a reslice
|
||||
def forceSlice(self):
|
||||
self._change_timer.start()
|
||||
self._engine_is_fresh = True
|
||||
|
||||
## Called when anything has changed to the stuff that needs to be sliced.
|
||||
#
|
||||
# This indicates that we should probably re-slice soon.
|
||||
def _onChanged(self, *args, **kwargs):
|
||||
self._change_timer.start()
|
||||
self.needsSlicing()
|
||||
if self._use_timer:
|
||||
self._change_timer.start()
|
||||
|
||||
## Called when the back-end connects to the front-end.
|
||||
def _onBackendConnected(self):
|
||||
if self._restart:
|
||||
self._onChanged()
|
||||
self._restart = False
|
||||
self._onChanged()
|
||||
|
||||
## Called when the user starts using some tool.
|
||||
#
|
||||
|
@ -454,9 +510,12 @@ class CuraEngineBackend(Backend):
|
|||
#
|
||||
# \param tool The tool that the user is using.
|
||||
def _onToolOperationStarted(self, tool):
|
||||
self._enabled = False # Do not reslice when a tool is doing it's 'thing'
|
||||
self._terminate() # Do not continue slicing once a tool has started
|
||||
|
||||
self._tool_active = True # Do not react on scene change
|
||||
self.disableTimer()
|
||||
# Restart engine as soon as possible, we know we want to slice afterwards
|
||||
if not self._engine_is_fresh:
|
||||
self._terminate()
|
||||
self._createSocket()
|
||||
|
||||
## Called when the user stops using some tool.
|
||||
#
|
||||
|
@ -464,8 +523,13 @@ class CuraEngineBackend(Backend):
|
|||
#
|
||||
# \param tool The tool that the user was using.
|
||||
def _onToolOperationStopped(self, tool):
|
||||
self._enabled = True # Tool stop, start listening for changes again.
|
||||
|
||||
self._tool_active = False # React on scene change again
|
||||
self.determineAutoSlicing() # Switch timer on if appropriate
|
||||
# Process all the postponed scene changes
|
||||
while self._postponed_scene_change_sources:
|
||||
source = self._postponed_scene_change_sources.pop(0)
|
||||
self._onSceneChanged(source)
|
||||
|
||||
## Called when the user changes the active view mode.
|
||||
def _onActiveViewChanged(self):
|
||||
if Application.getInstance().getController().getActiveView():
|
||||
|
@ -490,7 +554,6 @@ class CuraEngineBackend(Backend):
|
|||
if self._process:
|
||||
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
||||
self._process = None
|
||||
self._createSocket()
|
||||
|
||||
## Called when the global container stack changes
|
||||
def _onGlobalStackChanged(self):
|
||||
|
@ -525,9 +588,34 @@ class CuraEngineBackend(Backend):
|
|||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.containersChanged.disconnect(self._onChanged)
|
||||
|
||||
self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.containersChanged.connect(self._onChanged)
|
||||
|
||||
def _onProcessLayersFinished(self, job):
|
||||
self._process_layers_job = None
|
||||
|
||||
## Connect slice function to timer.
|
||||
def enableTimer(self):
|
||||
if not self._use_timer:
|
||||
self._change_timer.timeout.connect(self.slice)
|
||||
self._use_timer = True
|
||||
|
||||
## Disconnect slice function from timer.
|
||||
# This means that slicing will not be triggered automatically
|
||||
def disableTimer(self):
|
||||
if self._use_timer:
|
||||
self._use_timer = False
|
||||
self._change_timer.timeout.disconnect(self.slice)
|
||||
|
||||
def _onPreferencesChanged(self, preference):
|
||||
if preference != "general/auto_slice":
|
||||
return
|
||||
auto_slice = self.determineAutoSlicing()
|
||||
if auto_slice:
|
||||
self._change_timer.start()
|
||||
|
||||
## Tickle the backend so in case of auto slicing, it starts the timer.
|
||||
def tickle(self):
|
||||
if self._use_timer:
|
||||
self._change_timer.start()
|
||||
|
|
|
@ -8,6 +8,8 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Application import Application
|
||||
from UM.Mesh.MeshData import MeshData
|
||||
from UM.Preferences import Preferences
|
||||
from UM.View.GL.OpenGLContext import OpenGLContext
|
||||
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -15,6 +17,7 @@ from UM.Logger import Logger
|
|||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura import LayerDataBuilder
|
||||
from cura import LayerDataDecorator
|
||||
from cura import LayerPolygon
|
||||
|
@ -24,6 +27,17 @@ from time import time
|
|||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Return a 4-tuple with floats 0-1 representing the html color code
|
||||
#
|
||||
# \param color_code html color code, i.e. "#FF0000" -> red
|
||||
def colorCodeToRGBA(color_code):
|
||||
return [
|
||||
int(color_code[1:3], 16) / 255,
|
||||
int(color_code[3:5], 16) / 255,
|
||||
int(color_code[5:7], 16) / 255,
|
||||
1.0]
|
||||
|
||||
|
||||
class ProcessSlicedLayersJob(Job):
|
||||
def __init__(self, layers):
|
||||
super().__init__()
|
||||
|
@ -92,7 +106,6 @@ class ProcessSlicedLayersJob(Job):
|
|||
layer_data.addLayer(abs_layer_number)
|
||||
this_layer = layer_data.getLayer(abs_layer_number)
|
||||
layer_data.setLayerHeight(abs_layer_number, layer.height)
|
||||
layer_data.setLayerThickness(abs_layer_number, layer.thickness)
|
||||
|
||||
for p in range(layer.repeatedMessageCount("path_segment")):
|
||||
polygon = layer.getRepeatedMessage("path_segment", p)
|
||||
|
@ -110,23 +123,28 @@ class ProcessSlicedLayersJob(Job):
|
|||
|
||||
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
|
||||
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
|
||||
|
||||
# In the future, line_thicknesses should be given by CuraEngine as well.
|
||||
# Currently the infill layer thickness also translates to line width
|
||||
line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
|
||||
line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter
|
||||
|
||||
# Create a new 3D-array, copy the 2D points over and insert the right height.
|
||||
# This uses manual array creation + copy rather than numpy.insert since this is
|
||||
# faster.
|
||||
new_points = numpy.empty((len(points), 3), numpy.float32)
|
||||
if polygon.point_type == 0: # Point2D
|
||||
new_points[:, 0] = points[:, 0]
|
||||
new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation
|
||||
new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation
|
||||
new_points[:, 2] = -points[:, 1]
|
||||
else: # Point3D
|
||||
new_points[:, 0] = points[:, 0]
|
||||
new_points[:, 1] = points[:, 2]
|
||||
new_points[:, 2] = -points[:, 1]
|
||||
|
||||
this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths)
|
||||
this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses)
|
||||
this_poly.buildCache()
|
||||
|
||||
|
||||
this_layer.polygons.append(this_poly)
|
||||
|
||||
Job.yieldThread()
|
||||
|
@ -144,7 +162,35 @@ class ProcessSlicedLayersJob(Job):
|
|||
self._progress.setProgress(progress)
|
||||
|
||||
# We are done processing all the layers we got from the engine, now create a mesh out of the data
|
||||
layer_mesh = layer_data.build()
|
||||
|
||||
# Find out colors per extruder
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
manager = ExtruderManager.getInstance()
|
||||
extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
|
||||
if extruders:
|
||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
||||
for extruder in extruders:
|
||||
material = extruder.findContainer({"type": "material"})
|
||||
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
|
||||
color_code = material.getMetaDataEntry("color_code")
|
||||
color = colorCodeToRGBA(color_code)
|
||||
material_color_map[position, :] = color
|
||||
else:
|
||||
# Single extruder via global stack.
|
||||
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
||||
material = global_container_stack.findContainer({"type": "material"})
|
||||
color_code = material.getMetaDataEntry("color_code")
|
||||
if color_code is None: # not all stacks have a material color
|
||||
color_code = "#e0e000"
|
||||
color = colorCodeToRGBA(color_code)
|
||||
material_color_map[0, :] = color
|
||||
|
||||
# We have to scale the colors for compatibility mode
|
||||
if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")):
|
||||
line_type_brightness = 0.5 # for compatibility mode
|
||||
else:
|
||||
line_type_brightness = 1.0
|
||||
layer_mesh = layer_data.build(material_color_map, line_type_brightness)
|
||||
|
||||
if self._abort_requested:
|
||||
if self._progress:
|
||||
|
|
|
@ -17,8 +17,7 @@ from UM.Settings.Validator import ValidatorState
|
|||
from UM.Settings.SettingRelation import RelationType
|
||||
|
||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||
|
||||
import cura.Settings
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
class StartJobResult(IntEnum):
|
||||
Finished = 1
|
||||
|
@ -85,7 +84,7 @@ class StartSliceJob(Job):
|
|||
self.setResult(StartJobResult.BuildPlateError)
|
||||
return
|
||||
|
||||
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
material = extruder_stack.findContainer({"type": "material"})
|
||||
if material:
|
||||
if material.getMetaDataEntry("compatible") == False:
|
||||
|
@ -150,7 +149,7 @@ class StartSliceJob(Job):
|
|||
self._buildGlobalSettingsMessage(stack)
|
||||
self._buildGlobalInheritsStackMessage(stack)
|
||||
|
||||
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
|
||||
for group in object_groups:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue