This commit is contained in:
Remco Burema 2020-10-20 12:36:41 +02:00
commit a8acdd01e4
No known key found for this signature in database
GPG key ID: 215C49431D43F98C
23 changed files with 147 additions and 57 deletions

View file

@ -4,6 +4,7 @@ from typing import List
from UM.Application import Application from UM.Application import Application
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -27,10 +28,14 @@ class ArrangeObjectsJob(Job):
title = i18n_catalog.i18nc("@info:title", "Finding Location")) title = i18n_catalog.i18nc("@info:title", "Finding Location"))
status_message.show() status_message.show()
found_solution_for_all = None
try:
found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes) found_solution_for_all = arrange(self._nodes, Application.getInstance().getBuildVolume(), self._fixed_nodes)
except: # If the thread crashes, the message should still close
Logger.logException("e", "Unable to arrange the objects on the buildplate. The arrange algorithm has crashed.")
status_message.hide() status_message.hide()
if not found_solution_for_all: if found_solution_for_all is not None and not found_solution_for_all:
no_full_solution_message = Message( no_full_solution_message = Message(
i18n_catalog.i18nc("@info:status", i18n_catalog.i18nc("@info:status",
"Unable to find a location within the build volume for all objects"), "Unable to find a location within the build volume for all objects"),

View file

@ -3,6 +3,7 @@ from pynest2d import Point, Box, Item, NfpConfig, nest
from typing import List, TYPE_CHECKING, Optional, Tuple from typing import List, TYPE_CHECKING, Optional, Tuple
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Math.Polygon import Polygon from UM.Math.Polygon import Polygon
from UM.Math.Quaternion import Quaternion from UM.Math.Quaternion import Quaternion
@ -44,6 +45,9 @@ def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildV
node_items = [] node_items = []
for node in nodes_to_arrange: for node in nodes_to_arrange:
hull_polygon = node.callDecoration("getConvexHull") hull_polygon = node.callDecoration("getConvexHull")
if not hull_polygon or hull_polygon.getPoints is None:
Logger.log("w", "Object {} cannot be arranged because it has no convex hull.".format(node.getName()))
continue
converted_points = [] converted_points = []
for point in hull_polygon.getPoints(): for point in hull_polygon.getPoints():
converted_points.append(Point(point[0] * factor, point[1] * factor)) converted_points.append(Point(point[0] * factor, point[1] * factor))

View file

@ -1,11 +1,11 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime from datetime import datetime
import json import json
import random import random
from hashlib import sha512 from hashlib import sha512
from base64 import b64encode from base64 import b64encode
from typing import Optional from typing import Optional, Any, Dict, Tuple
import requests import requests
@ -16,6 +16,7 @@ from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settin
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
class AuthorizationHelpers: class AuthorizationHelpers:
"""Class containing several helpers to deal with the authorization flow.""" """Class containing several helpers to deal with the authorization flow."""
@ -121,10 +122,13 @@ class AuthorizationHelpers:
if not user_data or not isinstance(user_data, dict): if not user_data or not isinstance(user_data, dict):
Logger.log("w", "Could not parse user data from token: %s", user_data) Logger.log("w", "Could not parse user data from token: %s", user_data)
return None return None
return UserProfile( return UserProfile(
user_id = user_data["user_id"], user_id = user_data["user_id"],
username = user_data["username"], username = user_data["username"],
profile_image_url = user_data.get("profile_image_url", "") profile_image_url = user_data.get("profile_image_url", ""),
organization_id = user_data.get("organization", {}).get("organization_id", ""),
subscriptions = user_data.get("subscriptions", [])
) )
@staticmethod @staticmethod

View file

@ -1,6 +1,6 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Dict, Any from typing import Optional, Dict, Any, List
class BaseModel: class BaseModel:
@ -27,6 +27,8 @@ class UserProfile(BaseModel):
user_id = None # type: Optional[str] user_id = None # type: Optional[str]
username = None # type: Optional[str] username = None # type: Optional[str]
profile_image_url = None # type: Optional[str] profile_image_url = None # type: Optional[str]
organization_id = None # type: Optional[str]
subscriptions = None # type: Optional[List[Dict[str, Any]]]
class AuthenticationResponse(BaseModel): class AuthenticationResponse(BaseModel):

View file

@ -998,6 +998,11 @@ class MachineManager(QObject):
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self.activeIntentChanged.emit() self.activeIntentChanged.emit()
# Force an update of resolve values
property_names = ["resolve", "validationState"]
for setting_key in self._global_container_stack.getAllKeys():
self._global_container_stack.propertiesChanged.emit(setting_key, property_names)
def _onMaterialNameChanged(self) -> None: def _onMaterialNameChanged(self) -> None:
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()

View file

@ -7,10 +7,9 @@ from cura.CuraApplication import CuraApplication
class UltimakerCloudScope(DefaultUserAgentScope): class UltimakerCloudScope(DefaultUserAgentScope):
"""Add an Authorization header to the request for Ultimaker Cloud Api requests. """
Add an Authorization header to the request for Ultimaker Cloud Api requests, if available.
When the user is not logged in or a token is not available, a warning will be logged Also add the user agent headers (see DefaultUserAgentScope).
Also add the user agent headers (see DefaultUserAgentScope)
""" """
def __init__(self, application: CuraApplication): def __init__(self, application: CuraApplication):
@ -22,7 +21,7 @@ class UltimakerCloudScope(DefaultUserAgentScope):
super().requestHook(request) super().requestHook(request)
token = self._account.accessToken token = self._account.accessToken
if not self._account.isLoggedIn or token is None: if not self._account.isLoggedIn or token is None:
Logger.warning("Cannot add authorization to Cloud Api request") Logger.debug("User is not logged in for Cloud API request to {url}".format(url = request.url().toDisplayString()))
return return
header_dict = { header_dict = {

View file

@ -19,6 +19,7 @@ from UM.Scene.SceneNode import SceneNode # For typing.
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
@ -108,6 +109,7 @@ class ThreeMFReader(MeshReader):
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.addDecorator(BuildPlateDecorator(active_build_plate))
um_node.addDecorator(ConvexHullDecorator())
um_node.setName(node_name) um_node.setName(node_name)
um_node.setId(node_id) um_node.setId(node_id)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())

View file

@ -82,7 +82,7 @@ class CuraEngineBackend(QObject, Backend):
default_engine_location = execpath default_engine_location = execpath
break break
self._application = CuraApplication.getInstance() #type: CuraApplication application = CuraApplication.getInstance() #type: CuraApplication
self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel] self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel]
self._machine_error_checker = None #type: Optional[MachineErrorChecker] self._machine_error_checker = None #type: Optional[MachineErrorChecker]
@ -92,7 +92,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("i", "Found CuraEngine at: %s", default_engine_location) Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
default_engine_location = os.path.abspath(default_engine_location) default_engine_location = os.path.abspath(default_engine_location)
self._application.getPreferences().addPreference("backend/location", default_engine_location) application.getPreferences().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active. # Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False #type: bool self._layer_view_active = False #type: bool
@ -101,7 +101,7 @@ class CuraEngineBackend(QObject, Backend):
self._stored_layer_data = [] # type: List[Arcus.PythonMessage] self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = self._application.getController().getScene() #type: Scene self._scene = application.getController().getScene() #type: Scene
self._scene.sceneChanged.connect(self._onSceneChanged) self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for auto-slicing. Auto-slicing is triggered as follows: # Triggers for auto-slicing. Auto-slicing is triggered as follows:
@ -141,7 +141,7 @@ class CuraEngineBackend(QObject, Backend):
self._slice_start_time = None #type: Optional[float] self._slice_start_time = None #type: Optional[float]
self._is_disabled = False #type: bool self._is_disabled = False #type: bool
self._application.getPreferences().addPreference("general/auto_slice", False) application.getPreferences().addPreference("general/auto_slice", False)
self._use_timer = False #type: bool self._use_timer = False #type: bool
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
@ -151,19 +151,20 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.setSingleShot(True) self._change_timer.setSingleShot(True)
self._change_timer.setInterval(500) self._change_timer.setInterval(500)
self.determineAutoSlicing() self.determineAutoSlicing()
self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged) application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._application.initializationFinished.connect(self.initialize) application.initializationFinished.connect(self.initialize)
def initialize(self) -> None: def initialize(self) -> None:
self._multi_build_plate_model = self._application.getMultiBuildPlateModel() application = CuraApplication.getInstance()
self._multi_build_plate_model = application.getMultiBuildPlateModel()
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged) application.getController().activeViewChanged.connect(self._onActiveViewChanged)
if self._multi_build_plate_model: if self._multi_build_plate_model:
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged) self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged) application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash # extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
@ -173,10 +174,10 @@ class CuraEngineBackend(QObject, Backend):
self.backendConnected.connect(self._onBackendConnected) self.backendConnected.connect(self._onBackendConnected)
# When a tool operation is in progress, don't slice. So we need to listen for tool operations. # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted) application.getController().toolOperationStarted.connect(self._onToolOperationStarted)
self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped) application.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self._machine_error_checker = self._application.getMachineErrorChecker() self._machine_error_checker = application.getMachineErrorChecker()
self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished) self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished)
def close(self) -> None: def close(self) -> None:
@ -195,7 +196,7 @@ class CuraEngineBackend(QObject, Backend):
This is useful for debugging and used to actually start the engine. This is useful for debugging and used to actually start the engine.
:return: list of commands and args / parameters. :return: list of commands and args / parameters.
""" """
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""] command = [CuraApplication.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""]
parser = argparse.ArgumentParser(prog = "cura", add_help = False) parser = argparse.ArgumentParser(prog = "cura", add_help = False)
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.") parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
@ -259,7 +260,8 @@ class CuraEngineBackend(QObject, Backend):
self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here. self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here.
# see if we really have to slice # see if we really have to slice
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate application = CuraApplication.getInstance()
active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0) 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) Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
num_objects = self._numObjectsPerBuildPlate() num_objects = self._numObjectsPerBuildPlate()
@ -274,8 +276,8 @@ class CuraEngineBackend(QObject, Backend):
self.slice() self.slice()
return return
self._stored_optimized_layer_data[build_plate_to_be_sliced] = [] self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate: if application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced) application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
if self._process is None: # type: ignore if self._process is None: # type: ignore
self._createSocket() self._createSocket()
@ -314,7 +316,7 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0) self.processingProgress.emit(0)
Logger.log("d", "Attempting to kill the engine process") Logger.log("d", "Attempting to kill the engine process")
if self._application.getUseExternalBackend(): if CuraApplication.getInstance().getUseExternalBackend():
return return
if self._process is not None: # type: ignore if self._process is not None: # type: ignore
@ -350,8 +352,9 @@ class CuraEngineBackend(QObject, Backend):
self.backendError.emit(job) self.backendError.emit(job)
return return
application = CuraApplication.getInstance()
if job.getResult() == StartJobResult.MaterialIncompatible: if job.getResult() == StartJobResult.MaterialIncompatible:
if self._application.platformActivity: if application.platformActivity:
self._error_message = Message(catalog.i18nc("@info:status", self._error_message = Message(catalog.i18nc("@info:status",
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice")) "Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show() self._error_message.show()
@ -362,7 +365,7 @@ class CuraEngineBackend(QObject, Backend):
return return
if job.getResult() == StartJobResult.SettingError: if job.getResult() == StartJobResult.SettingError:
if self._application.platformActivity: if application.platformActivity:
if not self._global_container_stack: if not self._global_container_stack:
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!") Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
return return
@ -394,7 +397,7 @@ class CuraEngineBackend(QObject, Backend):
elif job.getResult() == StartJobResult.ObjectSettingError: elif job.getResult() == StartJobResult.ObjectSettingError:
errors = {} errors = {}
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): for node in DepthFirstIterator(application.getController().getScene().getRoot()):
stack = node.callDecoration("getStack") stack = node.callDecoration("getStack")
if not stack: if not stack:
continue continue
@ -415,7 +418,7 @@ class CuraEngineBackend(QObject, Backend):
return return
if job.getResult() == StartJobResult.BuildPlateError: if job.getResult() == StartJobResult.BuildPlateError:
if self._application.platformActivity: if application.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 = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
title = catalog.i18nc("@info:title", "Unable to slice")) title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show() self._error_message.show()
@ -433,7 +436,7 @@ class CuraEngineBackend(QObject, Backend):
return return
if job.getResult() == StartJobResult.NothingToSlice: if job.getResult() == StartJobResult.NothingToSlice:
if self._application.platformActivity: if application.platformActivity:
self._error_message = Message(catalog.i18nc("@info:status", "Please review settings and check if your models:" self._error_message = Message(catalog.i18nc("@info:status", "Please review settings and check if your models:"
"\n- Fit within the build volume" "\n- Fit within the build volume"
"\n- Are assigned to an enabled extruder" "\n- Are assigned to an enabled extruder"
@ -466,7 +469,7 @@ class CuraEngineBackend(QObject, Backend):
enable_timer = True enable_timer = True
self._is_disabled = False self._is_disabled = False
if not self._application.getPreferences().getValue("general/auto_slice"): if not CuraApplication.getInstance().getPreferences().getValue("general/auto_slice"):
enable_timer = False enable_timer = False
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isBlockSlicing"): if node.callDecoration("isBlockSlicing"):
@ -560,7 +563,7 @@ class CuraEngineBackend(QObject, Backend):
:param error: The exception that occurred. :param error: The exception that occurred.
""" """
if self._application.isShuttingDown(): if CuraApplication.getInstance().isShuttingDown():
return return
super()._onSocketError(error) super()._onSocketError(error)
@ -600,7 +603,7 @@ class CuraEngineBackend(QObject, Backend):
cast(SceneNode, node.getParent()).removeChild(node) cast(SceneNode, node.getParent()).removeChild(node)
def markSliceAll(self) -> None: def markSliceAll(self) -> None:
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1): for build_plate_number in range(CuraApplication.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced: if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)
@ -696,12 +699,13 @@ class CuraEngineBackend(QObject, Backend):
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically. gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end. except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
gcode_list = [] gcode_list = []
application = CuraApplication.getInstance()
for index, line in enumerate(gcode_list): for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) replaced = line.replace("{print_time}", str(application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths)) replaced = replaced.replace("{filament_amount}", str(application.getPrintInformation().materialLengths))
replaced = replaced.replace("{filament_weight}", str(self._application.getPrintInformation().materialWeights)) replaced = replaced.replace("{filament_weight}", str(application.getPrintInformation().materialWeights))
replaced = replaced.replace("{filament_cost}", str(self._application.getPrintInformation().materialCosts)) replaced = replaced.replace("{filament_cost}", str(application.getPrintInformation().materialCosts))
replaced = replaced.replace("{jobname}", str(self._application.getPrintInformation().jobName)) replaced = replaced.replace("{jobname}", str(application.getPrintInformation().jobName))
gcode_list[index] = replaced gcode_list[index] = replaced
@ -711,7 +715,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate())) Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate()))
# See if we need to process the sliced layers job. # See if we need to process the sliced layers job.
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
if ( if (
self._layer_view_active and self._layer_view_active and
(self._process_layers_job is None or not self._process_layers_job.isRunning()) and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and
@ -870,9 +874,9 @@ class CuraEngineBackend(QObject, Backend):
def _onActiveViewChanged(self) -> None: def _onActiveViewChanged(self) -> None:
"""Called when the user changes the active view mode.""" """Called when the user changes the active view mode."""
view = self._application.getController().getActiveView() view = CuraApplication.getInstance().getController().getActiveView()
if view: if view:
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment
@ -909,7 +913,7 @@ class CuraEngineBackend(QObject, Backend):
extruder.propertyChanged.disconnect(self._onSettingChanged) extruder.propertyChanged.disconnect(self._onSettingChanged)
extruder.containersChanged.disconnect(self._onChanged) extruder.containersChanged.disconnect(self._onChanged)
self._global_container_stack = self._application.getMachineManager().activeMachine self._global_container_stack = CuraApplication.getInstance().getMachineManager().activeMachine
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed. self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.

View file

@ -7,6 +7,7 @@
# tries to create PyQt objects on a non-main thread. # tries to create PyQt objects on a non-main thread.
import Arcus # @UnusedImport import Arcus # @UnusedImport
import Savitar # @UnusedImport import Savitar # @UnusedImport
import pynest2d # @UnusedImport
from . import PostProcessingPlugin from . import PostProcessingPlugin

View file

@ -13,7 +13,7 @@ from ..PostProcessingPlugin import PostProcessingPlugin
# not sure if needed # not sure if needed
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
""" In this file, commnunity refers to regular Cura for makers.""" """ In this file, community refers to regular Cura for makers."""
mock_plugin_registry = MagicMock() mock_plugin_registry = MagicMock()
mock_plugin_registry.getPluginPath = MagicMock(return_value = "mocked_plugin_path") mock_plugin_registry.getPluginPath = MagicMock(return_value = "mocked_plugin_path")

View file

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json import json
@ -116,6 +116,7 @@ class SliceInfo(QObject, Extension):
machine_manager = self._application.getMachineManager() machine_manager = self._application.getMachineManager()
print_information = self._application.getPrintInformation() print_information = self._application.getPrintInformation()
user_profile = self._application.getCuraAPI().account.userProfile
global_stack = machine_manager.activeMachine global_stack = machine_manager.activeMachine
@ -124,6 +125,8 @@ class SliceInfo(QObject, Extension):
data["schema_version"] = 0 data["schema_version"] = 0
data["cura_version"] = self._application.getVersion() data["cura_version"] = self._application.getVersion()
data["cura_build_type"] = ApplicationMetadata.CuraBuildType data["cura_build_type"] = ApplicationMetadata.CuraBuildType
data["organization_id"] = user_profile.get("organization_id", None) if user_profile else None
data["subscriptions"] = user_profile.get("subscriptions", []) if user_profile else []
active_mode = self._application.getPreferences().getValue("cura/active_mode") active_mode = self._application.getPreferences().getValue("cura/active_mode")
if active_mode == 0: if active_mode == 0:

View file

@ -1,12 +1,17 @@
<html> <html>
<body> <body>
<b>Cura Version:</b> 4.0<br/> <b>Cura Version:</b> 4.8<br/>
<b>Operating System:</b> Windows 10<br/> <b>Operating System:</b> Windows 10<br/>
<b>Language:</b> en_US<br/> <b>Language:</b> en_US<br/>
<b>Machine Type:</b> Ultimaker S5<br/> <b>Machine Type:</b> Ultimaker S5<br/>
<b>Intent Profile:</b> Default<br/> <b>Intent Profile:</b> Default<br/>
<b>Quality Profile:</b> Fast<br/> <b>Quality Profile:</b> Fast<br/>
<b>Using Custom Settings:</b> No <b>Using Custom Settings:</b> No<br/>
<b>Organization ID (if any):</b> ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE=<br/>
<b>Subscriptions (if any):</b>
<ul>
<li><b>Level:</b> 10, <b>Type:</b> Enterprise, <b>Plan:</b> Basic</li>
</ul>
<h3>Extruder 1:</h3> <h3>Extruder 1:</h3>
<ul> <ul>

View file

@ -42,8 +42,7 @@ UM.Dialog
Row { Row {
id: packageRow id: packageRow
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right
height: childrenRect.height height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width
leftPadding: UM.Theme.getSize("narrow_margin").width leftPadding: UM.Theme.getSize("narrow_margin").width

View file

@ -140,8 +140,7 @@ class CloudPackageChecker(QObject):
sync_message = Message(self._i18n_catalog.i18nc( sync_message = Message(self._i18n_catalog.i18nc(
"@info:generic", "@info:generic",
"Do you want to sync material and software packages with your account?"), "Do you want to sync material and software packages with your account?"),
title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ), title = self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
lifetime = 0)
sync_message.addAction("sync", sync_message.addAction("sync",
name = self._i18n_catalog.i18nc("@action:button", "Sync"), name = self._i18n_catalog.i18nc("@action:button", "Sync"),
icon = "", icon = "",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,014 KiB

After

Width:  |  Height:  |  Size: 899 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 KiB

After

Width:  |  Height:  |  Size: 682 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

After

Width:  |  Height:  |  Size: 430 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

View file

@ -81,6 +81,8 @@ class SendMaterialJob(Job):
container_registry = CuraApplication.getInstance().getContainerRegistry() container_registry = CuraApplication.getInstance().getContainerRegistry()
all_materials = container_registry.findInstanceContainersMetadata(type = "material") all_materials = container_registry.findInstanceContainersMetadata(type = "material")
all_base_files = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material). all_base_files = {material["base_file"] for material in all_materials if "base_file" in material} # Filters out uniques by making it a set. Don't include files without base file (i.e. empty material).
if "empty_material" in all_base_files:
all_base_files.remove("empty_material") # Don't send the empty material.
for root_material_id in all_base_files: for root_material_id in all_base_files:
if root_material_id not in materials_to_send: if root_material_id not in materials_to_send:

View file

@ -1,8 +1,9 @@
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import Arcus and Savitar first! # Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import custom Sip bindings first!
import Savitar # Dont remove this line import Savitar # Dont remove this line
import Arcus # No really. Don't. It needs to be there! import Arcus # No really. Don't. It needs to be there!
import pynest2d # Really!
from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used. from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used.
import pytest import pytest

View file

@ -231,7 +231,7 @@ Item
target: enabledCheckbox target: enabledCheckbox
property: "checked" property: "checked"
value: Cura.MachineManager.activeStack.isEnabled value: Cura.MachineManager.activeStack.isEnabled
when: Cura.MachineManger.activeStack != null when: Cura.MachineManager.activeStack != null
} }
/* Use a MouseArea to process the click on this checkbox. /* Use a MouseArea to process the click on this checkbox.

View file

@ -1,3 +1,57 @@
[4.8.0]
* (NOTE: Draft release notes for Beta, these may change for final.)
* New arrange algorithm!
Shoutout to Prusa, since they made the libnest2d library for this, and allowed a licence change.
* When opening a project file, pick any matching printer in addition to just exact match and new definition.
Previously, when someone sent you a project, you either had to have the exact same printer under the exact same name, or create an entirely new instance. Now, in the open project dialog, you can specify any printer that has a(n exactly) matching printer-type.
* Show warning message on profiles that where successfully imported, but not supported by the currently active configuration.
People where a bit confused when adding profiles, which then didn't show up. With this new version, when you add a profile that isn't supported by the current instance (but otherwise correctly imported), you get a warning-message.
* Show parts of the model below the buildplate in a different color.
When viewing the buildplate from below, there's now shadow visible anymore. As this helped the user determine what part of the model was below the buildplate, we decided to color that part differently instead.
* Show the familiar striped pattern for objects outside of the build-volume in Preview mode as well.
Models outside of the build-volume can of course not be sliced. In the Prepare mode, this was already visible with solid objects indicated in the familiar grey-yellow striped pattern. Now you can also see the objects that are still in the scene just outside if the build-volume in Preview mode.
* Iron the top-most bottom layer when spiralizing a solid model, contributed by smartavionics
Ironing was only used for top-layers, or every layer. But what is the biggest flat surface in a vase? This helpful pull request made it so that, in this case, the top-most bottom layer is used to iron on.
* Allow scrolling through setting-tooltips, useful for some plugins.
Certain plugins, such as the very useful Settings Guide, occasionally have very large tooltips. This update allows you to scroll through those.
* Bug Fixes
- Fix the simplify algorithm. Again.
- Fix percentage text-fields when scaling non-uniformly.
- Fix cloud printer stuck in connect/disconnect loop.
- Fix rare crash when processing stair stepping in support.
- Fix sudden increase in tree support branch diameter.
- Fix cases of tree-support resting against vertical wall.
- Fix conical support missing on printers with 'origin at center' set.
- Fix infill multiplier and connected lines settings not cooperating with each other.
- Fixed an issue with skin-edge support, contributed by smartavionics
- Fix printer renaming didn't always stick after restart.
- Fix move after retraction not changing speed if it's a factor 60 greater.
- Fix Windows file alteration detection (reload file popup message appears again).
- OBJ-file reader now doesn't get confused by legal negative indices.
- Fix off-by-one error that could cause horizontal faces to shift one layer upwards.
- Fix out of bounds array and lost checks for segments ended with mesh vertices, contributed bt skarasov
- Remove redundant 'successful responses' variable, contributed by aerotog
* Printer definitions and profiles
- Artillery Sidewinder X1, Artillery Sidewinder Genius, contributed by cataclism
- AnyCubic Kossel, contributed by FoxExe
- BIQU B1, contributed by looxonline
- BLV mgn Cube 300, contributed by wolfgangmauer
- Cocoon Create, Cocoon Create Touch, contributed by thushan
- Creality CR-6 SE, contributed by MatthieuMH
- Flying Bear Ghost 5, contributed by oducceu
- Fused Form 3D (FF300, FF600, FF600+, FFmini), contributed by FusedForm
- Add Acetate profiles for Strateo3D, contributed by KOUBeMT
[4.7.1] [4.7.1]
For an overview of the new features in Cura 4.7, please see this video: <a href="https://www.youtube.com/watch?v=vuKuil0dJqE">Change log overview</a> For an overview of the new features in Cura 4.7, please see this video: <a href="https://www.youtube.com/watch?v=vuKuil0dJqE">Change log overview</a>

View file

@ -6,9 +6,10 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import Arcus and Savitar first! # Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import custom Sip bindings first!
import Savitar # Dont remove this line import Savitar # Dont remove this line
import Arcus # No really. Don't. It needs to be there! import Arcus # No really. Don't. It needs to be there!
import pynest2d # Really!
from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used. from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used.
# Even though your IDE says these files are not used, don't believe it. It's lying. They need to be there. # Even though your IDE says these files are not used, don't believe it. It's lying. They need to be there.