Merge branch 'master' of github.com:Ultimaker/Cura into unittests_firmwareupdate_0

This commit is contained in:
Jaime van Kessel 2019-01-17 09:49:48 +01:00
commit f5c4b6919d
1123 changed files with 19962 additions and 12355 deletions

View file

@ -794,7 +794,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Clear all existing containers
quality_changes_info.global_info.container.clear()
for container_info in quality_changes_info.extruder_info_dict.values():
container_info.container.clear()
if container_info.container:
container_info.container.clear()
# Loop over everything and override the existing containers
global_info = quality_changes_info.global_info

View file

@ -1,8 +1,8 @@
{
"name": "3MF Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for reading 3MF files.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "3MF Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for writing 3MF files.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -943,7 +943,7 @@ This release adds support for printers with elliptic buildplates. This feature h
*AppImage for Linux
The Linux distribution is now in AppImage format, which makes Cura easier to install.
*bugfixes
*Bugfixes
The user is now notified when a new version of Cura is available.
When searching in the setting visibility preferences, the category for each setting is always displayed.
3MF files are now saved and loaded correctly.

View file

@ -1,8 +1,8 @@
{
"name": "Changelog",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Shows changes since latest checked version.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from .src.DrivePluginExtension import DrivePluginExtension
def getMetaData():
return {}
def register(app):
return {"extension": DrivePluginExtension()}

View file

@ -0,0 +1,8 @@
{
"name": "Cura Backups",
"author": "Ultimaker B.V.",
"description": "Backup and restore your configuration.",
"version": "1.2.0",
"api": 6,
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,168 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import base64
import hashlib
from datetime import datetime
from tempfile import NamedTemporaryFile
from typing import Any, Optional, List, Dict
import requests
from UM.Logger import Logger
from UM.Message import Message
from UM.Signal import Signal, signalemitter
from cura.CuraApplication import CuraApplication
from .UploadBackupJob import UploadBackupJob
from .Settings import Settings
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
@signalemitter
class DriveApiService:
BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
# Emit signal when restoring backup started or finished.
restoringStateChanged = Signal()
# Emit signal when creating backup started or finished.
creatingStateChanged = Signal()
def __init__(self) -> None:
self._cura_api = CuraApplication.getInstance().getCuraAPI()
def getBackups(self) -> List[Dict[str, Any]]:
access_token = self._cura_api.account.accessToken
if not access_token:
Logger.log("w", "Could not get access token.")
return []
backup_list_request = requests.get(self.BACKUP_URL, headers = {
"Authorization": "Bearer {}".format(access_token)
})
# HTTP status 300s mean redirection. 400s and 500s are errors.
# Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
if backup_list_request.status_code >= 300:
Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
return []
return backup_list_request.json()["data"]
def createBackup(self) -> None:
self.creatingStateChanged.emit(is_creating = True)
# Create the backup.
backup_zip_file, backup_meta_data = self._cura_api.backups.createBackup()
if not backup_zip_file or not backup_meta_data:
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.")
return
# Create an upload entry for the backup.
timestamp = datetime.now().isoformat()
backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"])
backup_upload_url = self._requestBackupUpload(backup_meta_data, len(backup_zip_file))
if not backup_upload_url:
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.")
return
# Upload the backup to storage.
upload_backup_job = UploadBackupJob(backup_upload_url, backup_zip_file)
upload_backup_job.finished.connect(self._onUploadFinished)
upload_backup_job.start()
def _onUploadFinished(self, job: "UploadBackupJob") -> None:
if job.backup_upload_error_message != "":
# If the job contains an error message we pass it along so the UI can display it.
self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message)
else:
self.creatingStateChanged.emit(is_creating = False)
def restoreBackup(self, backup: Dict[str, Any]) -> None:
self.restoringStateChanged.emit(is_restoring = True)
download_url = backup.get("download_url")
if not download_url:
# If there is no download URL, we can't restore the backup.
return self._emitRestoreError()
download_package = requests.get(download_url, stream = True)
if download_package.status_code >= 300:
# Something went wrong when attempting to download the backup.
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
return self._emitRestoreError()
# We store the file in a temporary path fist to ensure integrity.
temporary_backup_file = NamedTemporaryFile(delete = False)
with open(temporary_backup_file.name, "wb") as write_backup:
for chunk in download_package:
write_backup.write(chunk)
if not self._verifyMd5Hash(temporary_backup_file.name, backup.get("md5_hash", "")):
# Don't restore the backup if the MD5 hashes do not match.
# This can happen if the download was interrupted.
Logger.log("w", "Remote and local MD5 hashes do not match, not restoring backup.")
return self._emitRestoreError()
# Tell Cura to place the backup back in the user data folder.
with open(temporary_backup_file.name, "rb") as read_backup:
self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {}))
self.restoringStateChanged.emit(is_restoring = False)
def _emitRestoreError(self) -> None:
self.restoringStateChanged.emit(is_restoring = False,
error_message = catalog.i18nc("@info:backup_status",
"There was an error trying to restore your backup."))
# Verify the MD5 hash of a file.
# \param file_path Full path to the file.
# \param known_hash The known MD5 hash of the file.
# \return: Success or not.
@staticmethod
def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
with open(file_path, "rb") as read_backup:
local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars = b"_-").decode("utf-8")
return known_hash == local_md5_hash
def deleteBackup(self, backup_id: str) -> bool:
access_token = self._cura_api.account.accessToken
if not access_token:
Logger.log("w", "Could not get access token.")
return False
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
"Authorization": "Bearer {}".format(access_token)
})
if delete_backup.status_code >= 300:
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
return False
return True
# Request a backup upload slot from the API.
# \param backup_metadata: A dict containing some meta data about the backup.
# \param backup_size The size of the backup file in bytes.
# \return: The upload URL for the actual backup file if successful, otherwise None.
def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
access_token = self._cura_api.account.accessToken
if not access_token:
Logger.log("w", "Could not get access token.")
return None
backup_upload_request = requests.put(self.BACKUP_URL, json = {
"data": {
"backup_size": backup_size,
"metadata": backup_metadata
}
}, headers = {
"Authorization": "Bearer {}".format(access_token)
})
# Any status code of 300 or above indicates an error.
if backup_upload_request.status_code >= 300:
Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
return None
return backup_upload_request.json()["data"]["upload_url"]

View file

@ -0,0 +1,162 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from datetime import datetime
from typing import Optional, List, Dict, Any
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Extension import Extension
from UM.Logger import Logger
from UM.Message import Message
from cura.CuraApplication import CuraApplication
from .Settings import Settings
from .DriveApiService import DriveApiService
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
class DrivePluginExtension(QObject, Extension):
# Signal emitted when the list of backups changed.
backupsChanged = pyqtSignal()
# Signal emitted when restoring has started. Needed to prevent parallel restoring.
restoringStateChanged = pyqtSignal()
# Signal emitted when creating has started. Needed to prevent parallel creation of backups.
creatingStateChanged = pyqtSignal()
# Signal emitted when preferences changed (like auto-backup).
preferencesChanged = pyqtSignal()
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
def __init__(self) -> None:
QObject.__init__(self, None)
Extension.__init__(self)
# Local data caching for the UI.
self._drive_window = None # type: Optional[QObject]
self._backups = [] # type: List[Dict[str, Any]]
self._is_restoring_backup = False
self._is_creating_backup = False
# Initialize services.
preferences = CuraApplication.getInstance().getPreferences()
self._drive_api_service = DriveApiService()
# Attach signals.
CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
# Register preferences.
preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY,
datetime.now().strftime(self.DATE_FORMAT))
# Register the menu item
self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow)
# Make auto-backup on boot if required.
CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup)
def showDriveWindow(self) -> None:
if not self._drive_window:
plugin_dir_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive")
path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
self.refreshBackups()
if self._drive_window:
self._drive_window.show()
def _autoBackup(self) -> None:
preferences = CuraApplication.getInstance().getPreferences()
if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
self.createBackup()
def _isLastBackupTooLongAgo(self) -> bool:
current_date = datetime.now()
last_backup_date = self._getLastBackupDate()
date_diff = current_date - last_backup_date
return date_diff.days > 1
def _getLastBackupDate(self) -> "datetime":
preferences = CuraApplication.getInstance().getPreferences()
last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
return datetime.strptime(last_backup_date, self.DATE_FORMAT)
def _storeBackupDate(self) -> None:
backup_date = datetime.now().strftime(self.DATE_FORMAT)
preferences = CuraApplication.getInstance().getPreferences()
preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
def _onLoginStateChanged(self, logged_in: bool = False) -> None:
if logged_in:
self.refreshBackups()
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
self._is_restoring_backup = is_restoring
self.restoringStateChanged.emit()
if error_message:
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
self._is_creating_backup = is_creating
self.creatingStateChanged.emit()
if error_message:
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
else:
self._storeBackupDate()
if not is_creating and not error_message:
# We've finished creating a new backup, to the list has to be updated.
self.refreshBackups()
@pyqtSlot(bool, name = "toggleAutoBackup")
def toggleAutoBackup(self, enabled: bool) -> None:
preferences = CuraApplication.getInstance().getPreferences()
preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
@pyqtProperty(bool, notify = preferencesChanged)
def autoBackupEnabled(self) -> bool:
preferences = CuraApplication.getInstance().getPreferences()
return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
@pyqtProperty("QVariantList", notify = backupsChanged)
def backups(self) -> List[Dict[str, Any]]:
return self._backups
@pyqtSlot(name = "refreshBackups")
def refreshBackups(self) -> None:
self._backups = self._drive_api_service.getBackups()
self.backupsChanged.emit()
@pyqtProperty(bool, notify = restoringStateChanged)
def isRestoringBackup(self) -> bool:
return self._is_restoring_backup
@pyqtProperty(bool, notify = creatingStateChanged)
def isCreatingBackup(self) -> bool:
return self._is_creating_backup
@pyqtSlot(str, name = "restoreBackup")
def restoreBackup(self, backup_id: str) -> None:
for backup in self._backups:
if backup.get("backup_id") == backup_id:
self._drive_api_service.restoreBackup(backup)
return
Logger.log("w", "Unable to find backup with the ID %s", backup_id)
@pyqtSlot(name = "createBackup")
def createBackup(self) -> None:
self._drive_api_service.createBackup()
@pyqtSlot(str, name = "deleteBackup")
def deleteBackup(self, backup_id: str) -> None:
self._drive_api_service.deleteBackup(backup_id)
self.refreshBackups()

View file

@ -0,0 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura import UltimakerCloudAuthentication
class Settings:
# Keeps the plugin settings.
DRIVE_API_VERSION = 1
DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION))
AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled"
AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date"

View file

@ -0,0 +1,41 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import requests
from UM.Job import Job
from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class UploadBackupJob(Job):
MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups")
# This job is responsible for uploading the backup file to cloud storage.
# As it can take longer than some other tasks, we schedule this using a Cura Job.
def __init__(self, signed_upload_url: str, backup_zip: bytes) -> None:
super().__init__()
self._signed_upload_url = signed_upload_url
self._backup_zip = backup_zip
self._upload_success = False
self.backup_upload_error_message = ""
def run(self) -> None:
upload_message = Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE, progress = -1)
upload_message.show()
backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip)
upload_message.hide()
if backup_upload.status_code >= 300:
self.backup_upload_error_message = backup_upload.text
Logger.log("w", "Could not upload backup file: %s", backup_upload.text)
Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), title = self.MESSAGE_TITLE).show()
else:
self._upload_success = True
Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show()
self.finished.emit(self)

View file

View file

@ -0,0 +1,39 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import UM 1.1 as UM
ScrollView
{
property alias model: backupList.model
width: parent.width
clip: true
ListView
{
id: backupList
width: parent.width
delegate: Item
{
// Add a margin, otherwise the scrollbar is on top of the right most component
width: parent.width - UM.Theme.getSize("default_margin").width
height: childrenRect.height
BackupListItem
{
id: backupListItem
width: parent.width
}
Rectangle
{
id: divider
color: UM.Theme.getColor("lining")
height: UM.Theme.getSize("default_lining").height
}
}
}
}

View file

@ -0,0 +1,46 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import Cura 1.0 as Cura
import "../components"
RowLayout
{
id: backupListFooter
width: parent.width
property bool showInfoButton: false
Cura.PrimaryButton
{
id: infoButton
text: catalog.i18nc("@button", "Want more?")
iconSource: UM.Theme.getIcon("info")
onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2")
visible: backupListFooter.showInfoButton
}
Cura.PrimaryButton
{
id: createBackupButton
text: catalog.i18nc("@button", "Backup Now")
iconSource: UM.Theme.getIcon("plus")
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
onClicked: CuraDrive.createBackup()
busy: CuraDrive.isCreatingBackup
}
Cura.CheckBoxWithTooltip
{
id: autoBackupEnabled
checked: CuraDrive.autoBackupEnabled
onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked)
text: catalog.i18nc("@checkbox:description", "Auto Backup")
tooltip: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
}
}

View file

@ -0,0 +1,113 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.1
import UM 1.1 as UM
import Cura 1.0 as Cura
Item
{
id: backupListItem
width: parent.width
height: showDetails ? dataRow.height + backupDetails.height : dataRow.height
property bool showDetails: false
// Backup details toggle animation.
Behavior on height
{
PropertyAnimation
{
duration: 70
}
}
RowLayout
{
id: dataRow
spacing: UM.Theme.getSize("wide_margin").width
width: parent.width
height: 50 * screenScaleFactor
UM.SimpleButton
{
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
color: UM.Theme.getColor("small_button_text")
hoverColor: UM.Theme.getColor("small_button_text_hover")
iconSource: UM.Theme.getIcon("info")
onClicked: backupListItem.showDetails = !backupListItem.showDetails
}
Label
{
text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language"))
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 100 * screenScaleFactor
Layout.maximumWidth: 500 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label
{
text: modelData.metadata.description
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 100 * screenScaleFactor
Layout.maximumWidth: 500 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Cura.SecondaryButton
{
text: catalog.i18nc("@button", "Restore")
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
onClicked: confirmRestoreDialog.visible = true
}
UM.SimpleButton
{
width: UM.Theme.getSize("message_close").width
height: UM.Theme.getSize("message_close").height
color: UM.Theme.getColor("small_button_text")
hoverColor: UM.Theme.getColor("small_button_text_hover")
iconSource: UM.Theme.getIcon("cross1")
onClicked: confirmDeleteDialog.visible = true
}
}
BackupListItemDetails
{
id: backupDetails
backupDetailsData: modelData
width: parent.width
visible: parent.showDetails
anchors.top: dataRow.bottom
}
MessageDialog
{
id: confirmDeleteDialog
title: catalog.i18nc("@dialog:title", "Delete Backup")
text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.")
standardButtons: StandardButton.Yes | StandardButton.No
onYes: CuraDrive.deleteBackup(modelData.backup_id)
}
MessageDialog
{
id: confirmRestoreDialog
title: catalog.i18nc("@dialog:title", "Restore Backup")
text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?")
standardButtons: StandardButton.Yes | StandardButton.No
onYes: CuraDrive.restoreBackup(modelData.backup_id)
}
}

View file

@ -0,0 +1,63 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.1 as UM
ColumnLayout
{
id: backupDetails
width: parent.width
spacing: UM.Theme.getSize("default_margin").width
property var backupDetailsData
// Cura version
BackupListItemDetailsRow
{
iconSource: UM.Theme.getIcon("application")
label: catalog.i18nc("@backuplist:label", "Cura Version")
value: backupDetailsData.metadata.cura_release
}
// Machine count.
BackupListItemDetailsRow
{
iconSource: UM.Theme.getIcon("printer_single")
label: catalog.i18nc("@backuplist:label", "Machines")
value: backupDetailsData.metadata.machine_count
}
// Material count
BackupListItemDetailsRow
{
iconSource: UM.Theme.getIcon("category_material")
label: catalog.i18nc("@backuplist:label", "Materials")
value: backupDetailsData.metadata.material_count
}
// Profile count.
BackupListItemDetailsRow
{
iconSource: UM.Theme.getIcon("settings")
label: catalog.i18nc("@backuplist:label", "Profiles")
value: backupDetailsData.metadata.profile_count
}
// Plugin count.
BackupListItemDetailsRow
{
iconSource: UM.Theme.getIcon("plugin")
label: catalog.i18nc("@backuplist:label", "Plugins")
value: backupDetailsData.metadata.plugin_count
}
// Spacer.
Item
{
width: parent.width
height: UM.Theme.getSize("default_margin").height
}
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.3 as UM
RowLayout
{
id: detailsRow
width: parent.width
height: 40 * screenScaleFactor
property alias iconSource: icon.source
property alias label: detailName.text
property alias value: detailValue.text
UM.RecolorImage
{
id: icon
width: 18 * screenScaleFactor
height: width
source: ""
color: UM.Theme.getColor("text")
}
Label
{
id: detailName
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 50 * screenScaleFactor
Layout.maximumWidth: 100 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label
{
id: detailValue
color: UM.Theme.getColor("text")
elide: Text.ElideRight
Layout.minimumWidth: 50 * screenScaleFactor
Layout.maximumWidth: 100 * screenScaleFactor
Layout.fillWidth: true
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,44 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import UM 1.3 as UM
import Cura 1.1 as Cura
import "components"
import "pages"
Window
{
id: curaDriveDialog
minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width)
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
maximumWidth: Math.round(minimumWidth * 1.2)
maximumHeight: Math.round(minimumHeight * 1.2)
width: minimumWidth
height: minimumHeight
color: UM.Theme.getColor("main_background")
title: catalog.i18nc("@title:window", "Cura Backups")
// Globally available.
UM.I18nCatalog
{
id: catalog
name: "cura"
}
WelcomePage
{
id: welcomePage
visible: !Cura.API.account.isLoggedIn
}
BackupsPage
{
id: backupsPage
visible: Cura.API.account.isLoggedIn
}
}

View file

@ -0,0 +1,75 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import Cura 1.1 as Cura
import "../components"
Item
{
id: backupsPage
anchors.fill: parent
anchors.margins: UM.Theme.getSize("wide_margin").width
ColumnLayout
{
spacing: UM.Theme.getSize("wide_margin").height
width: parent.width
anchors.fill: parent
Label
{
id: backupTitle
text: catalog.i18nc("@title", "My Backups")
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
Layout.fillWidth: true
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@empty_state",
"You don't have any backups currently. Use the 'Backup Now' button to create one.")
width: parent.width
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
wrapMode: Label.WordWrap
visible: backupList.model.length == 0
Layout.fillWidth: true
Layout.fillHeight: true
renderType: Text.NativeRendering
}
BackupList
{
id: backupList
model: CuraDrive.backups
Layout.fillWidth: true
Layout.fillHeight: true
}
Label
{
text: catalog.i18nc("@backup_limit_info",
"During the preview phase, you'll be limited to 5 visible backups. Remove a backup to see older ones.")
width: parent.width
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
wrapMode: Label.WordWrap
visible: backupList.model.length > 4
renderType: Text.NativeRendering
}
BackupListFooter
{
id: backupListFooter
showInfoButton: backupList.model.length > 4
}
}
}

View file

@ -0,0 +1,56 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import UM 1.3 as UM
import Cura 1.1 as Cura
import "../components"
Column
{
id: welcomePage
spacing: UM.Theme.getSize("wide_margin").height
width: parent.width
height: childrenRect.height
anchors.centerIn: parent
Image
{
id: profileImage
fillMode: Image.PreserveAspectFit
source: "../images/icon.png"
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(parent.width / 4)
}
Label
{
id: welcomeTextLabel
text: catalog.i18nc("@description", "Backup and synchronize your Cura settings.")
width: Math.round(parent.width / 2)
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Label.WordWrap
renderType: Text.NativeRendering
}
Cura.PrimaryButton
{
id: loginButton
width: UM.Theme.getSize("account_button").width
height: UM.Theme.getSize("account_button").height
anchors.horizontalCenter: parent.horizontalCenter
text: catalog.i18nc("@button", "Sign in")
onClicked: Cura.API.account.login()
fixedWidthMode: true
}
}

View file

@ -29,7 +29,7 @@ message Object
bytes normals = 3; //An array of 3 floats.
bytes indices = 4; //An array of ints.
repeated Setting settings = 5; // Setting override per object, overruling the global settings.
string name = 6;
string name = 6; //Mesh name
}
message Progress
@ -58,6 +58,7 @@ message Polygon {
MoveCombingType = 8;
MoveRetractionType = 9;
SupportInterfaceType = 10;
PrimeTowerType = 11;
}
Type type = 1; // Type of move
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
@ -108,8 +109,9 @@ message PrintTimeMaterialEstimates { // The print time for each feature and mate
float time_travel = 9;
float time_retract = 10;
float time_support_interface = 11;
float time_prime_tower = 12;
repeated MaterialEstimates materialEstimates = 12; // materialEstimates data
repeated MaterialEstimates materialEstimates = 13; // materialEstimates data
}
message MaterialEstimates {

View file

@ -86,8 +86,8 @@ class CuraEngineBackend(QObject, Backend):
self._layer_view_active = False #type: bool
self._onActiveViewChanged()
self._stored_layer_data = [] #type: List[Arcus.PythonMessage]
self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = self._application.getController().getScene() #type: Scene
self._scene.sceneChanged.connect(self._onSceneChanged)
@ -203,7 +203,7 @@ class CuraEngineBackend(QObject, Backend):
@pyqtSlot()
def stopSlicing(self) -> None:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if self._slicing: # We were already slicing. Stop the old job.
self._terminate()
self._createSocket()
@ -229,6 +229,7 @@ class CuraEngineBackend(QObject, Backend):
if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0)
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
self.setState(BackendState.Done)
return
if self._process_layers_job:
@ -245,7 +246,7 @@ class CuraEngineBackend(QObject, Backend):
num_objects = self._numObjectsPerBuildPlate()
self._stored_layer_data = []
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
@ -253,7 +254,7 @@ class CuraEngineBackend(QObject, Backend):
if self._build_plates_to_be_sliced:
self.slice()
return
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
@ -322,7 +323,7 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job = None
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
@ -331,10 +332,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status",
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
return
if job.getResult() == StartJobResult.SettingError:
@ -362,10 +363,10 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
return
elif job.getResult() == StartJobResult.ObjectSettingError:
@ -386,7 +387,7 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
@ -395,28 +396,28 @@ class CuraEngineBackend(QObject, Backend):
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
return
if job.getResult() == StartJobResult.NothingToSlice:
if self._application.platformActivity:
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume or are assigned to a disabled extruder. Please scale or rotate models to fit, or enable an extruder."),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
self.setState(BackendState.Error)
self.backendError.emit(job)
else:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
self._invokeSlice()
return
@ -424,7 +425,7 @@ class CuraEngineBackend(QObject, Backend):
self._socket.sendMessage(job.getSliceMessage())
# Notify the user that it's now up to the backend to do it's job
self.backendStateChange.emit(BackendState.Processing)
self.setState(BackendState.Processing)
if self._slice_start_time:
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
@ -442,7 +443,7 @@ class CuraEngineBackend(QObject, Backend):
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
if node.callDecoration("isBlockSlicing"):
enable_timer = False
self.backendStateChange.emit(BackendState.Disabled)
self.setState(BackendState.Disabled)
self._is_disabled = True
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
@ -451,7 +452,7 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer == enable_timer:
return self._use_timer
if enable_timer:
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
self.enableTimer()
return True
else:
@ -518,7 +519,7 @@ class CuraEngineBackend(QObject, Backend):
self._build_plates_to_be_sliced.append(build_plate_number)
self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed)
@ -567,7 +568,7 @@ class CuraEngineBackend(QObject, Backend):
self.stopSlicing()
self.markSliceAll()
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
self.setState(BackendState.NotStarted)
if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData()
@ -613,7 +614,7 @@ class CuraEngineBackend(QObject, Backend):
# \param message The protobuf message containing the slicing progress.
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing)
self.setState(BackendState.Processing)
def _invokeSlice(self) -> None:
if self._use_timer:
@ -632,7 +633,7 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message signalling that slicing is finished.
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
self.backendStateChange.emit(BackendState.Done)
self.setState(BackendState.Done)
self.processingProgress.emit(1.0)
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
@ -832,7 +833,10 @@ class CuraEngineBackend(QObject, Backend):
self._onChanged()
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
del self._stored_optimized_layer_data[job.getBuildPlate()]
if job.getBuildPlate() in self._stored_optimized_layer_data:
del self._stored_optimized_layer_data[job.getBuildPlate()]
else:
Logger.log("w", "The optimized layer data was already deleted for buildplate %s", job.getBuildPlate())
self._process_layers_job = None
Logger.log("d", "See if there is more to slice(2)...")
self._invokeSlice()

View file

@ -137,6 +137,7 @@ class ProcessSlicedLayersJob(Job):
extruder = polygon.extruder
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
line_types = line_types.reshape((-1,1))
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
@ -195,7 +196,7 @@ class ProcessSlicedLayersJob(Job):
if extruders:
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
for extruder in extruders:
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
position = int(extruder.getMetaDataEntry("position", default = "0"))
try:
default_color = ExtrudersModel.defaultColors[position]
except IndexError:

View file

@ -66,11 +66,19 @@ class GcodeStartEndFormatter(Formatter):
return "{" + key + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError:
default_value_str = "{" + key + "}"
value = default_value_str
# "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value.
if key in kwargs["-1"]:
value = kwargs["-1"]
if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]:
value = kwargs[str(extruder_nr)][key]
if value == default_value_str:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
return value
## Job class that builds up the message of scene data to send to CuraEngine.
@ -315,7 +323,7 @@ class StartSliceJob(Job):
value = stack.getProperty(key, "value")
result[key] = value
Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
result["print_temperature"] = result["material_print_temperature"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.

View file

@ -2,7 +2,7 @@
"name": "CuraEngine Backend",
"author": "Ultimaker B.V.",
"description": "Provides the link to the CuraEngine slicing backend.",
"api": 5,
"version": "1.0.0",
"api": "6.0",
"version": "1.0.1",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Cura Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for importing Cura profiles.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Cura Profile Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for exporting Cura profiles.",
"api": 5,
"api": "6.0",
"i18n-catalog":"cura"
}

View file

@ -86,6 +86,11 @@ class FirmwareUpdateCheckerJob(Job):
current_version = self.getCurrentVersion()
# This case indicates that was an error checking the version.
# It happens for instance when not connected to internet.
if current_version == self.ZERO_VERSION:
return
# If it is the first time the version is checked, the checked_version is ""
setting_key_str = getSettingsKeyForMachine(machine_id)
checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str))

View file

@ -1,8 +1,8 @@
{
"name": "Firmware Update Checker",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Checks for firmware updates.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -57,7 +57,7 @@ class FirmwareUpdaterMachineAction(MachineAction):
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
if self._active_output_device and self._active_output_device.activePrinter and self._active_output_device.activePrinter.getController().can_update_firmware:
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
return self._active_firmware_updater

View file

@ -22,7 +22,7 @@ Cura.MachineAction
{
id: firmwareUpdaterMachineAction
anchors.fill: parent;
UM.I18nCatalog { id: catalog; name:"cura"}
UM.I18nCatalog { id: catalog; name: "cura"}
spacing: UM.Theme.getSize("default_margin").height
Label

View file

@ -1,8 +1,8 @@
{
"name": "Firmware Updater",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides a machine actions for updating firmware.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Compressed G-code Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Reads g-code from a compressed archive.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Compressed G-code Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Writes g-code to a compressed archive.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "G-code Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for importing profiles from g-code files.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -364,6 +364,8 @@ class FlavorParser:
self._layer_type = LayerPolygon.SupportType
elif type == "FILL":
self._layer_type = LayerPolygon.InfillType
elif type == "SUPPORT-INTERFACE":
self._layer_type = LayerPolygon.SupportInterfaceType
else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)

View file

@ -1,8 +1,8 @@
{
"name": "G-code Reader",
"author": "Victor Larchenko",
"version": "1.0.0",
"author": "Victor Larchenko, Ultimaker",
"version": "1.0.1",
"description": "Allows loading and displaying G-code files.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "G-code Writer",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Writes g-code to a file.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -20,7 +20,7 @@ UM.Dialog
GridLayout
{
UM.I18nCatalog{id: catalog; name:"cura"}
UM.I18nCatalog{id: catalog; name: "cura"}
anchors.fill: parent;
Layout.fillWidth: true
columnSpacing: 16 * screenScaleFactor

View file

@ -1,8 +1,8 @@
{
"name": "Image Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Enables ability to generate printable geometry from 2D image files.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Legacy Cura Profile Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides support for importing profiles from legacy Cura versions.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016 Ultimaker B.V.
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
@ -13,7 +13,8 @@ import Cura 1.0 as Cura
Cura.MachineAction
{
id: base
property var extrudersModel: Cura.ExtrudersModel{}
property var extrudersModel: Cura.ExtrudersModel{} // Do not retrieve the Model from a backend. Otherwise the tabs
// in tabView will not removed/updated. Probably QML bug
property int extruderTabsCount: 0
property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""
@ -23,7 +24,7 @@ Cura.MachineAction
target: base.extrudersModel
onModelChanged:
{
var extruderCount = base.extrudersModel.rowCount();
var extruderCount = base.extrudersModel.count;
base.extruderTabsCount = extruderCount;
}
}

View file

@ -1,8 +1,8 @@
{
"name": "Machine Settings action",
"author": "fieldOfView",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -4,19 +4,19 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import UM 1.2 as UM
import Cura 1.0 as Cura
Button
{
id: modelCheckerButton
UM.I18nCatalog{id: catalog; name:"cura"}
UM.I18nCatalog
{
id: catalog
name: "cura"
}
visible: manager.hasWarnings
tooltip: catalog.i18nc("@info:tooltip", "Some things could be problematic in this print. Click to see tips for adjustment.")
@ -25,6 +25,8 @@ Button
width: UM.Theme.getSize("save_button_specs_icons").width
height: UM.Theme.getSize("save_button_specs_icons").height
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
style: ButtonStyle
{
background: Item
@ -33,7 +35,6 @@ Button
{
width: UM.Theme.getSize("save_button_specs_icons").width;
height: UM.Theme.getSize("save_button_specs_icons").height;
sourceSize.width: width;
sourceSize.height: width;
color: control.hovered ? UM.Theme.getColor("text_scene_hover") : UM.Theme.getColor("text_scene");
source: "model_checker.svg"

View file

@ -1,8 +1,8 @@
{
"name": "Model Checker",
"author": "Ultimaker B.V.",
"version": "0.1",
"api": 5,
"version": "1.0.1",
"api": "6.0",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle
{
id: viewportOverlay
color: UM.Theme.getColor("viewport_overlay")
anchors.fill: parent
// This mouse area is to prevent mouse clicks to be passed onto the scene.
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons
onWheel: wheel.accepted = true
}
// Disable dropping files into Cura when the monitor page is active
DropArea
{
anchors.fill: parent
}
Loader
{
id: monitorViewComponent
anchors.fill: parent
height: parent.height
property real maximumWidth: parent.width
property real maximumHeight: parent.height
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem : null
}
}

View file

@ -1,45 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.3 as UM
import Cura 1.0 as Cura
Item
{
// parent could be undefined as this component is not visible at all times
width: parent ? parent.width : 0
height: parent ? parent.height : 0
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle
{
id: viewportOverlay
color: UM.Theme.getColor("viewport_overlay")
width: parent.width
height: parent.height
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons
onWheel: wheel.accepted = true
}
}
Loader
{
id: monitorViewComponent
width: parent.width
height: parent.height
property real maximumWidth: parent.width
property real maximumHeight: parent.height
sourceComponent: Cura.MachineManager.printerOutputDevices.length > 0 ? Cura.MachineManager.printerOutputDevices[0].monitorItem: null
visible: sourceComponent != null
}
}

View file

@ -0,0 +1,23 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.3
import UM 1.3 as UM
import Cura 1.1 as Cura
Item
{
signal showTooltip(Item item, point location, string text)
signal hideTooltip()
Cura.MachineSelector
{
id: machineSelection
headerCornerSide: Cura.RoundedRectangle.Direction.All
width: UM.Theme.getSize("machine_selector_widget").width
height: parent.height
anchors.centerIn: parent
}
}

View file

@ -65,15 +65,10 @@ class MonitorStage(CuraStage):
# We can only connect now, as we need to be sure that everything is loaded (plugins get created quite early)
Application.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
self._onOutputDevicesChanged()
self._updateMainOverlay()
self._updateSidebar()
def _updateMainOverlay(self):
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"),
"MonitorMainView.qml")
self.addDisplayComponent("main", main_component_path)
def _updateSidebar(self):
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
"MonitorSidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)
plugin_path = Application.getInstance().getPluginRegistry().getPluginPath(self.getPluginId())
if plugin_path is not None:
menu_component_path = os.path.join(plugin_path, "MonitorMenu.qml")
main_component_path = os.path.join(plugin_path, "MonitorMain.qml")
self.addDisplayComponent("menu", menu_component_path)
self.addDisplayComponent("main", main_component_path)

View file

@ -7,14 +7,16 @@ from . import MonitorStage
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Monitor"),
"weight": 1
"weight": 2
}
}
def register(app):
return {
"stage": MonitorStage.MonitorStage()

View file

@ -1,8 +1,8 @@
{
"name": "Monitor Stage",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides a monitor stage in Cura.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -265,7 +265,6 @@ Item {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: width
sourceSize.width: width
sourceSize.height: width
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
source: UM.Theme.getIcon("minus")

View file

@ -1,8 +1,8 @@
{
"name": "Per Model Settings Tool",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides the Per Model Settings.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -55,14 +55,14 @@ class PostProcessingPlugin(QObject, Extension):
def selectedScriptDefinitionId(self) -> Optional[str]:
try:
return self._script_list[self._selected_script_index].getDefinitionId()
except:
except IndexError:
return ""
@pyqtProperty(str, notify=selectedIndexChanged)
def selectedScriptStackId(self) -> Optional[str]:
try:
return self._script_list[self._selected_script_index].getStackId()
except:
except IndexError:
return ""
## Execute all post-processing scripts on the gcode.

View file

@ -25,13 +25,13 @@ UM.Dialog
{
if(!visible) //Whenever the window is closed (either via the "Close" button or the X on the window frame), we want to update it in the stack.
{
manager.writeScriptsToStack();
manager.writeScriptsToStack()
}
}
Item
{
UM.I18nCatalog{id: catalog; name:"cura"}
UM.I18nCatalog{id: catalog; name: "cura"}
id: base
property int columnWidth: Math.round((base.width / 2) - UM.Theme.getSize("default_margin").width)
property int textMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
@ -61,18 +61,23 @@ UM.Dialog
anchors.leftMargin: base.textMargin
anchors.right: parent.right
anchors.rightMargin: base.textMargin
font: UM.Theme.getFont("large")
font: UM.Theme.getFont("large_bold")
elide: Text.ElideRight
}
ListView
{
id: activeScriptsList
anchors.top: activeScriptsHeader.bottom
anchors.topMargin: base.textMargin
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin: base.textMargin
anchors
{
top: activeScriptsHeader.bottom
left: parent.left
right: parent.right
rightMargin: base.textMargin
topMargin: base.textMargin
leftMargin: UM.Theme.getSize("default_margin").width
}
height: childrenRect.height
model: manager.scriptList
delegate: Item
@ -84,8 +89,12 @@ UM.Dialog
id: activeScriptButton
text: manager.getScriptLabelByKey(modelData.toString())
exclusiveGroup: selectedScriptGroup
width: parent.width
height: UM.Theme.getSize("setting").height
checkable: true
checked: {
checked:
{
if (manager.selectedScriptIndex == index)
{
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
@ -102,8 +111,7 @@ UM.Dialog
manager.setSelectedScriptIndex(index)
base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
}
width: parent.width
height: UM.Theme.getSize("setting").height
style: ButtonStyle
{
background: Rectangle
@ -121,6 +129,7 @@ UM.Dialog
}
}
}
Button
{
id: removeButton
@ -141,7 +150,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.7)
height: Math.round(control.height / 2.7)
sourceSize.width: width
sourceSize.height: width
color: palette.text
source: UM.Theme.getIcon("cross1")
@ -176,7 +184,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
sourceSize.width: width
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("arrow_bottom")
@ -211,7 +218,6 @@ UM.Dialog
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(control.width / 2.5)
height: Math.round(control.height / 2.5)
sourceSize.width: width
sourceSize.height: width
color: control.enabled ? palette.text : disabledPalette.text
source: UM.Theme.getIcon("arrow_top")
@ -252,15 +258,15 @@ UM.Dialog
onTriggered: manager.addScriptToList(modelData.toString())
}
onObjectAdded: scriptsMenu.insertItem(index, object);
onObjectRemoved: scriptsMenu.removeItem(object);
onObjectAdded: scriptsMenu.insertItem(index, object)
onObjectRemoved: scriptsMenu.removeItem(object)
}
}
}
Rectangle
{
color: UM.Theme.getColor("sidebar")
color: UM.Theme.getColor("main_background")
anchors.left: activeScripts.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
@ -271,26 +277,35 @@ UM.Dialog
{
id: scriptSpecsHeader
text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
anchors.top: parent.top
anchors.topMargin: base.textMargin
anchors.left: parent.left
anchors.leftMargin: base.textMargin
anchors.right: parent.right
anchors.rightMargin: base.textMargin
anchors
{
top: parent.top
topMargin: base.textMargin
left: parent.left
leftMargin: base.textMargin
right: parent.right
rightMargin: base.textMargin
}
elide: Text.ElideRight
height: 20 * screenScaleFactor
font: UM.Theme.getFont("large")
font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
}
ScrollView
{
id: scrollView
anchors.top: scriptSpecsHeader.bottom
anchors.topMargin: settingsPanel.textMargin
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors
{
top: scriptSpecsHeader.bottom
topMargin: settingsPanel.textMargin
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
bottom: parent.bottom
}
visible: manager.selectedScriptDefinitionId != ""
style: UM.Theme.styles.scrollview;
@ -300,11 +315,12 @@ UM.Dialog
spacing: UM.Theme.getSize("default_lining").height
model: UM.SettingDefinitionsModel
{
id: definitionsModel;
id: definitionsModel
containerId: manager.selectedScriptDefinitionId
showAll: true
}
delegate:Loader
delegate: Loader
{
id: settingLoader
@ -315,23 +331,24 @@ UM.Dialog
{
if(model.type != undefined)
{
return UM.Theme.getSize("section").height;
return UM.Theme.getSize("section").height
}
else
{
return 0;
return 0
}
}
else
{
return 0;
return 0
}
}
Behavior on height { NumberAnimation { duration: 100 } }
opacity: provider.properties.enabled == "True" ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled: opacity > 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
property var propertyProvider: provider
@ -342,11 +359,12 @@ UM.Dialog
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded: {
onLoaded:
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
settingLoader.item.doDepthIndentation = true
settingLoader.item.doDepthIndentation = false
settingLoader.item.doQualityUserSettingEmphasis = false
}
@ -398,24 +416,20 @@ UM.Dialog
onShowTooltip:
{
tooltip.text = text;
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
tooltip.show(position);
tooltip.text = text
var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0)
tooltip.show(position)
tooltip.target.x = position.x + 1
}
onHideTooltip:
{
tooltip.hide();
}
onHideTooltip: tooltip.hide()
}
}
}
}
}
Cura.SidebarTooltip
Cura.PrintSetupTooltip
{
id: tooltip
}
@ -462,6 +476,7 @@ UM.Dialog
Cura.SettingUnknown { }
}
}
rightButtons: Button
{
text: catalog.i18nc("@action:button", "Close")
@ -469,44 +484,15 @@ UM.Dialog
onClicked: dialog.accept()
}
Button {
Cura.SecondaryButton
{
objectName: "postProcessingSaveAreaButton"
visible: activeScriptsList.count > 0
height: UM.Theme.getSize("save_button_save_to_button").height
height: UM.Theme.getSize("action_button").height
width: height
tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
onClicked: dialog.show()
style: ButtonStyle {
background: Rectangle {
id: deviceSelectionIcon
border.width: UM.Theme.getSize("default_lining").width
border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
control.pressed ? UM.Theme.getColor("action_button_active_border") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
control.pressed ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
Behavior on color { ColorAnimation { duration: 50; } }
anchors.left: parent.left
anchors.leftMargin: Math.round(UM.Theme.getSize("save_button_text_margin").width / 2);
width: parent.height
height: parent.height
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: Math.round(parent.width / 2)
height: Math.round(parent.height / 2)
sourceSize.width: width
sourceSize.height: height
color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
control.pressed ? UM.Theme.getColor("action_button_active_text") :
control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
source: "postprocessing.svg"
}
}
label: Label{ }
}
iconSource: "postprocessing.svg"
fixedWidthMode: true
}
}

View file

@ -1,8 +1,8 @@
{
"name": "Post Processing",
"author": "Ultimaker",
"version": "2.2",
"api": 5,
"version": "2.2.1",
"api": "6.0",
"description": "Extension that allows for user created scripts for post processing",
"catalog": "cura"
}

View file

@ -112,7 +112,7 @@ class ChangeAtZ(Script):
"e1_Change_speed":
{
"label": "Change Speed",
"description": "Select if total speed (print and travel) has to be cahnged",
"description": "Select if total speed (print and travel) has to be changed",
"type": "bool",
"default_value": false
},

View file

@ -0,0 +1,3 @@
A good example script is SearchAndReplace.py.
If you have any questions please ask them at:
https://github.com/Ultimaker/Cura/issues

View file

@ -1,43 +0,0 @@
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from ..Script import Script
class ExampleScript(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name":"Example script",
"key": "ExampleScript",
"metadata": {},
"version": 2,
"settings":
{
"test":
{
"label": "Test",
"description": "None",
"unit": "mm",
"type": "float",
"default_value": 0.5,
"minimum_value": "0",
"minimum_value_warning": "0.1",
"maximum_value_warning": "1"
},
"derp":
{
"label": "zomg",
"description": "afgasgfgasfgasf",
"unit": "mm",
"type": "float",
"default_value": 0.5,
"minimum_value": "0",
"minimum_value_warning": "0.1",
"maximum_value_warning": "1"
}
}
}"""
def execute(self, data):
return data

View file

@ -0,0 +1,134 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3
import UM 1.3 as UM
import Cura 1.1 as Cura
import QtGraphicalEffects 1.0 // For the dropshadow
Item
{
id: prepareMenu
UM.I18nCatalog
{
id: catalog
name: "cura"
}
// Item to ensure that all of the buttons are nicely centered.
Item
{
anchors.horizontalCenter: parent.horizontalCenter
width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width
height: parent.height
RowLayout
{
id: itemRow
anchors.left: openFileButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: Math.round(0.9 * prepareMenu.width)
height: parent.height
spacing: 0
Cura.MachineSelector
{
id: machineSelection
headerCornerSide: Cura.RoundedRectangle.Direction.Left
Layout.minimumWidth: UM.Theme.getSize("machine_selector_widget").width
Layout.maximumWidth: UM.Theme.getSize("machine_selector_widget").width
Layout.fillWidth: true
Layout.fillHeight: true
}
// Separator line
Rectangle
{
height: parent.height
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
Cura.ConfigurationMenu
{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: itemRow.width - machineSelection.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width
}
// Separator line
Rectangle
{
height: parent.height
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
Item
{
id: printSetupSelectorItem
// This is a work around to prevent the printSetupSelector from having to be re-loaded every time
// a stage switch is done.
children: [printSetupSelector]
height: childrenRect.height
width: childrenRect.width
}
}
Button
{
id: openFileButton
height: UM.Theme.getSize("stage_menu").height
width: UM.Theme.getSize("stage_menu").height
onClicked: Cura.Actions.open.trigger()
hoverEnabled: true
contentItem: Item
{
anchors.fill: parent
UM.RecolorImage
{
id: buttonIcon
anchors.centerIn: parent
source: UM.Theme.getIcon("load")
width: UM.Theme.getSize("button_icon").width
height: UM.Theme.getSize("button_icon").height
color: UM.Theme.getColor("icon")
sourceSize.height: height
}
}
background: Rectangle
{
id: background
height: UM.Theme.getSize("stage_menu").height
width: UM.Theme.getSize("stage_menu").height
radius: UM.Theme.getSize("default_radius").width
color: openFileButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
}
DropShadow
{
id: shadow
// Don't blur the shadow
radius: 0
anchors.fill: background
source: background
verticalOffset: 2
visible: true
color: UM.Theme.getColor("action_button_shadow")
// Should always be drawn behind the background.
z: background.z - 1
}
}
}
}

View file

@ -1,19 +1,19 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from cura.Stages.CuraStage import CuraStage
## Stage for preparing model (slicing).
class PrepareStage(CuraStage):
def __init__(self, parent = None):
super().__init__(parent)
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
def _engineCreated(self):
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
"PrepareSidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path)
menu_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("PrepareStage"), "PrepareMenu.qml")
self.addDisplayComponent("menu", menu_component_path)

View file

@ -1,8 +1,8 @@
{
"name": "Prepare Stage",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides a prepare stage in Cura.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Loader
{
id: previewMain
source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
}

View file

@ -0,0 +1,79 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3
import UM 1.3 as UM
import Cura 1.1 as Cura
Item
{
id: previewMenu
property real itemHeight: height - 2 * UM.Theme.getSize("default_lining").width
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Row
{
id: stageMenuRow
anchors.centerIn: parent
height: parent.height
width: childrenRect.width
// We want this row to have a preferred with equals to the 85% of the parent
property int preferredWidth: Math.round(0.85 * previewMenu.width)
Cura.ViewsSelector
{
id: viewsSelector
height: parent.height
width: UM.Theme.getSize("views_selector").width
headerCornerSide: Cura.RoundedRectangle.Direction.Left
}
// Separator line
Rectangle
{
height: parent.height
// If there is no viewPanel, we only need a single spacer, so hide this one.
visible: viewPanel.source != ""
width: visible ? UM.Theme.getSize("default_lining").width : 0
color: UM.Theme.getColor("lining")
}
// This component will grow freely up to complete the preferredWidth of the row.
Loader
{
id: viewPanel
height: parent.height
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
}
// Separator line
Rectangle
{
height: parent.height
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
Item
{
id: printSetupSelectorItem
// This is a work around to prevent the printSetupSelector from having to be re-loaded every time
// a stage switch is done.
children: [printSetupSelector]
height: childrenRect.height
width: childrenRect.width
}
}
}

View file

@ -0,0 +1,51 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from UM.Qt.QtApplication import QtApplication
from cura.Stages.CuraStage import CuraStage
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from UM.View.View import View
## Displays a preview of what you're about to print.
#
# The Python component of this stage just loads PreviewMain.qml for display
# when the stage is selected, and makes sure that it reverts to the previous
# view when the previous stage is activated.
class PreviewStage(CuraStage):
def __init__(self, application: QtApplication, parent = None) -> None:
super().__init__(parent)
self._application = application
self._application.engineCreatedSignal.connect(self._engineCreated)
self._previously_active_view = None # type: Optional[View]
## When selecting the stage, remember which was the previous view so that
# we can revert to that view when we go out of the stage later.
def onStageSelected(self) -> None:
self._previously_active_view = self._application.getController().getActiveView()
## Called when going to a different stage (away from the Preview Stage).
#
# When going to a different stage, the view should be reverted to what it
# was before. Normally, that just reverts it to solid view.
def onStageDeselected(self) -> None:
if self._previously_active_view is not None:
self._application.getController().setActiveView(self._previously_active_view.getPluginId())
self._previously_active_view = None
## Delayed load of the QML files.
#
# We need to make sure that the QML engine is running before we can load
# these.
def _engineCreated(self) -> None:
plugin_path = self._application.getPluginRegistry().getPluginPath(self.getPluginId())
if plugin_path is not None:
menu_component_path = os.path.join(plugin_path, "PreviewMenu.qml")
main_component_path = os.path.join(plugin_path, "PreviewMain.qml")
self.addDisplayComponent("menu", menu_component_path)
self.addDisplayComponent("main", main_component_path)

View file

@ -0,0 +1,22 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import PreviewStage
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Preview"),
"weight": 1
}
}
def register(app):
return {
"stage": PreviewStage.PreviewStage(app)
}

View file

@ -0,0 +1,8 @@
{
"name": "Preview Stage",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a preview stage in Cura.",
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -2,7 +2,7 @@
"name": "Removable Drive Output Device Plugin",
"author": "Ultimaker B.V.",
"description": "Provides removable drive hotplugging and writing support.",
"version": "1.0.0",
"api": 5,
"version": "1.0.1",
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -13,23 +13,20 @@ Item
{
id: sliderRoot
// handle properties
property real handleSize: 10
// Handle properties
property real handleSize: UM.Theme.getSize("slider_handle").width
property real handleRadius: handleSize / 2
property real minimumRangeHandleSize: handleSize / 2
property color upperHandleColor: "black"
property color lowerHandleColor: "black"
property color rangeHandleColor: "black"
property color handleActiveColor: "white"
property real handleLabelWidth: width
property color upperHandleColor: UM.Theme.getColor("slider_handle")
property color lowerHandleColor: UM.Theme.getColor("slider_handle")
property color rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
property color handleActiveColor: UM.Theme.getColor("slider_handle_active")
property var activeHandle: upperHandle
// track properties
property real trackThickness: 4 // width of the slider track
property real trackRadius: trackThickness / 2
property color trackColor: "white"
property real trackBorderWidth: 1 // width of the slider track border
property color trackBorderColor: "black"
// Track properties
property real trackThickness: UM.Theme.getSize("slider_groove").width // width of the slider track
property real trackRadius: UM.Theme.getSize("slider_groove_radius").width
property color trackColor: UM.Theme.getColor("slider_groove")
// value properties
property real maximumValue: 100
@ -80,7 +77,7 @@ Item
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
// Slider track
Rectangle
{
id: track
@ -90,8 +87,6 @@ Item
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.layersVisible
}
@ -106,7 +101,7 @@ Item
anchors.horizontalCenter: sliderRoot.horizontalCenter
visible: sliderRoot.layersVisible
// set the new value when dragging
// Set the new value when dragging
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
@ -140,9 +135,10 @@ Item
Rectangle
{
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
width: sliderRoot.trackThickness
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
radius: sliderRoot.trackRadius
color: sliderRoot.rangeHandleColor
}
@ -275,7 +271,7 @@ Item
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
@ -385,9 +381,9 @@ Item
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
target: Qt.point(sliderRoot.width + width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties

View file

@ -14,19 +14,17 @@ Item
id: sliderRoot
// handle properties
property real handleSize: 10
property real handleSize: UM.Theme.getSize("slider_handle").width
property real handleRadius: handleSize / 2
property color handleColor: "black"
property color handleActiveColor: "white"
property color rangeColor: "black"
property color handleColor: UM.Theme.getColor("slider_handle")
property color handleActiveColor: UM.Theme.getColor("slider_handle_active")
property color rangeColor: UM.Theme.getColor("slider_groove_fill")
property real handleLabelWidth: width
// track properties
property real trackThickness: 4 // width of the slider track
property real trackRadius: trackThickness / 2
property color trackColor: "white"
property real trackBorderWidth: 1 // width of the slider track border
property color trackBorderColor: "black"
property real trackThickness: UM.Theme.getSize("slider_groove").width
property real trackRadius: UM.Theme.getSize("slider_groove_radius").width
property color trackColor: UM.Theme.getColor("slider_groove")
// value properties
property real maximumValue: 100
@ -68,8 +66,6 @@ Item
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.pathsVisible
}
@ -86,9 +82,10 @@ Item
Rectangle
{
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
height: sliderRoot.trackThickness
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
radius: sliderRoot.trackRadius
color: sliderRoot.rangeColor
}
}

View file

@ -16,6 +16,7 @@ from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry
from UM.Qt.QtApplication import QtApplication
from UM.Resources import Resources
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -26,8 +27,8 @@ from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.GL.ShaderProgram import ShaderProgram
from UM.View.View import View
from UM.i18n import i18nCatalog
from cura.CuraView import CuraView
from cura.Scene.ConvexHullNode import ConvexHullNode
from cura.CuraApplication import CuraApplication
@ -48,15 +49,15 @@ catalog = i18nCatalog("cura")
## View used to display g-code paths.
class SimulationView(View):
# Must match SimulationView.qml
class SimulationView(CuraView):
# Must match SimulationViewMenuComponent.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
LAYER_VIEW_TYPE_LINE_TYPE = 1
LAYER_VIEW_TYPE_FEEDRATE = 2
LAYER_VIEW_TYPE_THICKNESS = 3
def __init__(self) -> None:
super().__init__()
def __init__(self, parent = None) -> None:
super().__init__(parent)
self._max_layers = 0
self._current_layer_num = 0
@ -113,6 +114,16 @@ class SimulationView(View):
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Simulation View"))
QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
def _onEngineCreated(self) -> None:
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path:
self.addDisplayComponent("main", os.path.join(plugin_path, "SimulationViewMainComponent.qml"))
self.addDisplayComponent("menu", os.path.join(plugin_path, "SimulationViewMenuComponent.qml"))
else:
Logger.log("e", "Unable to find the path for %s", self.getPluginId())
def _evaluateCompatibilityMode(self) -> bool:
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
@ -331,12 +342,16 @@ class SimulationView(View):
return self._extruder_count
def getMinFeedrate(self) -> float:
if abs(self._min_feedrate - 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_feedrate
def getMaxFeedrate(self) -> float:
return self._max_feedrate
def getMinThickness(self) -> float:
if abs(self._min_thickness - 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_thickness
def getMaxThickness(self) -> float:

View file

@ -1,808 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
id: base
width:
{
if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
}
else
{
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (viewSettings.collapsed)
{
if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
}
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
}
else if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
}
else if (UM.Preferences.getValue("layerview/layer_view_type") == 0)
{
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
else
{
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
Behavior on height { NumberAnimation { duration: 100 } }
property var buttonTarget:
{
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
}
return Qt.point(0,0)
}
Rectangle
{
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
height: parent.height
clip: true
z: layerSlider.z - 1
color: UM.Theme.getColor("tool_panel_background")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Button {
id: collapseButton
anchors.top: parent.top
anchors.topMargin: Math.round(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
onClicked: viewSettings.collapsed = !viewSettings.collapsed
style: ButtonStyle
{
background: UM.RecolorImage
{
width: control.width
height: control.height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("setting_control_text")
source: viewSettings.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
}
label: Label{ }
}
}
ColumnLayout
{
id: viewSettings
property bool collapsed: false
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
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 only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("layerview_row_spacing").height
Label
{
id: layerViewTypesLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Color scheme")
font: UM.Theme.getFont("default");
visible: !UM.SimulationView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("setting_control_text")
}
ListModel // matches SimulationView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Feedrate"),
type_id: 2
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Layer thickness"),
type_id: 3 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
anchors.left: parent.left
Layout.fillWidth: true
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.SimulationView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3);
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
}
}
Label
{
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.SimulationView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
Item
{
height: Math.round(UM.Theme.getSize("default_margin").width / 2)
width: width
}
Connections
{
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
playButton.pauseSimulation();
viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin");
viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill");
viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
}
}
Repeater
{
model: Cura.ExtrudersModel{}
CheckBox
{
id: extrudersModelCheckBox
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
onClicked:
{
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
}
visible: !UM.SimulationView.compatibilityMode
enabled: index + 1 <= 4
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
radius: Math.round(width / 2)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !viewSettings.show_legend & !viewSettings.show_gradient
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
Repeater
{
model: ListModel
{
id: typesLegendModel
Component.onCompleted:
{
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: viewSettings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: viewSettings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: viewSettings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: viewSettings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox
{
id: legendModelCheckBox
checked: model.initialValue
onClicked:
{
UM.Preferences.setValue(model.preference, checked);
}
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: viewSettings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width/2)
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox
{
checked: viewSettings.only_show_top_layers
onClicked:
{
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox
{
checked: viewSettings.top_layer_count == 5
onClicked:
{
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater
{
model: ListModel
{
id: typesLegendModelNoCheck
Component.onCompleted:
{
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label
{
text: label
visible: viewSettings.show_legend
id: typesLegendModelLabel
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: viewSettings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Item
{
id: gradientLegend
visible: viewSettings.show_gradient
width: parent.width
height: UM.Theme.getSize("layerview_row").height
anchors
{
topMargin: UM.Theme.getSize("slider_layerview_margin").height
horizontalCenter: parent.horizontalCenter
}
Label
{
text: minText()
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function minText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","min")
}
}
Label
{
text: unitsText()
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function unitsText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return "mm/s"
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return "mm"
}
}
return ""
}
}
Label
{
text: maxText()
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function maxText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","max")
}
}
}
// Gradient colors for feedrate
Rectangle
{ // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: feedrateGradient
visible: viewSettings.show_feedrate_gradient
anchors.left: parent.right
height: parent.width
width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient
{
GradientStop
{
position: 0.000
color: Qt.rgba(1, 0.5, 0, 1)
}
GradientStop
{
position: 0.625
color: Qt.rgba(0.375, 0.5, 0, 1)
}
GradientStop
{
position: 0.75
color: Qt.rgba(0.25, 1, 0, 1)
}
GradientStop
{
position: 1.0
color: Qt.rgba(0, 0, 1, 1)
}
}
}
// Gradient colors for layer thickness (similar to parula colormap)
Rectangle // In QML 5.9 can be changed by LinearGradient
{
// Invert values because then the bar is rotated 90 degrees
id: thicknessGradient
visible: viewSettings.show_thickness_gradient
anchors.left: parent.right
height: parent.width
width: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient
{
GradientStop
{
position: 0.000
color: Qt.rgba(1, 1, 0, 1)
}
GradientStop
{
position: 0.25
color: Qt.rgba(1, 0.75, 0.25, 1)
}
GradientStop
{
position: 0.5
color: Qt.rgba(0, 0.75, 0.5, 1)
}
GradientStop
{
position: 0.75
color: Qt.rgba(0, 0.375, 0.75, 1)
}
GradientStop
{
position: 1.0
color: Qt.rgba(0, 0, 0.5, 1)
}
}
}
}
}
Item
{
id: slidersBox
width: parent.width
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
anchors
{
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
left: parent.left
}
PathSlider
{
id: pathSlider
height: UM.Theme.getSize("slider_handle").width
anchors.left: playButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
visible: !UM.SimulationView.compatibilityMode
// custom properties
handleValue: UM.SimulationView.currentPath
maximumValue: UM.SimulationView.numPaths
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
handleColor: UM.Theme.getColor("slider_handle")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
rangeColor: UM.Theme.getColor("slider_groove_fill")
// update values when layer data changes
Connections
{
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (pathSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted:
{
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
LayerSlider
{
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors
{
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
right: parent.right
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
}
// custom properties
upperValue: UM.SimulationView.currentLayer
lowerValue: UM.SimulationView.minimumLayer
maximumValue: UM.SimulationView.numLayers
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
upperHandleColor: UM.Theme.getColor("slider_handle")
lowerHandleColor: UM.Theme.getColor("slider_handle")
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
// update values when layer data changes
Connections
{
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
onCurrentLayerChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (layerSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted:
{
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// Play simulation button
Button
{
id: playButton
iconSource: "./resources/simulation_resume.svg"
style: UM.Theme.styles.small_tool_button
visible: !UM.SimulationView.compatibilityMode
anchors
{
verticalCenter: pathSlider.verticalCenter
}
property var status: 0 // indicates if it's stopped (0) or playing (1)
onClicked:
{
switch(status)
{
case 0:
{
resumeSimulation()
break
}
case 1:
{
pauseSimulation()
break
}
}
}
function pauseSimulation()
{
UM.SimulationView.setSimulationRunning(false)
iconSource = "./resources/simulation_resume.svg"
simulationTimer.stop()
status = 0
layerSlider.manuallyChanged = true
pathSlider.manuallyChanged = true
}
function resumeSimulation()
{
UM.SimulationView.setSimulationRunning(true)
iconSource = "./resources/simulation_pause.svg"
simulationTimer.start()
layerSlider.manuallyChanged = false
pathSlider.manuallyChanged = false
}
}
Timer
{
id: simulationTimer
interval: 100
running: false
repeat: true
onTriggered:
{
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (playButton.status == 0)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer+1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
// correctly, because part of the logic is in this trigger function.
playButton.status = 1
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -0,0 +1,211 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.4 as UM
import Cura 1.0 as Cura
Item
{
property bool is_simulation_playing: false
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
PathSlider
{
id: pathSlider
height: UM.Theme.getSize("slider_handle").width
width: UM.Theme.getSize("slider_layerview_size").height
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
visible: !UM.SimulationView.compatibilityMode
// Custom properties
handleValue: UM.SimulationView.currentPath
maximumValue: UM.SimulationView.numPaths
// Update values when layer data changes.
Connections
{
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (pathSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
// Ensure that the slider handlers show the correct value after switching views.
Component.onCompleted:
{
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
UM.SimpleButton
{
id: playButton
iconSource: !is_simulation_playing ? "./resources/simulation_resume.svg": "./resources/simulation_pause.svg"
width: UM.Theme.getSize("small_button").width
height: UM.Theme.getSize("small_button").height
hoverColor: UM.Theme.getColor("slider_handle_active")
color: UM.Theme.getColor("slider_handle")
iconMargin: UM.Theme.getSize("thick_lining").width
visible: !UM.SimulationView.compatibilityMode
Connections
{
target: UM.Preferences
onPreferenceChanged:
{
playButton.pauseSimulation()
}
}
anchors
{
right: pathSlider.left
verticalCenter: pathSlider.verticalCenter
}
onClicked:
{
if(is_simulation_playing)
{
pauseSimulation()
}
else
{
resumeSimulation()
}
}
function pauseSimulation()
{
UM.SimulationView.setSimulationRunning(false)
simulationTimer.stop()
is_simulation_playing = false
layerSlider.manuallyChanged = true
pathSlider.manuallyChanged = true
}
function resumeSimulation()
{
UM.SimulationView.setSimulationRunning(true)
simulationTimer.start()
layerSlider.manuallyChanged = false
pathSlider.manuallyChanged = false
}
}
Timer
{
id: simulationTimer
interval: 100
running: false
repeat: true
onTriggered:
{
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (!is_simulation_playing)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath + 1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer + 1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath + 1)
}
}
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
// correctly, because part of the logic is in this trigger function.
is_simulation_playing = true
}
}
LayerSlider
{
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("slider_layerview_size").height
anchors
{
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: UM.Theme.getSize("default_margin").width
}
// Custom properties
upperValue: UM.SimulationView.currentLayer
lowerValue: UM.SimulationView.minimumLayer
maximumValue: UM.SimulationView.numLayers
// Update values when layer data changes
Connections
{
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
onCurrentLayerChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (layerSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// Make sure the slider handlers show the correct value after switching views
Component.onCompleted:
{
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
}

View file

@ -0,0 +1,551 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import QtGraphicalEffects 1.0
import UM 1.0 as UM
import Cura 1.0 as Cura
Cura.ExpandableComponent
{
id: base
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
Connections
{
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type")
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex)
viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|")
viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves")
viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers")
viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin")
viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill")
viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers")
viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count")
}
}
headerItem: Item
{
Label
{
id: colorSchemeLabel
text: catalog.i18nc("@label", "Color scheme")
verticalAlignment: Text.AlignVCenter
height: parent.height
elide: Text.ElideRight
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: layerTypeCombobox.currentText
verticalAlignment: Text.AlignVCenter
anchors
{
left: colorSchemeLabel.right
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
}
height: parent.height
elide: Text.ElideRight
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
}
contentItem: Column
{
id: viewSettings
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// If we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
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 only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
width: UM.Theme.getSize("layerview_menu_size").width - 2 * UM.Theme.getSize("default_margin").width
height: implicitHeight
spacing: UM.Theme.getSize("layerview_row_spacing").height
ListModel // matches SimulationView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Feedrate"),
type_id: 2
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Layer thickness"),
type_id: 3 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
width: parent.width
model: layerViewTypes
visible: !UM.SimulationView.compatibilityMode
style: UM.Theme.styles.combobox
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// 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);
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
}
}
Label
{
id: compatibilityModeLabel
text: catalog.i18nc("@label", "Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.SimulationView.compatibilityMode
height: UM.Theme.getSize("layerview_row").height
width: parent.width
renderType: Text.NativeRendering
}
Item // Spacer
{
height: UM.Theme.getSize("narrow_margin").width
width: width
}
Repeater
{
model: CuraApplication.getExtrudersModel()
CheckBox
{
id: extrudersModelCheckBox
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
visible: !UM.SimulationView.compatibilityMode
enabled: index < 4
onClicked:
{
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
}
style: UM.Theme.styles.checkbox
UM.RecolorImage
{
id: swatch
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
source: UM.Theme.getIcon("extruder_button")
color: model.color
}
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors
{
verticalCenter: parent.verticalCenter
left: extrudersModelCheckBox.left
right: extrudersModelCheckBox.right
leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width / 2)
rightMargin: UM.Theme.getSize("default_margin").width * 2
}
renderType: Text.NativeRendering
}
}
}
Repeater
{
model: ListModel
{
id: typesLegendModel
Component.onCompleted:
{
typesLegendModel.append({
label: catalog.i18nc("@label", "Travels"),
initialValue: viewSettings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Helpers"),
initialValue: viewSettings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Shell"),
initialValue: viewSettings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Infill"),
initialValue: viewSettings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox
{
id: legendModelCheckBox
checked: model.initialValue
onClicked: UM.Preferences.setValue(model.preference, checked)
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
style: UM.Theme.styles.checkbox
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: viewSettings.show_legend
}
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
renderType: Text.NativeRendering
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left
anchors.right: legendModelCheckBox.right
anchors.leftMargin: UM.Theme.getSize("checkbox").width + Math.round(UM.Theme.getSize("default_margin").width / 2)
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox
{
checked: viewSettings.only_show_top_layers
onClicked: UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0)
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
width: parent.width
}
CheckBox
{
checked: viewSettings.top_layer_count == 5
onClicked: UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1)
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
width: parent.width
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater
{
model: ListModel
{
id: typesLegendModelNoCheck
Component.onCompleted:
{
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label
{
text: label
visible: viewSettings.show_legend
id: typesLegendModelLabel
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
}
}
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Item
{
id: gradientLegend
visible: viewSettings.show_gradient
width: parent.width
height: UM.Theme.getSize("layerview_row").height
Label //Minimum value.
{
text:
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","min")
}
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
Label //Unit in the middle.
{
text:
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return "mm/s"
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return "mm"
}
}
return ""
}
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
Label //Maximum value.
{
text: {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","max")
}
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
// Gradient colors for feedrate
Rectangle
{
id: feedrateGradient
visible: viewSettings.show_feedrate_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.000
color: Qt.rgba(0, 0, 1, 1)
}
GradientStop
{
position: 0.25
color: Qt.rgba(0.25, 1, 0, 1)
}
GradientStop
{
position: 0.375
color: Qt.rgba(0.375, 0.5, 0, 1)
}
GradientStop
{
position: 1.0
color: Qt.rgba(1, 0.5, 0, 1)
}
}
}
}
// Gradient colors for layer thickness (similar to parula colormap)
Rectangle
{
id: thicknessGradient
visible: viewSettings.show_thickness_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.000
color: Qt.rgba(0, 0, 0.5, 1)
}
GradientStop
{
position: 0.25
color: Qt.rgba(0, 0.375, 0.75, 1)
}
GradientStop
{
position: 0.5
color: Qt.rgba(0, 0.75, 0.5, 1)
}
GradientStop
{
position: 0.75
color: Qt.rgba(1, 0.75, 0.25, 1)
}
GradientStop
{
position: 1.0
color: Qt.rgba(1, 1, 0, 1)
}
}
}
}
}
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -8,19 +8,21 @@ from . import SimulationViewProxy, SimulationView
catalog = i18nCatalog("cura")
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
"view_panel": "SimulationView.qml",
"weight": 2
"weight": 0
}
}
def createSimulationViewProxy(engine, script_engine):
return SimulationViewProxy.SimulationViewProxy()
def register(app):
simulation_view = SimulationView.SimulationView()
qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy)
return { "view": SimulationView.SimulationView()}
return { "view": simulation_view}

View file

@ -49,12 +49,13 @@ fragment =
// discard movements
discard;
}
// support: 4, 5, 7, 10
// support: 4, 5, 7, 10, 11 (prime tower)
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
((v_line_type >= 10.5) && (v_line_type <= 11.5))
)) {
discard;
}

View file

@ -154,7 +154,7 @@ geometry41core =
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
return;
}
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) {
return;
}
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {

View file

@ -45,19 +45,23 @@ fragment =
void main()
{
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5))
{ // actually, 8 and 9
// discard movements
discard;
}
// support: 4, 5, 7, 10
// support: 4, 5, 7, 10, 11
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
)) {
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
((v_line_type >= 10.5) && (v_line_type <= 11.5))
))
{
discard;
}
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
@ -65,7 +69,8 @@ fragment =
discard;
}
// infill:
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5))
{
// discard movements
discard;
}
@ -117,12 +122,13 @@ fragment41core =
// discard movements
discard;
}
// helpers: 4, 5, 7, 10
// helpers: 4, 5, 7, 10, 11
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
((v_line_type >= 4.5) && (v_line_type <= 5.5)) ||
((v_line_type >= 10.5) && (v_line_type <= 11.5))
)) {
discard;
}

View file

@ -1,8 +1,8 @@
{
"name": "Simulation View",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides the Simulation view.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Slice info",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -12,7 +12,6 @@ from UM.Math.Color import Color
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtrudersModel import ExtrudersModel
import math
@ -29,13 +28,16 @@ class SolidView(View):
self._non_printing_shader = None
self._support_mesh_shader = None
self._extruders_model = ExtrudersModel()
self._extruders_model = None
self._theme = None
def beginRendering(self):
scene = self.getController().getScene()
renderer = self.getRenderer()
if not self._extruders_model:
self._extruders_model = Application.getInstance().getExtrudersModel()
if not self._theme:
self._theme = Application.getInstance().getTheme()

View file

@ -10,7 +10,8 @@ def getMetaData():
return {
"view": {
"name": i18n_catalog.i18nc("@item:inmenu", "Solid view"),
"weight": 0
"weight": 0,
"visible": False
}
}

View file

@ -1,8 +1,8 @@
{
"name": "Solid View",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Provides a normal solid mesh view.",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,8 +1,8 @@
{
"name": "Support Eraser",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"version": "1.0.1",
"description": "Creates an eraser mesh to block the printing of support in certain places",
"api": 5,
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -1,7 +1,7 @@
{
"name": "Toolbox",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 5,
"version": "1.0.1",
"api": "6.0",
"description": "Find, manage and install new Cura packages."
}

View file

@ -0,0 +1,106 @@
import QtQuick 2.7
import QtQuick.Controls 2.1
import UM 1.0 as UM
import Cura 1.1 as Cura
Item
{
id: ratingWidget
property real rating: 0
property int indexHovered: -1
property string packageId: ""
property int userRating: 0
property bool canRate: false
signal rated(int rating)
width: contentRow.width
height: contentRow.height
MouseArea
{
id: mouseArea
anchors.fill: parent
hoverEnabled: ratingWidget.canRate
acceptedButtons: Qt.NoButton
onExited:
{
if(ratingWidget.canRate)
{
ratingWidget.indexHovered = -1
}
}
Row
{
id: contentRow
height: childrenRect.height
Repeater
{
model: 5 // We need to get 5 stars
Button
{
id: control
hoverEnabled: true
onHoveredChanged:
{
if(hovered && ratingWidget.canRate)
{
indexHovered = index
}
}
ToolTip.visible: control.hovered && !ratingWidget.canRate
ToolTip.text: !Cura.API.account.isLoggedIn ? catalog.i18nc("@label", "You need to login first before you can rate"): catalog.i18nc("@label", "You need to install the package before you can rate")
property bool isStarFilled:
{
// If the entire widget is hovered, override the actual rating.
if(ratingWidget.indexHovered >= 0)
{
return indexHovered >= index
}
if(ratingWidget.userRating > 0)
{
return userRating >= index +1
}
return rating >= index + 1
}
contentItem: Item {}
height: UM.Theme.getSize("rating_star").height
width: UM.Theme.getSize("rating_star").width
background: UM.RecolorImage
{
source: UM.Theme.getIcon(control.isStarFilled ? "star_filled" : "star_empty")
sourceSize.width: width
sourceSize.height: height
// Unfilled stars should always have the default color. Only filled stars should change on hover
color:
{
if(!ratingWidget.canRate)
{
return UM.Theme.getColor("rating_star")
}
if((ratingWidget.indexHovered >= 0 || ratingWidget.userRating > 0) && isStarFilled)
{
return UM.Theme.getColor("primary")
}
return UM.Theme.getColor("rating_star")
}
}
onClicked:
{
if(ratingWidget.canRate)
{
rated(index + 1) // Notify anyone who cares about this.
}
}
}
}
}
}
}

View file

@ -0,0 +1,36 @@
import QtQuick 2.3
import QtQuick.Controls 1.4
import UM 1.1 as UM
import Cura 1.1 as Cura
Row
{
id: rating
height: UM.Theme.getSize("rating_star").height
visible: model.average_rating > 0 //Has a rating at all.
spacing: UM.Theme.getSize("thick_lining").width
width: starIcon.width + spacing + numRatingsLabel.width
UM.RecolorImage
{
id: starIcon
source: UM.Theme.getIcon("star_filled")
color: model.user_rating == 0 ? UM.Theme.getColor("rating_star") : UM.Theme.getColor("primary")
height: UM.Theme.getSize("rating_star").height
width: UM.Theme.getSize("rating_star").width
sourceSize.height: height
sourceSize.width: width
}
Label
{
id: numRatingsLabel
text: model.average_rating != undefined ? model.average_rating.toFixed(1) + " (" + model.num_ratings + " " + catalog.i18nc("@label", "ratings") + ")": ""
verticalAlignment: Text.AlignVCenter
height: starIcon.height
width: contentWidth
anchors.verticalCenter: starIcon.verticalCenter
color: starIcon.color
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}

View file

@ -14,17 +14,17 @@ Window
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
width: 720 * screenScaleFactor
height: 640 * screenScaleFactor
width: Math.floor(720 * screenScaleFactor)
height: Math.floor(640 * screenScaleFactor)
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
maximumHeight: minimumHeight
color: UM.Theme.getColor("sidebar")
color: UM.Theme.getColor("main_background")
UM.I18nCatalog
{
id: catalog
name:"cura"
name: "cura"
}
Item
{
@ -38,7 +38,7 @@ Window
{
id: mainView
width: parent.width
z: -1
z: parent.z - 1
anchors
{
top: header.bottom
@ -95,6 +95,7 @@ Window
licenseDialog.show();
}
}
ToolboxLicenseDialog
{
id: licenseDialog

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -55,10 +55,11 @@ Item
bottomMargin: UM.Theme.getSize("default_margin").height
}
text: details.name || ""
font: UM.Theme.getFont("large")
font: UM.Theme.getFont("large_bold")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
renderType: Text.NativeRendering
}
Label
{
@ -70,6 +71,7 @@ Item
left: title.left
topMargin: UM.Theme.getSize("default_margin").height
}
renderType: Text.NativeRendering
}
Column
{
@ -86,14 +88,16 @@ Item
Label
{
text: catalog.i18nc("@label", "Website") + ":"
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Email") + ":"
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
}
Column
@ -118,10 +122,11 @@ Item
}
return ""
}
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
Label
@ -134,10 +139,11 @@ Item
}
return ""
}
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
}
Rectangle

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -61,9 +61,15 @@ Item
id: labelStyle
text: control.text
color: control.enabled ? (control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")) : UM.Theme.getColor("text_inactive")
font: UM.Theme.getFont("default_bold")
horizontalAlignment: Text.AlignRight
font: UM.Theme.getFont("medium_bold")
horizontalAlignment: Text.AlignLeft
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
width: control.width
renderType: Text.NativeRendering
}
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -36,12 +36,19 @@ Item
var pg_name = "printingGuidelines"
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
}
property var materialWebsiteUrl:
{
var pg_name = "website"
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
}
anchors.topMargin: UM.Theme.getSize("default_margin").height
height: visible ? childrenRect.height : 0
visible: packageData.type == "material" &&
(packageData.has_configs || technicalDataSheetUrl !== undefined ||
safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined)
safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined ||
materialWebsiteUrl !== undefined)
Item
{
@ -60,6 +67,7 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
}
TableView
@ -83,7 +91,7 @@ Item
model: packageData.supported_configs
headerDelegate: Rectangle
{
color: UM.Theme.getColor("sidebar")
color: UM.Theme.getColor("main_background")
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{
@ -92,6 +100,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
renderType: Text.NativeRendering
}
Rectangle
{
@ -111,6 +120,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
itemDelegate: Item
@ -123,6 +133,7 @@ Item
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
@ -137,6 +148,7 @@ Item
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}
@ -180,7 +192,8 @@ Item
anchors.top: combatibilityItem.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
visible: base.technicalDataSheetUrl !== undefined ||
base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined
base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined ||
base.materialWebsiteUrl !== undefined
height: visible ? contentHeight : 0
text:
{
@ -208,11 +221,22 @@ Item
var pg_name = catalog.i18nc("@action:label", "Printing Guidelines")
result += "<a href='%1'>%2</a>".arg(base.printingGuidelinesUrl).arg(pg_name)
}
if (base.materialWebsiteUrl !== undefined)
{
if (result.length > 0)
{
result += "<br/>"
}
var pg_name = catalog.i18nc("@action:label", "Website")
result += "<a href='%1'>%2</a>".arg(base.materialWebsiteUrl).arg(pg_name)
}
return result
}
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.10
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
@ -66,6 +66,7 @@ UM.Dialog
anchors.right: parent.right
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
renderType: Text.NativeRendering
}
// Buttons

View file

@ -26,10 +26,19 @@ Item
}
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height
Repeater
{
model: toolbox.packagesModel
delegate: ToolboxDetailTile {}
delegate: Loader
{
// FIXME: When using asynchronous loading, on Mac and Windows, the tile may fail to load complete,
// leaving an empty space below the title part. We turn it off for now to make it work on Mac and
// Windows.
// Can be related to this QT bug: https://bugreports.qt.io/browse/QTBUG-50992
asynchronous: false
source: "ToolboxDetailTile.qml"
}
}
}
}

View file

@ -1,11 +1,13 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
import Cura 1.1 as Cura
Item
{
id: page
@ -24,7 +26,7 @@ Item
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
height: UM.Theme.getSize("toolbox_detail_header").height
height: childrenRect.height + 3 * UM.Theme.getSize("default_margin").width
Rectangle
{
id: thumbnail
@ -37,7 +39,7 @@ Item
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
color: white //Always a white background for image (regardless of theme).
color: UM.Theme.getColor("main_background")
Image
{
anchors.fill: parent
@ -55,16 +57,21 @@ Item
top: thumbnail.top
left: thumbnail.right
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
bottomMargin: UM.Theme.getSize("default_margin").height
}
text: details === null ? "" : (details.name || "")
font: UM.Theme.getFont("large")
font: UM.Theme.getFont("large_bold")
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
width: contentWidth
height: contentHeight
renderType: Text.NativeRendering
}
SmallRatingWidget
{
anchors.left: title.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: title.verticalCenter
property var model: details
}
Column
@ -81,27 +88,38 @@ Item
height: childrenRect.height
Label
{
text: catalog.i18nc("@label", "Version") + ":"
font: UM.Theme.getFont("very_small")
text: catalog.i18nc("@label", "Your rating") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Version") + ":"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Last updated") + ":"
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Author") + ":"
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
Label
{
text: catalog.i18nc("@label", "Downloads") + ":"
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering
}
}
Column
@ -116,11 +134,54 @@ Item
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
height: childrenRect.height
RatingWidget
{
id: rating
visible: details.type == "plugin"
packageId: details.id != undefined ? details.id: ""
userRating: details.user_rating != undefined ? details.user_rating: 0
canRate: toolbox.isInstalled(details.id) && Cura.API.account.isLoggedIn
onRated:
{
toolbox.ratePackage(details.id, rating)
// HACK: This is a far from optimal solution, but without major refactoring, this is the best we can
// do. Since a rework of this is scheduled, it shouldn't live that long...
var index = toolbox.pluginsAvailableModel.find("id", details.id)
if(index != -1)
{
if(details.user_rating == 0) // User never rated before.
{
toolbox.pluginsAvailableModel.setProperty(index, "num_ratings", details.num_ratings + 1)
}
toolbox.pluginsAvailableModel.setProperty(index, "user_rating", rating)
// Hack; This is because the current selection is an outdated copy, so we need to re-copy it.
base.selection = toolbox.pluginsAvailableModel.getItem(index)
return
}
index = toolbox.pluginsShowcaseModel.find("id", details.id)
if(index != -1)
{
if(details.user_rating == 0) // User never rated before.
{
toolbox.pluginsShowcaseModel.setProperty(index, "user_rating", rating)
}
toolbox.pluginsShowcaseModel.setProperty(index, "num_ratings", details.num_ratings + 1)
// Hack; This is because the current selection is an outdated copy, so we need to re-copy it.
base.selection = toolbox.pluginsShowcaseModel.getItem(index)
}
}
}
Label
{
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Label
{
@ -133,8 +194,9 @@ Item
var date = new Date(details.last_updated)
return date.toLocaleString(UM.Preferences.getValue("general/language"))
}
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
Label
{
@ -144,34 +206,25 @@ Item
{
return ""
}
if (details.author_email)
{
return "<a href=\"mailto:" + details.author_email+"?Subject=Cura: " + details.name + "\">" + details.author_name + "</a>"
}
else
{
return "<a href=\"" + details.website + "\">" + details.author_name + "</a>"
}
}
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
renderType: Text.NativeRendering
}
Label
{
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
font: UM.Theme.getFont("very_small")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
}
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
}
}
ToolboxDetailList
{

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -31,17 +31,19 @@ Item
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
}
Label
{
anchors.top: packageName.bottom
width: parent.width
text: model.description
maximumLineCount: 6
maximumLineCount: 25
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
}
}

View file

@ -1,40 +1,69 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
import Cura 1.1 as Cura
Column
{
property bool installed: toolbox.isInstalled(model.id)
property bool canUpdate: toolbox.canUpdate(model.id)
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
ToolboxProgressButton
Item
{
id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model
complete: installed
readyAction: function()
width: installButton.width
height: installButton.height
ToolboxProgressButton
{
toolbox.activePackage = model
toolbox.startDownload(model.download_url)
id: installButton
active: toolbox.isDownloading && toolbox.activePackage == model
onReadyAction:
{
toolbox.activePackage = model
toolbox.startDownload(model.download_url)
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible && !installed// Don't show when the update button is visible
}
activeAction: function()
Cura.SecondaryButton
{
toolbox.cancelDownload()
visible: installed
onClicked: toolbox.viewCategory = "installed"
text: catalog.i18nc("@action:button", "Installed")
fixedWidthMode: true
width: installButton.width
height: installButton.height
}
completeAction: function()
}
Label
{
wrapMode: Text.WordWrap
text: catalog.i18nc("@label:The string between <a href=> and </a> is the highlighted link", "<a href='%1'>Log in</a> is required to install or update")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
visible: loginRequired
width: installButton.width
renderType: Text.NativeRendering
MouseArea
{
toolbox.viewCategory = "installed"
anchors.fill: parent
onClicked: Cura.API.account.login()
}
// Don't allow installing while another download is running
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible // Don't show when the update button is visible
}
ToolboxProgressButton
@ -44,24 +73,28 @@ Column
readyLabel: catalog.i18nc("@action:button", "Update")
activeLabel: catalog.i18nc("@action:button", "Updating")
completeLabel: catalog.i18nc("@action:button", "Updated")
readyAction: function()
onReadyAction:
{
toolbox.activePackage = model
toolbox.update(model.id)
}
activeAction: function()
{
toolbox.cancelDownload()
}
onActiveAction: toolbox.cancelDownload()
// Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
enabled: !(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired
opacity: enabled ? 1.0 : 0.5
visible: canUpdate
}
Connections
{
target: toolbox
onInstallChanged: installed = toolbox.isInstalled(model.id)
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
onFilterChanged:
{
installed = toolbox.isInstalled(model.id)
canUpdate = toolbox.canUpdate(model.id)
}
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@ -22,9 +22,10 @@ Column
text: gridArea.heading
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
font: UM.Theme.getFont("large")
renderType: Text.NativeRendering
}
GridLayout
Grid
{
id: grid
width: parent.width - 2 * parent.padding
@ -34,10 +35,12 @@ Column
Repeater
{
model: gridArea.model
delegate: ToolboxDownloadsGridTile
delegate: Loader
{
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height
asynchronous: true
width: Math.round((grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns)
height: UM.Theme.getSize("toolbox_thumbnail_small").height
source: "ToolboxDownloadsGridTile.qml"
}
}
}

View file

@ -1,11 +1,12 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import UM 1.1 as UM
import Cura 1.1 as Cura
Item
{
@ -14,91 +15,13 @@ Item
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
height: childrenRect.height
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Rectangle
{
id: highlight
anchors.fill: parent
opacity: 0.0
color: UM.Theme.getColor("primary")
}
Row
{
width: parent.width
height: childrenRect.height
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
Rectangle
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_small").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height
color: "white"
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Image
{
anchors.centerIn: parent
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../images/logobot.svg"
mipmap: true
}
UM.RecolorImage
{
width: (parent.width * 0.4) | 0
height: (parent.height * 0.4) | 0
anchors
{
bottom: parent.bottom
right: parent.right
}
sourceSize.width: width
sourceSize.height: height
visible: installedPackages != 0
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
source: "../images/installed_check.svg"
}
}
Column
{
width: parent.width - thumbnail.width - parent.spacing
spacing: Math.floor(UM.Theme.getSize("narrow_margin").width)
Label
{
id: name
text: model.name
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
}
Label
{
id: info
text: model.description
maximumLineCount: 2
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("very_small")
}
}
}
MouseArea
{
anchors.fill: parent
hoverEnabled: true
onEntered:
{
thumbnail.border.color = UM.Theme.getColor("primary")
highlight.opacity = 0.1
}
onExited:
{
thumbnail.border.color = UM.Theme.getColor("lining")
highlight.opacity = 0.0
}
onEntered: thumbnail.border.color = UM.Theme.getColor("primary")
onExited: thumbnail.border.color = UM.Theme.getColor("lining")
onClicked:
{
base.selection = model
@ -128,4 +51,83 @@ Item
}
}
}
Rectangle
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_small").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height
color: UM.Theme.getColor("main_background")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Image
{
anchors.centerIn: parent
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../images/logobot.svg"
mipmap: true
}
UM.RecolorImage
{
width: (parent.width * 0.4) | 0
height: (parent.height * 0.4) | 0
anchors
{
bottom: parent.bottom
right: parent.right
}
sourceSize.height: height
visible: installedPackages != 0
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
source: "../images/installed_check.svg"
}
}
Item
{
anchors
{
left: thumbnail.right
leftMargin: Math.floor(UM.Theme.getSize("narrow_margin").width)
right: parent.right
top: parent.top
bottom: parent.bottom
}
Label
{
id: name
text: model.name
width: parent.width
elide: Text.ElideRight
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
}
Label
{
id: info
text: model.description
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
anchors.top: name.bottom
anchors.bottom: rating.top
verticalAlignment: Text.AlignVCenter
maximumLineCount: 2
}
SmallRatingWidget
{
id: rating
anchors
{
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more