mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-09-25 20:53:15 +08:00
Merge branch 'master' into random_infill_start_
This commit is contained in:
commit
3cefa39e6c
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. -->
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -71,3 +71,7 @@ run.sh
|
|||||||
.scannerwork/
|
.scannerwork/
|
||||||
CuraEngine
|
CuraEngine
|
||||||
|
|
||||||
|
/.coverage
|
||||||
|
|
||||||
|
#Prevents import failures when plugin running tests
|
||||||
|
plugins/__init__.py
|
||||||
|
16
.gitlab-ci.yml
Normal file
16
.gitlab-ci.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
image: registry.gitlab.com/ultimaker/cura/cura-build-environment:centos7
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
|
||||||
|
build and test linux:
|
||||||
|
stage: build
|
||||||
|
tags:
|
||||||
|
- cura
|
||||||
|
- docker
|
||||||
|
- linux
|
||||||
|
script:
|
||||||
|
- docker/build.sh
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- build
|
@ -1,11 +1,10 @@
|
|||||||
project(cura NONE)
|
project(cura)
|
||||||
cmake_minimum_required(VERSION 2.8.12)
|
cmake_minimum_required(VERSION 3.6)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/
|
|
||||||
${CMAKE_MODULE_PATH})
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE DIRECTORY "The location of the Uranium repository")
|
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE DIRECTORY "The location of the Uranium repository")
|
||||||
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository")
|
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository")
|
||||||
|
|
||||||
@ -21,13 +20,34 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Remove the code for CMake <3.12 once we have switched over completely.
|
||||||
|
# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3
|
||||||
|
# module is copied from the CMake repository here so in CMake <3.12 we can still use it.
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
# Use FindPythonInterp and FindPythonLibs for CMake <3.12
|
||||||
|
find_package(PythonInterp 3 REQUIRED)
|
||||||
|
|
||||||
|
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
|
||||||
|
|
||||||
|
set(Python3_VERSION ${PYTHON_VERSION_STRING})
|
||||||
|
set(Python3_VERSION_MAJOR ${PYTHON_VERSION_MAJOR})
|
||||||
|
set(Python3_VERSION_MINOR ${PYTHON_VERSION_MINOR})
|
||||||
|
set(Python3_VERSION_PATCH ${PYTHON_VERSION_PATCH})
|
||||||
|
else()
|
||||||
|
# Use FindPython3 for CMake >=3.12
|
||||||
|
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(NOT ${URANIUM_DIR} STREQUAL "")
|
if(NOT ${URANIUM_DIR} STREQUAL "")
|
||||||
set(CMAKE_MODULE_PATH "${URANIUM_DIR}/cmake")
|
set(CMAKE_MODULE_PATH "${URANIUM_DIR}/cmake")
|
||||||
endif()
|
endif()
|
||||||
@ -40,12 +60,12 @@ if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
|||||||
CREATE_TRANSLATION_TARGETS()
|
CREATE_TRANSLATION_TARGETS()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(PythonInterp 3.5.0 REQUIRED)
|
|
||||||
|
|
||||||
install(DIRECTORY resources
|
install(DIRECTORY resources
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
||||||
install(DIRECTORY plugins
|
install(DIRECTORY plugins
|
||||||
DESTINATION lib${LIB_SUFFIX}/cura)
|
DESTINATION lib${LIB_SUFFIX}/cura)
|
||||||
|
|
||||||
if(NOT APPLE AND NOT WIN32)
|
if(NOT APPLE AND NOT WIN32)
|
||||||
install(FILES cura_app.py
|
install(FILES cura_app.py
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
@ -53,16 +73,16 @@ if(NOT APPLE AND NOT WIN32)
|
|||||||
RENAME cura)
|
RENAME cura)
|
||||||
if(EXISTS /etc/debian_version)
|
if(EXISTS /etc/debian_version)
|
||||||
install(DIRECTORY cura
|
install(DIRECTORY cura
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages
|
||||||
FILES_MATCHING PATTERN *.py)
|
FILES_MATCHING PATTERN *.py)
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages/cura)
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}/dist-packages/cura)
|
||||||
else()
|
else()
|
||||||
install(DIRECTORY cura
|
install(DIRECTORY cura
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
|
||||||
FILES_MATCHING PATTERN *.py)
|
FILES_MATCHING PATTERN *.py)
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
|
||||||
endif()
|
endif()
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/cura.desktop
|
install(FILES ${CMAKE_BINARY_DIR}/cura.desktop
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||||
@ -78,8 +98,8 @@ else()
|
|||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||||
install(DIRECTORY cura
|
install(DIRECTORY cura
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages
|
||||||
FILES_MATCHING PATTERN *.py)
|
FILES_MATCHING PATTERN *.py)
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
|
||||||
DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
|
DESTINATION lib${LIB_SUFFIX}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/cura)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
enable_testing()
|
include(CTest)
|
||||||
include(CMakeParseArguments)
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
find_package(PythonInterp 3.5.0 REQUIRED)
|
# FIXME: Remove the code for CMake <3.12 once we have switched over completely.
|
||||||
|
# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3
|
||||||
|
# module is copied from the CMake repository here so in CMake <3.12 we can still use it.
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
# Use FindPythonInterp and FindPythonLibs for CMake <3.12
|
||||||
|
find_package(PythonInterp 3 REQUIRED)
|
||||||
|
|
||||||
|
set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE})
|
||||||
|
else()
|
||||||
|
# Use FindPython3 for CMake >=3.12
|
||||||
|
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
|
add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose)
|
||||||
|
|
||||||
@ -36,7 +47,7 @@ function(cura_add_test)
|
|||||||
if (NOT ${test_exists})
|
if (NOT ${test_exists})
|
||||||
add_test(
|
add_test(
|
||||||
NAME ${_NAME}
|
NAME ${_NAME}
|
||||||
COMMAND ${PYTHON_EXECUTABLE} -m pytest --verbose --full-trace --capture=no --no-print-log --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
|
COMMAND ${Python3_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
|
||||||
)
|
)
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
|
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
|
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
|
||||||
@ -59,13 +70,13 @@ endforeach()
|
|||||||
#Add code style test.
|
#Add code style test.
|
||||||
add_test(
|
add_test(
|
||||||
NAME "code-style"
|
NAME "code-style"
|
||||||
COMMAND ${PYTHON_EXECUTABLE} run_mypy.py
|
COMMAND ${Python3_EXECUTABLE} run_mypy.py
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
#Add test for whether the shortcut alt-keys are unique in every translation.
|
#Add test for whether the shortcut alt-keys are unique in every translation.
|
||||||
add_test(
|
add_test(
|
||||||
NAME "shortcut-keys"
|
NAME "shortcut-keys"
|
||||||
COMMAND ${PYTHON_EXECUTABLE} scripts/check_shortcut_keys.py
|
COMMAND ${Python3_EXECUTABLE} scripts/check_shortcut_keys.py
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
)
|
)
|
@ -29,6 +29,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||||||
class Account(QObject):
|
class Account(QObject):
|
||||||
# Signal emitted when user logged in or out.
|
# Signal emitted when user logged in or out.
|
||||||
loginStateChanged = pyqtSignal(bool)
|
loginStateChanged = pyqtSignal(bool)
|
||||||
|
accessTokenChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, application: "CuraApplication", parent = None) -> None:
|
def __init__(self, application: "CuraApplication", parent = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -59,8 +60,12 @@ class Account(QObject):
|
|||||||
self._authorization_service.initialize(self._application.getPreferences())
|
self._authorization_service.initialize(self._application.getPreferences())
|
||||||
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
|
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
|
||||||
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
|
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
|
||||||
|
self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
|
||||||
self._authorization_service.loadAuthDataFromPreferences()
|
self._authorization_service.loadAuthDataFromPreferences()
|
||||||
|
|
||||||
|
def _onAccessTokenChanged(self):
|
||||||
|
self.accessTokenChanged.emit()
|
||||||
|
|
||||||
## Returns a boolean indicating whether the given authentication is applied against staging or not.
|
## Returns a boolean indicating whether the given authentication is applied against staging or not.
|
||||||
@property
|
@property
|
||||||
def is_staging(self) -> bool:
|
def is_staging(self) -> bool:
|
||||||
@ -105,7 +110,7 @@ class Account(QObject):
|
|||||||
return None
|
return None
|
||||||
return user_profile.profile_image_url
|
return user_profile.profile_image_url
|
||||||
|
|
||||||
@pyqtProperty(str, notify=loginStateChanged)
|
@pyqtProperty(str, notify=accessTokenChanged)
|
||||||
def accessToken(self) -> Optional[str]:
|
def accessToken(self) -> Optional[str]:
|
||||||
return self._authorization_service.getAccessToken()
|
return self._authorization_service.getAccessToken()
|
||||||
|
|
||||||
|
@ -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.2.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.2.0"
|
||||||
except ImportError:
|
|
||||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
|
# 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 +23,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 +49,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,14 +95,16 @@ 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
|
||||||
|
|
||||||
# Create check array for each edge segment, combine into fill array
|
# Create check array for each edge segment, combine into fill array
|
||||||
for k in range(vertices.shape[0]):
|
for k in range(vertices.shape[0]):
|
||||||
fill = numpy.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
|
check_array = cls._check(vertices[k - 1], vertices[k], base_array)
|
||||||
|
if check_array is not None:
|
||||||
|
fill = numpy.all([fill, check_array], axis=0)
|
||||||
|
|
||||||
# Set all values inside polygon to one
|
# Set all values inside polygon to one
|
||||||
base_array[fill] = 1
|
base_array[fill] = 1
|
||||||
@ -111,9 +120,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) -> Optional[numpy.array]:
|
||||||
if p1[0] == p2[0] and p1[1] == p2[1]:
|
if p1[0] == p2[0] and p1[1] == p2[1]:
|
||||||
return
|
return None
|
||||||
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 +141,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,9 +16,10 @@ 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._saving = False
|
self._saving = False
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
@ -32,6 +33,13 @@ class AutoSave:
|
|||||||
if not self._saving:
|
if not self._saving:
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
|
|
||||||
|
def setEnabled(self, enabled: bool) -> None:
|
||||||
|
self._enabled = enabled
|
||||||
|
if self._enabled:
|
||||||
|
self._change_timer.start()
|
||||||
|
else:
|
||||||
|
self._change_timer.stop()
|
||||||
|
|
||||||
def _onGlobalStackChanged(self):
|
def _onGlobalStackChanged(self):
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
||||||
|
@ -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)
|
||||||
archive.extractall(target_path)
|
try:
|
||||||
|
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.setSaveDataEnabled(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.setSaveDataEnabled(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")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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,9 +23,8 @@ 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
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
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:
|
||||||
|
@ -23,6 +23,7 @@ from UM.Platform import Platform
|
|||||||
from UM.PluginError import PluginNotFoundError
|
from UM.PluginError import PluginNotFoundError
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
|
from UM.Qt.Bindings import MainWindow
|
||||||
from UM.Qt.QtApplication import QtApplication # The class we're inheriting from.
|
from UM.Qt.QtApplication import QtApplication # The class we're inheriting from.
|
||||||
import UM.Util
|
import UM.Util
|
||||||
from UM.View.SelectionPass import SelectionPass # For typing.
|
from UM.View.SelectionPass import SelectionPass # For typing.
|
||||||
@ -112,7 +113,12 @@ from cura.UI import CuraSplashScreen, MachineActionManager, PrintInformation
|
|||||||
from cura.UI.MachineSettingsManager import MachineSettingsManager
|
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.RecommendedMode import RecommendedMode
|
||||||
from cura.UI.WelcomePagesModel import WelcomePagesModel
|
from cura.UI.WelcomePagesModel import WelcomePagesModel
|
||||||
|
from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel
|
||||||
|
|
||||||
|
from cura.Utils.NetworkingUtil import NetworkingUtil
|
||||||
|
|
||||||
from .SingleInstance import SingleInstance
|
from .SingleInstance import SingleInstance
|
||||||
from .AutoSave import AutoSave
|
from .AutoSave import AutoSave
|
||||||
@ -138,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 = 9
|
||||||
|
|
||||||
Created = False
|
Created = False
|
||||||
|
|
||||||
@ -212,12 +218,14 @@ class CuraApplication(QtApplication):
|
|||||||
self._cura_scene_controller = None
|
self._cura_scene_controller = None
|
||||||
self._machine_error_checker = None
|
self._machine_error_checker = None
|
||||||
|
|
||||||
self._machine_settings_manager = MachineSettingsManager(self)
|
self._machine_settings_manager = MachineSettingsManager(self, parent = self)
|
||||||
|
|
||||||
self._discovered_printer_model = DiscoveredPrintersModel(self)
|
self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self)
|
||||||
self._first_start_machine_actions_model = FirstStartMachineActionsModel(self)
|
self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self)
|
||||||
self._welcome_pages_model = WelcomePagesModel(self)
|
self._welcome_pages_model = WelcomePagesModel(self, parent = self)
|
||||||
self._text_manager = TextManager(self)
|
self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self)
|
||||||
|
self._whats_new_pages_model = WhatsNewPagesModel(self, parent = self)
|
||||||
|
self._text_manager = TextManager(parent = self)
|
||||||
|
|
||||||
self._quality_profile_drop_down_menu_model = None
|
self._quality_profile_drop_down_menu_model = None
|
||||||
self._custom_quality_profile_drop_down_menu_model = None
|
self._custom_quality_profile_drop_down_menu_model = None
|
||||||
@ -253,8 +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]
|
||||||
self._save_data_enabled = True
|
|
||||||
|
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
self._container_registry_class = CuraContainerRegistry
|
self._container_registry_class = CuraContainerRegistry
|
||||||
@ -414,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)
|
||||||
@ -517,8 +524,13 @@ 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_ypos", 56)
|
||||||
|
preferences.addPreference("view/colorscheme_xpos", 0)
|
||||||
|
preferences.addPreference("view/colorscheme_ypos", 56)
|
||||||
preferences.addPreference("cura/currency", "€")
|
preferences.addPreference("cura/currency", "€")
|
||||||
preferences.addPreference("cura/material_settings", "{}")
|
preferences.addPreference("cura/material_settings", "{}")
|
||||||
|
|
||||||
@ -665,12 +677,9 @@ class CuraApplication(QtApplication):
|
|||||||
self._message_box_callback = None
|
self._message_box_callback = None
|
||||||
self._message_box_callback_arguments = []
|
self._message_box_callback_arguments = []
|
||||||
|
|
||||||
def setSaveDataEnabled(self, enabled: bool) -> None:
|
|
||||||
self._save_data_enabled = enabled
|
|
||||||
|
|
||||||
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
||||||
def saveSettings(self):
|
def saveSettings(self):
|
||||||
if not self.started or not self._save_data_enabled:
|
if not self.started:
|
||||||
# Do not do saving during application start or when data should not be saved on quit.
|
# Do not do saving during application start or when data should not be saved on quit.
|
||||||
return
|
return
|
||||||
ContainerRegistry.getInstance().saveDirtyContainers()
|
ContainerRegistry.getInstance().saveDirtyContainers()
|
||||||
@ -762,6 +771,8 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
self._output_device_manager.start()
|
self._output_device_manager.start()
|
||||||
self._welcome_pages_model.initialize()
|
self._welcome_pages_model.initialize()
|
||||||
|
self._add_printer_pages_model.initialize()
|
||||||
|
self._whats_new_pages_model.initialize()
|
||||||
|
|
||||||
# Detect in which mode to run and execute that mode
|
# Detect in which mode to run and execute that mode
|
||||||
if self._is_headless:
|
if self._is_headless:
|
||||||
@ -828,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")
|
||||||
|
|
||||||
@ -873,6 +883,14 @@ class CuraApplication(QtApplication):
|
|||||||
def getWelcomePagesModel(self, *args) -> "WelcomePagesModel":
|
def getWelcomePagesModel(self, *args) -> "WelcomePagesModel":
|
||||||
return self._welcome_pages_model
|
return self._welcome_pages_model
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
|
def getAddPrinterPagesModel(self, *args) -> "AddPrinterPagesModel":
|
||||||
|
return self._add_printer_pages_model
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
|
def getWhatsNewPagesModel(self, *args) -> "WhatsNewPagesModel":
|
||||||
|
return self._whats_new_pages_model
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getMachineSettingsManager(self, *args) -> "MachineSettingsManager":
|
def getMachineSettingsManager(self, *args) -> "MachineSettingsManager":
|
||||||
return self._machine_settings_manager
|
return self._machine_settings_manager
|
||||||
@ -912,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)
|
||||||
@ -971,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)
|
||||||
@ -1013,12 +1031,17 @@ class CuraApplication(QtApplication):
|
|||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||||
|
|
||||||
|
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
||||||
|
|
||||||
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
|
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
|
||||||
|
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
|
||||||
|
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")
|
||||||
@ -1098,7 +1121,6 @@ class CuraApplication(QtApplication):
|
|||||||
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
||||||
self._camera_animation.start()
|
self._camera_animation.start()
|
||||||
|
|
||||||
requestAddPrinter = pyqtSignal()
|
|
||||||
activityChanged = pyqtSignal()
|
activityChanged = pyqtSignal()
|
||||||
sceneBoundingBoxChanged = pyqtSignal()
|
sceneBoundingBoxChanged = pyqtSignal()
|
||||||
|
|
||||||
@ -1240,7 +1262,7 @@ class CuraApplication(QtApplication):
|
|||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def arrangeObjectsToAllBuildPlates(self) -> None:
|
def arrangeObjectsToAllBuildPlates(self) -> None:
|
||||||
nodes_to_arrange = []
|
nodes_to_arrange = []
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if not isinstance(node, SceneNode):
|
if not isinstance(node, SceneNode):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -1267,7 +1289,7 @@ class CuraApplication(QtApplication):
|
|||||||
def arrangeAll(self) -> None:
|
def arrangeAll(self) -> None:
|
||||||
nodes_to_arrange = []
|
nodes_to_arrange = []
|
||||||
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if not isinstance(node, SceneNode):
|
if not isinstance(node, SceneNode):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -1305,7 +1327,7 @@ class CuraApplication(QtApplication):
|
|||||||
Logger.log("i", "Reloading all loaded mesh data.")
|
Logger.log("i", "Reloading all loaded mesh data.")
|
||||||
nodes = []
|
nodes = []
|
||||||
has_merged_nodes = False
|
has_merged_nodes = False
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
|
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
|
||||||
if node.getName() == "MergedMesh":
|
if node.getName() == "MergedMesh":
|
||||||
has_merged_nodes = True
|
has_merged_nodes = True
|
||||||
@ -1317,9 +1339,9 @@ class CuraApplication(QtApplication):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
file_name = node.getMeshData().getFileName()
|
mesh_data = node.getMeshData()
|
||||||
if file_name:
|
if mesh_data and mesh_data.getFileName():
|
||||||
job = ReadMeshJob(file_name)
|
job = ReadMeshJob(mesh_data.getFileName())
|
||||||
job._node = node # type: ignore
|
job._node = node # type: ignore
|
||||||
job.finished.connect(self._reloadMeshFinished)
|
job.finished.connect(self._reloadMeshFinished)
|
||||||
if has_merged_nodes:
|
if has_merged_nodes:
|
||||||
@ -1758,3 +1780,32 @@ class CuraApplication(QtApplication):
|
|||||||
def getSidebarCustomMenuItems(self) -> list:
|
def getSidebarCustomMenuItems(self) -> list:
|
||||||
return self._sidebar_custom_menu_items
|
return self._sidebar_custom_menu_items
|
||||||
|
|
||||||
|
@pyqtSlot(result = bool)
|
||||||
|
def shouldShowWelcomeDialog(self) -> bool:
|
||||||
|
# Only show the complete flow if there is no printer yet.
|
||||||
|
return self._machine_manager.activeMachine is None
|
||||||
|
|
||||||
|
@pyqtSlot(result = bool)
|
||||||
|
def shouldShowWhatsNewDialog(self) -> bool:
|
||||||
|
has_active_machine = self._machine_manager.activeMachine is not None
|
||||||
|
has_app_just_upgraded = self.hasJustUpdatedFromOldVersion()
|
||||||
|
|
||||||
|
# Only show the what's new dialog if there's no machine and we have just upgraded
|
||||||
|
show_whatsnew_only = has_active_machine and has_app_just_upgraded
|
||||||
|
return show_whatsnew_only
|
||||||
|
|
||||||
|
@pyqtSlot(result = int)
|
||||||
|
def appWidth(self) -> int:
|
||||||
|
main_window = QtApplication.getInstance().getMainWindow()
|
||||||
|
if main_window:
|
||||||
|
return main_window.width()
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@pyqtSlot(result = int)
|
||||||
|
def appHeight(self) -> int:
|
||||||
|
main_window = QtApplication.getInstance().getMainWindow()
|
||||||
|
if main_window:
|
||||||
|
return main_window.height()
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
@ -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@"
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, QUrl
|
from PyQt5.QtCore import pyqtProperty, QUrl
|
||||||
|
|
||||||
|
from UM.Resources import Resources
|
||||||
from UM.View.View import View
|
from UM.View.View import View
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
|
||||||
# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
|
# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
|
||||||
# to indicate this.
|
# to indicate this.
|
||||||
@ -12,13 +15,20 @@ from UM.View.View import View
|
|||||||
# the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage
|
# the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage
|
||||||
# to actually do something with this.
|
# to actually do something with this.
|
||||||
class CuraView(View):
|
class CuraView(View):
|
||||||
def __init__(self, parent = None) -> 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.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
|
||||||
|
"EmptyViewMenuComponent.qml"))
|
||||||
|
self._use_empty_menu_placeholder = use_empty_menu_placeholder
|
||||||
|
|
||||||
@pyqtProperty(QUrl, constant = True)
|
@pyqtProperty(QUrl, constant = True)
|
||||||
def mainComponent(self) -> QUrl:
|
def mainComponent(self) -> QUrl:
|
||||||
return self.getDisplayComponent("main")
|
return self.getDisplayComponent("main")
|
||||||
|
|
||||||
@pyqtProperty(QUrl, constant = True)
|
@pyqtProperty(QUrl, constant = True)
|
||||||
def stageMenuComponent(self) -> QUrl:
|
def stageMenuComponent(self) -> QUrl:
|
||||||
return self.getDisplayComponent("menu")
|
url = self.getDisplayComponent("menu")
|
||||||
|
if not url.toString() and self._use_empty_menu_placeholder:
|
||||||
|
url = self._empty_menu_placeholder_url
|
||||||
|
return url
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import List
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
|
from UM.Mesh.MeshData import MeshData
|
||||||
|
from cura.LayerPolygon import LayerPolygon
|
||||||
|
|
||||||
|
|
||||||
class Layer:
|
class Layer:
|
||||||
def __init__(self, layer_id):
|
def __init__(self, layer_id: int) -> None:
|
||||||
self._id = layer_id
|
self._id = layer_id
|
||||||
self._height = 0.0
|
self._height = 0.0
|
||||||
self._thickness = 0.0
|
self._thickness = 0.0
|
||||||
self._polygons = []
|
self._polygons = [] # type: List[LayerPolygon]
|
||||||
self._element_count = 0
|
self._element_count = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -20,7 +26,7 @@ class Layer:
|
|||||||
return self._thickness
|
return self._thickness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def polygons(self):
|
def polygons(self) -> List[LayerPolygon]:
|
||||||
return self._polygons
|
return self._polygons
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -33,14 +39,14 @@ class Layer:
|
|||||||
def setThickness(self, thickness):
|
def setThickness(self, thickness):
|
||||||
self._thickness = thickness
|
self._thickness = thickness
|
||||||
|
|
||||||
def lineMeshVertexCount(self):
|
def lineMeshVertexCount(self) -> int:
|
||||||
result = 0
|
result = 0
|
||||||
for polygon in self._polygons:
|
for polygon in self._polygons:
|
||||||
result += polygon.lineMeshVertexCount()
|
result += polygon.lineMeshVertexCount()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def lineMeshElementCount(self):
|
def lineMeshElementCount(self) -> int:
|
||||||
result = 0
|
result = 0
|
||||||
for polygon in self._polygons:
|
for polygon in self._polygons:
|
||||||
result += polygon.lineMeshElementCount()
|
result += polygon.lineMeshElementCount()
|
||||||
@ -57,18 +63,18 @@ class Layer:
|
|||||||
result_index_offset += polygon.lineMeshElementCount()
|
result_index_offset += polygon.lineMeshElementCount()
|
||||||
self._element_count += polygon.elementCount
|
self._element_count += polygon.elementCount
|
||||||
|
|
||||||
return (result_vertex_offset, result_index_offset)
|
return result_vertex_offset, result_index_offset
|
||||||
|
|
||||||
def createMesh(self):
|
def createMesh(self) -> MeshData:
|
||||||
return self.createMeshOrJumps(True)
|
return self.createMeshOrJumps(True)
|
||||||
|
|
||||||
def createJumps(self):
|
def createJumps(self) -> MeshData:
|
||||||
return self.createMeshOrJumps(False)
|
return self.createMeshOrJumps(False)
|
||||||
|
|
||||||
# Defines the two triplets of local point indices to use to draw the two faces for each line segment in createMeshOrJump
|
# Defines the two triplets of local point indices to use to draw the two faces for each line segment in createMeshOrJump
|
||||||
__index_pattern = numpy.array([[0, 3, 2, 0, 1, 3]], dtype = numpy.int32 )
|
__index_pattern = numpy.array([[0, 3, 2, 0, 1, 3]], dtype = numpy.int32 )
|
||||||
|
|
||||||
def createMeshOrJumps(self, make_mesh):
|
def createMeshOrJumps(self, make_mesh: bool) -> MeshData:
|
||||||
builder = MeshBuilder()
|
builder = MeshBuilder()
|
||||||
|
|
||||||
line_count = 0
|
line_count = 0
|
||||||
@ -79,14 +85,14 @@ class Layer:
|
|||||||
for polygon in self._polygons:
|
for polygon in self._polygons:
|
||||||
line_count += polygon.jumpCount
|
line_count += polygon.jumpCount
|
||||||
|
|
||||||
# Reserve the neccesary space for the data upfront
|
# Reserve the necessary space for the data upfront
|
||||||
builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
|
builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
|
||||||
|
|
||||||
for polygon in self._polygons:
|
for polygon in self._polygons:
|
||||||
# Filter out the types of lines we are not interesed in depending on whether we are drawing the mesh or the jumps.
|
# Filter out the types of lines we are not interested in depending on whether we are drawing the mesh or the jumps.
|
||||||
index_mask = numpy.logical_not(polygon.jumpMask) if make_mesh else polygon.jumpMask
|
index_mask = numpy.logical_not(polygon.jumpMask) if make_mesh else polygon.jumpMask
|
||||||
|
|
||||||
# Create an array with rows [p p+1] and only keep those we whant to draw based on make_mesh
|
# Create an array with rows [p p+1] and only keep those we want to draw based on make_mesh
|
||||||
points = numpy.concatenate((polygon.data[:-1], polygon.data[1:]), 1)[index_mask.ravel()]
|
points = numpy.concatenate((polygon.data[:-1], polygon.data[1:]), 1)[index_mask.ravel()]
|
||||||
# Line types of the points we want to draw
|
# Line types of the points we want to draw
|
||||||
line_types = polygon.types[index_mask]
|
line_types = polygon.types[index_mask]
|
||||||
|
@ -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)
|
||||||
@ -61,19 +61,19 @@ class LayerPolygon:
|
|||||||
|
|
||||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||||
# Should be generated in better way, not hardcoded.
|
# Should be generated in better way, not hardcoded.
|
||||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool)
|
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
|
||||||
|
|
||||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||||
|
|
||||||
def buildCache(self) -> None:
|
def buildCache(self) -> None:
|
||||||
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
# For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
|
||||||
self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype=bool)
|
self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype = bool)
|
||||||
mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
|
mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
|
||||||
self._index_begin = 0
|
self._index_begin = 0
|
||||||
self._index_end = mesh_line_count
|
self._index_end = mesh_line_count
|
||||||
|
|
||||||
self._build_cache_needed_points = numpy.ones((len(self._types), 2), dtype=numpy.bool)
|
self._build_cache_needed_points = numpy.ones((len(self._types), 2), dtype = numpy.bool)
|
||||||
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
# Only if the type of line segment changes do we need to add an extra vertex to change colors
|
||||||
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
|
||||||
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
# Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
|
||||||
@ -136,9 +136,9 @@ class LayerPolygon:
|
|||||||
self._index_begin += index_offset
|
self._index_begin += index_offset
|
||||||
self._index_end += index_offset
|
self._index_end += index_offset
|
||||||
|
|
||||||
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype=numpy.int32).reshape((-1, 1))
|
indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype = numpy.int32).reshape((-1, 1))
|
||||||
# When the line type changes the index needs to be increased by 2.
|
# When the line type changes the index needs to be increased by 2.
|
||||||
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype=numpy.int32).reshape((-1, 1))
|
indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
|
||||||
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
# Each line segment goes from it's starting point p to p+1, offset by the vertex index.
|
||||||
# The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
# The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
|
||||||
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
|
||||||
@ -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()
|
||||||
|
@ -127,7 +127,7 @@ class MachineErrorChecker(QObject):
|
|||||||
|
|
||||||
# Populate the (stack, key) tuples to check
|
# Populate the (stack, key) tuples to check
|
||||||
self._stacks_and_keys_to_check = deque()
|
self._stacks_and_keys_to_check = deque()
|
||||||
for stack in [global_stack] + list(global_stack.extruders.values()):
|
for stack in global_stack.extruders.values():
|
||||||
for key in stack.getAllKeys():
|
for key in stack.getAllKeys():
|
||||||
self._stacks_and_keys_to_check.append((stack, key))
|
self._stacks_and_keys_to_check.append((stack, key))
|
||||||
|
|
||||||
@ -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
|
||||||
@ -103,6 +103,8 @@ class MaterialManager(QObject):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
root_material_id = material_metadata.get("base_file", "")
|
root_material_id = material_metadata.get("base_file", "")
|
||||||
|
if root_material_id not in material_metadatas: #Not a registered material profile. Don't store this in the look-up tables.
|
||||||
|
continue
|
||||||
if root_material_id not in self._material_group_map:
|
if root_material_id not in self._material_group_map:
|
||||||
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
||||||
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
||||||
@ -118,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)
|
||||||
@ -200,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)
|
||||||
|
|
||||||
|
@ -3,14 +3,17 @@
|
|||||||
|
|
||||||
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
|
from typing import Callable, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Util import parseBool
|
||||||
|
from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt5.QtCore import QObject
|
||||||
|
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +47,10 @@ class DiscoveredPrinter(QObject):
|
|||||||
self._name = name
|
self._name = name
|
||||||
self.nameChanged.emit()
|
self.nameChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, constant = True)
|
||||||
|
def address(self) -> str:
|
||||||
|
return self._ip_address
|
||||||
|
|
||||||
machineTypeChanged = pyqtSignal()
|
machineTypeChanged = pyqtSignal()
|
||||||
|
|
||||||
@pyqtProperty(str, notify = machineTypeChanged)
|
@pyqtProperty(str, notify = machineTypeChanged)
|
||||||
@ -55,23 +62,61 @@ 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:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
readable_type = CuraApplication.getInstance().getMachineManager().getMachineTypeNameFromId(self._machine_type)
|
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
if not readable_type:
|
# In LocalClusterOutputDevice, when it updates a printer information, it updates the machine type using the field
|
||||||
readable_type = catalog.i18nc("@label", "Unknown")
|
# "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.
|
||||||
|
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||||
|
readable_type = self._machine_type
|
||||||
|
else:
|
||||||
|
readable_type = self._getMachineTypeNameFromId(self._machine_type)
|
||||||
|
if not readable_type:
|
||||||
|
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:
|
||||||
return self.readableMachineType.lower() == "unknown"
|
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||||
|
readable_type = self._machine_type
|
||||||
|
else:
|
||||||
|
readable_type = self._getMachineTypeNameFromId(self._machine_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
|
||||||
|
|
||||||
|
@pyqtProperty(bool, constant = True)
|
||||||
|
def isHostOfGroup(self) -> bool:
|
||||||
|
return getattr(self._device, "clusterSize", 1) > 0
|
||||||
|
|
||||||
|
@pyqtProperty(str, constant = True)
|
||||||
|
def sectionName(self) -> str:
|
||||||
|
if self.isUnknownMachineType or not self.isHostOfGroup:
|
||||||
|
return catalog.i18nc("@label", "The printer(s) below cannot be connected because they are part of a group")
|
||||||
|
else:
|
||||||
|
return catalog.i18nc("@label", "Available networked printers")
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Discovered printers are all the printers that were found on the network, which provide a more convenient way
|
# Discovered printers are all the printers that were found on the network, which provide a more convenient way
|
||||||
@ -80,18 +125,105 @@ class DiscoveredPrinter(QObject):
|
|||||||
#
|
#
|
||||||
class DiscoveredPrintersModel(QObject):
|
class DiscoveredPrintersModel(QObject):
|
||||||
|
|
||||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._application = application
|
||||||
self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
|
self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
|
||||||
|
|
||||||
|
self._plugin_for_manual_device = None # type: Optional[OutputDevicePlugin]
|
||||||
|
self._manual_device_address = ""
|
||||||
|
|
||||||
|
self._manual_device_request_timeout_in_seconds = 5 # timeout for adding a manual device in seconds
|
||||||
|
self._manual_device_request_timer = QTimer()
|
||||||
|
self._manual_device_request_timer.setInterval(self._manual_device_request_timeout_in_seconds * 1000)
|
||||||
|
self._manual_device_request_timer.setSingleShot(True)
|
||||||
|
self._manual_device_request_timer.timeout.connect(self._onManualRequestTimeout)
|
||||||
|
|
||||||
discoveredPrintersChanged = pyqtSignal()
|
discoveredPrintersChanged = pyqtSignal()
|
||||||
|
|
||||||
@pyqtProperty(list, notify = discoveredPrintersChanged)
|
@pyqtSlot(str)
|
||||||
|
def checkManualDevice(self, address: str) -> None:
|
||||||
|
if self.hasManualDeviceRequestInProgress:
|
||||||
|
Logger.log("i", "A manual device request for address [%s] is still in progress, do nothing",
|
||||||
|
self._manual_device_address)
|
||||||
|
return
|
||||||
|
|
||||||
|
priority_order = [
|
||||||
|
ManualDeviceAdditionAttempt.PRIORITY,
|
||||||
|
ManualDeviceAdditionAttempt.POSSIBLE,
|
||||||
|
] # type: List[ManualDeviceAdditionAttempt]
|
||||||
|
|
||||||
|
all_plugins_dict = self._application.getOutputDeviceManager().getAllOutputDevicePlugins()
|
||||||
|
|
||||||
|
can_add_manual_plugins = [item for item in filter(
|
||||||
|
lambda plugin_item: plugin_item.canAddManualDevice(address) in priority_order,
|
||||||
|
all_plugins_dict.values())]
|
||||||
|
|
||||||
|
if not can_add_manual_plugins:
|
||||||
|
Logger.log("d", "Could not find a plugin to accept adding %s manually via address.", address)
|
||||||
|
return
|
||||||
|
|
||||||
|
plugin = max(can_add_manual_plugins, key = lambda p: priority_order.index(p.canAddManualDevice(address)))
|
||||||
|
self._plugin_for_manual_device = plugin
|
||||||
|
self._plugin_for_manual_device.addManualDevice(address, callback = self._onManualDeviceRequestFinished)
|
||||||
|
self._manual_device_address = address
|
||||||
|
self._manual_device_request_timer.start()
|
||||||
|
self.hasManualDeviceRequestInProgressChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def cancelCurrentManualDeviceRequest(self) -> None:
|
||||||
|
self._manual_device_request_timer.stop()
|
||||||
|
|
||||||
|
if self._manual_device_address:
|
||||||
|
if self._plugin_for_manual_device is not None:
|
||||||
|
self._plugin_for_manual_device.removeManualDevice(self._manual_device_address, address = self._manual_device_address)
|
||||||
|
self._manual_device_address = ""
|
||||||
|
self._plugin_for_manual_device = None
|
||||||
|
self.hasManualDeviceRequestInProgressChanged.emit()
|
||||||
|
self.manualDeviceRequestFinished.emit(False)
|
||||||
|
|
||||||
|
def _onManualRequestTimeout(self) -> None:
|
||||||
|
Logger.log("w", "Manual printer [%s] request timed out. Cancel the current request.", self._manual_device_address)
|
||||||
|
self.cancelCurrentManualDeviceRequest()
|
||||||
|
|
||||||
|
hasManualDeviceRequestInProgressChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = hasManualDeviceRequestInProgressChanged)
|
||||||
|
def hasManualDeviceRequestInProgress(self) -> bool:
|
||||||
|
return self._manual_device_address != ""
|
||||||
|
|
||||||
|
manualDeviceRequestFinished = pyqtSignal(bool, arguments = ["success"])
|
||||||
|
|
||||||
|
def _onManualDeviceRequestFinished(self, success: bool, address: str) -> None:
|
||||||
|
self._manual_device_request_timer.stop()
|
||||||
|
if address == self._manual_device_address:
|
||||||
|
self._manual_device_address = ""
|
||||||
|
self.hasManualDeviceRequestInProgressChanged.emit()
|
||||||
|
self.manualDeviceRequestFinished.emit(success)
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantMap", notify = discoveredPrintersChanged)
|
||||||
|
def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
|
||||||
|
return self._discovered_printer_by_ip_dict
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = discoveredPrintersChanged)
|
||||||
def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
|
def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
|
||||||
item_list = list(x for x in self._discovered_printer_by_ip_dict.values())
|
item_list = list(
|
||||||
item_list.sort(key = lambda x: x.device.name)
|
x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary")))
|
||||||
return item_list
|
|
||||||
|
# Split the printers into 2 lists and sort them ascending based on names.
|
||||||
|
available_list = []
|
||||||
|
not_available_list = []
|
||||||
|
for item in item_list:
|
||||||
|
if item.isUnknownMachineType or getattr(item.device, "clusterSize", 1) < 1:
|
||||||
|
not_available_list.append(item)
|
||||||
|
else:
|
||||||
|
available_list.append(item)
|
||||||
|
|
||||||
|
available_list.sort(key = lambda x: x.device.name)
|
||||||
|
not_available_list.sort(key = lambda x: x.device.name)
|
||||||
|
|
||||||
|
return available_list + not_available_list
|
||||||
|
|
||||||
def addDiscoveredPrinter(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None],
|
def addDiscoveredPrinter(self, ip_address: str, key: str, name: str, create_callback: Callable[[str], None],
|
||||||
machine_type: str, device: "NetworkedPrinterOutputDevice") -> None:
|
machine_type: str, device: "NetworkedPrinterOutputDevice") -> None:
|
||||||
@ -130,11 +262,3 @@ class DiscoveredPrintersModel(QObject):
|
|||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
|
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
|
||||||
discovered_printer.create_callback(discovered_printer.getKey())
|
discovered_printer.create_callback(discovered_printer.getKey())
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def createMachineFromDiscoveredPrinterAddress(self, ip_address: str) -> None:
|
|
||||||
if ip_address not in self._discovered_printer_by_ip_dict:
|
|
||||||
Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.createMachineFromDiscoveredPrinter(self._discovered_printer_by_ip_dict[ip_address])
|
|
||||||
|
@ -35,6 +35,8 @@ class FirstStartMachineActionsModel(ListModel):
|
|||||||
self._application = application
|
self._application = application
|
||||||
self._application.initializationFinished.connect(self._initialize)
|
self._application.initializationFinished.connect(self._initialize)
|
||||||
|
|
||||||
|
self._previous_global_stack = None
|
||||||
|
|
||||||
def _initialize(self) -> None:
|
def _initialize(self) -> None:
|
||||||
self._application.getMachineManager().globalContainerChanged.connect(self._update)
|
self._application.getMachineManager().globalContainerChanged.connect(self._update)
|
||||||
self._update()
|
self._update()
|
||||||
@ -86,13 +88,19 @@ class FirstStartMachineActionsModel(ListModel):
|
|||||||
self.setItems([])
|
self.setItems([])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Do not update if the machine has not been switched. This can cause the SettingProviders on the Machine
|
||||||
|
# Setting page to do a force update, but they can use potential outdated cached values.
|
||||||
|
if self._previous_global_stack is not None and global_stack.getId() == self._previous_global_stack.getId():
|
||||||
|
return
|
||||||
|
self._previous_global_stack = global_stack
|
||||||
|
|
||||||
definition_id = global_stack.definition.getId()
|
definition_id = global_stack.definition.getId()
|
||||||
first_start_actions = self._application.getMachineActionManager().getFirstStartActions(definition_id)
|
first_start_actions = self._application.getMachineActionManager().getFirstStartActions(definition_id)
|
||||||
|
|
||||||
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()
|
||||||
|
@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QTimer
|
|||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
@ -54,7 +55,6 @@ class GlobalStacksModel(ListModel):
|
|||||||
items = []
|
items = []
|
||||||
|
|
||||||
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||||
|
|
||||||
for container_stack in container_stacks:
|
for container_stack in container_stacks:
|
||||||
has_remote_connection = False
|
has_remote_connection = False
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class GlobalStacksModel(ListModel):
|
|||||||
has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
|
has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
|
||||||
ConnectionType.CloudConnection.value]
|
ConnectionType.CloudConnection.value]
|
||||||
|
|
||||||
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
|
if parseBool(container_stack.getMetaDataEntry("hidden", False)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
section_name = "Network enabled printers" if has_remote_connection else "Local printers"
|
section_name = "Network enabled printers" if has_remote_connection else "Local printers"
|
||||||
@ -73,5 +73,5 @@ class GlobalStacksModel(ListModel):
|
|||||||
"hasRemoteConnection": has_remote_connection,
|
"hasRemoteConnection": has_remote_connection,
|
||||||
"metadata": container_stack.getMetaData().copy(),
|
"metadata": container_stack.getMetaData().copy(),
|
||||||
"discoverySource": section_name})
|
"discoverySource": section_name})
|
||||||
items.sort(key = lambda i: not i["hasRemoteConnection"])
|
items.sort(key = lambda i: (not i["hasRemoteConnection"], i["name"]))
|
||||||
self.setItems(items)
|
self.setItems(items)
|
||||||
|
@ -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
|
||||||
|
@ -85,7 +85,14 @@ class VariantManager:
|
|||||||
if variant_definition not in self._machine_to_buildplate_dict_map:
|
if variant_definition not in self._machine_to_buildplate_dict_map:
|
||||||
self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
|
self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
|
||||||
|
|
||||||
variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0]
|
try:
|
||||||
|
variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])[0]
|
||||||
|
except IndexError as e:
|
||||||
|
# It still needs to break, but we want to know what variant ID made it break.
|
||||||
|
msg = "Unable to find build plate variant with the id [%s]" % variant_metadata["id"]
|
||||||
|
Logger.logException("e", msg)
|
||||||
|
raise IndexError(msg)
|
||||||
|
|
||||||
buildplate_type = variant_container.getProperty("machine_buildplate_type", "value")
|
buildplate_type = variant_container.getProperty("machine_buildplate_type", "value")
|
||||||
if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
|
if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
|
||||||
self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
|
self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
|
||||||
|
@ -50,6 +50,7 @@ class AuthorizationHelpers:
|
|||||||
# \param refresh_token:
|
# \param refresh_token:
|
||||||
# \return An AuthenticationResponse object.
|
# \return An AuthenticationResponse object.
|
||||||
def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
|
def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
|
||||||
|
Logger.log("d", "Refreshing the access token.")
|
||||||
data = {
|
data = {
|
||||||
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
||||||
"redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
|
"redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
|
||||||
|
@ -34,6 +34,8 @@ class AuthorizationService:
|
|||||||
# Emit signal when authentication failed.
|
# Emit signal when authentication failed.
|
||||||
onAuthenticationError = Signal()
|
onAuthenticationError = Signal()
|
||||||
|
|
||||||
|
accessTokenChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, settings: "OAuth2Settings", preferences: Optional["Preferences"] = None) -> None:
|
def __init__(self, settings: "OAuth2Settings", preferences: Optional["Preferences"] = None) -> None:
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._auth_helpers = AuthorizationHelpers(settings)
|
self._auth_helpers = AuthorizationHelpers(settings)
|
||||||
@ -68,6 +70,7 @@ class AuthorizationService:
|
|||||||
self._user_profile = self._parseJWT()
|
self._user_profile = self._parseJWT()
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
# Unable to get connection, can't login.
|
# Unable to get connection, can't login.
|
||||||
|
Logger.logException("w", "Unable to validate user data with the remote server.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not self._user_profile and self._auth_data:
|
if not self._user_profile and self._auth_data:
|
||||||
@ -83,6 +86,7 @@ class AuthorizationService:
|
|||||||
def _parseJWT(self) -> Optional["UserProfile"]:
|
def _parseJWT(self) -> Optional["UserProfile"]:
|
||||||
if not self._auth_data or self._auth_data.access_token is None:
|
if not self._auth_data or self._auth_data.access_token is None:
|
||||||
# If no auth data exists, we should always log in again.
|
# If no auth data exists, we should always log in again.
|
||||||
|
Logger.log("d", "There was no auth data or access token")
|
||||||
return None
|
return None
|
||||||
user_data = self._auth_helpers.parseJWT(self._auth_data.access_token)
|
user_data = self._auth_helpers.parseJWT(self._auth_data.access_token)
|
||||||
if user_data:
|
if user_data:
|
||||||
@ -90,12 +94,16 @@ class AuthorizationService:
|
|||||||
return user_data
|
return user_data
|
||||||
# The JWT was expired or invalid and we should request a new one.
|
# The JWT was expired or invalid and we should request a new one.
|
||||||
if self._auth_data.refresh_token is None:
|
if self._auth_data.refresh_token is None:
|
||||||
|
Logger.log("w", "There was no refresh token in the auth data.")
|
||||||
return None
|
return None
|
||||||
self._auth_data = self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token)
|
self._auth_data = self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token)
|
||||||
if not self._auth_data or self._auth_data.access_token is None:
|
if not self._auth_data or self._auth_data.access_token is None:
|
||||||
|
Logger.log("w", "Unable to use the refresh token to get a new access token.")
|
||||||
# The token could not be refreshed using the refresh token. We should login again.
|
# The token could not be refreshed using the refresh token. We should login again.
|
||||||
return None
|
return None
|
||||||
|
# Ensure it gets stored as otherwise we only have it in memory. The stored refresh token has been deleted
|
||||||
|
# from the server already.
|
||||||
|
self._storeAuthData(self._auth_data)
|
||||||
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
||||||
|
|
||||||
## Get the access token as provided by the repsonse data.
|
## Get the access token as provided by the repsonse data.
|
||||||
@ -124,6 +132,7 @@ class AuthorizationService:
|
|||||||
self._storeAuthData(response)
|
self._storeAuthData(response)
|
||||||
self.onAuthStateChanged.emit(logged_in = True)
|
self.onAuthStateChanged.emit(logged_in = True)
|
||||||
else:
|
else:
|
||||||
|
Logger.log("w", "Failed to get a new access token from the server.")
|
||||||
self.onAuthStateChanged.emit(logged_in = False)
|
self.onAuthStateChanged.emit(logged_in = False)
|
||||||
|
|
||||||
## Delete the authentication data that we have stored locally (eg; logout)
|
## Delete the authentication data that we have stored locally (eg; logout)
|
||||||
@ -194,6 +203,7 @@ class AuthorizationService:
|
|||||||
|
|
||||||
## Store authentication data in preferences.
|
## Store authentication data in preferences.
|
||||||
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
|
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
|
||||||
|
Logger.log("d", "Attempting to store the auth data")
|
||||||
if self._preferences is None:
|
if self._preferences is None:
|
||||||
Logger.log("e", "Unable to save authentication data, since no preference has been set!")
|
Logger.log("e", "Unable to save authentication data, since no preference has been set!")
|
||||||
return
|
return
|
||||||
@ -206,6 +216,8 @@ class AuthorizationService:
|
|||||||
self._user_profile = None
|
self._user_profile = None
|
||||||
self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY)
|
self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY)
|
||||||
|
|
||||||
|
self.accessTokenChanged.emit()
|
||||||
|
|
||||||
def _onMessageActionTriggered(self, _, action):
|
def _onMessageActionTriggered(self, _, action):
|
||||||
if action == "retry":
|
if action == "retry":
|
||||||
self.loadAuthDataFromPreferences()
|
self.loadAuthDataFromPreferences()
|
||||||
|
@ -49,18 +49,20 @@ class PlatformPhysics:
|
|||||||
return
|
return
|
||||||
|
|
||||||
root = self._controller.getScene().getRoot()
|
root = self._controller.getScene().getRoot()
|
||||||
|
build_volume = Application.getInstance().getBuildVolume()
|
||||||
|
build_volume.updateNodeBoundaryCheck()
|
||||||
|
|
||||||
# Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
|
# Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
|
||||||
# same direction.
|
# same direction.
|
||||||
transformed_nodes = []
|
transformed_nodes = []
|
||||||
|
|
||||||
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
|
|
||||||
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
|
|
||||||
nodes = list(BreadthFirstIterator(root))
|
nodes = list(BreadthFirstIterator(root))
|
||||||
|
|
||||||
# Only check nodes inside build area.
|
# Only check nodes inside build area.
|
||||||
nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)]
|
nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)]
|
||||||
|
|
||||||
|
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
|
||||||
|
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
|
||||||
random.shuffle(nodes)
|
random.shuffle(nodes)
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
|
if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None:
|
||||||
@ -76,7 +78,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
|
||||||
@ -160,7 +162,6 @@ class PlatformPhysics:
|
|||||||
op.push()
|
op.push()
|
||||||
|
|
||||||
# After moving, we have to evaluate the boundary checks for nodes
|
# After moving, we have to evaluate the boundary checks for nodes
|
||||||
build_volume = Application.getInstance().getBuildVolume()
|
|
||||||
build_volume.updateNodeBoundaryCheck()
|
build_volume.updateNodeBoundaryCheck()
|
||||||
|
|
||||||
def _onToolOperationStarted(self, tool):
|
def _onToolOperationStarted(self, tool):
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING, cast
|
||||||
|
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
@ -12,6 +13,7 @@ from UM.View.RenderBatch import RenderBatch
|
|||||||
|
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||||
@ -44,9 +46,9 @@ class PreviewPass(RenderPass):
|
|||||||
|
|
||||||
self._renderer = Application.getInstance().getRenderer()
|
self._renderer = Application.getInstance().getRenderer()
|
||||||
|
|
||||||
self._shader = None #type: Optional[ShaderProgram]
|
self._shader = None # type: Optional[ShaderProgram]
|
||||||
self._non_printing_shader = None #type: Optional[ShaderProgram]
|
self._non_printing_shader = None # type: Optional[ShaderProgram]
|
||||||
self._support_mesh_shader = None #type: Optional[ShaderProgram]
|
self._support_mesh_shader = None # type: Optional[ShaderProgram]
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
|
|
||||||
# Set the camera to be used by this render pass
|
# Set the camera to be used by this render pass
|
||||||
@ -83,30 +85,31 @@ class PreviewPass(RenderPass):
|
|||||||
batch_support_mesh = RenderBatch(self._support_mesh_shader)
|
batch_support_mesh = RenderBatch(self._support_mesh_shader)
|
||||||
|
|
||||||
# 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()):
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
if hasattr(node, "_outside_buildarea") and not getattr(node, "_outside_buildarea"):
|
||||||
per_mesh_stack = node.callDecoration("getStack")
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
||||||
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
per_mesh_stack = node.callDecoration("getStack")
|
||||||
# Non printing mesh
|
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||||
continue
|
# Non printing mesh
|
||||||
elif per_mesh_stack is not None and per_mesh_stack.getProperty("support_mesh", "value"):
|
continue
|
||||||
# Support mesh
|
elif per_mesh_stack is not None and per_mesh_stack.getProperty("support_mesh", "value"):
|
||||||
uniforms = {}
|
# Support mesh
|
||||||
shade_factor = 0.6
|
uniforms = {}
|
||||||
diffuse_color = node.getDiffuseColor()
|
shade_factor = 0.6
|
||||||
diffuse_color2 = [
|
diffuse_color = cast(CuraSceneNode, node).getDiffuseColor()
|
||||||
diffuse_color[0] * shade_factor,
|
diffuse_color2 = [
|
||||||
diffuse_color[1] * shade_factor,
|
diffuse_color[0] * shade_factor,
|
||||||
diffuse_color[2] * shade_factor,
|
diffuse_color[1] * shade_factor,
|
||||||
1.0]
|
diffuse_color[2] * shade_factor,
|
||||||
uniforms["diffuse_color"] = prettier_color(diffuse_color)
|
1.0]
|
||||||
uniforms["diffuse_color_2"] = diffuse_color2
|
uniforms["diffuse_color"] = prettier_color(diffuse_color)
|
||||||
batch_support_mesh.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
|
uniforms["diffuse_color_2"] = diffuse_color2
|
||||||
else:
|
batch_support_mesh.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
|
||||||
# Normal scene node
|
else:
|
||||||
uniforms = {}
|
# Normal scene node
|
||||||
uniforms["diffuse_color"] = prettier_color(node.getDiffuseColor())
|
uniforms = {}
|
||||||
batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
|
uniforms["diffuse_color"] = prettier_color(cast(CuraSceneNode, node).getDiffuseColor())
|
||||||
|
batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
|
||||||
|
|
||||||
self.bind()
|
self.bind()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -62,7 +62,24 @@ class ExtruderConfigurationModel(QObject):
|
|||||||
return " ".join(message_chunks)
|
return " ".join(message_chunks)
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
return hash(self) == hash(other)
|
if not isinstance(other, ExtruderConfigurationModel):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._position != other.position:
|
||||||
|
return False
|
||||||
|
# Empty materials should be ignored for comparison
|
||||||
|
if self.activeMaterial is not None and other.activeMaterial is not None:
|
||||||
|
if self.activeMaterial.guid != other.activeMaterial.guid:
|
||||||
|
if self.activeMaterial.guid != "" and other.activeMaterial.guid != "":
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# At this point there is no material, so it doesn't matter what the hotend is.
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.hotendID != other.hotendID:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
||||||
# unique within a set
|
# unique within a set
|
||||||
|
@ -71,7 +71,23 @@ class PrinterConfigurationModel(QObject):
|
|||||||
return "\n".join(message_chunks)
|
return "\n".join(message_chunks)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return hash(self) == hash(other)
|
if not isinstance(other, PrinterConfigurationModel):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.printerType != other.printerType:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.buildplateConfiguration != other.buildplateConfiguration:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(self.extruderConfigurations) != len(other.extruderConfigurations):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for self_extruder, other_extruder in zip(sorted(self._extruder_configurations, key=lambda x: x.position), sorted(other.extruderConfigurations, key=lambda x: x.position)):
|
||||||
|
if self_extruder != other_extruder:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
|
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
|
||||||
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
|
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
|
||||||
|
@ -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
|
4
cura/PrinterOutput/PrintJobOutputModel.py
Normal file
4
cura/PrinterOutput/PrintJobOutputModel.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import warnings
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
4
cura/PrinterOutput/PrinterOutputModel.py
Normal file
4
cura/PrinterOutput/PrinterOutputModel.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import warnings
|
||||||
|
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
|
||||||
|
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
4
cura/PrinterOutputDevice.py
Normal file
4
cura/PrinterOutputDevice.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import warnings
|
||||||
|
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.
|
||||||
|
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
|
||||||
@ -6,13 +6,14 @@ from typing import cast, Dict, List, Optional
|
|||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
from UM.Math.Polygon import Polygon #For typing.
|
from UM.Math.Polygon import Polygon # For typing.
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator #To cast the deepcopy of every decorator back to SceneNodeDecorator.
|
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator # To cast the deepcopy of every decorator back to SceneNodeDecorator.
|
||||||
|
|
||||||
|
import cura.CuraApplication # To get the build plate.
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack # For typing.
|
||||||
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator # For per-object settings.
|
||||||
|
|
||||||
import cura.CuraApplication #To get the build plate.
|
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack #For typing.
|
|
||||||
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.
|
||||||
@ -20,7 +21,7 @@ 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:
|
||||||
@ -58,7 +59,7 @@ class CuraSceneNode(SceneNode):
|
|||||||
if extruder_id is not None:
|
if extruder_id is not None:
|
||||||
if extruder_id == extruder.getId():
|
if extruder_id == extruder.getId():
|
||||||
return extruder
|
return extruder
|
||||||
else: # If the id is unknown, then return the extruder in the position 0
|
else: # If the id is unknown, then return the extruder in the position 0
|
||||||
try:
|
try:
|
||||||
if extruder.getMetaDataEntry("position", default = "0") == "0": # Check if the position is zero
|
if extruder.getMetaDataEntry("position", default = "0") == "0": # Check if the position is zero
|
||||||
return extruder
|
return extruder
|
||||||
@ -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:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import os
|
import os
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, Union, Any, TYPE_CHECKING, List
|
from typing import Dict, Union, Any, TYPE_CHECKING, List, cast
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl
|
from PyQt5.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
@ -438,7 +438,7 @@ class ContainerManager(QObject):
|
|||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
|
|
||||||
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
|
container_list = [cast(InstanceContainer, n.getContainer()) for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
|
||||||
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
||||||
|
|
||||||
__instance = None # type: ContainerManager
|
__instance = None # type: ContainerManager
|
||||||
|
@ -5,10 +5,11 @@ import os
|
|||||||
import re
|
import re
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from typing import Any, cast, Dict, Optional
|
from typing import Any, cast, Dict, Optional, List, Union
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
from UM.PluginObject import PluginObject
|
||||||
from UM.Settings.ContainerFormatError import ContainerFormatError
|
from UM.Settings.ContainerFormatError import ContainerFormatError
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
@ -22,6 +23,7 @@ from UM.Platform import Platform
|
|||||||
from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with.
|
from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with.
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
from cura.ReaderWriters.ProfileWriter import ProfileWriter
|
||||||
|
|
||||||
from . import ExtruderStack
|
from . import ExtruderStack
|
||||||
from . import GlobalStack
|
from . import GlobalStack
|
||||||
@ -50,10 +52,10 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
# This will also try to convert a ContainerStack to either Extruder or
|
# This will also try to convert a ContainerStack to either Extruder or
|
||||||
# Global stack based on metadata information.
|
# Global stack based on metadata information.
|
||||||
@override(ContainerRegistry)
|
@override(ContainerRegistry)
|
||||||
def addContainer(self, container):
|
def addContainer(self, container: ContainerInterface) -> None:
|
||||||
# Note: Intentional check with type() because we want to ignore subclasses
|
# Note: Intentional check with type() because we want to ignore subclasses
|
||||||
if type(container) == ContainerStack:
|
if type(container) == ContainerStack:
|
||||||
container = self._convertContainerStack(container)
|
container = self._convertContainerStack(cast(ContainerStack, container))
|
||||||
|
|
||||||
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
||||||
# Check against setting version of the definition.
|
# Check against setting version of the definition.
|
||||||
@ -61,7 +63,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
||||||
if required_setting_version != actual_setting_version:
|
if required_setting_version != actual_setting_version:
|
||||||
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
|
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
|
||||||
return #Don't add.
|
return # Don't add.
|
||||||
|
|
||||||
super().addContainer(container)
|
super().addContainer(container)
|
||||||
|
|
||||||
@ -71,9 +73,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
# \param new_name \type{string} Base name, which may not be unique
|
# \param new_name \type{string} Base name, which may not be unique
|
||||||
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
|
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
|
||||||
# \return \type{string} Name that is unique for the specified type and name/id
|
# \return \type{string} Name that is unique for the specified type and name/id
|
||||||
def createUniqueName(self, container_type, current_name, new_name, fallback_name):
|
def createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str:
|
||||||
new_name = new_name.strip()
|
new_name = new_name.strip()
|
||||||
num_check = re.compile("(.*?)\s*#\d+$").match(new_name)
|
num_check = re.compile(r"(.*?)\s*#\d+$").match(new_name)
|
||||||
if num_check:
|
if num_check:
|
||||||
new_name = num_check.group(1)
|
new_name = num_check.group(1)
|
||||||
if new_name == "":
|
if new_name == "":
|
||||||
@ -92,7 +94,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
# Both the id and the name are checked, because they may not be the same and it is better if they are both unique
|
# Both the id and the name are checked, because they may not be the same and it is better if they are both unique
|
||||||
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
||||||
# \param container_name \type{string} Name to check
|
# \param container_name \type{string} Name to check
|
||||||
def _containerExists(self, container_type, container_name):
|
def _containerExists(self, container_type: str, container_name: str):
|
||||||
container_class = ContainerStack if container_type == "machine" else InstanceContainer
|
container_class = ContainerStack if container_type == "machine" else InstanceContainer
|
||||||
|
|
||||||
return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \
|
return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \
|
||||||
@ -100,16 +102,17 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
|
|
||||||
## Exports an profile to a file
|
## Exports an profile to a file
|
||||||
#
|
#
|
||||||
# \param instance_ids \type{list} the IDs of the profiles to export.
|
# \param container_list \type{list} the containers 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: List[InstanceContainer], file_name: str, file_type: str) -> 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,10 +124,12 @@ 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:
|
||||||
|
if profile_writer is None:
|
||||||
|
raise Exception("Unable to find a profile writer")
|
||||||
success = profile_writer.write(file_name, container_list)
|
success = profile_writer.write(file_name, container_list)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
||||||
@ -132,23 +137,24 @@ 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
|
||||||
# \param description
|
# \param description
|
||||||
# \return The plugin object matching the given extension and description.
|
# \return The plugin object matching the given extension and description.
|
||||||
def _findProfileWriter(self, extension, description):
|
def _findProfileWriter(self, extension: str, description: str) -> Optional[ProfileWriter]:
|
||||||
plugin_registry = PluginRegistry.getInstance()
|
plugin_registry = PluginRegistry.getInstance()
|
||||||
for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
|
for plugin_id, meta_data in self._getIOPlugins("profile_writer"):
|
||||||
for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
|
for supported_type in meta_data["profile_writer"]: # All file types this plugin can supposedly write.
|
||||||
@ -156,7 +162,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
if supported_extension == extension: # This plugin supports a file type with the same extension.
|
if supported_extension == extension: # This plugin supports a file type with the same extension.
|
||||||
supported_description = supported_type.get("description", None)
|
supported_description = supported_type.get("description", None)
|
||||||
if supported_description == description: # The description is also identical. Assume it's the same file type.
|
if supported_description == description: # The description is also identical. Assume it's the same file type.
|
||||||
return plugin_registry.getPluginObject(plugin_id)
|
return cast(ProfileWriter, plugin_registry.getPluginObject(plugin_id))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
## Imports a profile from a file
|
## Imports a profile from a file
|
||||||
@ -169,9 +175,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 +183,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
|
||||||
@ -281,7 +287,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
profile.addInstance(new_instance)
|
profile.addInstance(new_instance)
|
||||||
profile.setDirty(True)
|
profile.setDirty(True)
|
||||||
|
|
||||||
global_profile.removeInstance(qc_setting_key, postpone_emit=True)
|
global_profile.removeInstance(qc_setting_key, postpone_emit = True)
|
||||||
extruder_profiles.append(profile)
|
extruder_profiles.append(profile)
|
||||||
|
|
||||||
for profile in extruder_profiles:
|
for profile in extruder_profiles:
|
||||||
@ -322,7 +328,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
|
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
|
||||||
|
|
||||||
@override(ContainerRegistry)
|
@override(ContainerRegistry)
|
||||||
def load(self):
|
def load(self) -> None:
|
||||||
super().load()
|
super().load()
|
||||||
self._registerSingleExtrusionMachinesExtruderStacks()
|
self._registerSingleExtrusionMachinesExtruderStacks()
|
||||||
self._connectUpgradedExtruderStacksToMachines()
|
self._connectUpgradedExtruderStacksToMachines()
|
||||||
@ -404,7 +410,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
|
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
|
||||||
def _convertContainerStack(self, container):
|
def _convertContainerStack(self, container: ContainerStack) -> Union[ExtruderStack.ExtruderStack, GlobalStack.GlobalStack]:
|
||||||
assert type(container) == ContainerStack
|
assert type(container) == ContainerStack
|
||||||
|
|
||||||
container_type = container.getMetaDataEntry("type")
|
container_type = container.getMetaDataEntry("type")
|
||||||
@ -428,14 +434,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
|
|
||||||
return new_stack
|
return new_stack
|
||||||
|
|
||||||
def _registerSingleExtrusionMachinesExtruderStacks(self):
|
def _registerSingleExtrusionMachinesExtruderStacks(self) -> None:
|
||||||
machines = self.findContainerStacks(type = "machine", machine_extruder_trains = {"0": "fdmextruder"})
|
machines = self.findContainerStacks(type = "machine", machine_extruder_trains = {"0": "fdmextruder"})
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = machine.getId())
|
extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = machine.getId())
|
||||||
if not extruder_stacks:
|
if not extruder_stacks:
|
||||||
self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder")
|
self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder")
|
||||||
|
|
||||||
def _onContainerAdded(self, container):
|
def _onContainerAdded(self, container: ContainerInterface) -> None:
|
||||||
# We don't have all the machines loaded in the beginning, so in order to add the missing extruder stack
|
# We don't have all the machines loaded in the beginning, so in order to add the missing extruder stack
|
||||||
# for single extrusion machines, we subscribe to the containerAdded signal, and whenever a global stack
|
# for single extrusion machines, we subscribe to the containerAdded signal, and whenever a global stack
|
||||||
# is added, we check to see if an extruder stack needs to be added.
|
# is added, we check to see if an extruder stack needs to be added.
|
||||||
@ -669,7 +675,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
|
|
||||||
return extruder_stack
|
return extruder_stack
|
||||||
|
|
||||||
def _findQualityChangesContainerInCuraFolder(self, name):
|
def _findQualityChangesContainerInCuraFolder(self, name: str) -> Optional[InstanceContainer]:
|
||||||
quality_changes_dir = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QualityChangesInstanceContainer)
|
quality_changes_dir = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QualityChangesInstanceContainer)
|
||||||
|
|
||||||
instance_container = None
|
instance_container = None
|
||||||
@ -682,7 +688,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
parser = configparser.ConfigParser(interpolation = None)
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
try:
|
try:
|
||||||
parser.read([file_path])
|
parser.read([file_path])
|
||||||
except:
|
except Exception:
|
||||||
# Skip, it is not a valid stack file
|
# Skip, it is not a valid stack file
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -714,7 +720,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||||||
# due to problems with loading order, some stacks may not have the proper next stack
|
# due to problems with loading order, some stacks may not have the proper next stack
|
||||||
# set after upgrading, because the proper global stack was not yet loaded. This method
|
# set after upgrading, because the proper global stack was not yet loaded. This method
|
||||||
# makes sure those extruders also get the right stack set.
|
# makes sure those extruders also get the right stack set.
|
||||||
def _connectUpgradedExtruderStacksToMachines(self):
|
def _connectUpgradedExtruderStacksToMachines(self) -> None:
|
||||||
extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack)
|
extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack)
|
||||||
for extruder_stack in extruder_stacks:
|
for extruder_stack in extruder_stacks:
|
||||||
if extruder_stack.getNextStack():
|
if extruder_stack.getNextStack():
|
||||||
|
@ -129,8 +129,9 @@ class CuraStackBuilder:
|
|||||||
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
# It still needs to break, but we want to know what extruder ID made it break.
|
# It still needs to break, but we want to know what extruder ID made it break.
|
||||||
Logger.log("e", "Unable to find extruder with the id %s", extruder_definition_id)
|
msg = "Unable to find extruder definition with the id [%s]" % extruder_definition_id
|
||||||
raise e
|
Logger.logException("e", msg)
|
||||||
|
raise IndexError(msg)
|
||||||
|
|
||||||
# get material container for extruders
|
# get material container for extruders
|
||||||
material_container = application.empty_material_container
|
material_container = application.empty_material_container
|
||||||
|
@ -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()
|
||||||
@ -114,7 +115,7 @@ class ExtruderManager(QObject):
|
|||||||
selected_nodes = [] # type: List["SceneNode"]
|
selected_nodes = [] # type: List["SceneNode"]
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
||||||
if node.callDecoration("isGroup"):
|
if node.callDecoration("isGroup"):
|
||||||
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for grouped_node in BreadthFirstIterator(node):
|
||||||
if grouped_node.callDecoration("isGroup"):
|
if grouped_node.callDecoration("isGroup"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -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,7 +280,8 @@ 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
|
||||||
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
if extruder_str_nr in self.extruderIds:
|
||||||
|
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
|
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
|
||||||
@ -379,7 +381,13 @@ class ExtruderManager(QObject):
|
|||||||
elif extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
|
elif extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
|
||||||
Logger.log("e", "Single extruder printer [{printer}] expected extruder [{expected}], but got [{got}]. I'm making it [{expected}].".format(
|
Logger.log("e", "Single extruder printer [{printer}] expected extruder [{expected}], but got [{got}]. I'm making it [{expected}].".format(
|
||||||
printer = global_stack.getId(), expected = expected_extruder_definition_0_id, got = extruder_stack_0.definition.getId()))
|
printer = global_stack.getId(), expected = expected_extruder_definition_0_id, got = extruder_stack_0.definition.getId()))
|
||||||
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
|
try:
|
||||||
|
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
|
||||||
|
except IndexError as e:
|
||||||
|
# It still needs to break, but we want to know what extruder ID made it break.
|
||||||
|
msg = "Unable to find extruder definition with the id [%s]" % expected_extruder_definition_0_id
|
||||||
|
Logger.logException("e", msg)
|
||||||
|
raise IndexError(msg)
|
||||||
extruder_stack_0.definition = extruder_definition
|
extruder_stack_0.definition = extruder_definition
|
||||||
|
|
||||||
## Get all extruder values for a certain setting.
|
## Get all extruder values for a certain setting.
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import threading
|
import threading
|
||||||
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
|
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
|
||||||
|
import uuid
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
@ -34,6 +36,12 @@ class GlobalStack(CuraContainerStack):
|
|||||||
|
|
||||||
self.setMetaDataEntry("type", "machine") # For backward compatibility
|
self.setMetaDataEntry("type", "machine") # For backward compatibility
|
||||||
|
|
||||||
|
# TL;DR: If Cura is looking for printers that belong to the same group, it should use "group_id".
|
||||||
|
# Each GlobalStack by default belongs to a group which is identified via "group_id". This group_id is used to
|
||||||
|
# figure out which GlobalStacks are in the printer cluster for example without knowing the implementation
|
||||||
|
# details such as the um_network_key or some other identifier that's used by the underlying device plugin.
|
||||||
|
self.setMetaDataEntry("group_id", str(uuid.uuid4())) # Assign a new GlobalStack to a unique group by default
|
||||||
|
|
||||||
self._extruders = {} # type: Dict[str, "ExtruderStack"]
|
self._extruders = {} # type: Dict[str, "ExtruderStack"]
|
||||||
|
|
||||||
# This property is used to track which settings we are calculating the "resolve" for
|
# This property is used to track which settings we are calculating the "resolve" for
|
||||||
@ -68,6 +76,10 @@ class GlobalStack(CuraContainerStack):
|
|||||||
def maxExtruderCount(self):
|
def maxExtruderCount(self):
|
||||||
return len(self.getMetaDataEntry("machine_extruder_trains"))
|
return len(self.getMetaDataEntry("machine_extruder_trains"))
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=configuredConnectionTypesChanged)
|
||||||
|
def supportsNetworkConnection(self):
|
||||||
|
return self.getMetaDataEntry("supports_network_connection", False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getLoadingPriority(cls) -> int:
|
def getLoadingPriority(cls) -> int:
|
||||||
return 2
|
return 2
|
||||||
@ -106,7 +118,7 @@ class GlobalStack(CuraContainerStack):
|
|||||||
## \sa configuredConnectionTypes
|
## \sa configuredConnectionTypes
|
||||||
def removeConfiguredConnectionType(self, connection_type: int) -> None:
|
def removeConfiguredConnectionType(self, connection_type: int) -> None:
|
||||||
configured_connection_types = self.configuredConnectionTypes
|
configured_connection_types = self.configuredConnectionTypes
|
||||||
if connection_type in self.configured_connection_types:
|
if connection_type in configured_connection_types:
|
||||||
# Store the values as a string.
|
# Store the values as a string.
|
||||||
configured_connection_types.remove(connection_type)
|
configured_connection_types.remove(connection_type)
|
||||||
self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types]))
|
self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types]))
|
||||||
@ -212,7 +224,7 @@ class GlobalStack(CuraContainerStack):
|
|||||||
# Determine whether or not we should try to get the "resolve" property instead of the
|
# Determine whether or not we should try to get the "resolve" property instead of the
|
||||||
# requested property.
|
# requested property.
|
||||||
def _shouldResolve(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> bool:
|
def _shouldResolve(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> bool:
|
||||||
if property_name is not "value":
|
if property_name != "value":
|
||||||
# Do not try to resolve anything but the "value" property
|
# Do not try to resolve anything but the "value" property
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -252,14 +264,17 @@ 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 getHasMachineQuality(self) -> bool:
|
@pyqtProperty(int, constant=True)
|
||||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
def hasVariantBuildplates(self) -> bool:
|
||||||
|
return parseBool(self.getMetaDataEntry("has_variant_buildplates", 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)
|
||||||
|
@ -9,6 +9,7 @@ from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
|
|||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
|
||||||
|
|
||||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
|
from UM.Decorators import deprecated
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.Interfaces import ContainerInterface
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
@ -37,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
|
||||||
@ -387,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:
|
||||||
@ -463,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
|
||||||
|
|
||||||
@ -498,18 +500,21 @@ class MachineManager(QObject):
|
|||||||
return bool(self._stacks_have_errors)
|
return bool(self._stacks_have_errors)
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.definition.name instead", "4.1")
|
||||||
def activeMachineDefinitionName(self) -> str:
|
def activeMachineDefinitionName(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.definition.getName()
|
return self._global_container_stack.definition.getName()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.name instead", "4.1")
|
||||||
def activeMachineName(self) -> str:
|
def activeMachineName(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getMetaDataEntry("group_name", self._global_container_stack.getName())
|
return self._global_container_stack.getMetaDataEntry("group_name", self._global_container_stack.getName())
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.id instead", "4.1")
|
||||||
def activeMachineId(self) -> str:
|
def activeMachineId(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.getId()
|
return self._global_container_stack.getId()
|
||||||
@ -532,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
|
||||||
@ -543,6 +549,7 @@ class MachineManager(QObject):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify=globalContainerChanged)
|
@pyqtProperty("QVariantList", notify=globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.configuredConnectionTypes instead", "4.1")
|
||||||
def activeMachineConfiguredConnectionTypes(self):
|
def activeMachineConfiguredConnectionTypes(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.configuredConnectionTypes
|
return self._global_container_stack.configuredConnectionTypes
|
||||||
@ -591,7 +598,7 @@ class MachineManager(QObject):
|
|||||||
def activeStack(self) -> Optional["ExtruderStack"]:
|
def activeStack(self) -> Optional["ExtruderStack"]:
|
||||||
return self._active_container_stack
|
return self._active_container_stack
|
||||||
|
|
||||||
@pyqtProperty(str, notify=activeMaterialChanged)
|
@pyqtProperty(str, notify = activeMaterialChanged)
|
||||||
def activeMaterialId(self) -> str:
|
def activeMaterialId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
material = self._active_container_stack.material
|
material = self._active_container_stack.material
|
||||||
@ -715,6 +722,7 @@ class MachineManager(QObject):
|
|||||||
extruder_stack.userChanges.setProperty(key, "value", new_value)
|
extruder_stack.userChanges.setProperty(key, "value", new_value)
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeStack.variant.name instead", "4.1")
|
||||||
def activeVariantName(self) -> str:
|
def activeVariantName(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.variant
|
variant = self._active_container_stack.variant
|
||||||
@ -724,6 +732,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeStack.variant.id instead", "4.1")
|
||||||
def activeVariantId(self) -> str:
|
def activeVariantId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.variant
|
variant = self._active_container_stack.variant
|
||||||
@ -733,6 +742,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.variant.name instead", "4.1")
|
||||||
def activeVariantBuildplateName(self) -> str:
|
def activeVariantBuildplateName(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
variant = self._global_container_stack.variant
|
variant = self._global_container_stack.variant
|
||||||
@ -742,6 +752,7 @@ class MachineManager(QObject):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.definition.id instead", "4.1")
|
||||||
def activeDefinitionId(self) -> str:
|
def activeDefinitionId(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.definition.id
|
return self._global_container_stack.definition.id
|
||||||
@ -777,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)
|
||||||
|
|
||||||
@ -788,7 +800,6 @@ class MachineManager(QObject):
|
|||||||
self.setActiveMachine(other_machine_stacks[0]["id"])
|
self.setActiveMachine(other_machine_stacks[0]["id"])
|
||||||
|
|
||||||
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
|
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
|
||||||
network_key = metadata.get("um_network_key", None)
|
|
||||||
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
||||||
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
|
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
|
||||||
for container in containers:
|
for container in containers:
|
||||||
@ -796,29 +807,33 @@ class MachineManager(QObject):
|
|||||||
CuraContainerRegistry.getInstance().removeContainer(machine_id)
|
CuraContainerRegistry.getInstance().removeContainer(machine_id)
|
||||||
|
|
||||||
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
|
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
|
||||||
if network_key:
|
group_id = metadata.get("group_id", None)
|
||||||
metadata_filter = {"um_network_key": network_key}
|
if group_id:
|
||||||
|
metadata_filter = {"group_id": group_id}
|
||||||
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
if hidden_containers:
|
if hidden_containers:
|
||||||
# This reuses the method and remove all printers recursively
|
# This reuses the method and remove all printers recursively
|
||||||
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 Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False))
|
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 Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False))
|
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 Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variant_buildplates", False))
|
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
|
||||||
@ -881,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:
|
||||||
@ -910,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()
|
||||||
@ -940,7 +949,7 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
# Check to see if any objects are set to print with an extruder that will no longer exist
|
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||||
root_node = self._application.getController().getScene().getRoot()
|
root_node = self._application.getController().getScene().getRoot()
|
||||||
for node in DepthFirstIterator(root_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(root_node):
|
||||||
if node.getMeshData():
|
if node.getMeshData():
|
||||||
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
|
||||||
@ -978,6 +987,11 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
@pyqtSlot(int, result = QObject)
|
@pyqtSlot(int, result = QObject)
|
||||||
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
|
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
|
||||||
|
return self._getExtruder(position)
|
||||||
|
|
||||||
|
# This is a workaround for the deprecated decorator and the pyqtSlot not playing well together.
|
||||||
|
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
|
||||||
|
def _getExtruder(self, position) -> Optional[ExtruderStack]:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
return self._global_container_stack.extruders.get(str(position))
|
return self._global_container_stack.extruders.get(str(position))
|
||||||
return None
|
return None
|
||||||
@ -1090,6 +1104,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 []
|
||||||
@ -1099,9 +1114,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]:
|
||||||
@ -1254,8 +1277,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
|
||||||
@ -1264,7 +1287,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
|
||||||
@ -1345,27 +1368,31 @@ 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
|
||||||
# Get the definition id corresponding to this machine name
|
# Get the definition id corresponding to this machine name
|
||||||
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
|
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
|
||||||
# Try to find a machine with the same network key
|
# Try to find a machine with the same network key
|
||||||
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()})
|
metadata_filter = {"group_id": self._global_container_stack.getMetaDataEntry("group_id"),
|
||||||
|
"um_network_key": self.activeMachineNetworkKey(),
|
||||||
|
}
|
||||||
|
new_machine = self.getMachine(machine_definition_id, metadata_filter = metadata_filter)
|
||||||
# If there is no machine, then create a new one and set it to the non-hidden instance
|
# If there is no machine, then create a new one and set it to the non-hidden instance
|
||||||
if not new_machine:
|
if not new_machine:
|
||||||
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
|
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
|
||||||
if not new_machine:
|
if not new_machine:
|
||||||
return
|
return
|
||||||
|
new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id"))
|
||||||
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
|
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
|
||||||
new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
|
new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
|
||||||
new_machine.setMetaDataEntry("hidden", False)
|
|
||||||
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
|
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
|
||||||
else:
|
else:
|
||||||
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey())
|
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey())
|
||||||
new_machine.setMetaDataEntry("hidden", False)
|
|
||||||
|
|
||||||
# Set the current printer instance to hidden (the metadata entry must exist)
|
# Set the current printer instance to hidden (the metadata entry must exist)
|
||||||
|
new_machine.setMetaDataEntry("hidden", False)
|
||||||
self._global_container_stack.setMetaDataEntry("hidden", True)
|
self._global_container_stack.setMetaDataEntry("hidden", True)
|
||||||
|
|
||||||
self.setActiveMachine(new_machine.getId())
|
self.setActiveMachine(new_machine.getId())
|
||||||
@ -1428,6 +1455,7 @@ class MachineManager(QObject):
|
|||||||
self._global_container_stack.extruders[position].setEnabled(True)
|
self._global_container_stack.extruders[position].setEnabled(True)
|
||||||
self.updateMaterialWithVariant(position)
|
self.updateMaterialWithVariant(position)
|
||||||
|
|
||||||
|
self.updateDefaultExtruder()
|
||||||
self.updateNumberExtrudersEnabled()
|
self.updateNumberExtrudersEnabled()
|
||||||
|
|
||||||
if configuration.buildplateConfiguration is not None:
|
if configuration.buildplateConfiguration is not None:
|
||||||
@ -1630,10 +1658,6 @@ class MachineManager(QObject):
|
|||||||
|
|
||||||
return abbr_machine
|
return abbr_machine
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
# Gets all machines that belong to the given group_id.
|
||||||
def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
|
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
|
||||||
machine_type_name = ""
|
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)
|
||||||
results = self._container_registry.findDefinitionContainersMetadata(id = machine_type_id)
|
|
||||||
if results:
|
|
||||||
machine_type_name = results[0]["name"]
|
|
||||||
return machine_type_name
|
|
||||||
|
@ -73,8 +73,8 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
|||||||
|
|
||||||
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
|
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
|
||||||
# has not been updated yet.
|
# has not been updated yet.
|
||||||
deep_copy._is_non_printing_mesh = self.evaluateIsNonPrintingMesh()
|
deep_copy._is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
|
||||||
deep_copy._is_non_thumbnail_visible_mesh = self.evaluateIsNonThumbnailVisibleMesh()
|
deep_copy._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
|
||||||
|
|
||||||
return deep_copy
|
return deep_copy
|
||||||
|
|
||||||
@ -102,21 +102,21 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
|||||||
def isNonPrintingMesh(self):
|
def isNonPrintingMesh(self):
|
||||||
return self._is_non_printing_mesh
|
return self._is_non_printing_mesh
|
||||||
|
|
||||||
def evaluateIsNonPrintingMesh(self):
|
def _evaluateIsNonPrintingMesh(self):
|
||||||
return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
|
return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
|
||||||
|
|
||||||
def isNonThumbnailVisibleMesh(self):
|
def isNonThumbnailVisibleMesh(self):
|
||||||
return self._is_non_thumbnail_visible_mesh
|
return self._is_non_thumbnail_visible_mesh
|
||||||
|
|
||||||
def evaluateIsNonThumbnailVisibleMesh(self):
|
def _evaluateIsNonThumbnailVisibleMesh(self):
|
||||||
return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_thumbnail_visible_settings)
|
return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_thumbnail_visible_settings)
|
||||||
|
|
||||||
def _onSettingChanged(self, instance, 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.
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
# Trigger slice/need slicing if the value has changed.
|
# Trigger slice/need slicing if the value has changed.
|
||||||
self._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()
|
||||||
|
|
||||||
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 node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"):
|
if not getattr(node, "_outside_buildarea", False):
|
||||||
if bbox is None:
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||||
bbox = node.getBoundingBox()
|
if bbox is None:
|
||||||
else:
|
bbox = node.getBoundingBox()
|
||||||
bbox = bbox + node.getBoundingBox()
|
else:
|
||||||
|
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)
|
||||||
|
|
||||||
|
31
cura/UI/AddPrinterPagesModel.py
Normal file
31
cura/UI/AddPrinterPagesModel.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from .WelcomePagesModel import WelcomePagesModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This Qt ListModel is more or less the same the WelcomePagesModel, except that this model is only for adding a printer,
|
||||||
|
# so only the steps for adding a printer is included.
|
||||||
|
#
|
||||||
|
class AddPrinterPagesModel(WelcomePagesModel):
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
self._pages.append({"id": "add_network_or_local_printer",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
|
||||||
|
"next_page_id": "machine_actions",
|
||||||
|
"next_page_button_text": self._catalog.i18nc("@action:button", "Add"),
|
||||||
|
"previous_page_button_text": self._catalog.i18nc("@action:button", "Cancel"),
|
||||||
|
})
|
||||||
|
self._pages.append({"id": "add_printer_by_ip",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),
|
||||||
|
"next_page_id": "machine_actions",
|
||||||
|
})
|
||||||
|
self._pages.append({"id": "machine_actions",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"),
|
||||||
|
"should_show_function": self.shouldShowMachineActions,
|
||||||
|
})
|
||||||
|
self.setItems(self._pages)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["AddPrinterPagesModel"]
|
@ -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 _shouldNodeBeHandled(self, node: SceneNode) -> bool:
|
||||||
|
is_group = bool(node.callDecoration("isGroup"))
|
||||||
|
if not node.callDecoration("isSliceable") and not is_group:
|
||||||
|
return False
|
||||||
|
|
||||||
|
parent = node.getParent()
|
||||||
|
if parent and parent.callDecoration("isGroup"):
|
||||||
|
return False # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||||
|
|
||||||
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
|
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:
|
def _update(self, *args) -> None:
|
||||||
nodes = []
|
nodes = [] # type: List[Dict[str, Union[str, int, bool, SceneNode]]]
|
||||||
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
|
name_to_node_info_dict = {} # type: Dict[str, _NodeInfo]
|
||||||
active_build_plate_number = self._build_plate_number
|
|
||||||
group_nr = 1
|
|
||||||
name_count_dict = defaultdict(int) # type: Dict[str, int]
|
|
||||||
|
|
||||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore
|
||||||
if not isinstance(node, SceneNode):
|
if not self._shouldNodeBeHandled(node):
|
||||||
continue
|
|
||||||
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parent = node.getParent()
|
is_group = bool(node.callDecoration("isGroup"))
|
||||||
if parent and parent.callDecoration("isGroup"):
|
|
||||||
continue # 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")
|
|
||||||
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not 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:
|
||||||
|
original_name = name_match.groups()[0]
|
||||||
|
name_index = int(name_match.groups()[1])
|
||||||
else:
|
else:
|
||||||
name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
|
# Handle names for grouped nodes
|
||||||
group_nr += 1
|
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"]
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional, List, Dict, Any
|
|||||||
|
|
||||||
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
@ -23,6 +24,9 @@ if TYPE_CHECKING:
|
|||||||
# - page_url : The QUrl to the QML file that contains the content of this page
|
# - page_url : The QUrl to the QML file that contains the content of this page
|
||||||
# - next_page_id : (OPTIONAL) The next page ID to go to when this page finished. This is optional. If this is not
|
# - next_page_id : (OPTIONAL) The next page ID to go to when this page finished. This is optional. If this is not
|
||||||
# provided, it will go to the page with the current index + 1
|
# provided, it will go to the page with the current index + 1
|
||||||
|
# - next_page_button_text: (OPTIONAL) The text to show for the "next" button, by default it's the translated text of
|
||||||
|
# "Next". Note that each step QML can decide whether to use this text or not, so it's not
|
||||||
|
# mandatory.
|
||||||
# - should_show_function : (OPTIONAL) An optional function that returns True/False indicating if this page should be
|
# - should_show_function : (OPTIONAL) An optional function that returns True/False indicating if this page should be
|
||||||
# shown. By default all pages should be shown. If a function returns False, that page will
|
# shown. By default all pages should be shown. If a function returns False, that page will
|
||||||
# be skipped and its next page will be shown.
|
# be skipped and its next page will be shown.
|
||||||
@ -34,6 +38,8 @@ class WelcomePagesModel(ListModel):
|
|||||||
IdRole = Qt.UserRole + 1 # Page ID
|
IdRole = Qt.UserRole + 1 # Page ID
|
||||||
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
|
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
|
||||||
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
|
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
|
||||||
|
NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button
|
||||||
|
PreviousPageButtonTextRole = Qt.UserRole + 5 # The text for the previous page button
|
||||||
|
|
||||||
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
|
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -41,8 +47,13 @@ class WelcomePagesModel(ListModel):
|
|||||||
self.addRoleName(self.IdRole, "id")
|
self.addRoleName(self.IdRole, "id")
|
||||||
self.addRoleName(self.PageUrlRole, "page_url")
|
self.addRoleName(self.PageUrlRole, "page_url")
|
||||||
self.addRoleName(self.NextPageIdRole, "next_page_id")
|
self.addRoleName(self.NextPageIdRole, "next_page_id")
|
||||||
|
self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text")
|
||||||
|
self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text")
|
||||||
|
|
||||||
self._application = application
|
self._application = application
|
||||||
|
self._catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
self._default_next_button_text = self._catalog.i18nc("@action:button", "Next")
|
||||||
|
|
||||||
self._pages = [] # type: List[Dict[str, Any]]
|
self._pages = [] # type: List[Dict[str, Any]]
|
||||||
|
|
||||||
@ -50,6 +61,10 @@ class WelcomePagesModel(ListModel):
|
|||||||
# Store all the previous page indices so it can go back.
|
# Store all the previous page indices so it can go back.
|
||||||
self._previous_page_indices_stack = deque() # type: deque
|
self._previous_page_indices_stack = deque() # type: deque
|
||||||
|
|
||||||
|
# If the welcome flow should be shown. It can show the complete flow or just the changelog depending on the
|
||||||
|
# specific case. See initialize() for how this variable is set.
|
||||||
|
self._should_show_welcome_flow = False
|
||||||
|
|
||||||
allFinished = pyqtSignal() # emitted when all steps have been finished
|
allFinished = pyqtSignal() # emitted when all steps have been finished
|
||||||
currentPageIndexChanged = pyqtSignal()
|
currentPageIndexChanged = pyqtSignal()
|
||||||
|
|
||||||
@ -137,7 +152,8 @@ class WelcomePagesModel(ListModel):
|
|||||||
page_index = self.getPageIndexById(page_id)
|
page_index = self.getPageIndexById(page_id)
|
||||||
if page_index is None:
|
if page_index is None:
|
||||||
# FIXME: If we cannot find the next page, we cannot do anything here.
|
# FIXME: If we cannot find the next page, we cannot do anything here.
|
||||||
Logger.log("e", "Cannot find page with ID [%s]", page_index)
|
Logger.log("e", "Cannot find page with ID [%s], go to the next page by default", page_index)
|
||||||
|
self.goToNextPage()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._shouldPageBeShown(page_index):
|
if self._shouldPageBeShown(page_index):
|
||||||
@ -164,6 +180,12 @@ class WelcomePagesModel(ListModel):
|
|||||||
|
|
||||||
self.currentPageIndexChanged.emit()
|
self.currentPageIndexChanged.emit()
|
||||||
|
|
||||||
|
shouldShowWelcomeFlowChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = shouldShowWelcomeFlowChanged)
|
||||||
|
def shouldShowWelcomeFlow(self) -> bool:
|
||||||
|
return self._should_show_welcome_flow
|
||||||
|
|
||||||
# Gets the page index with the given page ID. If the page ID doesn't exist, returns None.
|
# Gets the page index with the given page ID. If the page ID doesn't exist, returns None.
|
||||||
def getPageIndexById(self, page_id: str) -> Optional[int]:
|
def getPageIndexById(self, page_id: str) -> Optional[int]:
|
||||||
page_idx = None
|
page_idx = None
|
||||||
@ -179,39 +201,81 @@ class WelcomePagesModel(ListModel):
|
|||||||
return QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
|
return QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
|
||||||
os.path.join("WelcomePages", page_filename)))
|
os.path.join("WelcomePages", page_filename)))
|
||||||
|
|
||||||
def initialize(self) -> None:
|
# FIXME: HACKs for optimization that we don't update the model every time the active machine gets changed.
|
||||||
# Add default welcome pages
|
def _onActiveMachineChanged(self) -> None:
|
||||||
self._pages.append({"id": "welcome",
|
self._application.getMachineManager().globalContainerChanged.disconnect(self._onActiveMachineChanged)
|
||||||
"page_url": self._getBuiltinWelcomePagePath("WelcomeContent.qml"),
|
self._initialize(update_should_show_flag = False)
|
||||||
})
|
|
||||||
self._pages.append({"id": "user_agreement",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("UserAgreementContent.qml"),
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "whats_new",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"),
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "data_collections",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("DataCollectionsContent.qml"),
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "add_network_or_local_printer",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
|
|
||||||
"next_page_id": "machine_actions",
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "add_printer_by_ip",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),
|
|
||||||
"next_page_id": "machine_actions",
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "machine_actions",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"),
|
|
||||||
"next_page_id": "cloud",
|
|
||||||
"should_show_function": self.shouldShowMachineActions,
|
|
||||||
})
|
|
||||||
self._pages.append({"id": "cloud",
|
|
||||||
"page_url": self._getBuiltinWelcomePagePath("CloudContent.qml"),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
self._application.getMachineManager().globalContainerChanged.connect(self._onActiveMachineChanged)
|
||||||
|
self._initialize()
|
||||||
|
|
||||||
|
def _initialize(self, update_should_show_flag: bool = True) -> None:
|
||||||
|
show_whatsnew_only = False
|
||||||
|
if update_should_show_flag:
|
||||||
|
has_active_machine = self._application.getMachineManager().activeMachine is not None
|
||||||
|
has_app_just_upgraded = self._application.hasJustUpdatedFromOldVersion()
|
||||||
|
|
||||||
|
# Only show the what's new dialog if there's no machine and we have just upgraded
|
||||||
|
show_complete_flow = not has_active_machine
|
||||||
|
show_whatsnew_only = has_active_machine and has_app_just_upgraded
|
||||||
|
|
||||||
|
# FIXME: This is a hack. Because of the circular dependency between MachineManager, ExtruderManager, and
|
||||||
|
# possibly some others, setting the initial active machine is not done when the MachineManager gets initialized.
|
||||||
|
# So at this point, we don't know if there will be an active machine or not. It could be that the active machine
|
||||||
|
# files are corrupted so we cannot rely on Preferences either. This makes sure that once the active machine
|
||||||
|
# gets changed, this model updates the flags, so it can decide whether to show the welcome flow or not.
|
||||||
|
should_show_welcome_flow = show_complete_flow or show_whatsnew_only
|
||||||
|
if should_show_welcome_flow != self._should_show_welcome_flow:
|
||||||
|
self._should_show_welcome_flow = should_show_welcome_flow
|
||||||
|
self.shouldShowWelcomeFlowChanged.emit()
|
||||||
|
|
||||||
|
# All pages
|
||||||
|
all_pages_list = [{"id": "welcome",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("WelcomeContent.qml"),
|
||||||
|
},
|
||||||
|
{"id": "user_agreement",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("UserAgreementContent.qml"),
|
||||||
|
},
|
||||||
|
{"id": "whats_new",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"),
|
||||||
|
},
|
||||||
|
{"id": "data_collections",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("DataCollectionsContent.qml"),
|
||||||
|
},
|
||||||
|
{"id": "add_network_or_local_printer",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
|
||||||
|
"next_page_id": "machine_actions",
|
||||||
|
},
|
||||||
|
{"id": "add_printer_by_ip",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),
|
||||||
|
"next_page_id": "machine_actions",
|
||||||
|
},
|
||||||
|
{"id": "machine_actions",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("FirstStartMachineActionsContent.qml"),
|
||||||
|
"next_page_id": "cloud",
|
||||||
|
"should_show_function": self.shouldShowMachineActions,
|
||||||
|
},
|
||||||
|
{"id": "cloud",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("CloudContent.qml"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
pages_to_show = all_pages_list
|
||||||
|
if show_whatsnew_only:
|
||||||
|
pages_to_show = list(filter(lambda x: x["id"] == "whats_new", all_pages_list))
|
||||||
|
|
||||||
|
self._pages = pages_to_show
|
||||||
self.setItems(self._pages)
|
self.setItems(self._pages)
|
||||||
|
|
||||||
|
# For convenience, inject the default "next" button text to each item if it's not present.
|
||||||
|
def setItems(self, items: List[Dict[str, Any]]) -> None:
|
||||||
|
for item in items:
|
||||||
|
if "next_page_button_text" not in item:
|
||||||
|
item["next_page_button_text"] = self._default_next_button_text
|
||||||
|
|
||||||
|
super().setItems(items)
|
||||||
|
|
||||||
# Indicates if the machine action panel should be shown by checking if there's any first start machine actions
|
# Indicates if the machine action panel should be shown by checking if there's any first start machine actions
|
||||||
# available.
|
# available.
|
||||||
def shouldShowMachineActions(self) -> bool:
|
def shouldShowMachineActions(self) -> bool:
|
||||||
|
22
cura/UI/WhatsNewPagesModel.py
Normal file
22
cura/UI/WhatsNewPagesModel.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from .WelcomePagesModel import WelcomePagesModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This Qt ListModel is more or less the same the WelcomePagesModel, except that this model is only for showing the
|
||||||
|
# "what's new" page. This is also used in the "Help" menu to show the changes log.
|
||||||
|
#
|
||||||
|
class WhatsNewPagesModel(WelcomePagesModel):
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
self._pages = []
|
||||||
|
self._pages.append({"id": "whats_new",
|
||||||
|
"page_url": self._getBuiltinWelcomePagePath("WhatsNewContent.qml"),
|
||||||
|
"next_page_button_text": self._catalog.i18nc("@action:button", "Close"),
|
||||||
|
})
|
||||||
|
self.setItems(self._pages)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["WhatsNewPagesModel"]
|
44
cura/Utils/NetworkingUtil.py
Normal file
44
cura/Utils/NetworkingUtil.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import socket
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a QObject because some of the functions can be used (and are useful) in QML.
|
||||||
|
#
|
||||||
|
class NetworkingUtil(QObject):
|
||||||
|
|
||||||
|
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||||
|
super().__init__(parent = parent)
|
||||||
|
|
||||||
|
# Checks if the given string is a valid IPv4 address.
|
||||||
|
@pyqtSlot(str, result = bool)
|
||||||
|
def isIPv4(self, address: str) -> bool:
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET, address)
|
||||||
|
result = True
|
||||||
|
except:
|
||||||
|
result = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Checks if the given string is a valid IPv6 address.
|
||||||
|
@pyqtSlot(str, result = bool)
|
||||||
|
def isIPv6(self, address: str) -> bool:
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET6, address)
|
||||||
|
result = True
|
||||||
|
except:
|
||||||
|
result = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Checks if the given string is a valid IPv4 or IPv6 address.
|
||||||
|
@pyqtSlot(str, result = bool)
|
||||||
|
def isValidIP(self, address: str) -> bool:
|
||||||
|
return self.isIPv4(address) or self.isIPv6(address)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["NetworkingUtil"]
|
@ -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")
|
||||||
|
43
docker/build.sh
Executable file
43
docker/build.sh
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Abort at the first error.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
PROJECT_DIR="$( cd "${SCRIPT_DIR}/.." && pwd )"
|
||||||
|
|
||||||
|
# Make sure that environment variables are set properly
|
||||||
|
source /opt/rh/devtoolset-7/enable
|
||||||
|
export PATH="${CURA_BUILD_ENV_PATH}/bin:${PATH}"
|
||||||
|
export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Clone Uranium and set PYTHONPATH first
|
||||||
|
#
|
||||||
|
|
||||||
|
# Check the branch to use:
|
||||||
|
# 1. Use the Uranium branch with the branch same if it exists.
|
||||||
|
# 2. Otherwise, use the default branch name "master"
|
||||||
|
URANIUM_BRANCH="${CI_COMMIT_REF_NAME:-master}"
|
||||||
|
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
||||||
|
if [ -z "${output}" ]; then
|
||||||
|
echo "Could not find Uranium banch ${URANIUM_BRANCH}, fallback to use master."
|
||||||
|
URANIUM_BRANCH="master"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using Uranium branch ${URANIUM_BRANCH} ..."
|
||||||
|
git clone --depth=1 -b "${URANIUM_BRANCH}" https://github.com/Ultimaker/Uranium.git "${PROJECT_DIR}"/Uranium
|
||||||
|
export PYTHONPATH="${PROJECT_DIR}/Uranium:.:${PYTHONPATH}"
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake3 \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DCMAKE_PREFIX_PATH="${CURA_BUILD_ENV_PATH}" \
|
||||||
|
-DURANIUM_DIR="${PROJECT_DIR}/Uranium" \
|
||||||
|
-DBUILD_TESTS=ON \
|
||||||
|
..
|
||||||
|
make
|
||||||
|
ctest3 --output-on-failure -T Test
|
@ -1,7 +1,7 @@
|
|||||||
# 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 typing import Optional
|
from typing import List, Optional, Union, TYPE_CHECKING
|
||||||
import os.path
|
import os.path
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
@ -9,15 +9,16 @@ import numpy
|
|||||||
|
|
||||||
import Savitar
|
import Savitar
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.Matrix import Matrix
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Mesh.MeshReader import MeshReader
|
from UM.Mesh.MeshReader import MeshReader
|
||||||
from UM.Scene.GroupDecorator import GroupDecorator
|
from UM.Scene.GroupDecorator import GroupDecorator
|
||||||
|
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
@ -25,11 +26,9 @@ from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
|||||||
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
||||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
|
|
||||||
MYPY = False
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not MYPY:
|
if not TYPE_CHECKING:
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
except ImportError:
|
except ImportError:
|
||||||
Logger.log("w", "Unable to load cElementTree, switching to slower version")
|
Logger.log("w", "Unable to load cElementTree, switching to slower version")
|
||||||
@ -55,7 +54,7 @@ class ThreeMFReader(MeshReader):
|
|||||||
self._unit = None
|
self._unit = None
|
||||||
self._object_count = 0 # Used to name objects as there is no node name yet.
|
self._object_count = 0 # Used to name objects as there is no node name yet.
|
||||||
|
|
||||||
def _createMatrixFromTransformationString(self, transformation):
|
def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:
|
||||||
if transformation == "":
|
if transformation == "":
|
||||||
return Matrix()
|
return Matrix()
|
||||||
|
|
||||||
@ -85,13 +84,13 @@ class ThreeMFReader(MeshReader):
|
|||||||
|
|
||||||
return temp_mat
|
return temp_mat
|
||||||
|
|
||||||
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node.
|
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
|
||||||
# \returns Uranium scene node.
|
# \returns Scene node.
|
||||||
def _convertSavitarNodeToUMNode(self, savitar_node):
|
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode) -> Optional[SceneNode]:
|
||||||
self._object_count += 1
|
self._object_count += 1
|
||||||
node_name = "Object %s" % self._object_count
|
node_name = "Object %s" % self._object_count
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
|
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
|
||||||
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||||
@ -122,7 +121,7 @@ class ThreeMFReader(MeshReader):
|
|||||||
|
|
||||||
# Add the setting override decorator, so we can add settings to this node.
|
# Add the setting override decorator, so we can add settings to this node.
|
||||||
if settings:
|
if settings:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
# Ensure the correct next container for the SettingOverride decorator is set.
|
# Ensure the correct next container for the SettingOverride decorator is set.
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
@ -161,7 +160,7 @@ class ThreeMFReader(MeshReader):
|
|||||||
um_node.addDecorator(sliceable_decorator)
|
um_node.addDecorator(sliceable_decorator)
|
||||||
return um_node
|
return um_node
|
||||||
|
|
||||||
def _read(self, file_name):
|
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
|
||||||
result = []
|
result = []
|
||||||
self._object_count = 0 # Used to name objects as there is no node name yet.
|
self._object_count = 0 # Used to name objects as there is no node name yet.
|
||||||
# The base object of 3mf is a zipped archive.
|
# The base object of 3mf is a zipped archive.
|
||||||
@ -181,12 +180,13 @@ class ThreeMFReader(MeshReader):
|
|||||||
mesh_data = um_node.getMeshData()
|
mesh_data = um_node.getMeshData()
|
||||||
if mesh_data is not None:
|
if mesh_data is not None:
|
||||||
extents = mesh_data.getExtents()
|
extents = mesh_data.getExtents()
|
||||||
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
|
if extents is not None:
|
||||||
transform_matrix.setByTranslation(center_vector)
|
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
|
||||||
|
transform_matrix.setByTranslation(center_vector)
|
||||||
transform_matrix.multiply(um_node.getLocalTransformation())
|
transform_matrix.multiply(um_node.getLocalTransformation())
|
||||||
um_node.setTransformation(transform_matrix)
|
um_node.setTransformation(transform_matrix)
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
# Create a transformation Matrix to convert from 3mf worldspace into ours.
|
# Create a transformation Matrix to convert from 3mf worldspace into ours.
|
||||||
# First step: flip the y and z axis.
|
# First step: flip the y and z axis.
|
||||||
@ -215,17 +215,20 @@ class ThreeMFReader(MeshReader):
|
|||||||
um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))
|
um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))
|
||||||
|
|
||||||
# Check if the model is positioned below the build plate and honor that when loading project files.
|
# Check if the model is positioned below the build plate and honor that when loading project files.
|
||||||
if um_node.getMeshData() is not None:
|
node_meshdata = um_node.getMeshData()
|
||||||
minimum_z_value = um_node.getMeshData().getExtents(um_node.getWorldTransformation()).minimum.y # y is z in transformation coordinates
|
if node_meshdata is not None:
|
||||||
if minimum_z_value < 0:
|
aabb = node_meshdata.getExtents(um_node.getWorldTransformation())
|
||||||
um_node.addDecorator(ZOffsetDecorator())
|
if aabb is not None:
|
||||||
um_node.callDecoration("setZOffset", minimum_z_value)
|
minimum_z_value = aabb.minimum.y # y is z in transformation coordinates
|
||||||
|
if minimum_z_value < 0:
|
||||||
|
um_node.addDecorator(ZOffsetDecorator())
|
||||||
|
um_node.callDecoration("setZOffset", minimum_z_value)
|
||||||
|
|
||||||
result.append(um_node)
|
result.append(um_node)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -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"):
|
||||||
extruder_info.variant_info = instance_container_info_dict[variant_id]
|
if variant_id in instance_container_info_dict:
|
||||||
|
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
|
||||||
|
|
||||||
|
173
plugins/AMFReader/AMFReader.py
Normal file
173
plugins/AMFReader/AMFReader.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# Copyright (c) 2019 fieldOfView
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
# This AMF parser is based on the AMF parser in legacy cura:
|
||||||
|
# https://github.com/daid/LegacyCura/blob/ad7641e059048c7dcb25da1f47c0a7e95e7f4f7c/Cura/util/meshLoaders/amf.py
|
||||||
|
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
|
||||||
|
from UM.Mesh.MeshReader import MeshReader
|
||||||
|
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
|
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
|
||||||
|
from UM.Scene.GroupDecorator import GroupDecorator
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
import trimesh
|
||||||
|
import os.path
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
MYPY = False
|
||||||
|
try:
|
||||||
|
if not MYPY:
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
except ImportError:
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
class AMFReader(MeshReader):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._supported_extensions = [".amf"]
|
||||||
|
self._namespaces = {} # type: Dict[str, str]
|
||||||
|
|
||||||
|
MimeTypeDatabase.addMimeType(
|
||||||
|
MimeType(
|
||||||
|
name="application/x-amf",
|
||||||
|
comment="AMF",
|
||||||
|
suffixes=["amf"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main entry point
|
||||||
|
# Reads the file, returns a SceneNode (possibly with nested ones), or None
|
||||||
|
def _read(self, file_name):
|
||||||
|
base_name = os.path.basename(file_name)
|
||||||
|
try:
|
||||||
|
zipped_file = zipfile.ZipFile(file_name)
|
||||||
|
xml_document = zipped_file.read(zipped_file.namelist()[0])
|
||||||
|
zipped_file.close()
|
||||||
|
except zipfile.BadZipfile:
|
||||||
|
raw_file = open(file_name, "r")
|
||||||
|
xml_document = raw_file.read()
|
||||||
|
raw_file.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
amf_document = ET.fromstring(xml_document)
|
||||||
|
except ET.ParseError:
|
||||||
|
Logger.log("e", "Could not parse XML in file %s" % base_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if "unit" in amf_document.attrib:
|
||||||
|
unit = amf_document.attrib["unit"].lower()
|
||||||
|
else:
|
||||||
|
unit = "millimeter"
|
||||||
|
if unit == "millimeter":
|
||||||
|
scale = 1.0
|
||||||
|
elif unit == "meter":
|
||||||
|
scale = 1000.0
|
||||||
|
elif unit == "inch":
|
||||||
|
scale = 25.4
|
||||||
|
elif unit == "feet":
|
||||||
|
scale = 304.8
|
||||||
|
elif unit == "micron":
|
||||||
|
scale = 0.001
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
|
||||||
|
scale = 1.0
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
for amf_object in amf_document.iter("object"):
|
||||||
|
for amf_mesh in amf_object.iter("mesh"):
|
||||||
|
amf_mesh_vertices = []
|
||||||
|
for vertices in amf_mesh.iter("vertices"):
|
||||||
|
for vertex in vertices.iter("vertex"):
|
||||||
|
for coordinates in vertex.iter("coordinates"):
|
||||||
|
v = [0.0, 0.0, 0.0]
|
||||||
|
for t in coordinates:
|
||||||
|
if t.tag == "x":
|
||||||
|
v[0] = float(t.text) * scale
|
||||||
|
elif t.tag == "y":
|
||||||
|
v[2] = float(t.text) * scale
|
||||||
|
elif t.tag == "z":
|
||||||
|
v[1] = float(t.text) * scale
|
||||||
|
amf_mesh_vertices.append(v)
|
||||||
|
if not amf_mesh_vertices:
|
||||||
|
continue
|
||||||
|
|
||||||
|
indices = []
|
||||||
|
for volume in amf_mesh.iter("volume"):
|
||||||
|
for triangle in volume.iter("triangle"):
|
||||||
|
f = [0, 0, 0]
|
||||||
|
for t in triangle:
|
||||||
|
if t.tag == "v1":
|
||||||
|
f[0] = int(t.text)
|
||||||
|
elif t.tag == "v2":
|
||||||
|
f[1] = int(t.text)
|
||||||
|
elif t.tag == "v3":
|
||||||
|
f[2] = int(t.text)
|
||||||
|
indices.append(f)
|
||||||
|
|
||||||
|
mesh = trimesh.base.Trimesh(vertices=numpy.array(amf_mesh_vertices, dtype=numpy.float32), faces=numpy.array(indices, dtype=numpy.int32))
|
||||||
|
mesh.merge_vertices()
|
||||||
|
mesh.remove_unreferenced_vertices()
|
||||||
|
mesh.fix_normals()
|
||||||
|
mesh_data = self._toMeshData(mesh)
|
||||||
|
|
||||||
|
new_node = CuraSceneNode()
|
||||||
|
new_node.setSelectable(True)
|
||||||
|
new_node.setMeshData(mesh_data)
|
||||||
|
new_node.setName(base_name if len(nodes)==0 else "%s %d" % (base_name, len(nodes)))
|
||||||
|
new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
|
||||||
|
new_node.addDecorator(SliceableObjectDecorator())
|
||||||
|
|
||||||
|
nodes.append(new_node)
|
||||||
|
|
||||||
|
if not nodes:
|
||||||
|
Logger.log("e", "No meshes in file %s" % base_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(nodes) == 1:
|
||||||
|
return nodes[0]
|
||||||
|
|
||||||
|
# Add all scenenodes to a group so they stay together
|
||||||
|
group_node = CuraSceneNode()
|
||||||
|
group_node.addDecorator(GroupDecorator())
|
||||||
|
group_node.addDecorator(ConvexHullDecorator())
|
||||||
|
group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
node.setParent(group_node)
|
||||||
|
|
||||||
|
return group_node
|
||||||
|
|
||||||
|
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
|
||||||
|
tri_faces = tri_node.faces
|
||||||
|
tri_vertices = tri_node.vertices
|
||||||
|
|
||||||
|
indices = []
|
||||||
|
vertices = []
|
||||||
|
|
||||||
|
index_count = 0
|
||||||
|
face_count = 0
|
||||||
|
for tri_face in tri_faces:
|
||||||
|
face = []
|
||||||
|
for tri_index in tri_face:
|
||||||
|
vertices.append(tri_vertices[tri_index])
|
||||||
|
face.append(index_count)
|
||||||
|
index_count += 1
|
||||||
|
indices.append(face)
|
||||||
|
face_count += 1
|
||||||
|
|
||||||
|
vertices = numpy.asarray(vertices, dtype=numpy.float32)
|
||||||
|
indices = numpy.asarray(indices, dtype=numpy.int32)
|
||||||
|
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
|
||||||
|
|
||||||
|
mesh_data = MeshData(vertices=vertices, indices=indices, normals=normals)
|
||||||
|
return mesh_data
|
21
plugins/AMFReader/__init__.py
Normal file
21
plugins/AMFReader/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Copyright (c) 2019 fieldOfView
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from . import AMFReader
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
|
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {
|
||||||
|
"mesh_reader": [
|
||||||
|
{
|
||||||
|
"extension": "amf",
|
||||||
|
"description": i18n_catalog.i18nc("@item:inlistbox", "AMF File")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return {"mesh_reader": AMFReader.AMFReader()}
|
7
plugins/AMFReader/plugin.json
Normal file
7
plugins/AMFReader/plugin.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "AMF Reader",
|
||||||
|
"author": "fieldOfView",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Provides support for reading AMF files.",
|
||||||
|
"api": "6.0.0"
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
# Copyright (c) 2019 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject
|
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
|
||||||
from UM.Extension import Extension
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
|
||||||
from UM.Version import Version
|
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeLog(Extension, QObject):
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
QObject.__init__(self, parent)
|
|
||||||
Extension.__init__(self)
|
|
||||||
self._changelog_window = None
|
|
||||||
self._changelog_context = None
|
|
||||||
version_string = Application.getInstance().getVersion()
|
|
||||||
if version_string is not "master":
|
|
||||||
self._current_app_version = Version(version_string)
|
|
||||||
else:
|
|
||||||
self._current_app_version = None
|
|
||||||
|
|
||||||
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
|
||||||
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
|
|
||||||
self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog"))
|
|
||||||
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
|
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
|
||||||
if not self._current_app_version:
|
|
||||||
return #We're on dev branch.
|
|
||||||
|
|
||||||
if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master":
|
|
||||||
latest_version_shown = Version("0.0.0")
|
|
||||||
else:
|
|
||||||
latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown"))
|
|
||||||
|
|
||||||
Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
|
|
||||||
|
|
||||||
# Do not show the changelog when there is no global container stack
|
|
||||||
# This implies we are running Cura for the first time.
|
|
||||||
if not Application.getInstance().getGlobalContainerStack():
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._current_app_version > latest_version_shown:
|
|
||||||
self.showChangelog()
|
|
||||||
|
|
||||||
def showChangelog(self):
|
|
||||||
if not self._changelog_window:
|
|
||||||
self.createChangelogWindow()
|
|
||||||
|
|
||||||
self._changelog_window.show()
|
|
||||||
|
|
||||||
def hideChangelog(self):
|
|
||||||
if self._changelog_window:
|
|
||||||
self._changelog_window.hide()
|
|
||||||
|
|
||||||
def createChangelogWindow(self):
|
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
|
|
||||||
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
|
@ -1,43 +0,0 @@
|
|||||||
// Copyright (c) 2015 Ultimaker B.V.
|
|
||||||
// Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
import QtQuick 2.1
|
|
||||||
import QtQuick.Controls 1.3
|
|
||||||
import QtQuick.Layouts 1.1
|
|
||||||
import QtQuick.Window 2.1
|
|
||||||
|
|
||||||
import UM 1.3 as UM
|
|
||||||
import Cura 1.0 as Cura
|
|
||||||
|
|
||||||
|
|
||||||
UM.Dialog
|
|
||||||
{
|
|
||||||
id: base
|
|
||||||
minimumWidth: (UM.Theme.getSize("modal_window_minimum").width * 0.75) | 0
|
|
||||||
minimumHeight: (UM.Theme.getSize("modal_window_minimum").height * 0.75) | 0
|
|
||||||
width: minimumWidth
|
|
||||||
height: minimumHeight
|
|
||||||
title: catalog.i18nc("@label", "Changelog")
|
|
||||||
|
|
||||||
TextArea
|
|
||||||
{
|
|
||||||
anchors.fill: parent
|
|
||||||
text: CuraApplication.getTextManager().getChangeLogText()
|
|
||||||
readOnly: true;
|
|
||||||
textFormat: TextEdit.RichText
|
|
||||||
}
|
|
||||||
|
|
||||||
rightButtons: [
|
|
||||||
Button
|
|
||||||
{
|
|
||||||
UM.I18nCatalog
|
|
||||||
{
|
|
||||||
id: catalog
|
|
||||||
name: "cura"
|
|
||||||
}
|
|
||||||
|
|
||||||
text: catalog.i18nc("@action:button", "Close")
|
|
||||||
onClicked: base.hide()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
# Copyright (c) 2015 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from . import ChangeLog
|
|
||||||
|
|
||||||
|
|
||||||
def getMetaData():
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def register(app):
|
|
||||||
return {"extension": ChangeLog.ChangeLog()}
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Changelog",
|
|
||||||
"author": "Ultimaker B.V.",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "Shows changes since latest checked version.",
|
|
||||||
"api": "6.0",
|
|
||||||
"i18n-catalog": "cura"
|
|
||||||
}
|
|
@ -45,7 +45,7 @@ class DriveApiService:
|
|||||||
"Authorization": "Bearer {}".format(access_token)
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
})
|
})
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
Logger.log("w", "Unable to connect with the server.")
|
Logger.logException("w", "Unable to connect with the server.")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
||||||
@ -98,7 +98,12 @@ class DriveApiService:
|
|||||||
# If there is no download URL, we can't restore the backup.
|
# If there is no download URL, we can't restore the backup.
|
||||||
return self._emitRestoreError()
|
return self._emitRestoreError()
|
||||||
|
|
||||||
download_package = requests.get(download_url, stream = True)
|
try:
|
||||||
|
download_package = requests.get(download_url, stream = True)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
Logger.logException("e", "Unable to connect with the server")
|
||||||
|
return self._emitRestoreError()
|
||||||
|
|
||||||
if download_package.status_code >= 300:
|
if download_package.status_code >= 300:
|
||||||
# Something went wrong when attempting to download the backup.
|
# Something went wrong when attempting to download the backup.
|
||||||
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
|
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
|
||||||
@ -142,9 +147,14 @@ class DriveApiService:
|
|||||||
Logger.log("w", "Could not get access token.")
|
Logger.log("w", "Could not get access token.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
try:
|
||||||
"Authorization": "Bearer {}".format(access_token)
|
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||||
})
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
Logger.logException("e", "Unable to connect with the server")
|
||||||
|
return False
|
||||||
|
|
||||||
if delete_backup.status_code >= 300:
|
if delete_backup.status_code >= 300:
|
||||||
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
||||||
return False
|
return False
|
||||||
@ -159,15 +169,19 @@ class DriveApiService:
|
|||||||
if not access_token:
|
if not access_token:
|
||||||
Logger.log("w", "Could not get access token.")
|
Logger.log("w", "Could not get access token.")
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
backup_upload_request = requests.put(self.BACKUP_URL, json = {
|
backup_upload_request = requests.put(
|
||||||
"data": {
|
self.BACKUP_URL,
|
||||||
"backup_size": backup_size,
|
json = {"data": {"backup_size": backup_size,
|
||||||
"metadata": backup_metadata
|
"metadata": backup_metadata
|
||||||
}
|
}
|
||||||
}, headers = {
|
},
|
||||||
"Authorization": "Bearer {}".format(access_token)
|
headers = {
|
||||||
})
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
})
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
Logger.logException("e", "Unable to connect with the server")
|
||||||
|
return None
|
||||||
|
|
||||||
# Any status code of 300 or above indicates an error.
|
# Any status code of 300 or above indicates an error.
|
||||||
if backup_upload_request.status_code >= 300:
|
if backup_upload_request.status_code >= 300:
|
||||||
|
@ -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)
|
||||||
@ -369,7 +369,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
elif job.getResult() == StartJobResult.ObjectSettingError:
|
elif job.getResult() == StartJobResult.ObjectSettingError:
|
||||||
errors = {}
|
errors = {}
|
||||||
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()):
|
||||||
stack = node.callDecoration("getStack")
|
stack = node.callDecoration("getStack")
|
||||||
if not stack:
|
if not stack:
|
||||||
continue
|
continue
|
||||||
@ -438,7 +438,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
|
|
||||||
if not self._application.getPreferences().getValue("general/auto_slice"):
|
if not self._application.getPreferences().getValue("general/auto_slice"):
|
||||||
enable_timer = False
|
enable_timer = False
|
||||||
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()):
|
||||||
if node.callDecoration("isBlockSlicing"):
|
if node.callDecoration("isBlockSlicing"):
|
||||||
enable_timer = False
|
enable_timer = False
|
||||||
self.setState(BackendState.Disabled)
|
self.setState(BackendState.Disabled)
|
||||||
@ -460,7 +460,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
## Return a dict with number of objects per build plate
|
## Return a dict with number of objects per build plate
|
||||||
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
||||||
num_objects = defaultdict(int) #type: Dict[int, int]
|
num_objects = defaultdict(int) #type: Dict[int, int]
|
||||||
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()):
|
||||||
# Only count sliceable objects
|
# Only count sliceable objects
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
@ -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()
|
||||||
@ -551,10 +548,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
# Clear out any old gcode
|
# Clear out any old gcode
|
||||||
self._scene.gcode_dict = {} # type: ignore
|
self._scene.gcode_dict = {} # type: ignore
|
||||||
|
|
||||||
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()):
|
||||||
if node.callDecoration("getLayerData"):
|
if node.callDecoration("getLayerData"):
|
||||||
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
||||||
node.getParent().removeChild(node)
|
# We can asume that all nodes have a parent as we're looping through the scene (and filter out root)
|
||||||
|
cast(SceneNode, node.getParent()).removeChild(node)
|
||||||
|
|
||||||
def markSliceAll(self) -> None:
|
def markSliceAll(self) -> None:
|
||||||
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
|
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
|
||||||
@ -563,10 +561,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 +733,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
|
||||||
@ -136,23 +136,23 @@ class ProcessSlicedLayersJob(Job):
|
|||||||
|
|
||||||
extruder = polygon.extruder
|
extruder = polygon.extruder
|
||||||
|
|
||||||
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
|
line_types = numpy.fromstring(polygon.line_type, dtype = "u1") # Convert bytearray to numpy array
|
||||||
|
|
||||||
line_types = line_types.reshape((-1,1))
|
line_types = line_types.reshape((-1,1))
|
||||||
|
|
||||||
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
|
points = numpy.fromstring(polygon.points, dtype = "f4") # Convert bytearray to numpy array
|
||||||
if polygon.point_type == 0: # Point2D
|
if polygon.point_type == 0: # Point2D
|
||||||
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||||
else: # Point3D
|
else: # Point3D
|
||||||
points = points.reshape((-1,3))
|
points = points.reshape((-1,3))
|
||||||
|
|
||||||
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
|
line_widths = numpy.fromstring(polygon.line_width, dtype = "f4") # Convert bytearray to numpy array
|
||||||
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||||
|
|
||||||
line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
|
line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype = "f4") # Convert bytearray to numpy array
|
||||||
line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||||
|
|
||||||
line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array
|
line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype = "f4") # Convert bytearray to numpy array
|
||||||
line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||||
|
|
||||||
# Create a new 3D-array, copy the 2D points over and insert the right height.
|
# Create a new 3D-array, copy the 2D points over and insert the right height.
|
||||||
@ -194,7 +194,7 @@ class ProcessSlicedLayersJob(Job):
|
|||||||
manager = ExtruderManager.getInstance()
|
manager = ExtruderManager.getInstance()
|
||||||
extruders = manager.getActiveExtruderStacks()
|
extruders = manager.getActiveExtruderStacks()
|
||||||
if extruders:
|
if extruders:
|
||||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((len(extruders), 4), dtype = numpy.float32)
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
position = int(extruder.getMetaDataEntry("position", default = "0"))
|
position = int(extruder.getMetaDataEntry("position", default = "0"))
|
||||||
try:
|
try:
|
||||||
@ -206,8 +206,8 @@ class ProcessSlicedLayersJob(Job):
|
|||||||
material_color_map[position, :] = color
|
material_color_map[position, :] = color
|
||||||
else:
|
else:
|
||||||
# Single extruder via global stack.
|
# Single extruder via global stack.
|
||||||
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((1, 4), dtype = numpy.float32)
|
||||||
color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
|
color_code = global_container_stack.material.getMetaDataEntry("color_code", default = "#e0e000")
|
||||||
color = colorCodeToRGBA(color_code)
|
color = colorCodeToRGBA(color_code)
|
||||||
material_color_map[0, :] = color
|
material_color_map[0, :] = color
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import Arcus #For typing.
|
|||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Settings.ContainerStack import ContainerStack #For typing.
|
from UM.Settings.ContainerStack import ContainerStack #For typing.
|
||||||
from UM.Settings.SettingRelation import SettingRelation #For typing.
|
from UM.Settings.SettingRelation import SettingRelation #For typing.
|
||||||
|
|
||||||
@ -107,7 +108,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()
|
||||||
@ -133,6 +134,14 @@ class StartSliceJob(Job):
|
|||||||
self.setResult(StartJobResult.BuildPlateError)
|
self.setResult(StartJobResult.BuildPlateError)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Wait for error checker to be done.
|
||||||
|
while CuraApplication.getInstance().getMachineErrorChecker().needToWaitForResult:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if CuraApplication.getInstance().getMachineErrorChecker().hasError:
|
||||||
|
self.setResult(StartJobResult.SettingError)
|
||||||
|
return
|
||||||
|
|
||||||
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
|
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
|
||||||
if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
|
if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
|
||||||
not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
|
not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
|
||||||
@ -150,7 +159,7 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
|
|
||||||
# Don't slice if there is a per object setting with an error value.
|
# Don't slice if there is a per object setting with an error value.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -160,15 +169,16 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
with self._scene.getSceneLock():
|
with self._scene.getSceneLock():
|
||||||
# Remove old layer data.
|
# Remove old layer data.
|
||||||
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()):
|
||||||
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
||||||
node.getParent().removeChild(node)
|
# Singe we walk through all nodes in the scene, they always have a parent.
|
||||||
|
cast(SceneNode, node.getParent()).removeChild(node)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Get the objects in their groups to print.
|
# Get the objects in their groups to print.
|
||||||
object_groups = []
|
object_groups = []
|
||||||
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||||
for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for node in OneAtATimeIterator(self._scene.getRoot()):
|
||||||
temp_list = []
|
temp_list = []
|
||||||
|
|
||||||
# Node can't be printed, so don't bother sending it.
|
# Node can't be printed, so don't bother sending it.
|
||||||
@ -183,7 +193,8 @@ class StartSliceJob(Job):
|
|||||||
children = node.getAllChildren()
|
children = node.getAllChildren()
|
||||||
children.append(node)
|
children.append(node)
|
||||||
for child_node in children:
|
for child_node in children:
|
||||||
if child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
|
mesh_data = child_node.getMeshData()
|
||||||
|
if mesh_data and mesh_data.getVertices() is not None:
|
||||||
temp_list.append(child_node)
|
temp_list.append(child_node)
|
||||||
|
|
||||||
if temp_list:
|
if temp_list:
|
||||||
@ -194,12 +205,10 @@ class StartSliceJob(Job):
|
|||||||
else:
|
else:
|
||||||
temp_list = []
|
temp_list = []
|
||||||
has_printing_mesh = False
|
has_printing_mesh = False
|
||||||
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()):
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
mesh_data = node.getMeshData()
|
||||||
per_object_stack = node.callDecoration("getStack")
|
if node.callDecoration("isSliceable") and mesh_data and mesh_data.getVertices() is not None:
|
||||||
is_non_printing_mesh = False
|
is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh"))
|
||||||
if per_object_stack:
|
|
||||||
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
|
|
||||||
|
|
||||||
# Find a reason not to add the node
|
# Find a reason not to add the node
|
||||||
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
|
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
|
||||||
@ -213,7 +222,7 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
#If the list doesn't have any model with suitable settings then clean the list
|
# If the list doesn't have any model with suitable settings then clean the list
|
||||||
# otherwise CuraEngine will crash
|
# otherwise CuraEngine will crash
|
||||||
if not has_printing_mesh:
|
if not has_printing_mesh:
|
||||||
temp_list.clear()
|
temp_list.clear()
|
||||||
@ -259,18 +268,19 @@ class StartSliceJob(Job):
|
|||||||
self._buildGlobalInheritsStackMessage(stack)
|
self._buildGlobalInheritsStackMessage(stack)
|
||||||
|
|
||||||
# Build messages for extruder stacks
|
# Build messages for extruder stacks
|
||||||
# Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first,
|
for extruder_stack in global_stack.extruderList:
|
||||||
# then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well.
|
|
||||||
extruder_stack_list = sorted(list(global_stack.extruders.items()), key = lambda item: int(item[0]))
|
|
||||||
for _, extruder_stack in extruder_stack_list:
|
|
||||||
self._buildExtruderMessage(extruder_stack)
|
self._buildExtruderMessage(extruder_stack)
|
||||||
|
|
||||||
for group in filtered_object_groups:
|
for group in filtered_object_groups:
|
||||||
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
||||||
if group[0].getParent() is not None and group[0].getParent().callDecoration("isGroup"):
|
parent = group[0].getParent()
|
||||||
self._handlePerObjectSettings(group[0].getParent(), group_message)
|
if parent is not None and parent.callDecoration("isGroup"):
|
||||||
|
self._handlePerObjectSettings(cast(CuraSceneNode, parent), group_message)
|
||||||
|
|
||||||
for object in group:
|
for object in group:
|
||||||
mesh_data = object.getMeshData()
|
mesh_data = object.getMeshData()
|
||||||
|
if mesh_data is None:
|
||||||
|
continue
|
||||||
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
|
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
|
||||||
translate = object.getWorldTransformation().getData()[:3, 3]
|
translate = object.getWorldTransformation().getData()[:3, 3]
|
||||||
|
|
||||||
@ -294,7 +304,7 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
obj.vertices = flat_verts
|
obj.vertices = flat_verts
|
||||||
|
|
||||||
self._handlePerObjectSettings(object, obj)
|
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
|
||||||
|
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
@ -337,25 +347,29 @@ class StartSliceJob(Job):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _cacheAllExtruderSettings(self):
|
||||||
|
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
|
||||||
|
|
||||||
|
# NB: keys must be strings for the string formatter
|
||||||
|
self._all_extruders_settings = {
|
||||||
|
"-1": self._buildReplacementTokens(global_stack)
|
||||||
|
}
|
||||||
|
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
|
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
||||||
|
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
||||||
|
|
||||||
## Replace setting tokens in a piece of g-code.
|
## Replace setting tokens in a piece of g-code.
|
||||||
# \param value A piece of g-code to replace tokens in.
|
# \param value A piece of g-code to replace tokens in.
|
||||||
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
|
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
|
||||||
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
|
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
|
||||||
if not self._all_extruders_settings:
|
if not self._all_extruders_settings:
|
||||||
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
|
self._cacheAllExtruderSettings()
|
||||||
|
|
||||||
# NB: keys must be strings for the string formatter
|
|
||||||
self._all_extruders_settings = {
|
|
||||||
"-1": self._buildReplacementTokens(global_stack)
|
|
||||||
}
|
|
||||||
|
|
||||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
|
||||||
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
|
||||||
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# any setting can be used as a token
|
# any setting can be used as a token
|
||||||
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
|
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
|
||||||
|
if self._all_extruders_settings is None:
|
||||||
|
return ""
|
||||||
settings = self._all_extruders_settings.copy()
|
settings = self._all_extruders_settings.copy()
|
||||||
settings["default_extruder_nr"] = default_extruder_nr
|
settings["default_extruder_nr"] = default_extruder_nr
|
||||||
return str(fmt.format(value, **settings))
|
return str(fmt.format(value, **settings))
|
||||||
@ -367,8 +381,14 @@ class StartSliceJob(Job):
|
|||||||
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
|
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
|
||||||
message = self._slice_message.addRepeatedMessage("extruders")
|
message = self._slice_message.addRepeatedMessage("extruders")
|
||||||
message.id = int(stack.getMetaDataEntry("position"))
|
message.id = int(stack.getMetaDataEntry("position"))
|
||||||
|
if not self._all_extruders_settings:
|
||||||
|
self._cacheAllExtruderSettings()
|
||||||
|
|
||||||
settings = self._buildReplacementTokens(stack)
|
if self._all_extruders_settings is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
extruder_nr = stack.getProperty("extruder_nr", "value")
|
||||||
|
settings = self._all_extruders_settings[str(extruder_nr)].copy()
|
||||||
|
|
||||||
# Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
|
# Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
|
||||||
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
|
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
|
||||||
@ -392,7 +412,13 @@ class StartSliceJob(Job):
|
|||||||
# The settings are taken from the global stack. This does not include any
|
# The settings are taken from the global stack. This does not include any
|
||||||
# per-extruder settings or per-object settings.
|
# per-extruder settings or per-object settings.
|
||||||
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
|
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
|
||||||
settings = self._buildReplacementTokens(stack)
|
if not self._all_extruders_settings:
|
||||||
|
self._cacheAllExtruderSettings()
|
||||||
|
|
||||||
|
if self._all_extruders_settings is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
settings = self._all_extruders_settings["-1"].copy()
|
||||||
|
|
||||||
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||||
start_gcode = settings["machine_start_gcode"]
|
start_gcode = settings["machine_start_gcode"]
|
||||||
|
@ -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,39 +20,43 @@ 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.
|
||||||
if profile is not None:
|
for upgraded_profile in upgraded_profiles:
|
||||||
results.append(profile)
|
serialization, new_id = upgraded_profile
|
||||||
|
profile = self._loadProfile(serialization, new_id)
|
||||||
|
if profile is not None:
|
||||||
|
results.append(profile)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
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,23 +68,24 @@ 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")
|
||||||
try:
|
try:
|
||||||
profile.deserialize(serialized)
|
profile.deserialize(serialized, file_name = profile_id)
|
||||||
except ContainerFormatError as e:
|
except ContainerFormatError as e:
|
||||||
Logger.log("e", "Error in the format of a container: %s", str(e))
|
Logger.log("e", "Error in the format of a container: %s", str(e))
|
||||||
return None
|
return None
|
||||||
@ -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)
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
from typing import Dict, List, NamedTuple, Optional, Union
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
from UM.Backend import Backend
|
from UM.Backend import Backend
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.LayerDataBuilder import LayerDataBuilder
|
from cura.LayerDataBuilder import LayerDataBuilder
|
||||||
from cura.LayerDataDecorator import LayerDataDecorator
|
from cura.LayerDataDecorator import LayerDataDecorator
|
||||||
from cura.LayerPolygon import LayerPolygon
|
from cura.LayerPolygon import LayerPolygon
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
import numpy
|
catalog = i18nCatalog("cura")
|
||||||
import math
|
|
||||||
import re
|
|
||||||
from typing import Dict, List, NamedTuple, Optional, Union
|
|
||||||
|
|
||||||
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
|
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
|
||||||
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
|
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
|
||||||
|
|
||||||
|
|
||||||
## This parser is intended to interpret the common firmware codes among all the
|
## This parser is intended to interpret the common firmware codes among all the
|
||||||
# different flavors
|
# different flavors
|
||||||
class FlavorParser:
|
class FlavorParser:
|
||||||
@ -33,7 +35,7 @@ class FlavorParser:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||||
self._cancelled = False
|
self._cancelled = False
|
||||||
self._message = None
|
self._message = None # type: Optional[Message]
|
||||||
self._layer_number = 0
|
self._layer_number = 0
|
||||||
self._extruder_number = 0
|
self._extruder_number = 0
|
||||||
self._clearValues()
|
self._clearValues()
|
||||||
@ -368,6 +370,8 @@ class FlavorParser:
|
|||||||
self._layer_type = LayerPolygon.InfillType
|
self._layer_type = LayerPolygon.InfillType
|
||||||
elif type == "SUPPORT-INTERFACE":
|
elif type == "SUPPORT-INTERFACE":
|
||||||
self._layer_type = LayerPolygon.SupportInterfaceType
|
self._layer_type = LayerPolygon.SupportInterfaceType
|
||||||
|
elif type == "PRIME-TOWER":
|
||||||
|
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)
|
||||||
|
|
||||||
@ -425,7 +429,8 @@ class FlavorParser:
|
|||||||
|
|
||||||
if line.startswith("M"):
|
if line.startswith("M"):
|
||||||
M = self._getInt(line, "M")
|
M = self._getInt(line, "M")
|
||||||
self.processMCode(M, line, current_position, current_path)
|
if M is not None:
|
||||||
|
self.processMCode(M, line, current_position, current_path)
|
||||||
|
|
||||||
# "Flush" leftovers. Last layer paths are still stored
|
# "Flush" leftovers. Last layer paths are still stored
|
||||||
if len(current_path) > 1:
|
if len(current_path) > 1:
|
||||||
@ -463,7 +468,7 @@ class FlavorParser:
|
|||||||
Logger.log("w", "File doesn't contain any valid layers")
|
Logger.log("w", "File doesn't contain any valid layers")
|
||||||
|
|
||||||
settings = CuraApplication.getInstance().getGlobalContainerStack()
|
settings = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not settings.getProperty("machine_center_is_zero", "value"):
|
if settings is not None and not settings.getProperty("machine_center_is_zero", "value"):
|
||||||
machine_width = settings.getProperty("machine_width", "value")
|
machine_width = settings.getProperty("machine_width", "value")
|
||||||
machine_depth = settings.getProperty("machine_depth", "value")
|
machine_depth = settings.getProperty("machine_depth", "value")
|
||||||
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
|
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -57,7 +57,10 @@ Item
|
|||||||
{
|
{
|
||||||
text: catalog.i18nc("@title:label", "Printer Settings")
|
text: catalog.i18nc("@title:label", "Printer Settings")
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
|
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)"
|
||||||
@ -172,7 +175,10 @@ Item
|
|||||||
{
|
{
|
||||||
text: catalog.i18nc("@title:label", "Printhead Settings")
|
text: catalog.i18nc("@title:label", "Printhead Settings")
|
||||||
font: UM.Theme.getFont("medium_bold")
|
font: UM.Theme.getFont("medium_bold")
|
||||||
|
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"
|
||||||
@ -189,6 +195,7 @@ Item
|
|||||||
|
|
||||||
axisName: "x"
|
axisName: "x"
|
||||||
axisMinOrMax: "min"
|
axisMinOrMax: "min"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
@ -207,6 +214,7 @@ Item
|
|||||||
|
|
||||||
axisName: "y"
|
axisName: "y"
|
||||||
axisMinOrMax: "min"
|
axisMinOrMax: "min"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
@ -225,6 +233,7 @@ Item
|
|||||||
|
|
||||||
axisName: "x"
|
axisName: "x"
|
||||||
axisMinOrMax: "max"
|
axisMinOrMax: "max"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
@ -245,6 +254,7 @@ Item
|
|||||||
|
|
||||||
axisName: "y"
|
axisName: "y"
|
||||||
axisMinOrMax: "max"
|
axisMinOrMax: "max"
|
||||||
|
allowNegativeValue: true
|
||||||
|
|
||||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||||
}
|
}
|
||||||
@ -283,18 +293,30 @@ Item
|
|||||||
optionModel: ListModel
|
optionModel: ListModel
|
||||||
{
|
{
|
||||||
id: extruderCountModel
|
id: extruderCountModel
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
extruderCountModel.clear()
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
function update()
|
||||||
|
{
|
||||||
|
clear()
|
||||||
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
|
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
|
||||||
{
|
{
|
||||||
// Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
|
// Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
|
||||||
// takes a QVariant as value, and Number gets translated into a float. This will cause problem
|
// takes a QVariant as value, and Number gets translated into a float. This will cause problem
|
||||||
// for integer settings such as "Number of Extruders".
|
// for integer settings such as "Number of Extruders".
|
||||||
extruderCountModel.append({ text: String(i), value: String(i) })
|
append({ text: String(i), value: String(i) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: Cura.MachineManager
|
||||||
|
onGlobalContainerChanged: extruderCountModel.update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,15 @@ Rectangle
|
|||||||
id: viewportOverlay
|
id: viewportOverlay
|
||||||
|
|
||||||
property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection
|
property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection
|
||||||
property bool isNetworkConfigurable: ["Ultimaker 3", "Ultimaker 3 Extended", "Ultimaker S5"].indexOf(Cura.MachineManager.activeMachineDefinitionName) > -1
|
property bool isNetworkConfigurable:
|
||||||
|
{
|
||||||
|
if(Cura.MachineManager.activeMachine === null)
|
||||||
|
{
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return Cura.MachineManager.activeMachine.supportsNetworkConnection
|
||||||
|
}
|
||||||
|
|
||||||
property bool isNetworkConfigured:
|
property bool isNetworkConfigured:
|
||||||
{
|
{
|
||||||
// Readability:
|
// Readability:
|
||||||
@ -89,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
|
||||||
@ -98,7 +106,6 @@ Rectangle
|
|||||||
width: contentWidth
|
width: contentWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 3: CAN NOT MONITOR
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: noNetworkLabel
|
id: noNetworkLabel
|
||||||
@ -106,24 +113,8 @@ Rectangle
|
|||||||
{
|
{
|
||||||
horizontalCenter: parent.horizontalCenter
|
horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
visible: !isNetworkConfigured
|
|
||||||
text: catalog.i18nc("@info", "Please select a network connected printer to monitor.")
|
|
||||||
font: UM.Theme.getFont("medium")
|
|
||||||
color: UM.Theme.getColor("monitor_text_primary")
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: contentWidth
|
|
||||||
lineHeight: UM.Theme.getSize("monitor_text_line_large").height
|
|
||||||
lineHeightMode: Text.FixedHeight
|
|
||||||
}
|
|
||||||
Label
|
|
||||||
{
|
|
||||||
id: noNetworkUltimakerLabel
|
|
||||||
anchors
|
|
||||||
{
|
|
||||||
horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
visible: !isNetworkConfigured && isNetworkConfigurable
|
visible: !isNetworkConfigured && isNetworkConfigurable
|
||||||
text: catalog.i18nc("@info", "Please connect your Ultimaker printer to your local network.")
|
text: catalog.i18nc("@info", "Please connect your printer to the network.")
|
||||||
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
|
||||||
@ -135,7 +126,7 @@ Rectangle
|
|||||||
{
|
{
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
left: noNetworkUltimakerLabel.left
|
left: noNetworkLabel.left
|
||||||
}
|
}
|
||||||
visible: !isNetworkConfigured && isNetworkConfigurable
|
visible: !isNetworkConfigured && isNetworkConfigurable
|
||||||
height: UM.Theme.getSize("monitor_text_line").height
|
height: UM.Theme.getSize("monitor_text_line").height
|
||||||
@ -160,7 +151,7 @@ Rectangle
|
|||||||
verticalCenter: externalLinkIcon.verticalCenter
|
verticalCenter: externalLinkIcon.verticalCenter
|
||||||
}
|
}
|
||||||
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")
|
||||||
linkColor: UM.Theme.getColor("monitor_text_link")
|
linkColor: UM.Theme.getColor("monitor_text_link")
|
||||||
text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
|
text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
@ -170,14 +161,8 @@ Rectangle
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
|
onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
|
||||||
onEntered:
|
onEntered: manageQueueText.font.underline = true
|
||||||
{
|
onExited: manageQueueText.font.underline = false
|
||||||
manageQueueText.font.underline = true
|
|
||||||
}
|
|
||||||
onExited:
|
|
||||||
{
|
|
||||||
manageQueueText.font.underline = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,17 @@ UM.TooltipArea
|
|||||||
UM.ActiveTool.forceUpdate();
|
UM.ActiveTool.forceUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the user removes settings from the list addedSettingsModel, we need to recheck if the
|
||||||
|
// setting is visible or not to show a mark in the CheckBox.
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: addedSettingsModel
|
||||||
|
onVisibleCountChanged:
|
||||||
|
{
|
||||||
|
check.checked = addedSettingsModel.getVisible(model.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -162,7 +162,7 @@ class PostProcessingPlugin(QObject, Extension):
|
|||||||
loaded_script = importlib.util.module_from_spec(spec)
|
loaded_script = importlib.util.module_from_spec(spec)
|
||||||
if spec.loader is None:
|
if spec.loader is None:
|
||||||
continue
|
continue
|
||||||
spec.loader.exec_module(loaded_script)
|
spec.loader.exec_module(loaded_script) # type: ignore
|
||||||
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
|
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
|
||||||
|
|
||||||
loaded_class = getattr(loaded_script, script_name)
|
loaded_class = getattr(loaded_script, script_name)
|
||||||
@ -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
|
||||||
@ -481,9 +483,9 @@ class ChangeAtZ(Script):
|
|||||||
state = 2
|
state = 2
|
||||||
done_layers = 0
|
done_layers = 0
|
||||||
if targetL_i > -100000:
|
if targetL_i > -100000:
|
||||||
modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version,targetL_i)
|
modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version, targetL_i)
|
||||||
else:
|
else:
|
||||||
modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version,targetZ)
|
modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version, targetZ)
|
||||||
if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
|
if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
|
||||||
modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
|
modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
|
||||||
else: #executes on RepRap, UM2 with Ultigcode and Cura setting
|
else: #executes on RepRap, UM2 with Ultigcode and Cura setting
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
# Cura PostProcessingPlugin
|
# Cura PostProcessingPlugin
|
||||||
# Author: Amanda de Castilho
|
# Author: Amanda de Castilho
|
||||||
# Date: August 28, 2018
|
# Date: August 28, 2018
|
||||||
|
# Modified: November 16, 2018 by Joshua Pope-Lewis
|
||||||
|
|
||||||
# Description: This plugin inserts a line at the start of each layer,
|
# Description: This plugin shows custom messages about your print on the Status bar...
|
||||||
# M117 - displays the filename and layer height to the LCD
|
# Please look at the 3 options
|
||||||
# Alternatively, user can override the filename to display alt text + layer height
|
# - Scolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you arent printing a small item select this option.
|
||||||
|
# - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - Type a custom name in here
|
||||||
|
# - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!)
|
||||||
|
|
||||||
from ..Script import Script
|
from ..Script import Script
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
@ -15,35 +18,72 @@ class DisplayFilenameAndLayerOnLCD(Script):
|
|||||||
|
|
||||||
def getSettingDataString(self):
|
def getSettingDataString(self):
|
||||||
return """{
|
return """{
|
||||||
"name": "Display filename and layer on LCD",
|
"name": "Display Filename And Layer On LCD",
|
||||||
"key": "DisplayFilenameAndLayerOnLCD",
|
"key": "DisplayFilenameAndLayerOnLCD",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"settings":
|
"settings":
|
||||||
{
|
{
|
||||||
|
"scroll":
|
||||||
|
{
|
||||||
|
"label": "Scroll enabled/Small layers?",
|
||||||
|
"description": "If SCROLL_LONG_FILENAMES is enabled select this setting however, if the model is small disable this setting!",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
"name":
|
"name":
|
||||||
{
|
{
|
||||||
"label": "text to display:",
|
"label": "Text to display:",
|
||||||
"description": "By default the current filename will be displayed on the LCD. Enter text here to override the filename and display something else.",
|
"description": "By default the current filename will be displayed on the LCD. Enter text here to override the filename and display something else.",
|
||||||
"type": "str",
|
"type": "str",
|
||||||
"default_value": ""
|
"default_value": ""
|
||||||
|
},
|
||||||
|
"startNum":
|
||||||
|
{
|
||||||
|
"label": "Initial layer number:",
|
||||||
|
"description": "Choose which number you prefer for the initial layer, 0 or 1",
|
||||||
|
"type": "int",
|
||||||
|
"default_value": 0,
|
||||||
|
"minimum_value": 0,
|
||||||
|
"maximum_value": 1
|
||||||
|
},
|
||||||
|
"maxlayer":
|
||||||
|
{
|
||||||
|
"label": "Display max layer?:",
|
||||||
|
"description": "Display how many layers are in the entire print on status bar?",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
def execute(self, data):
|
def execute(self, data):
|
||||||
|
max_layer = 0
|
||||||
if self.getSettingValueByKey("name") != "":
|
if self.getSettingValueByKey("name") != "":
|
||||||
name = self.getSettingValueByKey("name")
|
name = self.getSettingValueByKey("name")
|
||||||
else:
|
else:
|
||||||
name = Application.getInstance().getPrintInformation().jobName
|
name = Application.getInstance().getPrintInformation().jobName
|
||||||
lcd_text = "M117 " + name + " layer "
|
if not self.getSettingValueByKey("scroll"):
|
||||||
i = 0
|
if self.getSettingValueByKey("maxlayer"):
|
||||||
|
lcd_text = "M117 Layer "
|
||||||
|
else:
|
||||||
|
lcd_text = "M117 Printing Layer "
|
||||||
|
else:
|
||||||
|
lcd_text = "M117 Printing " + name + " - Layer "
|
||||||
|
i = self.getSettingValueByKey("startNum")
|
||||||
for layer in data:
|
for layer in data:
|
||||||
display_text = lcd_text + str(i)
|
display_text = lcd_text + str(i) + " " + name
|
||||||
layer_index = data.index(layer)
|
layer_index = data.index(layer)
|
||||||
lines = layer.split("\n")
|
lines = layer.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
if line.startswith(";LAYER_COUNT:"):
|
||||||
|
max_layer = line
|
||||||
|
max_layer = max_layer.split(":")[1]
|
||||||
if line.startswith(";LAYER:"):
|
if line.startswith(";LAYER:"):
|
||||||
|
if self.getSettingValueByKey("maxlayer"):
|
||||||
|
display_text = display_text + " of " + max_layer
|
||||||
|
else:
|
||||||
|
display_text = display_text + "!"
|
||||||
line_index = lines.index(line)
|
line_index = lines.index(line)
|
||||||
lines.insert(line_index + 1, display_text)
|
lines.insert(line_index + 1, display_text)
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -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:
|
continue
|
||||||
Logger.log("e", "Could not found the layer")
|
if 0 < layer_num < len(data):
|
||||||
continue
|
data[layer_num] = color_change + data[layer_num]
|
||||||
lines = layer_data.split("\n")
|
|
||||||
lines.insert(2, color_change)
|
|
||||||
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
|
|
@ -1,15 +1,16 @@
|
|||||||
# 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 ..Script import Script
|
from ..Script import Script
|
||||||
|
|
||||||
from UM.Application import Application #To get the current printer's settings.
|
from UM.Application import Application #To get the current printer's settings.
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
class PauseAtHeight(Script):
|
class PauseAtHeight(Script):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def getSettingDataString(self):
|
def getSettingDataString(self) -> str:
|
||||||
return """{
|
return """{
|
||||||
"name": "Pause at height",
|
"name": "Pause at height",
|
||||||
"key": "PauseAtHeight",
|
"key": "PauseAtHeight",
|
||||||
@ -113,11 +114,9 @@ class PauseAtHeight(Script):
|
|||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
def getNextXY(self, layer: str):
|
## Get the X and Y values for a layer (will be used to get X and Y of the
|
||||||
"""
|
# layer after the pause).
|
||||||
Get the X and Y values for a layer (will be used to get X and Y of
|
def getNextXY(self, layer: str) -> Tuple[float, float]:
|
||||||
the layer after the pause
|
|
||||||
"""
|
|
||||||
lines = layer.split("\n")
|
lines = layer.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
|
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
|
||||||
@ -126,8 +125,10 @@ class PauseAtHeight(Script):
|
|||||||
return x, y
|
return x, y
|
||||||
return 0, 0
|
return 0, 0
|
||||||
|
|
||||||
def execute(self, data: list):
|
## Inserts the pause commands.
|
||||||
"""data is a list. Each index contains a layer"""
|
# \param data: List of layers.
|
||||||
|
# \return New list of layers.
|
||||||
|
def execute(self, data: List[str]) -> List[str]:
|
||||||
pause_at = self.getSettingValueByKey("pause_at")
|
pause_at = self.getSettingValueByKey("pause_at")
|
||||||
pause_height = self.getSettingValueByKey("pause_height")
|
pause_height = self.getSettingValueByKey("pause_height")
|
||||||
pause_layer = self.getSettingValueByKey("pause_layer")
|
pause_layer = self.getSettingValueByKey("pause_layer")
|
||||||
|
@ -128,9 +128,26 @@ class Stretcher():
|
|||||||
onestep = GCodeStep(0, in_relative_movement)
|
onestep = GCodeStep(0, in_relative_movement)
|
||||||
onestep.copyPosFrom(current)
|
onestep.copyPosFrom(current)
|
||||||
elif _getValue(line, "G") == 1:
|
elif _getValue(line, "G") == 1:
|
||||||
|
last_x = current.step_x
|
||||||
|
last_y = current.step_y
|
||||||
|
last_z = current.step_z
|
||||||
|
last_e = current.step_e
|
||||||
current.readStep(line)
|
current.readStep(line)
|
||||||
onestep = GCodeStep(1, in_relative_movement)
|
if (current.step_x == last_x and current.step_y == last_y and
|
||||||
onestep.copyPosFrom(current)
|
current.step_z == last_z and current.step_e != last_e
|
||||||
|
):
|
||||||
|
# It's an extruder only move. Preserve it rather than process it as an
|
||||||
|
# extruded move. Otherwise, the stretched output might contain slight
|
||||||
|
# motion in X and Y in addition to E. This can cause problems with
|
||||||
|
# firmwares that implement pressure advance.
|
||||||
|
onestep = GCodeStep(-1, in_relative_movement)
|
||||||
|
onestep.copyPosFrom(current)
|
||||||
|
# Rather than copy the original line, write a new one with consistent
|
||||||
|
# extruder coordinates
|
||||||
|
onestep.comment = "G1 F{} E{}".format(onestep.step_f, onestep.step_e)
|
||||||
|
else:
|
||||||
|
onestep = GCodeStep(1, in_relative_movement)
|
||||||
|
onestep.copyPosFrom(current)
|
||||||
|
|
||||||
# end of relative movement
|
# end of relative movement
|
||||||
elif _getValue(line, "G") == 90:
|
elif _getValue(line, "G") == 90:
|
||||||
@ -145,6 +162,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)
|
||||||
|
@ -20,11 +20,19 @@ Item
|
|||||||
name: "cura"
|
name: "cura"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||||
|
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||||
|
}
|
||||||
|
|
||||||
// Item to ensure that all of the buttons are nicely centered.
|
// Item to ensure that all of the buttons are nicely centered.
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width
|
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
RowLayout
|
RowLayout
|
||||||
@ -32,9 +40,9 @@ Item
|
|||||||
id: itemRow
|
id: itemRow
|
||||||
|
|
||||||
anchors.left: openFileButton.right
|
anchors.left: openFileButton.right
|
||||||
|
anchors.right: parent.right
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
width: Math.round(0.9 * prepareMenu.width)
|
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
@ -20,15 +20,21 @@ Item
|
|||||||
name: "cura"
|
name: "cura"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||||
|
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||||
|
}
|
||||||
|
|
||||||
Row
|
Row
|
||||||
{
|
{
|
||||||
id: stageMenuRow
|
id: stageMenuRow
|
||||||
anchors.centerIn: parent
|
|
||||||
height: parent.height
|
|
||||||
width: childrenRect.width
|
|
||||||
|
|
||||||
// We want this row to have a preferred with equals to the 85% of the parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
property int preferredWidth: Math.round(0.85 * previewMenu.width)
|
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
Cura.ViewsSelector
|
Cura.ViewsSelector
|
||||||
{
|
{
|
||||||
@ -49,12 +55,12 @@ Item
|
|||||||
color: UM.Theme.getColor("lining")
|
color: UM.Theme.getColor("lining")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This component will grow freely up to complete the preferredWidth of the row.
|
// This component will grow freely up to complete the width of the row.
|
||||||
Loader
|
Loader
|
||||||
{
|
{
|
||||||
id: viewPanel
|
id: viewPanel
|
||||||
height: parent.height
|
height: parent.height
|
||||||
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
|
width: source != "" ? (previewMenu.width - viewsSelector.width - printSetupSelectorItem.width - 2 * (UM.Theme.getSize("wide_margin").width + UM.Theme.getSize("default_lining").width)) : 0
|
||||||
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
|
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,10 +218,10 @@ class SimulationView(CuraView):
|
|||||||
if theme is not None:
|
if theme is not None:
|
||||||
self._ghost_shader.setUniformValue("u_color", Color(*theme.getColor("layerview_ghost").getRgb()))
|
self._ghost_shader.setUniformValue("u_color", Color(*theme.getColor("layerview_ghost").getRgb()))
|
||||||
|
|
||||||
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
|
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
|
||||||
# However, it is somewhat relevant when the node is selected, so do render it then.
|
# However, it is somewhat relevant when the node is selected, so do render it then.
|
||||||
if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
|
if type(node) is ConvexHullNode and not Selection.isSelected(cast(ConvexHullNode, node).getWatchedNode()):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not node.render(renderer):
|
if not node.render(renderer):
|
||||||
@ -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:
|
||||||
|
@ -15,6 +15,8 @@ Cura.ExpandableComponent
|
|||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
|
|
||||||
|
dragPreferencesNamePrefix: "view/colorscheme"
|
||||||
|
|
||||||
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
|
@ -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
|
||||||
|
@ -71,7 +71,7 @@ Window
|
|||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.")
|
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
@ -89,6 +89,8 @@ Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
textArea.text: manager.getExampleData()
|
textArea.text: manager.getExampleData()
|
||||||
|
textArea.textFormat: Text.RichText
|
||||||
|
textArea.wrapMode: Text.Wrap
|
||||||
textArea.readOnly: true
|
textArea.readOnly: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +106,7 @@ Window
|
|||||||
Cura.RadioButton
|
Cura.RadioButton
|
||||||
{
|
{
|
||||||
id: dontSendButton
|
id: dontSendButton
|
||||||
text: catalog.i18nc("@text:window", "I don't want to send this data")
|
text: catalog.i18nc("@text:window", "I don't want to send anonymous data")
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
baseDialog.allowSendData = !checked
|
baseDialog.allowSendData = !checked
|
||||||
@ -113,7 +115,7 @@ Window
|
|||||||
Cura.RadioButton
|
Cura.RadioButton
|
||||||
{
|
{
|
||||||
id: allowSendButton
|
id: allowSendButton
|
||||||
text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura")
|
text: catalog.i18nc("@text:window", "Allow sending anonymous data")
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
baseDialog.allowSendData = checked
|
baseDialog.allowSendData = checked
|
||||||
|
@ -77,7 +77,7 @@ class SliceInfo(QObject, Extension):
|
|||||||
if not plugin_path:
|
if not plugin_path:
|
||||||
Logger.log("e", "Could not get plugin path!", self.getPluginId())
|
Logger.log("e", "Could not get plugin path!", self.getPluginId())
|
||||||
return None
|
return None
|
||||||
file_path = os.path.join(plugin_path, "example_data.json")
|
file_path = os.path.join(plugin_path, "example_data.html")
|
||||||
if file_path:
|
if file_path:
|
||||||
with open(file_path, "r", encoding = "utf-8") as f:
|
with open(file_path, "r", encoding = "utf-8") as f:
|
||||||
self._example_data_content = f.read()
|
self._example_data_content = f.read()
|
||||||
@ -126,6 +126,10 @@ class SliceInfo(QObject, Extension):
|
|||||||
else:
|
else:
|
||||||
data["active_mode"] = "custom"
|
data["active_mode"] = "custom"
|
||||||
|
|
||||||
|
data["camera_view"] = application.getPreferences().getValue("general/camera_perspective_mode")
|
||||||
|
if data["camera_view"] == "orthographic":
|
||||||
|
data["camera_view"] = "orthogonal" #The database still only recognises the old name "orthogonal".
|
||||||
|
|
||||||
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":
|
||||||
@ -181,6 +185,8 @@ class SliceInfo(QObject, Extension):
|
|||||||
model = dict()
|
model = dict()
|
||||||
model["hash"] = node.getMeshData().getHash()
|
model["hash"] = node.getMeshData().getHash()
|
||||||
bounding_box = node.getBoundingBox()
|
bounding_box = node.getBoundingBox()
|
||||||
|
if not bounding_box:
|
||||||
|
continue
|
||||||
model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
|
model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
|
||||||
"y": bounding_box.minimum.y,
|
"y": bounding_box.minimum.y,
|
||||||
"z": bounding_box.minimum.z},
|
"z": bounding_box.minimum.z},
|
||||||
|
64
plugins/SliceInfoPlugin/example_data.html
Normal file
64
plugins/SliceInfoPlugin/example_data.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<b>Cura Version:</b> 4.0<br/>
|
||||||
|
<b>Operating System:</b> Windows 10<br/>
|
||||||
|
<b>Language:</b> en_US<br/>
|
||||||
|
<b>Machine Type:</b> Ultimaker S5<br/>
|
||||||
|
<b>Quality Profile:</b> Fast<br/>
|
||||||
|
<b>Using Custom Settings:</b> No
|
||||||
|
|
||||||
|
<h3>Extruder 1:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><b>Material Type:</b> PLA</li>
|
||||||
|
<li><b>Print Core:</b> AA 0.4</li>
|
||||||
|
<li><b>Material Used:</b> 1240 mm</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Extruder 2:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><b>Material Type:</b> PVA</li>
|
||||||
|
<li><b>Print Core:</b> BB 0.4</li>
|
||||||
|
<li><b>Material Used:</b> 432 mm</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Print Settings:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><b>Layer Height:</b> 0.15</li>
|
||||||
|
<li><b>Wall Line Count:</b> 3</li>
|
||||||
|
<li><b>Enable Retraction:</b> no</li>
|
||||||
|
<li><b>Infill Density:</b> 20%</li>
|
||||||
|
<li><b>Infill Pattern:</b> triangles</li>
|
||||||
|
<li><b>Gradual Infill Steps:</b> 0</li>
|
||||||
|
<li><b>Printing Temperature:</b> 220 °C</li>
|
||||||
|
<li><b>Generate Support:</b> yes</li>
|
||||||
|
<li><b>Support Extruder:</b> 1</li>
|
||||||
|
<li><b>Build Plate Adhesion Type:</b> brim</li>
|
||||||
|
<li><b>Enable Prime Tower:</b> yes</li>
|
||||||
|
<li><b>Print Sequence:</b> All at once</li>
|
||||||
|
<li>...</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Model Information:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Model 1</b>
|
||||||
|
<ul>
|
||||||
|
<li><b>Hash:</b> b72789b9b...</li>
|
||||||
|
<li><b>Transformation:</b> [transformation matrix]</li>
|
||||||
|
<li><b>Bounding Box:</b> [minimum x, y, z; maximum x, y, z]</li>
|
||||||
|
<li><b>Is Helper Mesh:</b> no</li>
|
||||||
|
<li><b>Helper Mesh Type:</b> support mesh</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Print Times:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Infill: 61200 sec.</li>
|
||||||
|
<li>Support: 25480 sec.</li>
|
||||||
|
<li>Travel: 6224 sec.</li>
|
||||||
|
<li>Walls: 10225 sec.</li>
|
||||||
|
<li>Total: 103129 sec.</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user