mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-13 09:47:50 -06:00
Merge branch 'master' into fixes_toolbox
This commit is contained in:
commit
1d6ba3ffb0
1432 changed files with 55649 additions and 45174 deletions
43
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us fix issues.
|
||||||
|
title: ''
|
||||||
|
labels: 'Type: Bug'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Processing an issue will go much faster when this is filled out, and issues which do not use this template WILL BE REMOVED and no fix will be considered!
|
||||||
|
|
||||||
|
Before filing, PLEASE check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment.
|
||||||
|
|
||||||
|
Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do NOT write things like "Request:" or "[BUG]" in the title; this is what labels are for.
|
||||||
|
|
||||||
|
It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker. Information about how to find the log file can be found at https://github.com/Ultimaker/Cura#logging-issues
|
||||||
|
|
||||||
|
To upload a project, try changing the extension to e.g. .curaproject.3mf.zip so that GitHub accepts uploading the file. Otherwise, we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too.
|
||||||
|
|
||||||
|
Thank you for using Cura!
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Application version**
|
||||||
|
<!-- The version of the application this issue occurs with -->
|
||||||
|
|
||||||
|
**Platform**
|
||||||
|
<!-- Information about the operating system the issue occurs on. Include at least the operating system. In the case of visual glitches/issues, also include information about your graphics drivers and GPU. -->
|
||||||
|
|
||||||
|
**Printer**
|
||||||
|
<!-- Which printer was selected in Cura? If possible, please attach project file as .curaproject.3mf.zip -->
|
||||||
|
|
||||||
|
**Reproduction steps**
|
||||||
|
<!-- How did you encounter the bug? -->
|
||||||
|
|
||||||
|
**Actual results**
|
||||||
|
<!-- What happens after the above steps have been followed -->
|
||||||
|
|
||||||
|
**Expected results**
|
||||||
|
<!-- What should happen after the above steps have been followed -->
|
||||||
|
|
||||||
|
**Additional information**
|
||||||
|
<!-- Extra information relevant to the issue, like screenshots. Don't forget to attach the log files with this issue report. -->
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: 'Type: New Feature'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!--A clear and concise description of what you want to happen. If possible, describe why you think this is a good solution.-->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. Again, if possible, think about why these alternatives are not working out. -->
|
||||||
|
|
||||||
|
**Affected users and/or printers**
|
||||||
|
<!-- Who do you think will benefit from this? Is everyone going to benefit from these changes? Only a few people? -->
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
|
@ -10,3 +10,10 @@ build-and-test:
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- build
|
- build
|
||||||
|
|
||||||
|
build-and-test_merge-requests:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- docker/build.sh
|
||||||
|
only:
|
||||||
|
- merge_requests
|
||||||
|
|
|
@ -20,11 +20,12 @@ set(CURA_APP_NAME "cura" CACHE STRING "Short name of Cura, used for configuratio
|
||||||
set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura")
|
set(CURA_APP_DISPLAY_NAME "Ultimaker Cura" CACHE STRING "Display name of Cura")
|
||||||
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
|
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
|
||||||
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
||||||
set(CURA_SDK_VERSION "" CACHE STRING "SDK version of Cura")
|
|
||||||
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")
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
|
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||||
DEFAULT_CURA_VERSION = "master"
|
DEFAULT_CURA_VERSION = "master"
|
||||||
DEFAULT_CURA_BUILD_TYPE = ""
|
DEFAULT_CURA_BUILD_TYPE = ""
|
||||||
DEFAULT_CURA_DEBUG_MODE = False
|
DEFAULT_CURA_DEBUG_MODE = False
|
||||||
DEFAULT_CURA_SDK_VERSION = "6.0.0"
|
DEFAULT_CURA_SDK_VERSION = "6.1.0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraAppName # type: ignore
|
from cura.CuraVersion import CuraAppName # type: ignore
|
||||||
|
@ -42,9 +42,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CuraDebugMode = DEFAULT_CURA_DEBUG_MODE
|
CuraDebugMode = DEFAULT_CURA_DEBUG_MODE
|
||||||
|
|
||||||
try:
|
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
||||||
from cura.CuraVersion import CuraSDKVersion # type: ignore
|
# 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
|
||||||
if CuraSDKVersion == "":
|
# CuraVersion.py.in template.
|
||||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
CuraSDKVersion = "6.1.0"
|
||||||
except ImportError:
|
|
||||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
|
#Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
#Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
import copy
|
import copy
|
||||||
|
from typing import Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
## Polygon representation as an array for use with Arrange
|
## Polygon representation as an array for use with Arrange
|
||||||
class ShapeArray:
|
class ShapeArray:
|
||||||
def __init__(self, arr, offset_x, offset_y, scale = 1):
|
def __init__(self, arr: numpy.array, offset_x: float, offset_y: float, scale: float = 1) -> None:
|
||||||
self.arr = arr
|
self.arr = arr
|
||||||
self.offset_x = offset_x
|
self.offset_x = offset_x
|
||||||
self.offset_y = offset_y
|
self.offset_y = offset_y
|
||||||
|
@ -16,7 +22,7 @@ class ShapeArray:
|
||||||
# \param vertices
|
# \param vertices
|
||||||
# \param scale scale the coordinates
|
# \param scale scale the coordinates
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromPolygon(cls, vertices, scale = 1):
|
def fromPolygon(cls, vertices: numpy.array, scale: float = 1) -> "ShapeArray":
|
||||||
# scale
|
# scale
|
||||||
vertices = vertices * scale
|
vertices = vertices * scale
|
||||||
# flip y, x -> x, y
|
# flip y, x -> x, y
|
||||||
|
@ -42,7 +48,7 @@ class ShapeArray:
|
||||||
# \param min_offset offset for the offset ShapeArray
|
# \param min_offset offset for the offset ShapeArray
|
||||||
# \param scale scale the coordinates
|
# \param scale scale the coordinates
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromNode(cls, node, min_offset, scale = 0.5, include_children = False):
|
def fromNode(cls, node: "SceneNode", min_offset: float, scale: float = 0.5, include_children: bool = False) -> Tuple[Optional["ShapeArray"], Optional["ShapeArray"]]:
|
||||||
transform = node._transformation
|
transform = node._transformation
|
||||||
transform_x = transform._data[0][3]
|
transform_x = transform._data[0][3]
|
||||||
transform_y = transform._data[2][3]
|
transform_y = transform._data[2][3]
|
||||||
|
@ -88,7 +94,7 @@ class ShapeArray:
|
||||||
# \param shape numpy format shape, [x-size, y-size]
|
# \param shape numpy format shape, [x-size, y-size]
|
||||||
# \param vertices
|
# \param vertices
|
||||||
@classmethod
|
@classmethod
|
||||||
def arrayFromPolygon(cls, shape, vertices):
|
def arrayFromPolygon(cls, shape: Tuple[int, int], vertices: numpy.array) -> numpy.array:
|
||||||
base_array = numpy.zeros(shape, dtype = numpy.int32) # Initialize your array of zeros
|
base_array = numpy.zeros(shape, dtype = numpy.int32) # Initialize your array of zeros
|
||||||
|
|
||||||
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
|
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
|
||||||
|
@ -111,9 +117,9 @@ class ShapeArray:
|
||||||
# \param p2 2-tuple with x, y for point 2
|
# \param p2 2-tuple with x, y for point 2
|
||||||
# \param base_array boolean array to project the line on
|
# \param base_array boolean array to project the line on
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check(cls, p1, p2, base_array):
|
def _check(cls, p1: numpy.array, p2: numpy.array, base_array: numpy.array) -> bool:
|
||||||
if p1[0] == p2[0] and p1[1] == p2[1]:
|
if p1[0] == p2[0] and p1[1] == p2[1]:
|
||||||
return
|
return False
|
||||||
idxs = numpy.indices(base_array.shape) # Create 3D array of indices
|
idxs = numpy.indices(base_array.shape) # Create 3D array of indices
|
||||||
|
|
||||||
p1 = p1.astype(float)
|
p1 = p1.astype(float)
|
||||||
|
@ -132,4 +138,3 @@ class ShapeArray:
|
||||||
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
|
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
|
||||||
sign = numpy.sign(p2[0] - p1[0])
|
sign = numpy.sign(p2[0] - p1[0])
|
||||||
return idxs[1] * sign <= max_col_idx * sign
|
return idxs[1] * sign <= max_col_idx * sign
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2016 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 PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
|
@ -16,7 +16,7 @@ class AutoSave:
|
||||||
self._application.getPreferences().addPreference("cura/autosave_delay", 1000 * 10)
|
self._application.getPreferences().addPreference("cura/autosave_delay", 1000 * 10)
|
||||||
|
|
||||||
self._change_timer = QTimer()
|
self._change_timer = QTimer()
|
||||||
self._change_timer.setInterval(self._application.getPreferences().getValue("cura/autosave_delay"))
|
self._change_timer.setInterval(int(self._application.getPreferences().getValue("cura/autosave_delay")))
|
||||||
self._change_timer.setSingleShot(True)
|
self._change_timer.setSingleShot(True)
|
||||||
|
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
|
|
|
@ -148,5 +148,9 @@ class Backup:
|
||||||
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)
|
||||||
|
try:
|
||||||
archive.extractall(target_path)
|
archive.extractall(target_path)
|
||||||
|
except PermissionError:
|
||||||
|
Logger.logException("e", "Unable to extract the backup due to permission errors")
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -51,8 +51,18 @@ class BackupsManager:
|
||||||
## Here we try to disable the auto-save plug-in as it might interfere with
|
## Here we try to disable the auto-save plug-in as it might interfere with
|
||||||
# restoring a back-up.
|
# restoring a back-up.
|
||||||
def _disableAutoSave(self) -> None:
|
def _disableAutoSave(self) -> None:
|
||||||
self._application.getAutoSave().setEnabled(False)
|
auto_save = self._application.getAutoSave()
|
||||||
|
# The auto save is only not created if the application has not yet started.
|
||||||
|
if auto_save:
|
||||||
|
auto_save.setEnabled(False)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Unable to disable the autosave as application init has not been completed")
|
||||||
|
|
||||||
## Re-enable auto-save after we're done.
|
## Re-enable auto-save after we're done.
|
||||||
def _enableAutoSave(self) -> None:
|
def _enableAutoSave(self) -> None:
|
||||||
self._application.getAutoSave().setEnabled(True)
|
auto_save = self._application.getAutoSave()
|
||||||
|
# The auto save is only not created if the application has not yet started.
|
||||||
|
if auto_save:
|
||||||
|
auto_save.setEnabled(True)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Unable to enable the autosave as application init has not been completed")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (c) 2018 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.Scene.Camera import Camera
|
from UM.Mesh.MeshData import MeshData
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
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.
|
||||||
|
@ -20,13 +20,20 @@ from UM.Signal import Signal
|
||||||
from PyQt5.QtCore import QTimer
|
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
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
import math
|
import math
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -36,17 +43,17 @@ PRIME_CLEARANCE = 6.5
|
||||||
class BuildVolume(SceneNode):
|
class BuildVolume(SceneNode):
|
||||||
raftThicknessChanged = Signal()
|
raftThicknessChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, application, parent = None):
|
def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._application = application
|
self._application = application
|
||||||
self._machine_manager = self._application.getMachineManager()
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
|
||||||
self._volume_outline_color = None
|
self._volume_outline_color = None # type: Optional[Color]
|
||||||
self._x_axis_color = None
|
self._x_axis_color = None # type: Optional[Color]
|
||||||
self._y_axis_color = None
|
self._y_axis_color = None # type: Optional[Color]
|
||||||
self._z_axis_color = None
|
self._z_axis_color = None # type: Optional[Color]
|
||||||
self._disallowed_area_color = None
|
self._disallowed_area_color = None # type: Optional[Color]
|
||||||
self._error_area_color = None
|
self._error_area_color = None # type: Optional[Color]
|
||||||
|
|
||||||
self._width = 0 # type: float
|
self._width = 0 # type: float
|
||||||
self._height = 0 # type: float
|
self._height = 0 # type: float
|
||||||
|
@ -55,26 +62,27 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
self._shader = None
|
self._shader = None
|
||||||
|
|
||||||
self._origin_mesh = None
|
self._origin_mesh = None # type: Optional[MeshData]
|
||||||
self._origin_line_length = 20
|
self._origin_line_length = 20
|
||||||
self._origin_line_width = 0.5
|
self._origin_line_width = 0.5
|
||||||
|
|
||||||
self._grid_mesh = None
|
self._grid_mesh = None # type: Optional[MeshData]
|
||||||
self._grid_shader = None
|
self._grid_shader = None
|
||||||
|
|
||||||
self._disallowed_areas = []
|
self._disallowed_areas = [] # type: List[Polygon]
|
||||||
self._disallowed_areas_no_brim = []
|
self._disallowed_areas_no_brim = [] # type: List[Polygon]
|
||||||
self._disallowed_area_mesh = None
|
self._disallowed_area_mesh = None # type: Optional[MeshData]
|
||||||
|
self._disallowed_area_size = 0.
|
||||||
|
|
||||||
self._error_areas = []
|
self._error_areas = [] # type: List[Polygon]
|
||||||
self._error_mesh = None
|
self._error_mesh = None # type: Optional[MeshData]
|
||||||
|
|
||||||
self.setCalculateBoundingBox(False)
|
self.setCalculateBoundingBox(False)
|
||||||
self._volume_aabb = None
|
self._volume_aabb = None # type: Optional[AxisAlignedBox]
|
||||||
|
|
||||||
self._raft_thickness = 0.0
|
self._raft_thickness = 0.0
|
||||||
self._extra_z_clearance = 0.0
|
self._extra_z_clearance = 0.0
|
||||||
self._adhesion_type = None
|
self._adhesion_type = None # type: Any
|
||||||
self._platform = Platform(self)
|
self._platform = Platform(self)
|
||||||
|
|
||||||
self._build_volume_message = Message(catalog.i18nc("@info:status",
|
self._build_volume_message = Message(catalog.i18nc("@info:status",
|
||||||
|
@ -82,7 +90,7 @@ class BuildVolume(SceneNode):
|
||||||
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
||||||
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
|
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
|
||||||
|
|
||||||
self._global_container_stack = None
|
self._global_container_stack = None # type: Optional[GlobalStack]
|
||||||
|
|
||||||
self._stack_change_timer = QTimer()
|
self._stack_change_timer = QTimer()
|
||||||
self._stack_change_timer.setInterval(100)
|
self._stack_change_timer.setInterval(100)
|
||||||
|
@ -100,7 +108,7 @@ class BuildVolume(SceneNode):
|
||||||
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||||
|
|
||||||
#Objects loaded at the moment. We are connected to the property changed events of these objects.
|
#Objects loaded at the moment. We are connected to the property changed events of these objects.
|
||||||
self._scene_objects = set()
|
self._scene_objects = set() # type: Set[SceneNode]
|
||||||
|
|
||||||
self._scene_change_timer = QTimer()
|
self._scene_change_timer = QTimer()
|
||||||
self._scene_change_timer.setInterval(100)
|
self._scene_change_timer.setInterval(100)
|
||||||
|
@ -124,8 +132,8 @@ class BuildVolume(SceneNode):
|
||||||
# Enable and disable extruder
|
# Enable and disable extruder
|
||||||
self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
|
self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
|
||||||
|
|
||||||
# list of settings which were updated
|
# List of settings which were updated
|
||||||
self._changed_settings_since_last_rebuild = []
|
self._changed_settings_since_last_rebuild = [] # type: List[str]
|
||||||
|
|
||||||
def _onSceneChanged(self, source):
|
def _onSceneChanged(self, source):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
|
@ -165,15 +173,12 @@ class BuildVolume(SceneNode):
|
||||||
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
|
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
|
||||||
|
|
||||||
def setWidth(self, width: float) -> None:
|
def setWidth(self, width: float) -> None:
|
||||||
if width is not None:
|
|
||||||
self._width = width
|
self._width = width
|
||||||
|
|
||||||
def setHeight(self, height: float) -> None:
|
def setHeight(self, height: float) -> None:
|
||||||
if height is not None:
|
|
||||||
self._height = height
|
self._height = height
|
||||||
|
|
||||||
def setDepth(self, depth: float) -> None:
|
def setDepth(self, depth: float) -> None:
|
||||||
if depth is not None:
|
|
||||||
self._depth = depth
|
self._depth = depth
|
||||||
|
|
||||||
def setShape(self, shape: str) -> None:
|
def setShape(self, shape: str) -> None:
|
||||||
|
@ -222,13 +227,18 @@ class BuildVolume(SceneNode):
|
||||||
## For every sliceable node, update node._outside_buildarea
|
## For every sliceable node, update node._outside_buildarea
|
||||||
#
|
#
|
||||||
def updateNodeBoundaryCheck(self):
|
def updateNodeBoundaryCheck(self):
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return
|
||||||
|
|
||||||
root = self._application.getController().getScene().getRoot()
|
root = self._application.getController().getScene().getRoot()
|
||||||
nodes = list(BreadthFirstIterator(root))
|
nodes = cast(List[SceneNode], list(cast(Iterable, BreadthFirstIterator(root))))
|
||||||
group_nodes = []
|
group_nodes = [] # type: List[SceneNode]
|
||||||
|
|
||||||
build_volume_bounding_box = self.getBoundingBox()
|
build_volume_bounding_box = self.getBoundingBox()
|
||||||
if build_volume_bounding_box:
|
if build_volume_bounding_box:
|
||||||
# It's over 9000!
|
# It's over 9000!
|
||||||
|
# We set this to a very low number, as we do allow models to intersect the build plate.
|
||||||
|
# This means the model gets cut off at the build plate.
|
||||||
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
|
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
|
||||||
else:
|
else:
|
||||||
# No bounding box. This is triggered when running Cura from command line with a model for the first time
|
# No bounding box. This is triggered when running Cura from command line with a model for the first time
|
||||||
|
@ -241,14 +251,21 @@ class BuildVolume(SceneNode):
|
||||||
group_nodes.append(node) # Keep list of affected group_nodes
|
group_nodes.append(node) # Keep list of affected group_nodes
|
||||||
|
|
||||||
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
|
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
|
||||||
|
if not isinstance(node, CuraSceneNode):
|
||||||
|
continue
|
||||||
|
|
||||||
if node.collidesWithBbox(build_volume_bounding_box):
|
if node.collidesWithBbox(build_volume_bounding_box):
|
||||||
node.setOutsideBuildArea(True)
|
node.setOutsideBuildArea(True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if node.collidesWithArea(self.getDisallowedAreas()):
|
if node.collidesWithAreas(self.getDisallowedAreas()):
|
||||||
|
node.setOutsideBuildArea(True)
|
||||||
|
continue
|
||||||
|
# If the entire node is below the build plate, still mark it as outside.
|
||||||
|
node_bounding_box = node.getBoundingBox()
|
||||||
|
if node_bounding_box and node_bounding_box.top < 0:
|
||||||
node.setOutsideBuildArea(True)
|
node.setOutsideBuildArea(True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Mark the node as outside build volume if the set extruder is disabled
|
# Mark the node as outside build volume if the set extruder is disabled
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
if extruder_position not in self._global_container_stack.extruders:
|
if extruder_position not in self._global_container_stack.extruders:
|
||||||
|
@ -274,8 +291,8 @@ class BuildVolume(SceneNode):
|
||||||
child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
|
child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
|
||||||
|
|
||||||
## Update the outsideBuildArea of a single node, given bounds or current build volume
|
## Update the outsideBuildArea of a single node, given bounds or current build volume
|
||||||
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
|
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None) -> None:
|
||||||
if not isinstance(node, CuraSceneNode):
|
if not isinstance(node, CuraSceneNode) or self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if bounds is None:
|
if bounds is None:
|
||||||
|
@ -295,7 +312,7 @@ class BuildVolume(SceneNode):
|
||||||
node.setOutsideBuildArea(True)
|
node.setOutsideBuildArea(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if node.collidesWithArea(self.getDisallowedAreas()):
|
if node.collidesWithAreas(self.getDisallowedAreas()):
|
||||||
node.setOutsideBuildArea(True)
|
node.setOutsideBuildArea(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -307,32 +324,43 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
node.setOutsideBuildArea(False)
|
node.setOutsideBuildArea(False)
|
||||||
|
|
||||||
## Recalculates the build volume & disallowed areas.
|
def _buildGridMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d:float, z_fight_distance: float) -> MeshData:
|
||||||
def rebuild(self):
|
mb = MeshBuilder()
|
||||||
if not self._width or not self._height or not self._depth:
|
if self._shape != "elliptic":
|
||||||
return
|
# Build plate grid mesh
|
||||||
|
mb.addQuad(
|
||||||
|
Vector(min_w, min_h - z_fight_distance, min_d),
|
||||||
|
Vector(max_w, min_h - z_fight_distance, min_d),
|
||||||
|
Vector(max_w, min_h - z_fight_distance, max_d),
|
||||||
|
Vector(min_w, min_h - z_fight_distance, max_d)
|
||||||
|
)
|
||||||
|
|
||||||
if not self._engine_ready:
|
for n in range(0, 6):
|
||||||
return
|
v = mb.getVertex(n)
|
||||||
|
mb.setVertexUVCoordinates(n, v[0], v[2])
|
||||||
|
return mb.build()
|
||||||
|
else:
|
||||||
|
aspect = 1.0
|
||||||
|
scale_matrix = Matrix()
|
||||||
|
if self._width != 0:
|
||||||
|
# Scale circular meshes by aspect ratio if width != height
|
||||||
|
aspect = self._depth / self._width
|
||||||
|
scale_matrix.compose(scale=Vector(1, 1, aspect))
|
||||||
|
mb.addVertex(0, min_h - z_fight_distance, 0)
|
||||||
|
mb.addArc(max_w, Vector.Unit_Y, center=Vector(0, min_h - z_fight_distance, 0))
|
||||||
|
sections = mb.getVertexCount() - 1 # Center point is not an arc section
|
||||||
|
indices = []
|
||||||
|
for n in range(0, sections - 1):
|
||||||
|
indices.append([0, n + 2, n + 1])
|
||||||
|
mb.addIndices(numpy.asarray(indices, dtype=numpy.int32))
|
||||||
|
mb.calculateNormals()
|
||||||
|
|
||||||
if not self._volume_outline_color:
|
for n in range(0, mb.getVertexCount()):
|
||||||
theme = self._application.getTheme()
|
v = mb.getVertex(n)
|
||||||
self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
|
mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
|
||||||
self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
|
return mb.build().getTransformed(scale_matrix)
|
||||||
self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
|
|
||||||
self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
|
|
||||||
self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
|
|
||||||
self._error_area_color = Color(*theme.getColor("error_area").getRgb())
|
|
||||||
|
|
||||||
min_w = -self._width / 2
|
|
||||||
max_w = self._width / 2
|
|
||||||
min_h = 0.0
|
|
||||||
max_h = self._height
|
|
||||||
min_d = -self._depth / 2
|
|
||||||
max_d = self._depth / 2
|
|
||||||
|
|
||||||
z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting
|
|
||||||
|
|
||||||
|
def _buildMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d:float, z_fight_distance: float) -> MeshData:
|
||||||
if self._shape != "elliptic":
|
if self._shape != "elliptic":
|
||||||
# Outline 'cube' of the build volume
|
# Outline 'cube' of the build volume
|
||||||
mb = MeshBuilder()
|
mb = MeshBuilder()
|
||||||
|
@ -351,25 +379,10 @@ class BuildVolume(SceneNode):
|
||||||
mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self._volume_outline_color)
|
mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self._volume_outline_color)
|
||||||
mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self._volume_outline_color)
|
mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self._volume_outline_color)
|
||||||
|
|
||||||
self.setMeshData(mb.build())
|
return mb.build()
|
||||||
|
|
||||||
# Build plate grid mesh
|
|
||||||
mb = MeshBuilder()
|
|
||||||
mb.addQuad(
|
|
||||||
Vector(min_w, min_h - z_fight_distance, min_d),
|
|
||||||
Vector(max_w, min_h - z_fight_distance, min_d),
|
|
||||||
Vector(max_w, min_h - z_fight_distance, max_d),
|
|
||||||
Vector(min_w, min_h - z_fight_distance, max_d)
|
|
||||||
)
|
|
||||||
|
|
||||||
for n in range(0, 6):
|
|
||||||
v = mb.getVertex(n)
|
|
||||||
mb.setVertexUVCoordinates(n, v[0], v[2])
|
|
||||||
self._grid_mesh = mb.build()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Bottom and top 'ellipse' of the build volume
|
# Bottom and top 'ellipse' of the build volume
|
||||||
aspect = 1.0
|
|
||||||
scale_matrix = Matrix()
|
scale_matrix = Matrix()
|
||||||
if self._width != 0:
|
if self._width != 0:
|
||||||
# Scale circular meshes by aspect ratio if width != height
|
# Scale circular meshes by aspect ratio if width != height
|
||||||
|
@ -378,30 +391,9 @@ class BuildVolume(SceneNode):
|
||||||
mb = MeshBuilder()
|
mb = MeshBuilder()
|
||||||
mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self._volume_outline_color)
|
mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self._volume_outline_color)
|
||||||
mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self._volume_outline_color)
|
mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self._volume_outline_color)
|
||||||
self.setMeshData(mb.build().getTransformed(scale_matrix))
|
return mb.build().getTransformed(scale_matrix)
|
||||||
|
|
||||||
# Build plate grid mesh
|
|
||||||
mb = MeshBuilder()
|
|
||||||
mb.addVertex(0, min_h - z_fight_distance, 0)
|
|
||||||
mb.addArc(max_w, Vector.Unit_Y, center = Vector(0, min_h - z_fight_distance, 0))
|
|
||||||
sections = mb.getVertexCount() - 1 # Center point is not an arc section
|
|
||||||
indices = []
|
|
||||||
for n in range(0, sections - 1):
|
|
||||||
indices.append([0, n + 2, n + 1])
|
|
||||||
mb.addIndices(numpy.asarray(indices, dtype = numpy.int32))
|
|
||||||
mb.calculateNormals()
|
|
||||||
|
|
||||||
for n in range(0, mb.getVertexCount()):
|
|
||||||
v = mb.getVertex(n)
|
|
||||||
mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
|
|
||||||
self._grid_mesh = mb.build().getTransformed(scale_matrix)
|
|
||||||
|
|
||||||
# Indication of the machine origin
|
|
||||||
if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
|
|
||||||
origin = (Vector(min_w, min_h, min_d) + Vector(max_w, min_h, max_d)) / 2
|
|
||||||
else:
|
|
||||||
origin = Vector(min_w, min_h, max_d)
|
|
||||||
|
|
||||||
|
def _buildOriginMesh(self, origin: Vector) -> MeshData:
|
||||||
mb = MeshBuilder()
|
mb = MeshBuilder()
|
||||||
mb.addCube(
|
mb.addCube(
|
||||||
width=self._origin_line_length,
|
width=self._origin_line_length,
|
||||||
|
@ -424,39 +416,22 @@ class BuildVolume(SceneNode):
|
||||||
center=origin - Vector(0, 0, self._origin_line_length / 2),
|
center=origin - Vector(0, 0, self._origin_line_length / 2),
|
||||||
color=self._z_axis_color
|
color=self._z_axis_color
|
||||||
)
|
)
|
||||||
self._origin_mesh = mb.build()
|
return mb.build()
|
||||||
|
|
||||||
disallowed_area_height = 0.1
|
def _updateColors(self):
|
||||||
disallowed_area_size = 0
|
theme = self._application.getTheme()
|
||||||
if self._disallowed_areas:
|
if theme is None:
|
||||||
mb = MeshBuilder()
|
return
|
||||||
color = self._disallowed_area_color
|
self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
|
||||||
for polygon in self._disallowed_areas:
|
self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
|
||||||
points = polygon.getPoints()
|
self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
|
||||||
if len(points) == 0:
|
self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
|
||||||
continue
|
self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
|
||||||
|
self._error_area_color = Color(*theme.getColor("error_area").getRgb())
|
||||||
|
|
||||||
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
|
def _buildErrorMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d: float, disallowed_area_height: float) -> Optional[MeshData]:
|
||||||
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
|
if not self._error_areas:
|
||||||
for point in points:
|
return None
|
||||||
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
|
|
||||||
mb.addFace(first, previous_point, new_point, color = color)
|
|
||||||
previous_point = new_point
|
|
||||||
|
|
||||||
# Find the largest disallowed area to exclude it from the maximum scale bounds.
|
|
||||||
# This is a very nasty hack. This pretty much only works for UM machines.
|
|
||||||
# This disallowed area_size needs a -lot- of rework at some point in the future: TODO
|
|
||||||
if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
|
|
||||||
size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
|
|
||||||
else:
|
|
||||||
size = 0
|
|
||||||
disallowed_area_size = max(size, disallowed_area_size)
|
|
||||||
|
|
||||||
self._disallowed_area_mesh = mb.build()
|
|
||||||
else:
|
|
||||||
self._disallowed_area_mesh = None
|
|
||||||
|
|
||||||
if self._error_areas:
|
|
||||||
mb = MeshBuilder()
|
mb = MeshBuilder()
|
||||||
for error_area in self._error_areas:
|
for error_area in self._error_areas:
|
||||||
color = self._error_area_color
|
color = self._error_area_color
|
||||||
|
@ -470,9 +445,79 @@ class BuildVolume(SceneNode):
|
||||||
self._clamp(point[1], min_d, max_d))
|
self._clamp(point[1], min_d, max_d))
|
||||||
mb.addFace(first, previous_point, new_point, color=color)
|
mb.addFace(first, previous_point, new_point, color=color)
|
||||||
previous_point = new_point
|
previous_point = new_point
|
||||||
self._error_mesh = mb.build()
|
return mb.build()
|
||||||
|
|
||||||
|
def _buildDisallowedAreaMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d: float, disallowed_area_height: float) -> Optional[MeshData]:
|
||||||
|
if not self._disallowed_areas:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mb = MeshBuilder()
|
||||||
|
color = self._disallowed_area_color
|
||||||
|
for polygon in self._disallowed_areas:
|
||||||
|
points = polygon.getPoints()
|
||||||
|
if len(points) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||||
|
self._clamp(points[0][1], min_d, max_d))
|
||||||
|
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||||
|
self._clamp(points[0][1], min_d, max_d))
|
||||||
|
for point in points:
|
||||||
|
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
|
||||||
|
self._clamp(point[1], min_d, max_d))
|
||||||
|
mb.addFace(first, previous_point, new_point, color=color)
|
||||||
|
previous_point = new_point
|
||||||
|
|
||||||
|
# Find the largest disallowed area to exclude it from the maximum scale bounds.
|
||||||
|
# This is a very nasty hack. This pretty much only works for UM machines.
|
||||||
|
# This disallowed area_size needs a -lot- of rework at some point in the future: TODO
|
||||||
|
if numpy.min(points[:,
|
||||||
|
1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
|
||||||
|
size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
|
||||||
else:
|
else:
|
||||||
self._error_mesh = None
|
size = 0
|
||||||
|
self._disallowed_area_size = max(size, self._disallowed_area_size)
|
||||||
|
return mb.build()
|
||||||
|
|
||||||
|
## Recalculates the build volume & disallowed areas.
|
||||||
|
def rebuild(self) -> None:
|
||||||
|
if not self._width or not self._height or not self._depth:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._engine_ready:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._volume_outline_color:
|
||||||
|
self._updateColors()
|
||||||
|
|
||||||
|
min_w = -self._width / 2
|
||||||
|
max_w = self._width / 2
|
||||||
|
min_h = 0.0
|
||||||
|
max_h = self._height
|
||||||
|
min_d = -self._depth / 2
|
||||||
|
max_d = self._depth / 2
|
||||||
|
|
||||||
|
z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting
|
||||||
|
|
||||||
|
self._grid_mesh = self._buildGridMesh(min_w, max_w, min_h, max_h, min_d, max_d, z_fight_distance)
|
||||||
|
self.setMeshData(self._buildMesh(min_w, max_w, min_h, max_h, min_d, max_d, z_fight_distance))
|
||||||
|
|
||||||
|
# Indication of the machine origin
|
||||||
|
if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
|
||||||
|
origin = (Vector(min_w, min_h, min_d) + Vector(max_w, min_h, max_d)) / 2
|
||||||
|
else:
|
||||||
|
origin = Vector(min_w, min_h, max_d)
|
||||||
|
|
||||||
|
self._origin_mesh = self._buildOriginMesh(origin)
|
||||||
|
|
||||||
|
disallowed_area_height = 0.1
|
||||||
|
self._disallowed_area_size = 0.
|
||||||
|
self._disallowed_area_mesh = self._buildDisallowedAreaMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
|
||||||
|
|
||||||
|
self._error_mesh = self._buildErrorMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
|
||||||
|
|
||||||
self._volume_aabb = AxisAlignedBox(
|
self._volume_aabb = AxisAlignedBox(
|
||||||
minimum = Vector(min_w, min_h - 1.0, min_d),
|
minimum = Vector(min_w, min_h - 1.0, min_d),
|
||||||
|
@ -484,21 +529,24 @@ class BuildVolume(SceneNode):
|
||||||
# This is probably wrong in all other cases. TODO!
|
# This is probably wrong in all other cases. TODO!
|
||||||
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
|
# The +1 and -1 is added as there is always a bit of extra room required to work properly.
|
||||||
scale_to_max_bounds = AxisAlignedBox(
|
scale_to_max_bounds = AxisAlignedBox(
|
||||||
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1),
|
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + self._disallowed_area_size - bed_adhesion_size + 1),
|
||||||
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
|
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - self._disallowed_area_size + bed_adhesion_size - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
|
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds # type: ignore
|
||||||
|
|
||||||
self.updateNodeBoundaryCheck()
|
self.updateNodeBoundaryCheck()
|
||||||
|
|
||||||
def getBoundingBox(self) -> AxisAlignedBox:
|
def getBoundingBox(self):
|
||||||
return self._volume_aabb
|
return self._volume_aabb
|
||||||
|
|
||||||
def getRaftThickness(self) -> float:
|
def getRaftThickness(self) -> float:
|
||||||
return self._raft_thickness
|
return self._raft_thickness
|
||||||
|
|
||||||
def _updateRaftThickness(self):
|
def _updateRaftThickness(self) -> None:
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return
|
||||||
|
|
||||||
old_raft_thickness = self._raft_thickness
|
old_raft_thickness = self._raft_thickness
|
||||||
if self._global_container_stack.extruders:
|
if self._global_container_stack.extruders:
|
||||||
# This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
|
# This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
|
||||||
|
@ -518,28 +566,23 @@ class BuildVolume(SceneNode):
|
||||||
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
|
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
|
||||||
self.raftThicknessChanged.emit()
|
self.raftThicknessChanged.emit()
|
||||||
|
|
||||||
def _updateExtraZClearance(self) -> None:
|
def _calculateExtraZClearance(self, extruders: List["ContainerStack"]) -> float:
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return 0
|
||||||
|
|
||||||
extra_z = 0.0
|
extra_z = 0.0
|
||||||
extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
|
|
||||||
use_extruders = False
|
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
if extruder.getProperty("retraction_hop_enabled", "value"):
|
if extruder.getProperty("retraction_hop_enabled", "value"):
|
||||||
retraction_hop = extruder.getProperty("retraction_hop", "value")
|
retraction_hop = extruder.getProperty("retraction_hop", "value")
|
||||||
if extra_z is None or retraction_hop > extra_z:
|
if extra_z is None or retraction_hop > extra_z:
|
||||||
extra_z = retraction_hop
|
extra_z = retraction_hop
|
||||||
use_extruders = True
|
return extra_z
|
||||||
if not use_extruders:
|
|
||||||
# If no extruders, take global value.
|
|
||||||
if self._global_container_stack.getProperty("retraction_hop_enabled", "value"):
|
|
||||||
extra_z = self._global_container_stack.getProperty("retraction_hop", "value")
|
|
||||||
if extra_z != self._extra_z_clearance:
|
|
||||||
self._extra_z_clearance = extra_z
|
|
||||||
|
|
||||||
def _onStackChanged(self):
|
def _onStackChanged(self):
|
||||||
self._stack_change_timer.start()
|
self._stack_change_timer.start()
|
||||||
|
|
||||||
## Update the build volume visualization
|
## Update the build volume visualization
|
||||||
def _onStackChangeTimerFinished(self):
|
def _onStackChangeTimerFinished(self) -> None:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
@ -570,7 +613,7 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
self._updateDisallowedAreas()
|
self._updateDisallowedAreas()
|
||||||
self._updateRaftThickness()
|
self._updateRaftThickness()
|
||||||
self._updateExtraZClearance()
|
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
||||||
|
|
||||||
if self._engine_ready:
|
if self._engine_ready:
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
@ -579,20 +622,23 @@ class BuildVolume(SceneNode):
|
||||||
if camera:
|
if camera:
|
||||||
diagonal = self.getDiagonalSize()
|
diagonal = self.getDiagonalSize()
|
||||||
if diagonal > 1:
|
if diagonal > 1:
|
||||||
camera.setZoomRange(min = 0.1, max = diagonal * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume.
|
# You can zoom out up to 5 times the diagonal. This gives some space around the volume.
|
||||||
|
camera.setZoomRange(min = 0.1, max = diagonal * 5) # type: ignore
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self) -> None:
|
||||||
self._engine_ready = True
|
self._engine_ready = True
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def _onSettingChangeTimerFinished(self):
|
def _onSettingChangeTimerFinished(self) -> None:
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return
|
||||||
|
|
||||||
rebuild_me = False
|
rebuild_me = False
|
||||||
update_disallowed_areas = False
|
update_disallowed_areas = False
|
||||||
update_raft_thickness = False
|
update_raft_thickness = False
|
||||||
update_extra_z_clearance = True
|
update_extra_z_clearance = True
|
||||||
|
|
||||||
for setting_key in self._changed_settings_since_last_rebuild:
|
for setting_key in self._changed_settings_since_last_rebuild:
|
||||||
|
|
||||||
if setting_key == "print_sequence":
|
if setting_key == "print_sequence":
|
||||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
if self._application.getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
|
if self._application.getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
|
||||||
|
@ -605,33 +651,26 @@ class BuildVolume(SceneNode):
|
||||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
self._build_volume_message.hide()
|
self._build_volume_message.hide()
|
||||||
update_disallowed_areas = True
|
update_disallowed_areas = True
|
||||||
rebuild_me = True
|
|
||||||
|
|
||||||
# sometimes the machine size or shape settings are adjusted on the active machine, we should reflect this
|
# sometimes the machine size or shape settings are adjusted on the active machine, we should reflect this
|
||||||
if setting_key in self._machine_settings:
|
if setting_key in self._machine_settings:
|
||||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
self._updateMachineSizeProperties()
|
||||||
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
|
||||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
|
||||||
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
|
||||||
update_extra_z_clearance = True
|
update_extra_z_clearance = True
|
||||||
update_disallowed_areas = True
|
update_disallowed_areas = True
|
||||||
rebuild_me = True
|
|
||||||
|
|
||||||
if setting_key in self._skirt_settings + self._prime_settings + self._tower_settings + self._ooze_shield_settings + self._distance_settings + self._extruder_settings:
|
if setting_key in self._disallowed_area_settings:
|
||||||
update_disallowed_areas = True
|
update_disallowed_areas = True
|
||||||
rebuild_me = True
|
|
||||||
|
|
||||||
if setting_key in self._raft_settings:
|
if setting_key in self._raft_settings:
|
||||||
update_raft_thickness = True
|
update_raft_thickness = True
|
||||||
rebuild_me = True
|
|
||||||
|
|
||||||
if setting_key in self._extra_z_settings:
|
if setting_key in self._extra_z_settings:
|
||||||
update_extra_z_clearance = True
|
update_extra_z_clearance = True
|
||||||
rebuild_me = True
|
|
||||||
|
|
||||||
if setting_key in self._limit_to_extruder_settings:
|
if setting_key in self._limit_to_extruder_settings:
|
||||||
update_disallowed_areas = True
|
update_disallowed_areas = True
|
||||||
rebuild_me = True
|
|
||||||
|
rebuild_me = update_extra_z_clearance or update_disallowed_areas or update_raft_thickness
|
||||||
|
|
||||||
# We only want to update all of them once.
|
# We only want to update all of them once.
|
||||||
if update_disallowed_areas:
|
if update_disallowed_areas:
|
||||||
|
@ -641,7 +680,7 @@ class BuildVolume(SceneNode):
|
||||||
self._updateRaftThickness()
|
self._updateRaftThickness()
|
||||||
|
|
||||||
if update_extra_z_clearance:
|
if update_extra_z_clearance:
|
||||||
self._updateExtraZClearance()
|
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
||||||
|
|
||||||
if rebuild_me:
|
if rebuild_me:
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
@ -649,7 +688,7 @@ class BuildVolume(SceneNode):
|
||||||
# We just did a rebuild, reset the list.
|
# We just did a rebuild, reset the list.
|
||||||
self._changed_settings_since_last_rebuild = []
|
self._changed_settings_since_last_rebuild = []
|
||||||
|
|
||||||
def _onSettingPropertyChanged(self, setting_key: str, property_name: str):
|
def _onSettingPropertyChanged(self, setting_key: str, property_name: str) -> None:
|
||||||
if property_name != "value":
|
if property_name != "value":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -660,6 +699,14 @@ class BuildVolume(SceneNode):
|
||||||
def hasErrors(self) -> bool:
|
def hasErrors(self) -> bool:
|
||||||
return self._has_errors
|
return self._has_errors
|
||||||
|
|
||||||
|
def _updateMachineSizeProperties(self) -> None:
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return
|
||||||
|
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||||
|
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
|
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
|
self._shape = self._global_container_stack.getProperty("machine_shape", "value")
|
||||||
|
|
||||||
## Calls _updateDisallowedAreas and makes sure the changes appear in the
|
## Calls _updateDisallowedAreas and makes sure the changes appear in the
|
||||||
# scene.
|
# scene.
|
||||||
#
|
#
|
||||||
|
@ -671,57 +718,26 @@ class BuildVolume(SceneNode):
|
||||||
def _updateDisallowedAreasAndRebuild(self):
|
def _updateDisallowedAreasAndRebuild(self):
|
||||||
self._updateDisallowedAreas()
|
self._updateDisallowedAreas()
|
||||||
self._updateRaftThickness()
|
self._updateRaftThickness()
|
||||||
self._updateExtraZClearance()
|
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def _updateDisallowedAreas(self):
|
def _updateDisallowedAreas(self) -> None:
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._error_areas = []
|
self._error_areas = []
|
||||||
|
|
||||||
extruder_manager = ExtruderManager.getInstance()
|
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
|
||||||
used_extruders = extruder_manager.getUsedExtruderStacks()
|
|
||||||
disallowed_border_size = self.getEdgeDisallowedSize()
|
disallowed_border_size = self.getEdgeDisallowedSize()
|
||||||
|
|
||||||
if not used_extruders:
|
|
||||||
# If no extruder is used, assume that the active extruder is used (else nothing is drawn)
|
|
||||||
if extruder_manager.getActiveExtruderStack():
|
|
||||||
used_extruders = [extruder_manager.getActiveExtruderStack()]
|
|
||||||
else:
|
|
||||||
used_extruders = [self._global_container_stack]
|
|
||||||
|
|
||||||
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) # Normal machine disallowed areas can always be added.
|
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) # Normal machine disallowed areas can always be added.
|
||||||
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
|
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
|
||||||
result_areas_no_brim = self._computeDisallowedAreasStatic(0, used_extruders) # Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
result_areas_no_brim = self._computeDisallowedAreasStatic(0, used_extruders) # Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
|
||||||
prime_disallowed_areas = copy.deepcopy(result_areas_no_brim)
|
|
||||||
|
|
||||||
# Check if prime positions intersect with disallowed areas.
|
# Check if prime positions intersect with disallowed areas.
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
extruder_id = extruder.getId()
|
extruder_id = extruder.getId()
|
||||||
|
|
||||||
collision = False
|
|
||||||
for prime_polygon in prime_areas[extruder_id]:
|
|
||||||
for disallowed_polygon in prime_disallowed_areas[extruder_id]:
|
|
||||||
if prime_polygon.intersectsPolygon(disallowed_polygon) is not None:
|
|
||||||
collision = True
|
|
||||||
break
|
|
||||||
if collision:
|
|
||||||
break
|
|
||||||
|
|
||||||
#Also check other prime positions (without additional offset).
|
|
||||||
for other_extruder_id in prime_areas:
|
|
||||||
if extruder_id == other_extruder_id: #It is allowed to collide with itself.
|
|
||||||
continue
|
|
||||||
for other_prime_polygon in prime_areas[other_extruder_id]:
|
|
||||||
if prime_polygon.intersectsPolygon(other_prime_polygon):
|
|
||||||
collision = True
|
|
||||||
break
|
|
||||||
if collision:
|
|
||||||
break
|
|
||||||
if collision:
|
|
||||||
break
|
|
||||||
|
|
||||||
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
result_areas[extruder_id].extend(prime_areas[extruder_id])
|
||||||
result_areas_no_brim[extruder_id].extend(prime_areas[extruder_id])
|
result_areas_no_brim[extruder_id].extend(prime_areas[extruder_id])
|
||||||
|
|
||||||
|
@ -730,27 +746,22 @@ class BuildVolume(SceneNode):
|
||||||
polygon = Polygon(numpy.array(area, numpy.float32))
|
polygon = Polygon(numpy.array(area, numpy.float32))
|
||||||
polygon_disallowed_border = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
|
polygon_disallowed_border = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
|
||||||
result_areas[extruder_id].append(polygon_disallowed_border) # Don't perform the offset on these.
|
result_areas[extruder_id].append(polygon_disallowed_border) # Don't perform the offset on these.
|
||||||
#polygon_minimal_border = polygon.getMinkowskiHull(5)
|
result_areas_no_brim[extruder_id].append(polygon) # No brim
|
||||||
result_areas_no_brim[extruder_id].append(polygon) # no brim
|
|
||||||
|
|
||||||
# Add prime tower location as disallowed area.
|
# Add prime tower location as disallowed area.
|
||||||
if len(used_extruders) > 1: #No prime tower in single-extrusion.
|
if len([x for x in used_extruders if x.isEnabled]) > 1: # No prime tower if only one extruder is enabled
|
||||||
|
|
||||||
if len([x for x in used_extruders if x.isEnabled == True]) > 1: #No prime tower if only one extruder is enabled
|
|
||||||
prime_tower_collision = False
|
prime_tower_collision = False
|
||||||
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
|
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
|
||||||
for extruder_id in prime_tower_areas:
|
for extruder_id in prime_tower_areas:
|
||||||
for i_area, prime_tower_area in enumerate(prime_tower_areas[extruder_id]):
|
for area_index, prime_tower_area in enumerate(prime_tower_areas[extruder_id]):
|
||||||
for area in result_areas[extruder_id]:
|
for area in result_areas[extruder_id]:
|
||||||
if prime_tower_area.intersectsPolygon(area) is not None:
|
if prime_tower_area.intersectsPolygon(area) is not None:
|
||||||
prime_tower_collision = True
|
prime_tower_collision = True
|
||||||
break
|
break
|
||||||
if prime_tower_collision: # Already found a collision.
|
if prime_tower_collision: # Already found a collision.
|
||||||
break
|
break
|
||||||
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
|
if self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
|
||||||
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
|
prime_tower_areas[extruder_id][area_index] = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
|
||||||
prime_tower_areas[extruder_id][i_area] = prime_tower_area.getMinkowskiHull(
|
|
||||||
Polygon.approximatedCircle(disallowed_border_size))
|
|
||||||
if not prime_tower_collision:
|
if not prime_tower_collision:
|
||||||
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||||
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
|
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||||
|
@ -776,11 +787,14 @@ class BuildVolume(SceneNode):
|
||||||
# where that extruder may not print.
|
# where that extruder may not print.
|
||||||
def _computeDisallowedAreasPrinted(self, used_extruders):
|
def _computeDisallowedAreasPrinted(self, used_extruders):
|
||||||
result = {}
|
result = {}
|
||||||
|
adhesion_extruder = None #type: ExtruderStack
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
|
if int(extruder.getProperty("extruder_nr", "value")) == int(self._global_container_stack.getProperty("adhesion_extruder_nr", "value")):
|
||||||
|
adhesion_extruder = extruder
|
||||||
result[extruder.getId()] = []
|
result[extruder.getId()] = []
|
||||||
|
|
||||||
# Currently, the only normally printed object is the prime tower.
|
# Currently, the only normally printed object is the prime tower.
|
||||||
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable"):
|
if self._global_container_stack.getProperty("prime_tower_enable", "value"):
|
||||||
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
||||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
|
@ -790,27 +804,19 @@ class BuildVolume(SceneNode):
|
||||||
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
|
prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
|
||||||
prime_tower_y = prime_tower_y + machine_depth / 2
|
prime_tower_y = prime_tower_y + machine_depth / 2
|
||||||
|
|
||||||
if (ExtruderManager.getInstance().getResolveOrValue("prime_tower_brim_enable") and
|
if adhesion_extruder is not None and self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
|
||||||
ExtruderManager.getInstance().getResolveOrValue("adhesion_type") != "raft"):
|
|
||||||
brim_size = (
|
brim_size = (
|
||||||
extruder.getProperty("brim_line_count", "value") *
|
adhesion_extruder.getProperty("brim_line_count", "value") *
|
||||||
extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
|
adhesion_extruder.getProperty("skirt_brim_line_width", "value") / 100.0 *
|
||||||
extruder.getProperty("initial_layer_line_width_factor", "value")
|
adhesion_extruder.getProperty("initial_layer_line_width_factor", "value")
|
||||||
)
|
)
|
||||||
prime_tower_x -= brim_size
|
prime_tower_x -= brim_size
|
||||||
prime_tower_y += brim_size
|
prime_tower_y += brim_size
|
||||||
|
|
||||||
if self._global_container_stack.getProperty("prime_tower_circular", "value"):
|
|
||||||
radius = prime_tower_size / 2
|
radius = prime_tower_size / 2
|
||||||
prime_tower_area = Polygon.approximatedCircle(radius)
|
prime_tower_area = Polygon.approximatedCircle(radius)
|
||||||
prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
|
prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
|
||||||
else:
|
|
||||||
prime_tower_area = Polygon([
|
|
||||||
[prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
|
|
||||||
[prime_tower_x, prime_tower_y - prime_tower_size],
|
|
||||||
[prime_tower_x, prime_tower_y],
|
|
||||||
[prime_tower_x - prime_tower_size, prime_tower_y],
|
|
||||||
])
|
|
||||||
prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0))
|
prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0))
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
result[extruder.getId()].append(prime_tower_area) #The prime tower location is the same for each extruder, regardless of offset.
|
result[extruder.getId()].append(prime_tower_area) #The prime tower location is the same for each extruder, regardless of offset.
|
||||||
|
@ -828,9 +834,10 @@ class BuildVolume(SceneNode):
|
||||||
# \param used_extruders The extruder stacks to generate disallowed areas
|
# \param used_extruders The extruder stacks to generate disallowed areas
|
||||||
# for.
|
# for.
|
||||||
# \return A dictionary with for each used extruder ID the prime areas.
|
# \return A dictionary with for each used extruder ID the prime areas.
|
||||||
def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders):
|
def _computeDisallowedAreasPrimeBlob(self, border_size: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
|
||||||
result = {}
|
result = {} # type: Dict[str, List[Polygon]]
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return result
|
||||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
|
@ -867,9 +874,12 @@ class BuildVolume(SceneNode):
|
||||||
# for.
|
# for.
|
||||||
# \return A dictionary with for each used extruder ID the disallowed areas
|
# \return A dictionary with for each used extruder ID the disallowed areas
|
||||||
# where that extruder may not print.
|
# where that extruder may not print.
|
||||||
def _computeDisallowedAreasStatic(self, border_size, used_extruders):
|
def _computeDisallowedAreasStatic(self, border_size:float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
|
||||||
# Convert disallowed areas to polygons and dilate them.
|
# Convert disallowed areas to polygons and dilate them.
|
||||||
machine_disallowed_polygons = []
|
machine_disallowed_polygons = []
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"):
|
for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"):
|
||||||
polygon = Polygon(numpy.array(area, numpy.float32))
|
polygon = Polygon(numpy.array(area, numpy.float32))
|
||||||
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
||||||
|
@ -880,7 +890,7 @@ class BuildVolume(SceneNode):
|
||||||
nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry(
|
nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry(
|
||||||
"nozzle_offsetting_for_disallowed_areas", True)
|
"nozzle_offsetting_for_disallowed_areas", True)
|
||||||
|
|
||||||
result = {}
|
result = {} # type: Dict[str, List[Polygon]]
|
||||||
for extruder in used_extruders:
|
for extruder in used_extruders:
|
||||||
extruder_id = extruder.getId()
|
extruder_id = extruder.getId()
|
||||||
offset_x = extruder.getProperty("machine_nozzle_offset_x", "value")
|
offset_x = extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||||
|
@ -903,7 +913,8 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
# Only do nozzle offsetting if needed
|
# Only do nozzle offsetting if needed
|
||||||
if nozzle_offsetting_for_disallowed_areas:
|
if nozzle_offsetting_for_disallowed_areas:
|
||||||
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
|
# The build volume is defined as the union of the area that all extruders can reach, so we need to know
|
||||||
|
# the relative offset to all extruders.
|
||||||
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||||
if other_offset_x is None:
|
if other_offset_x is None:
|
||||||
|
@ -1000,14 +1011,86 @@ class BuildVolume(SceneNode):
|
||||||
# stack.
|
# stack.
|
||||||
#
|
#
|
||||||
# \return A sequence of setting values, one for each extruder.
|
# \return A sequence of setting values, one for each extruder.
|
||||||
def _getSettingFromAllExtruders(self, setting_key):
|
def _getSettingFromAllExtruders(self, setting_key: str) -> List[Any]:
|
||||||
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 in range(len(all_values)):
|
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
|
||||||
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
|
if not setting_value and (setting_type == "int" or setting_type == "float"):
|
||||||
all_values[i] = 0
|
all_values[i] = 0
|
||||||
return all_values
|
return all_values
|
||||||
|
|
||||||
|
def _calculateBedAdhesionSize(self, used_extruders):
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
container_stack = self._global_container_stack
|
||||||
|
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||||
|
skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value")
|
||||||
|
initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value")
|
||||||
|
# Use brim width if brim is enabled OR the prime tower has a brim.
|
||||||
|
if adhesion_type == "brim" or (self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and adhesion_type != "raft"):
|
||||||
|
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
|
||||||
|
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
|
||||||
|
|
||||||
|
for extruder_stack in used_extruders:
|
||||||
|
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
||||||
|
|
||||||
|
# We don't create an additional line for the extruder we're printing the brim with.
|
||||||
|
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
||||||
|
elif adhesion_type == "skirt": # No brim? Also not on prime tower? Then use whatever the adhesion type is saying: Skirt, raft or none.
|
||||||
|
skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
|
||||||
|
skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
|
||||||
|
|
||||||
|
bed_adhesion_size = skirt_distance + (
|
||||||
|
skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
|
||||||
|
|
||||||
|
for extruder_stack in used_extruders:
|
||||||
|
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
||||||
|
|
||||||
|
# We don't create an additional line for the extruder we're printing the skirt with.
|
||||||
|
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
||||||
|
elif adhesion_type == "raft":
|
||||||
|
bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value")
|
||||||
|
elif adhesion_type == "none":
|
||||||
|
bed_adhesion_size = 0
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
|
||||||
|
|
||||||
|
max_length_available = 0.5 * min(
|
||||||
|
self._global_container_stack.getProperty("machine_width", "value"),
|
||||||
|
self._global_container_stack.getProperty("machine_depth", "value")
|
||||||
|
)
|
||||||
|
bed_adhesion_size = min(bed_adhesion_size, max_length_available)
|
||||||
|
return bed_adhesion_size
|
||||||
|
|
||||||
|
def _calculateFarthestShieldDistance(self, container_stack):
|
||||||
|
farthest_shield_distance = 0
|
||||||
|
if container_stack.getProperty("draft_shield_enabled", "value"):
|
||||||
|
farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("draft_shield_dist", "value"))
|
||||||
|
if container_stack.getProperty("ooze_shield_enabled", "value"):
|
||||||
|
farthest_shield_distance = max(farthest_shield_distance,container_stack.getProperty("ooze_shield_dist", "value"))
|
||||||
|
return farthest_shield_distance
|
||||||
|
|
||||||
|
def _calculateSupportExpansion(self, container_stack):
|
||||||
|
support_expansion = 0
|
||||||
|
support_enabled = self._global_container_stack.getProperty("support_enable", "value")
|
||||||
|
support_offset = self._global_container_stack.getProperty("support_offset", "value")
|
||||||
|
if support_enabled and support_offset:
|
||||||
|
support_expansion += support_offset
|
||||||
|
return support_expansion
|
||||||
|
|
||||||
|
def _calculateMoveFromWallRadius(self, used_extruders):
|
||||||
|
move_from_wall_radius = 0 # Moves that start from outer wall.
|
||||||
|
all_values = [move_from_wall_radius]
|
||||||
|
all_values.extend(self._getSettingFromAllExtruders("infill_wipe_dist"))
|
||||||
|
move_from_wall_radius = max(all_values)
|
||||||
|
avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts", "value") for stack in used_extruders]
|
||||||
|
travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
|
||||||
|
for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): # For each extruder (or just global).
|
||||||
|
if avoid_other_parts_enabled:
|
||||||
|
move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
|
||||||
|
return move_from_wall_radius
|
||||||
|
|
||||||
## Calculate the disallowed radius around the edge.
|
## Calculate the disallowed radius around the edge.
|
||||||
#
|
#
|
||||||
# This disallowed radius is to allow for space around the models that is
|
# This disallowed radius is to allow for space around the models that is
|
||||||
|
@ -1024,67 +1107,10 @@ class BuildVolume(SceneNode):
|
||||||
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||||
return 0.1 # Return a very small value, so we do draw disallowed area's near the edges.
|
return 0.1 # Return a very small value, so we do draw disallowed area's near the edges.
|
||||||
|
|
||||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
bed_adhesion_size = self._calculateBedAdhesionSize(used_extruders)
|
||||||
skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value")
|
support_expansion = self._calculateSupportExpansion(self._global_container_stack)
|
||||||
initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value")
|
farthest_shield_distance = self._calculateFarthestShieldDistance(self._global_container_stack)
|
||||||
if adhesion_type == "skirt":
|
move_from_wall_radius = self._calculateMoveFromWallRadius(used_extruders)
|
||||||
skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
|
|
||||||
skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
|
|
||||||
|
|
||||||
bed_adhesion_size = skirt_distance + (skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
for extruder_stack in used_extruders:
|
|
||||||
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
|
||||||
|
|
||||||
# We don't create an additional line for the extruder we're printing the skirt with.
|
|
||||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
elif (adhesion_type == "brim" or
|
|
||||||
(self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and
|
|
||||||
self._global_container_stack.getProperty("adhesion_type", "value") != "raft")):
|
|
||||||
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
|
|
||||||
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
for extruder_stack in used_extruders:
|
|
||||||
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
|
|
||||||
|
|
||||||
# We don't create an additional line for the extruder we're printing the brim with.
|
|
||||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
|
||||||
|
|
||||||
elif adhesion_type == "raft":
|
|
||||||
bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value")
|
|
||||||
|
|
||||||
elif adhesion_type == "none":
|
|
||||||
bed_adhesion_size = 0
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
|
|
||||||
|
|
||||||
max_length_available = 0.5 * min(
|
|
||||||
self._global_container_stack.getProperty("machine_width", "value"),
|
|
||||||
self._global_container_stack.getProperty("machine_depth", "value")
|
|
||||||
)
|
|
||||||
bed_adhesion_size = min(bed_adhesion_size, max_length_available)
|
|
||||||
|
|
||||||
support_expansion = 0
|
|
||||||
support_enabled = self._global_container_stack.getProperty("support_enable", "value")
|
|
||||||
support_offset = self._global_container_stack.getProperty("support_offset", "value")
|
|
||||||
if support_enabled and support_offset:
|
|
||||||
support_expansion += support_offset
|
|
||||||
|
|
||||||
farthest_shield_distance = 0
|
|
||||||
if container_stack.getProperty("draft_shield_enabled", "value"):
|
|
||||||
farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("draft_shield_dist", "value"))
|
|
||||||
if container_stack.getProperty("ooze_shield_enabled", "value"):
|
|
||||||
farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("ooze_shield_dist", "value"))
|
|
||||||
|
|
||||||
move_from_wall_radius = 0 # Moves that start from outer wall.
|
|
||||||
move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
|
|
||||||
avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts","value") for stack in used_extruders]
|
|
||||||
travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
|
|
||||||
for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): #For each extruder (or just global).
|
|
||||||
if avoid_other_parts_enabled:
|
|
||||||
move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
|
|
||||||
|
|
||||||
# Now combine our different pieces of data to get the final border size.
|
# Now combine our different pieces of data to get the final border size.
|
||||||
# Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
|
# Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
|
||||||
|
@ -1100,8 +1126,9 @@ class BuildVolume(SceneNode):
|
||||||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
|
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
|
||||||
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
|
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
|
||||||
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
|
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
|
||||||
_tower_settings = ["prime_tower_enable", "prime_tower_circular", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]
|
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]
|
||||||
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
||||||
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports"]
|
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports"]
|
||||||
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
|
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
|
||||||
_limit_to_extruder_settings = ["wall_extruder_nr", "wall_0_extruder_nr", "wall_x_extruder_nr", "top_bottom_extruder_nr", "infill_extruder_nr", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "adhesion_extruder_nr"]
|
_limit_to_extruder_settings = ["wall_extruder_nr", "wall_0_extruder_nr", "wall_x_extruder_nr", "top_bottom_extruder_nr", "infill_extruder_nr", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "adhesion_extruder_nr"]
|
||||||
|
_disallowed_area_settings = _skirt_settings + _prime_settings + _tower_settings + _ooze_shield_settings + _distance_settings + _extruder_settings
|
||||||
|
|
|
@ -319,7 +319,8 @@ class CrashHandler:
|
||||||
|
|
||||||
def _userDescriptionWidget(self):
|
def _userDescriptionWidget(self):
|
||||||
group = QGroupBox()
|
group = QGroupBox()
|
||||||
group.setTitle(catalog.i18nc("@title:groupbox", "User description"))
|
group.setTitle(catalog.i18nc("@title:groupbox", "User description" +
|
||||||
|
" (Note: Developers may not speak your language, please use English if possible)"))
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
# When sending the report, the user comments will be collected
|
# When sending the report, the user comments will be collected
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
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, TYPE_CHECKING, 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
|
||||||
|
@ -23,10 +23,9 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
|
from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
|
|
||||||
class CuraActions(QObject):
|
class CuraActions(QObject):
|
||||||
def __init__(self, parent: QObject = None) -> None:
|
def __init__(self, parent: QObject = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
|
@ -114,6 +114,7 @@ from cura.UI.MachineSettingsManager import MachineSettingsManager
|
||||||
from cura.UI.ObjectsModel import ObjectsModel
|
from cura.UI.ObjectsModel import ObjectsModel
|
||||||
from cura.UI.TextManager import TextManager
|
from cura.UI.TextManager import TextManager
|
||||||
from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel
|
from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel
|
||||||
|
from cura.UI.RecommendedMode import RecommendedMode
|
||||||
from cura.UI.WelcomePagesModel import WelcomePagesModel
|
from cura.UI.WelcomePagesModel import WelcomePagesModel
|
||||||
from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel
|
from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ class CuraApplication(QtApplication):
|
||||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||||
# changes of the settings.
|
# changes of the settings.
|
||||||
SettingVersion = 7
|
SettingVersion = 8
|
||||||
|
|
||||||
Created = False
|
Created = False
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ class CuraApplication(QtApplication):
|
||||||
self._plugins_loaded = False
|
self._plugins_loaded = False
|
||||||
|
|
||||||
# Backups
|
# Backups
|
||||||
self._auto_save = None
|
self._auto_save = None # type: Optional[AutoSave]
|
||||||
|
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
self._container_registry_class = CuraContainerRegistry
|
self._container_registry_class = CuraContainerRegistry
|
||||||
|
@ -420,7 +421,7 @@ class CuraApplication(QtApplication):
|
||||||
# Add empty variant, material and quality containers.
|
# Add empty variant, material and quality containers.
|
||||||
# Since they are empty, they should never be serialized and instead just programmatically created.
|
# Since they are empty, they should never be serialized and instead just programmatically created.
|
||||||
# We need them to simplify the switching between materials.
|
# We need them to simplify the switching between materials.
|
||||||
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container # type: EmptyInstanceContainer
|
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
|
||||||
|
|
||||||
self._container_registry.addContainer(
|
self._container_registry.addContainer(
|
||||||
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
||||||
|
@ -523,6 +524,7 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
preferences.addPreference("cura/use_multi_build_plate", False)
|
||||||
|
preferences.addPreference("cura/show_list_of_objects", False)
|
||||||
preferences.addPreference("view/settings_list_height", 400)
|
preferences.addPreference("view/settings_list_height", 400)
|
||||||
preferences.addPreference("view/settings_visible", False)
|
preferences.addPreference("view/settings_visible", False)
|
||||||
preferences.addPreference("view/settings_xpos", 0)
|
preferences.addPreference("view/settings_xpos", 0)
|
||||||
|
@ -837,7 +839,6 @@ class CuraApplication(QtApplication):
|
||||||
if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
|
if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
|
||||||
diagonal = 375
|
diagonal = 375
|
||||||
camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
|
camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
|
||||||
camera.setPerspective(True)
|
|
||||||
camera.lookAt(Vector(0, 0, 0))
|
camera.lookAt(Vector(0, 0, 0))
|
||||||
controller.getScene().setActiveCamera("3d")
|
controller.getScene().setActiveCamera("3d")
|
||||||
|
|
||||||
|
@ -929,7 +930,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def getObjectsModel(self, *args):
|
def getObjectsModel(self, *args):
|
||||||
if self._object_manager is None:
|
if self._object_manager is None:
|
||||||
self._object_manager = ObjectsModel.createObjectsModel()
|
self._object_manager = ObjectsModel(self)
|
||||||
return self._object_manager
|
return self._object_manager
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
|
@ -988,7 +989,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
return super().event(event)
|
return super().event(event)
|
||||||
|
|
||||||
def getAutoSave(self):
|
def getAutoSave(self) -> Optional[AutoSave]:
|
||||||
return self._auto_save
|
return self._auto_save
|
||||||
|
|
||||||
## Get print information (duration / material used)
|
## Get print information (duration / material used)
|
||||||
|
@ -1036,10 +1037,11 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
|
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
|
||||||
qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel")
|
qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel")
|
||||||
qmlRegisterType(TextManager, "Cura", 1, 0, "TextManager")
|
qmlRegisterType(TextManager, "Cura", 1, 0, "TextManager")
|
||||||
|
qmlRegisterType(RecommendedMode, "Cura", 1, 0, "RecommendedMode")
|
||||||
|
|
||||||
qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
|
qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
|
||||||
|
|
||||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
|
qmlRegisterType(ObjectsModel, "Cura", 1, 0, "ObjectsModel")
|
||||||
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
||||||
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||||
|
|
|
@ -6,7 +6,6 @@ CuraAppDisplayName = "@CURA_APP_DISPLAY_NAME@"
|
||||||
CuraVersion = "@CURA_VERSION@"
|
CuraVersion = "@CURA_VERSION@"
|
||||||
CuraBuildType = "@CURA_BUILDTYPE@"
|
CuraBuildType = "@CURA_BUILDTYPE@"
|
||||||
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
||||||
CuraSDKVersion = "@CURA_SDK_VERSION@"
|
|
||||||
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@"
|
||||||
|
|
|
@ -18,7 +18,7 @@ class CuraView(View):
|
||||||
def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None:
|
def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._empty_menu_placeholder_url = QUrl(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
|
self._empty_menu_placeholder_url = QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
|
||||||
"EmptyViewMenuComponent.qml"))
|
"EmptyViewMenuComponent.qml"))
|
||||||
self._use_empty_menu_placeholder = use_empty_menu_placeholder
|
self._use_empty_menu_placeholder = use_empty_menu_placeholder
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2017 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.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -20,7 +20,7 @@ class LayerPolygon:
|
||||||
MoveCombingType = 8
|
MoveCombingType = 8
|
||||||
MoveRetractionType = 9
|
MoveRetractionType = 9
|
||||||
SupportInterfaceType = 10
|
SupportInterfaceType = 10
|
||||||
PrimeTower = 11
|
PrimeTowerType = 11
|
||||||
__number_of_types = 12
|
__number_of_types = 12
|
||||||
|
|
||||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||||
|
@ -245,7 +245,7 @@ class LayerPolygon:
|
||||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||||
theme.getColor("layerview_prime_tower").getRgbF()
|
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||||
])
|
])
|
||||||
|
|
||||||
return cls.__color_map
|
return cls.__color_map
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
# 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
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.PluginObject import PluginObject
|
from UM.PluginObject import PluginObject
|
||||||
|
@ -72,18 +73,26 @@ class MachineAction(QObject, PluginObject):
|
||||||
return self._finished
|
return self._finished
|
||||||
|
|
||||||
## Protected helper to create a view object based on provided QML.
|
## Protected helper to create a view object based on provided QML.
|
||||||
def _createViewFromQML(self) -> None:
|
def _createViewFromQML(self) -> Optional["QObject"]:
|
||||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
if plugin_path is None:
|
if plugin_path is None:
|
||||||
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
|
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
|
||||||
return
|
return None
|
||||||
path = os.path.join(plugin_path, self._qml_url)
|
path = os.path.join(plugin_path, self._qml_url)
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
|
return view
|
||||||
|
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QUrl, constant = True)
|
||||||
def displayItem(self):
|
def qmlPath(self) -> "QUrl":
|
||||||
if not self._view:
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
self._createViewFromQML()
|
if plugin_path is None:
|
||||||
return self._view
|
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
|
||||||
|
return QUrl("")
|
||||||
|
path = os.path.join(plugin_path, self._qml_url)
|
||||||
|
return QUrl.fromLocalFile(path)
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
|
def getDisplayItem(self) -> Optional["QObject"]:
|
||||||
|
return self._createViewFromQML()
|
||||||
|
|
|
@ -168,7 +168,7 @@ class MachineErrorChecker(QObject):
|
||||||
if validator_type:
|
if validator_type:
|
||||||
validator = validator_type(key)
|
validator = validator_type(key)
|
||||||
validation_state = validator(stack)
|
validation_state = validator(stack)
|
||||||
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
|
||||||
# Finish
|
# Finish
|
||||||
self._setResult(True)
|
self._setResult(True)
|
||||||
return
|
return
|
||||||
|
|
|
@ -93,7 +93,7 @@ class MaterialManager(QObject):
|
||||||
self._container_registry.findContainersMetadata(type = "material") if
|
self._container_registry.findContainersMetadata(type = "material") if
|
||||||
metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
|
metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
self._material_group_map = dict()
|
||||||
|
|
||||||
# Map #1
|
# Map #1
|
||||||
# root_material_id -> MaterialGroup
|
# root_material_id -> MaterialGroup
|
||||||
|
@ -120,7 +120,7 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
# Map #1.5
|
# Map #1.5
|
||||||
# GUID -> material group list
|
# GUID -> material group list
|
||||||
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
self._guid_material_groups_map = defaultdict(list)
|
||||||
for root_material_id, material_group in self._material_group_map.items():
|
for root_material_id, material_group in self._material_group_map.items():
|
||||||
guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
|
guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
|
||||||
self._guid_material_groups_map[guid].append(material_group)
|
self._guid_material_groups_map[guid].append(material_group)
|
||||||
|
@ -202,7 +202,7 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
# Map #4
|
# Map #4
|
||||||
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
||||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
self._diameter_machine_nozzle_buildplate_material_map = dict()
|
||||||
for material_metadata in material_metadatas.values():
|
for material_metadata in material_metadatas.values():
|
||||||
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,14 @@ class DiscoveredPrinter(QObject):
|
||||||
self._machine_type = machine_type
|
self._machine_type = machine_type
|
||||||
self.machineTypeChanged.emit()
|
self.machineTypeChanged.emit()
|
||||||
|
|
||||||
|
# Checks if the given machine type name in the available machine list.
|
||||||
|
# The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
|
||||||
|
# the machine type, which is "Ultimaker 3" for "ultimaker_3".
|
||||||
|
def _hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(name = machine_type_name)
|
||||||
|
return len(results) > 0
|
||||||
|
|
||||||
# Human readable machine type string
|
# Human readable machine type string
|
||||||
@pyqtProperty(str, notify = machineTypeChanged)
|
@pyqtProperty(str, notify = machineTypeChanged)
|
||||||
def readableMachineType(self) -> str:
|
def readableMachineType(self) -> str:
|
||||||
|
@ -70,24 +78,30 @@ class DiscoveredPrinter(QObject):
|
||||||
# In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field
|
# In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field
|
||||||
# "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
|
# "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
|
||||||
# like "Ultimaker 3". The code below handles this case.
|
# like "Ultimaker 3". The code below handles this case.
|
||||||
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
|
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||||
readable_type = self._machine_type
|
readable_type = self._machine_type
|
||||||
else:
|
else:
|
||||||
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
|
readable_type = self._getMachineTypeNameFromId(self._machine_type)
|
||||||
if not readable_type:
|
if not readable_type:
|
||||||
readable_type = catalog.i18nc("@label", "Unknown")
|
readable_type = catalog.i18nc("@label", "Unknown")
|
||||||
return readable_type
|
return readable_type
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = machineTypeChanged)
|
@pyqtProperty(bool, notify = machineTypeChanged)
|
||||||
def isUnknownMachineType(self) -> bool:
|
def isUnknownMachineType(self) -> bool:
|
||||||
from cura.CuraApplication import CuraApplication
|
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
|
||||||
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
|
|
||||||
readable_type = self._machine_type
|
readable_type = self._machine_type
|
||||||
else:
|
else:
|
||||||
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
|
readable_type = self._getMachineTypeNameFromId(self._machine_type)
|
||||||
return not readable_type
|
return not readable_type
|
||||||
|
|
||||||
|
def _getMachineTypeNameFromId(self, machine_type_id: str) -> str:
|
||||||
|
machine_type_name = ""
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(id = machine_type_id)
|
||||||
|
if results:
|
||||||
|
machine_type_name = results[0]["name"]
|
||||||
|
return machine_type_name
|
||||||
|
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QObject, constant = True)
|
||||||
def device(self) -> "NetworkedPrinterOutputDevice":
|
def device(self) -> "NetworkedPrinterOutputDevice":
|
||||||
return self._device
|
return self._device
|
||||||
|
|
|
@ -100,7 +100,7 @@ class FirstStartMachineActionsModel(ListModel):
|
||||||
item_list = []
|
item_list = []
|
||||||
for item in first_start_actions:
|
for item in first_start_actions:
|
||||||
item_list.append({"title": item.label,
|
item_list.append({"title": item.label,
|
||||||
"content": item.displayItem,
|
"content": item.getDisplayItem(),
|
||||||
"action": item,
|
"action": item,
|
||||||
})
|
})
|
||||||
item.reset()
|
item.reset()
|
||||||
|
|
|
@ -202,9 +202,6 @@ class QualityManager(QObject):
|
||||||
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||||
|
|
||||||
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
|
|
||||||
has_machine_specific_qualities = machine.getHasMachineQuality()
|
|
||||||
|
|
||||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
# (1) the machine-specific node
|
# (1) the machine-specific node
|
||||||
# (2) the generic node
|
# (2) the generic node
|
||||||
|
|
|
@ -76,7 +76,7 @@ class PlatformPhysics:
|
||||||
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
|
||||||
|
|
||||||
# If there is no convex hull for the node, start calculating it and continue.
|
# If there is no convex hull for the node, start calculating it and continue.
|
||||||
if not node.getDecorator(ConvexHullDecorator):
|
if not node.getDecorator(ConvexHullDecorator) and not node.callDecoration("isNonPrintingMesh"):
|
||||||
node.addDecorator(ConvexHullDecorator())
|
node.addDecorator(ConvexHullDecorator())
|
||||||
|
|
||||||
# only push away objects if this node is a printing mesh
|
# only push away objects if this node is a printing mesh
|
||||||
|
|
|
@ -84,6 +84,7 @@ class PreviewPass(RenderPass):
|
||||||
|
|
||||||
# Fill up the batch with objects that can be sliced.
|
# Fill up the batch with objects that can be sliced.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
|
if hasattr(node, "_outside_buildarea") and not node._outside_buildarea:
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
||||||
per_mesh_stack = node.callDecoration("getStack")
|
per_mesh_stack = node.callDecoration("getStack")
|
||||||
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||||
|
|
|
@ -55,7 +55,7 @@ class GenericOutputController(PrinterOutputController):
|
||||||
self._preheat_hotends_timer.stop()
|
self._preheat_hotends_timer.stop()
|
||||||
for extruder in self._preheat_hotends:
|
for extruder in self._preheat_hotends:
|
||||||
extruder.updateIsPreheating(False)
|
extruder.updateIsPreheating(False)
|
||||||
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
|
self._preheat_hotends = set()
|
||||||
|
|
||||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||||
self._output_device.sendCommand("G91")
|
self._output_device.sendCommand("G91")
|
||||||
|
@ -159,7 +159,7 @@ class GenericOutputController(PrinterOutputController):
|
||||||
def _onPreheatHotendsTimerFinished(self) -> None:
|
def _onPreheatHotendsTimerFinished(self) -> None:
|
||||||
for extruder in self._preheat_hotends:
|
for extruder in self._preheat_hotends:
|
||||||
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
|
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
|
||||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
self._preheat_hotends = set()
|
||||||
|
|
||||||
# Cancel any ongoing preheating timers, without setting back the temperature to 0
|
# Cancel any ongoing preheating timers, without setting back the temperature to 0
|
||||||
# This can be used eg at the start of a print
|
# This can be used eg at the start of a print
|
||||||
|
@ -167,7 +167,7 @@ class GenericOutputController(PrinterOutputController):
|
||||||
if self._preheat_hotends_timer.isActive():
|
if self._preheat_hotends_timer.isActive():
|
||||||
for extruder in self._preheat_hotends:
|
for extruder in self._preheat_hotends:
|
||||||
extruder.updateIsPreheating(False)
|
extruder.updateIsPreheating(False)
|
||||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
self._preheat_hotends = set()
|
||||||
|
|
||||||
self._preheat_hotends_timer.stop()
|
self._preheat_hotends_timer.stop()
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
# 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, pyqtSlot, QUrl
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional, TYPE_CHECKING
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
from cura.PrinterOutput.Peripheral import Peripheral
|
||||||
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
||||||
from cura.PrinterOutput.Models.ExtruderOutputModel import ExtruderOutputModel
|
from cura.PrinterOutput.Models.ExtruderOutputModel import ExtruderOutputModel
|
||||||
|
|
||||||
MYPY = False
|
if TYPE_CHECKING:
|
||||||
if MYPY:
|
|
||||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ class PrinterOutputModel(QObject):
|
||||||
self._is_preheating = False
|
self._is_preheating = False
|
||||||
self._printer_type = ""
|
self._printer_type = ""
|
||||||
self._buildplate = ""
|
self._buildplate = ""
|
||||||
|
self._peripherals = [] # type: List[Peripheral]
|
||||||
|
|
||||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||||
self._extruders]
|
self._extruders]
|
||||||
|
@ -295,3 +296,17 @@ class PrinterOutputModel(QObject):
|
||||||
if self._printer_configuration.isValid():
|
if self._printer_configuration.isValid():
|
||||||
return self._printer_configuration
|
return self._printer_configuration
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
peripheralsChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(str, notify = peripheralsChanged)
|
||||||
|
def peripherals(self) -> str:
|
||||||
|
return ", ".join(*[peripheral.name for peripheral in self._peripherals])
|
||||||
|
|
||||||
|
def addPeripheral(self, peripheral: Peripheral) -> None:
|
||||||
|
self._peripherals.append(peripheral)
|
||||||
|
self.peripheralsChanged.emit()
|
||||||
|
|
||||||
|
def removePeripheral(self, peripheral: Peripheral) -> None:
|
||||||
|
self._peripherals.remove(peripheral)
|
||||||
|
self.peripheralsChanged.emit()
|
|
@ -60,8 +60,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._gcode = [] # type: List[str]
|
self._gcode = [] # type: List[str]
|
||||||
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
|
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
|
||||||
|
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
def setAuthenticationState(self, authentication_state: AuthState) -> None:
|
def setAuthenticationState(self, authentication_state: AuthState) -> None:
|
||||||
|
|
16
cura/PrinterOutput/Peripheral.py
Normal file
16
cura/PrinterOutput/Peripheral.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
|
||||||
|
## Data class that represents a peripheral for a printer.
|
||||||
|
#
|
||||||
|
# Output device plug-ins may specify that the printer has a certain set of
|
||||||
|
# peripherals. This set is then possibly shown in the interface of the monitor
|
||||||
|
# stage.
|
||||||
|
class Peripheral:
|
||||||
|
## Constructs the peripheral.
|
||||||
|
# \param type A unique ID for the type of peripheral.
|
||||||
|
# \param name A human-readable name for the peripheral.
|
||||||
|
def __init__(self, peripheral_type: str, name: str) -> None:
|
||||||
|
self.type = peripheral_type
|
||||||
|
self.name = name
|
|
@ -1,4 +1,4 @@
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn("Importing cura.PrinterOutput.PrintJobOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrintJobOutputModel inststad", DeprecationWarning, stacklevel=2)
|
warnings.warn("Importing cura.PrinterOutput.PrintJobOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrintJobOutputModel instead", DeprecationWarning, stacklevel=2)
|
||||||
# We moved the the models to one submodule deeper
|
# We moved the the models to one submodule deeper
|
||||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
|
@ -144,7 +144,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = printersChanged)
|
@pyqtProperty(QObject, notify = printersChanged)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn("Importing cura.PrinterOutput.PrinterOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrinterOutputModel inststad", DeprecationWarning, stacklevel=2)
|
warnings.warn("Importing cura.PrinterOutput.PrinterOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrinterOutputModel instead", DeprecationWarning, stacklevel=2)
|
||||||
# We moved the the models to one submodule deeper
|
# We moved the the models to one submodule deeper
|
||||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
|
@ -1,4 +1,4 @@
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn("Importing cura.PrinterOutputDevice has been deprecated since 4.1, use cura.PrinterOutput.PrinterOutputDevice inststad", DeprecationWarning, stacklevel=2)
|
warnings.warn("Importing cura.PrinterOutputDevice has been deprecated since 4.1, use cura.PrinterOutput.PrinterOutputDevice instead", DeprecationWarning, stacklevel=2)
|
||||||
# We moved the PrinterOutput device to it's own submodule.
|
# We moved the PrinterOutput device to it's own submodule.
|
||||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
|
@ -66,6 +66,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
node.boundingBoxChanged.connect(self._onChanged)
|
node.boundingBoxChanged.connect(self._onChanged)
|
||||||
|
|
||||||
|
per_object_stack = node.callDecoration("getStack")
|
||||||
|
if per_object_stack:
|
||||||
|
per_object_stack.propertyChanged.connect(self._onSettingValueChanged)
|
||||||
|
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## Force that a new (empty) object is created upon copy.
|
## Force that a new (empty) object is created upon copy.
|
||||||
|
@ -76,7 +80,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
def getConvexHull(self) -> Optional[Polygon]:
|
def getConvexHull(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
if self._node.callDecoration("isNonPrintingMesh"):
|
||||||
|
return None
|
||||||
hull = self._compute2DConvexHull()
|
hull = self._compute2DConvexHull()
|
||||||
|
|
||||||
if self._global_stack and self._node is not None and hull is not None:
|
if self._global_stack and self._node is not None and hull is not None:
|
||||||
|
@ -106,7 +111,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
def getConvexHullHead(self) -> Optional[Polygon]:
|
def getConvexHullHead(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
if self._node.callDecoration("isNonPrintingMesh"):
|
||||||
|
return None
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
|
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
|
||||||
head_with_fans = self._compute2DConvexHeadMin()
|
head_with_fans = self._compute2DConvexHeadMin()
|
||||||
|
@ -123,6 +129,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if self._node.callDecoration("isNonPrintingMesh"):
|
||||||
|
return None
|
||||||
|
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
|
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self.hasGroupAsParent(self._node):
|
||||||
# Printing one at a time and it's not an object in a group
|
# Printing one at a time and it's not an object in a group
|
||||||
|
@ -398,4 +407,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
## Settings that change the convex hull.
|
## Settings that change the convex hull.
|
||||||
#
|
#
|
||||||
# If these settings change, the convex hull should be recalculated.
|
# If these settings change, the convex hull should be recalculated.
|
||||||
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width"}
|
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2018 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 copy import deepcopy
|
from copy import deepcopy
|
||||||
|
@ -14,13 +14,14 @@ import cura.CuraApplication #To get the build plate.
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack # For typing.
|
from cura.Settings.ExtruderStack import ExtruderStack # For typing.
|
||||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator # For per-object settings.
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator # For per-object settings.
|
||||||
|
|
||||||
|
|
||||||
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||||
# Note that many other nodes can just be UM SceneNode objects.
|
# Note that many other nodes can just be UM SceneNode objects.
|
||||||
class CuraSceneNode(SceneNode):
|
class CuraSceneNode(SceneNode):
|
||||||
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
|
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
|
||||||
super().__init__(parent = parent, visible = visible, name = name)
|
super().__init__(parent = parent, visible = visible, name = name)
|
||||||
if not no_setting_override:
|
if not no_setting_override:
|
||||||
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
|
self.addDecorator(SettingOverrideDecorator()) # Now we always have a getActiveExtruderPosition, unless explicitly disabled
|
||||||
self._outside_buildarea = False
|
self._outside_buildarea = False
|
||||||
|
|
||||||
def setOutsideBuildArea(self, new_value: bool) -> None:
|
def setOutsideBuildArea(self, new_value: bool) -> None:
|
||||||
|
@ -85,24 +86,14 @@ class CuraSceneNode(SceneNode):
|
||||||
1.0
|
1.0
|
||||||
]
|
]
|
||||||
|
|
||||||
## Return if the provided bbox collides with the bbox of this scene node
|
|
||||||
def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
|
|
||||||
bbox = self.getBoundingBox()
|
|
||||||
if bbox is not None:
|
|
||||||
# Mark the node as outside the build volume if the bounding box test fails.
|
|
||||||
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
## Return if any area collides with the convex hull of this scene node
|
## Return if any area collides with the convex hull of this scene node
|
||||||
def collidesWithArea(self, areas: List[Polygon]) -> bool:
|
def collidesWithAreas(self, areas: List[Polygon]) -> bool:
|
||||||
convex_hull = self.callDecoration("getConvexHull")
|
convex_hull = self.callDecoration("getConvexHull")
|
||||||
if convex_hull:
|
if convex_hull:
|
||||||
if not convex_hull.isValid():
|
if not convex_hull.isValid():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check for collisions between disallowed areas and the object
|
# Check for collisions between provided areas and the object
|
||||||
for area in areas:
|
for area in areas:
|
||||||
overlap = convex_hull.intersectsPolygon(area)
|
overlap = convex_hull.intersectsPolygon(area)
|
||||||
if overlap is None:
|
if overlap is None:
|
||||||
|
@ -115,12 +106,15 @@ class CuraSceneNode(SceneNode):
|
||||||
self._aabb = None
|
self._aabb = None
|
||||||
if self._mesh_data:
|
if self._mesh_data:
|
||||||
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||||
|
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
|
||||||
|
position = self.getWorldPosition()
|
||||||
|
self._aabb = AxisAlignedBox(minimum=position, maximum=position)
|
||||||
|
|
||||||
for child in self._children:
|
for child in self.getAllChildren():
|
||||||
if child.callDecoration("isNonPrintingMesh"):
|
if child.callDecoration("isNonPrintingMesh"):
|
||||||
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
|
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
|
||||||
continue
|
continue
|
||||||
if not child._mesh_data:
|
if not child.getMeshData():
|
||||||
# Nodes without mesh data should not affect bounding boxes of their parents.
|
# Nodes without mesh data should not affect bounding boxes of their parents.
|
||||||
continue
|
continue
|
||||||
if self._aabb is None:
|
if self._aabb is None:
|
||||||
|
|
|
@ -103,13 +103,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
# \param instance_ids \type{list} the IDs of the profiles to export.
|
# \param instance_ids \type{list} the IDs of the profiles to export.
|
||||||
# \param file_name \type{str} the full path and filename to export to.
|
# \param file_name \type{str} the full path and filename to export to.
|
||||||
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
||||||
def exportQualityProfile(self, container_list, file_name, file_type):
|
# \return True if the export succeeded, false otherwise.
|
||||||
|
def exportQualityProfile(self, container_list, file_name, file_type) -> bool:
|
||||||
# Parse the fileType to deduce what plugin can save the file format.
|
# Parse the fileType to deduce what plugin can save the file format.
|
||||||
# fileType has the format "<description> (*.<extension>)"
|
# fileType has the format "<description> (*.<extension>)"
|
||||||
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
||||||
if split < 0: # Not found. Invalid format.
|
if split < 0: # Not found. Invalid format.
|
||||||
Logger.log("e", "Invalid file format identifier %s", file_type)
|
Logger.log("e", "Invalid file format identifier %s", file_type)
|
||||||
return
|
return False
|
||||||
description = file_type[:split]
|
description = file_type[:split]
|
||||||
extension = file_type[split + 4:-1] # Leave out the " (*." and ")".
|
extension = file_type[split + 4:-1] # Leave out the " (*." and ")".
|
||||||
if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any.
|
if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any.
|
||||||
|
@ -121,7 +122,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
||||||
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
||||||
if result == QMessageBox.No:
|
if result == QMessageBox.No:
|
||||||
return
|
return False
|
||||||
|
|
||||||
profile_writer = self._findProfileWriter(extension, description)
|
profile_writer = self._findProfileWriter(extension, description)
|
||||||
try:
|
try:
|
||||||
|
@ -132,17 +133,18 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
lifetime = 0,
|
lifetime = 0,
|
||||||
title = catalog.i18nc("@info:title", "Error"))
|
title = catalog.i18nc("@info:title", "Error"))
|
||||||
m.show()
|
m.show()
|
||||||
return
|
return False
|
||||||
if not success:
|
if not success:
|
||||||
Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
|
Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
|
||||||
m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name),
|
m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name),
|
||||||
lifetime = 0,
|
lifetime = 0,
|
||||||
title = catalog.i18nc("@info:title", "Error"))
|
title = catalog.i18nc("@info:title", "Error"))
|
||||||
m.show()
|
m.show()
|
||||||
return
|
return False
|
||||||
m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Exported profile to <filename>{0}</filename>", file_name),
|
m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Exported profile to <filename>{0}</filename>", file_name),
|
||||||
title = catalog.i18nc("@info:title", "Export succeeded"))
|
title = catalog.i18nc("@info:title", "Export succeeded"))
|
||||||
m.show()
|
m.show()
|
||||||
|
return True
|
||||||
|
|
||||||
## Gets the plugin object matching the criteria
|
## Gets the plugin object matching the criteria
|
||||||
# \param extension
|
# \param extension
|
||||||
|
@ -169,9 +171,6 @@ 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")}
|
||||||
|
|
||||||
plugin_registry = PluginRegistry.getInstance()
|
|
||||||
extension = file_name.split(".")[-1]
|
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.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)}
|
||||||
|
@ -180,6 +179,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
for position in sorted(global_stack.extruders):
|
for position in sorted(global_stack.extruders):
|
||||||
machine_extruders.append(global_stack.extruders[position])
|
machine_extruders.append(global_stack.extruders[position])
|
||||||
|
|
||||||
|
plugin_registry = PluginRegistry.getInstance()
|
||||||
|
extension = file_name.split(".")[-1]
|
||||||
|
|
||||||
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
||||||
if meta_data["profile_reader"][0]["extension"] != extension:
|
if meta_data["profile_reader"][0]["extension"] != extension:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -12,7 +12,7 @@ from UM.Scene.SceneNode import SceneNode
|
||||||
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.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Decorators import deprecated
|
||||||
|
|
||||||
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
|
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \param index The index of the extruder whose name to get.
|
# \param index The index of the extruder whose name to get.
|
||||||
@pyqtSlot(int, result = str)
|
@pyqtSlot(int, result = str)
|
||||||
|
@deprecated("Use Cura.MachineManager.activeMachine.extruders[index].name instead", "4.3")
|
||||||
def getExtruderName(self, index: int) -> str:
|
def getExtruderName(self, index: int) -> str:
|
||||||
try:
|
try:
|
||||||
return self.getActiveExtruderStacks()[index].getName()
|
return self.getActiveExtruderStacks()[index].getName()
|
||||||
|
@ -131,7 +132,7 @@ class ExtruderManager(QObject):
|
||||||
elif current_extruder_trains:
|
elif current_extruder_trains:
|
||||||
object_extruders.add(current_extruder_trains[0].getId())
|
object_extruders.add(current_extruder_trains[0].getId())
|
||||||
|
|
||||||
self._selected_object_extruders = list(object_extruders) # type: List[Union[str, "ExtruderStack"]]
|
self._selected_object_extruders = list(object_extruders)
|
||||||
|
|
||||||
return self._selected_object_extruders
|
return self._selected_object_extruders
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ class ExtruderManager(QObject):
|
||||||
# This will trigger a recalculation of the extruders used for the
|
# This will trigger a recalculation of the extruders used for the
|
||||||
# selection.
|
# selection.
|
||||||
def resetSelectedObjectExtruders(self) -> None:
|
def resetSelectedObjectExtruders(self) -> None:
|
||||||
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
|
self._selected_object_extruders = []
|
||||||
self.selectedObjectExtrudersChanged.emit()
|
self.selectedObjectExtrudersChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
|
@ -180,7 +181,7 @@ class ExtruderManager(QObject):
|
||||||
# \param setting_key \type{str} The setting to get the property of.
|
# \param setting_key \type{str} The setting to get the property of.
|
||||||
# \param property \type{str} The property to get.
|
# \param property \type{str} The property to get.
|
||||||
# \return \type{List} the list of results
|
# \return \type{List} the list of results
|
||||||
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
|
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List[Any]:
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for extruder_stack in self.getActiveExtruderStacks():
|
for extruder_stack in self.getActiveExtruderStacks():
|
||||||
|
@ -205,7 +206,7 @@ class ExtruderManager(QObject):
|
||||||
# list.
|
# list.
|
||||||
#
|
#
|
||||||
# \return A list of extruder stacks.
|
# \return A list of extruder stacks.
|
||||||
def getUsedExtruderStacks(self) -> List["ContainerStack"]:
|
def getUsedExtruderStacks(self) -> List["ExtruderStack"]:
|
||||||
global_stack = self._application.getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
|
@ -279,6 +280,7 @@ class ExtruderManager(QObject):
|
||||||
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||||
if extruder_str_nr == "-1":
|
if extruder_str_nr == "-1":
|
||||||
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
||||||
|
if extruder_str_nr in self.extruderIds:
|
||||||
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -264,18 +264,18 @@ class GlobalStack(CuraContainerStack):
|
||||||
def getHeadAndFansCoordinates(self):
|
def getHeadAndFansCoordinates(self):
|
||||||
return self.getProperty("machine_head_with_fans_polygon", "value")
|
return self.getProperty("machine_head_with_fans_polygon", "value")
|
||||||
|
|
||||||
def getHasMaterials(self) -> bool:
|
@pyqtProperty(int, constant=True)
|
||||||
|
def hasMaterials(self):
|
||||||
return parseBool(self.getMetaDataEntry("has_materials", False))
|
return parseBool(self.getMetaDataEntry("has_materials", False))
|
||||||
|
|
||||||
def getHasVariants(self) -> bool:
|
@pyqtProperty(int, constant=True)
|
||||||
|
def hasVariants(self):
|
||||||
return parseBool(self.getMetaDataEntry("has_variants", False))
|
return parseBool(self.getMetaDataEntry("has_variants", False))
|
||||||
|
|
||||||
def getHasVariantsBuildPlates(self) -> bool:
|
@pyqtProperty(int, constant=True)
|
||||||
|
def hasVariantBuildplates(self) -> bool:
|
||||||
return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
|
return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
|
||||||
|
|
||||||
def getHasMachineQuality(self) -> bool:
|
|
||||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
|
||||||
|
|
||||||
## Get default firmware file name if one is specified in the firmware
|
## Get default firmware file name if one is specified in the firmware
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
def getDefaultFirmwareName(self) -> str:
|
def getDefaultFirmwareName(self) -> str:
|
||||||
|
|
|
@ -38,11 +38,10 @@ from .CuraStackBuilder import CuraStackBuilder
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.CuraContainerStack import CuraContainerStack
|
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
|
||||||
from cura.Machines.MaterialManager import MaterialManager
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
from cura.Machines.QualityManager import QualityManager
|
from cura.Machines.QualityManager import QualityManager
|
||||||
from cura.Machines.VariantManager import VariantManager
|
from cura.Machines.VariantManager import VariantManager
|
||||||
|
@ -388,12 +387,13 @@ class MachineManager(QObject):
|
||||||
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
if machine.definition.getId() == definition_id:
|
if machine.definition.getId() == definition_id:
|
||||||
return machine
|
return cast(GlobalStack, machine)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def addMachine(self, definition_id: str, name: Optional[str] = None) -> None:
|
def addMachine(self, definition_id: str, name: Optional[str] = None) -> None:
|
||||||
|
Logger.log("i", "Trying to add a machine with the definition id [%s]", definition_id)
|
||||||
if name is None:
|
if name is None:
|
||||||
definitions = CuraContainerRegistry.getInstance().findDefinitionContainers(id = definition_id)
|
definitions = CuraContainerRegistry.getInstance().findDefinitionContainers(id = definition_id)
|
||||||
if definitions:
|
if definitions:
|
||||||
|
@ -464,6 +464,7 @@ class MachineManager(QObject):
|
||||||
# \param key \type{str} the name of the key to delete
|
# \param key \type{str} the name of the key to delete
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def clearUserSettingAllCurrentStacks(self, key: str) -> None:
|
def clearUserSettingAllCurrentStacks(self, key: str) -> None:
|
||||||
|
Logger.log("i", "Clearing the setting [%s] from all stacks", key)
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -536,6 +537,7 @@ class MachineManager(QObject):
|
||||||
return bool(self._printer_output_devices)
|
return bool(self._printer_output_devices)
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.configuredConnectionTypes instead", "4.2")
|
||||||
def activeMachineHasRemoteConnection(self) -> bool:
|
def activeMachineHasRemoteConnection(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
has_remote_connection = False
|
has_remote_connection = False
|
||||||
|
@ -786,6 +788,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def removeMachine(self, machine_id: str) -> None:
|
def removeMachine(self, machine_id: str) -> None:
|
||||||
|
Logger.log("i", "Attempting to remove a machine with the id [%s]", machine_id)
|
||||||
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
|
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
|
||||||
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
|
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
|
||||||
|
|
||||||
|
@ -813,21 +816,24 @@ class MachineManager(QObject):
|
||||||
self.removeMachine(hidden_containers[0].getId())
|
self.removeMachine(hidden_containers[0].getId())
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.hasMaterials instead", "4.2")
|
||||||
def hasMaterials(self) -> bool:
|
def hasMaterials(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getHasMaterials()
|
return self._global_container_stack.hasMaterials
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.hasVariants instead", "4.2")
|
||||||
def hasVariants(self) -> bool:
|
def hasVariants(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getHasVariants()
|
return self._global_container_stack.hasVariants
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.hasVariantBuildplates instead", "4.2")
|
||||||
def hasVariantBuildplates(self) -> bool:
|
def hasVariantBuildplates(self) -> bool:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getHasVariantsBuildPlates()
|
return self._global_container_stack.hasVariantBuildplates
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## The selected buildplate is compatible if it is compatible with all the materials in all the extruders
|
## The selected buildplate is compatible if it is compatible with all the materials in all the extruders
|
||||||
|
@ -890,17 +896,12 @@ 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
|
||||||
setting_enabled = self._global_container_stack.getProperty(setting_key, "enabled")
|
|
||||||
if not setting_enabled:
|
|
||||||
# A setting is not visible anymore
|
|
||||||
result.append(setting_key)
|
|
||||||
Logger.log("d", "Reset setting [%s] from [%s] because the setting is no longer enabled", setting_key, container)
|
|
||||||
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
|
||||||
|
|
||||||
old_value = container.getProperty(setting_key, "value")
|
old_value = container.getProperty(setting_key, "value")
|
||||||
|
if isinstance(old_value, SettingFunction):
|
||||||
|
old_value = old_value(self._global_container_stack)
|
||||||
if int(old_value) < 0:
|
if int(old_value) < 0:
|
||||||
continue
|
continue
|
||||||
if int(old_value) >= extruder_count or not self._global_container_stack.extruders[str(old_value)].isEnabled:
|
if int(old_value) >= extruder_count or not self._global_container_stack.extruders[str(old_value)].isEnabled:
|
||||||
|
@ -919,9 +920,8 @@ class MachineManager(QObject):
|
||||||
# Apply quality changes that are incompatible to user changes, so we do not change the quality changes itself.
|
# Apply quality changes that are incompatible to user changes, so we do not change the quality changes itself.
|
||||||
self._global_container_stack.userChanges.setProperty(setting_key, "value", self._default_extruder_position)
|
self._global_container_stack.userChanges.setProperty(setting_key, "value", self._default_extruder_position)
|
||||||
if add_user_changes:
|
if add_user_changes:
|
||||||
caution_message = Message(catalog.i18nc(
|
caution_message = Message(
|
||||||
"@info:generic",
|
catalog.i18nc("@info:message Followed by a list of settings.", "Settings have been changed to match the current availability of extruders:") + " [{settings_list}]".format(settings_list = ", ".join(add_user_changes)),
|
||||||
"Settings have been changed to match the current availability of extruders: [%s]" % ", ".join(add_user_changes)),
|
|
||||||
lifetime = 0,
|
lifetime = 0,
|
||||||
title = catalog.i18nc("@info:title", "Settings updated"))
|
title = catalog.i18nc("@info:title", "Settings updated"))
|
||||||
caution_message.show()
|
caution_message.show()
|
||||||
|
@ -985,6 +985,8 @@ class MachineManager(QObject):
|
||||||
self._application.globalContainerStackChanged.emit()
|
self._application.globalContainerStackChanged.emit()
|
||||||
self.forceUpdateAllSettings()
|
self.forceUpdateAllSettings()
|
||||||
|
|
||||||
|
# Note that this function is deprecated, but the decorators for this don't play well together!
|
||||||
|
# @deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
|
||||||
@pyqtSlot(int, result = QObject)
|
@pyqtSlot(int, result = QObject)
|
||||||
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
|
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
|
@ -1099,6 +1101,7 @@ class MachineManager(QObject):
|
||||||
container.removeInstance(setting_name)
|
container.removeInstance(setting_name)
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = globalContainerChanged)
|
@pyqtProperty("QVariantList", notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
|
||||||
def currentExtruderPositions(self) -> List[str]:
|
def currentExtruderPositions(self) -> List[str]:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return []
|
return []
|
||||||
|
@ -1108,9 +1111,17 @@ class MachineManager(QObject):
|
||||||
def _onRootMaterialChanged(self) -> None:
|
def _onRootMaterialChanged(self) -> None:
|
||||||
self._current_root_material_id = {}
|
self._current_root_material_id = {}
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
for position in self._global_container_stack.extruders:
|
for position in self._global_container_stack.extruders:
|
||||||
self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
|
material_id = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
|
||||||
|
if position not in self._current_root_material_id or material_id != self._current_root_material_id[position]:
|
||||||
|
changed = True
|
||||||
|
self._current_root_material_id[position] = material_id
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
self.activeMaterialChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty("QVariant", notify = rootMaterialChanged)
|
@pyqtProperty("QVariant", notify = rootMaterialChanged)
|
||||||
def currentRootMaterialId(self) -> Dict[str, str]:
|
def currentRootMaterialId(self) -> Dict[str, str]:
|
||||||
|
@ -1263,8 +1274,8 @@ class MachineManager(QObject):
|
||||||
if self._global_container_stack is not None:
|
if self._global_container_stack is not None:
|
||||||
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
||||||
for position, extruder in self._global_container_stack.extruders.items():
|
for position, extruder in self._global_container_stack.extruders.items():
|
||||||
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
if not extruder.isEnabled:
|
||||||
return False
|
continue
|
||||||
if not extruder.material.getMetaDataEntry("compatible"):
|
if not extruder.material.getMetaDataEntry("compatible"):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -1273,7 +1284,7 @@ class MachineManager(QObject):
|
||||||
def _updateQualityWithMaterial(self, *args: Any) -> None:
|
def _updateQualityWithMaterial(self, *args: Any) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
Logger.log("i", "Updating quality/quality_changes due to material change")
|
Logger.log("d", "Updating quality/quality_changes due to material change")
|
||||||
current_quality_type = None
|
current_quality_type = None
|
||||||
if self._current_quality_group:
|
if self._current_quality_group:
|
||||||
current_quality_type = self._current_quality_group.quality_type
|
current_quality_type = self._current_quality_group.quality_type
|
||||||
|
@ -1354,6 +1365,7 @@ class MachineManager(QObject):
|
||||||
# instance with the same network key.
|
# instance with the same network key.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def switchPrinterType(self, machine_name: str) -> None:
|
def switchPrinterType(self, machine_name: str) -> None:
|
||||||
|
Logger.log("i", "Attempting to switch the printer type to [%s]", machine_name)
|
||||||
# Don't switch if the user tries to change to the same type of printer
|
# Don't switch if the user tries to change to the same type of printer
|
||||||
if self._global_container_stack is None or self.activeMachineDefinitionName == machine_name:
|
if self._global_container_stack is None or self.activeMachineDefinitionName == machine_name:
|
||||||
return
|
return
|
||||||
|
@ -1643,21 +1655,6 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
return abbr_machine
|
return abbr_machine
|
||||||
|
|
||||||
# Checks if the given machine type name in the available machine list.
|
|
||||||
# The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
|
|
||||||
# the machine type, which is "Ultimaker 3" for "ultimaker_3".
|
|
||||||
def hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
|
|
||||||
results = self._container_registry.findDefinitionContainersMetadata(name = machine_type_name)
|
|
||||||
return len(results) > 0
|
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
|
||||||
def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
|
|
||||||
machine_type_name = ""
|
|
||||||
results = self._container_registry.findDefinitionContainersMetadata(id = machine_type_id)
|
|
||||||
if results:
|
|
||||||
machine_type_name = results[0]["name"]
|
|
||||||
return machine_type_name
|
|
||||||
|
|
||||||
# Gets all machines that belong to the given group_id.
|
# Gets all machines that belong to the given group_id.
|
||||||
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
|
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
|
||||||
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)
|
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)
|
||||||
|
|
|
@ -114,14 +114,9 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
def _onSettingChanged(self, setting_key, property_name): # Reminder: 'property' is a built-in function
|
def _onSettingChanged(self, setting_key, property_name): # Reminder: 'property' is a built-in function
|
||||||
# We're only interested in a few settings and only if it's value changed.
|
# We're only interested in a few settings and only if it's value changed.
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
if setting_key in self._non_printing_mesh_settings or setting_key in self._non_thumbnail_visible_settings:
|
|
||||||
# Trigger slice/need slicing if the value has changed.
|
# Trigger slice/need slicing if the value has changed.
|
||||||
new_is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
|
self._is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
|
||||||
self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
|
self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
|
||||||
|
|
||||||
if self._is_non_printing_mesh != new_is_non_printing_mesh:
|
|
||||||
self._is_non_printing_mesh = new_is_non_printing_mesh
|
|
||||||
|
|
||||||
Application.getInstance().getBackend().needsSlicing()
|
Application.getInstance().getBackend().needsSlicing()
|
||||||
Application.getInstance().getBackend().tickle()
|
Application.getInstance().getBackend().tickle()
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
# Copyright (c) 2018 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 copy
|
import copy
|
||||||
|
|
||||||
from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container
|
from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
# Empty definition changes
|
# Empty definition changes
|
||||||
|
@ -28,7 +30,7 @@ empty_material_container.setMetaDataEntry("type", "material")
|
||||||
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
|
||||||
empty_quality_container = copy.deepcopy(empty_container)
|
empty_quality_container = copy.deepcopy(empty_container)
|
||||||
empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID)
|
empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID)
|
||||||
empty_quality_container.setName("Not Supported")
|
empty_quality_container.setName(catalog.i18nc("@info:not supported profile", "Not supported"))
|
||||||
empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
|
empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
|
||||||
empty_quality_container.setMetaDataEntry("type", "quality")
|
empty_quality_container.setMetaDataEntry("type", "quality")
|
||||||
empty_quality_container.setMetaDataEntry("supported", False)
|
empty_quality_container.setMetaDataEntry("supported", False)
|
||||||
|
|
|
@ -48,12 +48,12 @@ class Snapshot:
|
||||||
# determine zoom and look at
|
# determine zoom and look at
|
||||||
bbox = None
|
bbox = None
|
||||||
for node in DepthFirstIterator(root):
|
for node in DepthFirstIterator(root):
|
||||||
|
if not getattr(node, "_outside_buildarea", False):
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"):
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||||
if bbox is None:
|
if bbox is None:
|
||||||
bbox = node.getBoundingBox()
|
bbox = node.getBoundingBox()
|
||||||
else:
|
else:
|
||||||
bbox = bbox + node.getBoundingBox()
|
bbox = bbox + node.getBoundingBox()
|
||||||
|
|
||||||
# If there is no bounding box, it means that there is no model in the buildplate
|
# If there is no bounding box, it means that there is no model in the buildplate
|
||||||
if bbox is None:
|
if bbox is None:
|
||||||
return None
|
return None
|
||||||
|
@ -66,7 +66,7 @@ class Snapshot:
|
||||||
looking_from_offset = Vector(-1, 1, 2)
|
looking_from_offset = Vector(-1, 1, 2)
|
||||||
if size > 0:
|
if size > 0:
|
||||||
# determine the watch distance depending on the size
|
# determine the watch distance depending on the size
|
||||||
looking_from_offset = looking_from_offset * size * 1.3
|
looking_from_offset = looking_from_offset * size * 1.75
|
||||||
camera.setPosition(look_at + looking_from_offset)
|
camera.setPosition(look_at + looking_from_offset)
|
||||||
camera.lookAt(look_at)
|
camera.lookAt(look_at)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright (c) 2018 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.Logger import Logger
|
||||||
|
import re
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from collections import defaultdict
|
from PyQt5.QtCore import QTimer, Qt
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
@ -17,13 +17,40 @@ from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
# Simple convenience class to keep stuff together. Since we're still stuck on python 3.5, we can't use the full
|
||||||
|
# typed named tuple, so we have to do it like this.
|
||||||
|
# Once we are at python 3.6, feel free to change this to a named tuple.
|
||||||
|
class _NodeInfo:
|
||||||
|
def __init__(self, index_to_node: Optional[Dict[int, SceneNode]] = None, nodes_to_rename: Optional[List[SceneNode]] = None, is_group: bool = False) -> None:
|
||||||
|
if index_to_node is None:
|
||||||
|
index_to_node = {}
|
||||||
|
if nodes_to_rename is None:
|
||||||
|
nodes_to_rename = []
|
||||||
|
self.index_to_node = index_to_node # type: Dict[int, SceneNode]
|
||||||
|
self.nodes_to_rename = nodes_to_rename # type: List[SceneNode]
|
||||||
|
self.is_group = is_group # type: bool
|
||||||
|
|
||||||
|
|
||||||
## Keep track of all objects in the project
|
## Keep track of all objects in the project
|
||||||
class ObjectsModel(ListModel):
|
class ObjectsModel(ListModel):
|
||||||
def __init__(self) -> None:
|
NameRole = Qt.UserRole + 1
|
||||||
super().__init__()
|
SelectedRole = Qt.UserRole + 2
|
||||||
|
OutsideAreaRole = Qt.UserRole + 3
|
||||||
|
BuilplateNumberRole = Qt.UserRole + 4
|
||||||
|
NodeRole = Qt.UserRole + 5
|
||||||
|
|
||||||
|
def __init__(self, parent = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.SelectedRole, "selected")
|
||||||
|
self.addRoleName(self.OutsideAreaRole, "outside_build_area")
|
||||||
|
self.addRoleName(self.BuilplateNumberRole, "buildplate_number")
|
||||||
|
self.addRoleName(self.NodeRole, "node")
|
||||||
|
|
||||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
|
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
|
||||||
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
|
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
|
||||||
|
Selection.selectionChanged.connect(self._updateDelayed)
|
||||||
|
|
||||||
self._update_timer = QTimer()
|
self._update_timer = QTimer()
|
||||||
self._update_timer.setInterval(200)
|
self._update_timer.setInterval(200)
|
||||||
|
@ -32,6 +59,11 @@ class ObjectsModel(ListModel):
|
||||||
|
|
||||||
self._build_plate_number = -1
|
self._build_plate_number = -1
|
||||||
|
|
||||||
|
self._group_name_template = catalog.i18nc("@label", "Group #{group_nr}")
|
||||||
|
self._group_name_prefix = self._group_name_template.split("#")[0]
|
||||||
|
|
||||||
|
self._naming_regex = re.compile("^(.+)\(([0-9]+)\)$")
|
||||||
|
|
||||||
def setActiveBuildPlate(self, nr: int) -> None:
|
def setActiveBuildPlate(self, nr: int) -> None:
|
||||||
if self._build_plate_number != nr:
|
if self._build_plate_number != nr:
|
||||||
self._build_plate_number = nr
|
self._build_plate_number = nr
|
||||||
|
@ -44,61 +76,109 @@ class ObjectsModel(ListModel):
|
||||||
def _updateDelayed(self, *args) -> None:
|
def _updateDelayed(self, *args) -> None:
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
||||||
def _update(self, *args) -> None:
|
def _shouldNodeBeHandled(self, node: SceneNode) -> bool:
|
||||||
nodes = []
|
is_group = bool(node.callDecoration("isGroup"))
|
||||||
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
|
if not node.callDecoration("isSliceable") and not is_group:
|
||||||
active_build_plate_number = self._build_plate_number
|
return False
|
||||||
group_nr = 1
|
|
||||||
name_count_dict = defaultdict(int) # type: Dict[str, int]
|
|
||||||
|
|
||||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore
|
|
||||||
if not isinstance(node, SceneNode):
|
|
||||||
continue
|
|
||||||
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
parent = node.getParent()
|
parent = node.getParent()
|
||||||
if parent and parent.callDecoration("isGroup"):
|
if parent and parent.callDecoration("isGroup"):
|
||||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
return False # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
|
||||||
continue
|
|
||||||
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
|
if Application.getInstance().getPreferences().getValue("view/filter_current_build_plate") and node_build_plate_number != self._build_plate_number:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _renameNodes(self, node_info_dict: Dict[str, _NodeInfo]) -> List[SceneNode]:
|
||||||
|
# Go through all names and find out the names for all nodes that need to be renamed.
|
||||||
|
all_nodes = [] # type: List[SceneNode]
|
||||||
|
for name, node_info in node_info_dict.items():
|
||||||
|
# First add the ones that do not need to be renamed.
|
||||||
|
for node in node_info.index_to_node.values():
|
||||||
|
all_nodes.append(node)
|
||||||
|
|
||||||
|
# Generate new names for the nodes that need to be renamed
|
||||||
|
current_index = 0
|
||||||
|
for node in node_info.nodes_to_rename:
|
||||||
|
current_index += 1
|
||||||
|
while current_index in node_info.index_to_node:
|
||||||
|
current_index += 1
|
||||||
|
|
||||||
|
if not node_info.is_group:
|
||||||
|
new_group_name = "{0}({1})".format(name, current_index)
|
||||||
|
else:
|
||||||
|
new_group_name = "{0}#{1}".format(name, current_index)
|
||||||
|
|
||||||
|
old_name = node.getName()
|
||||||
|
node.setName(new_group_name)
|
||||||
|
Logger.log("d", "Node [%s] renamed to [%s]", old_name, new_group_name)
|
||||||
|
all_nodes.append(node)
|
||||||
|
return all_nodes
|
||||||
|
|
||||||
|
def _update(self, *args) -> None:
|
||||||
|
nodes = [] # type: List[Dict[str, Union[str, int, bool, SceneNode]]]
|
||||||
|
name_to_node_info_dict = {} # type: Dict[str, _NodeInfo]
|
||||||
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore
|
||||||
|
if not self._shouldNodeBeHandled(node):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not node.callDecoration("isGroup"):
|
is_group = bool(node.callDecoration("isGroup"))
|
||||||
|
|
||||||
|
force_rename = False
|
||||||
|
if not is_group:
|
||||||
|
# Handle names for individual nodes
|
||||||
name = node.getName()
|
name = node.getName()
|
||||||
|
|
||||||
|
name_match = self._naming_regex.fullmatch(name)
|
||||||
|
if name_match is None:
|
||||||
|
original_name = name
|
||||||
|
name_index = 0
|
||||||
else:
|
else:
|
||||||
name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
|
original_name = name_match.groups()[0]
|
||||||
group_nr += 1
|
name_index = int(name_match.groups()[1])
|
||||||
|
else:
|
||||||
|
# Handle names for grouped nodes
|
||||||
|
original_name = self._group_name_prefix
|
||||||
|
|
||||||
|
current_name = node.getName()
|
||||||
|
if current_name.startswith(self._group_name_prefix):
|
||||||
|
name_index = int(current_name.split("#")[-1])
|
||||||
|
else:
|
||||||
|
# Force rename this group because this node has not been named as a group yet, probably because
|
||||||
|
# it's a newly created group.
|
||||||
|
name_index = 0
|
||||||
|
force_rename = True
|
||||||
|
|
||||||
|
if original_name not in name_to_node_info_dict:
|
||||||
|
# Keep track of 2 things:
|
||||||
|
# - known indices for nodes which doesn't need to be renamed
|
||||||
|
# - a list of nodes that need to be renamed. When renaming then, we should avoid using the known indices.
|
||||||
|
name_to_node_info_dict[original_name] = _NodeInfo(is_group = is_group)
|
||||||
|
node_info = name_to_node_info_dict[original_name]
|
||||||
|
if not force_rename and name_index not in node_info.index_to_node:
|
||||||
|
node_info.index_to_node[name_index] = node
|
||||||
|
else:
|
||||||
|
node_info.nodes_to_rename.append(node)
|
||||||
|
|
||||||
|
all_nodes = self._renameNodes(name_to_node_info_dict)
|
||||||
|
|
||||||
|
for node in all_nodes:
|
||||||
if hasattr(node, "isOutsideBuildArea"):
|
if hasattr(node, "isOutsideBuildArea"):
|
||||||
is_outside_build_area = node.isOutsideBuildArea() # type: ignore
|
is_outside_build_area = node.isOutsideBuildArea() # type: ignore
|
||||||
else:
|
else:
|
||||||
is_outside_build_area = False
|
is_outside_build_area = False
|
||||||
|
|
||||||
#check if we already have an instance of the object based on name
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
name_count_dict[name] += 1
|
|
||||||
name_count = name_count_dict[name]
|
|
||||||
|
|
||||||
if name_count > 1:
|
|
||||||
name = "{0}({1})".format(name, name_count-1)
|
|
||||||
node.setName(name)
|
|
||||||
|
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"name": name,
|
"name": node.getName(),
|
||||||
"isSelected": Selection.isSelected(node),
|
"selected": Selection.isSelected(node),
|
||||||
"isOutsideBuildArea": is_outside_build_area,
|
"outside_build_area": is_outside_build_area,
|
||||||
"buildPlateNumber": node_build_plate_number,
|
"buildplate_number": node_build_plate_number,
|
||||||
"node": node
|
"node": node
|
||||||
})
|
})
|
||||||
|
|
||||||
nodes = sorted(nodes, key=lambda n: n["name"])
|
nodes = sorted(nodes, key=lambda n: n["name"])
|
||||||
self.setItems(nodes)
|
self.setItems(nodes)
|
||||||
|
|
||||||
self.itemsChanged.emit()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def createObjectsModel():
|
|
||||||
return ObjectsModel()
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ class PrintInformation(QObject):
|
||||||
"support_interface": catalog.i18nc("@tooltip", "Support Interface"),
|
"support_interface": catalog.i18nc("@tooltip", "Support Interface"),
|
||||||
"support": catalog.i18nc("@tooltip", "Support"),
|
"support": catalog.i18nc("@tooltip", "Support"),
|
||||||
"skirt": catalog.i18nc("@tooltip", "Skirt"),
|
"skirt": catalog.i18nc("@tooltip", "Skirt"),
|
||||||
|
"prime_tower": catalog.i18nc("@tooltip", "Prime Tower"),
|
||||||
"travel": catalog.i18nc("@tooltip", "Travel"),
|
"travel": catalog.i18nc("@tooltip", "Travel"),
|
||||||
"retract": catalog.i18nc("@tooltip", "Retractions"),
|
"retract": catalog.i18nc("@tooltip", "Retractions"),
|
||||||
"none": catalog.i18nc("@tooltip", "Other")
|
"none": catalog.i18nc("@tooltip", "Other")
|
||||||
|
|
49
cura/UI/RecommendedMode.py
Normal file
49
cura/UI/RecommendedMode.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
|
||||||
|
from cura import CuraApplication
|
||||||
|
|
||||||
|
#
|
||||||
|
# This object contains helper/convenience functions for Recommended mode.
|
||||||
|
#
|
||||||
|
class RecommendedMode(QObject):
|
||||||
|
|
||||||
|
# Sets to use the adhesion or not for the "Adhesion" CheckBox in Recommended mode.
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def setAdhesion(self, checked: bool) -> None:
|
||||||
|
application = CuraApplication.CuraApplication.getInstance()
|
||||||
|
global_stack = application.getMachineManager().activeMachine
|
||||||
|
if global_stack is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove the adhesion type value set by the user.
|
||||||
|
adhesion_type_key = "adhesion_type"
|
||||||
|
user_changes_container = global_stack.userChanges
|
||||||
|
if adhesion_type_key in user_changes_container.getAllKeys():
|
||||||
|
user_changes_container.removeInstance(adhesion_type_key)
|
||||||
|
|
||||||
|
# Get the default value of adhesion type after user's value has been removed.
|
||||||
|
# skirt and none are counted as "no adhesion", the others are considered as "with adhesion". The conditions are
|
||||||
|
# as the following:
|
||||||
|
# - if the user checks the adhesion checkbox, get the default value (including the custom quality) for adhesion
|
||||||
|
# type.
|
||||||
|
# (1) If the default value is "skirt" or "none" (no adhesion), set adhesion_type to "brim".
|
||||||
|
# (2) If the default value is "with adhesion", do nothing.
|
||||||
|
# - if the user unchecks the adhesion checkbox, get the default value (including the custom quality) for
|
||||||
|
# adhesion type.
|
||||||
|
# (1) If the default value is "skirt" or "none" (no adhesion), do nothing.
|
||||||
|
# (2) Otherwise, set adhesion_type to "skirt".
|
||||||
|
value = global_stack.getProperty(adhesion_type_key, "value")
|
||||||
|
if checked:
|
||||||
|
if value in ("skirt", "none"):
|
||||||
|
value = "brim"
|
||||||
|
else:
|
||||||
|
if value not in ("skirt", "none"):
|
||||||
|
value = "skirt"
|
||||||
|
|
||||||
|
user_changes_container.setProperty(adhesion_type_key, "value", value)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["RecommendedMode"]
|
|
@ -32,7 +32,8 @@ if not known_args["debug"]:
|
||||||
elif Platform.isOSX():
|
elif Platform.isOSX():
|
||||||
return os.path.expanduser("~/Library/Logs/" + CuraAppName)
|
return os.path.expanduser("~/Library/Logs/" + CuraAppName)
|
||||||
|
|
||||||
if hasattr(sys, "frozen"):
|
# Do not redirect stdout and stderr to files if we are running CLI.
|
||||||
|
if hasattr(sys, "frozen") and "cli" not in os.path.basename(sys.argv[0]).lower():
|
||||||
dirpath = get_cura_dir_path()
|
dirpath = get_cura_dir_path()
|
||||||
os.makedirs(dirpath, exist_ok = True)
|
os.makedirs(dirpath, exist_ok = True)
|
||||||
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
|
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
|
||||||
|
|
|
@ -419,13 +419,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
if parser.has_option("metadata", "enabled"):
|
if parser.has_option("metadata", "enabled"):
|
||||||
extruder_info.enabled = parser["metadata"]["enabled"]
|
extruder_info.enabled = parser["metadata"]["enabled"]
|
||||||
if variant_id not in ("empty", "empty_variant"):
|
if variant_id not in ("empty", "empty_variant"):
|
||||||
|
if variant_id in instance_container_info_dict:
|
||||||
extruder_info.variant_info = instance_container_info_dict[variant_id]
|
extruder_info.variant_info = instance_container_info_dict[variant_id]
|
||||||
|
|
||||||
if material_id not in ("empty", "empty_material"):
|
if material_id not in ("empty", "empty_material"):
|
||||||
root_material_id = reverse_material_id_dict[material_id]
|
root_material_id = reverse_material_id_dict[material_id]
|
||||||
extruder_info.root_material_id = root_material_id
|
extruder_info.root_material_id = root_material_id
|
||||||
|
|
||||||
definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)]
|
definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)]
|
||||||
if definition_changes_id not in ("empty", "empty_definition_changes"):
|
if definition_changes_id not in ("empty", "empty_definition_changes"):
|
||||||
extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
|
extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
|
||||||
|
|
||||||
user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)]
|
user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)]
|
||||||
if user_changes_id not in ("empty", "empty_user_changes"):
|
if user_changes_id not in ("empty", "empty_user_changes"):
|
||||||
extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
|
extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
|
||||||
|
@ -905,6 +909,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
continue
|
continue
|
||||||
extruder_info = self._machine_info.extruder_info_dict[position]
|
extruder_info = self._machine_info.extruder_info_dict[position]
|
||||||
if extruder_info.variant_info is None:
|
if extruder_info.variant_info is None:
|
||||||
|
# If there is no variant_info, try to use the default variant. Otherwise, leave it be.
|
||||||
|
node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE, global_stack)
|
||||||
|
if node is not None and node.getContainer() is not None:
|
||||||
|
extruder_stack.variant = node.getContainer()
|
||||||
continue
|
continue
|
||||||
parser = extruder_info.variant_info.parser
|
parser = extruder_info.variant_info.parser
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
|
||||||
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
|
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
|
||||||
Logger.log("d", "Aborting process layers job...")
|
Logger.log("i", "Aborting process layers job...")
|
||||||
self._process_layers_job.abort()
|
self._process_layers_job.abort()
|
||||||
self._process_layers_job = None
|
self._process_layers_job = None
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Perform a slice of the scene.
|
## Perform a slice of the scene.
|
||||||
def slice(self) -> None:
|
def slice(self) -> None:
|
||||||
Logger.log("d", "Starting to slice...")
|
Logger.log("i", "Starting to slice...")
|
||||||
self._slice_start_time = time()
|
self._slice_start_time = time()
|
||||||
if not self._build_plates_to_be_sliced:
|
if not self._build_plates_to_be_sliced:
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
|
@ -517,9 +517,6 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
self.printDurationMessage.emit(source_build_plate_number, {}, [])
|
self.printDurationMessage.emit(source_build_plate_number, {}, [])
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.setState(BackendState.NotStarted)
|
|
||||||
# if not self._use_timer:
|
|
||||||
# With manually having to slice, we want to clear the old invalid layer data.
|
|
||||||
self._clearLayerData(build_plate_changed)
|
self._clearLayerData(build_plate_changed)
|
||||||
|
|
||||||
self._invokeSlice()
|
self._invokeSlice()
|
||||||
|
@ -563,10 +560,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Convenient function: mark everything to slice, emit state and clear layer data
|
## Convenient function: mark everything to slice, emit state and clear layer data
|
||||||
def needsSlicing(self) -> None:
|
def needsSlicing(self) -> None:
|
||||||
|
self.determineAutoSlicing()
|
||||||
self.stopSlicing()
|
self.stopSlicing()
|
||||||
self.markSliceAll()
|
self.markSliceAll()
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.setState(BackendState.NotStarted)
|
|
||||||
if not self._use_timer:
|
if not self._use_timer:
|
||||||
# With manually having to slice, we want to clear the old invalid layer data.
|
# With manually having to slice, we want to clear the old invalid layer data.
|
||||||
self._clearLayerData()
|
self._clearLayerData()
|
||||||
|
@ -735,6 +732,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
"support_interface": message.time_support_interface,
|
"support_interface": message.time_support_interface,
|
||||||
"support": message.time_support,
|
"support": message.time_support,
|
||||||
"skirt": message.time_skirt,
|
"skirt": message.time_skirt,
|
||||||
|
"prime_tower": message.time_prime_tower,
|
||||||
"travel": message.time_travel,
|
"travel": message.time_travel,
|
||||||
"retract": message.time_retract,
|
"retract": message.time_retract,
|
||||||
"none": message.time_none
|
"none": message.time_none
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#Copyright (c) 2017 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 gc
|
import gc
|
||||||
|
|
|
@ -107,7 +107,7 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
for key in stack.getAllKeys():
|
for key in stack.getAllKeys():
|
||||||
validation_state = stack.getProperty(key, "validationState")
|
validation_state = stack.getProperty(key, "validationState")
|
||||||
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
|
||||||
Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
|
Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
|
||||||
return True
|
return True
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
# Copyright (c) 2018 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 configparser
|
import configparser
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.ReaderWriters.ProfileReader import ProfileReader
|
from cura.ReaderWriters.ProfileReader import ProfileReader
|
||||||
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
@ -17,23 +20,26 @@ import zipfile
|
||||||
class CuraProfileReader(ProfileReader):
|
class CuraProfileReader(ProfileReader):
|
||||||
## Initialises the cura profile reader.
|
## Initialises the cura profile reader.
|
||||||
# This does nothing since the only other function is basically stateless.
|
# This does nothing since the only other function is basically stateless.
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
## Reads a cura profile from a file and returns it.
|
## Reads a cura profile from a file and returns it.
|
||||||
#
|
#
|
||||||
# \param file_name The file to read the cura profile from.
|
# \param file_name The file to read the cura profile from.
|
||||||
# \return The cura profile that was in the file, if any. If the file could
|
# \return The cura profiles that were in the file, if any. If the file
|
||||||
# not be read or didn't contain a valid profile, \code None \endcode is
|
# could not be read or didn't contain a valid profile, ``None`` is
|
||||||
# returned.
|
# returned.
|
||||||
def read(self, file_name):
|
def read(self, file_name: str) -> List[Optional[InstanceContainer]]:
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(file_name, "r") as archive:
|
with zipfile.ZipFile(file_name, "r") as archive:
|
||||||
results = []
|
results = [] # type: List[Optional[InstanceContainer]]
|
||||||
for profile_id in archive.namelist():
|
for profile_id in archive.namelist():
|
||||||
with archive.open(profile_id) as f:
|
with archive.open(profile_id) as f:
|
||||||
serialized = f.read()
|
serialized = f.read()
|
||||||
profile = self._loadProfile(serialized.decode("utf-8"), profile_id)
|
upgraded_profiles = self._upgradeProfile(serialized.decode("utf-8"), profile_id) #After upgrading it may split into multiple profiles.
|
||||||
|
for upgraded_profile in upgraded_profiles:
|
||||||
|
serialization, new_id = upgraded_profile
|
||||||
|
profile = self._loadProfile(serialization, new_id)
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
results.append(profile)
|
results.append(profile)
|
||||||
return results
|
return results
|
||||||
|
@ -41,15 +47,16 @@ class CuraProfileReader(ProfileReader):
|
||||||
except zipfile.BadZipFile:
|
except zipfile.BadZipFile:
|
||||||
# It must be an older profile from Cura 2.1.
|
# It must be an older profile from Cura 2.1.
|
||||||
with open(file_name, encoding = "utf-8") as fhandle:
|
with open(file_name, encoding = "utf-8") as fhandle:
|
||||||
serialized = fhandle.read()
|
serialized_bytes = fhandle.read()
|
||||||
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
|
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized_bytes, file_name)]
|
||||||
|
|
||||||
## Convert a profile from an old Cura to this Cura if needed.
|
## Convert a profile from an old Cura to this Cura if needed.
|
||||||
#
|
#
|
||||||
# \param serialized \type{str} The profile data to convert in the serialized on-disk format.
|
# \param serialized The profile data to convert in the serialized on-disk
|
||||||
# \param profile_id \type{str} The name of the profile.
|
# format.
|
||||||
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
|
# \param profile_id The name of the profile.
|
||||||
def _upgradeProfile(self, serialized, profile_id):
|
# \return List of serialized profile strings and matching profile names.
|
||||||
|
def _upgradeProfile(self, serialized: str, profile_id: str) -> List[Tuple[str, str]]:
|
||||||
parser = configparser.ConfigParser(interpolation = None)
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
parser.read_string(serialized)
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
@ -61,18 +68,19 @@ class CuraProfileReader(ProfileReader):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
version = int(parser["general"]["version"])
|
version = int(parser["general"]["version"])
|
||||||
|
setting_version = int(parser["metadata"].get("setting_version", "0"))
|
||||||
if InstanceContainer.Version != version:
|
if InstanceContainer.Version != version:
|
||||||
name = parser["general"]["name"]
|
name = parser["general"]["name"]
|
||||||
return self._upgradeProfileVersion(serialized, name, version)
|
return self._upgradeProfileVersion(serialized, name, version, setting_version)
|
||||||
else:
|
else:
|
||||||
return [(serialized, profile_id)]
|
return [(serialized, profile_id)]
|
||||||
|
|
||||||
## Load a profile from a serialized string.
|
## Load a profile from a serialized string.
|
||||||
#
|
#
|
||||||
# \param serialized \type{str} The profile data to read.
|
# \param serialized The profile data to read.
|
||||||
# \param profile_id \type{str} The name of the profile.
|
# \param profile_id The name of the profile.
|
||||||
# \return \type{InstanceContainer|None}
|
# \return The profile that was stored in the string.
|
||||||
def _loadProfile(self, serialized, profile_id):
|
def _loadProfile(self, serialized: str, profile_id: str) -> Optional[InstanceContainer]:
|
||||||
# Create an empty profile.
|
# Create an empty profile.
|
||||||
profile = InstanceContainer(profile_id)
|
profile = InstanceContainer(profile_id)
|
||||||
profile.setMetaDataEntry("type", "quality_changes")
|
profile.setMetaDataEntry("type", "quality_changes")
|
||||||
|
@ -88,21 +96,31 @@ class CuraProfileReader(ProfileReader):
|
||||||
|
|
||||||
## Upgrade a serialized profile to the current profile format.
|
## Upgrade a serialized profile to the current profile format.
|
||||||
#
|
#
|
||||||
# \param serialized \type{str} The profile data to convert.
|
# \param serialized The profile data to convert.
|
||||||
# \param profile_id \type{str} The name of the profile.
|
# \param profile_id The name of the profile.
|
||||||
# \param source_version \type{int} The profile version of 'serialized'.
|
# \param source_version The profile version of 'serialized'.
|
||||||
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
|
# \return List of serialized profile strings and matching profile names.
|
||||||
def _upgradeProfileVersion(self, serialized, profile_id, source_version):
|
def _upgradeProfileVersion(self, serialized: str, profile_id: str, main_version: int, setting_version: int) -> List[Tuple[str, str]]:
|
||||||
converter_plugins = PluginRegistry.getInstance().getAllMetaData(filter={"version_upgrade": {} }, active_only=True)
|
source_version = main_version * 1000000 + setting_version
|
||||||
|
|
||||||
source_format = ("profile", source_version)
|
from UM.VersionUpgradeManager import VersionUpgradeManager
|
||||||
profile_convert_funcs = [plugin["version_upgrade"][source_format][2] for plugin in converter_plugins
|
results = VersionUpgradeManager.getInstance().updateFilesData("quality_changes", source_version, [serialized], [profile_id])
|
||||||
if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version]
|
if results is None:
|
||||||
|
|
||||||
if not profile_convert_funcs:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
filenames, outputs = profile_convert_funcs[0](serialized, profile_id)
|
serialized = results.files_data[0]
|
||||||
if filenames is None and outputs is None:
|
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
if "general" not in parser:
|
||||||
|
Logger.log("w", "Missing required section 'general'.")
|
||||||
return []
|
return []
|
||||||
return list(zip(outputs, filenames))
|
|
||||||
|
new_source_version = results.version
|
||||||
|
if int(new_source_version / 1000000) != InstanceContainer.Version or new_source_version % 1000000 != CuraApplication.SettingVersion:
|
||||||
|
Logger.log("e", "Failed to upgrade profile [%s]", profile_id)
|
||||||
|
|
||||||
|
if int(parser["general"]["version"]) != InstanceContainer.Version:
|
||||||
|
Logger.log("e", "Failed to upgrade profile [%s]", profile_id)
|
||||||
|
return []
|
||||||
|
return [(serialized, profile_id)]
|
||||||
|
|
|
@ -104,7 +104,7 @@ class FirmwareUpdateCheckerJob(Job):
|
||||||
# because the new version of Cura will be release before the firmware and we don't want to
|
# because the new version of Cura will be release before the firmware and we don't want to
|
||||||
# notify the user when no new firmware version is available.
|
# notify the user when no new firmware version is available.
|
||||||
if (checked_version != "") and (checked_version != current_version):
|
if (checked_version != "") and (checked_version != current_version):
|
||||||
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
|
Logger.log("i", "Showing firmware update message for new version: {version}".format(current_version))
|
||||||
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
|
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
|
||||||
self._lookups.getRedirectUserUrl())
|
self._lookups.getRedirectUserUrl())
|
||||||
message.actionTriggered.connect(self._callback)
|
message.actionTriggered.connect(self._callback)
|
||||||
|
|
|
@ -371,7 +371,7 @@ class FlavorParser:
|
||||||
elif type == "SUPPORT-INTERFACE":
|
elif type == "SUPPORT-INTERFACE":
|
||||||
self._layer_type = LayerPolygon.SupportInterfaceType
|
self._layer_type = LayerPolygon.SupportInterfaceType
|
||||||
elif type == "PRIME-TOWER":
|
elif type == "PRIME-TOWER":
|
||||||
self._layer_type = LayerPolygon.SkirtType
|
self._layer_type = LayerPolygon.PrimeTowerType
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
|
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ Item
|
||||||
|
|
||||||
property int labelWidth: 210 * screenScaleFactor
|
property int labelWidth: 210 * screenScaleFactor
|
||||||
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
|
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
|
||||||
property var labelFont: UM.Theme.getFont("medium")
|
property var labelFont: UM.Theme.getFont("default")
|
||||||
|
|
||||||
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
||||||
property int columnSpacing: 3 * screenScaleFactor
|
property int columnSpacing: 3 * screenScaleFactor
|
||||||
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes
|
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
|
||||||
|
|
||||||
property string extruderStackId: ""
|
property string extruderStackId: ""
|
||||||
property int extruderPosition: 0
|
property int extruderPosition: 0
|
||||||
|
@ -107,6 +107,7 @@ Item
|
||||||
labelWidth: base.labelWidth
|
labelWidth: base.labelWidth
|
||||||
controlWidth: base.controlWidth
|
controlWidth: base.controlWidth
|
||||||
unitText: catalog.i18nc("@label", "mm")
|
unitText: catalog.i18nc("@label", "mm")
|
||||||
|
allowNegativeValue: true
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +122,7 @@ Item
|
||||||
labelWidth: base.labelWidth
|
labelWidth: base.labelWidth
|
||||||
controlWidth: base.controlWidth
|
controlWidth: base.controlWidth
|
||||||
unitText: catalog.i18nc("@label", "mm")
|
unitText: catalog.i18nc("@label", "mm")
|
||||||
|
allowNegativeValue: true
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,13 @@ Item
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
||||||
property int labelWidth: 120 * screenScaleFactor
|
|
||||||
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
|
|
||||||
property var labelFont: UM.Theme.getFont("default")
|
|
||||||
|
|
||||||
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
||||||
property int columnSpacing: 3 * screenScaleFactor
|
property int columnSpacing: 3 * screenScaleFactor
|
||||||
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes
|
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
|
||||||
|
|
||||||
|
property int labelWidth: (columnWidth * 2 / 3 - UM.Theme.getSize("default_margin").width * 2) | 0
|
||||||
|
property int controlWidth: (columnWidth / 3) | 0
|
||||||
|
property var labelFont: UM.Theme.getFont("default")
|
||||||
|
|
||||||
property string machineStackId: Cura.MachineManager.activeMachineId
|
property string machineStackId: Cura.MachineManager.activeMachineId
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ Item
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Cura.NumericTextFieldWithUnit // "X (Width)"
|
Cura.NumericTextFieldWithUnit // "X (Width)"
|
||||||
|
@ -175,6 +177,8 @@ Item
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
Cura.PrintHeadMinMaxTextField // "X min"
|
Cura.PrintHeadMinMaxTextField // "X min"
|
||||||
|
@ -191,6 +195,7 @@ Item
|
||||||
|
|
||||||
axisName: "x"
|
axisName: "x"
|
||||||
axisMinOrMax: "min"
|
axisMinOrMax: "min"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
@ -209,6 +214,7 @@ Item
|
||||||
|
|
||||||
axisName: "y"
|
axisName: "y"
|
||||||
axisMinOrMax: "min"
|
axisMinOrMax: "min"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
@ -227,6 +233,7 @@ Item
|
||||||
|
|
||||||
axisName: "x"
|
axisName: "x"
|
||||||
axisMinOrMax: "max"
|
axisMinOrMax: "max"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
@ -247,6 +254,7 @@ Item
|
||||||
|
|
||||||
axisName: "y"
|
axisName: "y"
|
||||||
axisMinOrMax: "max"
|
axisMinOrMax: "max"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ Rectangle
|
||||||
horizontalCenter: parent.horizontalCenter
|
horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
visible: isNetworkConfigured && !isConnected
|
visible: isNetworkConfigured && !isConnected
|
||||||
text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.")
|
text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.\n- Check if you are signed in to discover cloud-connected printers.")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
color: UM.Theme.getColor("monitor_text_primary")
|
color: UM.Theme.getColor("monitor_text_primary")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
|
@ -160,7 +160,7 @@ Item {
|
||||||
model: UM.SettingDefinitionsModel
|
model: UM.SettingDefinitionsModel
|
||||||
{
|
{
|
||||||
id: addedSettingsModel;
|
id: addedSettingsModel;
|
||||||
containerId: Cura.MachineManager.activeDefinitionId
|
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
|
||||||
expanded: [ "*" ]
|
expanded: [ "*" ]
|
||||||
filter:
|
filter:
|
||||||
{
|
{
|
||||||
|
@ -467,7 +467,7 @@ Item {
|
||||||
model: UM.SettingDefinitionsModel
|
model: UM.SettingDefinitionsModel
|
||||||
{
|
{
|
||||||
id: definitionsModel;
|
id: definitionsModel;
|
||||||
containerId: Cura.MachineManager.activeDefinitionId
|
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
|
||||||
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
|
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
|
||||||
expanded: [ "*" ]
|
expanded: [ "*" ]
|
||||||
exclude:
|
exclude:
|
||||||
|
|
|
@ -219,6 +219,7 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
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)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._script_list.clear()
|
self._script_list.clear()
|
||||||
|
|
|
@ -100,8 +100,8 @@ class ChangeAtZ(Script):
|
||||||
},
|
},
|
||||||
"d_twLayers":
|
"d_twLayers":
|
||||||
{
|
{
|
||||||
"label": "No. Layers",
|
"label": "Layer Spread",
|
||||||
"description": "No. of layers used to change",
|
"description": "The change will be gradual over this many layers. Enter 1 to make the change immediate.",
|
||||||
"unit": "",
|
"unit": "",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"default_value": 1,
|
"default_value": 1,
|
||||||
|
@ -330,7 +330,7 @@ class ChangeAtZ(Script):
|
||||||
"extruderOne": self.getSettingValueByKey("i2_extruderOne"),
|
"extruderOne": self.getSettingValueByKey("i2_extruderOne"),
|
||||||
"extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
|
"extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
|
||||||
"fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
|
"fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
|
||||||
old = {"speed": -1, "flowrate": -1, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
|
old = {"speed": -1, "flowrate": 100, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
|
||||||
"extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
|
"extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
|
||||||
twLayers = self.getSettingValueByKey("d_twLayers")
|
twLayers = self.getSettingValueByKey("d_twLayers")
|
||||||
if self.getSettingValueByKey("c_behavior") == "single_layer":
|
if self.getSettingValueByKey("c_behavior") == "single_layer":
|
||||||
|
@ -410,6 +410,8 @@ class ChangeAtZ(Script):
|
||||||
tmp_extruder = self.getValue(line, "T", None)
|
tmp_extruder = self.getValue(line, "T", None)
|
||||||
if tmp_extruder == None: #check if extruder is specified
|
if tmp_extruder == None: #check if extruder is specified
|
||||||
old["flowrate"] = self.getValue(line, "S", old["flowrate"])
|
old["flowrate"] = self.getValue(line, "S", old["flowrate"])
|
||||||
|
if old["flowrate"] == -1:
|
||||||
|
old["flowrate"] = 100.0
|
||||||
elif tmp_extruder == 0: #first extruder
|
elif tmp_extruder == 0: #first extruder
|
||||||
old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
|
old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
|
||||||
elif tmp_extruder == 1: #second extruder
|
elif tmp_extruder == 1: #second extruder
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Optional, Tuple
|
from typing import List
|
||||||
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from ..Script import Script
|
from ..Script import Script
|
||||||
|
|
||||||
class FilamentChange(Script):
|
class FilamentChange(Script):
|
||||||
|
@ -65,9 +63,10 @@ class FilamentChange(Script):
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
def execute(self, data: list):
|
## Inserts the filament change g-code at specific layer numbers.
|
||||||
|
# \param data A list of layers of g-code.
|
||||||
"""data is a list. Each index contains a layer"""
|
# \return A similar list, with filament change commands inserted.
|
||||||
|
def execute(self, data: List[str]):
|
||||||
layer_nums = self.getSettingValueByKey("layer_number")
|
layer_nums = self.getSettingValueByKey("layer_number")
|
||||||
initial_retract = self.getSettingValueByKey("initial_retract")
|
initial_retract = self.getSettingValueByKey("initial_retract")
|
||||||
later_retract = self.getSettingValueByKey("later_retract")
|
later_retract = self.getSettingValueByKey("later_retract")
|
||||||
|
@ -88,32 +87,16 @@ class FilamentChange(Script):
|
||||||
if y_pos is not None:
|
if y_pos is not None:
|
||||||
color_change = color_change + (" Y%.2f" % y_pos)
|
color_change = color_change + (" Y%.2f" % y_pos)
|
||||||
|
|
||||||
color_change = color_change + " ; Generated by FilamentChange plugin"
|
color_change = color_change + " ; Generated by FilamentChange plugin\n"
|
||||||
|
|
||||||
layer_targets = layer_nums.split(",")
|
layer_targets = layer_nums.split(",")
|
||||||
if len(layer_targets) > 0:
|
if len(layer_targets) > 0:
|
||||||
for layer_num in layer_targets:
|
for layer_num in layer_targets:
|
||||||
layer_num = int(layer_num.strip())
|
try:
|
||||||
if layer_num <= len(data):
|
layer_num = int(layer_num.strip()) + 1 #Needs +1 because the 1st layer is reserved for start g-code.
|
||||||
index, layer_data = self._searchLayerData(data, layer_num - 1)
|
except ValueError: #Layer number is not an integer.
|
||||||
if layer_data is None:
|
|
||||||
Logger.log("e", "Could not find the layer {layer_num}".format(layer_num = layer_num))
|
|
||||||
continue
|
continue
|
||||||
lines = layer_data.split("\n")
|
if 0 < layer_num < len(data):
|
||||||
lines.insert(2, color_change)
|
data[layer_num] = color_change + data[layer_num]
|
||||||
final_line = "\n".join(lines)
|
|
||||||
data[index] = final_line
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
## This method returns the data corresponding with the indicated layer number, looking in the gcode for
|
|
||||||
# the occurrence of this layer number.
|
|
||||||
def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]:
|
|
||||||
for index, layer_data in enumerate(data):
|
|
||||||
first_line = layer_data.split("\n")[0]
|
|
||||||
# The first line should contain the layer number at the beginning.
|
|
||||||
if first_line[:len(self._layer_keyword)] == self._layer_keyword:
|
|
||||||
# If found the layer that we are looking for, then return the data
|
|
||||||
if first_line[len(self._layer_keyword):] == str(layer_num):
|
|
||||||
return index, layer_data
|
|
||||||
return 0, None
|
|
|
@ -145,6 +145,7 @@ class Stretcher():
|
||||||
current.readStep(line)
|
current.readStep(line)
|
||||||
onestep = GCodeStep(-1, in_relative_movement)
|
onestep = GCodeStep(-1, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
|
onestep.comment = line
|
||||||
else:
|
else:
|
||||||
onestep = GCodeStep(-1, in_relative_movement)
|
onestep = GCodeStep(-1, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
|
|
|
@ -572,14 +572,14 @@ class SimulationView(CuraView):
|
||||||
self._current_layer_jumps = job.getResult().get("jumps")
|
self._current_layer_jumps = job.getResult().get("jumps")
|
||||||
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
|
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
|
||||||
|
|
||||||
self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
|
self._top_layers_job = None
|
||||||
|
|
||||||
def _updateWithPreferences(self) -> None:
|
def _updateWithPreferences(self) -> None:
|
||||||
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
||||||
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.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type"))));
|
self.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type"))))
|
||||||
|
|
||||||
for extruder_nr, extruder_opacity in enumerate(Application.getInstance().getPreferences().getValue("layerview/extruder_opacities").split("|")):
|
for extruder_nr, extruder_opacity in enumerate(Application.getInstance().getPreferences().getValue("layerview/extruder_opacities").split("|")):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
[shaders]
|
[shaders]
|
||||||
vertex =
|
vertex =
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp float u_shade_factor;
|
uniform lowp float u_shade_factor;
|
||||||
uniform highp int u_layer_view_type;
|
uniform highp int u_layer_view_type;
|
||||||
|
@ -16,7 +19,7 @@ vertex =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
// shade the color depending on the extruder index
|
// shade the color depending on the extruder index
|
||||||
v_color = a_color;
|
v_color = a_color;
|
||||||
// 8 and 9 are travel moves
|
// 8 and 9 are travel moves
|
||||||
|
@ -76,7 +79,10 @@ fragment =
|
||||||
|
|
||||||
vertex41core =
|
vertex41core =
|
||||||
#version 410
|
#version 410
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp float u_shade_factor;
|
uniform lowp float u_shade_factor;
|
||||||
uniform highp int u_layer_view_type;
|
uniform highp int u_layer_view_type;
|
||||||
|
@ -92,7 +98,7 @@ vertex41core =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
v_color = a_color;
|
v_color = a_color;
|
||||||
if ((a_line_type != 8) && (a_line_type != 9)) {
|
if ((a_line_type != 8) && (a_line_type != 9)) {
|
||||||
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||||
|
@ -154,7 +160,9 @@ u_show_skin = 1
|
||||||
u_show_infill = 1
|
u_show_infill = 1
|
||||||
|
|
||||||
[bindings]
|
[bindings]
|
||||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
u_modelMatrix = model_matrix
|
||||||
|
u_viewMatrix = view_matrix
|
||||||
|
u_projectionMatrix = projection_matrix
|
||||||
|
|
||||||
[attributes]
|
[attributes]
|
||||||
a_vertex = vertex
|
a_vertex = vertex
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[shaders]
|
[shaders]
|
||||||
vertex41core =
|
vertex41core =
|
||||||
#version 410
|
#version 410
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
|
||||||
|
|
||||||
uniform highp mat4 u_modelMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
uniform highp mat4 u_viewProjectionMatrix;
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp float u_max_feedrate;
|
uniform lowp float u_max_feedrate;
|
||||||
uniform lowp float u_min_feedrate;
|
uniform lowp float u_min_feedrate;
|
||||||
|
@ -104,7 +104,10 @@ vertex41core =
|
||||||
geometry41core =
|
geometry41core =
|
||||||
#version 410
|
#version 410
|
||||||
|
|
||||||
uniform highp mat4 u_viewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform int u_show_travel_moves;
|
uniform int u_show_travel_moves;
|
||||||
uniform int u_show_helpers;
|
uniform int u_show_helpers;
|
||||||
uniform int u_show_skin;
|
uniform int u_show_skin;
|
||||||
|
@ -136,6 +139,8 @@ geometry41core =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||||
|
|
||||||
vec4 g_vertex_delta;
|
vec4 g_vertex_delta;
|
||||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||||
|
@ -183,65 +188,83 @@ geometry41core =
|
||||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
||||||
|
|
||||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||||
|
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||||
|
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||||
|
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
|
||||||
// Travels: flat plane with pointy ends
|
// Travels: flat plane with pointy ends
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
|
||||||
//And reverse so that the line is also visible from the back side.
|
//And reverse so that the line is also visible from the back side.
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
} else {
|
} else {
|
||||||
|
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
|
||||||
|
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
|
||||||
|
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
|
||||||
|
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
|
||||||
|
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
|
||||||
|
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
|
||||||
|
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
|
||||||
|
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
|
||||||
|
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
|
||||||
|
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
|
||||||
|
|
||||||
// All normal lines are rendered as 3d tubes.
|
// All normal lines are rendered as 3d tubes.
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
// left side
|
// left side
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
// right side
|
// right side
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
}
|
}
|
||||||
|
@ -301,9 +324,9 @@ u_min_thickness = 0
|
||||||
u_max_thickness = 1
|
u_max_thickness = 1
|
||||||
|
|
||||||
[bindings]
|
[bindings]
|
||||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
|
||||||
u_modelMatrix = model_matrix
|
u_modelMatrix = model_matrix
|
||||||
u_viewProjectionMatrix = view_projection_matrix
|
u_viewMatrix = view_matrix
|
||||||
|
u_projectionMatrix = projection_matrix
|
||||||
u_normalMatrix = normal_matrix
|
u_normalMatrix = normal_matrix
|
||||||
u_lightPosition = light_0_position
|
u_lightPosition = light_0_position
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[shaders]
|
[shaders]
|
||||||
vertex41core =
|
vertex41core =
|
||||||
#version 410
|
#version 410
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
|
||||||
|
|
||||||
uniform highp mat4 u_modelMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
uniform highp mat4 u_viewProjectionMatrix;
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
|
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
|
||||||
|
|
||||||
|
@ -58,7 +58,10 @@ vertex41core =
|
||||||
geometry41core =
|
geometry41core =
|
||||||
#version 410
|
#version 410
|
||||||
|
|
||||||
uniform highp mat4 u_viewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform int u_show_travel_moves;
|
uniform int u_show_travel_moves;
|
||||||
uniform int u_show_helpers;
|
uniform int u_show_helpers;
|
||||||
uniform int u_show_skin;
|
uniform int u_show_skin;
|
||||||
|
@ -90,6 +93,8 @@ geometry41core =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||||
|
|
||||||
vec4 g_vertex_delta;
|
vec4 g_vertex_delta;
|
||||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||||
|
@ -137,65 +142,83 @@ geometry41core =
|
||||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
||||||
|
|
||||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||||
|
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||||
|
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||||
|
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||||
|
|
||||||
// Travels: flat plane with pointy ends
|
// Travels: flat plane with pointy ends
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
|
||||||
//And reverse so that the line is also visible from the back side.
|
//And reverse so that the line is also visible from the back side.
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
} else {
|
} else {
|
||||||
|
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
|
||||||
|
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
|
||||||
|
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
|
||||||
|
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
|
||||||
|
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
|
||||||
|
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
|
||||||
|
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
|
||||||
|
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
|
||||||
|
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
|
||||||
|
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
|
||||||
|
|
||||||
// All normal lines are rendered as 3d tubes.
|
// All normal lines are rendered as 3d tubes.
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
// left side
|
// left side
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
// right side
|
// right side
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
}
|
}
|
||||||
|
@ -246,9 +269,9 @@ u_show_skin = 1
|
||||||
u_show_infill = 1
|
u_show_infill = 1
|
||||||
|
|
||||||
[bindings]
|
[bindings]
|
||||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
|
||||||
u_modelMatrix = model_matrix
|
u_modelMatrix = model_matrix
|
||||||
u_viewProjectionMatrix = view_projection_matrix
|
u_viewMatrix = view_matrix
|
||||||
|
u_projectionMatrix = projection_matrix
|
||||||
u_normalMatrix = normal_matrix
|
u_normalMatrix = normal_matrix
|
||||||
u_lightPosition = light_0_position
|
u_lightPosition = light_0_position
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
[shaders]
|
[shaders]
|
||||||
vertex =
|
vertex =
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp float u_shade_factor;
|
uniform lowp float u_shade_factor;
|
||||||
uniform highp int u_layer_view_type;
|
uniform highp int u_layer_view_type;
|
||||||
|
@ -16,7 +19,7 @@ vertex =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
// shade the color depending on the extruder index
|
// shade the color depending on the extruder index
|
||||||
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer;
|
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer;
|
||||||
// 8 and 9 are travel moves
|
// 8 and 9 are travel moves
|
||||||
|
@ -80,7 +83,10 @@ fragment =
|
||||||
|
|
||||||
vertex41core =
|
vertex41core =
|
||||||
#version 410
|
#version 410
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
uniform lowp float u_active_extruder;
|
uniform lowp float u_active_extruder;
|
||||||
uniform lowp float u_shade_factor;
|
uniform lowp float u_shade_factor;
|
||||||
uniform highp int u_layer_view_type;
|
uniform highp int u_layer_view_type;
|
||||||
|
@ -96,7 +102,7 @@ vertex41core =
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
|
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
|
||||||
// if ((a_line_type != 8) && (a_line_type != 9)) {
|
// if ((a_line_type != 8) && (a_line_type != 9)) {
|
||||||
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||||
|
@ -159,7 +165,9 @@ u_show_skin = 1
|
||||||
u_show_infill = 1
|
u_show_infill = 1
|
||||||
|
|
||||||
[bindings]
|
[bindings]
|
||||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
u_modelMatrix = model_matrix
|
||||||
|
u_viewMatrix = view_matrix
|
||||||
|
u_projectionMatrix = projection_matrix
|
||||||
|
|
||||||
[attributes]
|
[attributes]
|
||||||
a_vertex = vertex
|
a_vertex = vertex
|
||||||
|
|
|
@ -126,6 +126,8 @@ class SliceInfo(QObject, Extension):
|
||||||
else:
|
else:
|
||||||
data["active_mode"] = "custom"
|
data["active_mode"] = "custom"
|
||||||
|
|
||||||
|
data["camera_view"] = application.getPreferences().getValue("general/camera_perspective_mode")
|
||||||
|
|
||||||
definition_changes = global_stack.definitionChanges
|
definition_changes = global_stack.definitionChanges
|
||||||
machine_settings_changed_by_user = False
|
machine_settings_changed_by_user = False
|
||||||
if definition_changes.getId() != "empty":
|
if definition_changes.getId() != "empty":
|
||||||
|
|
|
@ -98,8 +98,10 @@ class SupportEraser(Tool):
|
||||||
|
|
||||||
node.setName("Eraser")
|
node.setName("Eraser")
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
|
node.setCalculateBoundingBox(True)
|
||||||
mesh = self._createCube(10)
|
mesh = self._createCube(10)
|
||||||
node.setMeshData(mesh.build())
|
node.setMeshData(mesh.build())
|
||||||
|
node.calculateBoundingBoxMesh()
|
||||||
|
|
||||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||||
|
|
|
@ -109,6 +109,8 @@ Item
|
||||||
top: description.bottom
|
top: description.bottom
|
||||||
left: properties.right
|
left: properties.right
|
||||||
leftMargin: UM.Theme.getSize("default_margin").width
|
leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
topMargin: UM.Theme.getSize("default_margin").height
|
topMargin: UM.Theme.getSize("default_margin").height
|
||||||
}
|
}
|
||||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||||
|
@ -123,6 +125,8 @@ Item
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
width: parent.width
|
||||||
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("text")
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
|
|
|
@ -89,6 +89,7 @@ Item
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Your rating") + ":"
|
text: catalog.i18nc("@label", "Your rating") + ":"
|
||||||
|
visible: details.type == "plugin"
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
|
|
@ -48,7 +48,6 @@ Item
|
||||||
{
|
{
|
||||||
text: model.name
|
text: model.name
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font: UM.Theme.getFont("large_bold")
|
font: UM.Theme.getFont("large_bold")
|
||||||
color: pluginInfo.color
|
color: pluginInfo.color
|
||||||
|
|
|
@ -278,7 +278,7 @@ class Toolbox(QObject, Extension):
|
||||||
for plugin_id in old_plugin_ids:
|
for plugin_id in old_plugin_ids:
|
||||||
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
||||||
if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
|
if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
|
||||||
Logger.log("i", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
|
Logger.log("d", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
|
||||||
|
|
||||||
old_metadata = self._plugin_registry.getMetaData(plugin_id)
|
old_metadata = self._plugin_registry.getMetaData(plugin_id)
|
||||||
new_metadata = self._convertPluginMetadata(old_metadata)
|
new_metadata = self._convertPluginMetadata(old_metadata)
|
||||||
|
@ -526,7 +526,7 @@ class Toolbox(QObject, Extension):
|
||||||
# Make API Calls
|
# Make API Calls
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def _makeRequestByType(self, request_type: str) -> None:
|
def _makeRequestByType(self, request_type: str) -> None:
|
||||||
Logger.log("i", "Requesting %s metadata from server.", request_type)
|
Logger.log("d", "Requesting %s metadata from server.", request_type)
|
||||||
request = QNetworkRequest(self._request_urls[request_type])
|
request = QNetworkRequest(self._request_urls[request_type])
|
||||||
for header_name, header_value in self._request_headers:
|
for header_name, header_value in self._request_headers:
|
||||||
request.setRawHeader(header_name, header_value)
|
request.setRawHeader(header_name, header_value)
|
||||||
|
|
|
@ -78,7 +78,7 @@ Cura.MachineAction
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
text: catalog.i18nc("@label", "To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n\nSelect your printer from the list below:")
|
text: catalog.i18nc("@label", "To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.") + "\n\n" + catalog.i18nc("@label", "Select your printer from the list below:")
|
||||||
}
|
}
|
||||||
|
|
||||||
Row
|
Row
|
||||||
|
|
|
@ -30,6 +30,26 @@ UM.Dialog
|
||||||
OutputDevice.forceSendJob(printer.activePrintJob.key)
|
OutputDevice.forceSendJob(printer.activePrintJob.key)
|
||||||
overrideConfirmationDialog.close()
|
overrideConfirmationDialog.close()
|
||||||
}
|
}
|
||||||
|
visible:
|
||||||
|
{
|
||||||
|
// Don't show the button if we're missing a printer or print job
|
||||||
|
if (!printer || !printer.activePrintJob)
|
||||||
|
{
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each required change...
|
||||||
|
for (var i = 0; i < printer.activePrintJob.configurationChanges.length; i++)
|
||||||
|
{
|
||||||
|
var change = printer.activePrintJob.configurationChanges[i]
|
||||||
|
// If that type of change is in the list of blocking changes, hide the button
|
||||||
|
if (!change.canOverride)
|
||||||
|
{
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
|
|
|
@ -81,7 +81,7 @@ Item
|
||||||
enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
|
enabled: visible && !(printJob.state == "pausing" || printJob.state == "resuming");
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (printJob.state == "paused") {
|
if (printJob.state == "paused") {
|
||||||
printJob.setState("print");
|
printJob.setState("resume");
|
||||||
popUp.close();
|
popUp.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,6 @@ Item
|
||||||
// The print job which all other data is derived from
|
// The print job which all other data is derived from
|
||||||
property var printJob: null
|
property var printJob: null
|
||||||
|
|
||||||
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
|
|
||||||
// they might not need to though.
|
|
||||||
property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
|
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
||||||
|
@ -51,7 +47,7 @@ Item
|
||||||
{
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
width: UM.Theme.getSize("monitor_column").width
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
color: UM.Theme.getColor("monitor_skeleton_loading")
|
color: UM.Theme.getColor("monitor_skeleton_loading")
|
||||||
|
@ -79,7 +75,7 @@ Item
|
||||||
{
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
width: UM.Theme.getSize("monitor_column").width
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
color: UM.Theme.getColor("monitor_skeleton_loading")
|
color: UM.Theme.getColor("monitor_skeleton_loading")
|
||||||
|
@ -217,7 +213,7 @@ Item
|
||||||
}
|
}
|
||||||
width: 32 * screenScaleFactor // TODO: Theme!
|
width: 32 * screenScaleFactor // TODO: Theme!
|
||||||
height: 32 * screenScaleFactor // TODO: Theme!
|
height: 32 * screenScaleFactor // TODO: Theme!
|
||||||
enabled: !cloudConnection
|
enabled: OutputDevice.supportsPrintJobActions
|
||||||
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
||||||
visible:
|
visible:
|
||||||
{
|
{
|
||||||
|
@ -250,7 +246,7 @@ Item
|
||||||
MonitorInfoBlurb
|
MonitorInfoBlurb
|
||||||
{
|
{
|
||||||
id: contextMenuDisabledInfo
|
id: contextMenuDisabledInfo
|
||||||
text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
|
text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.")
|
||||||
target: contextMenuButton
|
target: contextMenuButton
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,9 +28,12 @@ Item
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
|
left: parent.left
|
||||||
}
|
}
|
||||||
value: printJob ? printJob.progress : 0
|
value: printJob ? printJob.progress : 0
|
||||||
|
width: UM.Theme.getSize("monitor_column").width
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: percentLabel
|
id: percentLabel
|
||||||
|
@ -38,6 +41,7 @@ Item
|
||||||
{
|
{
|
||||||
left: progressBar.right
|
left: progressBar.right
|
||||||
leftMargin: 18 * screenScaleFactor // TODO: Theme!
|
leftMargin: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
text: printJob ? Math.round(printJob.progress * 100) + "%" : "0%"
|
text: printJob ? Math.round(printJob.progress * 100) + "%" : "0%"
|
||||||
color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
|
color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_text_primary") : UM.Theme.getColor("monitor_text_disabled")
|
||||||
|
@ -56,6 +60,7 @@ Item
|
||||||
{
|
{
|
||||||
left: percentLabel.right
|
left: percentLabel.right
|
||||||
leftMargin: 18 * screenScaleFactor // TODO: Theme!
|
leftMargin: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
color: UM.Theme.getColor("monitor_text_primary")
|
color: UM.Theme.getColor("monitor_text_primary")
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||||
|
|
|
@ -172,8 +172,7 @@ Item
|
||||||
}
|
}
|
||||||
width: 36 * screenScaleFactor // TODO: Theme!
|
width: 36 * screenScaleFactor // TODO: Theme!
|
||||||
height: 36 * screenScaleFactor // TODO: Theme!
|
height: 36 * screenScaleFactor // TODO: Theme!
|
||||||
enabled: !cloudConnection
|
enabled: OutputDevice.supportsPrintJobActions
|
||||||
|
|
||||||
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
onClicked: enabled ? contextMenu.switchPopupState() : {}
|
||||||
visible:
|
visible:
|
||||||
{
|
{
|
||||||
|
@ -206,7 +205,7 @@ Item
|
||||||
MonitorInfoBlurb
|
MonitorInfoBlurb
|
||||||
{
|
{
|
||||||
id: contextMenuDisabledInfo
|
id: contextMenuDisabledInfo
|
||||||
text: catalog.i18nc("@info", "These options are not available because you are monitoring a cloud printer.")
|
text: catalog.i18nc("@info", "Please update your printer's firmware to manage the queue remotely.")
|
||||||
target: contextMenuButton
|
target: contextMenuButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +243,6 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Divider
|
// Divider
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,7 +42,6 @@ Item
|
||||||
}
|
}
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
width: childrenRect.width
|
width: childrenRect.width
|
||||||
visible: !cloudConnection
|
|
||||||
|
|
||||||
UM.RecolorImage
|
UM.RecolorImage
|
||||||
{
|
{
|
||||||
|
@ -65,7 +64,7 @@ Item
|
||||||
color: UM.Theme.getColor("monitor_text_link")
|
color: UM.Theme.getColor("monitor_text_link")
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||||
linkColor: UM.Theme.getColor("monitor_text_link")
|
linkColor: UM.Theme.getColor("monitor_text_link")
|
||||||
text: catalog.i18nc("@label link to connect manager", "Go to Cura Connect")
|
text: catalog.i18nc("@label link to connect manager", "Manage in browser")
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,9 +72,7 @@ Item
|
||||||
MouseArea
|
MouseArea
|
||||||
{
|
{
|
||||||
anchors.fill: manageQueueLabel
|
anchors.fill: manageQueueLabel
|
||||||
enabled: !cloudConnection
|
onClicked: OutputDevice.openPrintJobControlPanel()
|
||||||
hoverEnabled: !cloudConnection
|
|
||||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
|
|
||||||
onEntered:
|
onEntered:
|
||||||
{
|
{
|
||||||
manageQueueText.font.underline = true
|
manageQueueText.font.underline = true
|
||||||
|
@ -98,6 +95,22 @@ Item
|
||||||
}
|
}
|
||||||
spacing: 18 * screenScaleFactor // TODO: Theme!
|
spacing: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@label", "There are no print jobs in the queue. Slice and send a job to add one.")
|
||||||
|
color: UM.Theme.getColor("monitor_text_primary")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 600 * screenScaleFactor // TODO: Theme! (Should match column size)
|
||||||
|
|
||||||
|
// FIXED-LINE-HEIGHT:
|
||||||
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
visible: printJobList.count === 0
|
||||||
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Print jobs")
|
text: catalog.i18nc("@label", "Print jobs")
|
||||||
|
@ -111,6 +124,7 @@ Item
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
visible: printJobList.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -120,12 +134,13 @@ Item
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
width: UM.Theme.getSize("monitor_column").width
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
visible: printJobList.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -135,12 +150,13 @@ Item
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 216 * screenScaleFactor // TODO: Theme! (Should match column size)
|
width: UM.Theme.getSize("monitor_column").width
|
||||||
|
|
||||||
// FIXED-LINE-HEIGHT:
|
// FIXED-LINE-HEIGHT:
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
height: 18 * screenScaleFactor // TODO: Theme!
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
|
visible: printJobList.count > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,89 +200,4 @@ Item
|
||||||
spacing: 6 // TODO: Theme!
|
spacing: 6 // TODO: Theme!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle
|
|
||||||
{
|
|
||||||
anchors
|
|
||||||
{
|
|
||||||
horizontalCenter: parent.horizontalCenter
|
|
||||||
top: printJobQueueHeadings.bottom
|
|
||||||
topMargin: 12 * screenScaleFactor // TODO: Theme!
|
|
||||||
}
|
|
||||||
height: 48 * screenScaleFactor // TODO: Theme!
|
|
||||||
width: parent.width
|
|
||||||
color: UM.Theme.getColor("monitor_card_background")
|
|
||||||
border.color: UM.Theme.getColor("monitor_card_border")
|
|
||||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
|
||||||
|
|
||||||
visible: printJobList.model.length == 0
|
|
||||||
|
|
||||||
Row
|
|
||||||
{
|
|
||||||
anchors
|
|
||||||
{
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: 18 * screenScaleFactor // TODO: Theme!
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
spacing: 18 * screenScaleFactor // TODO: Theme!
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
|
||||||
|
|
||||||
Label
|
|
||||||
{
|
|
||||||
text: i18n.i18nc("@info", "All jobs are printed.")
|
|
||||||
color: UM.Theme.getColor("monitor_text_primary")
|
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
|
||||||
|
|
||||||
Item
|
|
||||||
{
|
|
||||||
id: viewPrintHistoryLabel
|
|
||||||
|
|
||||||
height: 18 * screenScaleFactor // TODO: Theme!
|
|
||||||
width: childrenRect.width
|
|
||||||
visible: !cloudConnection
|
|
||||||
|
|
||||||
UM.RecolorImage
|
|
||||||
{
|
|
||||||
id: printHistoryIcon
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: UM.Theme.getColor("monitor_text_link")
|
|
||||||
source: UM.Theme.getIcon("external_link")
|
|
||||||
width: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
|
||||||
height: 16 * screenScaleFactor // TODO: Theme! (Y U NO USE 18 LIKE ALL OTHER ICONS?!)
|
|
||||||
}
|
|
||||||
Label
|
|
||||||
{
|
|
||||||
id: viewPrintHistoryText
|
|
||||||
anchors
|
|
||||||
{
|
|
||||||
left: printHistoryIcon.right
|
|
||||||
leftMargin: 6 * screenScaleFactor // TODO: Theme!
|
|
||||||
verticalCenter: printHistoryIcon.verticalCenter
|
|
||||||
}
|
|
||||||
color: UM.Theme.getColor("monitor_text_link")
|
|
||||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
|
||||||
linkColor: UM.Theme.getColor("monitor_text_link")
|
|
||||||
text: catalog.i18nc("@label link to connect manager", "View print history")
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
|
||||||
MouseArea
|
|
||||||
{
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
|
|
||||||
onEntered:
|
|
||||||
{
|
|
||||||
viewPrintHistoryText.font.underline = true
|
|
||||||
}
|
|
||||||
onExited:
|
|
||||||
{
|
|
||||||
viewPrintHistoryText.font.underline = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,21 @@ class CloudApiClient:
|
||||||
reply = self._manager.post(self._createEmptyRequest(url), b"")
|
reply = self._manager.post(self._createEmptyRequest(url), b"")
|
||||||
self._addCallback(reply, on_finished, CloudPrintResponse)
|
self._addCallback(reply, on_finished, CloudPrintResponse)
|
||||||
|
|
||||||
|
## Send a print job action to the cluster for the given print job.
|
||||||
|
# \param cluster_id: The ID of the cluster.
|
||||||
|
# \param cluster_job_id: The ID of the print job within the cluster.
|
||||||
|
# \param action: The name of the action to execute.
|
||||||
|
def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, data: Optional[Dict[str, Any]] = None) -> None:
|
||||||
|
body = b""
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
body = json.dumps({"data": data}).encode()
|
||||||
|
except JSONDecodeError as err:
|
||||||
|
Logger.log("w", "Could not encode body: %s", err)
|
||||||
|
return
|
||||||
|
url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action)
|
||||||
|
self._manager.post(self._createEmptyRequest(url), body)
|
||||||
|
|
||||||
## We override _createEmptyRequest in order to add the user credentials.
|
## We override _createEmptyRequest in order to add the user credentials.
|
||||||
# \param url: The URL to request
|
# \param url: The URL to request
|
||||||
# \param content_type: The type of the body contents.
|
# \param content_type: The type of the body contents.
|
||||||
|
|
|
@ -1,5 +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 cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
@ -13,10 +14,13 @@ class CloudOutputController(PrinterOutputController):
|
||||||
|
|
||||||
# The cloud connection only supports fetching the printer and queue status and adding a job to the queue.
|
# The cloud connection only supports fetching the printer and queue status and adding a job to the queue.
|
||||||
# To let the UI know this we mark all features below as False.
|
# To let the UI know this we mark all features below as False.
|
||||||
self.can_pause = False
|
self.can_pause = True
|
||||||
self.can_abort = False
|
self.can_abort = True
|
||||||
self.can_pre_heat_bed = False
|
self.can_pre_heat_bed = False
|
||||||
self.can_pre_heat_hotends = False
|
self.can_pre_heat_hotends = False
|
||||||
self.can_send_raw_gcode = False
|
self.can_send_raw_gcode = False
|
||||||
self.can_control_manually = False
|
self.can_control_manually = False
|
||||||
self.can_update_firmware = False
|
self.can_update_firmware = False
|
||||||
|
|
||||||
|
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||||
|
self._output_device.setJobState(job.key, state)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from time import time
|
||||||
from typing import Dict, List, Optional, Set, cast
|
from typing import Dict, List, Optional, Set, cast
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
from PyQt5.QtGui import QDesktopServices
|
||||||
|
|
||||||
from UM import i18nCatalog
|
from UM import i18nCatalog
|
||||||
from UM.Backend.Backend import BackendState
|
from UM.Backend.Backend import BackendState
|
||||||
|
@ -15,6 +16,7 @@ from UM.Message import Message
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Qt.Duration import Duration, DurationFormat
|
from UM.Qt.Duration import Duration, DurationFormat
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Version import Version
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
|
||||||
|
@ -33,8 +35,7 @@ from .Models.CloudPrintResponse import CloudPrintResponse
|
||||||
from .Models.CloudPrintJobResponse import CloudPrintJobResponse
|
from .Models.CloudPrintJobResponse import CloudPrintJobResponse
|
||||||
from .Models.CloudClusterPrinterStatus import CloudClusterPrinterStatus
|
from .Models.CloudClusterPrinterStatus import CloudClusterPrinterStatus
|
||||||
from .Models.CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
|
from .Models.CloudClusterPrintJobStatus import CloudClusterPrintJobStatus
|
||||||
from .Utils import findChanges, formatDateCompleted, formatTimeCompleted
|
from .Utils import formatDateCompleted, formatTimeCompleted
|
||||||
|
|
||||||
|
|
||||||
I18N_CATALOG = i18nCatalog("cura")
|
I18N_CATALOG = i18nCatalog("cura")
|
||||||
|
|
||||||
|
@ -44,10 +45,12 @@ I18N_CATALOG = i18nCatalog("cura")
|
||||||
# As such, those methods have been implemented here.
|
# As such, those methods have been implemented here.
|
||||||
# Note that this device represents a single remote cluster, not a list of multiple clusters.
|
# Note that this device represents a single remote cluster, not a list of multiple clusters.
|
||||||
class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
# The interval with which the remote clusters are checked
|
# The interval with which the remote clusters are checked
|
||||||
CHECK_CLUSTER_INTERVAL = 10.0 # seconds
|
CHECK_CLUSTER_INTERVAL = 10.0 # seconds
|
||||||
|
|
||||||
|
# The minimum version of firmware that support print job actions over cloud.
|
||||||
|
PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.3.0")
|
||||||
|
|
||||||
# Signal triggered when the print jobs in the queue were changed.
|
# Signal triggered when the print jobs in the queue were changed.
|
||||||
printJobsChanged = pyqtSignal()
|
printJobsChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@ -58,14 +61,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Therefore we create a private signal used to trigger the printersChanged signal.
|
# Therefore we create a private signal used to trigger the printersChanged signal.
|
||||||
_clusterPrintersChanged = pyqtSignal()
|
_clusterPrintersChanged = pyqtSignal()
|
||||||
|
|
||||||
# Map of Cura Connect machine_variant field to Cura machine types.
|
|
||||||
# Needed for printer discovery stack creation.
|
|
||||||
_host_machine_variant_to_machine_type_map = {
|
|
||||||
"Ultimaker 3": "ultimaker3",
|
|
||||||
"Ultimaker 3 Extended": "ultimaker3_extended",
|
|
||||||
"Ultimaker S5": "ultimaker_s5"
|
|
||||||
}
|
|
||||||
|
|
||||||
## Creates a new cloud output device
|
## Creates a new cloud output device
|
||||||
# \param api_client: The client that will run the API calls
|
# \param api_client: The client that will run the API calls
|
||||||
# \param cluster: The device response received from the cloud API.
|
# \param cluster: The device response received from the cloud API.
|
||||||
|
@ -79,6 +74,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
|
b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
|
||||||
b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
|
b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
|
||||||
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
||||||
|
b"printer_type": cluster.printer_type.encode() if cluster.printer_type else b"",
|
||||||
b"cluster_size": b"1" # cloud devices are always clusters of at least one
|
b"cluster_size": b"1" # cloud devices are always clusters of at least one
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +100,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
# We keep track of which printer is visible in the monitor page.
|
# We keep track of which printer is visible in the monitor page.
|
||||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||||
self._host_machine_type = ""
|
|
||||||
|
|
||||||
# Properties to populate later on with received cloud data.
|
# Properties to populate later on with received cloud data.
|
||||||
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
||||||
|
@ -176,13 +171,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via Cloud"))
|
self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via Cloud"))
|
||||||
|
|
||||||
## Called when Cura requests an output device to receive a (G-code) file.
|
## Called when Cura requests an output device to receive a (G-code) file.
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
|
|
||||||
# Show an error message if we're already sending a job.
|
# Show an error message if we're already sending a job.
|
||||||
if self._progress.visible:
|
if self._progress.visible:
|
||||||
message = Message(
|
message = Message(
|
||||||
text = I18N_CATALOG.i18nc("@info:status", "Sending new jobs (temporarily) blocked, still sending the previous print job."),
|
text=I18N_CATALOG.i18nc("@info:status",
|
||||||
|
"Sending new jobs (temporarily) blocked, still sending the previous print job."),
|
||||||
title=I18N_CATALOG.i18nc("@info:title", "Cloud error"),
|
title=I18N_CATALOG.i18nc("@info:title", "Cloud error"),
|
||||||
lifetime=10
|
lifetime=10
|
||||||
)
|
)
|
||||||
|
@ -240,70 +236,74 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._updatePrintJobs(status.print_jobs)
|
self._updatePrintJobs(status.print_jobs)
|
||||||
|
|
||||||
## Updates the local list of printers with the list received from the cloud.
|
## Updates the local list of printers with the list received from the cloud.
|
||||||
# \param jobs: The printers received from the cloud.
|
# \param remote_printers: The printers received from the cloud.
|
||||||
def _updatePrinters(self, printers: List[CloudClusterPrinterStatus]) -> None:
|
def _updatePrinters(self, remote_printers: List[CloudClusterPrinterStatus]) -> None:
|
||||||
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
|
|
||||||
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
|
|
||||||
|
|
||||||
if len(printers) > 0:
|
# Keep track of the new printers to show.
|
||||||
# We need the machine type of the host (1st list entry) to allow discovery to work.
|
# We create a new list instead of changing the existing one to get the correct order.
|
||||||
self._host_machine_type = printers[0].machine_variant
|
new_printers = []
|
||||||
|
|
||||||
removed_printers, added_printers, updated_printers = findChanges(previous, received)
|
# Check which printers need to be created or updated.
|
||||||
|
for index, printer_data in enumerate(remote_printers):
|
||||||
|
printer = next(iter(printer for printer in self._printers if printer.key == printer_data.uuid), None)
|
||||||
|
if not printer:
|
||||||
|
new_printers.append(printer_data.createOutputModel(CloudOutputController(self)))
|
||||||
|
else:
|
||||||
|
printer_data.updateOutputModel(printer)
|
||||||
|
new_printers.append(printer)
|
||||||
|
|
||||||
|
# Check which printers need to be removed (de-referenced).
|
||||||
|
remote_printers_keys = [printer_data.uuid for printer_data in remote_printers]
|
||||||
|
removed_printers = [printer for printer in self._printers if printer.key not in remote_printers_keys]
|
||||||
for removed_printer in removed_printers:
|
for removed_printer in removed_printers:
|
||||||
if self._active_printer == removed_printer:
|
if self._active_printer and self._active_printer.key == removed_printer.key:
|
||||||
self.setActivePrinter(None)
|
self.setActivePrinter(None)
|
||||||
self._printers.remove(removed_printer)
|
|
||||||
|
|
||||||
for added_printer in added_printers:
|
self._printers = new_printers
|
||||||
self._printers.append(added_printer.createOutputModel(CloudOutputController(self)))
|
if self._printers and not self.activePrinter:
|
||||||
|
|
||||||
for model, printer in updated_printers:
|
|
||||||
printer.updateOutputModel(model)
|
|
||||||
|
|
||||||
# Always have an active printer
|
|
||||||
if self._printers and not self._active_printer:
|
|
||||||
self.setActivePrinter(self._printers[0])
|
self.setActivePrinter(self._printers[0])
|
||||||
|
|
||||||
if added_printers or removed_printers:
|
|
||||||
self.printersChanged.emit()
|
self.printersChanged.emit()
|
||||||
|
|
||||||
## Updates the local list of print jobs with the list received from the cloud.
|
## Updates the local list of print jobs with the list received from the cloud.
|
||||||
# \param jobs: The print jobs received from the cloud.
|
# \param remote_jobs: The print jobs received from the cloud.
|
||||||
def _updatePrintJobs(self, jobs: List[CloudClusterPrintJobStatus]) -> None:
|
def _updatePrintJobs(self, remote_jobs: List[CloudClusterPrintJobStatus]) -> None:
|
||||||
received = {j.uuid: j for j in jobs} # type: Dict[str, CloudClusterPrintJobStatus]
|
|
||||||
previous = {j.key: j for j in self._print_jobs} # type: Dict[str, UM3PrintJobOutputModel]
|
|
||||||
|
|
||||||
removed_jobs, added_jobs, updated_jobs = findChanges(previous, received)
|
# Keep track of the new print jobs to show.
|
||||||
|
# We create a new list instead of changing the existing one to get the correct order.
|
||||||
|
new_print_jobs = []
|
||||||
|
|
||||||
|
# Check which print jobs need to be created or updated.
|
||||||
|
for index, print_job_data in enumerate(remote_jobs):
|
||||||
|
print_job = next(
|
||||||
|
iter(print_job for print_job in self._print_jobs if print_job.key == print_job_data.uuid), None)
|
||||||
|
if not print_job:
|
||||||
|
new_print_jobs.append(self._createPrintJobModel(print_job_data))
|
||||||
|
else:
|
||||||
|
print_job_data.updateOutputModel(print_job)
|
||||||
|
if print_job_data.printer_uuid:
|
||||||
|
self._updateAssignedPrinter(print_job, print_job_data.printer_uuid)
|
||||||
|
new_print_jobs.append(print_job)
|
||||||
|
|
||||||
|
# Check which print job need to be removed (de-referenced).
|
||||||
|
remote_job_keys = [print_job_data.uuid for print_job_data in remote_jobs]
|
||||||
|
removed_jobs = [print_job for print_job in self._print_jobs if print_job.key not in remote_job_keys]
|
||||||
for removed_job in removed_jobs:
|
for removed_job in removed_jobs:
|
||||||
if removed_job.assignedPrinter:
|
if removed_job.assignedPrinter:
|
||||||
removed_job.assignedPrinter.updateActivePrintJob(None)
|
removed_job.assignedPrinter.updateActivePrintJob(None)
|
||||||
removed_job.stateChanged.disconnect(self._onPrintJobStateChanged)
|
removed_job.stateChanged.disconnect(self._onPrintJobStateChanged)
|
||||||
self._print_jobs.remove(removed_job)
|
|
||||||
|
|
||||||
for added_job in added_jobs:
|
self._print_jobs = new_print_jobs
|
||||||
self._addPrintJob(added_job)
|
|
||||||
|
|
||||||
for model, job in updated_jobs:
|
|
||||||
job.updateOutputModel(model)
|
|
||||||
if job.printer_uuid:
|
|
||||||
self._updateAssignedPrinter(model, job.printer_uuid)
|
|
||||||
|
|
||||||
# We only have to update when jobs are added or removed
|
|
||||||
# updated jobs push their changes via their output model
|
|
||||||
if added_jobs or removed_jobs:
|
|
||||||
self.printJobsChanged.emit()
|
self.printJobsChanged.emit()
|
||||||
|
|
||||||
## Registers a new print job received via the cloud API.
|
## Create a new print job model based on the remote status of the job.
|
||||||
# \param job: The print job received.
|
# \param remote_job: The remote print job data.
|
||||||
def _addPrintJob(self, job: CloudClusterPrintJobStatus) -> None:
|
def _createPrintJobModel(self, remote_job: CloudClusterPrintJobStatus) -> UM3PrintJobOutputModel:
|
||||||
model = job.createOutputModel(CloudOutputController(self))
|
model = remote_job.createOutputModel(CloudOutputController(self))
|
||||||
model.stateChanged.connect(self._onPrintJobStateChanged)
|
model.stateChanged.connect(self._onPrintJobStateChanged)
|
||||||
if job.printer_uuid:
|
if remote_job.printer_uuid:
|
||||||
self._updateAssignedPrinter(model, job.printer_uuid)
|
self._updateAssignedPrinter(model, remote_job.printer_uuid)
|
||||||
self._print_jobs.append(model)
|
return model
|
||||||
|
|
||||||
## Handles the event of a change in a print job state
|
## Handles the event of a change in a print job state
|
||||||
def _onPrintJobStateChanged(self) -> None:
|
def _onPrintJobStateChanged(self) -> None:
|
||||||
|
@ -314,7 +314,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._finished_jobs.add(job.key)
|
self._finished_jobs.add(job.key)
|
||||||
Message(
|
Message(
|
||||||
title=I18N_CATALOG.i18nc("@info:status", "Print finished"),
|
title=I18N_CATALOG.i18nc("@info:status", "Print finished"),
|
||||||
text = (I18N_CATALOG.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.").format(
|
text=(I18N_CATALOG.i18nc("@info:status",
|
||||||
|
"Printer '{printer_name}' has finished printing '{job_name}'.").format(
|
||||||
printer_name=job.assignedPrinter.name,
|
printer_name=job.assignedPrinter.name,
|
||||||
job_name=job.name
|
job_name=job.name
|
||||||
) if job.assignedPrinter else
|
) if job.assignedPrinter else
|
||||||
|
@ -330,7 +331,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
Logger.log("w", "Missing printer %s for job %s in %s", model.assignedPrinter, model.key,
|
Logger.log("w", "Missing printer %s for job %s in %s", model.assignedPrinter, model.key,
|
||||||
[p.key for p in self._printers])
|
[p.key for p in self._printers])
|
||||||
return
|
return
|
||||||
|
|
||||||
printer.updateActivePrintJob(model)
|
printer.updateActivePrintJob(model)
|
||||||
model.updateAssignedPrinter(printer)
|
model.updateAssignedPrinter(printer)
|
||||||
|
|
||||||
|
@ -340,7 +340,8 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._progress.show()
|
self._progress.show()
|
||||||
self._uploaded_print_job = job_response
|
self._uploaded_print_job = job_response
|
||||||
tool_path = cast(bytes, self._tool_path)
|
tool_path = cast(bytes, self._tool_path)
|
||||||
self._api.uploadToolPath(job_response, tool_path, self._onPrintJobUploaded, self._progress.update, self._onUploadError)
|
self._api.uploadToolPath(job_response, tool_path, self._onPrintJobUploaded, self._progress.update,
|
||||||
|
self._onUploadError)
|
||||||
|
|
||||||
## Requests the print to be sent to the printer when we finished uploading the mesh.
|
## Requests the print to be sent to the printer when we finished uploading the mesh.
|
||||||
def _onPrintJobUploaded(self) -> None:
|
def _onPrintJobUploaded(self) -> None:
|
||||||
|
@ -372,12 +373,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
).show()
|
).show()
|
||||||
self.writeFinished.emit()
|
self.writeFinished.emit()
|
||||||
|
|
||||||
## Gets the printer type of the cluster host. Falls back to the printer type in the device properties.
|
## Whether the printer that this output device represents supports print job actions via the cloud.
|
||||||
@pyqtProperty(str, notify=_clusterPrintersChanged)
|
@pyqtProperty(bool, notify=_clusterPrintersChanged)
|
||||||
def printerType(self) -> str:
|
def supportsPrintJobActions(self) -> bool:
|
||||||
if self._printers and self._host_machine_type in self._host_machine_variant_to_machine_type_map:
|
if not self._printers:
|
||||||
return self._host_machine_variant_to_machine_type_map[self._host_machine_type]
|
return False
|
||||||
return super().printerType
|
version_number = self.printers[0].firmwareVersion.split(".")
|
||||||
|
firmware_version = Version([version_number[0], version_number[1], version_number[2]])
|
||||||
|
return firmware_version >= self.PRINT_JOB_ACTIONS_MIN_VERSION
|
||||||
|
|
||||||
## Gets the number of printers in the cluster.
|
## Gets the number of printers in the cluster.
|
||||||
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
|
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
|
||||||
|
@ -419,6 +422,23 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
return [print_job for print_job in self._print_jobs if
|
return [print_job for print_job in self._print_jobs if
|
||||||
print_job.assignedPrinter is not None and print_job.state != "queued"]
|
print_job.assignedPrinter is not None and print_job.state != "queued"]
|
||||||
|
|
||||||
|
## Set the remote print job state.
|
||||||
|
def setJobState(self, print_job_uuid: str, state: str) -> None:
|
||||||
|
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, state)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def sendJobToTop(self, print_job_uuid: str) -> None:
|
||||||
|
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "move",
|
||||||
|
{"list": "queued", "to_position": 0})
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
|
||||||
|
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "remove")
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def forceSendJob(self, print_job_uuid: str) -> None:
|
||||||
|
self._api.doPrintJobAction(self._cluster.cluster_id, print_job_uuid, "force")
|
||||||
|
|
||||||
@pyqtSlot(int, result=str)
|
@pyqtSlot(int, result=str)
|
||||||
def formatDuration(self, seconds: int) -> str:
|
def formatDuration(self, seconds: int) -> str:
|
||||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||||
|
@ -431,6 +451,18 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def getDateCompleted(self, time_remaining: int) -> str:
|
def getDateCompleted(self, time_remaining: int) -> str:
|
||||||
return formatDateCompleted(time_remaining)
|
return formatDateCompleted(time_remaining)
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=printJobsChanged)
|
||||||
|
def receivedPrintJobs(self) -> bool:
|
||||||
|
return bool(self._print_jobs)
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def openPrintJobControlPanel(self) -> None:
|
||||||
|
QDesktopServices.openUrl(QUrl("https://mycloud.ultimaker.com"))
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def openPrinterControlPanel(self) -> None:
|
||||||
|
QDesktopServices.openUrl(QUrl("https://mycloud.ultimaker.com"))
|
||||||
|
|
||||||
## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud.
|
## TODO: The following methods are required by the monitor page QML, but are not actually available using cloud.
|
||||||
# TODO: We fake the methods here to not break the monitor page.
|
# TODO: We fake the methods here to not break the monitor page.
|
||||||
|
|
||||||
|
@ -442,30 +474,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
|
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = printJobsChanged)
|
|
||||||
def receivedPrintJobs(self) -> bool:
|
|
||||||
return bool(self._print_jobs)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def openPrintJobControlPanel(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def openPrinterControlPanel(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def sendJobToTop(self, print_job_uuid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def forceSendJob(self, print_job_uuid: str) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
|
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
|
||||||
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
|
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -96,7 +96,7 @@ class CloudOutputDeviceManager:
|
||||||
device = CloudOutputDevice(self._api, cluster)
|
device = CloudOutputDevice(self._api, cluster)
|
||||||
self._remote_clusters[cluster.cluster_id] = device
|
self._remote_clusters[cluster.cluster_id] = device
|
||||||
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
|
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
|
||||||
cluster.cluster_id,
|
device.key,
|
||||||
device.key,
|
device.key,
|
||||||
cluster.friendly_name,
|
cluster.friendly_name,
|
||||||
self._createMachineFromDiscoveredPrinter,
|
self._createMachineFromDiscoveredPrinter,
|
||||||
|
@ -109,7 +109,7 @@ class CloudOutputDeviceManager:
|
||||||
for device, cluster in updates:
|
for device, cluster in updates:
|
||||||
device.clusterData = cluster
|
device.clusterData = cluster
|
||||||
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(
|
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(
|
||||||
cluster.cluster_id,
|
device.key,
|
||||||
cluster.friendly_name,
|
cluster.friendly_name,
|
||||||
device.printerType,
|
device.printerType,
|
||||||
)
|
)
|
||||||
|
|
|
@ -91,7 +91,6 @@ class CloudClusterPrintJobStatus(BaseCloudModel):
|
||||||
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
|
def createOutputModel(self, controller: CloudOutputController) -> UM3PrintJobOutputModel:
|
||||||
model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
|
model = UM3PrintJobOutputModel(controller, self.uuid, self.name)
|
||||||
self.updateOutputModel(model)
|
self.updateOutputModel(model)
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
## Creates a new configuration model
|
## Creates a new configuration model
|
||||||
|
|
|
@ -15,9 +15,12 @@ class CloudClusterResponse(BaseCloudModel):
|
||||||
# \param is_online: Whether this cluster is currently connected to the cloud.
|
# \param is_online: Whether this cluster is currently connected to the cloud.
|
||||||
# \param status: The status of the cluster authentication (active or inactive).
|
# \param status: The status of the cluster authentication (active or inactive).
|
||||||
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
||||||
|
# \param host_internal_ip: The internal IP address of the host printer.
|
||||||
|
# \param friendly_name: The human readable name of the host printer.
|
||||||
|
# \param printer_type: The machine type of the host printer.
|
||||||
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
||||||
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
||||||
friendly_name: Optional[str] = None, **kwargs) -> None:
|
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", **kwargs) -> None:
|
||||||
self.cluster_id = cluster_id
|
self.cluster_id = cluster_id
|
||||||
self.host_guid = host_guid
|
self.host_guid = host_guid
|
||||||
self.host_name = host_name
|
self.host_name = host_name
|
||||||
|
@ -26,6 +29,7 @@ class CloudClusterResponse(BaseCloudModel):
|
||||||
self.host_version = host_version
|
self.host_version = host_version
|
||||||
self.host_internal_ip = host_internal_ip
|
self.host_internal_ip = host_internal_ip
|
||||||
self.friendly_name = friendly_name
|
self.friendly_name = friendly_name
|
||||||
|
self.printer_type = printer_type
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Validates the model, raising an exception if the model is invalid.
|
# Validates the model, raising an exception if the model is invalid.
|
||||||
|
|
|
@ -106,8 +106,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
self._active_camera_url = QUrl() # type: QUrl
|
self._active_camera_url = QUrl() # type: QUrl
|
||||||
|
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
|
|
||||||
self.sendMaterialProfiles()
|
self.sendMaterialProfiles()
|
||||||
|
@ -140,6 +140,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
if self._printer_selection_dialog is not None:
|
if self._printer_selection_dialog is not None:
|
||||||
self._printer_selection_dialog.show()
|
self._printer_selection_dialog.show()
|
||||||
|
|
||||||
|
## Whether the printer that this output device represents supports print job actions via the local network.
|
||||||
|
@pyqtProperty(bool, constant=True)
|
||||||
|
def supportsPrintJobActions(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@pyqtProperty(int, constant=True)
|
@pyqtProperty(int, constant=True)
|
||||||
def clusterSize(self) -> int:
|
def clusterSize(self) -> int:
|
||||||
return self._cluster_size
|
return self._cluster_size
|
||||||
|
@ -385,6 +390,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
data = "{\"force\": true}"
|
data = "{\"force\": true}"
|
||||||
self.put("print_jobs/{uuid}".format(uuid=print_job_uuid), data, on_finished=None)
|
self.put("print_jobs/{uuid}".format(uuid=print_job_uuid), data, on_finished=None)
|
||||||
|
|
||||||
|
# Set the remote print job state.
|
||||||
|
def setJobState(self, print_job_uuid: str, state: str) -> None:
|
||||||
|
# We rewrite 'resume' to 'print' here because we are using the old print job action endpoints.
|
||||||
|
action = "print" if state == "resume" else state
|
||||||
|
data = "{\"action\": \"%s\"}" % action
|
||||||
|
self.put("print_jobs/%s/action" % print_job_uuid, data, on_finished=None)
|
||||||
|
|
||||||
def _printJobStateChanged(self) -> None:
|
def _printJobStateChanged(self) -> None:
|
||||||
username = self._getUserName()
|
username = self._getUserName()
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||||
|
|
||||||
|
|
||||||
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||||
def __init__(self, output_device):
|
def __init__(self, output_device):
|
||||||
super().__init__(output_device)
|
super().__init__(output_device)
|
||||||
|
@ -15,6 +16,5 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||||
self.can_control_manually = False
|
self.can_control_manually = False
|
||||||
self.can_send_raw_gcode = False
|
self.can_send_raw_gcode = False
|
||||||
|
|
||||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||||
data = "{\"action\": \"%s\"}" % state
|
self._output_device.setJobState(job.key, state)
|
||||||
self._output_device.put("print_jobs/%s/action" % job.key, data, on_finished=None)
|
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||||
|
|
||||||
|
BLOCKING_CHANGE_TYPES = [
|
||||||
|
"material_insert", "buildplate_change"
|
||||||
|
]
|
||||||
|
|
||||||
class ConfigurationChangeModel(QObject):
|
class ConfigurationChangeModel(QObject):
|
||||||
def __init__(self, type_of_change: str, index: int, target_name: str, origin_name: str) -> None:
|
def __init__(self, type_of_change: str, index: int, target_name: str, origin_name: str) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._type_of_change = type_of_change
|
self._type_of_change = type_of_change
|
||||||
# enum = ["material", "print_core_change"]
|
# enum = ["material", "print_core_change"]
|
||||||
|
self._can_override = self._type_of_change not in BLOCKING_CHANGE_TYPES
|
||||||
self._index = index
|
self._index = index
|
||||||
self._target_name = target_name
|
self._target_name = target_name
|
||||||
self._origin_name = origin_name
|
self._origin_name = origin_name
|
||||||
|
@ -27,3 +32,7 @@ class ConfigurationChangeModel(QObject):
|
||||||
@pyqtProperty(str, constant = True)
|
@pyqtProperty(str, constant = True)
|
||||||
def originName(self) -> str:
|
def originName(self) -> str:
|
||||||
return self._origin_name
|
return self._origin_name
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def canOverride(self) -> bool:
|
||||||
|
return self._can_override
|
|
@ -178,7 +178,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# NotImplementedError. We can simply ignore these.
|
# NotImplementedError. We can simply ignore these.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
if not self.activePrinter:
|
if not self.activePrinter:
|
||||||
# No active printer. Unable to write
|
# No active printer. Unable to write
|
||||||
return
|
return
|
||||||
|
|
|
@ -169,6 +169,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
self.addManualDevice(address)
|
self.addManualDevice(address)
|
||||||
self.resetLastManualDevice()
|
self.resetLastManualDevice()
|
||||||
|
|
||||||
|
# TODO: CHANGE TO HOSTNAME
|
||||||
def refreshConnections(self):
|
def refreshConnections(self):
|
||||||
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not active_machine:
|
if not active_machine:
|
||||||
|
@ -235,10 +236,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
self._application.callLater(manual_printer_request.callback, False, address)
|
self._application.callLater(manual_printer_request.callback, False, address)
|
||||||
|
|
||||||
def addManualDevice(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None:
|
def addManualDevice(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None:
|
||||||
if address in self._manual_instances:
|
|
||||||
Logger.log("i", "Manual printer with address [%s] has already been added, do nothing", address)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._manual_instances[address] = ManualPrinterRequest(address, callback = callback)
|
self._manual_instances[address] = ManualPrinterRequest(address, callback = callback)
|
||||||
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys()))
|
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys()))
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,9 @@ class TestCloudOutputDevice(TestCase):
|
||||||
|
|
||||||
controller_fields = {
|
controller_fields = {
|
||||||
"_output_device": self.device,
|
"_output_device": self.device,
|
||||||
"can_abort": False,
|
"can_abort": True,
|
||||||
"can_control_manually": False,
|
"can_control_manually": False,
|
||||||
"can_pause": False,
|
"can_pause": True,
|
||||||
"can_pre_heat_bed": False,
|
"can_pre_heat_bed": False,
|
||||||
"can_pre_heat_hotends": False,
|
"can_pre_heat_hotends": False,
|
||||||
"can_send_raw_gcode": False,
|
"can_send_raw_gcode": False,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Mesh.MeshWriter import MeshWriter #To get the g-code output.
|
from UM.Mesh.MeshWriter import MeshWriter #To get the g-code output.
|
||||||
|
from UM.Message import Message #Show an error when already printing.
|
||||||
from UM.PluginRegistry import PluginRegistry #To get the g-code output.
|
from UM.PluginRegistry import PluginRegistry #To get the g-code output.
|
||||||
from UM.Qt.Duration import DurationFormat
|
from UM.Qt.Duration import DurationFormat
|
||||||
|
|
||||||
|
@ -23,11 +24,15 @@ from queue import Queue
|
||||||
from serial import Serial, SerialException, SerialTimeoutException
|
from serial import Serial, SerialException, SerialTimeoutException
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Union, Optional, List, cast
|
from typing import Union, Optional, List, cast, TYPE_CHECKING
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import functools # Used for reduce
|
import functools # Used for reduce
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.FileHandler.FileHandler import FileHandler
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,16 +117,20 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
## Request the current scene to be sent to a USB-connected printer.
|
## Request the current scene to be sent to a USB-connected printer.
|
||||||
#
|
#
|
||||||
# \param nodes A collection of scene nodes to send. This is ignored.
|
# \param nodes A collection of scene nodes to send. This is ignored.
|
||||||
# \param file_name \type{string} A suggestion for a file name to write.
|
# \param file_name A suggestion for a file name to write.
|
||||||
# \param filter_by_machine Whether to filter MIME types by machine. This
|
# \param filter_by_machine Whether to filter MIME types by machine. This
|
||||||
# is ignored.
|
# is ignored.
|
||||||
# \param kwargs Keyword arguments.
|
# \param kwargs Keyword arguments.
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
|
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
|
||||||
|
file_handler: Optional["FileHandler"] = None, filter_by_machine: bool = False, **kwargs) -> None:
|
||||||
if self._is_printing:
|
if self._is_printing:
|
||||||
|
message = Message(text = catalog.i18nc("@message", "A print is still in progress. Cura cannot start another print via USB until the previous print has completed."), title = catalog.i18nc("@message", "Print in Progress"))
|
||||||
|
message.show()
|
||||||
return # Already printing
|
return # Already printing
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
# cancel any ongoing preheat timer before starting a print
|
# cancel any ongoing preheat timer before starting a print
|
||||||
self._printers[0].getController().stopPreheatTimers()
|
controller = cast(GenericOutputController, self._printers[0].getController())
|
||||||
|
controller.stopPreheatTimers()
|
||||||
|
|
||||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||||
|
|
||||||
|
@ -181,7 +190,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
try:
|
try:
|
||||||
self._serial = Serial(str(self._serial_port), self._baud_rate, timeout=self._timeout, writeTimeout=self._timeout)
|
self._serial = Serial(str(self._serial_port), self._baud_rate, timeout=self._timeout, writeTimeout=self._timeout)
|
||||||
except SerialException:
|
except SerialException:
|
||||||
Logger.log("w", "An exception occured while trying to create serial connection")
|
Logger.log("w", "An exception occurred while trying to create serial connection")
|
||||||
return
|
return
|
||||||
CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
||||||
self._onGlobalContainerStackChanged()
|
self._onGlobalContainerStackChanged()
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
|
from os import environ
|
||||||
|
from re import search
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
|
|
||||||
|
@ -112,6 +114,27 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
||||||
port = (port.device, port.description, port.hwid)
|
port = (port.device, port.description, port.hwid)
|
||||||
if only_list_usb and not port[2].startswith("USB"):
|
if only_list_usb and not port[2].startswith("USB"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# To prevent cura from messing with serial ports of other devices,
|
||||||
|
# filter by regular expressions passed in as environment variables.
|
||||||
|
# Get possible patterns with python3 -m serial.tools.list_ports -v
|
||||||
|
|
||||||
|
# set CURA_DEVICENAMES=USB[1-9] -> e.g. not matching /dev/ttyUSB0
|
||||||
|
pattern = environ.get('CURA_DEVICENAMES')
|
||||||
|
if pattern and not search(pattern, port[0]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# set CURA_DEVICETYPES=CP2102 -> match a type of serial converter
|
||||||
|
pattern = environ.get('CURA_DEVICETYPES')
|
||||||
|
if pattern and not search(pattern, port[1]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# set CURA_DEVICEINFOS=LOCATION=2-1.4 -> match a physical port
|
||||||
|
# set CURA_DEVICEINFOS=VID:PID=10C4:EA60 -> match a vendor:product
|
||||||
|
pattern = environ.get('CURA_DEVICEINFOS')
|
||||||
|
if pattern and not search(pattern, port[2]):
|
||||||
|
continue
|
||||||
|
|
||||||
base_list += [port[0]]
|
base_list += [port[0]]
|
||||||
|
|
||||||
return list(base_list)
|
return list(base_list)
|
||||||
|
|
|
@ -52,7 +52,7 @@ class VersionUpgrade40to41(VersionUpgrade):
|
||||||
parser["metadata"]["setting_version"] = "7"
|
parser["metadata"]["setting_version"] = "7"
|
||||||
|
|
||||||
# Limit Maximum Deviation instead of Maximum Resolution. This should have approximately the same effect as before the algorithm change, only more consistent.
|
# Limit Maximum Deviation instead of Maximum Resolution. This should have approximately the same effect as before the algorithm change, only more consistent.
|
||||||
if "meshfix_maximum_resolution" in parser["values"]:
|
if "values" in parser and "meshfix_maximum_resolution" in parser["values"]:
|
||||||
resolution = parser["values"]["meshfix_maximum_resolution"]
|
resolution = parser["values"]["meshfix_maximum_resolution"]
|
||||||
if resolution.startswith("="):
|
if resolution.startswith("="):
|
||||||
resolution = resolution[1:]
|
resolution = resolution[1:]
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import io
|
||||||
|
import os.path # To get the file ID.
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
from UM.VersionUpgrade import VersionUpgrade
|
||||||
|
|
||||||
|
_renamed_settings = {
|
||||||
|
"support_minimal_diameter": "support_tower_maximum_supported_diameter"
|
||||||
|
} # type: Dict[str, str]
|
||||||
|
_removed_settings = ["prime_tower_circular", "max_feedrate_z_override"] # type: List[str]
|
||||||
|
_renamed_profiles = {
|
||||||
|
# Include CreawsomeMod profiles here as well for the people who installed that.
|
||||||
|
# Definitions.
|
||||||
|
"creawsome_base": "creality_base",
|
||||||
|
"creawsome_cr10": "creality_cr10",
|
||||||
|
"creawsome_cr10mini": "creality_cr10mini",
|
||||||
|
"creawsome_cr10s": "creality_cr10s",
|
||||||
|
"creawsome_cr10s4": "creality_cr10s4",
|
||||||
|
"creawsome_cr10s5": "creality_cr10s5",
|
||||||
|
"creawsome_cr10spro": "creality_cr10spro",
|
||||||
|
"creawsome_cr20": "creality_cr20",
|
||||||
|
"creawsome_cr20pro": "creality_cr20pro",
|
||||||
|
"creawsome_ender2": "creality_ender2",
|
||||||
|
"creawsome_ender3": "creality_ender3",
|
||||||
|
"creawsome_ender4": "creality_ender4",
|
||||||
|
"creawsome_ender5": "creality_ender5",
|
||||||
|
|
||||||
|
# Extruder definitions.
|
||||||
|
"creawsome_base_extruder_0": "creality_base_extruder_0",
|
||||||
|
|
||||||
|
# Variants.
|
||||||
|
"creawsome_base_0.2": "creality_base_0.2",
|
||||||
|
"creawsome_base_0.3": "creality_base_0.3",
|
||||||
|
"creawsome_base_0.4": "creality_base_0.4",
|
||||||
|
"creawsome_base_0.5": "creality_base_0.5",
|
||||||
|
"creawsome_base_0.6": "creality_base_0.6",
|
||||||
|
"creawsome_base_0.8": "creality_base_0.8",
|
||||||
|
"creawsome_base_1.0": "creality_base_1.0",
|
||||||
|
"creawsome_cr10_0.2": "creality_cr10_0.2",
|
||||||
|
"creawsome_cr10_0.3": "creality_cr10_0.3",
|
||||||
|
"creawsome_cr10_0.4": "creality_cr10_0.4",
|
||||||
|
"creawsome_cr10_0.5": "creality_cr10_0.5",
|
||||||
|
"creawsome_cr10_0.6": "creality_cr10_0.6",
|
||||||
|
"creawsome_cr10_0.8": "creality_cr10_0.8",
|
||||||
|
"creawsome_cr10_1.0": "creality_cr10_1.0",
|
||||||
|
"creawsome_cr10mini_0.2": "creality_cr10mini_0.2",
|
||||||
|
"creawsome_cr10mini_0.3": "creality_cr10mini_0.3",
|
||||||
|
"creawsome_cr10mini_0.4": "creality_cr10mini_0.4",
|
||||||
|
"creawsome_cr10mini_0.5": "creality_cr10mini_0.5",
|
||||||
|
"creawsome_cr10mini_0.6": "creality_cr10mini_0.6",
|
||||||
|
"creawsome_cr10mini_0.8": "creality_cr10mini_0.8",
|
||||||
|
"creawsome_cr10mini_1.0": "creality_cr10mini_1.0",
|
||||||
|
"creawsome_cr10s4_0.2": "creality_cr10s4_0.2",
|
||||||
|
"creawsome_cr10s4_0.3": "creality_cr10s4_0.3",
|
||||||
|
"creawsome_cr10s4_0.4": "creality_cr10s4_0.4",
|
||||||
|
"creawsome_cr10s4_0.5": "creality_cr10s4_0.5",
|
||||||
|
"creawsome_cr10s4_0.6": "creality_cr10s4_0.6",
|
||||||
|
"creawsome_cr10s4_0.8": "creality_cr10s4_0.8",
|
||||||
|
"creawsome_cr10s4_1.0": "creality_cr10s4_1.0",
|
||||||
|
"creawsome_cr10s5_0.2": "creality_cr10s5_0.2",
|
||||||
|
"creawsome_cr10s5_0.3": "creality_cr10s5_0.3",
|
||||||
|
"creawsome_cr10s5_0.4": "creality_cr10s5_0.4",
|
||||||
|
"creawsome_cr10s5_0.5": "creality_cr10s5_0.5",
|
||||||
|
"creawsome_cr10s5_0.6": "creality_cr10s5_0.6",
|
||||||
|
"creawsome_cr10s5_0.8": "creality_cr10s5_0.8",
|
||||||
|
"creawsome_cr10s5_1.0": "creality_cr10s5_1.0",
|
||||||
|
"creawsome_cr10s_0.2": "creality_cr10s_0.2",
|
||||||
|
"creawsome_cr10s_0.3": "creality_cr10s_0.3",
|
||||||
|
"creawsome_cr10s_0.4": "creality_cr10s_0.4",
|
||||||
|
"creawsome_cr10s_0.5": "creality_cr10s_0.5",
|
||||||
|
"creawsome_cr10s_0.6": "creality_cr10s_0.6",
|
||||||
|
"creawsome_cr10s_0.8": "creality_cr10s_0.8",
|
||||||
|
"creawsome_cr10s_1.0": "creality_cr10s_1.0",
|
||||||
|
"creawsome_cr10spro_0.2": "creality_cr10spro_0.2",
|
||||||
|
"creawsome_cr10spro_0.3": "creality_cr10spro_0.3",
|
||||||
|
"creawsome_cr10spro_0.4": "creality_cr10spro_0.4",
|
||||||
|
"creawsome_cr10spro_0.5": "creality_cr10spro_0.5",
|
||||||
|
"creawsome_cr10spro_0.6": "creality_cr10spro_0.6",
|
||||||
|
"creawsome_cr10spro_0.8": "creality_cr10spro_0.8",
|
||||||
|
"creawsome_cr10spro_1.0": "creality_cr10spro_1.0",
|
||||||
|
"creawsome_cr20_0.2": "creality_cr20_0.2",
|
||||||
|
"creawsome_cr20_0.3": "creality_cr20_0.3",
|
||||||
|
"creawsome_cr20_0.4": "creality_cr20_0.4",
|
||||||
|
"creawsome_cr20_0.5": "creality_cr20_0.5",
|
||||||
|
"creawsome_cr20_0.6": "creality_cr20_0.6",
|
||||||
|
"creawsome_cr20_0.8": "creality_cr20_0.8",
|
||||||
|
"creawsome_cr20_1.0": "creality_cr20_1.0",
|
||||||
|
"creawsome_cr20pro_0.2": "creality_cr20pro_0.2",
|
||||||
|
"creawsome_cr20pro_0.3": "creality_cr20pro_0.3",
|
||||||
|
"creawsome_cr20pro_0.4": "creality_cr20pro_0.4",
|
||||||
|
"creawsome_cr20pro_0.5": "creality_cr20pro_0.5",
|
||||||
|
"creawsome_cr20pro_0.6": "creality_cr20pro_0.6",
|
||||||
|
"creawsome_cr20pro_0.8": "creality_cr20pro_0.8",
|
||||||
|
"creawsome_cr20pro_1.0": "creality_cr20pro_1.0",
|
||||||
|
"creawsome_ender2_0.2": "creality_ender2_0.2",
|
||||||
|
"creawsome_ender2_0.3": "creality_ender2_0.3",
|
||||||
|
"creawsome_ender2_0.4": "creality_ender2_0.4",
|
||||||
|
"creawsome_ender2_0.5": "creality_ender2_0.5",
|
||||||
|
"creawsome_ender2_0.6": "creality_ender2_0.6",
|
||||||
|
"creawsome_ender2_0.8": "creality_ender2_0.8",
|
||||||
|
"creawsome_ender2_1.0": "creality_ender2_1.0",
|
||||||
|
"creawsome_ender3_0.2": "creality_ender3_0.2",
|
||||||
|
"creawsome_ender3_0.3": "creality_ender3_0.3",
|
||||||
|
"creawsome_ender3_0.4": "creality_ender3_0.4",
|
||||||
|
"creawsome_ender3_0.5": "creality_ender3_0.5",
|
||||||
|
"creawsome_ender3_0.6": "creality_ender3_0.6",
|
||||||
|
"creawsome_ender3_0.8": "creality_ender3_0.8",
|
||||||
|
"creawsome_ender3_1.0": "creality_ender3_1.0",
|
||||||
|
"creawsome_ender4_0.2": "creality_ender4_0.2",
|
||||||
|
"creawsome_ender4_0.3": "creality_ender4_0.3",
|
||||||
|
"creawsome_ender4_0.4": "creality_ender4_0.4",
|
||||||
|
"creawsome_ender4_0.5": "creality_ender4_0.5",
|
||||||
|
"creawsome_ender4_0.6": "creality_ender4_0.6",
|
||||||
|
"creawsome_ender4_0.8": "creality_ender4_0.8",
|
||||||
|
"creawsome_ender4_1.0": "creality_ender4_1.0",
|
||||||
|
"creawsome_ender5_0.2": "creality_ender5_0.2",
|
||||||
|
"creawsome_ender5_0.3": "creality_ender5_0.3",
|
||||||
|
"creawsome_ender5_0.4": "creality_ender5_0.4",
|
||||||
|
"creawsome_ender5_0.5": "creality_ender5_0.5",
|
||||||
|
"creawsome_ender5_0.6": "creality_ender5_0.6",
|
||||||
|
"creawsome_ender5_0.8": "creality_ender5_0.8",
|
||||||
|
"creawsome_ender5_1.0": "creality_ender5_1.0",
|
||||||
|
|
||||||
|
# Upgrade for people who had the original Creality profiles from 4.1 and earlier.
|
||||||
|
"creality_cr10_extruder_0": "creality_base_extruder_0",
|
||||||
|
"creality_cr10s4_extruder_0": "creality_base_extruder_0",
|
||||||
|
"creality_cr10s5_extruder_0": "creality_base_extruder_0",
|
||||||
|
"creality_ender3_extruder_0": "creality_base_extruder_0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For legacy Creality printers, select the correct quality profile depending on the material.
|
||||||
|
_creality_quality_per_material = {
|
||||||
|
# Since legacy Creality printers didn't have different variants, we always pick the 0.4mm variant.
|
||||||
|
"generic_abs_175": {
|
||||||
|
"high": "base_0.4_ABS_super",
|
||||||
|
"normal": "base_0.4_ABS_super",
|
||||||
|
"fast": "base_0.4_ABS_super",
|
||||||
|
"draft": "base_0.4_ABS_standard",
|
||||||
|
"extra_fast": "base_0.4_ABS_low",
|
||||||
|
"coarse": "base_0.4_ABS_low",
|
||||||
|
"extra_coarse": "base_0.4_ABS_low"
|
||||||
|
},
|
||||||
|
"generic_petg_175": {
|
||||||
|
"high": "base_0.4_PETG_super",
|
||||||
|
"normal": "base_0.4_PETG_super",
|
||||||
|
"fast": "base_0.4_PETG_super",
|
||||||
|
"draft": "base_0.4_PETG_standard",
|
||||||
|
"extra_fast": "base_0.4_PETG_low",
|
||||||
|
"coarse": "base_0.4_PETG_low",
|
||||||
|
"extra_coarse": "base_0.4_PETG_low"
|
||||||
|
},
|
||||||
|
"generic_pla_175": {
|
||||||
|
"high": "base_0.4_PLA_super",
|
||||||
|
"normal": "base_0.4_PLA_super",
|
||||||
|
"fast": "base_0.4_PLA_super",
|
||||||
|
"draft": "base_0.4_PLA_standard",
|
||||||
|
"extra_fast": "base_0.4_PLA_low",
|
||||||
|
"coarse": "base_0.4_PLA_low",
|
||||||
|
"extra_coarse": "base_0.4_PLA_low"
|
||||||
|
},
|
||||||
|
"generic_tpu_175": {
|
||||||
|
"high": "base_0.4_TPU_super",
|
||||||
|
"normal": "base_0.4_TPU_super",
|
||||||
|
"fast": "base_0.4_TPU_super",
|
||||||
|
"draft": "base_0.4_TPU_standard",
|
||||||
|
"extra_fast": "base_0.4_TPU_standard",
|
||||||
|
"coarse": "base_0.4_TPU_standard",
|
||||||
|
"extra_coarse": "base_0.4_TPU_standard"
|
||||||
|
},
|
||||||
|
"empty_material": { # For the global stack.
|
||||||
|
"high": "base_global_super",
|
||||||
|
"normal": "base_global_super",
|
||||||
|
"fast": "base_global_super",
|
||||||
|
"draft": "base_global_standard",
|
||||||
|
"extra_fast": "base_global_low",
|
||||||
|
"coarse": "base_global_low",
|
||||||
|
"extra_coarse": "base_global_low"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default variant to select for legacy Creality printers, now that we have variants.
|
||||||
|
_default_variants = {
|
||||||
|
"creality_cr10_extruder_0": "creality_cr10_0.4",
|
||||||
|
"creality_cr10s4_extruder_0": "creality_cr10s4_0.4",
|
||||||
|
"creality_cr10s5_extruder_0": "creality_cr10s5_0.4",
|
||||||
|
"creality_ender3_extruder_0": "creality_ender3_0.4"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Whether the quality changes profile belongs to one of the upgraded printers can only be recognised by how they start.
|
||||||
|
# If they are, they must use the creality base definition so that they still belong to those printers.
|
||||||
|
_quality_changes_to_creality_base = {
|
||||||
|
"creality_cr10_extruder_0",
|
||||||
|
"creality_cr10s4_extruder_0",
|
||||||
|
"creality_cr10s5_extruder_0",
|
||||||
|
"creality_ender3_extruder_0"
|
||||||
|
"creality_cr10",
|
||||||
|
"creality_cr10s4",
|
||||||
|
"creality_cr10s5",
|
||||||
|
"creality_ender3",
|
||||||
|
}
|
||||||
|
_creality_limited_quality_type = {
|
||||||
|
"high": "super",
|
||||||
|
"normal": "super",
|
||||||
|
"fast": "super",
|
||||||
|
"draft": "draft",
|
||||||
|
"extra_fast": "draft",
|
||||||
|
"coarse": "draft",
|
||||||
|
"extra_coarse": "draft"
|
||||||
|
}
|
||||||
|
|
||||||
|
## Upgrades configurations from the state they were in at version 4.1 to the
|
||||||
|
# state they should be in at version 4.2.
|
||||||
|
class VersionUpgrade41to42(VersionUpgrade):
|
||||||
|
## Gets the version number from a CFG file in Uranium's 4.1 format.
|
||||||
|
#
|
||||||
|
# Since the format may change, this is implemented for the 4.1 format only
|
||||||
|
# and needs to be included in the version upgrade system rather than
|
||||||
|
# globally in Uranium.
|
||||||
|
#
|
||||||
|
# \param serialised The serialised form of a CFG file.
|
||||||
|
# \return The version number stored in the CFG file.
|
||||||
|
# \raises ValueError The format of the version number in the file is
|
||||||
|
# incorrect.
|
||||||
|
# \raises KeyError The format of the file is incorrect.
|
||||||
|
def getCfgVersion(self, serialised: str) -> int:
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialised)
|
||||||
|
format_version = int(parser.get("general", "version")) # Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||||
|
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||||
|
return format_version * 1000000 + setting_version
|
||||||
|
|
||||||
|
## Upgrades instance containers to have the new version
|
||||||
|
# number.
|
||||||
|
#
|
||||||
|
# This renames the renamed settings in the containers.
|
||||||
|
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["metadata"]["setting_version"] = "8"
|
||||||
|
|
||||||
|
# Certain instance containers (such as definition changes) reference to a certain definition container
|
||||||
|
# Since a number of those changed name, we also need to update those.
|
||||||
|
old_definition = parser["general"]["definition"]
|
||||||
|
if old_definition in _renamed_profiles:
|
||||||
|
parser["general"]["definition"] = _renamed_profiles[old_definition]
|
||||||
|
|
||||||
|
# Rename settings.
|
||||||
|
if "values" in parser:
|
||||||
|
for old_name, new_name in _renamed_settings.items():
|
||||||
|
if old_name in parser["values"]:
|
||||||
|
parser["values"][new_name] = parser["values"][old_name]
|
||||||
|
del parser["values"][old_name]
|
||||||
|
# Remove settings.
|
||||||
|
for key in _removed_settings:
|
||||||
|
if key in parser["values"]:
|
||||||
|
del parser["values"][key]
|
||||||
|
|
||||||
|
# For quality-changes profiles made for Creality printers, change the definition to the creality_base and make sure that the quality is something we have a profile for.
|
||||||
|
if parser["metadata"].get("type", "") == "quality_changes":
|
||||||
|
for possible_printer in _quality_changes_to_creality_base:
|
||||||
|
if os.path.basename(filename).startswith(possible_printer + "_"):
|
||||||
|
parser["general"]["definition"] = "creality_base"
|
||||||
|
parser["metadata"]["quality_type"] = _creality_limited_quality_type.get(parser["metadata"]["quality_type"], "draft")
|
||||||
|
break
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
||||||
|
|
||||||
|
## Upgrades Preferences to have the new version number.
|
||||||
|
#
|
||||||
|
# This renames the renamed settings in the list of visible settings.
|
||||||
|
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["metadata"]["setting_version"] = "8"
|
||||||
|
|
||||||
|
# Renamed settings.
|
||||||
|
if "visible_settings" in parser["general"]:
|
||||||
|
visible_settings = parser["general"]["visible_settings"]
|
||||||
|
visible_setting_set = set(visible_settings.split(";"))
|
||||||
|
for old_name, new_name in _renamed_settings.items():
|
||||||
|
if old_name in visible_setting_set:
|
||||||
|
visible_setting_set.remove(old_name)
|
||||||
|
visible_setting_set.add(new_name)
|
||||||
|
for removed_key in _removed_settings:
|
||||||
|
if removed_key in visible_setting_set:
|
||||||
|
visible_setting_set.remove(removed_key)
|
||||||
|
parser["general"]["visible_settings"] = ";".join(visible_setting_set)
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
||||||
|
|
||||||
|
## Upgrades stacks to have the new version number.
|
||||||
|
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["metadata"]["setting_version"] = "8"
|
||||||
|
|
||||||
|
# Change renamed profiles.
|
||||||
|
if "containers" in parser:
|
||||||
|
# For legacy Creality printers, change the variant to 0.4.
|
||||||
|
definition_id = parser["containers"]["6"]
|
||||||
|
if parser["metadata"].get("type", "machine") == "extruder_train":
|
||||||
|
if parser["containers"]["4"] == "empty_variant": # Necessary for people entering from CreawsomeMod who already had a variant.
|
||||||
|
if definition_id in _default_variants:
|
||||||
|
parser["containers"]["4"] = _default_variants[definition_id]
|
||||||
|
if definition_id == "creality_cr10_extruder_0": # We can't disambiguate between Creality CR-10 and Creality-CR10S since they share the same extruder definition. Have to go by the name.
|
||||||
|
if "cr-10s" in parser["metadata"].get("machine", "Creality CR-10").lower(): # Not perfect, since the user can change this name :(
|
||||||
|
parser["containers"]["4"] = "creality_cr10s_0.4"
|
||||||
|
|
||||||
|
# Also change the quality to go along with it.
|
||||||
|
material_id = parser["containers"]["3"]
|
||||||
|
old_quality_id = parser["containers"]["2"]
|
||||||
|
if material_id in _creality_quality_per_material and old_quality_id in _creality_quality_per_material[material_id]:
|
||||||
|
parser["containers"]["2"] = _creality_quality_per_material[material_id][old_quality_id]
|
||||||
|
|
||||||
|
stack_copy = {} # type: Dict[str, str] # Make a copy so that we don't modify the dict we're iterating over.
|
||||||
|
stack_copy.update(parser["containers"])
|
||||||
|
for position, profile_id in stack_copy.items():
|
||||||
|
if profile_id in _renamed_profiles:
|
||||||
|
parser["containers"][position] = _renamed_profiles[profile_id]
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
59
plugins/VersionUpgrade/VersionUpgrade41to42/__init__.py
Normal file
59
plugins/VersionUpgrade/VersionUpgrade41to42/__init__.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
|
from . import VersionUpgrade41to42
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
upgrade = VersionUpgrade41to42.VersionUpgrade41to42()
|
||||||
|
|
||||||
|
def getMetaData() -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"version_upgrade": {
|
||||||
|
# From To Upgrade function
|
||||||
|
("preferences", 6000007): ("preferences", 6000008, upgrade.upgradePreferences),
|
||||||
|
("machine_stack", 4000007): ("machine_stack", 4000008, upgrade.upgradeStack),
|
||||||
|
("extruder_train", 4000007): ("extruder_train", 4000008, upgrade.upgradeStack),
|
||||||
|
("definition_changes", 4000007): ("definition_changes", 4000008, upgrade.upgradeInstanceContainer),
|
||||||
|
("quality_changes", 4000007): ("quality_changes", 4000008, upgrade.upgradeInstanceContainer),
|
||||||
|
("quality", 4000007): ("quality", 4000008, upgrade.upgradeInstanceContainer),
|
||||||
|
("user", 4000007): ("user", 4000008, upgrade.upgradeInstanceContainer),
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"preferences": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"."}
|
||||||
|
},
|
||||||
|
"machine_stack": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./machine_instances"}
|
||||||
|
},
|
||||||
|
"extruder_train": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./extruders"}
|
||||||
|
},
|
||||||
|
"definition_changes": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./definition_changes"}
|
||||||
|
},
|
||||||
|
"quality_changes": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./quality_changes"}
|
||||||
|
},
|
||||||
|
"quality": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./quality"}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./user"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def register(app: "Application") -> Dict[str, Any]:
|
||||||
|
return { "version_upgrade": upgrade }
|
8
plugins/VersionUpgrade/VersionUpgrade41to42/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade41to42/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "Version Upgrade 4.1 to 4.2",
|
||||||
|
"author": "Ultimaker B.V.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
|
||||||
|
"api": "6.0",
|
||||||
|
"i18n-catalog": "cura"
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
[shaders]
|
[shaders]
|
||||||
vertex =
|
vertex =
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
attribute highp vec4 a_vertex;
|
attribute highp vec4 a_vertex;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment =
|
fragment =
|
||||||
|
@ -19,13 +21,15 @@ fragment =
|
||||||
|
|
||||||
vertex41core =
|
vertex41core =
|
||||||
#version 410
|
#version 410
|
||||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
uniform highp mat4 u_modelMatrix;
|
||||||
|
uniform highp mat4 u_viewMatrix;
|
||||||
|
uniform highp mat4 u_projectionMatrix;
|
||||||
|
|
||||||
in highp vec4 a_vertex;
|
in highp vec4 a_vertex;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment41core =
|
fragment41core =
|
||||||
|
@ -43,7 +47,9 @@ fragment41core =
|
||||||
u_color = [0.02, 0.02, 0.02, 1.0]
|
u_color = [0.02, 0.02, 0.02, 1.0]
|
||||||
|
|
||||||
[bindings]
|
[bindings]
|
||||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
u_modelMatrix = model_matrix
|
||||||
|
u_viewMatrix = view_matrix
|
||||||
|
u_projectionMatrix = projection_matrix
|
||||||
|
|
||||||
[attributes]
|
[attributes]
|
||||||
a_vertex = vertex
|
a_vertex = vertex
|
||||||
|
|
|
@ -63,9 +63,19 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId()))
|
Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId()))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Some metadata such as diameter should also be instantiated to be a setting. Go though all values for the
|
||||||
|
# "properties" field and apply the new values to SettingInstances as well.
|
||||||
|
new_setting_values_dict = {}
|
||||||
|
if key == "properties":
|
||||||
|
for k, v in value.items():
|
||||||
|
if k in self.__material_properties_setting_map:
|
||||||
|
new_setting_values_dict[self.__material_properties_setting_map[k]] = v
|
||||||
|
|
||||||
# Prevent recursion
|
# Prevent recursion
|
||||||
if not apply_to_all:
|
if not apply_to_all:
|
||||||
super().setMetaDataEntry(key, value)
|
super().setMetaDataEntry(key, value)
|
||||||
|
for k, v in new_setting_values_dict.items():
|
||||||
|
self.setProperty(k, "value", v)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the MaterialGroup
|
# Get the MaterialGroup
|
||||||
|
@ -74,17 +84,23 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
material_group = material_manager.getMaterialGroup(root_material_id)
|
material_group = material_manager.getMaterialGroup(root_material_id)
|
||||||
if not material_group: #If the profile is not registered in the registry but loose/temporary, it will not have a base file tree.
|
if not material_group: #If the profile is not registered in the registry but loose/temporary, it will not have a base file tree.
|
||||||
super().setMetaDataEntry(key, value)
|
super().setMetaDataEntry(key, value)
|
||||||
|
for k, v in new_setting_values_dict.items():
|
||||||
|
self.setProperty(k, "value", v)
|
||||||
return
|
return
|
||||||
# Update the root material container
|
# Update the root material container
|
||||||
root_material_container = material_group.root_material_node.getContainer()
|
root_material_container = material_group.root_material_node.getContainer()
|
||||||
if root_material_container is not None:
|
if root_material_container is not None:
|
||||||
root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
|
root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||||
|
for k, v in new_setting_values_dict.items():
|
||||||
|
root_material_container.setProperty(k, "value", v)
|
||||||
|
|
||||||
# Update all containers derived from it
|
# Update all containers derived from it
|
||||||
for node in material_group.derived_material_node_list:
|
for node in material_group.derived_material_node_list:
|
||||||
container = node.getContainer()
|
container = node.getContainer()
|
||||||
if container is not None:
|
if container is not None:
|
||||||
container.setMetaDataEntry(key, value, apply_to_all = False)
|
container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||||
|
for k, v in new_setting_values_dict.items():
|
||||||
|
container.setProperty(k, "value", v)
|
||||||
|
|
||||||
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
||||||
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
||||||
|
@ -174,13 +190,16 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
## End Name Block
|
## End Name Block
|
||||||
|
|
||||||
for key, value in metadata.items():
|
for key, value in metadata.items():
|
||||||
builder.start(key) # type: ignore
|
key_to_use = key
|
||||||
|
if key in self._metadata_tags_that_have_cura_namespace:
|
||||||
|
key_to_use = "cura:" + key_to_use
|
||||||
|
builder.start(key_to_use) # type: ignore
|
||||||
if value is not None: #Nones get handled well by the builder.
|
if value is not None: #Nones get handled well by the builder.
|
||||||
#Otherwise the builder always expects a string.
|
#Otherwise the builder always expects a string.
|
||||||
#Deserialize expects the stringified version.
|
#Deserialize expects the stringified version.
|
||||||
value = str(value)
|
value = str(value)
|
||||||
builder.data(value)
|
builder.data(value)
|
||||||
builder.end(key)
|
builder.end(key_to_use)
|
||||||
|
|
||||||
builder.end("metadata")
|
builder.end("metadata")
|
||||||
## End Metadata Block
|
## End Metadata Block
|
||||||
|
@ -950,7 +969,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
|
|
||||||
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
|
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
|
||||||
machine_id_list = product_id_map.get(identifier.get("product"), [])
|
machine_id_list = product_id_map.get(identifier.get("product", ""), [])
|
||||||
if not machine_id_list:
|
if not machine_id_list:
|
||||||
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
|
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
|
||||||
|
|
||||||
|
@ -1167,6 +1186,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<XmlMaterialProfile '{my_id}' ('{name}') from base file '{base_file}'>".format(my_id = self.getId(), name = self.getName(), base_file = self.getMetaDataEntry("base_file"))
|
return "<XmlMaterialProfile '{my_id}' ('{name}') from base file '{base_file}'>".format(my_id = self.getId(), name = self.getName(), base_file = self.getMetaDataEntry("base_file"))
|
||||||
|
|
||||||
|
_metadata_tags_that_have_cura_namespace = {"pva_compatible", "breakaway_compatible"}
|
||||||
|
|
||||||
# Map XML file setting names to internal names
|
# Map XML file setting names to internal names
|
||||||
__material_settings_setting_map = {
|
__material_settings_setting_map = {
|
||||||
"print temperature": "default_material_print_temperature",
|
"print temperature": "default_material_print_temperature",
|
||||||
|
@ -1180,6 +1201,13 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
"surface energy": "material_surface_energy",
|
"surface energy": "material_surface_energy",
|
||||||
"shrinkage percentage": "material_shrinkage_percentage",
|
"shrinkage percentage": "material_shrinkage_percentage",
|
||||||
"build volume temperature": "build_volume_temperature",
|
"build volume temperature": "build_volume_temperature",
|
||||||
|
"anti ooze retract position": "material_anti_ooze_retracted_position",
|
||||||
|
"anti ooze retract speed": "material_anti_ooze_retraction_speed",
|
||||||
|
"break preparation position": "material_break_preparation_retracted_position",
|
||||||
|
"break preparation speed": "material_break_preparation_speed",
|
||||||
|
"break position": "material_break_retracted_position",
|
||||||
|
"break speed": "material_break_speed",
|
||||||
|
"break temperature": "material_break_temperature"
|
||||||
}
|
}
|
||||||
__unmapped_settings = [
|
__unmapped_settings = [
|
||||||
"hardware compatible",
|
"hardware compatible",
|
||||||
|
|
|
@ -764,6 +764,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"VersionUpgrade41to42": {
|
||||||
|
"package_info": {
|
||||||
|
"package_id": "VersionUpgrade41to42",
|
||||||
|
"package_type": "plugin",
|
||||||
|
"display_name": "Version Upgrade 4.1 to 4.2",
|
||||||
|
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
|
||||||
|
"package_version": "1.0.0",
|
||||||
|
"sdk_version": "6.0.0",
|
||||||
|
"website": "https://ultimaker.com",
|
||||||
|
"author": {
|
||||||
|
"author_id": "UltimakerPackages",
|
||||||
|
"display_name": "Ultimaker B.V.",
|
||||||
|
"email": "plugins@ultimaker.com",
|
||||||
|
"website": "https://ultimaker.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"X3DReader": {
|
"X3DReader": {
|
||||||
"package_info": {
|
"package_info": {
|
||||||
"package_id": "X3DReader",
|
"package_id": "X3DReader",
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"speed_wall": { "value": "speed_print * 0.7" },
|
"speed_wall": { "value": "speed_print * 0.7" },
|
||||||
"speed_topbottom": { "value": "speed_print * 0.7" },
|
"speed_topbottom": { "value": "speed_print * 0.7" },
|
||||||
"speed_layer_0": { "value": "speed_print * 0.7" },
|
"speed_layer_0": { "value": "speed_print * 0.7" },
|
||||||
"gantry_height": { "default_value": 0 },
|
"gantry_height": { "value": "0" },
|
||||||
"retraction_speed": { "default_value" : 10 },
|
"retraction_speed": { "default_value" : 10 },
|
||||||
"retraction_amount": { "default_value" : 2.5 },
|
"retraction_amount": { "default_value" : 2.5 },
|
||||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
|
|
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