Convert doxygen to rst for Toolbox, TrimeshReader

This commit is contained in:
Nino van Hooff 2020-05-15 13:53:10 +02:00
parent 8e347c1034
commit d96359f208
11 changed files with 98 additions and 56 deletions

View File

@ -9,8 +9,12 @@ from PyQt5.QtCore import Qt, pyqtProperty
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
class AuthorsModel(ListModel): class AuthorsModel(ListModel):
"""Model that holds cura packages.
By setting the filter property the instances held by this model can be changed.
"""
def __init__(self, parent = None) -> None: def __init__(self, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -67,9 +71,11 @@ class AuthorsModel(ListModel):
filtered_items.sort(key = lambda k: k["name"]) filtered_items.sort(key = lambda k: k["name"])
self.setItems(filtered_items) self.setItems(filtered_items)
## Set the filter of this model based on a string.
# \param filter_dict \type{Dict} Dictionary to do the filtering by.
def setFilter(self, filter_dict: Dict[str, str]) -> None: def setFilter(self, filter_dict: Dict[str, str]) -> None:
"""Set the filter of this model based on a string.
:param filter_dict: Dictionary to do the filtering by.
"""
if filter_dict != self._filter: if filter_dict != self._filter:
self._filter = filter_dict self._filter = filter_dict
self._update() self._update()

View File

@ -20,9 +20,9 @@ class CloudApiModel:
cloud_api_version=cloud_api_version, cloud_api_version=cloud_api_version,
) )
## https://api.ultimaker.com/cura-packages/v1/user/packages/{package_id}
@classmethod @classmethod
def userPackageUrl(cls, package_id: str) -> str: def userPackageUrl(cls, package_id: str) -> str:
"""https://api.ultimaker.com/cura-packages/v1/user/packages/{package_id}"""
return (CloudApiModel.api_url_user_packages + "/{package_id}").format( return (CloudApiModel.api_url_user_packages + "/{package_id}").format(
package_id=package_id package_id=package_id

View File

@ -8,9 +8,11 @@ from UM.Signal import Signal
from .SubscribedPackagesModel import SubscribedPackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel
## Shows a list of packages to be added or removed. The user can select which packages to (un)install. The user's
# choices are emitted on the `packageMutations` Signal.
class DiscrepanciesPresenter(QObject): class DiscrepanciesPresenter(QObject):
"""Shows a list of packages to be added or removed. The user can select which packages to (un)install. The user's
choices are emitted on the `packageMutations` Signal.
"""
def __init__(self, app: QtApplication) -> None: def __init__(self, app: QtApplication) -> None:
super().__init__(app) super().__init__(app)

View File

@ -16,9 +16,11 @@ from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
from .SubscribedPackagesModel import SubscribedPackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel
## Downloads a set of packages from the Ultimaker Cloud Marketplace
# use download() exactly once: should not be used for multiple sets of downloads since this class contains state
class DownloadPresenter: class DownloadPresenter:
"""Downloads a set of packages from the Ultimaker Cloud Marketplace
use download() exactly once: should not be used for multiple sets of downloads since this class contains state
"""
DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB DISK_WRITE_BUFFER_SIZE = 256 * 1024 # 256 KB

View File

@ -43,10 +43,12 @@ class LicensePresenter(QObject):
self._compatibility_dialog_path = "resources/qml/dialogs/ToolboxLicenseDialog.qml" self._compatibility_dialog_path = "resources/qml/dialogs/ToolboxLicenseDialog.qml"
## Show a license dialog for multiple packages where users can read a license and accept or decline them
# \param plugin_path: Root directory of the Toolbox plugin
# \param packages: Dict[package id, file path]
def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None: def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
"""Show a license dialog for multiple packages where users can read a license and accept or decline them
:param plugin_path: Root directory of the Toolbox plugin
:param packages: Dict[package id, file path]
"""
if self._presented: if self._presented:
Logger.error("{clazz} is single-use. Create a new {clazz} instead", clazz=self.__class__.__name__) Logger.error("{clazz} is single-use. Create a new {clazz} instead", clazz=self.__class__.__name__)
return return

View File

@ -3,9 +3,11 @@ from UM.Message import Message
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
## Presents a dialog telling the user that a restart is required to apply changes
# Since we cannot restart Cura, the app is closed instead when the button is clicked
class RestartApplicationPresenter: class RestartApplicationPresenter:
"""Presents a dialog telling the user that a restart is required to apply changes
Since we cannot restart Cura, the app is closed instead when the button is clicked
"""
def __init__(self, app: CuraApplication) -> None: def __init__(self, app: CuraApplication) -> None:
self._app = app self._app = app
self._i18n_catalog = i18nCatalog("cura") self._i18n_catalog = i18nCatalog("cura")

View File

@ -16,20 +16,23 @@ from .RestartApplicationPresenter import RestartApplicationPresenter
from .SubscribedPackagesModel import SubscribedPackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel
## Orchestrates the synchronizing of packages from the user account to the installed packages
# Example flow:
# - CloudPackageChecker compares a list of packages the user `subscribed` to in their account
# If there are `discrepancies` between the account and locally installed packages, they are emitted
# - DiscrepanciesPresenter shows a list of packages to be added or removed to the user. It emits the `packageMutations`
# the user selected to be performed
# - The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed
# - The DownloadPresenter shows a download progress dialog. It emits A tuple of succeeded and failed downloads
# - The LicensePresenter extracts licenses from the downloaded packages and presents a license for each package to
# be installed. It emits the `licenseAnswers` signal for accept or declines
# - The CloudApiClient removes the declined packages from the account
# - The SyncOrchestrator uses PackageManager to install the downloaded packages and delete temp files.
# - The RestartApplicationPresenter notifies the user that a restart is required for changes to take effect
class SyncOrchestrator(Extension): class SyncOrchestrator(Extension):
"""Orchestrates the synchronizing of packages from the user account to the installed packages
Example flow:
- CloudPackageChecker compares a list of packages the user `subscribed` to in their account
If there are `discrepancies` between the account and locally installed packages, they are emitted
- DiscrepanciesPresenter shows a list of packages to be added or removed to the user. It emits the `packageMutations`
the user selected to be performed
- The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed
- The DownloadPresenter shows a download progress dialog. It emits A tuple of succeeded and failed downloads
- The LicensePresenter extracts licenses from the downloaded packages and presents a license for each package to
be installed. It emits the `licenseAnswers` signal for accept or declines
- The CloudApiClient removes the declined packages from the account
- The SyncOrchestrator uses PackageManager to install the downloaded packages and delete temp files.
- The RestartApplicationPresenter notifies the user that a restart is required for changes to take effect
"""
def __init__(self, app: CuraApplication) -> None: def __init__(self, app: CuraApplication) -> None:
super().__init__() super().__init__()
@ -63,10 +66,12 @@ class SyncOrchestrator(Extension):
self._download_presenter.done.connect(self._onDownloadFinished) self._download_presenter.done.connect(self._onDownloadFinished)
self._download_presenter.download(mutations) self._download_presenter.download(mutations)
## Called when a set of packages have finished downloading
# \param success_items: Dict[package_id, Dict[str, str]]
# \param error_items: List[package_id]
def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None: def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None:
"""Called when a set of packages have finished downloading
:param success_items:: Dict[package_id, Dict[str, str]]
:param error_items:: List[package_id]
"""
if error_items: if error_items:
message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items))) message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items)))
self._showErrorMessage(message) self._showErrorMessage(message)
@ -96,7 +101,8 @@ class SyncOrchestrator(Extension):
if has_changes: if has_changes:
self._restart_presenter.present() self._restart_presenter.present()
## Logs an error and shows it to the user
def _showErrorMessage(self, text: str): def _showErrorMessage(self, text: str):
"""Logs an error and shows it to the user"""
Logger.error(text) Logger.error(text)
Message(text, lifetime=0).show() Message(text, lifetime=0).show()

View File

@ -6,8 +6,9 @@ from PyQt5.QtCore import Qt
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
## Model that holds supported configurations (for material/quality packages).
class ConfigsModel(ListModel): class ConfigsModel(ListModel):
"""Model that holds supported configurations (for material/quality packages)."""
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)

View File

@ -12,8 +12,12 @@ from UM.Qt.ListModel import ListModel
from .ConfigsModel import ConfigsModel from .ConfigsModel import ConfigsModel
## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed.
class PackagesModel(ListModel): class PackagesModel(ListModel):
"""Model that holds Cura packages.
By setting the filter property the instances held by this model can be changed.
"""
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
@ -131,9 +135,11 @@ class PackagesModel(ListModel):
filtered_items.sort(key = lambda k: k["name"]) filtered_items.sort(key = lambda k: k["name"])
self.setItems(filtered_items) self.setItems(filtered_items)
## Set the filter of this model based on a string.
# \param filter_dict \type{Dict} Dictionary to do the filtering by.
def setFilter(self, filter_dict: Dict[str, str]) -> None: def setFilter(self, filter_dict: Dict[str, str]) -> None:
"""Set the filter of this model based on a string.
:param filter_dict: Dictionary to do the filtering by.
"""
if filter_dict != self._filter: if filter_dict != self._filter:
self._filter = filter_dict self._filter = filter_dict
self._update() self._update()

View File

@ -37,10 +37,10 @@ try:
except ImportError: except ImportError:
CuraMarketplaceRoot = DEFAULT_MARKETPLACE_ROOT CuraMarketplaceRoot = DEFAULT_MARKETPLACE_ROOT
# todo Remove license and download dialog, use SyncOrchestrator instead
## Provides a marketplace for users to download plugins an materials
class Toolbox(QObject, Extension): class Toolbox(QObject, Extension):
"""Provides a marketplace for users to download plugins an materials"""
def __init__(self, application: CuraApplication) -> None: def __init__(self, application: CuraApplication) -> None:
super().__init__() super().__init__()
@ -135,8 +135,9 @@ class Toolbox(QObject, Extension):
closeLicenseDialog = pyqtSignal() closeLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal() uninstallVariablesChanged = pyqtSignal()
## Go back to the start state (welcome screen or loading if no login required)
def _restart(self): def _restart(self):
"""Go back to the start state (welcome screen or loading if no login required)"""
# For an Essentials build, login is mandatory # For an Essentials build, login is mandatory
if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion: if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion:
self.setViewPage("welcome") self.setViewPage("welcome")
@ -311,10 +312,13 @@ class Toolbox(QObject, Extension):
self.restartRequiredChanged.emit() self.restartRequiredChanged.emit()
return package_id return package_id
## Check package usage and uninstall
# If the package is in use, you'll get a confirmation dialog to set everything to default
@pyqtSlot(str) @pyqtSlot(str)
def checkPackageUsageAndUninstall(self, package_id: str) -> None: def checkPackageUsageAndUninstall(self, package_id: str) -> None:
"""Check package usage and uninstall
If the package is in use, you'll get a confirmation dialog to set everything to default
"""
package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id) package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id)
if package_used_materials or package_used_qualities: if package_used_materials or package_used_qualities:
# Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall # Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall
@ -352,10 +356,13 @@ class Toolbox(QObject, Extension):
if self._confirm_reset_dialog is not None: if self._confirm_reset_dialog is not None:
self._confirm_reset_dialog.close() self._confirm_reset_dialog.close()
## Uses "uninstall variables" to reset qualities and materials, then uninstall
# It's used as an action on Confirm reset on Uninstall
@pyqtSlot() @pyqtSlot()
def resetMaterialsQualitiesAndUninstall(self) -> None: def resetMaterialsQualitiesAndUninstall(self) -> None:
"""Uses "uninstall variables" to reset qualities and materials, then uninstall
It's used as an action on Confirm reset on Uninstall
"""
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
machine_manager = application.getMachineManager() machine_manager = application.getMachineManager()
container_tree = ContainerTree.getInstance() container_tree = ContainerTree.getInstance()
@ -418,8 +425,9 @@ class Toolbox(QObject, Extension):
self._restart_required = True self._restart_required = True
self.restartRequiredChanged.emit() self.restartRequiredChanged.emit()
## Actual update packages that are in self._to_update
def _update(self) -> None: def _update(self) -> None:
"""Actual update packages that are in self._to_update"""
if self._to_update: if self._to_update:
plugin_id = self._to_update.pop(0) plugin_id = self._to_update.pop(0)
remote_package = self.getRemotePackage(plugin_id) remote_package = self.getRemotePackage(plugin_id)
@ -433,9 +441,10 @@ class Toolbox(QObject, Extension):
if self._to_update: if self._to_update:
self._application.callLater(self._update) self._application.callLater(self._update)
## Update a plugin by plugin_id
@pyqtSlot(str) @pyqtSlot(str)
def update(self, plugin_id: str) -> None: def update(self, plugin_id: str) -> None:
"""Update a plugin by plugin_id"""
self._to_update.append(plugin_id) self._to_update.append(plugin_id)
self._application.callLater(self._update) self._application.callLater(self._update)
@ -714,9 +723,10 @@ class Toolbox(QObject, Extension):
self._active_package = package self._active_package = package
self.activePackageChanged.emit() self.activePackageChanged.emit()
## The active package is the package that is currently being downloaded
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged) @pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> Optional[QObject]: def activePackage(self) -> Optional[QObject]:
"""The active package is the package that is currently being downloaded"""
return self._active_package return self._active_package
def setViewCategory(self, category: str = "plugin") -> None: def setViewCategory(self, category: str = "plugin") -> None:
@ -724,7 +734,7 @@ class Toolbox(QObject, Extension):
self._view_category = category self._view_category = category
self.viewChanged.emit() self.viewChanged.emit()
## Function explicitly defined so that it can be called through the callExtensionsMethod # Function explicitly defined so that it can be called through the callExtensionsMethod
# which cannot receive arguments. # which cannot receive arguments.
def setViewCategoryToMaterials(self) -> None: def setViewCategoryToMaterials(self) -> None:
self.setViewCategory("material") self.setViewCategory("material")

View File

@ -22,8 +22,10 @@ from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator # Adde
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
## Class that leverages Trimesh to import files.
class TrimeshReader(MeshReader): class TrimeshReader(MeshReader):
"""Class that leverages Trimesh to import files."""
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@ -79,11 +81,13 @@ class TrimeshReader(MeshReader):
) )
) )
## Reads a file using Trimesh.
# \param file_name The file path. This is assumed to be one of the file
# types that Trimesh can read. It will not be checked again.
# \return A scene node that contains the file's contents.
def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]: def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
"""Reads a file using Trimesh.
:param file_name: The file path. This is assumed to be one of the file
types that Trimesh can read. It will not be checked again.
:return: A scene node that contains the file's contents.
"""
# CURA-6739 # CURA-6739
# GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will # GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will
# try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load() # try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load()
@ -130,13 +134,14 @@ class TrimeshReader(MeshReader):
node.setParent(group_node) node.setParent(group_node)
return group_node return group_node
## Converts a Trimesh to Uranium's MeshData.
# \param tri_node A Trimesh containing the contents of a file that was
# just read.
# \param file_name The full original filename used to watch for changes
# \return Mesh data from the Trimesh in a way that Uranium can understand
# it.
def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData: def _toMeshData(self, tri_node: trimesh.base.Trimesh, file_name: str = "") -> MeshData:
"""Converts a Trimesh to Uranium's MeshData.
:param tri_node: A Trimesh containing the contents of a file that was just read.
:param file_name: The full original filename used to watch for changes
:return: Mesh data from the Trimesh in a way that Uranium can understand it.
"""
tri_faces = tri_node.faces tri_faces = tri_node.faces
tri_vertices = tri_node.vertices tri_vertices = tri_node.vertices