mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-10 07:15:03 -06:00
Merge branch 'master' into libArachne_rebased
Conflicts: cura/CuraApplication.py: Setting version fdmprinter and fdmextruder: Setting version intents, qualities and variants: Setting version Changelog: 4.9.1 was added, should be below Arachne changes
This commit is contained in:
commit
611208368c
1015 changed files with 103196 additions and 86144 deletions
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from configparser import ConfigParser
|
||||
|
@ -412,7 +412,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)]
|
||||
quality_type = "empty_quality"
|
||||
if quality_container_id not in ("empty", "empty_quality"):
|
||||
quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
|
||||
if quality_container_id in instance_container_info_dict:
|
||||
quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
|
||||
else: # If a version upgrade changed the quality profile in the stack, we'll need to look for it in the built-in profiles instead of the workspace.
|
||||
quality_matches = ContainerRegistry.getInstance().findContainersMetadata(id = quality_container_id)
|
||||
if quality_matches: # If there's no profile with this ID, leave it empty_quality.
|
||||
quality_type = quality_matches[0]["quality_type"]
|
||||
|
||||
# Get machine info
|
||||
serialized = archive.open(global_stack_file).read().decode("utf-8")
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for reading 3MF files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for writing 3MF files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
"author": "fieldOfView",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading AMF files.",
|
||||
"api": "7.5.0"
|
||||
"api": "7.6.0"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"version": "1.2.0",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "CuraEngine Backend",
|
||||
"author": "Ultimaker B.V.",
|
||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"version": "1.0.1",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for importing Cura profiles.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for exporting Cura profiles.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog":"cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
|
||||
"version": "1.0.0",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -65,6 +65,11 @@ Item
|
|||
model: manager.digitalFactoryFileModel
|
||||
visible: model.count != 0 && manager.retrievingFileStatus != DF.RetrievalStatus.InProgress
|
||||
selectionMode: OldControls.SelectionMode.SingleSelection
|
||||
onDoubleClicked:
|
||||
{
|
||||
manager.setSelectedFileIndices([row]);
|
||||
openFilesButton.clicked();
|
||||
}
|
||||
|
||||
OldControls.TableViewColumn
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import json
|
||||
import threading
|
||||
from json import JSONDecodeError
|
||||
from typing import List, Dict, Any, Callable, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
@ -43,7 +44,7 @@ class DFFileExportAndUploadManager:
|
|||
self._library_project_id = library_project_id # type: str
|
||||
self._library_project_name = library_project_name # type: str
|
||||
self._file_name = file_name # type: str
|
||||
|
||||
self._upload_jobs = [] # type: List[ExportFileJob]
|
||||
self._formats = formats # type: List[str]
|
||||
self._api = DigitalFactoryApiClient(application = CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error)))
|
||||
|
||||
|
@ -80,6 +81,8 @@ class DFFileExportAndUploadManager:
|
|||
)
|
||||
self._generic_success_message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
|
||||
|
||||
|
||||
def _onCuraProjectFileExported(self, job: ExportFileJob) -> None:
|
||||
"""Handler for when the DF Library workspace file (3MF) has been created locally.
|
||||
|
||||
|
@ -271,7 +274,11 @@ class DFFileExportAndUploadManager:
|
|||
def extractErrorTitle(reply_body: Optional[str]) -> str:
|
||||
error_title = ""
|
||||
if reply_body:
|
||||
reply_dict = json.loads(reply_body)
|
||||
try:
|
||||
reply_dict = json.loads(reply_body)
|
||||
except JSONDecodeError:
|
||||
Logger.logException("w", "Unable to extract title from reply body")
|
||||
return error_title
|
||||
if "errors" in reply_dict and len(reply_dict["errors"]) >= 1 and "title" in reply_dict["errors"][0]:
|
||||
error_title = reply_dict["errors"][0]["title"]
|
||||
return error_title
|
||||
|
@ -313,8 +320,13 @@ class DFFileExportAndUploadManager:
|
|||
QDesktopServices.openUrl(QUrl(project_url))
|
||||
message.hide()
|
||||
|
||||
def start(self) -> None:
|
||||
for job in self._upload_jobs:
|
||||
job.start()
|
||||
|
||||
def initializeFileUploadJobMetadata(self) -> Dict[str, Any]:
|
||||
metadata = {}
|
||||
self._upload_jobs = []
|
||||
if "3mf" in self._formats and "3mf" in self._file_handlers and self._file_handlers["3mf"]:
|
||||
filename_3mf = self._file_name + ".3mf"
|
||||
metadata[filename_3mf] = {
|
||||
|
@ -335,7 +347,7 @@ class DFFileExportAndUploadManager:
|
|||
}
|
||||
job_3mf = ExportFileJob(self._file_handlers["3mf"], self._nodes, self._file_name, "3mf")
|
||||
job_3mf.finished.connect(self._onCuraProjectFileExported)
|
||||
job_3mf.start()
|
||||
self._upload_jobs.append(job_3mf)
|
||||
|
||||
if "ufp" in self._formats and "ufp" in self._file_handlers and self._file_handlers["ufp"]:
|
||||
filename_ufp = self._file_name + ".ufp"
|
||||
|
@ -357,5 +369,5 @@ class DFFileExportAndUploadManager:
|
|||
}
|
||||
job_ufp = ExportFileJob(self._file_handlers["ufp"], self._nodes, self._file_name, "ufp")
|
||||
job_ufp.finished.connect(self._onPrintFileExported)
|
||||
job_ufp.start()
|
||||
self._upload_jobs.append(job_ufp)
|
||||
return metadata
|
||||
|
|
|
@ -385,6 +385,11 @@ class DigitalFactoryController(QObject):
|
|||
def _applicationInitializationFinished(self) -> None:
|
||||
self._supported_file_types = self._application.getInstance().getMeshFileHandler().getSupportedFileTypesRead()
|
||||
|
||||
# Although Cura supports these, it's super confusing in this context to show them.
|
||||
for extension in ["jpg", "jpeg", "png", "bmp", "gif"]:
|
||||
if extension in self._supported_file_types:
|
||||
del self._supported_file_types[extension]
|
||||
|
||||
@pyqtSlot()
|
||||
def openSelectedFiles(self) -> None:
|
||||
""" Downloads, then opens all files selected in the Qt frontend open dialog.
|
||||
|
@ -541,6 +546,7 @@ class DigitalFactoryController(QObject):
|
|||
on_upload_success = self.uploadFileSuccess.emit,
|
||||
on_upload_finished = self.uploadFileFinished.emit,
|
||||
on_upload_progress = self.uploadFileProgress.emit)
|
||||
self.file_upload_manager.start()
|
||||
|
||||
# Save the project id to make sure it will be preselected the next time the user opens the save dialog
|
||||
self._current_workspace_information.setEntryToStore("digital_factory", "library_project_id", library_project_id)
|
||||
|
|
|
@ -40,6 +40,7 @@ class DigitalFactoryFileModel(ListModel):
|
|||
def setFiles(self, df_files_in_project: List[DigitalFactoryFileResponse]) -> None:
|
||||
if self._files == df_files_in_project:
|
||||
return
|
||||
self.clear()
|
||||
self._files = df_files_in_project
|
||||
self._update()
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class DigitalFactoryProjectModel(ListModel):
|
|||
|
||||
dfProjectModelChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.addRoleName(self.DisplayNameRole, "displayName")
|
||||
self.addRoleName(self.LibraryProjectIdRole, "libraryProjectId")
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from src.DFFileExportAndUploadManager import DFFileExportAndUploadManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def upload_manager():
|
||||
file_handler = MagicMock(name = "file_handler")
|
||||
file_handler.getSupportedFileTypesWrite = MagicMock(return_value = [{
|
||||
"id": "test",
|
||||
"extension": ".3mf",
|
||||
"description": "nope",
|
||||
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||
"mode": "binary",
|
||||
"hide_in_file_dialog": True,
|
||||
}])
|
||||
node = MagicMock(name = "SceneNode")
|
||||
application = MagicMock(name = "CuraApplication")
|
||||
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
|
||||
return DFFileExportAndUploadManager(file_handlers = {"3mf": file_handler},
|
||||
nodes = [node],
|
||||
library_project_id = "test_library_project_id",
|
||||
library_project_name = "test_library_project_name",
|
||||
file_name = "file_name",
|
||||
formats = ["3mf"],
|
||||
on_upload_error = MagicMock(),
|
||||
on_upload_success = MagicMock(),
|
||||
on_upload_finished = MagicMock(),
|
||||
on_upload_progress = MagicMock())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input,expected_result",
|
||||
[("", ""),
|
||||
("invalid json! {}", ""),
|
||||
("{\"errors\": [{}]}", ""),
|
||||
("{\"errors\": [{\"title\": \"some title\"}]}", "some title")])
|
||||
def test_extractErrorTitle(upload_manager, input, expected_result):
|
||||
assert upload_manager.extractErrorTitle(input) == expected_result
|
||||
|
||||
|
||||
def test_exportJobError(upload_manager):
|
||||
mocked_application = MagicMock()
|
||||
with patch("UM.Application.Application.getInstance", MagicMock(return_value = mocked_application)):
|
||||
upload_manager._onJobExportError("file_name.3mf")
|
||||
|
||||
# Ensure that message was displayed
|
||||
mocked_application.showMessageSignal.emit.assert_called_once()
|
73
plugins/DigitalLibrary/tests/TestDigitalFactoryFileModel.py
Normal file
73
plugins/DigitalLibrary/tests/TestDigitalFactoryFileModel.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from pathlib import Path
|
||||
|
||||
from src.DigitalFactoryFileModel import DigitalFactoryFileModel
|
||||
from src.DigitalFactoryFileResponse import DigitalFactoryFileResponse
|
||||
|
||||
|
||||
file_1 = DigitalFactoryFileResponse(client_id = "client_id_1",
|
||||
content_type = "zomg",
|
||||
file_name = "file_1.3mf",
|
||||
file_id = "file_id_1",
|
||||
library_project_id = "project_id_1",
|
||||
status = "test",
|
||||
user_id = "user_id_1",
|
||||
username = "username_1",
|
||||
uploaded_at = "2021-04-07T10:33:25.000Z")
|
||||
|
||||
file_2 = DigitalFactoryFileResponse(client_id ="client_id_2",
|
||||
content_type = "zomg",
|
||||
file_name = "file_2.3mf",
|
||||
file_id = "file_id_2",
|
||||
library_project_id = "project_id_2",
|
||||
status = "test",
|
||||
user_id = "user_id_2",
|
||||
username = "username_2",
|
||||
uploaded_at = "2021-02-06T09:33:22.000Z")
|
||||
|
||||
file_wtf = DigitalFactoryFileResponse(client_id ="client_id_1",
|
||||
content_type = "zomg",
|
||||
file_name = "file_3.wtf",
|
||||
file_id = "file_id_3",
|
||||
library_project_id = "project_id_1",
|
||||
status = "test",
|
||||
user_id = "user_id_1",
|
||||
username = "username_1",
|
||||
uploaded_at = "2021-04-06T12:33:25.000Z")
|
||||
|
||||
|
||||
def test_setFiles():
|
||||
model = DigitalFactoryFileModel()
|
||||
|
||||
assert model.count == 0
|
||||
|
||||
model.setFiles([file_1, file_2])
|
||||
assert model.count == 2
|
||||
|
||||
assert model.getItem(0)["fileName"] == "file_1.3mf"
|
||||
assert model.getItem(1)["fileName"] == "file_2.3mf"
|
||||
|
||||
|
||||
def test_clearProjects():
|
||||
model = DigitalFactoryFileModel()
|
||||
model.setFiles([file_1, file_2])
|
||||
model.clearFiles()
|
||||
assert model.count == 0
|
||||
|
||||
|
||||
def test_setProjectMultipleTimes():
|
||||
model = DigitalFactoryFileModel()
|
||||
model.setFiles([file_1, file_2])
|
||||
model.setFiles([file_2])
|
||||
assert model.count == 1
|
||||
assert model.getItem(0)["fileName"] == "file_2.3mf"
|
||||
|
||||
|
||||
def test_setFilter():
|
||||
model = DigitalFactoryFileModel()
|
||||
|
||||
model.setFiles([file_1, file_2, file_wtf])
|
||||
model.setFilters({"file_name": lambda x: Path(x).suffix[1:].lower() in ["3mf"]})
|
||||
assert model.count == 2
|
||||
|
||||
model.clearFilters()
|
||||
assert model.count == 3
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
from src.DigitalFactoryProjectModel import DigitalFactoryProjectModel
|
||||
from src.DigitalFactoryProjectResponse import DigitalFactoryProjectResponse
|
||||
|
||||
|
||||
project_1 = DigitalFactoryProjectResponse(library_project_id = "omg",
|
||||
display_name = "zomg",
|
||||
username = "nope",
|
||||
organization_shared = True)
|
||||
|
||||
project_2 = DigitalFactoryProjectResponse(library_project_id = "omg2",
|
||||
display_name = "zomg2",
|
||||
username = "nope",
|
||||
organization_shared = False)
|
||||
|
||||
|
||||
def test_setProjects():
|
||||
model = DigitalFactoryProjectModel()
|
||||
|
||||
assert model.count == 0
|
||||
|
||||
model.setProjects([project_1, project_2])
|
||||
assert model.count == 2
|
||||
|
||||
assert model.getItem(0)["displayName"] == "zomg"
|
||||
assert model.getItem(1)["displayName"] == "zomg2"
|
||||
|
||||
|
||||
def test_clearProjects():
|
||||
model = DigitalFactoryProjectModel()
|
||||
model.setProjects([project_1, project_2])
|
||||
model.clearProjects()
|
||||
assert model.count == 0
|
||||
|
||||
|
||||
def test_setProjectMultipleTimes():
|
||||
model = DigitalFactoryProjectModel()
|
||||
model.setProjects([project_1, project_2])
|
||||
model.setProjects([project_2])
|
||||
assert model.count == 1
|
||||
assert model.getItem(0)["displayName"] == "zomg2"
|
||||
|
||||
|
||||
def test_extendProjects():
|
||||
model = DigitalFactoryProjectModel()
|
||||
|
||||
assert model.count == 0
|
||||
|
||||
model.setProjects([project_1])
|
||||
assert model.count == 1
|
||||
|
||||
model.extendProjects([project_2])
|
||||
assert model.count == 2
|
||||
assert model.getItem(0)["displayName"] == "zomg"
|
||||
assert model.getItem(1)["displayName"] == "zomg2"
|
86
plugins/DigitalLibrary/tests/TestDigitalLibraryApiClient.py
Normal file
86
plugins/DigitalLibrary/tests/TestDigitalLibraryApiClient.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from src.DigitalFactoryApiClient import DigitalFactoryApiClient
|
||||
from src.PaginationManager import PaginationManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application():
|
||||
app = MagicMock(spec=CuraApplication, name = "Mocked Cura Application")
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pagination_manager():
|
||||
manager = MagicMock(name = "Mocked Pagination Manager")
|
||||
return manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_client(application, pagination_manager):
|
||||
api_client = DigitalFactoryApiClient(application, MagicMock())
|
||||
api_client._projects_pagination_mgr = pagination_manager
|
||||
return api_client
|
||||
|
||||
|
||||
def test_getProjectsFirstPage(api_client):
|
||||
# setup
|
||||
http_manager = MagicMock()
|
||||
api_client._http = http_manager
|
||||
pagination_manager = api_client._projects_pagination_mgr
|
||||
pagination_manager.limit = 20
|
||||
|
||||
finished_callback = MagicMock()
|
||||
failed_callback = MagicMock()
|
||||
|
||||
# Call
|
||||
api_client.getProjectsFirstPage(on_finished = finished_callback, failed = failed_callback)
|
||||
|
||||
# Asserts
|
||||
pagination_manager.reset.assert_called_once() # Should be called since we asked for new set of projects
|
||||
http_manager.get.assert_called_once()
|
||||
args = http_manager.get.call_args_list[0]
|
||||
|
||||
# Ensure that it's called with the right limit
|
||||
assert args[0][0] == "https://api.ultimaker.com/cura/v1/projects?limit=20"
|
||||
|
||||
# Change the limit & try again
|
||||
http_manager.get.reset_mock()
|
||||
pagination_manager.limit = 80
|
||||
api_client.getProjectsFirstPage(on_finished = finished_callback, failed = failed_callback)
|
||||
args = http_manager.get.call_args_list[0]
|
||||
|
||||
# Ensure that it's called with the right limit
|
||||
assert args[0][0] == "https://api.ultimaker.com/cura/v1/projects?limit=80"
|
||||
|
||||
|
||||
def test_getMoreProjects_noNewProjects(api_client):
|
||||
api_client.hasMoreProjectsToLoad = MagicMock(return_value = False)
|
||||
http_manager = MagicMock()
|
||||
api_client._http = http_manager
|
||||
|
||||
finished_callback = MagicMock()
|
||||
failed_callback = MagicMock()
|
||||
api_client.getMoreProjects(finished_callback, failed_callback)
|
||||
|
||||
http_manager.get.assert_not_called()
|
||||
|
||||
|
||||
def test_getMoreProjects_hasNewProjects(api_client):
|
||||
api_client.hasMoreProjectsToLoad = MagicMock(return_value = True)
|
||||
http_manager = MagicMock()
|
||||
api_client._http = http_manager
|
||||
|
||||
finished_callback = MagicMock()
|
||||
failed_callback = MagicMock()
|
||||
api_client.getMoreProjects(finished_callback, failed_callback)
|
||||
|
||||
http_manager.get.assert_called_once()
|
||||
|
||||
|
||||
def test_clear(api_client):
|
||||
api_client.clear()
|
||||
api_client._projects_pagination_mgr.reset.assert_called_once()
|
5
plugins/DigitalLibrary/tests/conftest.py
Normal file
5
plugins/DigitalLibrary/tests/conftest.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
# Ensure that the importing for all tests work
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Checks for firmware updates.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Reads g-code from a compressed archive.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Writes g-code to a compressed archive.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for importing profiles from g-code files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Victor Larchenko, Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Allows loading and displaying G-code files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Writes g-code to a file.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "fieldOfView, Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Model Checker",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a monitor stage in Cura.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -73,38 +73,40 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||
|
||||
# Add all instances that are not added, but are in visibility list
|
||||
for item in visible:
|
||||
if settings.getInstance(item) is None: # Setting was not added already.
|
||||
definition = self._stack.getSettingDefinition(item)
|
||||
if definition:
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
if settings.getInstance(item) is not None: # Setting was added already.
|
||||
continue
|
||||
definition = self._stack.getSettingDefinition(item)
|
||||
if not definition:
|
||||
Logger.log("w", f"Unable to add instance ({item}) to per-object visibility because we couldn't find the matching definition.")
|
||||
continue
|
||||
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
stack_nr = -1
|
||||
stack = None
|
||||
# Check from what stack we should copy the raw property of the setting from.
|
||||
if self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
if definition.limit_to_extruder != "-1":
|
||||
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
||||
|
||||
# Check if the found stack_number is in the extruder list of extruders.
|
||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
|
||||
stack_nr = -1
|
||||
stack = None
|
||||
# Check from what stack we should copy the raw property of the setting from.
|
||||
if self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
if definition.limit_to_extruder != "-1":
|
||||
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
||||
|
||||
# Check if the found stack_number is in the extruder list of extruders.
|
||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
|
||||
stack_nr = -1
|
||||
# Use the found stack number to get the right stack to copy the value from.
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||
else:
|
||||
stack = self._stack
|
||||
|
||||
# Use the found stack number to get the right stack to copy the value from.
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||
else:
|
||||
stack = self._stack
|
||||
|
||||
# Use the raw property to set the value (so the inheritance doesn't break)
|
||||
if stack is not None:
|
||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||
else:
|
||||
new_instance.setProperty("value", None)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
visibility_changed = True
|
||||
else:
|
||||
Logger.log("w", "Unable to add instance (%s) to per-object visibility because we couldn't find the matching definition", item)
|
||||
# Use the raw property to set the value (so the inheritance doesn't break)
|
||||
if stack is not None:
|
||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||
else:
|
||||
new_instance.setProperty("value", None)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
visibility_changed = True
|
||||
|
||||
if visibility_changed:
|
||||
self.visibilityChanged.emit()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Copyright (c) 2021 Ultimaker B.V.
|
||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
|
@ -136,10 +136,12 @@ Item
|
|||
}
|
||||
|
||||
|
||||
ComboBox
|
||||
Cura.ComboBox
|
||||
{
|
||||
id: infillOnlyComboBox
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
textRole: "text"
|
||||
|
||||
model: ListModel
|
||||
{
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides the Per Model Settings.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
# Copyright (c) 2020 Jaime van Kessel, Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
# Workaround for a race condition on certain systems where there
|
||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
||||
# first seems to prevent Sip from going into a state where it
|
||||
# tries to create PyQt objects on a non-main thread.
|
||||
import Arcus # @UnusedImport
|
||||
import Savitar # @UnusedImport
|
||||
import pynest2d # @UnusedImport
|
||||
|
||||
from . import PostProcessingPlugin
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Post Processing",
|
||||
"author": "Ultimaker",
|
||||
"version": "2.2.1",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"description": "Extension that allows for user created scripts for post processing",
|
||||
"catalog": "cura"
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
# Modification 06.09.2020
|
||||
# add checkbox, now you can choose and use configuration from the firmware itself.
|
||||
|
||||
from typing import List
|
||||
from ..Script import Script
|
||||
|
||||
|
@ -13,7 +16,7 @@ class FilamentChange(Script):
|
|||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name":"Filament Change",
|
||||
"name": "Filament Change",
|
||||
"key": "FilamentChange",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
|
@ -27,14 +30,21 @@ class FilamentChange(Script):
|
|||
"type": "str",
|
||||
"default_value": "1"
|
||||
},
|
||||
|
||||
"firmware_config":
|
||||
{
|
||||
"label": "Use Firmware Configuration",
|
||||
"description": "Use the settings in your firmware, or customise the parameters of the filament change here.",
|
||||
"type": "bool",
|
||||
"default_value": false
|
||||
},
|
||||
"initial_retract":
|
||||
{
|
||||
"label": "Initial Retraction",
|
||||
"description": "Initial filament retraction distance. The filament will be retracted with this amount before moving the nozzle away from the ongoing print.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 30.0
|
||||
"default_value": 30.0,
|
||||
"enabled": "not firmware_config"
|
||||
},
|
||||
"later_retract":
|
||||
{
|
||||
|
@ -42,7 +52,8 @@ class FilamentChange(Script):
|
|||
"description": "Later filament retraction distance for removal. The filament will be retracted all the way out of the printer so that you can change the filament.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 300.0
|
||||
"default_value": 300.0,
|
||||
"enabled": "not firmware_config"
|
||||
},
|
||||
"x_position":
|
||||
{
|
||||
|
@ -50,7 +61,8 @@ class FilamentChange(Script):
|
|||
"description": "Extruder X position. The print head will move here for filament change.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0
|
||||
"default_value": 0,
|
||||
"enabled": "not firmware_config"
|
||||
},
|
||||
"y_position":
|
||||
{
|
||||
|
@ -58,7 +70,17 @@ class FilamentChange(Script):
|
|||
"description": "Extruder Y position. The print head will move here for filament change.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0
|
||||
"default_value": 0,
|
||||
"enabled": "not firmware_config"
|
||||
},
|
||||
"z_position":
|
||||
{
|
||||
"label": "Z Position (relative)",
|
||||
"description": "Extruder relative Z position. Move the print head up for filament change.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
@ -74,20 +96,26 @@ class FilamentChange(Script):
|
|||
later_retract = self.getSettingValueByKey("later_retract")
|
||||
x_pos = self.getSettingValueByKey("x_position")
|
||||
y_pos = self.getSettingValueByKey("y_position")
|
||||
z_pos = self.getSettingValueByKey("z_position")
|
||||
firmware_config = self.getSettingValueByKey("firmware_config")
|
||||
|
||||
color_change = "M600"
|
||||
|
||||
if initial_retract is not None and initial_retract > 0.:
|
||||
color_change = color_change + (" E%.2f" % initial_retract)
|
||||
if not firmware_config:
|
||||
if initial_retract is not None and initial_retract > 0.:
|
||||
color_change = color_change + (" E%.2f" % initial_retract)
|
||||
|
||||
if later_retract is not None and later_retract > 0.:
|
||||
color_change = color_change + (" L%.2f" % later_retract)
|
||||
if later_retract is not None and later_retract > 0.:
|
||||
color_change = color_change + (" L%.2f" % later_retract)
|
||||
|
||||
if x_pos is not None:
|
||||
color_change = color_change + (" X%.2f" % x_pos)
|
||||
|
||||
if y_pos is not None:
|
||||
color_change = color_change + (" Y%.2f" % y_pos)
|
||||
if x_pos is not None:
|
||||
color_change = color_change + (" X%.2f" % x_pos)
|
||||
|
||||
if y_pos is not None:
|
||||
color_change = color_change + (" Y%.2f" % y_pos)
|
||||
|
||||
if z_pos is not None and z_pos > 0.:
|
||||
color_change = color_change + (" Z%.2f" % z_pos)
|
||||
|
||||
color_change = color_change + " ; Generated by FilamentChange plugin\n"
|
||||
|
||||
|
@ -101,4 +129,4 @@ class FilamentChange(Script):
|
|||
if 0 < layer_num < len(data):
|
||||
data[layer_num] = color_change + data[layer_num]
|
||||
|
||||
return data
|
||||
return data
|
||||
|
|
|
@ -387,7 +387,7 @@ class PauseAtHeight(Script):
|
|||
#Retraction
|
||||
prepend_gcode += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n"
|
||||
if retraction_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = 6000) + "\n"
|
||||
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = 6000) + "\n"
|
||||
|
||||
#Move the head away
|
||||
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + " ; move up a millimeter to get out of the way\n"
|
||||
|
@ -507,7 +507,15 @@ class PauseAtHeight(Script):
|
|||
else:
|
||||
Logger.log("w", "No previous feedrate found in gcode, feedrate for next layer(s) might be incorrect")
|
||||
|
||||
prepend_gcode += self.putValue(M = 82) + " ; switch back to absolute E values\n"
|
||||
extrusion_mode_string = "absolute"
|
||||
extrusion_mode_numeric = 82
|
||||
|
||||
relative_extrusion = Application.getInstance().getGlobalContainerStack().getProperty("relative_extrusion", "value")
|
||||
if relative_extrusion:
|
||||
extrusion_mode_string = "relative"
|
||||
extrusion_mode_numeric = 83
|
||||
|
||||
prepend_gcode += self.putValue(M = extrusion_mode_numeric) + " ; switch back to " + extrusion_mode_string + " E values\n"
|
||||
|
||||
# reset extrude value to pre pause value
|
||||
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a prepare stage in Cura.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a preview stage in Cura.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Provides removable drive hotplugging and writing support.",
|
||||
"version": "1.0.1",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class SimulationPass(RenderPass):
|
|||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
if not self._compatibility_mode:
|
||||
self._layer_shader.setUniformValue("u_starts_color", Color(*Application.getInstance().getTheme().getColor("layerview_starts").getRgb()))
|
||||
|
||||
|
||||
if self._layer_view:
|
||||
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
|
||||
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
|
||||
|
@ -73,6 +73,8 @@ class SimulationPass(RenderPass):
|
|||
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
|
||||
self._layer_shader.setUniformValue("u_max_line_width", self._layer_view.getMaxLineWidth())
|
||||
self._layer_shader.setUniformValue("u_min_line_width", self._layer_view.getMinLineWidth())
|
||||
self._layer_shader.setUniformValue("u_max_flow_rate", self._layer_view.getMaxFlowRate())
|
||||
self._layer_shader.setUniformValue("u_min_flow_rate", self._layer_view.getMinFlowRate())
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
|
||||
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
|
||||
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
|
||||
|
@ -86,6 +88,8 @@ class SimulationPass(RenderPass):
|
|||
self._layer_shader.setUniformValue("u_min_feedrate", 0)
|
||||
self._layer_shader.setUniformValue("u_max_thickness", 1)
|
||||
self._layer_shader.setUniformValue("u_min_thickness", 0)
|
||||
self._layer_shader.setUniformValue("u_max_flow_rate", 1)
|
||||
self._layer_shader.setUniformValue("u_min_flow_rate", 0)
|
||||
self._layer_shader.setUniformValue("u_max_line_width", 1)
|
||||
self._layer_shader.setUniformValue("u_min_line_width", 0)
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", 1)
|
||||
|
@ -174,9 +178,9 @@ class SimulationPass(RenderPass):
|
|||
self._switching_layers = True
|
||||
|
||||
# The first line does not have a previous line: add a MoveCombingType in front for start detection
|
||||
# this way the first start of the layer can also be drawn
|
||||
# this way the first start of the layer can also be drawn
|
||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||
# Remove the last element
|
||||
# Remove the last element
|
||||
prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
|
||||
layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import sys
|
||||
|
@ -30,6 +30,7 @@ from UM.View.GL.ShaderProgram import ShaderProgram
|
|||
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura.CuraView import CuraView
|
||||
from cura.LayerPolygon import LayerPolygon # To distinguish line types.
|
||||
from cura.Scene.ConvexHullNode import ConvexHullNode
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
@ -93,6 +94,8 @@ class SimulationView(CuraView):
|
|||
self._min_thickness = sys.float_info.max
|
||||
self._max_line_width = sys.float_info.min
|
||||
self._min_line_width = sys.float_info.max
|
||||
self._min_flow_rate = sys.float_info.max
|
||||
self._max_flow_rate = sys.float_info.min
|
||||
|
||||
self._global_container_stack = None # type: Optional[ContainerStack]
|
||||
self._proxy = None
|
||||
|
@ -115,6 +118,7 @@ class SimulationView(CuraView):
|
|||
Application.getInstance().getPreferences().addPreference("layerview/show_infill", True)
|
||||
Application.getInstance().getPreferences().addPreference("layerview/show_starts", True)
|
||||
|
||||
self.visibleStructuresChanged.connect(self.calculateColorSchemeLimits)
|
||||
self._updateWithPreferences()
|
||||
|
||||
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
||||
|
@ -198,6 +202,7 @@ class SimulationView(CuraView):
|
|||
if node.getMeshData() is None:
|
||||
return
|
||||
self.setActivity(False)
|
||||
self.calculateColorSchemeLimits()
|
||||
self.calculateMaxLayers()
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
|
@ -218,12 +223,6 @@ class SimulationView(CuraView):
|
|||
def resetLayerData(self) -> None:
|
||||
self._current_layer_mesh = None
|
||||
self._current_layer_jumps = None
|
||||
self._max_feedrate = sys.float_info.min
|
||||
self._min_feedrate = sys.float_info.max
|
||||
self._max_thickness = sys.float_info.min
|
||||
self._min_thickness = sys.float_info.max
|
||||
self._max_line_width = sys.float_info.min
|
||||
self._min_line_width = sys.float_info.max
|
||||
|
||||
def beginRendering(self) -> None:
|
||||
scene = self.getController().getScene()
|
||||
|
@ -248,58 +247,59 @@ class SimulationView(CuraView):
|
|||
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
|
||||
|
||||
def setLayer(self, value: int) -> None:
|
||||
"""
|
||||
Set the upper end of the range of visible layers.
|
||||
|
||||
If setting it below the lower end of the range, the lower end is lowered so that 1 layer stays visible.
|
||||
:param value: The new layer number to show, 0-indexed.
|
||||
"""
|
||||
if self._current_layer_num != value:
|
||||
self._current_layer_num = value
|
||||
if self._current_layer_num < 0:
|
||||
self._current_layer_num = 0
|
||||
if self._current_layer_num > self._max_layers:
|
||||
self._current_layer_num = self._max_layers
|
||||
if self._current_layer_num < self._minimum_layer_num:
|
||||
self._minimum_layer_num = self._current_layer_num
|
||||
self._current_layer_num = min(max(value, 0), self._max_layers)
|
||||
self._minimum_layer_num = min(self._current_layer_num, self._minimum_layer_num)
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def setMinimumLayer(self, value: int) -> None:
|
||||
"""
|
||||
Set the lower end of the range of visible layers.
|
||||
|
||||
If setting it above the upper end of the range, the upper end is increased so that 1 layer stays visible.
|
||||
:param value: The new lower end of the range of visible layers, 0-indexed.
|
||||
"""
|
||||
if self._minimum_layer_num != value:
|
||||
self._minimum_layer_num = value
|
||||
if self._minimum_layer_num < 0:
|
||||
self._minimum_layer_num = 0
|
||||
if self._minimum_layer_num > self._max_layers:
|
||||
self._minimum_layer_num = self._max_layers
|
||||
if self._minimum_layer_num > self._current_layer_num:
|
||||
self._current_layer_num = self._minimum_layer_num
|
||||
self._minimum_layer_num = min(max(value, 0), self._max_layers)
|
||||
self._current_layer_num = max(self._current_layer_num, self._minimum_layer_num)
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def setPath(self, value: int) -> None:
|
||||
"""
|
||||
Set the upper end of the range of visible paths on the current layer.
|
||||
|
||||
If setting it below the lower end of the range, the lower end is lowered so that 1 path stays visible.
|
||||
:param value: The new path index to show, 0-indexed.
|
||||
"""
|
||||
if self._current_path_num != value:
|
||||
self._current_path_num = value
|
||||
if self._current_path_num < 0:
|
||||
self._current_path_num = 0
|
||||
if self._current_path_num > self._max_paths:
|
||||
self._current_path_num = self._max_paths
|
||||
if self._current_path_num < self._minimum_path_num:
|
||||
self._minimum_path_num = self._current_path_num
|
||||
self._current_path_num = min(max(value, 0), self._max_paths)
|
||||
self._minimum_path_num = min(self._minimum_path_num, self._current_path_num)
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
self.currentPathNumChanged.emit()
|
||||
|
||||
def setMinimumPath(self, value: int) -> None:
|
||||
"""
|
||||
Set the lower end of the range of visible paths on the current layer.
|
||||
|
||||
If setting it above the upper end of the range, the upper end is increased so that 1 path stays visible.
|
||||
:param value: The new lower end of the range of visible paths, 0-indexed.
|
||||
"""
|
||||
if self._minimum_path_num != value:
|
||||
self._minimum_path_num = value
|
||||
if self._minimum_path_num < 0:
|
||||
self._minimum_path_num = 0
|
||||
if self._minimum_path_num > self._max_layers:
|
||||
self._minimum_path_num = self._max_layers
|
||||
if self._minimum_path_num > self._current_path_num:
|
||||
self._current_path_num = self._minimum_path_num
|
||||
self._minimum_path_num = min(max(value, 0), self._max_paths)
|
||||
self._current_path_num = max(self._current_path_num, self._minimum_path_num)
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.currentPathNumChanged.emit()
|
||||
|
||||
def setSimulationViewType(self, layer_view_type: int) -> None:
|
||||
|
@ -333,37 +333,52 @@ class SimulationView(CuraView):
|
|||
# If more than 16 extruders are called for, this should be converted to a sampler1d.
|
||||
return Matrix(self._extruder_opacity)
|
||||
|
||||
def setShowTravelMoves(self, show):
|
||||
def setShowTravelMoves(self, show: bool) -> None:
|
||||
if show == self._show_travel_moves:
|
||||
return
|
||||
self._show_travel_moves = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
self.visibleStructuresChanged.emit()
|
||||
|
||||
def getShowTravelMoves(self):
|
||||
def getShowTravelMoves(self) -> bool:
|
||||
return self._show_travel_moves
|
||||
|
||||
def setShowHelpers(self, show: bool) -> None:
|
||||
if show == self._show_helpers:
|
||||
return
|
||||
self._show_helpers = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
self.visibleStructuresChanged.emit()
|
||||
|
||||
def getShowHelpers(self) -> bool:
|
||||
return self._show_helpers
|
||||
|
||||
def setShowSkin(self, show: bool) -> None:
|
||||
if show == self._show_skin:
|
||||
return
|
||||
self._show_skin = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
self.visibleStructuresChanged.emit()
|
||||
|
||||
def getShowSkin(self) -> bool:
|
||||
return self._show_skin
|
||||
|
||||
def setShowInfill(self, show: bool) -> None:
|
||||
if show == self._show_infill:
|
||||
return
|
||||
self._show_infill = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
self.visibleStructuresChanged.emit()
|
||||
|
||||
def getShowInfill(self) -> bool:
|
||||
return self._show_infill
|
||||
|
||||
def setShowStarts(self, show: bool) -> None:
|
||||
if show == self._show_starts:
|
||||
return
|
||||
self._show_starts = show
|
||||
self.currentLayerNumChanged.emit()
|
||||
self.visibleStructuresChanged.emit()
|
||||
|
||||
def getShowStarts(self) -> bool:
|
||||
return self._show_starts
|
||||
|
@ -398,12 +413,23 @@ class SimulationView(CuraView):
|
|||
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||
return self._min_line_width
|
||||
|
||||
def getMaxFlowRate(self) -> float:
|
||||
return self._max_flow_rate
|
||||
|
||||
def getMinFlowRate(self) -> float:
|
||||
if abs(self._min_flow_rate - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
|
||||
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||
return self._min_flow_rate
|
||||
|
||||
def calculateMaxLayers(self) -> None:
|
||||
"""
|
||||
Calculates number of layers, triggers signals if the number of layers changed and makes sure the top layers are
|
||||
recalculated for legacy layer view.
|
||||
"""
|
||||
scene = self.getController().getScene()
|
||||
|
||||
self._old_max_layers = self._max_layers
|
||||
new_max_layers = -1
|
||||
"""Recalculate num max layers"""
|
||||
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
|
@ -418,19 +444,6 @@ class SimulationView(CuraView):
|
|||
if len(layer_data.getLayer(layer_id).polygons) < 1:
|
||||
continue
|
||||
|
||||
# Store the max and min feedrates and thicknesses for display purposes
|
||||
for p in layer_data.getLayer(layer_id).polygons:
|
||||
self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate)
|
||||
self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate)
|
||||
self._max_line_width = max(float(p.lineWidths.max()), self._max_line_width)
|
||||
self._min_line_width = min(float(p.lineWidths.min()), self._min_line_width)
|
||||
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
|
||||
try:
|
||||
self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness)
|
||||
except ValueError:
|
||||
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding
|
||||
# the zero) can't be calculated
|
||||
Logger.log("i", "Min thickness can't be calculated because all the values are zero")
|
||||
if max_layer_number < layer_id:
|
||||
max_layer_number = layer_id
|
||||
if min_layer_number > layer_id:
|
||||
|
@ -454,6 +467,87 @@ class SimulationView(CuraView):
|
|||
self.maxLayersChanged.emit()
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
def calculateColorSchemeLimits(self) -> None:
|
||||
"""
|
||||
Calculates the limits of the colour schemes, depending on the layer view data that is visible to the user.
|
||||
"""
|
||||
# Before we start, save the old values so that we can tell if any of the spectrums need to change.
|
||||
old_min_feedrate = self._min_feedrate
|
||||
old_max_feedrate = self._max_feedrate
|
||||
old_min_linewidth = self._min_line_width
|
||||
old_max_linewidth = self._max_line_width
|
||||
old_min_thickness = self._min_thickness
|
||||
old_max_thickness = self._max_thickness
|
||||
old_min_flow_rate = self._min_flow_rate
|
||||
old_max_flow_rate = self._max_flow_rate
|
||||
|
||||
self._min_feedrate = sys.float_info.max
|
||||
self._max_feedrate = sys.float_info.min
|
||||
self._min_line_width = sys.float_info.max
|
||||
self._max_line_width = sys.float_info.min
|
||||
self._min_thickness = sys.float_info.max
|
||||
self._max_thickness = sys.float_info.min
|
||||
self._min_flow_rate = sys.float_info.max
|
||||
self._max_flow_rate = sys.float_info.min
|
||||
|
||||
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
|
||||
visible_line_types = []
|
||||
if self.getShowSkin(): # Actually "shell".
|
||||
visible_line_types.append(LayerPolygon.SkinType)
|
||||
visible_line_types.append(LayerPolygon.Inset0Type)
|
||||
visible_line_types.append(LayerPolygon.InsetXType)
|
||||
if self.getShowInfill():
|
||||
visible_line_types.append(LayerPolygon.InfillType)
|
||||
if self.getShowHelpers():
|
||||
visible_line_types.append(LayerPolygon.PrimeTowerType)
|
||||
visible_line_types.append(LayerPolygon.SkirtType)
|
||||
visible_line_types.append(LayerPolygon.SupportType)
|
||||
visible_line_types.append(LayerPolygon.SupportInfillType)
|
||||
visible_line_types.append(LayerPolygon.SupportInterfaceType)
|
||||
visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added
|
||||
if self.getShowTravelMoves():
|
||||
visible_line_types.append(LayerPolygon.MoveCombingType)
|
||||
visible_line_types.append(LayerPolygon.MoveRetractionType)
|
||||
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
for layer_index in layer_data.getLayers():
|
||||
for polyline in layer_data.getLayer(layer_index).polygons:
|
||||
is_visible = numpy.isin(polyline.types, visible_line_types)
|
||||
visible_indices = numpy.where(is_visible)[0]
|
||||
visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0]
|
||||
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
||||
continue
|
||||
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
||||
visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion)
|
||||
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
|
||||
visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
|
||||
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
||||
visible_thicknesses_with_extrusion = numpy.take(polyline.lineThicknesses, visible_indicies_with_extrusion)
|
||||
self._max_feedrate = max(float(visible_feedrates.max()), self._max_feedrate)
|
||||
if visible_feedrates_with_extrusion.size != 0:
|
||||
flow_rates = visible_feedrates_with_extrusion * visible_linewidths_with_extrusion * visible_thicknesses_with_extrusion
|
||||
self._min_flow_rate = min(float(flow_rates.min()), self._min_flow_rate)
|
||||
self._max_flow_rate = max(float(flow_rates.max()), self._max_flow_rate)
|
||||
self._min_feedrate = min(float(visible_feedrates.min()), self._min_feedrate)
|
||||
self._max_line_width = max(float(visible_linewidths.max()), self._max_line_width)
|
||||
self._min_line_width = min(float(visible_linewidths.min()), self._min_line_width)
|
||||
self._max_thickness = max(float(visible_thicknesses.max()), self._max_thickness)
|
||||
try:
|
||||
self._min_thickness = min(float(visible_thicknesses[numpy.nonzero(visible_thicknesses)].min()), self._min_thickness)
|
||||
except ValueError:
|
||||
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding the zero) can't be calculated.
|
||||
Logger.log("w", "Min thickness can't be calculated because all the values are zero")
|
||||
|
||||
if old_min_feedrate != self._min_feedrate or old_max_feedrate != self._max_feedrate \
|
||||
or old_min_linewidth != self._min_line_width or old_max_linewidth != self._max_line_width \
|
||||
or old_min_thickness != self._min_thickness or old_max_thickness != self._max_thickness \
|
||||
or old_min_flow_rate != self._min_flow_rate or old_max_flow_rate != self._max_flow_rate:
|
||||
self.colorSchemeLimitsChanged.emit()
|
||||
|
||||
def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
|
||||
# Update the currentPath
|
||||
scene = self.getController().getScene()
|
||||
|
@ -480,6 +574,8 @@ class SimulationView(CuraView):
|
|||
preferencesChanged = Signal()
|
||||
busyChanged = Signal()
|
||||
activityChanged = Signal()
|
||||
visibleStructuresChanged = Signal()
|
||||
colorSchemeLimitsChanged = Signal()
|
||||
|
||||
def getProxy(self, engine, script_engine):
|
||||
"""Hackish way to ensure the proxy is already created
|
||||
|
@ -511,6 +607,7 @@ class SimulationView(CuraView):
|
|||
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
self.calculateColorSchemeLimits()
|
||||
self.calculateMaxLayers()
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ Cura.ExpandableComponent
|
|||
property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
|
||||
property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
|
||||
property bool show_line_width_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 4
|
||||
property bool show_flow_rate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 5
|
||||
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
||||
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
||||
|
||||
|
@ -125,6 +126,10 @@ Cura.ExpandableComponent
|
|||
text: catalog.i18nc("@label:listbox", "Line Width"),
|
||||
type_id: 4
|
||||
})
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Flow"),
|
||||
type_id: 5
|
||||
})
|
||||
}
|
||||
|
||||
ComboBox
|
||||
|
@ -150,10 +155,13 @@ Cura.ExpandableComponent
|
|||
{
|
||||
// Update the visibility of the legends.
|
||||
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
|
||||
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3 || type_id == 4);
|
||||
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode &&
|
||||
(type_id == 2 || type_id == 3 || type_id == 4 || type_id == 5) ;
|
||||
|
||||
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
|
||||
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
|
||||
viewSettings.show_line_width_gradient = viewSettings.show_gradient && (type_id == 4);
|
||||
viewSettings.show_flow_rate_gradient = viewSettings.show_gradient && (type_id == 5);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,18 +397,24 @@ Cura.ExpandableComponent
|
|||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
||||
return parseFloat(UM.SimulationView.minFeedrate).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
||||
return parseFloat(UM.SimulationView.minThickness).toFixed(2)
|
||||
}
|
||||
//Line width selected
|
||||
// Line width selected
|
||||
if(UM.Preferences.getValue("layerview/layer_view_type") == 4)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMinLineWidth()).toFixed(2);
|
||||
return parseFloat(UM.SimulationView.minLineWidth).toFixed(2);
|
||||
}
|
||||
// Flow Rate selected
|
||||
if(UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.minFlowRate).toFixed(2);
|
||||
}
|
||||
|
||||
}
|
||||
return catalog.i18nc("@label","min")
|
||||
}
|
||||
|
@ -431,6 +445,11 @@ Cura.ExpandableComponent
|
|||
{
|
||||
return "mm"
|
||||
}
|
||||
// Flow Rate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||
{
|
||||
return "mm³/s"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -448,17 +467,22 @@ Cura.ExpandableComponent
|
|||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
||||
return parseFloat(UM.SimulationView.maxFeedrate).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
||||
return parseFloat(UM.SimulationView.maxThickness).toFixed(2)
|
||||
}
|
||||
//Line width selected
|
||||
if(UM.Preferences.getValue("layerview/layer_view_type") == 4)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.getMaxLineWidth()).toFixed(2);
|
||||
return parseFloat(UM.SimulationView.maxLineWidth).toFixed(2);
|
||||
}
|
||||
// Flow rate selected
|
||||
if(UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||
{
|
||||
return parseFloat(UM.SimulationView.maxFlowRate).toFixed(2);
|
||||
}
|
||||
}
|
||||
return catalog.i18nc("@label","max")
|
||||
|
@ -474,7 +498,10 @@ Cura.ExpandableComponent
|
|||
Rectangle
|
||||
{
|
||||
id: feedrateGradient
|
||||
visible: viewSettings.show_feedrate_gradient || viewSettings.show_line_width_gradient
|
||||
visible: (
|
||||
viewSettings.show_feedrate_gradient ||
|
||||
viewSettings.show_line_width_gradient
|
||||
)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||
|
@ -526,7 +553,9 @@ Cura.ExpandableComponent
|
|||
Rectangle
|
||||
{
|
||||
id: thicknessGradient
|
||||
visible: viewSettings.show_thickness_gradient
|
||||
visible: (
|
||||
viewSettings.show_thickness_gradient
|
||||
)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||
|
@ -578,6 +607,85 @@ Cura.ExpandableComponent
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gradient colors for flow (similar to jet colormap)
|
||||
Rectangle
|
||||
{
|
||||
id: jetGradient
|
||||
visible: (
|
||||
viewSettings.show_flow_rate_gradient
|
||||
)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
|
||||
LinearGradient
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
leftMargin: UM.Theme.getSize("default_lining").width
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("default_lining").width
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_lining").width
|
||||
bottom: parent.bottom
|
||||
bottomMargin: UM.Theme.getSize("default_lining").width
|
||||
}
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(parent.width, 0)
|
||||
gradient: Gradient
|
||||
{
|
||||
GradientStop
|
||||
{
|
||||
position: 0.0
|
||||
color: Qt.rgba(0, 0, 0.5, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.125
|
||||
color: Qt.rgba(0, 0.0, 1.0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.25
|
||||
color: Qt.rgba(0, 0.5, 1.0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.375
|
||||
color: Qt.rgba(0.0, 1.0, 1.0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.5
|
||||
color: Qt.rgba(0.5, 1.0, 0.5, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.625
|
||||
color: Qt.rgba(1.0, 1.0, 0.0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.75
|
||||
color: Qt.rgba(1.0, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 0.875
|
||||
color: Qt.rgba(1.0, 0.0, 0, 1)
|
||||
}
|
||||
GradientStop
|
||||
{
|
||||
position: 1.0
|
||||
color: Qt.rgba(0.5, 0, 0, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -28,6 +28,7 @@ class SimulationViewProxy(QObject):
|
|||
globalStackChanged = pyqtSignal()
|
||||
preferencesChanged = pyqtSignal()
|
||||
busyChanged = pyqtSignal()
|
||||
colorSchemeLimitsChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify=activityChanged)
|
||||
def layerActivity(self):
|
||||
|
@ -101,30 +102,38 @@ class SimulationViewProxy(QObject):
|
|||
def getSimulationRunning(self):
|
||||
return self._simulation_view.isSimulationRunning()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMinFeedrate(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def minFeedrate(self):
|
||||
return self._simulation_view.getMinFeedrate()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMaxFeedrate(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def maxFeedrate(self):
|
||||
return self._simulation_view.getMaxFeedrate()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMinThickness(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def minThickness(self):
|
||||
return self._simulation_view.getMinThickness()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMaxThickness(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def maxThickness(self):
|
||||
return self._simulation_view.getMaxThickness()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMaxLineWidth(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def maxLineWidth(self):
|
||||
return self._simulation_view.getMaxLineWidth()
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMinLineWidth(self):
|
||||
@pyqtProperty(float, notify = colorSchemeLimitsChanged)
|
||||
def minLineWidth(self):
|
||||
return self._simulation_view.getMinLineWidth()
|
||||
|
||||
@pyqtProperty(float, notify=colorSchemeLimitsChanged)
|
||||
def maxFlowRate(self):
|
||||
return self._simulation_view.getMaxFlowRate()
|
||||
|
||||
@pyqtProperty(float, notify=colorSchemeLimitsChanged)
|
||||
def minFlowRate(self):
|
||||
return self._simulation_view.getMinFlowRate()
|
||||
|
||||
# Opacity 0..1
|
||||
@pyqtSlot(int, float)
|
||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
||||
|
@ -153,6 +162,9 @@ class SimulationViewProxy(QObject):
|
|||
self.currentLayerChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
||||
def _onColorSchemeLimitsChanged(self):
|
||||
self.colorSchemeLimitsChanged.emit()
|
||||
|
||||
def _onPathChanged(self):
|
||||
self.currentPathChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
@ -182,6 +194,7 @@ class SimulationViewProxy(QObject):
|
|||
active_view = self._controller.getActiveView()
|
||||
if active_view == self._simulation_view:
|
||||
self._simulation_view.currentLayerNumChanged.connect(self._onLayerChanged)
|
||||
self._simulation_view.colorSchemeLimitsChanged.connect(self._onColorSchemeLimitsChanged)
|
||||
self._simulation_view.currentPathNumChanged.connect(self._onPathChanged)
|
||||
self._simulation_view.maxLayersChanged.connect(self._onMaxLayersChanged)
|
||||
self._simulation_view.maxPathsChanged.connect(self._onMaxPathsChanged)
|
||||
|
@ -194,6 +207,7 @@ class SimulationViewProxy(QObject):
|
|||
# Disconnect all of em again.
|
||||
self.is_simulationView_selected = False
|
||||
self._simulation_view.currentLayerNumChanged.disconnect(self._onLayerChanged)
|
||||
self._simulation_view.colorSchemeLimitsChanged.connect(self._onColorSchemeLimitsChanged)
|
||||
self._simulation_view.currentPathNumChanged.disconnect(self._onPathChanged)
|
||||
self._simulation_view.maxLayersChanged.disconnect(self._onMaxLayersChanged)
|
||||
self._simulation_view.maxPathsChanged.disconnect(self._onMaxPathsChanged)
|
||||
|
|
|
@ -12,6 +12,8 @@ vertex41core =
|
|||
uniform lowp float u_min_thickness;
|
||||
uniform lowp float u_max_line_width;
|
||||
uniform lowp float u_min_line_width;
|
||||
uniform lowp float u_max_flow_rate;
|
||||
uniform lowp float u_min_flow_rate;
|
||||
uniform lowp int u_layer_view_type;
|
||||
uniform lowp mat4 u_extruder_opacity; // currently only for max 16 extruders, others always visible
|
||||
|
||||
|
@ -44,7 +46,15 @@ vertex41core =
|
|||
|
||||
vec4 feedrateGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value)/(max_value - min_value);
|
||||
float value;
|
||||
if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors).
|
||||
{
|
||||
value = 0.5; //Pick a colour in exactly the middle of the range.
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (abs_value - min_value) / (max_value - min_value);
|
||||
}
|
||||
float red = value;
|
||||
float green = 1-abs(1-4*value);
|
||||
if (value > 0.375)
|
||||
|
@ -57,7 +67,15 @@ vertex41core =
|
|||
|
||||
vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value)/(max_value - min_value);
|
||||
float value;
|
||||
if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors).
|
||||
{
|
||||
value = 0.5; //Pick a colour in exactly the middle of the range.
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (abs_value - min_value) / (max_value - min_value);
|
||||
}
|
||||
float red = min(max(4*value-2, 0), 1);
|
||||
float green = min(1.5*value, 0.75);
|
||||
if (value > 0.75)
|
||||
|
@ -70,7 +88,15 @@ vertex41core =
|
|||
|
||||
vec4 lineWidthGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value) / (max_value - min_value);
|
||||
float value;
|
||||
if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors).
|
||||
{
|
||||
value = 0.5; //Pick a colour in exactly the middle of the range.
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (abs_value - min_value) / (max_value - min_value);
|
||||
}
|
||||
float red = value;
|
||||
float green = 1 - abs(1 - 4 * value);
|
||||
if(value > 0.375)
|
||||
|
@ -81,6 +107,30 @@ vertex41core =
|
|||
return vec4(red, green, blue, 1.0);
|
||||
}
|
||||
|
||||
float clamp(float v)
|
||||
{
|
||||
float t = v < 0 ? 0 : v;
|
||||
return t > 1.0 ? 1.0 : t;
|
||||
}
|
||||
|
||||
// Inspired by https://stackoverflow.com/a/46628410
|
||||
vec4 flowRateGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float t;
|
||||
if(abs(min_value - max_value) < 0.0001)
|
||||
{
|
||||
t = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = 2.0 * ((abs_value - min_value) / (max_value - min_value)) - 1;
|
||||
}
|
||||
float red = clamp(1.5 - abs(2.0 * t - 1.0));
|
||||
float green = clamp(1.5 - abs(2.0 * t));
|
||||
float blue = clamp(1.5 - abs(2.0 * t + 1.0));
|
||||
return vec4(red, green, blue, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 v1_vertex = a_vertex;
|
||||
|
@ -106,6 +156,10 @@ vertex41core =
|
|||
case 4: // "Line width"
|
||||
v_color = lineWidthGradientColor(a_line_dim.x, u_min_line_width, u_max_line_width);
|
||||
break;
|
||||
case 5: // "Flow"
|
||||
float flow_rate = a_line_dim.x * a_line_dim.y * a_feedrate;
|
||||
v_color = flowRateGradientColor(flow_rate, u_min_flow_rate, u_max_flow_rate);
|
||||
break;
|
||||
}
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
|
@ -294,7 +348,6 @@ geometry41core =
|
|||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
if ((u_show_starts == 1) && (v_prev_line_type[0] != 1) && (v_line_type[0] == 1)) {
|
||||
float w = size_x;
|
||||
float h = size_y;
|
||||
|
@ -313,7 +366,7 @@ geometry41core =
|
|||
myEmitVertex(v_vertex[0] + vec3(-w, -h, -w), u_starts_color, normalize(vec3(-1.0, -1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, -h, -w, 0.0))); // Back-bottom-right
|
||||
myEmitVertex(v_vertex[0] + vec3( w, h, -w), u_starts_color, normalize(vec3( 1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4( w, h, -w, 0.0))); // Back-top-left
|
||||
myEmitVertex(v_vertex[0] + vec3(-w, h, -w), u_starts_color, normalize(vec3(-1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, h, -w, 0.0))); // Back-top-right
|
||||
|
||||
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides the Simulation view.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -229,6 +229,11 @@ class SliceInfo(QObject, Extension):
|
|||
|
||||
model["model_settings"] = model_settings
|
||||
|
||||
if node.source_mime_type is None:
|
||||
model["mime_type"] = ""
|
||||
else:
|
||||
model["mime_type"] = node.source_mime_type.name
|
||||
|
||||
data["models"].append(model)
|
||||
|
||||
print_times = print_information.printTimes()
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<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>
|
||||
<li><b>File type:</b> STL</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a normal solid mesh view.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
"name": "Toolbox",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"description": "Find, manage and install new Cura packages."
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading model files.",
|
||||
"api": "7.5.0"
|
||||
"api": "7.6.0"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||
"supported_sdk_versions": ["7.5.0"],
|
||||
"supported_sdk_versions": ["7.6.0"],
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -5,6 +5,7 @@ from typing import cast, List, Dict
|
|||
|
||||
from Charon.VirtualFile import VirtualFile # To open UFP files.
|
||||
from Charon.OpenMode import OpenMode # To indicate that we want to write to UFP files.
|
||||
from Charon.filetypes.OpenPackagingConvention import OPCError
|
||||
from io import StringIO # For converting g-code to bytes.
|
||||
|
||||
from PyQt5.QtCore import QBuffer
|
||||
|
@ -47,35 +48,53 @@ class UFPWriter(MeshWriter):
|
|||
archive = VirtualFile()
|
||||
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
||||
|
||||
self._writeObjectList(archive)
|
||||
try:
|
||||
self._writeObjectList(archive)
|
||||
|
||||
# Store the g-code from the scene.
|
||||
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
||||
# Store the g-code from the scene.
|
||||
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
return False
|
||||
gcode_textio = StringIO() # We have to convert the g-code into bytes.
|
||||
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||
success = gcode_writer.write(gcode_textio, None)
|
||||
if not success: # Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||
self.setInformation(gcode_writer.getInformation())
|
||||
return False
|
||||
gcode = archive.getStream("/3D/model.gcode")
|
||||
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||
try:
|
||||
gcode = archive.getStream("/3D/model.gcode")
|
||||
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
return False
|
||||
|
||||
# Attempt to store the thumbnail, if any:
|
||||
backend = CuraApplication.getInstance().getBackend()
|
||||
snapshot = None if getattr(backend, "getLatestSnapshot", None) is None else backend.getLatestSnapshot()
|
||||
if snapshot:
|
||||
archive.addContentType(extension = "png", mime_type = "image/png")
|
||||
thumbnail = archive.getStream("/Metadata/thumbnail.png")
|
||||
try:
|
||||
archive.addContentType(extension = "png", mime_type = "image/png")
|
||||
thumbnail = archive.getStream("/Metadata/thumbnail.png")
|
||||
|
||||
thumbnail_buffer = QBuffer()
|
||||
thumbnail_buffer.open(QBuffer.ReadWrite)
|
||||
snapshot.save(thumbnail_buffer, "PNG")
|
||||
thumbnail_buffer = QBuffer()
|
||||
thumbnail_buffer.open(QBuffer.ReadWrite)
|
||||
snapshot.save(thumbnail_buffer, "PNG")
|
||||
|
||||
thumbnail.write(thumbnail_buffer.data())
|
||||
archive.addRelation(virtual_path = "/Metadata/thumbnail.png",
|
||||
relation_type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
|
||||
origin = "/3D/model.gcode")
|
||||
thumbnail.write(thumbnail_buffer.data())
|
||||
archive.addRelation(virtual_path = "/Metadata/thumbnail.png",
|
||||
relation_type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
|
||||
origin = "/3D/model.gcode")
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
return False
|
||||
else:
|
||||
Logger.log("w", "Thumbnail not created, cannot save it")
|
||||
|
||||
|
@ -90,7 +109,7 @@ class UFPWriter(MeshWriter):
|
|||
|
||||
try:
|
||||
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
||||
except:
|
||||
except OPCError:
|
||||
Logger.log("w", "The material extension: %s was already added", material_extension)
|
||||
|
||||
added_materials = []
|
||||
|
@ -120,17 +139,23 @@ class UFPWriter(MeshWriter):
|
|||
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
||||
return False
|
||||
|
||||
material_file = archive.getStream(material_file_name)
|
||||
material_file.write(serialized_material.encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = material_file_name,
|
||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||
origin = "/3D/model.gcode")
|
||||
try:
|
||||
material_file = archive.getStream(material_file_name)
|
||||
material_file.write(serialized_material.encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = material_file_name,
|
||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||
origin = "/3D/model.gcode")
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
return False
|
||||
|
||||
added_materials.append(material_file_name)
|
||||
|
||||
try:
|
||||
archive.close()
|
||||
except OSError as e:
|
||||
except EnvironmentError as e:
|
||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||
self.setInformation(error_msg)
|
||||
Logger.error(error_msg)
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Manages network connections to Ultimaker networked printers.",
|
||||
"version": "2.0.0",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -7,26 +7,34 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
|||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
from ..Models.Http.ClusterMaterial import ClusterMaterial
|
||||
from ..Models.LocalMaterial import LocalMaterial
|
||||
from ..Messages.MaterialSyncMessage import MaterialSyncMessage
|
||||
|
||||
import time
|
||||
import threading
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .LocalClusterOutputDevice import LocalClusterOutputDevice
|
||||
|
||||
|
||||
class SendMaterialJob(Job):
|
||||
|
||||
"""Asynchronous job to send material profiles to the printer.
|
||||
|
||||
This way it won't freeze up the interface while sending those materials.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, device: "LocalClusterOutputDevice") -> None:
|
||||
super().__init__()
|
||||
self.device = device # type: LocalClusterOutputDevice
|
||||
|
||||
self._send_material_thread = threading.Thread(target = self._sendMissingMaterials)
|
||||
self._send_material_thread.setDaemon(True)
|
||||
|
||||
self._remote_materials = {} # type: Dict[str, ClusterMaterial]
|
||||
|
||||
def run(self) -> None:
|
||||
"""Send the request to the printer and register a callback"""
|
||||
|
||||
|
@ -36,9 +44,15 @@ class SendMaterialJob(Job):
|
|||
"""Callback for when the remote materials were returned."""
|
||||
|
||||
remote_materials_by_guid = {material.guid: material for material in materials}
|
||||
self._sendMissingMaterials(remote_materials_by_guid)
|
||||
self._remote_materials = remote_materials_by_guid
|
||||
# It's not the nicest way to do it, but if we don't handle this in a thread
|
||||
# we are blocking the main interface (even though the original call was done in a job)
|
||||
# This should really be refactored so that calculating the list of materials that need to be sent
|
||||
# to the printer is done outside of the job (and running the job actually sends the materials)
|
||||
# TODO: Fix this hack that was introduced for 4.9.1
|
||||
self._send_material_thread.start()
|
||||
|
||||
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
|
||||
def _sendMissingMaterials(self) -> None:
|
||||
"""Determine which materials should be updated and send them to the printer.
|
||||
|
||||
:param remote_materials_by_guid: The remote materials by GUID.
|
||||
|
@ -47,7 +61,7 @@ class SendMaterialJob(Job):
|
|||
if len(local_materials_by_guid) == 0:
|
||||
Logger.log("d", "There are no local materials to synchronize with the printer.")
|
||||
return
|
||||
material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, remote_materials_by_guid)
|
||||
material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, self._remote_materials)
|
||||
if len(material_ids_to_send) == 0:
|
||||
Logger.log("d", "There are no remote materials to update.")
|
||||
return
|
||||
|
@ -96,7 +110,11 @@ class SendMaterialJob(Job):
|
|||
|
||||
file_name = os.path.basename(file_path)
|
||||
self._sendMaterialFile(file_path, file_name, root_material_id)
|
||||
time.sleep(1) # Throttle the sending a bit.
|
||||
|
||||
# This needs to be called on the QT thread since the onFinished needs to happen
|
||||
# in the same thread as where the network manager is located (aka; main thread)
|
||||
@call_on_qt_thread
|
||||
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
|
||||
"""Send a single material file to the printer.
|
||||
|
||||
|
|
|
@ -1,9 +1,2 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# Workaround for a race condition on certain systems where there
|
||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
||||
# first seems to prevent Sip from going into a state where it
|
||||
# tries to create PyQt objects on a non-main thread.
|
||||
import Arcus #@UnusedImport
|
||||
import Savitar #@UnusedImport
|
|
@ -70,7 +70,10 @@ class AutoDetectBaudJob(Job):
|
|||
timeout_time = time() + wait_response_timeout
|
||||
|
||||
while timeout_time > time():
|
||||
line = serial.readline()
|
||||
# If baudrate is wrong, then readline() might never
|
||||
# return, even with timeouts set. Using read_until
|
||||
# with size limit seems to fix this.
|
||||
line = serial.read_until(size = 100)
|
||||
if b"ok" in line and b"T:" in line:
|
||||
self.setResult(baud_rate)
|
||||
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "USB printing",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.2",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 3.5 to Cura 4.0.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Upgrades configurations from Cura 4.0 to Cura 4.1.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.1 to Cura 4.2.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.2 to Cura 4.3.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.3 to Cura 4.4.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.4 to Cura 4.5.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.5 to Cura 4.6.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.6.0 to Cura 4.6.2.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.6.2 to Cura 4.7.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.7 to Cura 4.8.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.8 to Cura 4.9.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import configparser
|
||||
import io
|
||||
import os.path # To get the file ID.
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
|
||||
class VersionUpgrade49to410(VersionUpgrade):
|
||||
"""
|
||||
Upgrades configurations from the state they were in at version 4.9 to the state they should be in at version 4.10.
|
||||
"""
|
||||
|
||||
_renamed_profiles = {
|
||||
# Definitions.
|
||||
"twotrees_bluer" : "two_trees_bluer",
|
||||
|
||||
# Upgrade for people who had the original TwoTrees Bluer profiles from 4.9 and earlier.
|
||||
"twotrees_bluer_extruder_0": "two_trees_base_extruder_0",
|
||||
"twotrees_bluer_extruder_1": "two_trees_base_extruder_0"
|
||||
}
|
||||
|
||||
_quality_changes_to_two_trees_base = {
|
||||
"twotrees_bluer_extruder_0",
|
||||
"twotrees_bluer_extruder_1",
|
||||
"twotrees_bluer"
|
||||
}
|
||||
|
||||
# Default variant to select for legacy TwoTrees printers, now that we have variants.
|
||||
_default_variants = {
|
||||
"twotrees_bluer_extruder_0" : "two_trees_bluer_0.4",
|
||||
"twotrees_bluer_extruder_1" : "two_trees_bluer_0.4"
|
||||
}
|
||||
|
||||
_two_trees_bluer_quality_type_conversion = {
|
||||
"high" : "ultra",
|
||||
"normal" : "super",
|
||||
"fast" : "standard",
|
||||
"draft" : "standard",
|
||||
"extra_fast" : "low",
|
||||
"coarse" : "draft",
|
||||
"extra_coarse": "draft"
|
||||
}
|
||||
|
||||
_two_trees_quality_per_material = {
|
||||
# Since legacy TwoTrees printers didn't have different variants, we always pick the 0.4mm variant.
|
||||
"generic_abs_175" : {
|
||||
"high" : "two_trees_0.4_ABS_super",
|
||||
"normal" : "two_trees_0.4_ABS_super",
|
||||
"fast" : "two_trees_0.4_ABS_super",
|
||||
"draft" : "two_trees_0.4_ABS_standard",
|
||||
"extra_fast" : "two_trees_0.4_ABS_low",
|
||||
"coarse" : "two_trees_0.4_ABS_low",
|
||||
"extra_coarse": "two_trees_0.4_ABS_low"
|
||||
},
|
||||
"generic_petg_175": {
|
||||
"high" : "two_trees_0.4_PETG_super",
|
||||
"normal" : "two_trees_0.4_PETG_super",
|
||||
"fast" : "two_trees_0.4_PETG_super",
|
||||
"draft" : "two_trees_0.4_PETG_standard",
|
||||
"extra_fast" : "two_trees_0.4_PETG_low",
|
||||
"coarse" : "two_trees_0.4_PETG_low",
|
||||
"extra_coarse": "two_trees_0.4_PETG_low"
|
||||
},
|
||||
"generic_pla_175" : {
|
||||
"high" : "two_trees_0.4_PLA_super",
|
||||
"normal" : "two_trees_0.4_PLA_super",
|
||||
"fast" : "two_trees_0.4_PLA_super",
|
||||
"draft" : "two_trees_0.4_PLA_standard",
|
||||
"extra_fast" : "two_trees_0.4_PLA_low",
|
||||
"coarse" : "two_trees_0.4_PLA_low",
|
||||
"extra_coarse": "two_trees_0.4_PLA_low"
|
||||
},
|
||||
"generic_tpu_175" : {
|
||||
"high" : "two_trees_0.4_TPU_super",
|
||||
"normal" : "two_trees_0.4_TPU_super",
|
||||
"fast" : "two_trees_0.4_TPU_super",
|
||||
"draft" : "two_trees_0.4_TPU_standard",
|
||||
"extra_fast" : "two_trees_0.4_TPU_standard",
|
||||
"coarse" : "two_trees_0.4_TPU_standard",
|
||||
"extra_coarse": "two_trees_0.4_TPU_standard"
|
||||
},
|
||||
"empty_material" : { # For the global stack.
|
||||
"high" : "two_trees_global_super",
|
||||
"normal" : "two_trees_global_super",
|
||||
"fast" : "two_trees_global_super",
|
||||
"draft" : "two_trees_global_standard",
|
||||
"extra_fast" : "two_trees_global_low",
|
||||
"coarse" : "two_trees_global_low",
|
||||
"extra_coarse": "two_trees_global_low"
|
||||
}
|
||||
}
|
||||
|
||||
_deltacomb_quality_type_conversion = {
|
||||
"a" : "D005",
|
||||
"b" : "D010",
|
||||
"c" : "D015",
|
||||
"d" : "D020",
|
||||
"e" : "D030",
|
||||
"f" : "D045",
|
||||
"g" : "D060"
|
||||
}
|
||||
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
"""Upgrades instance containers to have the new version number.
|
||||
|
||||
This renames the renamed settings in the containers.
|
||||
"""
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update setting version number.
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "17"
|
||||
|
||||
if "general" not in parser:
|
||||
parser["general"] = {}
|
||||
# Certain instance containers (such as definition changes) reference to a certain definition container
|
||||
# Since a number of those changed name, we also need to update those.
|
||||
old_definition = parser.get("general", "definition", fallback = "")
|
||||
if old_definition in self._renamed_profiles:
|
||||
parser["general"]["definition"] = self._renamed_profiles[old_definition]
|
||||
|
||||
# For quality-changes profiles made for TwoTrees Bluer printers, change the definition to the two_trees_base and make sure that the quality is something we have a profile for.
|
||||
if parser.get("metadata", "type", fallback = "") == "quality_changes":
|
||||
for possible_printer in self._quality_changes_to_two_trees_base:
|
||||
if os.path.basename(filename).startswith(possible_printer + "_"):
|
||||
parser["general"]["definition"] = "two_trees_base"
|
||||
parser["metadata"]["quality_type"] = self._two_trees_bluer_quality_type_conversion.get(parser.get("metadata", "quality_type", fallback = "fast"), "standard")
|
||||
break
|
||||
|
||||
if os.path.basename(filename).startswith("deltacomb_"):
|
||||
parser["general"]["definition"] = "deltacomb_base"
|
||||
parser["metadata"]["quality_type"] = self._deltacomb_quality_type_conversion.get(parser.get("metadata", "quality_type", fallback = "c"), "D015")
|
||||
break
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
"""Upgrades stacks to have the new version number."""
|
||||
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update setting version number.
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "17"
|
||||
|
||||
# Change renamed profiles.
|
||||
if "containers" in parser:
|
||||
definition_id = parser["containers"]["7"]
|
||||
if definition_id in self._quality_changes_to_two_trees_base:
|
||||
material_id = parser["containers"]["4"]
|
||||
old_quality_id = parser["containers"]["3"]
|
||||
if parser["metadata"].get("type", "machine") == "extruder_train":
|
||||
if parser["containers"]["5"] == "empty_variant":
|
||||
if definition_id in self._default_variants:
|
||||
parser["containers"]["5"] = self._default_variants[definition_id] # For legacy TwoTrees printers, change the variant to 0.4.
|
||||
|
||||
# Also change the quality to go along with it.
|
||||
if material_id in self._two_trees_quality_per_material and old_quality_id in self._two_trees_quality_per_material[material_id]:
|
||||
parser["containers"]["3"] = self._two_trees_quality_per_material[material_id][old_quality_id]
|
||||
stack_copy = {} # type: Dict[str, str] # Make a copy so that we don't modify the dict we're iterating over.
|
||||
stack_copy.update(parser["containers"])
|
||||
for position, profile_id in stack_copy.items():
|
||||
if profile_id in self._renamed_profiles:
|
||||
parser["containers"][position] = self._renamed_profiles[profile_id]
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
55
plugins/VersionUpgrade/VersionUpgrade49to410/__init__.py
Normal file
55
plugins/VersionUpgrade/VersionUpgrade49to410/__init__.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Any, Dict, TYPE_CHECKING
|
||||
|
||||
from . import VersionUpgrade49to410
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Application import Application
|
||||
|
||||
upgrade = VersionUpgrade49to410.VersionUpgrade49to410()
|
||||
|
||||
|
||||
def getMetaData() -> Dict[str, Any]:
|
||||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("machine_stack", 5000016): ("machine_stack", 5000017, upgrade.upgradeStack),
|
||||
("extruder_train", 5000016): ("extruder_train", 5000017, upgrade.upgradeStack),
|
||||
("definition_changes", 4000016): ("definition_changes", 4000017, upgrade.upgradeInstanceContainer),
|
||||
("quality_changes", 4000016): ("quality_changes", 4000017, upgrade.upgradeInstanceContainer),
|
||||
("quality", 4000016): ("quality", 4000017, upgrade.upgradeInstanceContainer),
|
||||
("user", 4000016): ("user", 4000017, upgrade.upgradeInstanceContainer),
|
||||
},
|
||||
"sources": {
|
||||
"machine_stack": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
},
|
||||
"extruder_train": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./extruders"}
|
||||
},
|
||||
"definition_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./definition_changes"}
|
||||
},
|
||||
"quality_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality_changes"}
|
||||
},
|
||||
"quality": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality"}
|
||||
},
|
||||
"user": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register(app: "Application") -> Dict[str, Any]:
|
||||
return {"version_upgrade": upgrade}
|
8
plugins/VersionUpgrade/VersionUpgrade49to410/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade49to410/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 4.9 to 4.10",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 4.9 to Cura 4.10.",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Seva Alekseyev, Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for reading X3D files.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides the X-Ray view.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides capabilities to read and write XML-based material profiles.",
|
||||
"api": "7.5.0",
|
||||
"api": "7.6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import custom Sip bindings first!
|
||||
import Savitar # Dont remove this line
|
||||
import Arcus # No really. Don't. It needs to be there!
|
||||
import pynest2d # Really!
|
||||
from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used.
|
||||
|
||||
import pytest
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue