mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-25 07:33:57 -06:00
Merge branch 'master' into infill_mesh_shell_defaults
This commit is contained in:
commit
53e1742d27
341 changed files with 77506 additions and 69425 deletions
1
.github/workflows/cicd.yml
vendored
1
.github/workflows/cicd.yml
vendored
|
@ -6,6 +6,7 @@ on:
|
||||||
- master
|
- master
|
||||||
- 'WIP**'
|
- 'WIP**'
|
||||||
- '4.*'
|
- '4.*'
|
||||||
|
- 'CURA-*'
|
||||||
pull_request:
|
pull_request:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
116
.pylintrc
Normal file
116
.pylintrc
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# This file contains the Pylint rules used in the stardust projects.
|
||||||
|
|
||||||
|
# To configure PyLint as an external tool in PyCharm, create a new External Tool with the settings:
|
||||||
|
#
|
||||||
|
# Name: PyLint
|
||||||
|
# Program: Check with 'which pylint'. For example: ~/.local/bin/pylint
|
||||||
|
# Arguments: $FileDirName$ --rcfile=.pylintrc --msg-template='{abspath}:{line}:{column}:({symbol}):{msg_id}:{msg}'
|
||||||
|
# Working directory: $ContentRoot$
|
||||||
|
# Output filters: $FILE_PATH$:$LINE$:$COLUMN$:.*
|
||||||
|
#
|
||||||
|
# You can add a keyboard shortcut in the keymap settings. To run Pylint to a project, select the module
|
||||||
|
# you want to check (e.g. cura folder) before running the external tool.
|
||||||
|
#
|
||||||
|
# If you find a better way to configure the external tool please edit this file.
|
||||||
|
|
||||||
|
[MASTER]
|
||||||
|
# List of plugins (as comma separated values of python modules names) to load,
|
||||||
|
# usually to register additional checkers.
|
||||||
|
load-plugins=pylint_quotes
|
||||||
|
|
||||||
|
# We expect double string quotes
|
||||||
|
string-quote=double-avoid-escape
|
||||||
|
|
||||||
|
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||||
|
# user-friendly hints instead of false-positive error messages.
|
||||||
|
suggestion-mode=yes
|
||||||
|
|
||||||
|
# Add files or directories to the blacklist. They should be base names, not paths.
|
||||||
|
ignore=tests
|
||||||
|
|
||||||
|
[REFACTORING]
|
||||||
|
# Maximum number of nested blocks for function / method body
|
||||||
|
max-nested-blocks=5
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
# C0326: No space allowed around keyword argument assignment
|
||||||
|
# C0411: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||||
|
# C0412: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||||
|
# C0413: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||||
|
# R0201: Method could be a function (no-self-use)
|
||||||
|
# R0401: Cyclic imports (cyclic-import) are used for typing
|
||||||
|
# R0801: Unfortunately the error is triggered for a lot of similar models (duplicate-code)
|
||||||
|
# R1710: Either all return statements in a function should return an expression, or none of them should.
|
||||||
|
# W0221: Parameters differ from overridden method (tornado http methods have a flexible number of parameters)
|
||||||
|
# W0511: Ignore warnings generated for TODOs in the code
|
||||||
|
# C0111: We don't use docstring
|
||||||
|
# C0303: Trailing whitespace isn't something we care about
|
||||||
|
# C4001: You can put " in a string if you escape it first...
|
||||||
|
disable=C0326,C0411,C0412,C0413,R0201,R0401,R0801,R1710,W0221,W0511, C0111, C0303,C4001
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
# Maximum number of characters on a single line.
|
||||||
|
max-line-length=120
|
||||||
|
|
||||||
|
# Maximum number of lines in a module.
|
||||||
|
max-module-lines=500
|
||||||
|
|
||||||
|
good-names=os
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
# allow modules and functions to use PascalCase
|
||||||
|
module-rgx=[a-zA-Z0-9_]+$
|
||||||
|
function-rgx=
|
||||||
|
## Allowed methods:
|
||||||
|
# getSomething
|
||||||
|
# _getSomething
|
||||||
|
# __getSomething
|
||||||
|
# __new__
|
||||||
|
## Disallowed:
|
||||||
|
# _GET
|
||||||
|
# GetSomething
|
||||||
|
method-rgx=(_{,2}[a-z][A-Za-z0-9]*_{,2})$
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
# Maximum number of arguments for function / method.
|
||||||
|
max-args=7
|
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902).
|
||||||
|
max-attributes=8
|
||||||
|
|
||||||
|
# Maximum number of boolean expressions in an if statement.
|
||||||
|
max-bool-expr=5
|
||||||
|
|
||||||
|
# Maximum number of branch for function / method body.
|
||||||
|
max-branches=12
|
||||||
|
|
||||||
|
# Maximum number of locals for function / method body.
|
||||||
|
max-locals=15
|
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901).
|
||||||
|
max-parents=7
|
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904).
|
||||||
|
max-public-methods=20
|
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body.
|
||||||
|
max-returns=6
|
||||||
|
|
||||||
|
# Maximum number of statements in function / method body.
|
||||||
|
max-statements=50
|
||||||
|
|
||||||
|
# Minimum number of public methods for a class (R0903).
|
||||||
|
# We set this to 0 because our models and fields do not have methods.
|
||||||
|
min-public-methods=0
|
||||||
|
|
||||||
|
ignored-argument-names=arg|args|kwargs|_
|
||||||
|
|
||||||
|
[CLASSES]
|
||||||
|
defining-attr-methods=__init__,__new__,setUp,initialize
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
ignored-classes=NotImplemented
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
dummy-variables-rgx=_+[a-z0-9_]{2,30}
|
|
@ -23,6 +23,7 @@ set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
||||||
set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
|
set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
|
||||||
set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
|
set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
|
||||||
set(CURA_CLOUD_ACCOUNT_API_ROOT "" CACHE STRING "Alternative Cura cloud account API version")
|
set(CURA_CLOUD_ACCOUNT_API_ROOT "" CACHE STRING "Alternative Cura cloud account API version")
|
||||||
|
set(CURA_MARKETPLACE_ROOT "" CACHE STRING "Alternative Marketplace location")
|
||||||
|
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,13 @@ function(cura_add_test)
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
#Add test for import statements which are not compatible with all builds
|
||||||
|
add_test(
|
||||||
|
NAME "invalid-imports"
|
||||||
|
COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
|
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
|
||||||
|
|
||||||
file(GLOB_RECURSE _plugins plugins/*/__init__.py)
|
file(GLOB_RECURSE _plugins plugins/*/__init__.py)
|
||||||
|
|
|
@ -28,11 +28,12 @@ class CuraAPI(QObject):
|
||||||
# The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
|
# The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
|
||||||
# Since the API is intended to be used by plugins, the cura application should have already created this.
|
# Since the API is intended to be used by plugins, the cura application should have already created this.
|
||||||
def __new__(cls, application: Optional["CuraApplication"] = None):
|
def __new__(cls, application: Optional["CuraApplication"] = None):
|
||||||
if cls.__instance is None:
|
if cls.__instance is not None:
|
||||||
if application is None:
|
raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
|
||||||
raise Exception("Upon first time creation, the application must be set.")
|
if application is None:
|
||||||
cls.__instance = super(CuraAPI, cls).__new__(cls)
|
raise RuntimeError("Upon first time creation, the application must be set.")
|
||||||
cls._application = application
|
cls.__instance = super(CuraAPI, cls).__new__(cls)
|
||||||
|
cls._application = application
|
||||||
return cls.__instance
|
return cls.__instance
|
||||||
|
|
||||||
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
|
def __init__(self, application: Optional["CuraApplication"] = None) -> None:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
# ---------
|
# ---------
|
||||||
|
@ -13,7 +13,7 @@ DEFAULT_CURA_DEBUG_MODE = False
|
||||||
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
||||||
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
||||||
# CuraVersion.py.in template.
|
# CuraVersion.py.in template.
|
||||||
CuraSDKVersion = "7.0.0"
|
CuraSDKVersion = "7.1.0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraAppName # type: ignore
|
from cura.CuraVersion import CuraAppName # type: ignore
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 List
|
from typing import List, Optional
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -8,6 +8,7 @@ from UM.Math.Polygon import Polygon
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from cura.Arranging.ShapeArray import ShapeArray
|
from cura.Arranging.ShapeArray import ShapeArray
|
||||||
|
from cura.BuildVolume import BuildVolume
|
||||||
from cura.Scene import ZOffsetDecorator
|
from cura.Scene import ZOffsetDecorator
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
@ -27,7 +28,7 @@ LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points
|
||||||
#
|
#
|
||||||
# Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
|
# Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
|
||||||
class Arrange:
|
class Arrange:
|
||||||
build_volume = None
|
build_volume = None # type: Optional[BuildVolume]
|
||||||
|
|
||||||
def __init__(self, x, y, offset_x, offset_y, scale= 0.5):
|
def __init__(self, x, y, offset_x, offset_y, scale= 0.5):
|
||||||
self._scale = scale # convert input coordinates to arrange coordinates
|
self._scale = scale # convert input coordinates to arrange coordinates
|
||||||
|
@ -68,7 +69,7 @@ class Arrange:
|
||||||
points = copy.deepcopy(vertices._points)
|
points = copy.deepcopy(vertices._points)
|
||||||
|
|
||||||
# After scaling (like up to 0.1 mm) the node might not have points
|
# After scaling (like up to 0.1 mm) the node might not have points
|
||||||
if len(points) == 0:
|
if not points.size:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
||||||
|
@ -113,7 +114,7 @@ class Arrange:
|
||||||
found_spot = True
|
found_spot = True
|
||||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Could not find spot!"),
|
Logger.log("d", "Could not find spot!")
|
||||||
found_spot = False
|
found_spot = False
|
||||||
node.setPosition(Vector(200, center_y, 100))
|
node.setPosition(Vector(200, center_y, 100))
|
||||||
return found_spot
|
return found_spot
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ArrangeArray:
|
||||||
self._has_empty = False
|
self._has_empty = False
|
||||||
self._arrange = [] # type: List[Arrange]
|
self._arrange = [] # type: List[Arrange]
|
||||||
|
|
||||||
def _update_first_empty(self):
|
def _updateFirstEmpty(self):
|
||||||
for i, a in enumerate(self._arrange):
|
for i, a in enumerate(self._arrange):
|
||||||
if a.isEmpty:
|
if a.isEmpty:
|
||||||
self._first_empty = i
|
self._first_empty = i
|
||||||
|
@ -42,7 +42,7 @@ class ArrangeArray:
|
||||||
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
|
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
|
||||||
self._arrange.append(new_arrange)
|
self._arrange.append(new_arrange)
|
||||||
self._count += 1
|
self._count += 1
|
||||||
self._update_first_empty()
|
self._updateFirstEmpty()
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return self._count
|
return self._count
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
|
||||||
class AutoSave:
|
class AutoSave:
|
||||||
def __init__(self, application):
|
def __init__(self, application: "CuraApplication") -> None:
|
||||||
self._application = application
|
self._application = application
|
||||||
self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)
|
self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)
|
||||||
|
|
||||||
|
@ -22,14 +26,14 @@ class AutoSave:
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
self._saving = False
|
self._saving = False
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
# only initialise if the application is created and has started
|
# only initialise if the application is created and has started
|
||||||
self._change_timer.timeout.connect(self._onTimeout)
|
self._change_timer.timeout.connect(self._onTimeout)
|
||||||
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||||
self._onGlobalStackChanged()
|
self._onGlobalStackChanged()
|
||||||
self._triggerTimer()
|
self._triggerTimer()
|
||||||
|
|
||||||
def _triggerTimer(self, *args):
|
def _triggerTimer(self, *args: Any) -> None:
|
||||||
if not self._saving:
|
if not self._saving:
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
|
|
||||||
|
@ -40,7 +44,7 @@ class AutoSave:
|
||||||
else:
|
else:
|
||||||
self._change_timer.stop()
|
self._change_timer.stop()
|
||||||
|
|
||||||
def _onGlobalStackChanged(self):
|
def _onGlobalStackChanged(self) -> None:
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
||||||
self._global_stack.containersChanged.disconnect(self._triggerTimer)
|
self._global_stack.containersChanged.disconnect(self._triggerTimer)
|
||||||
|
@ -51,7 +55,7 @@ class AutoSave:
|
||||||
self._global_stack.propertyChanged.connect(self._triggerTimer)
|
self._global_stack.propertyChanged.connect(self._triggerTimer)
|
||||||
self._global_stack.containersChanged.connect(self._triggerTimer)
|
self._global_stack.containersChanged.connect(self._triggerTimer)
|
||||||
|
|
||||||
def _onTimeout(self):
|
def _onTimeout(self) -> None:
|
||||||
self._saving = True # To prevent the save process from triggering another autosave.
|
self._saving = True # To prevent the save process from triggering another autosave.
|
||||||
Logger.log("d", "Autosaving preferences, instances and profiles")
|
Logger.log("d", "Autosaving preferences, instances and profiles")
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,14 @@ class Backup:
|
||||||
# \return Whether we had success or not.
|
# \return Whether we had success or not.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
|
def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
|
||||||
|
|
||||||
|
# Implement security recommendations: Sanity check on zip files will make it harder to spoof.
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
config_filename = CuraApplication.getInstance().getApplicationName() + ".cfg" # Should be there if valid.
|
||||||
|
if config_filename not in [file.filename for file in archive.filelist]:
|
||||||
|
Logger.logException("e", "Unable to extract the backup due to corruption of compressed file(s).")
|
||||||
|
return False
|
||||||
|
|
||||||
Logger.log("d", "Removing current data in location: %s", target_path)
|
Logger.log("d", "Removing current data in location: %s", target_path)
|
||||||
Resources.factoryReset()
|
Resources.factoryReset()
|
||||||
Logger.log("d", "Extracting backup to location: %s", target_path)
|
Logger.log("d", "Extracting backup to location: %s", target_path)
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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 numpy
|
||||||
|
import math
|
||||||
|
|
||||||
|
from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
|
||||||
|
|
||||||
from UM.Mesh.MeshData import MeshData
|
from UM.Mesh.MeshData import MeshData
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
from UM.Application import Application #To modify the maximum zoom level.
|
from UM.Application import Application #To modify the maximum zoom level.
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Scene.Platform import Platform
|
from UM.Scene.Platform import Platform
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Math.Matrix import Matrix
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Color import Color
|
from UM.Math.Color import Color
|
||||||
|
@ -17,23 +23,23 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Signal import Signal
|
from UM.Signal import Signal
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
from UM.View.RenderBatch import RenderBatch
|
from UM.View.RenderBatch import RenderBatch
|
||||||
from UM.View.GL.OpenGL import OpenGL
|
from UM.View.GL.OpenGL import OpenGL
|
||||||
|
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
from PyQt5.QtCore import QTimer
|
||||||
|
|
||||||
import numpy
|
|
||||||
import math
|
|
||||||
|
|
||||||
from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
# Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
|
# Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
|
||||||
PRIME_CLEARANCE = 6.5
|
PRIME_CLEARANCE = 6.5
|
||||||
|
|
||||||
|
@ -1012,13 +1018,13 @@ class BuildVolume(SceneNode):
|
||||||
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
|
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
|
||||||
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
||||||
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
|
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
|
||||||
if not setting_value and (setting_type == "int" or setting_type == "float"):
|
if not setting_value and setting_type in ["int", "float"]:
|
||||||
all_values[i] = 0
|
all_values[i] = 0
|
||||||
return all_values
|
return all_values
|
||||||
|
|
||||||
def _calculateBedAdhesionSize(self, used_extruders):
|
def _calculateBedAdhesionSize(self, used_extruders):
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return None
|
||||||
|
|
||||||
container_stack = self._global_container_stack
|
container_stack = self._global_container_stack
|
||||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||||
|
|
|
@ -12,9 +12,13 @@ import json
|
||||||
import locale
|
import locale
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from sentry_sdk.hub import Hub
|
try:
|
||||||
from sentry_sdk.utils import event_from_exception
|
from sentry_sdk.hub import Hub
|
||||||
from sentry_sdk import configure_scope
|
from sentry_sdk.utils import event_from_exception
|
||||||
|
from sentry_sdk import configure_scope
|
||||||
|
with_sentry_sdk = True
|
||||||
|
except ImportError:
|
||||||
|
with_sentry_sdk = False
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||||
|
@ -54,6 +58,8 @@ class CrashHandler:
|
||||||
self.traceback = tb
|
self.traceback = tb
|
||||||
self.has_started = has_started
|
self.has_started = has_started
|
||||||
self.dialog = None # Don't create a QDialog before there is a QApplication
|
self.dialog = None # Don't create a QDialog before there is a QApplication
|
||||||
|
self.cura_version = None
|
||||||
|
self.cura_locale = None
|
||||||
|
|
||||||
Logger.log("c", "An uncaught error has occurred!")
|
Logger.log("c", "An uncaught error has occurred!")
|
||||||
for line in traceback.format_exception(exception_type, value, tb):
|
for line in traceback.format_exception(exception_type, value, tb):
|
||||||
|
@ -66,8 +72,9 @@ class CrashHandler:
|
||||||
if has_started and exception_type in skip_exception_types:
|
if has_started and exception_type in skip_exception_types:
|
||||||
return
|
return
|
||||||
|
|
||||||
with configure_scope() as scope:
|
if with_sentry_sdk:
|
||||||
scope.set_tag("during_startup", not has_started)
|
with configure_scope() as scope:
|
||||||
|
scope.set_tag("during_startup", not has_started)
|
||||||
|
|
||||||
if not has_started:
|
if not has_started:
|
||||||
self._send_report_checkbox = None
|
self._send_report_checkbox = None
|
||||||
|
@ -203,16 +210,17 @@ class CrashHandler:
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
group.setLayout(layout)
|
group.setLayout(layout)
|
||||||
|
|
||||||
with configure_scope() as scope:
|
if with_sentry_sdk:
|
||||||
scope.set_tag("qt_version", QT_VERSION_STR)
|
with configure_scope() as scope:
|
||||||
scope.set_tag("pyqt_version", PYQT_VERSION_STR)
|
scope.set_tag("qt_version", QT_VERSION_STR)
|
||||||
scope.set_tag("os", platform.system())
|
scope.set_tag("pyqt_version", PYQT_VERSION_STR)
|
||||||
scope.set_tag("os_version", platform.version())
|
scope.set_tag("os", platform.system())
|
||||||
scope.set_tag("locale_os", self.data["locale_os"])
|
scope.set_tag("os_version", platform.version())
|
||||||
scope.set_tag("locale_cura", self.cura_locale)
|
scope.set_tag("locale_os", self.data["locale_os"])
|
||||||
scope.set_tag("is_enterprise", ApplicationMetadata.IsEnterpriseVersion)
|
scope.set_tag("locale_cura", self.cura_locale)
|
||||||
|
scope.set_tag("is_enterprise", ApplicationMetadata.IsEnterpriseVersion)
|
||||||
scope.set_user({"id": str(uuid.getnode())})
|
|
||||||
|
scope.set_user({"id": str(uuid.getnode())})
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
@ -247,12 +255,13 @@ class CrashHandler:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with configure_scope() as scope:
|
if with_sentry_sdk:
|
||||||
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
|
with configure_scope() as scope:
|
||||||
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
|
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
|
||||||
scope.set_tag("gpu_type", opengl_instance.getGPUType())
|
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
|
||||||
scope.set_tag("active_machine", active_machine_definition_id)
|
scope.set_tag("gpu_type", opengl_instance.getGPUType())
|
||||||
scope.set_tag("active_machine_manufacturer", active_machine_manufacturer)
|
scope.set_tag("active_machine", active_machine_definition_id)
|
||||||
|
scope.set_tag("active_machine_manufacturer", active_machine_manufacturer)
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
@ -335,9 +344,10 @@ class CrashHandler:
|
||||||
"module_name": module_name, "version": module_version, "is_plugin": isPlugin}
|
"module_name": module_name, "version": module_version, "is_plugin": isPlugin}
|
||||||
self.data["exception"] = exception_dict
|
self.data["exception"] = exception_dict
|
||||||
|
|
||||||
with configure_scope() as scope:
|
if with_sentry_sdk:
|
||||||
scope.set_tag("is_plugin", isPlugin)
|
with configure_scope() as scope:
|
||||||
scope.set_tag("module", module_name)
|
scope.set_tag("is_plugin", isPlugin)
|
||||||
|
scope.set_tag("module", module_name)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
@ -396,15 +406,24 @@ class CrashHandler:
|
||||||
# Before sending data, the user comments are stored
|
# Before sending data, the user comments are stored
|
||||||
self.data["user_info"] = self.user_description_text_area.toPlainText()
|
self.data["user_info"] = self.user_description_text_area.toPlainText()
|
||||||
|
|
||||||
try:
|
if with_sentry_sdk:
|
||||||
hub = Hub.current
|
try:
|
||||||
event, hint = event_from_exception((self.exception_type, self.value, self.traceback))
|
hub = Hub.current
|
||||||
hub.capture_event(event, hint=hint)
|
event, hint = event_from_exception((self.exception_type, self.value, self.traceback))
|
||||||
hub.flush()
|
hub.capture_event(event, hint=hint)
|
||||||
except Exception as e: # We don't want any exception to cause problems
|
hub.flush()
|
||||||
Logger.logException("e", "An exception occurred while trying to send crash report")
|
except Exception as e: # We don't want any exception to cause problems
|
||||||
|
Logger.logException("e", "An exception occurred while trying to send crash report")
|
||||||
|
if not self.has_started:
|
||||||
|
print("An exception occurred while trying to send crash report: %s" % e)
|
||||||
|
else:
|
||||||
|
msg = "SentrySDK is not available and the report could not be sent."
|
||||||
|
Logger.logException("e", msg)
|
||||||
if not self.has_started:
|
if not self.has_started:
|
||||||
print("An exception occurred while trying to send crash report: %s" % e)
|
print(msg)
|
||||||
|
print("Exception type: {}".format(self.exception_type))
|
||||||
|
print("Value: {}".format(self.value))
|
||||||
|
print("Traceback: {}".format(self.traceback))
|
||||||
|
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,15 @@
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl
|
from PyQt5.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from typing import List, Optional, cast
|
from typing import List, cast
|
||||||
|
|
||||||
from UM.Event import CallFunctionEvent
|
from UM.Event import CallFunctionEvent
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Math.Quaternion import Quaternion
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
from UM.Operations.RotateOperation import RotateOperation
|
|
||||||
from UM.Operations.TranslateOperation import TranslateOperation
|
from UM.Operations.TranslateOperation import TranslateOperation
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# 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.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List
|
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qm
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Decorators import override, deprecated
|
from UM.Decorators import override
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
@ -130,6 +130,8 @@ from . import CameraAnimation
|
||||||
from . import CuraActions
|
from . import CuraActions
|
||||||
from . import PrintJobPreviewImageProvider
|
from . import PrintJobPreviewImageProvider
|
||||||
|
|
||||||
|
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||||
|
|
||||||
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
@ -189,9 +191,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
|
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
|
||||||
|
|
||||||
self._cura_package_manager = None
|
self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager]
|
||||||
|
|
||||||
self._machine_action_manager = None
|
|
||||||
|
|
||||||
self.empty_container = None # type: EmptyInstanceContainer
|
self.empty_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||||
|
@ -264,7 +264,6 @@ class CuraApplication(QtApplication):
|
||||||
# Backups
|
# Backups
|
||||||
self._auto_save = None # type: Optional[AutoSave]
|
self._auto_save = None # type: Optional[AutoSave]
|
||||||
|
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
|
||||||
self._container_registry_class = CuraContainerRegistry
|
self._container_registry_class = CuraContainerRegistry
|
||||||
# Redefined here in order to please the typing.
|
# Redefined here in order to please the typing.
|
||||||
self._container_registry = None # type: CuraContainerRegistry
|
self._container_registry = None # type: CuraContainerRegistry
|
||||||
|
@ -349,6 +348,9 @@ class CuraApplication(QtApplication):
|
||||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]:
|
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]:
|
||||||
Resources.addExpectedDirNameInData(dir_name)
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
|
||||||
|
app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
|
||||||
|
Resources.addSearchPath(os.path.join(app_root, "share", "cura", "resources"))
|
||||||
|
|
||||||
Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
|
Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
|
||||||
if not hasattr(sys, "frozen"):
|
if not hasattr(sys, "frozen"):
|
||||||
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
|
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
|
||||||
|
@ -392,6 +394,8 @@ class CuraApplication(QtApplication):
|
||||||
SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
|
SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
|
||||||
SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
|
SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
|
||||||
SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
|
SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
|
||||||
|
SettingFunction.registerOperator("valueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndex)
|
||||||
|
SettingFunction.registerOperator("extruderValueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndexInExtruder)
|
||||||
|
|
||||||
# Adds all resources and container related resources.
|
# Adds all resources and container related resources.
|
||||||
def __addAllResourcesAndContainerResources(self) -> None:
|
def __addAllResourcesAndContainerResources(self) -> None:
|
||||||
|
@ -631,6 +635,12 @@ class CuraApplication(QtApplication):
|
||||||
def showPreferences(self) -> None:
|
def showPreferences(self) -> None:
|
||||||
self.showPreferencesWindow.emit()
|
self.showPreferencesWindow.emit()
|
||||||
|
|
||||||
|
# This is called by drag-and-dropping curapackage files.
|
||||||
|
@pyqtSlot(QUrl)
|
||||||
|
def installPackageViaDragAndDrop(self, file_url: str) -> Optional[str]:
|
||||||
|
filename = QUrl(file_url).toLocalFile()
|
||||||
|
return self._package_manager.installPackage(filename)
|
||||||
|
|
||||||
@override(Application)
|
@override(Application)
|
||||||
def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
|
def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
|
||||||
return self._global_container_stack
|
return self._global_container_stack
|
||||||
|
@ -697,7 +707,7 @@ class CuraApplication(QtApplication):
|
||||||
self._message_box_callback_arguments = []
|
self._message_box_callback_arguments = []
|
||||||
|
|
||||||
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
||||||
def saveSettings(self):
|
def saveSettings(self) -> None:
|
||||||
if not self.started:
|
if not self.started:
|
||||||
# Do not do saving during application start or when data should not be saved on quit.
|
# Do not do saving during application start or when data should not be saved on quit.
|
||||||
return
|
return
|
||||||
|
@ -760,7 +770,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
Logger.log("i", "Initializing machine manager")
|
Logger.log("i", "Initializing machine manager")
|
||||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing machine manager..."))
|
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing machine manager..."))
|
||||||
self._machine_manager = MachineManager(self, parent = self)
|
self.getMachineManager()
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
|
|
||||||
Logger.log("i", "Initializing container manager")
|
Logger.log("i", "Initializing container manager")
|
||||||
|
@ -933,7 +943,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def getMachineManager(self, *args) -> MachineManager:
|
def getMachineManager(self, *args) -> MachineManager:
|
||||||
if self._machine_manager is None:
|
if self._machine_manager is None:
|
||||||
self._machine_manager = MachineManager(self)
|
self._machine_manager = MachineManager(self, parent = self)
|
||||||
return self._machine_manager
|
return self._machine_manager
|
||||||
|
|
||||||
def getExtruderManager(self, *args) -> ExtruderManager:
|
def getExtruderManager(self, *args) -> ExtruderManager:
|
||||||
|
@ -987,8 +997,8 @@ class CuraApplication(QtApplication):
|
||||||
## Get the machine action manager
|
## Get the machine action manager
|
||||||
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
||||||
# It wants to give this function an engine and script engine, but we don't care about that.
|
# It wants to give this function an engine and script engine, but we don't care about that.
|
||||||
def getMachineActionManager(self, *args):
|
def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager:
|
||||||
return self._machine_action_manager
|
return cast(MachineActionManager.MachineActionManager, self._machine_action_manager)
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
||||||
|
@ -1441,7 +1451,7 @@ class CuraApplication(QtApplication):
|
||||||
if center is not None:
|
if center is not None:
|
||||||
object_centers.append(center)
|
object_centers.append(center)
|
||||||
|
|
||||||
if object_centers and len(object_centers) > 0:
|
if object_centers:
|
||||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||||
|
@ -1491,7 +1501,7 @@ class CuraApplication(QtApplication):
|
||||||
if center is not None:
|
if center is not None:
|
||||||
object_centers.append(center)
|
object_centers.append(center)
|
||||||
|
|
||||||
if object_centers and len(object_centers) > 0:
|
if object_centers:
|
||||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||||
|
@ -1673,7 +1683,7 @@ class CuraApplication(QtApplication):
|
||||||
extension = os.path.splitext(f)[1]
|
extension = os.path.splitext(f)[1]
|
||||||
extension = extension.lower()
|
extension = extension.lower()
|
||||||
filename = os.path.basename(f)
|
filename = os.path.basename(f)
|
||||||
if len(self._currently_loading_files) > 0:
|
if self._currently_loading_files:
|
||||||
# If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files
|
# If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files
|
||||||
if extension in self._non_sliceable_extensions:
|
if extension in self._non_sliceable_extensions:
|
||||||
message = Message(
|
message = Message(
|
||||||
|
@ -1794,8 +1804,8 @@ class CuraApplication(QtApplication):
|
||||||
node.addDecorator(build_plate_decorator)
|
node.addDecorator(build_plate_decorator)
|
||||||
build_plate_decorator.setBuildPlateNumber(target_build_plate)
|
build_plate_decorator.setBuildPlateNumber(target_build_plate)
|
||||||
|
|
||||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
operation = AddSceneNodeOperation(node, scene.getRoot())
|
||||||
op.push()
|
operation.push()
|
||||||
|
|
||||||
node.callDecoration("setActiveExtruder", default_extruder_id)
|
node.callDecoration("setActiveExtruder", default_extruder_id)
|
||||||
scene.sceneChanged.emit(node)
|
scene.sceneChanged.emit(node)
|
||||||
|
@ -1826,15 +1836,21 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def _onContextMenuRequested(self, x: float, y: float) -> None:
|
def _onContextMenuRequested(self, x: float, y: float) -> None:
|
||||||
# Ensure we select the object if we request a context menu over an object without having a selection.
|
# Ensure we select the object if we request a context menu over an object without having a selection.
|
||||||
if not Selection.hasSelection():
|
if Selection.hasSelection():
|
||||||
node = self.getController().getScene().findObject(cast(SelectionPass, self.getRenderer().getRenderPass("selection")).getIdAtPosition(x, y))
|
return
|
||||||
if node:
|
selection_pass = cast(SelectionPass, self.getRenderer().getRenderPass("selection"))
|
||||||
parent = node.getParent()
|
if not selection_pass: # If you right-click before the rendering has been initialised there might not be a selection pass yet.
|
||||||
while(parent and parent.callDecoration("isGroup")):
|
print("--------------ding! Got the crash.")
|
||||||
node = parent
|
return
|
||||||
parent = node.getParent()
|
node = self.getController().getScene().findObject(selection_pass.getIdAtPosition(x, y))
|
||||||
|
if not node:
|
||||||
|
return
|
||||||
|
parent = node.getParent()
|
||||||
|
while parent and parent.callDecoration("isGroup"):
|
||||||
|
node = parent
|
||||||
|
parent = node.getParent()
|
||||||
|
|
||||||
Selection.add(node)
|
Selection.add(node)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def showMoreInformationDialogForAnonymousDataCollection(self):
|
def showMoreInformationDialogForAnonymousDataCollection(self):
|
||||||
|
@ -1869,16 +1885,14 @@ class CuraApplication(QtApplication):
|
||||||
main_window = QtApplication.getInstance().getMainWindow()
|
main_window = QtApplication.getInstance().getMainWindow()
|
||||||
if main_window:
|
if main_window:
|
||||||
return main_window.width()
|
return main_window.width()
|
||||||
else:
|
return 0
|
||||||
return 0
|
|
||||||
|
|
||||||
@pyqtSlot(result = int)
|
@pyqtSlot(result = int)
|
||||||
def appHeight(self) -> int:
|
def appHeight(self) -> int:
|
||||||
main_window = QtApplication.getInstance().getMainWindow()
|
main_window = QtApplication.getInstance().getMainWindow()
|
||||||
if main_window:
|
if main_window:
|
||||||
return main_window.height()
|
return main_window.height()
|
||||||
else:
|
return 0
|
||||||
return 0
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def deleteAll(self, only_selectable: bool = True) -> None:
|
def deleteAll(self, only_selectable: bool = True) -> None:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 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 List, Tuple
|
from typing import List, Tuple, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication #To find some resource types.
|
from cura.CuraApplication import CuraApplication #To find some resource types.
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
@ -9,12 +9,16 @@ from cura.Settings.GlobalStack import GlobalStack
|
||||||
from UM.PackageManager import PackageManager #The class we're extending.
|
from UM.PackageManager import PackageManager #The class we're extending.
|
||||||
from UM.Resources import Resources #To find storage paths for some resource types.
|
from UM.Resources import Resources #To find storage paths for some resource types.
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Qt.QtApplication import QtApplication
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
|
||||||
|
|
||||||
class CuraPackageManager(PackageManager):
|
class CuraPackageManager(PackageManager):
|
||||||
def __init__(self, application, parent = None):
|
def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(application, parent)
|
super().__init__(application, parent)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||||
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
CuraAppName = "@CURA_APP_NAME@"
|
CuraAppName = "@CURA_APP_NAME@"
|
||||||
|
@ -9,3 +9,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
||||||
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
||||||
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
||||||
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
|
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
|
||||||
|
CuraMarketplaceRoot = "@CURA_MARKETPLACE_ROOT@"
|
|
@ -26,6 +26,7 @@ class CuraView(View):
|
||||||
def mainComponent(self) -> QUrl:
|
def mainComponent(self) -> QUrl:
|
||||||
return self.getDisplayComponent("main")
|
return self.getDisplayComponent("main")
|
||||||
|
|
||||||
|
|
||||||
@pyqtProperty(QUrl, constant = True)
|
@pyqtProperty(QUrl, constant = True)
|
||||||
def stageMenuComponent(self) -> QUrl:
|
def stageMenuComponent(self) -> QUrl:
|
||||||
url = self.getDisplayComponent("menu")
|
url = self.getDisplayComponent("menu")
|
||||||
|
|
|
@ -33,10 +33,10 @@ class Layer:
|
||||||
def elementCount(self):
|
def elementCount(self):
|
||||||
return self._element_count
|
return self._element_count
|
||||||
|
|
||||||
def setHeight(self, height):
|
def setHeight(self, height: float) -> None:
|
||||||
self._height = height
|
self._height = height
|
||||||
|
|
||||||
def setThickness(self, thickness):
|
def setThickness(self, thickness: float) -> None:
|
||||||
self._thickness = thickness
|
self._thickness = thickness
|
||||||
|
|
||||||
def lineMeshVertexCount(self) -> int:
|
def lineMeshVertexCount(self) -> int:
|
||||||
|
|
|
@ -16,8 +16,7 @@ class LayerData(MeshData):
|
||||||
def getLayer(self, layer):
|
def getLayer(self, layer):
|
||||||
if layer in self._layers:
|
if layer in self._layers:
|
||||||
return self._layers[layer]
|
return self._layers[layer]
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
def getLayers(self):
|
def getLayers(self):
|
||||||
return self._layers
|
return self._layers
|
||||||
|
|
|
@ -9,7 +9,7 @@ from cura.LayerData import LayerData
|
||||||
|
|
||||||
## Simple decorator to indicate a scene node holds layer data.
|
## Simple decorator to indicate a scene node holds layer data.
|
||||||
class LayerDataDecorator(SceneNodeDecorator):
|
class LayerDataDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._layer_data = None # type: Optional[LayerData]
|
self._layer_data = None # type: Optional[LayerData]
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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 UM.Qt.QtApplication import QtApplication
|
|
||||||
from typing import Any, Optional
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
from typing import Optional, cast
|
||||||
|
|
||||||
|
from UM.Qt.Bindings.Theme import Theme
|
||||||
|
from UM.Qt.QtApplication import QtApplication
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ class LayerPolygon:
|
||||||
|
|
||||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||||
# Should be generated in better way, not hardcoded.
|
# Should be generated in better way, not hardcoded.
|
||||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
|
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
|
||||||
|
|
||||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||||
|
@ -149,17 +150,17 @@ class LayerPolygon:
|
||||||
def getColors(self):
|
def getColors(self):
|
||||||
return self._colors
|
return self._colors
|
||||||
|
|
||||||
def mapLineTypeToColor(self, line_types):
|
def mapLineTypeToColor(self, line_types: numpy.ndarray) -> numpy.ndarray:
|
||||||
return self._color_map[line_types]
|
return self._color_map[line_types]
|
||||||
|
|
||||||
def isInfillOrSkinType(self, line_types):
|
def isInfillOrSkinType(self, line_types: numpy.ndarray) -> numpy.ndarray:
|
||||||
return self._isInfillOrSkinTypeMap[line_types]
|
return self._is_infill_or_skin_type_map[line_types]
|
||||||
|
|
||||||
def lineMeshVertexCount(self):
|
def lineMeshVertexCount(self) -> int:
|
||||||
return (self._vertex_end - self._vertex_begin)
|
return self._vertex_end - self._vertex_begin
|
||||||
|
|
||||||
def lineMeshElementCount(self):
|
def lineMeshElementCount(self) -> int:
|
||||||
return (self._index_end - self._index_begin)
|
return self._index_end - self._index_begin
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extruder(self):
|
def extruder(self):
|
||||||
|
@ -202,7 +203,7 @@ class LayerPolygon:
|
||||||
return self._jump_count
|
return self._jump_count
|
||||||
|
|
||||||
# Calculate normals for the entire polygon using numpy.
|
# Calculate normals for the entire polygon using numpy.
|
||||||
def getNormals(self):
|
def getNormals(self) -> numpy.ndarray:
|
||||||
normals = numpy.copy(self._data)
|
normals = numpy.copy(self._data)
|
||||||
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
||||||
|
|
||||||
|
@ -226,13 +227,13 @@ class LayerPolygon:
|
||||||
|
|
||||||
return normals
|
return normals
|
||||||
|
|
||||||
__color_map = None # type: numpy.ndarray[Any]
|
__color_map = None # type: numpy.ndarray
|
||||||
|
|
||||||
## Gets the instance of the VersionUpgradeManager, or creates one.
|
## Gets the instance of the VersionUpgradeManager, or creates one.
|
||||||
@classmethod
|
@classmethod
|
||||||
def getColorMap(cls):
|
def getColorMap(cls) -> numpy.ndarray:
|
||||||
if cls.__color_map is None:
|
if cls.__color_map is None:
|
||||||
theme = QtApplication.getInstance().getTheme()
|
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
||||||
cls.__color_map = numpy.array([
|
cls.__color_map = numpy.array([
|
||||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ContainerNode:
|
||||||
## Gets the metadata of the container that this node represents.
|
## Gets the metadata of the container that this node represents.
|
||||||
# Getting the metadata from the container directly is about 10x as fast.
|
# Getting the metadata from the container directly is about 10x as fast.
|
||||||
# \return The metadata of the container in this node.
|
# \return The metadata of the container in this node.
|
||||||
def getMetadata(self):
|
def getMetadata(self) -> Dict[str, Any]:
|
||||||
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
||||||
|
|
||||||
## Get an entry from the metadata of the container that this node contains.
|
## Get an entry from the metadata of the container that this node contains.
|
||||||
|
|
|
@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
||||||
# nodes that have children) but that child node may be a node representing the
|
# nodes that have children) but that child node may be a node representing the
|
||||||
# empty instance container.
|
# empty instance container.
|
||||||
class ContainerTree:
|
class ContainerTree:
|
||||||
__instance = None
|
__instance = None # type: Optional["ContainerTree"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getInstance(cls):
|
def getInstance(cls):
|
||||||
|
@ -75,7 +75,7 @@ class ContainerTree:
|
||||||
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
||||||
|
|
||||||
## Ran after completely starting up the application.
|
## Ran after completely starting up the application.
|
||||||
def _onStartupFinished(self):
|
def _onStartupFinished(self) -> None:
|
||||||
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
|
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
|
||||||
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
|
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class ContainerTree:
|
||||||
# \param container_stacks All of the stacks to pre-load the container
|
# \param container_stacks All of the stacks to pre-load the container
|
||||||
# trees for. This needs to be provided from here because the stacks
|
# trees for. This needs to be provided from here because the stacks
|
||||||
# need to be constructed on the main thread because they are QObject.
|
# need to be constructed on the main thread because they are QObject.
|
||||||
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]):
|
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None:
|
||||||
self.tree_root = tree_root
|
self.tree_root = tree_root
|
||||||
self.container_stacks = container_stacks
|
self.container_stacks = container_stacks
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
|
@ -6,13 +6,13 @@ import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||||
|
from typing import Optional, Any, Set
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.SettingDefinition import SettingDefinition
|
from UM.Settings.SettingDefinition import SettingDefinition
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Settings.Validator import ValidatorState
|
||||||
|
|
||||||
|
import cura.CuraApplication
|
||||||
#
|
#
|
||||||
# This class performs setting error checks for the currently active machine.
|
# This class performs setting error checks for the currently active machine.
|
||||||
#
|
#
|
||||||
|
@ -24,25 +24,25 @@ from UM.Settings.Validator import ValidatorState
|
||||||
#
|
#
|
||||||
class MachineErrorChecker(QObject):
|
class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._global_stack = None
|
self._global_stack = None
|
||||||
|
|
||||||
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
|
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
|
||||||
self._error_keys = set() # A set of settings keys that have errors
|
self._error_keys = set() # type: Set[str] # A set of settings keys that have errors
|
||||||
self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
|
self._error_keys_in_progress = set() # type: Set[str] # The variable that stores the results of the currently in progress check
|
||||||
|
|
||||||
self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors
|
self._stacks_and_keys_to_check = None # type: Optional[deque] # a FIFO queue of tuples (stack, key) to check for errors
|
||||||
|
|
||||||
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
|
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
|
||||||
# error check needs to take place while there is already one running at the moment.
|
# error check needs to take place while there is already one running at the moment.
|
||||||
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
|
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
self._machine_manager = self._application.getMachineManager()
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
|
||||||
self._start_time = 0 # measure checking time
|
self._start_time = 0. # measure checking time
|
||||||
|
|
||||||
# This timer delays the starting of error check so we can react less frequently if the user is frequently
|
# This timer delays the starting of error check so we can react less frequently if the user is frequently
|
||||||
# changing settings.
|
# changing settings.
|
||||||
|
@ -94,13 +94,13 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
# Start the error check for property changed
|
# Start the error check for property changed
|
||||||
# this is seperate from the startErrorCheck because it ignores a number property types
|
# this is seperate from the startErrorCheck because it ignores a number property types
|
||||||
def startErrorCheckPropertyChanged(self, key, property_name):
|
def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None:
|
||||||
if property_name != "value":
|
if property_name != "value":
|
||||||
return
|
return
|
||||||
self.startErrorCheck()
|
self.startErrorCheck()
|
||||||
|
|
||||||
# Starts the error check timer to schedule a new error check.
|
# Starts the error check timer to schedule a new error check.
|
||||||
def startErrorCheck(self, *args) -> None:
|
def startErrorCheck(self, *args: Any) -> None:
|
||||||
if not self._check_in_progress:
|
if not self._check_in_progress:
|
||||||
self._need_to_check = True
|
self._need_to_check = True
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
|
|
|
@ -176,9 +176,9 @@ class MachineNode(ContainerNode):
|
||||||
|
|
||||||
# Find the global qualities for this printer.
|
# Find the global qualities for this printer.
|
||||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
|
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
|
||||||
if len(global_qualities) == 0: # This printer doesn't override the global qualities.
|
if not global_qualities: # This printer doesn't override the global qualities.
|
||||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
|
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
|
||||||
if len(global_qualities) == 0: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree.
|
if not global_qualities: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree.
|
||||||
global_qualities = [cura.CuraApplication.CuraApplication.getInstance().empty_quality_container.getMetaData()]
|
global_qualities = [cura.CuraApplication.CuraApplication.getInstance().empty_quality_container.getMetaData()]
|
||||||
for global_quality in global_qualities:
|
for global_quality in global_qualities:
|
||||||
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
||||||
|
|
|
@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from cura.Machines.VariantNode import VariantNode
|
from cura.Machines.VariantNode import VariantNode
|
||||||
|
|
||||||
|
|
||||||
## Represents a material in the container tree.
|
## Represents a material in the container tree.
|
||||||
#
|
#
|
||||||
# Its subcontainers are quality profiles.
|
# Its subcontainers are quality profiles.
|
||||||
|
|
|
@ -45,7 +45,7 @@ class BaseMaterialsModel(ListModel):
|
||||||
# can be caused in the middle of a XMLMaterial loading, and the material container we try to find may not be
|
# can be caused in the middle of a XMLMaterial loading, and the material container we try to find may not be
|
||||||
# in the system yet. This will cause an infinite recursion of (1) trying to load a material, (2) trying to
|
# in the system yet. This will cause an infinite recursion of (1) trying to load a material, (2) trying to
|
||||||
# update the material model, (3) cannot find the material container, load it, (4) repeat #1.
|
# update the material model, (3) cannot find the material container, load it, (4) repeat #1.
|
||||||
self._update_timer = QTimer()
|
self._update_timer = QTimer(self)
|
||||||
self._update_timer.setInterval(100)
|
self._update_timer.setInterval(100)
|
||||||
self._update_timer.setSingleShot(True)
|
self._update_timer.setSingleShot(True)
|
||||||
self._update_timer.timeout.connect(self._update)
|
self._update_timer.timeout.connect(self._update)
|
||||||
|
|
|
@ -34,7 +34,7 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
brand_item_list = []
|
brand_item_list = []
|
||||||
brand_group_dict = {}
|
brand_group_dict = {}
|
||||||
|
|
||||||
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
|
# Part 1: Generate the entire tree of brands -> material types -> specific materials
|
||||||
for root_material_id, container_node in self._available_materials.items():
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
# Do not include the materials from a to-be-removed package
|
# Do not include the materials from a to-be-removed package
|
||||||
if bool(container_node.getMetaDataEntry("removed", False)):
|
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||||
|
|
|
@ -41,4 +41,4 @@ class QualityNode(ContainerNode):
|
||||||
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
|
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
|
||||||
|
|
||||||
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
|
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
|
||||||
# Otherwise, there are no intents for global profiles.
|
# Otherwise, there are no intents for global profiles.
|
||||||
|
|
|
@ -51,7 +51,7 @@ class VariantNode(ContainerNode):
|
||||||
# Find all the materials for this variant's name.
|
# Find all the materials for this variant's name.
|
||||||
else: # Printer has its own material profiles. Look for material profiles with this printer's definition.
|
else: # Printer has its own material profiles. Look for material profiles with this printer's definition.
|
||||||
base_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter")
|
base_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter")
|
||||||
printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = None)
|
printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id)
|
||||||
variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = self.variant_name) # If empty_variant, this won't return anything.
|
variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = self.variant_name) # If empty_variant, this won't return anything.
|
||||||
materials_per_base_file = {material["base_file"]: material for material in base_materials}
|
materials_per_base_file = {material["base_file"]: material for material in base_materials}
|
||||||
materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones.
|
materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones.
|
||||||
|
|
|
@ -47,7 +47,7 @@ class MultiplyObjectsJob(Job):
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
||||||
not_fit_count = 0
|
not_fit_count = 0
|
||||||
|
found_solution_for_all = False
|
||||||
for node in self._objects:
|
for node in self._objects:
|
||||||
# If object is part of a group, multiply group
|
# If object is part of a group, multiply group
|
||||||
current_node = node
|
current_node = node
|
||||||
|
@ -66,7 +66,7 @@ class MultiplyObjectsJob(Job):
|
||||||
|
|
||||||
found_solution_for_all = True
|
found_solution_for_all = True
|
||||||
arranger.resetLastPriority()
|
arranger.resetLastPriority()
|
||||||
for i in range(self._count):
|
for _ in range(self._count):
|
||||||
# We do place the nodes one by one, as we want to yield in between.
|
# We do place the nodes one by one, as we want to yield in between.
|
||||||
new_node = copy.deepcopy(node)
|
new_node = copy.deepcopy(node)
|
||||||
solution_found = False
|
solution_found = False
|
||||||
|
@ -98,10 +98,10 @@ class MultiplyObjectsJob(Job):
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
if nodes:
|
if nodes:
|
||||||
op = GroupedOperation()
|
operation = GroupedOperation()
|
||||||
for new_node in nodes:
|
for new_node in nodes:
|
||||||
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
operation.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
||||||
op.push()
|
operation.push()
|
||||||
status_message.hide()
|
status_message.hide()
|
||||||
|
|
||||||
if not found_solution_for_all:
|
if not found_solution_for_all:
|
||||||
|
|
|
@ -115,9 +115,10 @@ class AuthorizationHelpers:
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
## Generate a 16-character verification code.
|
## Generate a verification code of arbitrary length.
|
||||||
# \param code_length: How long should the code be?
|
# \param code_length: How long should the code be? This should never be lower than 16, but it's probably better to
|
||||||
def generateVerificationCode(code_length: int = 16) -> str:
|
# leave it at 32
|
||||||
|
def generateVerificationCode(code_length: int = 32) -> str:
|
||||||
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
|
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -25,6 +25,8 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
|
self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
|
||||||
self.verification_code = None # type: Optional[str]
|
self.verification_code = None # type: Optional[str]
|
||||||
|
|
||||||
|
self.state = None # type: Optional[str]
|
||||||
|
|
||||||
# CURA-6609: Some browser seems to issue a HEAD instead of GET request as the callback.
|
# CURA-6609: Some browser seems to issue a HEAD instead of GET request as the callback.
|
||||||
def do_HEAD(self) -> None:
|
def do_HEAD(self) -> None:
|
||||||
self.do_GET()
|
self.do_GET()
|
||||||
|
@ -58,7 +60,14 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
# \return HTTP ResponseData containing a success page to show to the user.
|
# \return HTTP ResponseData containing a success page to show to the user.
|
||||||
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
|
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
|
||||||
code = self._queryGet(query, "code")
|
code = self._queryGet(query, "code")
|
||||||
if code and self.authorization_helpers is not None and self.verification_code is not None:
|
state = self._queryGet(query, "state")
|
||||||
|
if state != self.state:
|
||||||
|
token_response = AuthenticationResponse(
|
||||||
|
success = False,
|
||||||
|
err_message=catalog.i18nc("@message",
|
||||||
|
"The provided state is not correct.")
|
||||||
|
)
|
||||||
|
elif code and self.authorization_helpers is not None and self.verification_code is not None:
|
||||||
# If the code was returned we get the access token.
|
# If the code was returned we get the access token.
|
||||||
token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode(
|
token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode(
|
||||||
code, self.verification_code)
|
code, self.verification_code)
|
||||||
|
|
|
@ -25,3 +25,6 @@ class AuthorizationRequestServer(HTTPServer):
|
||||||
## Set the verification code on the request handler.
|
## Set the verification code on the request handler.
|
||||||
def setVerificationCode(self, verification_code: str) -> None:
|
def setVerificationCode(self, verification_code: str) -> None:
|
||||||
self.RequestHandlerClass.verification_code = verification_code # type: ignore
|
self.RequestHandlerClass.verification_code = verification_code # type: ignore
|
||||||
|
|
||||||
|
def setState(self, state: str) -> None:
|
||||||
|
self.RequestHandlerClass.state = state # type: ignore
|
||||||
|
|
|
@ -153,13 +153,15 @@ class AuthorizationService:
|
||||||
verification_code = self._auth_helpers.generateVerificationCode()
|
verification_code = self._auth_helpers.generateVerificationCode()
|
||||||
challenge_code = self._auth_helpers.generateVerificationCodeChallenge(verification_code)
|
challenge_code = self._auth_helpers.generateVerificationCodeChallenge(verification_code)
|
||||||
|
|
||||||
|
state = AuthorizationHelpers.generateVerificationCode()
|
||||||
|
|
||||||
# Create the query string needed for the OAuth2 flow.
|
# Create the query string needed for the OAuth2 flow.
|
||||||
query_string = urlencode({
|
query_string = urlencode({
|
||||||
"client_id": self._settings.CLIENT_ID,
|
"client_id": self._settings.CLIENT_ID,
|
||||||
"redirect_uri": self._settings.CALLBACK_URL,
|
"redirect_uri": self._settings.CALLBACK_URL,
|
||||||
"scope": self._settings.CLIENT_SCOPES,
|
"scope": self._settings.CLIENT_SCOPES,
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
"state": "(.Y.)",
|
"state": state, # Forever in our Hearts, RIP "(.Y.)" (2018-2020)
|
||||||
"code_challenge": challenge_code,
|
"code_challenge": challenge_code,
|
||||||
"code_challenge_method": "S512"
|
"code_challenge_method": "S512"
|
||||||
})
|
})
|
||||||
|
@ -168,7 +170,7 @@ class AuthorizationService:
|
||||||
QDesktopServices.openUrl(QUrl("{}?{}".format(self._auth_url, query_string)))
|
QDesktopServices.openUrl(QUrl("{}?{}".format(self._auth_url, query_string)))
|
||||||
|
|
||||||
# Start a local web server to receive the callback URL on.
|
# Start a local web server to receive the callback URL on.
|
||||||
self._server.start(verification_code)
|
self._server.start(verification_code, state)
|
||||||
|
|
||||||
## Callback method for the authentication flow.
|
## Callback method for the authentication flow.
|
||||||
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
|
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
|
||||||
|
|
|
@ -36,7 +36,8 @@ class LocalAuthorizationServer:
|
||||||
|
|
||||||
## Starts the local web server to handle the authorization callback.
|
## Starts the local web server to handle the authorization callback.
|
||||||
# \param verification_code The verification code part of the OAuth2 client identification.
|
# \param verification_code The verification code part of the OAuth2 client identification.
|
||||||
def start(self, verification_code: str) -> None:
|
# \param state The unique state code (to ensure that the request we get back is really from the server.
|
||||||
|
def start(self, verification_code: str, state: str) -> None:
|
||||||
if self._web_server:
|
if self._web_server:
|
||||||
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
|
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
|
||||||
# We still inject the new verification code though.
|
# We still inject the new verification code though.
|
||||||
|
@ -53,6 +54,7 @@ class LocalAuthorizationServer:
|
||||||
self._web_server.setAuthorizationHelpers(self._auth_helpers)
|
self._web_server.setAuthorizationHelpers(self._auth_helpers)
|
||||||
self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
|
self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
|
||||||
self._web_server.setVerificationCode(verification_code)
|
self._web_server.setVerificationCode(verification_code)
|
||||||
|
self._web_server.setState(state)
|
||||||
|
|
||||||
# Start the server on a new thread.
|
# Start the server on a new thread.
|
||||||
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
|
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
class BaseModel:
|
class BaseModel:
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,9 +53,10 @@ class ResponseData(BaseModel):
|
||||||
redirect_uri = None # type: Optional[str]
|
redirect_uri = None # type: Optional[str]
|
||||||
content_type = "text/html" # type: str
|
content_type = "text/html" # type: str
|
||||||
|
|
||||||
|
|
||||||
## Possible HTTP responses.
|
## Possible HTTP responses.
|
||||||
HTTP_STATUS = {
|
HTTP_STATUS = {
|
||||||
"OK": ResponseStatus(code = 200, message = "OK"),
|
"OK": ResponseStatus(code = 200, message = "OK"),
|
||||||
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
||||||
"REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
|
"REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
|
||||||
}
|
} # type: Dict[str, ResponseStatus]
|
||||||
|
|
|
@ -122,6 +122,6 @@ class _ObjectOrder:
|
||||||
# \param order List of indices in which to print objects, ordered by printing
|
# \param order List of indices in which to print objects, ordered by printing
|
||||||
# order.
|
# order.
|
||||||
# \param todo: List of indices which are not yet inserted into the order list.
|
# \param todo: List of indices which are not yet inserted into the order list.
|
||||||
def __init__(self, order: List[SceneNode], todo: List[SceneNode]):
|
def __init__(self, order: List[SceneNode], todo: List[SceneNode]) -> None:
|
||||||
self.order = order
|
self.order = order
|
||||||
self.todo = todo
|
self.todo = todo
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
# Copyright (c) 2015 Ultimaker B.V.
|
# Copyright (c) 2015 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 UM.Math.Vector import Vector
|
||||||
from UM.Operations.Operation import Operation
|
from UM.Operations.Operation import Operation
|
||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
|
|
||||||
## A specialised operation designed specifically to modify the previous operation.
|
## A specialised operation designed specifically to modify the previous operation.
|
||||||
class PlatformPhysicsOperation(Operation):
|
class PlatformPhysicsOperation(Operation):
|
||||||
def __init__(self, node, translation):
|
def __init__(self, node: SceneNode, translation: Vector) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._node = node
|
self._node = node
|
||||||
self._old_transformation = node.getLocalTransformation()
|
self._old_transformation = node.getLocalTransformation()
|
||||||
self._translation = translation
|
self._translation = translation
|
||||||
self._always_merge = True
|
self._always_merge = True
|
||||||
|
|
||||||
def undo(self):
|
def undo(self) -> None:
|
||||||
self._node.setTransformation(self._old_transformation)
|
self._node.setTransformation(self._old_transformation)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self) -> None:
|
||||||
self._node.translate(self._translation, SceneNode.TransformSpace.World)
|
self._node.translate(self._translation, SceneNode.TransformSpace.World)
|
||||||
|
|
||||||
def mergeWith(self, other):
|
def mergeWith(self, other: Operation) -> GroupedOperation:
|
||||||
group = GroupedOperation()
|
group = GroupedOperation()
|
||||||
|
|
||||||
group.addOperation(other)
|
group.addOperation(other)
|
||||||
|
@ -28,5 +29,5 @@ class PlatformPhysicsOperation(Operation):
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return "PlatformPhysicsOp.(trans.={0})".format(self._translation)
|
return "PlatformPhysicsOp.(trans.={0})".format(self._translation)
|
||||||
|
|
|
@ -6,9 +6,9 @@ from UM.Operations.Operation import Operation
|
||||||
|
|
||||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||||
|
|
||||||
|
|
||||||
## Simple operation to set the buildplate number of a scenenode.
|
## Simple operation to set the buildplate number of a scenenode.
|
||||||
class SetBuildPlateNumberOperation(Operation):
|
class SetBuildPlateNumberOperation(Operation):
|
||||||
|
|
||||||
def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
|
def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._node = node
|
self._node = node
|
||||||
|
@ -16,11 +16,11 @@ class SetBuildPlateNumberOperation(Operation):
|
||||||
self._previous_build_plate_nr = None
|
self._previous_build_plate_nr = None
|
||||||
self._decorator_added = False
|
self._decorator_added = False
|
||||||
|
|
||||||
def undo(self):
|
def undo(self) -> None:
|
||||||
if self._previous_build_plate_nr:
|
if self._previous_build_plate_nr:
|
||||||
self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr)
|
self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self) -> None:
|
||||||
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
||||||
if not stack:
|
if not stack:
|
||||||
self._node.addDecorator(SettingOverrideDecorator())
|
self._node.addDecorator(SettingOverrideDecorator())
|
||||||
|
|
|
@ -1,36 +1,37 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Operations import Operation
|
from UM.Operations import Operation
|
||||||
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
|
||||||
## An operation that parents a scene node to another scene node.
|
|
||||||
|
|
||||||
|
## An operation that parents a scene node to another scene node.
|
||||||
class SetParentOperation(Operation.Operation):
|
class SetParentOperation(Operation.Operation):
|
||||||
## Initialises this SetParentOperation.
|
## Initialises this SetParentOperation.
|
||||||
#
|
#
|
||||||
# \param node The node which will be reparented.
|
# \param node The node which will be reparented.
|
||||||
# \param parent_node The node which will be the parent.
|
# \param parent_node The node which will be the parent.
|
||||||
def __init__(self, node, parent_node):
|
def __init__(self, node: SceneNode, parent_node: Optional[SceneNode]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._node = node
|
self._node = node
|
||||||
self._parent = parent_node
|
self._parent = parent_node
|
||||||
self._old_parent = node.getParent() # To restore the previous parent in case of an undo.
|
self._old_parent = node.getParent() # To restore the previous parent in case of an undo.
|
||||||
|
|
||||||
## Undoes the set-parent operation, restoring the old parent.
|
## Undoes the set-parent operation, restoring the old parent.
|
||||||
def undo(self):
|
def undo(self) -> None:
|
||||||
self._set_parent(self._old_parent)
|
self._set_parent(self._old_parent)
|
||||||
|
|
||||||
## Re-applies the set-parent operation.
|
## Re-applies the set-parent operation.
|
||||||
def redo(self):
|
def redo(self) -> None:
|
||||||
self._set_parent(self._parent)
|
self._set_parent(self._parent)
|
||||||
|
|
||||||
## Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
|
## Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
|
||||||
#
|
#
|
||||||
# \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene.
|
# \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene.
|
||||||
def _set_parent(self, new_parent):
|
def _set_parent(self, new_parent: Optional[SceneNode]) -> None:
|
||||||
if new_parent:
|
if new_parent:
|
||||||
current_parent = self._node.getParent()
|
current_parent = self._node.getParent()
|
||||||
if current_parent:
|
if current_parent:
|
||||||
|
@ -59,5 +60,5 @@ class SetParentOperation(Operation.Operation):
|
||||||
## Returns a programmer-readable representation of this operation.
|
## Returns a programmer-readable representation of this operation.
|
||||||
#
|
#
|
||||||
# \return A programmer-readable representation of this operation.
|
# \return A programmer-readable representation of this operation.
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent)
|
return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent)
|
||||||
|
|
|
@ -17,9 +17,6 @@ from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||||
|
|
||||||
MYPY = False
|
|
||||||
if MYPY:
|
|
||||||
from UM.Scene.Camera import Camera
|
from UM.Scene.Camera import Camera
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from PyQt5.QtQuick import QQuickImageProvider
|
||||||
from PyQt5.QtCore import QSize
|
from PyQt5.QtCore import QSize
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||||
|
@ -10,7 +11,7 @@ class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||||
super().__init__(QQuickImageProvider.Image)
|
super().__init__(QQuickImageProvider.Image)
|
||||||
|
|
||||||
## Request a new image.
|
## Request a new image.
|
||||||
def requestImage(self, id: str, size: QSize) -> QImage:
|
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
|
||||||
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
|
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
|
||||||
# increment, we need to strip that first.
|
# increment, we need to strip that first.
|
||||||
uuid = id[id.find("/") + 1:]
|
uuid = id[id.find("/") + 1:]
|
||||||
|
@ -22,6 +23,6 @@ class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||||
if print_job.key == uuid:
|
if print_job.key == uuid:
|
||||||
if print_job.getPreviewImage():
|
if print_job.getPreviewImage():
|
||||||
return print_job.getPreviewImage(), QSize(15, 15)
|
return print_job.getPreviewImage(), QSize(15, 15)
|
||||||
else:
|
|
||||||
return QImage(), QSize(15, 15)
|
return QImage(), QSize(15, 15)
|
||||||
return QImage(), QSize(15,15)
|
return QImage(), QSize(15, 15)
|
|
@ -161,7 +161,7 @@ class PrintJobOutputModel(QObject):
|
||||||
self._time_elapsed = new_time_elapsed
|
self._time_elapsed = new_time_elapsed
|
||||||
self.timeElapsedChanged.emit()
|
self.timeElapsedChanged.emit()
|
||||||
|
|
||||||
def updateState(self, new_state):
|
def updateState(self, new_state: str) -> None:
|
||||||
if self._state != new_state:
|
if self._state != new_state:
|
||||||
self._state = new_state
|
self._state = new_state
|
||||||
self.stateChanged.emit()
|
self.stateChanged.emit()
|
||||||
|
|
|
@ -148,7 +148,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = printersChanged)
|
@pyqtProperty(QObject, notify = printersChanged)
|
||||||
def activePrinter(self) -> Optional["PrinterOutputModel"]:
|
def activePrinter(self) -> Optional["PrinterOutputModel"]:
|
||||||
if len(self._printers):
|
if self._printers:
|
||||||
return self._printers[0]
|
return self._printers[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,6 @@ class BlockSlicingDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
def isBlockSlicing(self) -> bool:
|
def isBlockSlicing(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
return BlockSlicingDecorator()
|
|
@ -17,8 +17,8 @@ class GCodeListDecorator(SceneNodeDecorator):
|
||||||
def getGCodeList(self) -> List[str]:
|
def getGCodeList(self) -> List[str]:
|
||||||
return self._gcode_list
|
return self._gcode_list
|
||||||
|
|
||||||
def setGCodeList(self, list: List[str]) -> None:
|
def setGCodeList(self, gcode_list: List[str]) -> None:
|
||||||
self._gcode_list = list
|
self._gcode_list = gcode_list
|
||||||
|
|
||||||
def __deepcopy__(self, memo) -> "GCodeListDecorator":
|
def __deepcopy__(self, memo) -> "GCodeListDecorator":
|
||||||
copied_decorator = GCodeListDecorator()
|
copied_decorator = GCodeListDecorator()
|
||||||
|
|
|
@ -15,7 +15,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.SettingInstance import SettingInstance
|
from UM.Settings.SettingInstance import SettingInstance
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
|
@ -176,7 +175,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if not file_name:
|
if not file_name:
|
||||||
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>: {1}", file_name, "Invalid path")}
|
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>: {1}", file_name, "Invalid path")}
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
|
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
|
||||||
container_tree = ContainerTree.getInstance()
|
container_tree = ContainerTree.getInstance()
|
||||||
|
@ -384,7 +383,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if not quality_type:
|
if not quality_type:
|
||||||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_stack is None:
|
if global_stack is None:
|
||||||
return None
|
return None
|
||||||
definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||||
|
|
|
@ -133,6 +133,38 @@ class CuraFormulaFunctions:
|
||||||
context = self.createContextForDefaultValueEvaluation(global_stack)
|
context = self.createContextForDefaultValueEvaluation(global_stack)
|
||||||
return self.getResolveOrValue(property_key, context = context)
|
return self.getResolveOrValue(property_key, context = context)
|
||||||
|
|
||||||
|
# Gets the value for the given setting key starting from the given container index.
|
||||||
|
def getValueFromContainerAtIndex(self, property_key: str, container_index: int,
|
||||||
|
context: Optional["PropertyEvaluationContext"] = None) -> Any:
|
||||||
|
machine_manager = self._application.getMachineManager()
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
|
context = self.createContextForDefaultValueEvaluation(global_stack)
|
||||||
|
context.context["evaluate_from_container_index"] = container_index
|
||||||
|
|
||||||
|
return global_stack.getProperty(property_key, "value", context = context)
|
||||||
|
|
||||||
|
# Gets the extruder value for the given setting key starting from the given container index.
|
||||||
|
def getValueFromContainerAtIndexInExtruder(self, extruder_position: int, property_key: str, container_index: int,
|
||||||
|
context: Optional["PropertyEvaluationContext"] = None) -> Any:
|
||||||
|
machine_manager = self._application.getMachineManager()
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
|
if extruder_position == -1:
|
||||||
|
extruder_position = int(machine_manager.defaultExtruderPosition)
|
||||||
|
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
try:
|
||||||
|
extruder_stack = global_stack.extruderList[int(extruder_position)]
|
||||||
|
except IndexError:
|
||||||
|
Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. " % (property_key, extruder_position))
|
||||||
|
return None
|
||||||
|
|
||||||
|
context = self.createContextForDefaultValueEvaluation(extruder_stack)
|
||||||
|
context.context["evaluate_from_container_index"] = container_index
|
||||||
|
|
||||||
|
return self.getValueInExtruder(extruder_position, property_key, context)
|
||||||
|
|
||||||
# Creates a context for evaluating default values (skip the user_changes container).
|
# Creates a context for evaluating default values (skip the user_changes container).
|
||||||
def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext":
|
def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext":
|
||||||
context = PropertyEvaluationContext(source_stack)
|
context = PropertyEvaluationContext(source_stack)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||||
|
@ -275,6 +275,25 @@ class ExtruderManager(QObject):
|
||||||
Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids)
|
Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
## Get the extruder that the print will start with.
|
||||||
|
#
|
||||||
|
# This should mirror the implementation in CuraEngine of
|
||||||
|
# ``FffGcodeWriter::getStartExtruder()``.
|
||||||
|
def getInitialExtruderNr(self) -> int:
|
||||||
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
global_stack = application.getGlobalContainerStack()
|
||||||
|
|
||||||
|
# Starts with the adhesion extruder.
|
||||||
|
if global_stack.getProperty("adhesion_type", "value") != "none":
|
||||||
|
return global_stack.getProperty("adhesion_extruder_nr", "value")
|
||||||
|
|
||||||
|
# No adhesion? Well maybe there is still support brim.
|
||||||
|
if (global_stack.getProperty("support_enable", "value") or global_stack.getProperty("support_tree_enable", "value")) and global_stack.getProperty("support_brim_enable", "value"):
|
||||||
|
return global_stack.getProperty("support_infill_extruder_nr", "value")
|
||||||
|
|
||||||
|
# REALLY no adhesion? Use the first used extruder.
|
||||||
|
return self.getUsedExtruderStacks()[0].getProperty("extruder_nr", "value")
|
||||||
|
|
||||||
## Removes the container stack and user profile for the extruders for a specific machine.
|
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||||
#
|
#
|
||||||
# \param machine_id The machine to remove the extruders for.
|
# \param machine_id The machine to remove the extruders for.
|
||||||
|
|
|
@ -227,7 +227,7 @@ class MachineManager(QObject):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
for extruder_stack in self._global_container_stack.extruderList:
|
||||||
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
extruder_stack.containersChanged.disconnect(self._onContainersChanged)
|
extruder_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ class MachineManager(QObject):
|
||||||
self._global_container_stack.setMaterial(empty_material_container)
|
self._global_container_stack.setMaterial(empty_material_container)
|
||||||
|
|
||||||
# Listen for changes on all extruder stacks
|
# Listen for changes on all extruder stacks
|
||||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
for extruder_stack in self._global_container_stack.extruderList:
|
||||||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||||
extruder_stack.containersChanged.connect(self._onContainersChanged)
|
extruder_stack.containersChanged.connect(self._onContainersChanged)
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
||||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruder_stacks = self._global_container_stack.extruderList
|
||||||
count = 1 # We start with the global stack
|
count = 1 # We start with the global stack
|
||||||
for stack in extruder_stacks:
|
for stack in extruder_stacks:
|
||||||
md = stack.getMetaData()
|
md = stack.getMetaData()
|
||||||
|
@ -388,8 +388,7 @@ class MachineManager(QObject):
|
||||||
if self._global_container_stack.getTop().getNumInstances() != 0:
|
if self._global_container_stack.getTop().getNumInstances() != 0:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
for stack in self._global_container_stack.extruderList:
|
||||||
for stack in stacks:
|
|
||||||
if stack.getTop().getNumInstances() != 0:
|
if stack.getTop().getNumInstances() != 0:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -399,8 +398,7 @@ class MachineManager(QObject):
|
||||||
def numUserSettings(self) -> int:
|
def numUserSettings(self) -> int:
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return 0
|
return 0
|
||||||
num_user_settings = 0
|
num_user_settings = self._global_container_stack.getTop().getNumInstances()
|
||||||
num_user_settings += self._global_container_stack.getTop().getNumInstances()
|
|
||||||
stacks = self._global_container_stack.extruderList
|
stacks = self._global_container_stack.extruderList
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
num_user_settings += stack.getTop().getNumInstances()
|
num_user_settings += stack.getTop().getNumInstances()
|
||||||
|
@ -427,7 +425,7 @@ class MachineManager(QObject):
|
||||||
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||||
stacks = [stack]
|
stacks = [stack]
|
||||||
else:
|
else:
|
||||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
stacks = self._global_container_stack.extruderList
|
||||||
|
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
|
@ -612,10 +610,9 @@ class MachineManager(QObject):
|
||||||
if self._active_container_stack is None or self._global_container_stack is None:
|
if self._active_container_stack is None or self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
new_value = self._active_container_stack.getProperty(key, "value")
|
new_value = self._active_container_stack.getProperty(key, "value")
|
||||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
|
|
||||||
|
|
||||||
# Check in which stack the value has to be replaced
|
# Check in which stack the value has to be replaced
|
||||||
for extruder_stack in extruder_stacks:
|
for extruder_stack in self._global_container_stack.extruderList:
|
||||||
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
||||||
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
|
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
|
||||||
|
|
||||||
|
@ -750,6 +747,11 @@ class MachineManager(QObject):
|
||||||
result = [] # type: List[str]
|
result = [] # type: List[str]
|
||||||
for setting_instance in container.findInstances():
|
for setting_instance in container.findInstances():
|
||||||
setting_key = setting_instance.definition.key
|
setting_key = setting_instance.definition.key
|
||||||
|
if setting_key == "print_sequence":
|
||||||
|
old_value = container.getProperty(setting_key, "value")
|
||||||
|
Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
|
||||||
|
result.append(setting_key)
|
||||||
|
continue
|
||||||
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
|
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -798,7 +800,7 @@ class MachineManager(QObject):
|
||||||
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
|
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
|
||||||
|
|
||||||
self.updateDefaultExtruder()
|
self.updateDefaultExtruder()
|
||||||
self.updateNumberExtrudersEnabled()
|
self.numberExtrudersEnabledChanged.emit()
|
||||||
self.correctExtruderSettings()
|
self.correctExtruderSettings()
|
||||||
|
|
||||||
# Check to see if any objects are set to print with an extruder that will no longer exist
|
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||||
|
@ -929,7 +931,7 @@ class MachineManager(QObject):
|
||||||
def _getContainerChangedSignals(self) -> List[Signal]:
|
def _getContainerChangedSignals(self) -> List[Signal]:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return []
|
return []
|
||||||
return [s.containersChanged for s in ExtruderManager.getInstance().getActiveExtruderStacks() + [self._global_container_stack]]
|
return [s.containersChanged for s in self._global_container_stack.extruderList + [self._global_container_stack]]
|
||||||
|
|
||||||
@pyqtSlot(str, str, str)
|
@pyqtSlot(str, str, str)
|
||||||
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
||||||
|
|
|
@ -43,7 +43,7 @@ class MachineActionManager(QObject):
|
||||||
# Dict of all actions that need to be done when first added by definition ID
|
# Dict of all actions that need to be done when first added by definition ID
|
||||||
self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
|
self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
# Add machine_action as plugin type
|
# Add machine_action as plugin type
|
||||||
PluginRegistry.addType("machine_action", self.addMachineAction)
|
PluginRegistry.addType("machine_action", self.addMachineAction)
|
||||||
|
|
||||||
|
|
42
cura_app.py
42
cura_app.py
|
@ -12,7 +12,11 @@ from UM.Platform import Platform
|
||||||
from cura import ApplicationMetadata
|
from cura import ApplicationMetadata
|
||||||
from cura.ApplicationMetadata import CuraAppName
|
from cura.ApplicationMetadata import CuraAppName
|
||||||
|
|
||||||
import sentry_sdk
|
try:
|
||||||
|
import sentry_sdk
|
||||||
|
with_sentry_sdk = True
|
||||||
|
except ImportError:
|
||||||
|
with_sentry_sdk = False
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog = "cura",
|
parser = argparse.ArgumentParser(prog = "cura",
|
||||||
add_help = False)
|
add_help = False)
|
||||||
|
@ -24,21 +28,26 @@ parser.add_argument("--debug",
|
||||||
|
|
||||||
known_args = vars(parser.parse_known_args()[0])
|
known_args = vars(parser.parse_known_args()[0])
|
||||||
|
|
||||||
sentry_env = "production"
|
if with_sentry_sdk:
|
||||||
if ApplicationMetadata.CuraVersion == "master":
|
sentry_env = "unknown" # Start off with a "IDK"
|
||||||
sentry_env = "development"
|
if hasattr(sys, "frozen"):
|
||||||
try:
|
sentry_env = "production" # A frozen build is a "real" distribution.
|
||||||
if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
|
elif ApplicationMetadata.CuraVersion == "master":
|
||||||
sentry_env = "nightly"
|
sentry_env = "development"
|
||||||
except IndexError:
|
elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion:
|
||||||
pass
|
sentry_env = "beta"
|
||||||
|
try:
|
||||||
sentry_sdk.init("https://5034bf0054fb4b889f82896326e79b13@sentry.io/1821564",
|
if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
|
||||||
environment = sentry_env,
|
sentry_env = "nightly"
|
||||||
release = "cura%s" % ApplicationMetadata.CuraVersion,
|
except IndexError:
|
||||||
default_integrations = False,
|
pass
|
||||||
max_breadcrumbs = 300,
|
|
||||||
server_name = "cura")
|
sentry_sdk.init("https://5034bf0054fb4b889f82896326e79b13@sentry.io/1821564",
|
||||||
|
environment = sentry_env,
|
||||||
|
release = "cura%s" % ApplicationMetadata.CuraVersion,
|
||||||
|
default_integrations = False,
|
||||||
|
max_breadcrumbs = 300,
|
||||||
|
server_name = "cura")
|
||||||
|
|
||||||
if not known_args["debug"]:
|
if not known_args["debug"]:
|
||||||
def get_cura_dir_path():
|
def get_cura_dir_path():
|
||||||
|
@ -162,6 +171,7 @@ else:
|
||||||
# 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
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,28 +13,46 @@ export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
||||||
|
|
||||||
cd "${PROJECT_DIR}"
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Clone Uranium and set PYTHONPATH first
|
# Clone Uranium and set PYTHONPATH first
|
||||||
#
|
#
|
||||||
|
|
||||||
# Check the branch to use:
|
# Check the branch to use for Uranium.
|
||||||
# 1. Use the Uranium branch with the branch same if it exists.
|
# It tries the following branch names and uses the first one that's available.
|
||||||
# 2. Otherwise, use the default branch name "master"
|
# - GITHUB_HEAD_REF: the branch name of a PR. If it's not a PR, it will be empty.
|
||||||
|
# - GITHUB_BASE_REF: the branch a PR is based on. If it's not a PR, it will be empty.
|
||||||
|
# - GITHUB_REF: the branch name if it's a branch on the repository;
|
||||||
|
# refs/pull/123/merge if it's a pull_request.
|
||||||
|
# - master: the master branch. It should always exist.
|
||||||
|
|
||||||
|
# For debugging.
|
||||||
echo "GITHUB_REF: ${GITHUB_REF}"
|
echo "GITHUB_REF: ${GITHUB_REF}"
|
||||||
|
echo "GITHUB_HEAD_REF: ${GITHUB_HEAD_REF}"
|
||||||
echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF}"
|
echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF}"
|
||||||
|
|
||||||
GIT_REF_NAME="${GITHUB_REF}"
|
GIT_REF_NAME_LIST=( "${GITHUB_HEAD_REF}" "${GITHUB_BASE_REF}" "${GITHUB_REF}" "master" )
|
||||||
if [ -n "${GITHUB_BASE_REF}" ]; then
|
for git_ref_name in "${GIT_REF_NAME_LIST[@]}"
|
||||||
GIT_REF_NAME="${GITHUB_BASE_REF}"
|
do
|
||||||
fi
|
if [ -z "${git_ref_name}" ]; then
|
||||||
GIT_REF_NAME="$(basename "${GIT_REF_NAME}")"
|
continue
|
||||||
|
fi
|
||||||
URANIUM_BRANCH="${GIT_REF_NAME:-master}"
|
git_ref_name="$(basename "${git_ref_name}")"
|
||||||
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
# Skip refs/pull/1234/merge as pull requests use it as GITHUB_REF
|
||||||
if [ -z "${output}" ]; then
|
if [[ "${git_ref_name}" == "merge" ]]; then
|
||||||
echo "Could not find Uranium banch ${URANIUM_BRANCH}, fallback to use master."
|
echo "Skip [${git_ref_name}]"
|
||||||
URANIUM_BRANCH="master"
|
continue
|
||||||
fi
|
fi
|
||||||
|
URANIUM_BRANCH="${git_ref_name}"
|
||||||
|
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
||||||
|
if [ -n "${output}" ]; then
|
||||||
|
echo "Found Uranium branch [${URANIUM_BRANCH}]."
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "Could not find Uranium banch [${URANIUM_BRANCH}], try next."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
echo "Using Uranium branch ${URANIUM_BRANCH} ..."
|
echo "Using Uranium branch ${URANIUM_BRANCH} ..."
|
||||||
git clone --depth=1 -b "${URANIUM_BRANCH}" https://github.com/Ultimaker/Uranium.git "${PROJECT_DIR}"/Uranium
|
git clone --depth=1 -b "${URANIUM_BRANCH}" https://github.com/Ultimaker/Uranium.git "${PROJECT_DIR}"/Uranium
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
import zipfile
|
import zipfile
|
||||||
import os
|
import os
|
||||||
from typing import cast, Dict, List, Optional, Tuple
|
import json
|
||||||
|
from typing import cast, Dict, List, Optional, Tuple, Any
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
@ -732,7 +733,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
|
|
||||||
base_file_name = os.path.basename(file_name)
|
base_file_name = os.path.basename(file_name)
|
||||||
self.setWorkspaceName(base_file_name)
|
self.setWorkspaceName(base_file_name)
|
||||||
return nodes
|
|
||||||
|
return nodes, self._loadMetadata(file_name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]:
|
||||||
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
|
|
||||||
|
metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")]
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
|
||||||
|
for metadata_file in metadata_files:
|
||||||
|
try:
|
||||||
|
plugin_id = metadata_file.split("/")[0]
|
||||||
|
result[plugin_id] = json.loads(archive.open("%s/plugin_metadata.json" % plugin_id).read().decode("utf-8"))
|
||||||
|
except Exception:
|
||||||
|
Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def _processQualityChanges(self, global_stack):
|
def _processQualityChanges(self, global_stack):
|
||||||
if self._machine_info.quality_changes_info is None:
|
if self._machine_info.quality_changes_info is None:
|
||||||
|
@ -1005,8 +1024,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
|
|
||||||
# Set metadata fields that are missing from the global stack
|
# Set metadata fields that are missing from the global stack
|
||||||
for key, value in self._machine_info.metadata_dict.items():
|
for key, value in self._machine_info.metadata_dict.items():
|
||||||
if key not in global_stack.getMetaData():
|
global_stack.setMetaDataEntry(key, value)
|
||||||
global_stack.setMetaDataEntry(key, value)
|
|
||||||
|
|
||||||
def _updateActiveMachine(self, global_stack):
|
def _updateActiveMachine(self, global_stack):
|
||||||
# Actually change the active machine.
|
# Actually change the active machine.
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for reading 3MF files.",
|
"description": "Provides support for reading 3MF files.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,11 +73,25 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
||||||
version_config_parser.write(version_file_string)
|
version_config_parser.write(version_file_string)
|
||||||
archive.writestr(version_file, version_file_string.getvalue())
|
archive.writestr(version_file, version_file_string.getvalue())
|
||||||
|
|
||||||
|
self._writePluginMetadataToArchive(archive)
|
||||||
|
|
||||||
# Close the archive & reset states.
|
# Close the archive & reset states.
|
||||||
archive.close()
|
archive.close()
|
||||||
mesh_writer.setStoreArchive(False)
|
mesh_writer.setStoreArchive(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None:
|
||||||
|
file_name_template = "%s/plugin_metadata.json"
|
||||||
|
|
||||||
|
for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items():
|
||||||
|
file_name = file_name_template % plugin_id
|
||||||
|
file_in_archive = zipfile.ZipInfo(file_name)
|
||||||
|
# We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
|
||||||
|
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
import json
|
||||||
|
archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4, skipkeys = True))
|
||||||
|
|
||||||
## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
|
## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
|
||||||
# \param container That follows the \type{ContainerInterface} to archive.
|
# \param container That follows the \type{ContainerInterface} to archive.
|
||||||
# \param archive The archive to write to.
|
# \param archive The archive to write to.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (c) 2015 Ultimaker B.V.
|
# Copyright (c) 2015 Ultimaker B.V.
|
||||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from UM.Mesh.MeshWriter import MeshWriter
|
from UM.Mesh.MeshWriter import MeshWriter
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
@ -40,7 +41,7 @@ class ThreeMFWriter(MeshWriter):
|
||||||
}
|
}
|
||||||
|
|
||||||
self._unit_matrix_string = self._convertMatrixToString(Matrix())
|
self._unit_matrix_string = self._convertMatrixToString(Matrix())
|
||||||
self._archive = None
|
self._archive = None # type: Optional[zipfile.ZipFile]
|
||||||
self._store_archive = False
|
self._store_archive = False
|
||||||
|
|
||||||
def _convertMatrixToString(self, matrix):
|
def _convertMatrixToString(self, matrix):
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for writing 3MF files.",
|
"description": "Provides support for writing 3MF files.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
"author": "fieldOfView",
|
"author": "fieldOfView",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for reading AMF files.",
|
"description": "Provides support for reading AMF files.",
|
||||||
"api": "7.0.0"
|
"api": "7.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Backup and restore your configuration.",
|
"description": "Backup and restore your configuration.",
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,22 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if Platform.isWindows():
|
if Platform.isWindows():
|
||||||
executable_name += ".exe"
|
executable_name += ".exe"
|
||||||
default_engine_location = executable_name
|
default_engine_location = executable_name
|
||||||
if os.path.exists(os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)):
|
|
||||||
default_engine_location = os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)
|
search_path = [
|
||||||
if hasattr(sys, "frozen"):
|
os.path.abspath(os.path.dirname(sys.executable)),
|
||||||
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name)
|
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "bin")),
|
||||||
|
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")),
|
||||||
|
|
||||||
|
os.path.join(CuraApplication.getInstallPrefix(), "bin"),
|
||||||
|
os.path.dirname(os.path.abspath(sys.executable)),
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in search_path:
|
||||||
|
engine_path = os.path.join(path, executable_name)
|
||||||
|
if os.path.isfile(engine_path):
|
||||||
|
default_engine_location = engine_path
|
||||||
|
break
|
||||||
|
|
||||||
if Platform.isLinux() and not default_engine_location:
|
if Platform.isLinux() and not default_engine_location:
|
||||||
if not os.getenv("PATH"):
|
if not os.getenv("PATH"):
|
||||||
raise OSError("There is something wrong with your Linux installation.")
|
raise OSError("There is something wrong with your Linux installation.")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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.
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
@ -153,7 +153,7 @@ class StartSliceJob(Job):
|
||||||
self.setResult(StartJobResult.MaterialIncompatible)
|
self.setResult(StartJobResult.MaterialIncompatible)
|
||||||
return
|
return
|
||||||
|
|
||||||
for position, extruder_stack in stack.extruders.items():
|
for extruder_stack in stack.extruderList:
|
||||||
material = extruder_stack.findContainer({"type": "material"})
|
material = extruder_stack.findContainer({"type": "material"})
|
||||||
if not extruder_stack.isEnabled:
|
if not extruder_stack.isEnabled:
|
||||||
continue
|
continue
|
||||||
|
@ -162,7 +162,6 @@ class StartSliceJob(Job):
|
||||||
self.setResult(StartJobResult.MaterialIncompatible)
|
self.setResult(StartJobResult.MaterialIncompatible)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
# Don't slice if there is a per object setting with an error value.
|
# Don't slice if there is a per object setting with an error value.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
||||||
|
@ -172,146 +171,145 @@ class StartSliceJob(Job):
|
||||||
self.setResult(StartJobResult.ObjectSettingError)
|
self.setResult(StartJobResult.ObjectSettingError)
|
||||||
return
|
return
|
||||||
|
|
||||||
with self._scene.getSceneLock():
|
# Remove old layer data.
|
||||||
# Remove old layer data.
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
||||||
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
# Singe we walk through all nodes in the scene, they always have a parent.
|
||||||
# Singe we walk through all nodes in the scene, they always have a parent.
|
cast(SceneNode, node.getParent()).removeChild(node)
|
||||||
cast(SceneNode, node.getParent()).removeChild(node)
|
break
|
||||||
break
|
|
||||||
|
|
||||||
# Get the objects in their groups to print.
|
# Get the objects in their groups to print.
|
||||||
object_groups = []
|
object_groups = []
|
||||||
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||||
for node in OneAtATimeIterator(self._scene.getRoot()):
|
for node in OneAtATimeIterator(self._scene.getRoot()):
|
||||||
temp_list = []
|
|
||||||
|
|
||||||
# Node can't be printed, so don't bother sending it.
|
|
||||||
if getattr(node, "_outside_buildarea", False):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Filter on current build plate
|
|
||||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
|
||||||
if build_plate_number is not None and build_plate_number != self._build_plate_number:
|
|
||||||
continue
|
|
||||||
|
|
||||||
children = node.getAllChildren()
|
|
||||||
children.append(node)
|
|
||||||
for child_node in children:
|
|
||||||
mesh_data = child_node.getMeshData()
|
|
||||||
if mesh_data and mesh_data.getVertices() is not None:
|
|
||||||
temp_list.append(child_node)
|
|
||||||
|
|
||||||
if temp_list:
|
|
||||||
object_groups.append(temp_list)
|
|
||||||
Job.yieldThread()
|
|
||||||
if len(object_groups) == 0:
|
|
||||||
Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
|
|
||||||
else:
|
|
||||||
temp_list = []
|
temp_list = []
|
||||||
has_printing_mesh = False
|
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
|
||||||
mesh_data = node.getMeshData()
|
|
||||||
if node.callDecoration("isSliceable") and mesh_data and mesh_data.getVertices() is not None:
|
|
||||||
is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh"))
|
|
||||||
|
|
||||||
# Find a reason not to add the node
|
# Node can't be printed, so don't bother sending it.
|
||||||
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
|
if getattr(node, "_outside_buildarea", False):
|
||||||
continue
|
continue
|
||||||
if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh:
|
|
||||||
continue
|
|
||||||
|
|
||||||
temp_list.append(node)
|
# Filter on current build plate
|
||||||
if not is_non_printing_mesh:
|
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
has_printing_mesh = True
|
if build_plate_number is not None and build_plate_number != self._build_plate_number:
|
||||||
|
continue
|
||||||
|
|
||||||
Job.yieldThread()
|
children = node.getAllChildren()
|
||||||
|
children.append(node)
|
||||||
# If the list doesn't have any model with suitable settings then clean the list
|
for child_node in children:
|
||||||
# otherwise CuraEngine will crash
|
mesh_data = child_node.getMeshData()
|
||||||
if not has_printing_mesh:
|
if mesh_data and mesh_data.getVertices() is not None:
|
||||||
temp_list.clear()
|
temp_list.append(child_node)
|
||||||
|
|
||||||
if temp_list:
|
if temp_list:
|
||||||
object_groups.append(temp_list)
|
object_groups.append(temp_list)
|
||||||
|
Job.yieldThread()
|
||||||
|
if len(object_groups) == 0:
|
||||||
|
Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
|
||||||
|
else:
|
||||||
|
temp_list = []
|
||||||
|
has_printing_mesh = False
|
||||||
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
|
mesh_data = node.getMeshData()
|
||||||
|
if node.callDecoration("isSliceable") and mesh_data and mesh_data.getVertices() is not None:
|
||||||
|
is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh"))
|
||||||
|
|
||||||
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
# Find a reason not to add the node
|
||||||
if not global_stack:
|
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
|
||||||
return
|
continue
|
||||||
extruders_enabled = {position: stack.isEnabled for position, stack in global_stack.extruders.items()}
|
if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh:
|
||||||
filtered_object_groups = []
|
|
||||||
has_model_with_disabled_extruders = False
|
|
||||||
associated_disabled_extruders = set()
|
|
||||||
for group in object_groups:
|
|
||||||
stack = global_stack
|
|
||||||
skip_group = False
|
|
||||||
for node in group:
|
|
||||||
# Only check if the printing extruder is enabled for printing meshes
|
|
||||||
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
|
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
|
||||||
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
|
|
||||||
skip_group = True
|
|
||||||
has_model_with_disabled_extruders = True
|
|
||||||
associated_disabled_extruders.add(extruder_position)
|
|
||||||
if not skip_group:
|
|
||||||
filtered_object_groups.append(group)
|
|
||||||
|
|
||||||
if has_model_with_disabled_extruders:
|
|
||||||
self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
|
|
||||||
associated_disabled_extruders = {str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])}
|
|
||||||
self.setMessage(", ".join(associated_disabled_extruders))
|
|
||||||
return
|
|
||||||
|
|
||||||
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
|
||||||
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
|
|
||||||
# the build volume)
|
|
||||||
if not filtered_object_groups:
|
|
||||||
self.setResult(StartJobResult.NothingToSlice)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._buildGlobalSettingsMessage(stack)
|
|
||||||
self._buildGlobalInheritsStackMessage(stack)
|
|
||||||
|
|
||||||
# Build messages for extruder stacks
|
|
||||||
for extruder_stack in global_stack.extruderList:
|
|
||||||
self._buildExtruderMessage(extruder_stack)
|
|
||||||
|
|
||||||
for group in filtered_object_groups:
|
|
||||||
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
|
||||||
parent = group[0].getParent()
|
|
||||||
if parent is not None and parent.callDecoration("isGroup"):
|
|
||||||
self._handlePerObjectSettings(cast(CuraSceneNode, parent), group_message)
|
|
||||||
|
|
||||||
for object in group:
|
|
||||||
mesh_data = object.getMeshData()
|
|
||||||
if mesh_data is None:
|
|
||||||
continue
|
continue
|
||||||
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
|
|
||||||
translate = object.getWorldTransformation().getData()[:3, 3]
|
|
||||||
|
|
||||||
# This effectively performs a limited form of MeshData.getTransformed that ignores normals.
|
temp_list.append(node)
|
||||||
verts = mesh_data.getVertices()
|
if not is_non_printing_mesh:
|
||||||
verts = verts.dot(rot_scale)
|
has_printing_mesh = True
|
||||||
verts += translate
|
|
||||||
|
|
||||||
# Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
|
Job.yieldThread()
|
||||||
verts[:, [1, 2]] = verts[:, [2, 1]]
|
|
||||||
verts[:, 1] *= -1
|
|
||||||
|
|
||||||
obj = group_message.addRepeatedMessage("objects")
|
# If the list doesn't have any model with suitable settings then clean the list
|
||||||
obj.id = id(object)
|
# otherwise CuraEngine will crash
|
||||||
obj.name = object.getName()
|
if not has_printing_mesh:
|
||||||
indices = mesh_data.getIndices()
|
temp_list.clear()
|
||||||
if indices is not None:
|
|
||||||
flat_verts = numpy.take(verts, indices.flatten(), axis=0)
|
|
||||||
else:
|
|
||||||
flat_verts = numpy.array(verts)
|
|
||||||
|
|
||||||
obj.vertices = flat_verts
|
if temp_list:
|
||||||
|
object_groups.append(temp_list)
|
||||||
|
|
||||||
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
|
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
return
|
||||||
|
extruders_enabled = {position: stack.isEnabled for position, stack in global_stack.extruders.items()}
|
||||||
|
filtered_object_groups = []
|
||||||
|
has_model_with_disabled_extruders = False
|
||||||
|
associated_disabled_extruders = set()
|
||||||
|
for group in object_groups:
|
||||||
|
stack = global_stack
|
||||||
|
skip_group = False
|
||||||
|
for node in group:
|
||||||
|
# Only check if the printing extruder is enabled for printing meshes
|
||||||
|
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
|
||||||
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
|
||||||
|
skip_group = True
|
||||||
|
has_model_with_disabled_extruders = True
|
||||||
|
associated_disabled_extruders.add(extruder_position)
|
||||||
|
if not skip_group:
|
||||||
|
filtered_object_groups.append(group)
|
||||||
|
|
||||||
Job.yieldThread()
|
if has_model_with_disabled_extruders:
|
||||||
|
self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
|
||||||
|
associated_disabled_extruders = {str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])}
|
||||||
|
self.setMessage(", ".join(associated_disabled_extruders))
|
||||||
|
return
|
||||||
|
|
||||||
|
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
||||||
|
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
|
||||||
|
# the build volume)
|
||||||
|
if not filtered_object_groups:
|
||||||
|
self.setResult(StartJobResult.NothingToSlice)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._buildGlobalSettingsMessage(stack)
|
||||||
|
self._buildGlobalInheritsStackMessage(stack)
|
||||||
|
|
||||||
|
# Build messages for extruder stacks
|
||||||
|
for extruder_stack in global_stack.extruderList:
|
||||||
|
self._buildExtruderMessage(extruder_stack)
|
||||||
|
|
||||||
|
for group in filtered_object_groups:
|
||||||
|
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
||||||
|
parent = group[0].getParent()
|
||||||
|
if parent is not None and parent.callDecoration("isGroup"):
|
||||||
|
self._handlePerObjectSettings(cast(CuraSceneNode, parent), group_message)
|
||||||
|
|
||||||
|
for object in group:
|
||||||
|
mesh_data = object.getMeshData()
|
||||||
|
if mesh_data is None:
|
||||||
|
continue
|
||||||
|
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
|
||||||
|
translate = object.getWorldTransformation().getData()[:3, 3]
|
||||||
|
|
||||||
|
# This effectively performs a limited form of MeshData.getTransformed that ignores normals.
|
||||||
|
verts = mesh_data.getVertices()
|
||||||
|
verts = verts.dot(rot_scale)
|
||||||
|
verts += translate
|
||||||
|
|
||||||
|
# Convert from Y up axes to Z up axes. Equals a 90 degree rotation.
|
||||||
|
verts[:, [1, 2]] = verts[:, [2, 1]]
|
||||||
|
verts[:, 1] *= -1
|
||||||
|
|
||||||
|
obj = group_message.addRepeatedMessage("objects")
|
||||||
|
obj.id = id(object)
|
||||||
|
obj.name = object.getName()
|
||||||
|
indices = mesh_data.getIndices()
|
||||||
|
if indices is not None:
|
||||||
|
flat_verts = numpy.take(verts, indices.flatten(), axis=0)
|
||||||
|
else:
|
||||||
|
flat_verts = numpy.array(verts)
|
||||||
|
|
||||||
|
obj.vertices = flat_verts
|
||||||
|
|
||||||
|
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
|
||||||
|
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
self.setResult(StartJobResult.Finished)
|
self.setResult(StartJobResult.Finished)
|
||||||
|
|
||||||
|
@ -345,10 +343,7 @@ class StartSliceJob(Job):
|
||||||
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
|
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
|
||||||
result["date"] = time.strftime("%d-%m-%Y")
|
result["date"] = time.strftime("%d-%m-%Y")
|
||||||
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
||||||
|
result["initial_extruder_nr"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr()
|
||||||
initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
|
||||||
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
|
||||||
result["initial_extruder_nr"] = initial_extruder_nr
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "CuraEngine Backend",
|
"name": "CuraEngine Backend",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing Cura profiles.",
|
"description": "Provides support for importing Cura profiles.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for exporting Cura profiles.",
|
"description": "Provides support for exporting Cura profiles.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog":"cura"
|
"i18n-catalog":"cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Checks for firmware updates.",
|
"description": "Checks for firmware updates.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a machine actions for updating firmware.",
|
"description": "Provides a machine actions for updating firmware.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Reads g-code from a compressed archive.",
|
"description": "Reads g-code from a compressed archive.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Writes g-code to a compressed archive.",
|
"description": "Writes g-code to a compressed archive.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing profiles from g-code files.",
|
"description": "Provides support for importing profiles from g-code files.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 math
|
import math
|
||||||
|
@ -258,16 +258,19 @@ class FlavorParser:
|
||||||
continue
|
continue
|
||||||
if item.startswith(";"):
|
if item.startswith(";"):
|
||||||
continue
|
continue
|
||||||
if item[0] == "X":
|
try:
|
||||||
x = float(item[1:])
|
if item[0] == "X":
|
||||||
if item[0] == "Y":
|
x = float(item[1:])
|
||||||
y = float(item[1:])
|
if item[0] == "Y":
|
||||||
if item[0] == "Z":
|
y = float(item[1:])
|
||||||
z = float(item[1:])
|
if item[0] == "Z":
|
||||||
if item[0] == "F":
|
z = float(item[1:])
|
||||||
f = float(item[1:]) / 60
|
if item[0] == "F":
|
||||||
if item[0] == "E":
|
f = float(item[1:]) / 60
|
||||||
e = float(item[1:])
|
if item[0] == "E":
|
||||||
|
e = float(item[1:])
|
||||||
|
except ValueError: # Improperly formatted g-code: Coordinates are not floats.
|
||||||
|
continue # Skip the command then.
|
||||||
params = PositionOptional(x, y, z, f, e)
|
params = PositionOptional(x, y, z, f, e)
|
||||||
return func(position, params, path)
|
return func(position, params, path)
|
||||||
return position
|
return position
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "G-code Reader",
|
"name": "G-code Reader",
|
||||||
"author": "Victor Larchenko, Ultimaker",
|
"author": "Victor Larchenko, Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Allows loading and displaying G-code files.",
|
"description": "Allows loading and displaying G-code files.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Writes g-code to a file.",
|
"description": "Writes g-code to a file.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "Machine Settings action",
|
"name": "Machine Settings Action",
|
||||||
"author": "fieldOfView",
|
"author": "fieldOfView, Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Model Checker",
|
"name": "Model Checker",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a monitor stage in Cura.",
|
"description": "Provides a monitor stage in Cura.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides the Per Model Settings.",
|
"description": "Provides the Per Model Settings.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
# There can be duplicates, which will be executed in sequence.
|
# There can be duplicates, which will be executed in sequence.
|
||||||
self._script_list = [] # type: List[Script]
|
self._script_list = [] # type: List[Script]
|
||||||
self._selected_script_index = -1
|
self._selected_script_index = -1
|
||||||
|
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if self._global_container_stack:
|
||||||
|
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||||
|
|
||||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
|
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) # When the current printer changes, update the list of scripts.
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) # When the current printer changes, update the list of scripts.
|
||||||
|
@ -209,33 +212,34 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
self.scriptListChanged.emit()
|
self.scriptListChanged.emit()
|
||||||
self._propertyChanged()
|
self._propertyChanged()
|
||||||
|
|
||||||
## When the global container stack is changed, swap out the list of active
|
def _restoreScriptInforFromMetadata(self):
|
||||||
# scripts.
|
|
||||||
def _onGlobalContainerStackChanged(self) -> None:
|
|
||||||
self.loadAllScripts()
|
self.loadAllScripts()
|
||||||
new_stack = Application.getInstance().getGlobalContainerStack()
|
new_stack = self._global_container_stack
|
||||||
if new_stack is None:
|
if new_stack is None:
|
||||||
return
|
return
|
||||||
self._script_list.clear()
|
self._script_list.clear()
|
||||||
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
|
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
|
||||||
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
||||||
self.setSelectedScriptIndex(-1)
|
self.setSelectedScriptIndex(-1)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._script_list.clear()
|
self._script_list.clear()
|
||||||
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
|
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
|
||||||
for script_str in scripts_list_strs.split("\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
for script_str in scripts_list_strs.split(
|
||||||
|
"\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
||||||
if not script_str: # There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
if not script_str: # There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
||||||
continue
|
continue
|
||||||
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") # Unescape escape sequences.
|
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") # Unescape escape sequences.
|
||||||
script_parser = configparser.ConfigParser(interpolation = None)
|
script_parser = configparser.ConfigParser(interpolation=None)
|
||||||
script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
|
script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
|
||||||
script_parser.read_string(script_str)
|
script_parser.read_string(script_str)
|
||||||
for script_name, settings in script_parser.items(): # There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
|
for script_name, settings in script_parser.items(): # There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
|
||||||
if script_name == "DEFAULT": # ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
|
if script_name == "DEFAULT": # ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
|
||||||
continue
|
continue
|
||||||
if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
|
if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
|
||||||
Logger.log("e", "Unknown post-processing script {script_name} was encountered in this global stack.".format(script_name = script_name))
|
Logger.log("e",
|
||||||
|
"Unknown post-processing script {script_name} was encountered in this global stack.".format(
|
||||||
|
script_name=script_name))
|
||||||
continue
|
continue
|
||||||
new_script = self._loaded_scripts[script_name]()
|
new_script = self._loaded_scripts[script_name]()
|
||||||
new_script.initialize()
|
new_script.initialize()
|
||||||
|
@ -245,7 +249,22 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
self._script_list.append(new_script)
|
self._script_list.append(new_script)
|
||||||
|
|
||||||
self.setSelectedScriptIndex(0)
|
self.setSelectedScriptIndex(0)
|
||||||
|
# Ensure that we always force an update (otherwise the fields don't update correctly!)
|
||||||
|
self.selectedIndexChanged.emit()
|
||||||
self.scriptListChanged.emit()
|
self.scriptListChanged.emit()
|
||||||
|
self._propertyChanged()
|
||||||
|
|
||||||
|
## When the global container stack is changed, swap out the list of active
|
||||||
|
# scripts.
|
||||||
|
def _onGlobalContainerStackChanged(self) -> None:
|
||||||
|
if self._global_container_stack:
|
||||||
|
self._global_container_stack.metaDataChanged.disconnect(self._restoreScriptInforFromMetadata)
|
||||||
|
|
||||||
|
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
|
if self._global_container_stack:
|
||||||
|
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||||
|
self._restoreScriptInforFromMetadata()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def writeScriptsToStack(self) -> None:
|
def writeScriptsToStack(self) -> None:
|
||||||
|
@ -267,14 +286,18 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
|
|
||||||
script_list_string = "\n".join(script_list_strs) # ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
script_list_string = "\n".join(script_list_strs) # ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
if self._global_container_stack is None:
|
||||||
if global_stack is None:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if "post_processing_scripts" not in global_stack.getMetaData():
|
# Ensure we don't get triggered by our own write.
|
||||||
global_stack.setMetaDataEntry("post_processing_scripts", "")
|
self._global_container_stack.metaDataChanged.disconnect(self._restoreScriptInforFromMetadata)
|
||||||
|
|
||||||
global_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
|
if "post_processing_scripts" not in self._global_container_stack.getMetaData():
|
||||||
|
self._global_container_stack.setMetaDataEntry("post_processing_scripts", "")
|
||||||
|
|
||||||
|
self._global_container_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
|
||||||
|
# We do want to listen to other events.
|
||||||
|
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||||
|
|
||||||
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
||||||
def _createView(self) -> None:
|
def _createView(self) -> None:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Post Processing",
|
"name": "Post Processing",
|
||||||
"author": "Ultimaker",
|
"author": "Ultimaker",
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"description": "Extension that allows for user created scripts for post processing",
|
"description": "Extension that allows for user created scripts for post processing",
|
||||||
"catalog": "cura"
|
"catalog": "cura"
|
||||||
}
|
}
|
|
@ -72,18 +72,25 @@ class DisplayFilenameAndLayerOnLCD(Script):
|
||||||
lcd_text = "M117 Printing " + name + " - Layer "
|
lcd_text = "M117 Printing " + name + " - Layer "
|
||||||
i = self.getSettingValueByKey("startNum")
|
i = self.getSettingValueByKey("startNum")
|
||||||
for layer in data:
|
for layer in data:
|
||||||
display_text = lcd_text + str(i) + " " + name
|
display_text = lcd_text + str(i)
|
||||||
layer_index = data.index(layer)
|
layer_index = data.index(layer)
|
||||||
lines = layer.split("\n")
|
lines = layer.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith(";LAYER_COUNT:"):
|
if line.startswith(";LAYER_COUNT:"):
|
||||||
max_layer = line
|
max_layer = line
|
||||||
max_layer = max_layer.split(":")[1]
|
max_layer = max_layer.split(":")[1]
|
||||||
|
if self.getSettingValueByKey("startNum") == 0:
|
||||||
|
max_layer = str(int(max_layer) - 1)
|
||||||
if line.startswith(";LAYER:"):
|
if line.startswith(";LAYER:"):
|
||||||
if self.getSettingValueByKey("maxlayer"):
|
if self.getSettingValueByKey("maxlayer"):
|
||||||
display_text = display_text + " of " + max_layer
|
display_text = display_text + " of " + max_layer
|
||||||
|
if not self.getSettingValueByKey("scroll"):
|
||||||
|
display_text = display_text + " " + name
|
||||||
else:
|
else:
|
||||||
display_text = display_text + "!"
|
if not self.getSettingValueByKey("scroll"):
|
||||||
|
display_text = display_text + " " + name + "!"
|
||||||
|
else:
|
||||||
|
display_text = display_text + "!"
|
||||||
line_index = lines.index(line)
|
line_index = lines.index(line)
|
||||||
lines.insert(line_index + 1, display_text)
|
lines.insert(line_index + 1, display_text)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
|
@ -35,7 +35,7 @@ class GCodeStep():
|
||||||
Class to store the current value of each G_Code parameter
|
Class to store the current value of each G_Code parameter
|
||||||
for any G-Code step
|
for any G-Code step
|
||||||
"""
|
"""
|
||||||
def __init__(self, step, in_relative_movement: bool = False):
|
def __init__(self, step, in_relative_movement: bool = False) -> None:
|
||||||
self.step = step
|
self.step = step
|
||||||
self.step_x = 0
|
self.step_x = 0
|
||||||
self.step_y = 0
|
self.step_y = 0
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from ..Script import Script
|
from ..Script import Script
|
||||||
|
|
||||||
|
|
||||||
class TimeLapse(Script):
|
class TimeLapse(Script):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -75,21 +76,29 @@ class TimeLapse(Script):
|
||||||
trigger_command = self.getSettingValueByKey("trigger_command")
|
trigger_command = self.getSettingValueByKey("trigger_command")
|
||||||
pause_length = self.getSettingValueByKey("pause_length")
|
pause_length = self.getSettingValueByKey("pause_length")
|
||||||
gcode_to_append = ";TimeLapse Begin\n"
|
gcode_to_append = ";TimeLapse Begin\n"
|
||||||
|
last_x = 0
|
||||||
|
last_y = 0
|
||||||
|
|
||||||
if park_print_head:
|
if park_print_head:
|
||||||
gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + " ;Park print head\n"
|
gcode_to_append += self.putValue(G=1, F=feed_rate,
|
||||||
gcode_to_append += self.putValue(M = 400) + " ;Wait for moves to finish\n"
|
X=x_park, Y=y_park) + " ;Park print head\n"
|
||||||
|
gcode_to_append += self.putValue(M=400) + " ;Wait for moves to finish\n"
|
||||||
gcode_to_append += trigger_command + " ;Snap Photo\n"
|
gcode_to_append += trigger_command + " ;Snap Photo\n"
|
||||||
gcode_to_append += self.putValue(G = 4, P = pause_length) + " ;Wait for camera\n"
|
gcode_to_append += self.putValue(G=4, P=pause_length) + " ;Wait for camera\n"
|
||||||
gcode_to_append += ";TimeLapse End\n"
|
|
||||||
for layer in data:
|
for idx, layer in enumerate(data):
|
||||||
|
for line in layer.split("\n"):
|
||||||
|
if self.getValue(line, "G") in {0, 1}: # Track X,Y location.
|
||||||
|
last_x = self.getValue(line, "X", last_x)
|
||||||
|
last_y = self.getValue(line, "Y", last_y)
|
||||||
# Check that a layer is being printed
|
# Check that a layer is being printed
|
||||||
lines = layer.split("\n")
|
lines = layer.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if ";LAYER:" in line:
|
if ";LAYER:" in line:
|
||||||
index = data.index(layer)
|
|
||||||
layer += gcode_to_append
|
layer += gcode_to_append
|
||||||
|
|
||||||
data[index] = layer
|
layer += "G0 X%s Y%s\n" % (last_x, last_y)
|
||||||
|
|
||||||
|
data[idx] = layer
|
||||||
break
|
break
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a prepare stage in Cura.",
|
"description": "Provides a prepare stage in Cura.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a preview stage in Cura.",
|
"description": "Provides a preview stage in Cura.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides removable drive hotplugging and writing support.",
|
"description": "Provides removable drive hotplugging and writing support.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
|
|
||||||
from UM.Logger import LogOutput
|
from UM.Logger import LogOutput
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from sentry_sdk import add_breadcrumb
|
try:
|
||||||
|
from sentry_sdk import add_breadcrumb
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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 TYPE_CHECKING, Dict, Any
|
from typing import TYPE_CHECKING, Dict, Any
|
||||||
|
try:
|
||||||
|
import sentry_sdk
|
||||||
|
has_sentry = True
|
||||||
|
except ImportError:
|
||||||
|
has_sentry = False
|
||||||
|
|
||||||
from . import SentryLogger
|
from . import SentryLogger
|
||||||
|
|
||||||
|
@ -13,4 +18,6 @@ def getMetaData() -> Dict[str, Any]:
|
||||||
|
|
||||||
|
|
||||||
def register(app: "Application") -> Dict[str, Any]:
|
def register(app: "Application") -> Dict[str, Any]:
|
||||||
|
if not has_sentry:
|
||||||
|
return {} # Nothing to do here!
|
||||||
return {"logger": SentryLogger.SentryLogger()}
|
return {"logger": SentryLogger.SentryLogger()}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Logs certain events so that they can be used by the crash reporter",
|
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,10 @@ class SimulationPass(RenderPass):
|
||||||
end = self._layer_view.end_elements_index
|
end = self._layer_view.end_elements_index
|
||||||
index = self._layer_view._current_path_num
|
index = self._layer_view._current_path_num
|
||||||
offset = 0
|
offset = 0
|
||||||
for polygon in layer_data.getLayer(self._layer_view._current_layer_num).polygons:
|
layer = layer_data.getLayer(self._layer_view._current_layer_num)
|
||||||
|
if layer is None:
|
||||||
|
continue
|
||||||
|
for polygon in layer.polygons:
|
||||||
# The size indicates all values in the two-dimension array, and the second dimension is
|
# The size indicates all values in the two-dimension array, and the second dimension is
|
||||||
# always size 3 because we have 3D points.
|
# always size 3 because we have 3D points.
|
||||||
if index >= polygon.data.size // 3 - offset:
|
if index >= polygon.data.size // 3 - offset:
|
||||||
|
|
|
@ -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 sys
|
import sys
|
||||||
|
@ -116,8 +116,9 @@ class SimulationView(CuraView):
|
||||||
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
||||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||||
|
|
||||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled."),
|
||||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||||
|
self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layers to show"))
|
||||||
|
|
||||||
QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||||
|
|
||||||
|
@ -149,6 +150,7 @@ class SimulationView(CuraView):
|
||||||
if self._activity == activity:
|
if self._activity == activity:
|
||||||
return
|
return
|
||||||
self._activity = activity
|
self._activity = activity
|
||||||
|
self._updateSliceWarningVisibility()
|
||||||
self.activityChanged.emit()
|
self.activityChanged.emit()
|
||||||
|
|
||||||
def getSimulationPass(self) -> SimulationPass:
|
def getSimulationPass(self) -> SimulationPass:
|
||||||
|
@ -543,11 +545,13 @@ class SimulationView(CuraView):
|
||||||
self._composite_pass.getLayerBindings().append("simulationview")
|
self._composite_pass.getLayerBindings().append("simulationview")
|
||||||
self._old_composite_shader = self._composite_pass.getCompositeShader()
|
self._old_composite_shader = self._composite_pass.getCompositeShader()
|
||||||
self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
|
self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
|
||||||
|
self._updateSliceWarningVisibility()
|
||||||
|
|
||||||
elif event.type == Event.ViewDeactivateEvent:
|
elif event.type == Event.ViewDeactivateEvent:
|
||||||
self._controller.getScene().getRoot().childrenChanged.disconnect(self._onSceneChanged)
|
self._controller.getScene().getRoot().childrenChanged.disconnect(self._onSceneChanged)
|
||||||
Application.getInstance().getPreferences().preferenceChanged.disconnect(self._onPreferencesChanged)
|
Application.getInstance().getPreferences().preferenceChanged.disconnect(self._onPreferencesChanged)
|
||||||
self._wireprint_warning_message.hide()
|
self._wireprint_warning_message.hide()
|
||||||
|
self._slice_first_warning_message.hide()
|
||||||
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
|
@ -661,6 +665,12 @@ class SimulationView(CuraView):
|
||||||
|
|
||||||
self._updateWithPreferences()
|
self._updateWithPreferences()
|
||||||
|
|
||||||
|
def _updateSliceWarningVisibility(self):
|
||||||
|
if not self.getActivity():
|
||||||
|
self._slice_first_warning_message.show()
|
||||||
|
else:
|
||||||
|
self._slice_first_warning_message.hide()
|
||||||
|
|
||||||
|
|
||||||
class _CreateTopLayersJob(Job):
|
class _CreateTopLayersJob(Job):
|
||||||
def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:
|
def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:
|
||||||
|
|
|
@ -112,7 +112,7 @@ Cura.ExpandableComponent
|
||||||
type_id: 1
|
type_id: 1
|
||||||
})
|
})
|
||||||
layerViewTypes.append({
|
layerViewTypes.append({
|
||||||
text: catalog.i18nc("@label:listbox", "Feedrate"),
|
text: catalog.i18nc("@label:listbox", "Speed"),
|
||||||
type_id: 2
|
type_id: 2
|
||||||
})
|
})
|
||||||
layerViewTypes.append({
|
layerViewTypes.append({
|
||||||
|
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class SimulationViewProxy(QObject):
|
class SimulationViewProxy(QObject):
|
||||||
def __init__(self, simulation_view: "SimulationView", parent=None):
|
def __init__(self, simulation_view: "SimulationView", parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._simulation_view = simulation_view
|
self._simulation_view = simulation_view
|
||||||
self._current_layer = 0
|
self._current_layer = 0
|
||||||
|
|
|
@ -80,7 +80,7 @@ vertex41core =
|
||||||
case 1: // "Line type"
|
case 1: // "Line type"
|
||||||
v_color = a_color;
|
v_color = a_color;
|
||||||
break;
|
break;
|
||||||
case 2: // "Feedrate"
|
case 2: // "Speed", or technically 'Feedrate'
|
||||||
v_color = feedrateGradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
|
v_color = feedrateGradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
|
||||||
break;
|
break;
|
||||||
case 3: // "Layer thickness"
|
case 3: // "Layer thickness"
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides the Simulation view.",
|
"description": "Provides the Simulation view.",
|
||||||
"api": "7.0",
|
"api": "7.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ Window
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
|
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ Window
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
textArea.text: manager.getExampleData()
|
textArea.text: (manager === null) ? "" : manager.getExampleData()
|
||||||
textArea.textFormat: Text.RichText
|
textArea.textFormat: Text.RichText
|
||||||
textArea.wrapMode: Text.Wrap
|
textArea.wrapMode: Text.Wrap
|
||||||
textArea.readOnly: true
|
textArea.readOnly: true
|
||||||
|
|
|
@ -5,14 +5,13 @@ import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
from typing import cast, Optional, Set
|
from typing import cast, Optional, Set, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtSlot, QObject
|
||||||
|
from PyQt5.QtNetwork import QNetworkRequest
|
||||||
|
|
||||||
from UM.Extension import Extension
|
from UM.Extension import Extension
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Message import Message
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
@ -20,7 +19,8 @@ from UM.Qt.Duration import DurationFormat
|
||||||
|
|
||||||
from cura import ApplicationMetadata
|
from cura import ApplicationMetadata
|
||||||
|
|
||||||
from .SliceInfoJob import SliceInfoJob
|
if TYPE_CHECKING:
|
||||||
|
from PyQt5.QtNetwork import QNetworkReply
|
||||||
|
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
@ -36,7 +36,8 @@ class SliceInfo(QObject, Extension):
|
||||||
QObject.__init__(self, parent)
|
QObject.__init__(self, parent)
|
||||||
Extension.__init__(self)
|
Extension.__init__(self)
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
from cura.CuraApplication import CuraApplication
|
||||||
|
self._application = CuraApplication.getInstance()
|
||||||
|
|
||||||
self._application.getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
|
self._application.getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
|
||||||
self._application.getPreferences().addPreference("info/send_slice_info", True)
|
self._application.getPreferences().addPreference("info/send_slice_info", True)
|
||||||
|
@ -56,7 +57,7 @@ class SliceInfo(QObject, Extension):
|
||||||
## Perform action based on user input.
|
## Perform action based on user input.
|
||||||
# Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
|
# Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
|
||||||
def messageActionTriggered(self, message_id, action_id):
|
def messageActionTriggered(self, message_id, action_id):
|
||||||
Application.getInstance().getPreferences().setValue("info/asked_send_slice_info", True)
|
self._application.getPreferences().setValue("info/asked_send_slice_info", True)
|
||||||
if action_id == "MoreInfo":
|
if action_id == "MoreInfo":
|
||||||
self.showMoreInfoDialog()
|
self.showMoreInfoDialog()
|
||||||
self.send_slice_info_message.hide()
|
self.send_slice_info_message.hide()
|
||||||
|
@ -69,7 +70,7 @@ class SliceInfo(QObject, Extension):
|
||||||
def _createDialog(self, qml_name):
|
def _createDialog(self, qml_name):
|
||||||
Logger.log("d", "Creating dialog [%s]", qml_name)
|
Logger.log("d", "Creating dialog [%s]", qml_name)
|
||||||
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
|
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
|
||||||
dialog = Application.getInstance().createQmlComponent(file_path, {"manager": self})
|
dialog = self._application.createQmlComponent(file_path, {"manager": self})
|
||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
|
@ -87,12 +88,10 @@ class SliceInfo(QObject, Extension):
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
def setSendSliceInfo(self, enabled: bool):
|
def setSendSliceInfo(self, enabled: bool):
|
||||||
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
|
self._application.getPreferences().setValue("info/send_slice_info", enabled)
|
||||||
|
|
||||||
def _getUserModifiedSettingKeys(self) -> list:
|
def _getUserModifiedSettingKeys(self) -> list:
|
||||||
from cura.CuraApplication import CuraApplication
|
machine_manager = self._application.getMachineManager()
|
||||||
application = cast(CuraApplication, Application.getInstance())
|
|
||||||
machine_manager = application.getMachineManager()
|
|
||||||
global_stack = machine_manager.activeMachine
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
user_modified_setting_keys = set() # type: Set[str]
|
user_modified_setting_keys = set() # type: Set[str]
|
||||||
|
@ -106,30 +105,28 @@ class SliceInfo(QObject, Extension):
|
||||||
|
|
||||||
def _onWriteStarted(self, output_device):
|
def _onWriteStarted(self, output_device):
|
||||||
try:
|
try:
|
||||||
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
|
if not self._application.getPreferences().getValue("info/send_slice_info"):
|
||||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||||
return # Do nothing, user does not want to send data
|
return # Do nothing, user does not want to send data
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
machine_manager = self._application.getMachineManager()
|
||||||
application = cast(CuraApplication, Application.getInstance())
|
print_information = self._application.getPrintInformation()
|
||||||
machine_manager = application.getMachineManager()
|
|
||||||
print_information = application.getPrintInformation()
|
|
||||||
|
|
||||||
global_stack = machine_manager.activeMachine
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
data = dict() # The data that we're going to submit.
|
data = dict() # The data that we're going to submit.
|
||||||
data["time_stamp"] = time.time()
|
data["time_stamp"] = time.time()
|
||||||
data["schema_version"] = 0
|
data["schema_version"] = 0
|
||||||
data["cura_version"] = application.getVersion()
|
data["cura_version"] = self._application.getVersion()
|
||||||
data["cura_build_type"] = ApplicationMetadata.CuraBuildType
|
data["cura_build_type"] = ApplicationMetadata.CuraBuildType
|
||||||
|
|
||||||
active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode")
|
active_mode = self._application.getPreferences().getValue("cura/active_mode")
|
||||||
if active_mode == 0:
|
if active_mode == 0:
|
||||||
data["active_mode"] = "recommended"
|
data["active_mode"] = "recommended"
|
||||||
else:
|
else:
|
||||||
data["active_mode"] = "custom"
|
data["active_mode"] = "custom"
|
||||||
|
|
||||||
data["camera_view"] = application.getPreferences().getValue("general/camera_perspective_mode")
|
data["camera_view"] = self._application.getPreferences().getValue("general/camera_perspective_mode")
|
||||||
if data["camera_view"] == "orthographic":
|
if data["camera_view"] == "orthographic":
|
||||||
data["camera_view"] = "orthogonal" #The database still only recognises the old name "orthogonal".
|
data["camera_view"] = "orthogonal" #The database still only recognises the old name "orthogonal".
|
||||||
|
|
||||||
|
@ -142,7 +139,7 @@ class SliceInfo(QObject, Extension):
|
||||||
machine_settings_changed_by_user = True
|
machine_settings_changed_by_user = True
|
||||||
|
|
||||||
data["machine_settings_changed_by_user"] = machine_settings_changed_by_user
|
data["machine_settings_changed_by_user"] = machine_settings_changed_by_user
|
||||||
data["language"] = Application.getInstance().getPreferences().getValue("general/language")
|
data["language"] = self._application.getPreferences().getValue("general/language")
|
||||||
data["os"] = {"type": platform.system(), "version": platform.version()}
|
data["os"] = {"type": platform.system(), "version": platform.version()}
|
||||||
|
|
||||||
data["active_machine"] = {"definition_id": global_stack.definition.getId(),
|
data["active_machine"] = {"definition_id": global_stack.definition.getId(),
|
||||||
|
@ -184,7 +181,7 @@ class SliceInfo(QObject, Extension):
|
||||||
|
|
||||||
data["models"] = []
|
data["models"] = []
|
||||||
# Listing all files placed on the build plate
|
# Listing all files placed on the build plate
|
||||||
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()):
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
model = dict()
|
model = dict()
|
||||||
model["hash"] = node.getMeshData().getHash()
|
model["hash"] = node.getMeshData().getHash()
|
||||||
|
@ -263,10 +260,23 @@ class SliceInfo(QObject, Extension):
|
||||||
# Convert data to bytes
|
# Convert data to bytes
|
||||||
binary_data = json.dumps(data).encode("utf-8")
|
binary_data = json.dumps(data).encode("utf-8")
|
||||||
|
|
||||||
# Sending slice info non-blocking
|
# Send slice info non-blocking
|
||||||
reportJob = SliceInfoJob(self.info_url, binary_data)
|
network_manager = self._application.getHttpRequestManager()
|
||||||
reportJob.start()
|
network_manager.post(self.info_url, data = binary_data,
|
||||||
|
callback = self._onRequestFinished, error_callback = self._onRequestError)
|
||||||
except Exception:
|
except Exception:
|
||||||
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
|
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
|
||||||
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
|
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
|
||||||
Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.
|
Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.
|
||||||
|
|
||||||
|
def _onRequestFinished(self, reply: "QNetworkReply") -> None:
|
||||||
|
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||||
|
if status_code == 200:
|
||||||
|
Logger.log("i", "SliceInfo sent successfully")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = reply.readAll().data().decode("utf-8")
|
||||||
|
Logger.log("e", "SliceInfo request failed, status code %s, data: %s", status_code, data)
|
||||||
|
|
||||||
|
def _onRequestError(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||||
|
Logger.log("e", "Got error for SliceInfo request: %s", reply.errorString())
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
from UM.Job import Job
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from UM.Platform import Platform
|
|
||||||
|
|
||||||
import ssl
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
|
|
||||||
import certifi
|
|
||||||
|
|
||||||
|
|
||||||
class SliceInfoJob(Job):
|
|
||||||
def __init__(self, url, data):
|
|
||||||
super().__init__()
|
|
||||||
self._url = url
|
|
||||||
self._data = data
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self._url or not self._data:
|
|
||||||
Logger.log("e", "URL or DATA for sending slice info was not set!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
|
|
||||||
context = ssl.SSLContext(protocol = ssl.PROTOCOL_TLSv1_2)
|
|
||||||
context.load_verify_locations(cafile = certifi.where())
|
|
||||||
|
|
||||||
# Submit data
|
|
||||||
kwoptions = {"data": self._data,
|
|
||||||
"timeout": 5,
|
|
||||||
"context": context}
|
|
||||||
|
|
||||||
Logger.log("i", "Sending anonymous slice info to [%s]...", self._url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
f = urllib.request.urlopen(self._url, **kwoptions)
|
|
||||||
Logger.log("i", "Sent anonymous slice info.")
|
|
||||||
f.close()
|
|
||||||
except urllib.error.HTTPError:
|
|
||||||
Logger.logException("e", "An HTTP error occurred while trying to send slice information")
|
|
||||||
except Exception: # We don't want any exception to cause problems
|
|
||||||
Logger.logException("e", "An exception occurred while trying to send slice information")
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue