Browse Source

Merge pull request #1 from Ultimaker/master

up
SzymonGamza 5 years ago
parent
commit
56b244b0aa

+ 7 - 0
cmake/CuraTests.cmake

@@ -56,6 +56,13 @@ function(cura_add_test)
     endif()
 endfunction()
 
+#Add test for import statements which are not compatible with all builds
+add_test(
+    NAME "invalid-imports"
+    COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
+    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
+
 cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
 
 file(GLOB_RECURSE _plugins plugins/*/__init__.py)

+ 6 - 2
cura_app.py

@@ -29,9 +29,13 @@ parser.add_argument("--debug",
 known_args = vars(parser.parse_known_args()[0])
 
 if with_sentry_sdk:
-    sentry_env = "production"
-    if ApplicationMetadata.CuraVersion == "master":
+    sentry_env = "unknown"  # Start off with a "IDK"
+    if hasattr(sys, "frozen"):
+        sentry_env = "production"  # A frozen build is a "real" distribution.
+    elif ApplicationMetadata.CuraVersion == "master":
         sentry_env = "development"
+    elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion:
+        sentry_env = "beta"
     try:
         if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
             sentry_env = "nightly"

+ 2 - 0
docker/build.sh

@@ -13,6 +13,8 @@ export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
 
 cd "${PROJECT_DIR}"
 
+
+
 #
 # Clone Uranium and set PYTHONPATH first
 #

+ 21 - 2
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -4,7 +4,8 @@
 from configparser import ConfigParser
 import zipfile
 import os
-from typing import cast, Dict, List, Optional, Tuple
+import json
+from typing import cast, Dict, List, Optional, Tuple, Any
 
 import xml.etree.ElementTree as ET
 
@@ -732,7 +733,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         base_file_name = os.path.basename(file_name)
         self.setWorkspaceName(base_file_name)
-        return nodes
+
+        return nodes, self._loadMetadata(file_name)
+
+    @staticmethod
+    def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]:
+        archive = zipfile.ZipFile(file_name, "r")
+
+        metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")]
+
+        result = dict()
+
+        for metadata_file in metadata_files:
+            try:
+                plugin_id = metadata_file.split("/")[0]
+                result[plugin_id] = json.loads(archive.open("%s/plugin_metadata.json" % plugin_id).read().decode("utf-8"))
+            except Exception:
+                Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file)
+
+        return result
 
     def _processQualityChanges(self, global_stack):
         if self._machine_info.quality_changes_info is None:

+ 14 - 0
plugins/3MFWriter/ThreeMFWorkspaceWriter.py

@@ -73,11 +73,25 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
         version_config_parser.write(version_file_string)
         archive.writestr(version_file, version_file_string.getvalue())
 
+        self._writePluginMetadataToArchive(archive)
+
         # Close the archive & reset states.
         archive.close()
         mesh_writer.setStoreArchive(False)
         return True
 
+    @staticmethod
+    def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None:
+        file_name_template = "%s/plugin_metadata.json"
+
+        for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items():
+            file_name = file_name_template % plugin_id
+            file_in_archive = zipfile.ZipInfo(file_name)
+            # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
+            file_in_archive.compress_type = zipfile.ZIP_DEFLATED
+            import json
+            archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4, skipkeys = True))
+
     ##  Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
     #   \param container That follows the \type{ContainerInterface} to archive.
     #   \param archive The archive to write to.

+ 2 - 1
plugins/3MFWriter/ThreeMFWriter.py

@@ -1,5 +1,6 @@
 # Copyright (c) 2015 Ultimaker B.V.
 # Uranium is released under the terms of the LGPLv3 or higher.
+from typing import Optional
 
 from UM.Mesh.MeshWriter import MeshWriter
 from UM.Math.Vector import Vector
@@ -40,7 +41,7 @@ class ThreeMFWriter(MeshWriter):
         }
 
         self._unit_matrix_string = self._convertMatrixToString(Matrix())
-        self._archive = None
+        self._archive = None  # type: Optional[zipfile.ZipFile]
         self._store_archive = False
 
     def _convertMatrixToString(self, matrix):

+ 45 - 12
plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml

@@ -5,6 +5,7 @@ import QtQuick 2.10
 import QtQuick.Dialogs 1.1
 import QtQuick.Window 2.2
 import QtQuick.Controls 1.4
+import QtQuick.Layouts 1.3
 import QtQuick.Controls.Styles 1.4
 
 // TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
@@ -20,31 +21,63 @@ UM.Dialog
     minimumHeight: UM.Theme.getSize("license_window_minimum").height
     width: minimumWidth
     height: minimumHeight
+    backgroundColor: UM.Theme.getColor("main_background")
+    margin: screenScaleFactor * 10
 
-    Item
+    ColumnLayout
     {
         anchors.fill: parent
+        spacing: UM.Theme.getSize("thick_margin").height
 
         UM.I18nCatalog{id: catalog; name: "cura"}
 
-
         Label
         {
             id: licenseHeader
-            anchors.top: parent.top
-            anchors.left: parent.left
-            anchors.right: parent.right
-            text: licenseModel.headerText
+            Layout.fillWidth: true
+            text: catalog.i18nc("@label", "You need to accept the license to install the package")
             wrapMode: Text.Wrap
             renderType: Text.NativeRendering
         }
+
+        Row {
+            id: packageRow
+
+            anchors.left: parent.left
+            anchors.right: parent.right
+            height: childrenRect.height
+            spacing: UM.Theme.getSize("default_margin").width
+            leftPadding: UM.Theme.getSize("narrow_margin").width
+
+            Image
+            {
+                id: icon
+                width: 30 * screenScaleFactor
+                height: width
+                fillMode: Image.PreserveAspectFit
+                source: licenseModel.iconUrl || "../../images/logobot.svg"
+                mipmap: true
+            }
+
+            Label
+            {
+                id: packageName
+                text: licenseModel.packageName
+                font.bold: true
+                anchors.verticalCenter: icon.verticalCenter
+                height: contentHeight
+                wrapMode: Text.Wrap
+                renderType: Text.NativeRendering
+            }
+
+
+        }
+
         TextArea
         {
             id: licenseText
-            anchors.top: licenseHeader.bottom
-            anchors.bottom: parent.bottom
-            anchors.left: parent.left
-            anchors.right: parent.right
+            Layout.fillWidth: true
+            Layout.fillHeight: true
             anchors.topMargin: UM.Theme.getSize("default_margin").height
             readOnly: true
             text: licenseModel.licenseText
@@ -57,7 +90,7 @@ UM.Dialog
             leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
             rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
 
-            text: catalog.i18nc("@button", "Agree")
+            text: licenseModel.acceptButtonText
             onClicked: { handler.onLicenseAccepted() }
         }
     ]
@@ -67,7 +100,7 @@ UM.Dialog
         Cura.SecondaryButton
         {
             id: declineButton
-            text: catalog.i18nc("@button", "Decline and remove from account")
+            text: licenseModel.declineButtonText
             onClicked: { handler.onLicenseDeclined() }
         }
     ]

+ 10 - 2
plugins/Toolbox/src/CloudSync/DownloadPresenter.py

@@ -62,7 +62,8 @@ class DownloadPresenter:
                 "received": 0,
                 "total": 1,  # make sure this is not considered done yet. Also divByZero-safe
                 "file_written": None,
-                "request_data": request_data
+                "request_data": request_data,
+                "package_model": item
             }
 
         self._started = True
@@ -128,7 +129,14 @@ class DownloadPresenter:
             if not item["file_written"]:
                 return False
 
-        success_items = {package_id : value["file_written"] for package_id, value in self._progress.items()}
+        success_items = {
+            package_id:
+                {
+                    "package_path": value["file_written"],
+                    "icon_url": value["package_model"]["icon_url"]
+                }
+            for package_id, value in self._progress.items()
+        }
         error_items = [package_id for package_id in self._error]
 
         self._progress_message.hide()

+ 29 - 8
plugins/Toolbox/src/CloudSync/LicenseModel.py

@@ -6,31 +6,52 @@ catalog = i18nCatalog("cura")
 
 # Model for the ToolboxLicenseDialog
 class LicenseModel(QObject):
+    DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline")
+    ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree")
+
     dialogTitleChanged = pyqtSignal()
-    headerChanged = pyqtSignal()
+    packageNameChanged = pyqtSignal()
     licenseTextChanged = pyqtSignal()
+    iconChanged = pyqtSignal()
 
-    def __init__(self) -> None:
+    def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None:
         super().__init__()
 
         self._current_page_idx = 0
         self._page_count = 1
         self._dialogTitle = ""
-        self._header_text = ""
         self._license_text = ""
         self._package_name = ""
+        self._icon_url = ""
+        self._decline_button_text = decline_button_text
+
+    @pyqtProperty(str, constant = True)
+    def acceptButtonText(self):
+        return self.ACCEPT_BUTTON_TEXT
+
+    @pyqtProperty(str, constant = True)
+    def declineButtonText(self):
+        return self._decline_button_text
 
     @pyqtProperty(str, notify=dialogTitleChanged)
     def dialogTitle(self) -> str:
         return self._dialogTitle
 
-    @pyqtProperty(str, notify=headerChanged)
-    def headerText(self) -> str:
-        return self._header_text
+    @pyqtProperty(str, notify=packageNameChanged)
+    def packageName(self) -> str:
+        return self._package_name
 
     def setPackageName(self, name: str) -> None:
-        self._header_text = name + ": " + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
-        self.headerChanged.emit()
+        self._package_name = name
+        self.packageNameChanged.emit()
+
+    @pyqtProperty(str, notify=iconChanged)
+    def iconUrl(self) -> str:
+        return self._icon_url
+
+    def setIconUrl(self, url: str):
+        self._icon_url = url
+        self.iconChanged.emit()
 
     @pyqtProperty(str, notify=licenseTextChanged)
     def licenseText(self) -> str:

+ 12 - 7
plugins/Toolbox/src/CloudSync/LicensePresenter.py

@@ -17,6 +17,7 @@ class LicensePresenter(QObject):
 
     def __init__(self, app: CuraApplication) -> None:
         super().__init__()
+        self._catalog = i18nCatalog("cura")
         self._dialog = None  # type: Optional[QObject]
         self._package_manager = app.getPackageManager()  # type: PackageManager
         # Emits List[Dict[str, [Any]] containing for example
@@ -25,7 +26,8 @@ class LicensePresenter(QObject):
 
         self._current_package_idx = 0
         self._package_models = []  # type: List[Dict]
-        self._license_model = LicenseModel()  # type: LicenseModel
+        decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account")
+        self._license_model = LicenseModel(decline_button_text=decline_button_text)  # type: LicenseModel
 
         self._app = app
 
@@ -34,7 +36,7 @@ class LicensePresenter(QObject):
     ## 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, str]) -> None:
+    def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
         path = os.path.join(plugin_path, self._compatibility_dialog_path)
 
         self._initState(packages)
@@ -42,7 +44,7 @@ class LicensePresenter(QObject):
         if self._dialog is None:
 
             context_properties = {
-                "catalog": i18nCatalog("cura"),
+                "catalog": self._catalog,
                 "licenseModel": self._license_model,
                 "handler": self
             }
@@ -60,18 +62,20 @@ class LicensePresenter(QObject):
         self._package_models[self._current_package_idx]["accepted"] = False
         self._checkNextPage()
 
-    def _initState(self, packages: Dict[str, str]) -> None:
+    def _initState(self, packages: Dict[str, Dict[str, str]]) -> None:
         self._package_models = [
                 {
                     "package_id" : package_id,
-                    "package_path" : package_path,
+                    "package_path" : item["package_path"],
+                    "icon_url" : item["icon_url"],
                     "accepted" : None  #: None: no answer yet
                 }
-                for package_id, package_path in packages.items()
+                for package_id, item in packages.items()
         ]
 
     def _presentCurrentPackage(self) -> None:
         package_model = self._package_models[self._current_package_idx]
+        package_info = self._package_manager.getPackageInfo(package_model["package_path"])
         license_content = self._package_manager.getPackageLicense(package_model["package_path"])
         if license_content is None:
             # Implicitly accept when there is no license
@@ -79,7 +83,8 @@ class LicensePresenter(QObject):
             return
 
         self._license_model.setCurrentPageIdx(self._current_package_idx)
-        self._license_model.setPackageName(package_model["package_id"])
+        self._license_model.setPackageName(package_info["display_name"])
+        self._license_model.setIconUrl(package_model["icon_url"])
         self._license_model.setLicenseText(license_content)
         if self._dialog:
             self._dialog.open()  # Does nothing if already open

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