This commit is contained in:
ChrisTerBeke 2018-05-02 12:28:57 +02:00
commit e6f37975b3
511 changed files with 3654 additions and 1870 deletions

View file

@ -187,7 +187,7 @@ class BuildVolume(SceneNode):
self._grid_shader.setUniformValue("u_gridColor1", Color(*theme.getColor("buildplate_grid_minor").getRgb()))
renderer.queueNode(self, mode = RenderBatch.RenderMode.Lines)
renderer.queueNode(self, mesh = self._origin_mesh)
renderer.queueNode(self, mesh = self._origin_mesh, backface_cull = True)
renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True)
if self._disallowed_area_mesh:
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)

View file

@ -100,7 +100,6 @@ from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
import sys
import os.path
import numpy
import copy
import os
@ -133,20 +132,21 @@ class CuraApplication(QtApplication):
QmlFiles = Resources.UserType + 1
Firmware = Resources.UserType + 2
QualityInstanceContainer = Resources.UserType + 3
MaterialInstanceContainer = Resources.UserType + 4
VariantInstanceContainer = Resources.UserType + 5
UserInstanceContainer = Resources.UserType + 6
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9
SettingVisibilityPreset = Resources.UserType + 10
QualityChangesInstanceContainer = Resources.UserType + 4
MaterialInstanceContainer = Resources.UserType + 5
VariantInstanceContainer = Resources.UserType + 6
UserInstanceContainer = Resources.UserType + 7
MachineStack = Resources.UserType + 8
ExtruderStack = Resources.UserType + 9
DefinitionChangesContainer = Resources.UserType + 10
SettingVisibilityPreset = Resources.UserType + 11
Q_ENUMS(ResourceTypes)
def __init__(self, **kwargs):
self._boot_loading_time = time.time()
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name)
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
@ -182,6 +182,7 @@ class CuraApplication(QtApplication):
## Add the 4 types of profiles to storage.
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
Resources.addStorageType(self.ResourceTypes.MaterialInstanceContainer, "materials")
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
@ -191,7 +192,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
@ -205,7 +206,7 @@ class CuraApplication(QtApplication):
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
{
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"),
("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
@ -241,6 +242,13 @@ class CuraApplication(QtApplication):
tray_icon_name = "cura-icon-32.png",
**kwargs)
# Initialize the package manager to remove and install scheduled packages.
from cura.CuraPackageManager import CuraPackageManager
self._cura_package_manager = CuraPackageManager(self)
self._cura_package_manager.initialize()
self.initialize()
# FOR TESTING ONLY
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
assert not "This crash is triggered by the trigger_early_crash command line argument."
@ -252,21 +260,33 @@ class CuraApplication(QtApplication):
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
self.setRequiredPlugins([
# Misc.:
"ConsoleLogger",
"CuraEngineBackend",
"UserAgreement",
"SolidView",
"SimulationView",
"STLReader",
"SelectionTool",
"CameraTool",
"GCodeWriter",
"LocalFileOutputDevice",
"TranslateTool",
"FileLogger",
"XmlMaterialProfile",
"PluginBrowser",
"Toolbox",
"PrepareStage",
"MonitorStage"
"MonitorStage",
"LocalFileOutputDevice",
# Views:
"SimpleView",
"SimulationView",
"SolidView",
# Readers & Writers:
"GCodeWriter",
"STLReader",
# Tools:
"CameraTool",
"MirrorTool",
"RotateTool",
"ScaleTool",
"SelectionTool",
"TranslateTool"
])
self._physics = None
self._volume = None
@ -388,8 +408,6 @@ class CuraApplication(QtApplication):
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._onGlobalContainerChanged()
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
self._quality_profile_drop_down_menu_model = None
@ -397,7 +415,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -526,7 +543,9 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True
@classmethod
def addCommandLineOptions(self, parser, parsed_command_line = {}):
def addCommandLineOptions(cls, parser, parsed_command_line = None):
if parsed_command_line is None:
parsed_command_line = {}
super().addCommandLineOptions(parser, parsed_command_line = parsed_command_line)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--single-instance", action="store_true", default=False)
@ -583,7 +602,10 @@ class CuraApplication(QtApplication):
# This should be called directly before creating an instance of CuraApplication.
# \returns \type{bool} True if the whole Cura app should continue running.
@classmethod
def preStartUp(cls, parser = None, parsed_command_line = {}):
def preStartUp(cls, parser = None, parsed_command_line = None):
if parsed_command_line is None:
parsed_command_line = {}
# Peek the arguments and look for the 'single-instance' flag.
if not parser:
parser = argparse.ArgumentParser(prog = "cura", add_help = False) # pylint: disable=bad-whitespace
@ -700,7 +722,6 @@ class CuraApplication(QtApplication):
self._post_start_timer.timeout.connect(self._onPostStart)
self._post_start_timer.start()
Logger.log("d", "Booting Cura took %s seconds", time.time() - self._boot_loading_time)
self.exec_()
def _onPostStart(self):
@ -789,6 +810,10 @@ class CuraApplication(QtApplication):
self._extruder_manager = ExtruderManager.createExtruderManager()
return self._extruder_manager
@pyqtSlot(result = QObject)
def getCuraPackageManager(self, *args):
return self._cura_package_manager
def getVariantManager(self, *args):
return self._variant_manager

360
cura/CuraPackageManager.py Normal file
View file

@ -0,0 +1,360 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
import json
import os
import shutil
import zipfile
import tempfile
from PyQt5.QtCore import pyqtSlot, QObject, pyqtSignal
from UM.Application import Application
from UM.Logger import Logger
from UM.Resources import Resources
from UM.Version import Version
class CuraPackageManager(QObject):
# The prefix that's added to all files for an installed package to avoid naming conflicts with user created
# files.
PREFIX_PLACE_HOLDER = "-CP;"
def __init__(self, parent = None):
super().__init__(parent)
self._application = parent
self._container_registry = self._application.getContainerRegistry()
self._plugin_registry = self._application.getPluginRegistry()
# JSON file that keeps track of all installed packages.
self._package_management_file_path = os.path.join(os.path.abspath(Resources.getDataStoragePath()),
"packages.json")
self._installed_package_dict = {} # a dict of all installed packages
self._to_remove_package_set = set() # a set of packages that need to be removed at the next start
self._to_install_package_dict = {} # a dict of packages that need to be installed at the next start
installedPackagesChanged = pyqtSignal() # Emitted whenever the installed packages collection have been changed.
def initialize(self):
self._loadManagementData()
self._removeAllScheduledPackages()
self._installAllScheduledPackages()
# (for initialize) Loads the package management file if exists
def _loadManagementData(self) -> None:
if not os.path.exists(self._package_management_file_path):
Logger.log("i", "Package management file %s doesn't exist, do nothing", self._package_management_file_path)
return
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
container_registry = self._application.getContainerRegistry()
with container_registry.lockFile():
with open(self._package_management_file_path, "r", encoding = "utf-8") as f:
management_dict = json.load(f, encoding = "utf-8")
self._installed_package_dict = management_dict.get("installed", {})
self._to_remove_package_set = set(management_dict.get("to_remove", []))
self._to_install_package_dict = management_dict.get("to_install", {})
Logger.log("i", "Package management file %s is loaded", self._package_management_file_path)
def _saveManagementData(self) -> None:
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
container_registry = self._application.getContainerRegistry()
with container_registry.lockFile():
with open(self._package_management_file_path, "w", encoding = "utf-8") as f:
data_dict = {"installed": self._installed_package_dict,
"to_remove": list(self._to_remove_package_set),
"to_install": self._to_install_package_dict}
data_dict["to_remove"] = list(data_dict["to_remove"])
json.dump(data_dict, f)
Logger.log("i", "Package management file %s is saved", self._package_management_file_path)
# (for initialize) Removes all packages that have been scheduled to be removed.
def _removeAllScheduledPackages(self) -> None:
for package_id in self._to_remove_package_set:
self._purgePackage(package_id)
self._to_remove_package_set.clear()
self._saveManagementData()
# (for initialize) Installs all packages that have been scheduled to be installed.
def _installAllScheduledPackages(self) -> None:
for package_id, installation_package_data in self._to_install_package_dict.items():
self._installPackage(installation_package_data)
self._to_install_package_dict.clear()
self._saveManagementData()
# Checks the given package is installed. If so, return a dictionary that contains the package's information.
def getInstalledPackageInfo(self, package_id: str) -> Optional[dict]:
if package_id in self._to_remove_package_set:
return None
if package_id in self._to_install_package_dict:
package_info = self._to_install_package_dict[package_id]["package_info"]
package_info["is_bundled"] = False
return package_info
if package_id in self._installed_package_dict:
package_info = self._installed_package_dict.get(package_id)
package_info["is_bundled"] = False
return package_info
for section, packages in self.getAllInstalledPackagesInfo().items():
for package in packages:
if package["package_id"] == package_id:
package_info = package
return package_info
return None
def getAllInstalledPackagesInfo(self) -> dict:
installed_package_id_set = set(self._installed_package_dict.keys()) | set(self._to_install_package_dict.keys())
installed_package_id_set = installed_package_id_set.difference(self._to_remove_package_set)
managed_package_id_set = set(installed_package_id_set) | self._to_remove_package_set
# TODO: For absolutely no reason, this function seems to run in a loop
# even though no loop is ever called with it.
# map of <package_type> -> <package_id> -> <package_info>
installed_packages_dict = {}
for package_id in installed_package_id_set:
if package_id in Application.getInstance().getRequiredPlugins():
continue
if package_id in self._to_install_package_dict:
package_info = self._to_install_package_dict[package_id]["package_info"]
else:
package_info = self._installed_package_dict[package_id]
package_info["is_bundled"] = False
package_type = package_info["package_type"]
if package_type not in installed_packages_dict:
installed_packages_dict[package_type] = []
installed_packages_dict[package_type].append( package_info )
# We also need to get information from the plugin registry such as if a plugin is active
package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id)
# Also get all bundled plugins
all_metadata = self._plugin_registry.getAllMetaData()
for item in all_metadata:
plugin_package_info = self.__convertPluginMetadataToPackageMetadata(item)
# Only gather the bundled plugins here.
package_id = plugin_package_info["package_id"]
if package_id in managed_package_id_set:
continue
if package_id in Application.getInstance().getRequiredPlugins():
continue
plugin_package_info["is_bundled"] = True if plugin_package_info["author"]["display_name"] == "Ultimaker B.V." else False
plugin_package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id)
package_type = "plugin"
if package_type not in installed_packages_dict:
installed_packages_dict[package_type] = []
installed_packages_dict[package_type].append( plugin_package_info )
return installed_packages_dict
def __convertPluginMetadataToPackageMetadata(self, plugin_metadata: dict) -> dict:
package_metadata = {
"package_id": plugin_metadata["id"],
"package_type": "plugin",
"display_name": plugin_metadata["plugin"]["name"],
"description": plugin_metadata["plugin"].get("description"),
"package_version": plugin_metadata["plugin"]["version"],
"cura_version": int(plugin_metadata["plugin"]["api"]),
"website": "",
"author_id": plugin_metadata["plugin"].get("author", "UnknownID"),
"author": {
"author_id": plugin_metadata["plugin"].get("author", "UnknownID"),
"display_name": plugin_metadata["plugin"].get("author", ""),
"email": "",
"website": "",
},
"tags": ["plugin"],
}
return package_metadata
# Checks if the given package is installed.
def isPackageInstalled(self, package_id: str) -> bool:
return self.getInstalledPackageInfo(package_id) is not None
# Schedules the given package file to be installed upon the next start.
@pyqtSlot(str)
def installPackage(self, filename: str) -> None:
# Get package information
package_info = self.getPackageInfo(filename)
package_id = package_info["package_id"]
has_changes = False
# Check the delayed installation and removal lists first
if package_id in self._to_remove_package_set:
self._to_remove_package_set.remove(package_id)
has_changes = True
# Check if it is installed
installed_package_info = self.getInstalledPackageInfo(package_info["package_id"])
to_install_package = installed_package_info is None # Install if the package has not been installed
if installed_package_info is not None:
# Compare versions and only schedule the installation if the given package is newer
new_version = package_info["package_version"]
installed_version = installed_package_info["package_version"]
if Version(new_version) > Version(installed_version):
Logger.log("i", "Package [%s] version [%s] is newer than the installed version [%s], update it.",
package_id, new_version, installed_version)
to_install_package = True
if to_install_package:
Logger.log("i", "Package [%s] version [%s] is scheduled to be installed.",
package_id, package_info["package_version"])
# Copy the file to cache dir so we don't need to rely on the original file to be present
package_cache_dir = os.path.join(os.path.abspath(Resources.getCacheStoragePath()), "cura_packages")
if not os.path.exists(package_cache_dir):
os.makedirs(package_cache_dir, exist_ok=True)
target_file_path = os.path.join(package_cache_dir, package_id + ".curapackage")
shutil.copy2(filename, target_file_path)
self._to_install_package_dict[package_id] = {"package_info": package_info,
"filename": target_file_path}
has_changes = True
self._saveManagementData()
if has_changes:
self.installedPackagesChanged.emit()
# Schedules the given package to be removed upon the next start.
@pyqtSlot(str)
def removePackage(self, package_id: str) -> None:
# Check the delayed installation and removal lists first
if not self.isPackageInstalled(package_id):
Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id)
return
# Remove from the delayed installation list if present
if package_id in self._to_install_package_dict:
del self._to_install_package_dict[package_id]
# Schedule for a delayed removal:
self._to_remove_package_set.add(package_id)
self._saveManagementData()
self.installedPackagesChanged.emit()
# Removes everything associated with the given package ID.
def _purgePackage(self, package_id: str) -> None:
# Iterate through all directories in the data storage directory and look for sub-directories that belong to
# the package we need to remove, that is the sub-dirs with the package_id as names, and remove all those dirs.
data_storage_dir = os.path.abspath(Resources.getDataStoragePath())
for root, dir_names, _ in os.walk(data_storage_dir):
for dir_name in dir_names:
package_dir = os.path.join(root, dir_name, package_id)
if os.path.exists(package_dir):
Logger.log("i", "Removing '%s' for package [%s]", package_dir, package_id)
shutil.rmtree(package_dir)
break
# Installs all files associated with the given package.
def _installPackage(self, installation_package_data: dict):
package_info = installation_package_data["package_info"]
filename = installation_package_data["filename"]
package_id = package_info["package_id"]
if not os.path.exists(filename):
Logger.log("w", "Package [%s] file '%s' is missing, cannot install this package", package_id, filename)
return
Logger.log("i", "Installing package [%s] from file [%s]", package_id, filename)
# If it's installed, remove it first and then install
if package_id in self._installed_package_dict:
self._purgePackage(package_id)
# Install the package
archive = zipfile.ZipFile(filename, "r")
temp_dir = tempfile.TemporaryDirectory()
archive.extractall(temp_dir.name)
from cura.CuraApplication import CuraApplication
installation_dirs_dict = {
"materials": Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer),
"quality": Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer),
"plugins": os.path.abspath(Resources.getStoragePath(Resources.Plugins)),
}
for sub_dir_name, installation_root_dir in installation_dirs_dict.items():
src_dir_path = os.path.join(temp_dir.name, "files", sub_dir_name)
dst_dir_path = os.path.join(installation_root_dir, package_id)
if not os.path.exists(src_dir_path):
continue
# Need to rename the container files so they don't get ID conflicts
to_rename_files = sub_dir_name not in ("plugins",)
self.__installPackageFiles(package_id, src_dir_path, dst_dir_path, need_to_rename_files= to_rename_files)
archive.close()
# Remove the file
os.remove(filename)
def __installPackageFiles(self, package_id: str, src_dir: str, dst_dir: str, need_to_rename_files: bool = True) -> None:
shutil.move(src_dir, dst_dir)
# Rename files if needed
if not need_to_rename_files:
return
for root, _, file_names in os.walk(dst_dir):
for filename in file_names:
new_filename = self.PREFIX_PLACE_HOLDER + package_id + "-" + filename
old_file_path = os.path.join(root, filename)
new_file_path = os.path.join(root, new_filename)
os.rename(old_file_path, new_file_path)
# Gets package information from the given file.
def getPackageInfo(self, filename: str) -> dict:
archive = zipfile.ZipFile(filename, "r")
try:
# All information is in package.json
with archive.open("package.json", "r") as f:
package_info_dict = json.loads(f.read().decode("utf-8"))
return package_info_dict
except Exception as e:
raise RuntimeError("Could not get package information from file '%s': %s" % (filename, e))
finally:
archive.close()
# Gets the license file content if present in the given package file.
# Returns None if there is no license file found.
def getPackageLicense(self, filename: str) -> Optional[str]:
license_string = None
archive = zipfile.ZipFile(filename)
try:
# Go through all the files and use the first successful read as the result
for file_info in archive.infolist():
if file_info.is_dir() or not file_info.filename.startswith("files/"):
continue
filename_parts = os.path.basename(file_info.filename.lower()).split(".")
stripped_filename = filename_parts[0]
if stripped_filename in ("license", "licence"):
Logger.log("i", "Found potential license file '%s'", file_info.filename)
try:
with archive.open(file_info.filename, "r") as f:
data = f.read()
license_string = data.decode("utf-8")
break
except:
Logger.logException("e", "Failed to load potential license file '%s' as text file.",
file_info.filename)
license_string = None
except Exception as e:
raise RuntimeError("Could not get package license from file '%s': %s" % (filename, e))
finally:
archive.close()
return license_string

View file

View file

@ -2,7 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher.
import os
import os.path
import re
import configparser
@ -29,7 +28,7 @@ from . import GlobalStack
from cura.CuraApplication import CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.ProfileReader import NoProfileException
from cura.ReaderWriters.ProfileReader import NoProfileException
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -676,7 +675,7 @@ class CuraContainerRegistry(ContainerRegistry):
return extruder_stack
def _findQualityChangesContainerInCuraFolder(self, name):
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityChangesInstanceContainer)
instance_container = None

View file

@ -46,11 +46,15 @@ import faulthandler
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
linux_distro_name = platform.linux_distribution()[0].lower()
# TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
import ctypes
from ctypes.util import find_library
libGL = find_library("GL")
ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
# The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
try:
import ctypes
from ctypes.util import find_library
libGL = find_library("GL")
ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
except:
# GLES-only systems (e.g. ARM Mali) do not have libGL, ignore error
pass
# When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs.
if Platform.isWindows() and hasattr(sys, "frozen"):

View file

@ -6,7 +6,7 @@ from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.ProfileReader import ProfileReader
from cura.ReaderWriters.ProfileReader import ProfileReader
import zipfile

View file

@ -3,8 +3,7 @@
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from UM.SaveFile import SaveFile
from cura.ProfileWriter import ProfileWriter
from cura.ReaderWriters.ProfileWriter import ProfileWriter
import zipfile
## Writes profiles to Cura's own profile format with config files.

View file

@ -3,11 +3,10 @@
import gzip
from io import TextIOWrapper
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
from UM.PluginRegistry import PluginRegistry
## A file reader that reads gzipped g-code.
#
# If you're zipping g-code, you might as well use gzip!

View file

@ -1,21 +1,24 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
from UM.Platform import Platform
from . import GCodeGzReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
file_extension = "gz" if Platform.isOSX() else "gcode.gz"
return {
"mesh_reader": [
{
"extension": "gcode.gz",
"extension": file_extension,
"description": i18n_catalog.i18nc("@item:inlistbox", "Compressed G-code File")
}
]
}
def register(app):
app.addNonSliceableExtension(".gcode.gz")
app.addNonSliceableExtension(".gz")
return { "mesh_reader": GCodeGzReader.GCodeGzReader() }

View file

@ -1,16 +1,19 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
from UM.Platform import Platform
from . import GCodeGzWriter
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
file_extension = "gz" if Platform.isOSX() else "gcode.gz"
return {
"mesh_writer": {
"output": [{
"extension": "gcode.gz",
"extension": file_extension,
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
"mime_type": "application/gzip",
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode

View file

@ -10,7 +10,7 @@ from UM.Logger import Logger
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from cura.ProfileReader import ProfileReader, NoProfileException
from cura.ReaderWriters.ProfileReader import ProfileReader, NoProfileException
## A class that reads profile data from g-code files.
#

View file

@ -12,8 +12,7 @@ from UM.Logger import Logger # Logging errors.
from UM.PluginRegistry import PluginRegistry # For getting the path to this plugin's directory.
from UM.Settings.ContainerRegistry import ContainerRegistry #To create unique profile IDs.
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.ProfileReader import ProfileReader # The plug-in type to implement.
from cura.Settings.ExtruderManager import ExtruderManager #To get the current extruder definition.
from cura.ReaderWriters.ProfileReader import ProfileReader # The plug-in type to implement.
## A plugin that reads profile data from legacy Cura versions.

View file

@ -1,389 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# PluginBrowser is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from UM.Application import Application
from UM.Logger import Logger
from UM.PluginError import PluginNotFoundError
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Bindings.PluginsModel import PluginsModel
from UM.Extension import Extension
from UM.i18n import i18nCatalog
from UM.Version import Version
from UM.Message import Message
import json
import os
import tempfile
import platform
import zipfile
from cura.CuraApplication import CuraApplication
i18n_catalog = i18nCatalog("cura")
class PluginBrowser(QObject, Extension):
def __init__(self, parent=None):
super().__init__(parent)
self._api_version = 4
self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
self._plugin_list_request = None
self._download_plugin_request = None
self._download_plugin_reply = None
self._network_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._plugins_metadata = []
self._plugins_model = None
# Can be 'installed' or 'available'
self._view = "available"
self._restart_required = False
self._dialog = None
self._restartDialog = None
self._download_progress = 0
self._is_downloading = False
self._request_header = [b"User-Agent",
str.encode("%s/%s (%s %s)" % (Application.getInstance().getApplicationName(),
Application.getInstance().getVersion(),
platform.system(),
platform.machine(),
)
)
]
# Installed plugins are really installed after reboot. In order to
# prevent the user from downloading the same file over and over again,
# we keep track of the upgraded plugins.
# NOTE: This will be depreciated in favor of the 'status' system.
self._newly_installed_plugin_ids = []
self._newly_uninstalled_plugin_ids = []
self._plugin_statuses = {} # type: Dict[str, str]
# variables for the license agreement dialog
self._license_dialog_plugin_name = ""
self._license_dialog_license_content = ""
self._license_dialog_plugin_file_location = ""
self._restart_dialog_message = ""
showLicenseDialog = pyqtSignal()
showRestartDialog = pyqtSignal()
pluginsMetadataChanged = pyqtSignal()
onDownloadProgressChanged = pyqtSignal()
onIsDownloadingChanged = pyqtSignal()
restartRequiredChanged = pyqtSignal()
viewChanged = pyqtSignal()
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self):
return self._license_dialog_plugin_name
@pyqtSlot(result = str)
def getLicenseDialogPluginFileLocation(self):
return self._license_dialog_plugin_file_location
@pyqtSlot(result = str)
def getLicenseDialogLicenseContent(self):
return self._license_dialog_license_content
@pyqtSlot(result = str)
def getRestartDialogMessage(self):
return self._restart_dialog_message
def openLicenseDialog(self, plugin_name, license_content, plugin_file_location):
self._license_dialog_plugin_name = plugin_name
self._license_dialog_license_content = license_content
self._license_dialog_plugin_file_location = plugin_file_location
self.showLicenseDialog.emit()
def openRestartDialog(self, message):
self._restart_dialog_message = message
self.showRestartDialog.emit()
@pyqtProperty(bool, notify = onIsDownloadingChanged)
def isDownloading(self):
return self._is_downloading
@pyqtSlot()
def browsePlugins(self):
self._createNetworkManager()
self.requestPluginList()
if not self._dialog:
self._dialog = self._createDialog("PluginBrowser.qml")
self._dialog.show()
@pyqtSlot()
def requestPluginList(self):
Logger.log("i", "Requesting plugin list")
url = QUrl(self._api_url + "plugins")
self._plugin_list_request = QNetworkRequest(url)
self._plugin_list_request.setRawHeader(*self._request_header)
self._network_manager.get(self._plugin_list_request)
def _createDialog(self, qml_name):
Logger.log("d", "Creating dialog [%s]", qml_name)
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
dialog = Application.getInstance().createQmlComponent(path, {"manager": self})
return dialog
def setIsDownloading(self, is_downloading):
if self._is_downloading != is_downloading:
self._is_downloading = is_downloading
self.onIsDownloadingChanged.emit()
def _onDownloadPluginProgress(self, bytes_sent, bytes_total):
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
self.setDownloadProgress(new_progress)
if new_progress == 100.0:
self.setIsDownloading(False)
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
# must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curaplugin", delete = False)
location = self._temp_plugin_file.name
# write first and close, otherwise on Windows, it cannot read the file
self._temp_plugin_file.write(self._download_plugin_reply.readAll())
self._temp_plugin_file.close()
self._checkPluginLicenseOrInstall(location)
return
## Checks if the downloaded plugin ZIP file contains a license file or not.
# If it does, it will show a popup dialog displaying the license to the user. The plugin will be installed if the
# user accepts the license.
# If there is no license file, the plugin will be directory installed.
def _checkPluginLicenseOrInstall(self, file_path):
with zipfile.ZipFile(file_path, "r") as zip_ref:
plugin_id = None
for file in zip_ref.infolist():
if file.filename.endswith("/"):
plugin_id = file.filename.strip("/")
break
if plugin_id is None:
msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from <filename>{0}</filename>", file_path)
msg_title = i18n_catalog.i18nc("@info:tile", "Warning")
self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title)
return
# find a potential license file
plugin_root_dir = plugin_id + "/"
license_file = None
for f in zip_ref.infolist():
# skip directories (with file_size = 0) and files not in the plugin directory
if f.file_size == 0 or not f.filename.startswith(plugin_root_dir):
continue
file_name = os.path.basename(f.filename).lower()
file_base_name, file_ext = os.path.splitext(file_name)
if file_base_name in ["license", "licence"]:
license_file = f.filename
break
# show a dialog for user to read and accept/decline the license
if license_file is not None:
Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id)
license_content = zip_ref.read(license_file).decode('utf-8')
self.openLicenseDialog(plugin_id, license_content, file_path)
return
# there is no license file, directly install the plugin
self.installPlugin(file_path)
@pyqtSlot(str)
def installPlugin(self, file_path):
# Ensure that it starts with a /, as otherwise it doesn't work on windows.
if not file_path.startswith("/"):
location = "/" + file_path
else:
location = file_path
result = PluginRegistry.getInstance().installPlugin("file://" + location)
self._newly_installed_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit()
self.openRestartDialog(result["message"])
self._restart_required = True
self.restartRequiredChanged.emit()
# Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
@pyqtSlot(str)
def removePlugin(self, plugin_id):
result = PluginRegistry.getInstance().uninstallPlugin(plugin_id)
self._newly_uninstalled_plugin_ids.append(result["id"])
self.pluginsMetadataChanged.emit()
self._restart_required = True
self.restartRequiredChanged.emit()
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
@pyqtSlot(str)
def enablePlugin(self, plugin_id):
self._plugin_registry.enablePlugin(plugin_id)
self.pluginsMetadataChanged.emit()
Logger.log("i", "%s was set as 'active'", id)
@pyqtSlot(str)
def disablePlugin(self, plugin_id):
self._plugin_registry.disablePlugin(plugin_id)
self.pluginsMetadataChanged.emit()
Logger.log("i", "%s was set as 'deactive'", id)
@pyqtProperty(int, notify = onDownloadProgressChanged)
def downloadProgress(self):
return self._download_progress
def setDownloadProgress(self, progress):
if progress != self._download_progress:
self._download_progress = progress
self.onDownloadProgressChanged.emit()
@pyqtSlot(str)
def downloadAndInstallPlugin(self, url):
Logger.log("i", "Attempting to download & install plugin from %s", url)
url = QUrl(url)
self._download_plugin_request = QNetworkRequest(url)
self._download_plugin_request.setRawHeader(*self._request_header)
self._download_plugin_reply = self._network_manager.get(self._download_plugin_request)
self.setDownloadProgress(0)
self.setIsDownloading(True)
self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress)
@pyqtSlot()
def cancelDownload(self):
Logger.log("i", "user cancelled the download of a plugin")
self._download_plugin_reply.abort()
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply = None
self._download_plugin_request = None
self.setDownloadProgress(0)
self.setIsDownloading(False)
@pyqtSlot(str)
def setView(self, view):
self._view = view
self.viewChanged.emit()
self.pluginsMetadataChanged.emit()
@pyqtProperty(QObject, notify=pluginsMetadataChanged)
def pluginsModel(self):
self._plugins_model = PluginsModel(None, self._view)
# self._plugins_model.update()
# Check each plugin the registry for matching plugin from server
# metadata, and if found, compare the versions. Higher version sets
# 'can_upgrade' to 'True':
for plugin in self._plugins_model.items:
if self._checkCanUpgrade(plugin["id"], plugin["version"]):
plugin["can_upgrade"] = True
for item in self._plugins_metadata:
if item["id"] == plugin["id"]:
plugin["update_url"] = item["file_location"]
return self._plugins_model
def _checkCanUpgrade(self, plugin_id, version):
if not self._plugin_registry.isInstalledPlugin(plugin_id):
return False
try:
plugin_object = self._plugin_registry.getPluginObject(plugin_id)
except PluginNotFoundError:
# CURA-5287
# At this point, we know that this plugin is installed because it passed the previous check, but we cannot
# get the PluginObject. This means there is a bug in the plugin or something. So, we always allow to upgrade
# this plugin and hopefully that fixes it.
Logger.log("w", "Could not find plugin %s", plugin_id)
return True
# Scan plugin server data for plugin with the given id:
for plugin in self._plugins_metadata:
if plugin_id == plugin["id"]:
reg_version = Version(plugin_object.getVersion())
new_version = Version(plugin["version"])
if new_version > reg_version:
Logger.log("i", "%s has an update available: %s", plugin["id"], plugin["version"])
return True
return False
def _onRequestFinished(self, reply):
reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError:
Logger.log("w", "Got a timeout.")
# Reset everything.
self.setDownloadProgress(0)
self.setIsDownloading(False)
if self._download_plugin_reply:
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply.abort()
self._download_plugin_reply = None
return
elif reply.error() == QNetworkReply.HostNotFoundError:
Logger.log("w", "Unable to reach server.")
return
if reply.operation() == QNetworkAccessManager.GetOperation:
if reply_url == self._api_url + "plugins":
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Add metadata to the manager:
self._plugins_metadata = json_data
self._plugin_registry.addExternalPlugins(self._plugins_metadata)
self.pluginsMetadataChanged.emit()
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
return
else:
# Ignore any operation that is not a get operation
pass
def _onNetworkAccesibleChanged(self, accessible):
if accessible == 0:
self.setDownloadProgress(0)
self.setIsDownloading(False)
if self._download_plugin_reply:
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
self._download_plugin_reply.abort()
self._download_plugin_reply = None
def _createNetworkManager(self):
if self._network_manager:
self._network_manager.finished.disconnect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
@pyqtProperty(bool, notify = restartRequiredChanged)
def restartRequired(self):
return self._restart_required
@pyqtProperty(str, notify = viewChanged)
def viewing(self):
return self._view
@pyqtSlot()
def restart(self):
CuraApplication.getInstance().windowClosed()

View file

@ -1,369 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
// PluginBrowser is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM
Window {
id: base
title: catalog.i18nc("@title:tab", "Plugins");
modality: Qt.ApplicationModal
width: 800 * screenScaleFactor
height: 640 * screenScaleFactor
minimumWidth: 350 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
color: UM.Theme.getColor("sidebar")
Item {
id: view
anchors {
fill: parent
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_margin").height
bottomMargin: UM.Theme.getSize("default_margin").height
}
Rectangle {
id: topBar
width: parent.width
color: "transparent"
height: childrenRect.height
Row {
spacing: 12
height: childrenRect.height
width: childrenRect.width
anchors.horizontalCenter: parent.horizontalCenter
Button {
text: "Install"
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 48
Rectangle {
visible: manager.viewing == "available" ? true : false
color: UM.Theme.getColor("primary")
anchors.bottom: parent.bottom
width: parent.width
height: 3
}
}
label: Text {
text: control.text
color: UM.Theme.getColor("text")
font {
pixelSize: 15
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: manager.setView("available")
}
Button {
text: "Manage"
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 48
Rectangle {
visible: manager.viewing == "installed" ? true : false
color: UM.Theme.getColor("primary")
anchors.bottom: parent.bottom
width: parent.width
height: 3
}
}
label: Text {
text: control.text
color: UM.Theme.getColor("text")
font {
pixelSize: 15
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: manager.setView("installed")
}
}
}
// Scroll view breaks in QtQuick.Controls 2.x
ScrollView {
id: installedPluginList
width: parent.width
height: 400
anchors {
top: topBar.bottom
topMargin: UM.Theme.getSize("default_margin").height
bottom: bottomBar.top
bottomMargin: UM.Theme.getSize("default_margin").height
}
frameVisible: true
ListView {
id: pluginList
property var activePlugin
property var filter: "installed"
anchors.fill: parent
model: manager.pluginsModel
delegate: PluginEntry {}
}
}
Rectangle {
id: bottomBar
width: parent.width
height: childrenRect.height
color: "transparent"
anchors.bottom: parent.bottom
Label {
visible: manager.restartRequired
text: "You will need to restart Cura before changes in plugins have effect."
height: 30
verticalAlignment: Text.AlignVCenter
}
Button {
id: restartChangedButton
text: "Quit Cura"
anchors.right: closeButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
visible: manager.restartRequired
iconName: "dialog-restart"
onClicked: manager.restart()
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: UM.Theme.getColor("primary")
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("button_text")
font {
pixelSize: 13
bold: true
}
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
Button {
id: closeButton
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
onClicked: {
if ( manager.isDownloading ) {
manager.cancelDownload()
}
base.close();
}
anchors.right: parent.right
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 30
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("text")
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
}
UM.I18nCatalog { id: catalog; name: "cura" }
Connections {
target: manager
onShowLicenseDialog: {
licenseDialog.pluginName = manager.getLicenseDialogPluginName();
licenseDialog.licenseContent = manager.getLicenseDialogLicenseContent();
licenseDialog.pluginFileLocation = manager.getLicenseDialogPluginFileLocation();
licenseDialog.show();
}
}
UM.Dialog {
id: licenseDialog
title: catalog.i18nc("@title:window", "Plugin License Agreement")
minimumWidth: UM.Theme.getSize("license_window_minimum").width
minimumHeight: UM.Theme.getSize("license_window_minimum").height
width: minimumWidth
height: minimumHeight
property var pluginName;
property var licenseContent;
property var pluginFileLocation;
Item
{
anchors.fill: parent
Label
{
id: licenseTitle
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text: licenseDialog.pluginName + 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?")
wrapMode: Text.Wrap
}
TextArea
{
id: licenseText
anchors.top: licenseTitle.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true
text: licenseDialog.licenseContent != null ? licenseDialog.licenseContent : ""
}
}
rightButtons: [
Button
{
id: acceptButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Accept")
onClicked:
{
licenseDialog.close();
manager.installPlugin(licenseDialog.pluginFileLocation);
}
},
Button
{
id: declineButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Decline")
onClicked:
{
licenseDialog.close();
}
}
]
}
Connections {
target: manager
onShowRestartDialog: {
restartDialog.message = manager.getRestartDialogMessage();
restartDialog.show();
}
}
Window {
id: restartDialog
// title: catalog.i18nc("@title:tab", "Plugins");
width: 360 * screenScaleFactor
height: 120 * screenScaleFactor
minimumWidth: 360 * screenScaleFactor
minimumHeight: 120 * screenScaleFactor
color: UM.Theme.getColor("sidebar")
property var message;
Text {
id: message
anchors {
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
}
text: restartDialog.message != null ? restartDialog.message : ""
}
Button {
id: laterButton
text: "Later"
onClicked: restartDialog.close();
anchors {
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
bottom: parent.bottom
bottomMargin: UM.Theme.getSize("default_margin").height
}
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 30
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("text")
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
Button {
id: restartButton
text: "Quit Cura"
anchors {
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
bottom: parent.bottom
bottomMargin: UM.Theme.getSize("default_margin").height
}
onClicked: manager.restart()
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: UM.Theme.getColor("primary")
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("button_text")
font {
pixelSize: 13
bold: true
}
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}

View file

@ -1,486 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// PluginBrowser is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM
Component {
id: pluginDelegate
Rectangle {
// Don't show required plugins as they can't be managed anyway:
height: !model.required ? 84 : 0
visible: !model.required ? true : false
color: "transparent"
anchors {
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
}
// Bottom border:
Rectangle {
color: UM.Theme.getColor("lining")
width: parent.width
height: 1
anchors.bottom: parent.bottom
}
// Plugin info
Column {
id: pluginInfo
property var color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
// Styling:
height: parent.height
anchors {
left: parent.left
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
right: authorInfo.left
rightMargin: UM.Theme.getSize("default_margin").width
}
Label {
text: model.name
width: parent.width
height: 24
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
font {
pixelSize: 13
bold: true
}
color: pluginInfo.color
}
Text {
text: model.description
width: parent.width
height: 36
clip: true
wrapMode: Text.WordWrap
color: pluginInfo.color
elide: Text.ElideRight
}
}
// Author info
Column {
id: authorInfo
width: 192
height: parent.height
anchors {
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
right: pluginActions.left
rightMargin: UM.Theme.getSize("default_margin").width
}
Label {
text: "<a href=\"mailto:"+model.author_email+"?Subject=Cura: "+model.name+"\">"+model.author+"</a>"
width: parent.width
height: 24
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
onLinkActivated: Qt.openUrlExternally("mailto:"+model.author_email+"?Subject=Cura: "+model.name+" Plugin")
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
}
}
// Plugin actions
Row {
id: pluginActions
width: 96
height: parent.height
anchors {
top: parent.top
right: parent.right
topMargin: UM.Theme.getSize("default_margin").height
}
layoutDirection: Qt.RightToLeft
spacing: UM.Theme.getSize("default_margin").width
// For 3rd-Party Plugins:
Button {
id: installButton
text: {
if ( manager.isDownloading && pluginList.activePlugin == model ) {
return catalog.i18nc( "@action:button", "Cancel" );
} else {
if (model.can_upgrade) {
return catalog.i18nc("@action:button", "Update");
}
return catalog.i18nc("@action:button", "Install");
}
}
enabled:
{
if ( manager.isDownloading )
{
return pluginList.activePlugin == model ? true : false
}
else
{
return true
}
}
opacity: enabled ? 1.0 : 0.5
visible: model.external && ((model.status !== "installed") || model.can_upgrade)
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: "transparent"
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Label {
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: {
if ( manager.isDownloading && pluginList.activePlugin == model ) {
manager.cancelDownload();
} else {
pluginList.activePlugin = model;
if ( model.can_upgrade ) {
manager.downloadAndInstallPlugin( model.update_url );
} else {
manager.downloadAndInstallPlugin( model.file_location );
}
}
}
}
Button {
id: removeButton
text: "Uninstall"
visible: model.can_uninstall && model.status == "installed"
enabled: !manager.isDownloading
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: "transparent"
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: manager.removePlugin( model.id )
}
// For Ultimaker Plugins:
Button {
id: enableButton
text: "Enable"
visible: !model.external && model.enabled == false
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: "transparent"
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: {
manager.enablePlugin(model.id);
}
}
Button {
id: disableButton
text: "Disable"
visible: !model.external && model.enabled == true
style: ButtonStyle {
background: Rectangle {
implicitWidth: 96
implicitHeight: 30
color: "transparent"
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: {
manager.disablePlugin(model.id);
}
}
/*
Rectangle {
id: removeControls
visible: model.status == "installed" && model.enabled
width: 96
height: 30
color: "transparent"
Button {
id: removeButton
text: "Disable"
enabled: {
if ( manager.isDownloading && pluginList.activePlugin == model ) {
return false;
} else if ( model.required ) {
return false;
} else {
return true;
}
}
onClicked: {
manager.disablePlugin(model.id);
}
style: ButtonStyle {
background: Rectangle {
color: "white"
implicitWidth: 96
implicitHeight: 30
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: "grey"
text: control.text
horizontalAlignment: Text.AlignLeft
}
}
}
Button {
id: removeDropDown
property bool open: false
UM.RecolorImage {
anchors.centerIn: parent
height: 10
width: 10
source: UM.Theme.getIcon("arrow_bottom")
color: "grey"
}
enabled: {
if ( manager.isDownloading && pluginList.activePlugin == model ) {
return false;
} else if ( model.required ) {
return false;
} else {
return true;
}
}
anchors.right: parent.right
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 30
implicitHeight: 30
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: "grey"
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
// For the disable option:
// onClicked: pluginList.model.setEnabled(model.id, checked)
onClicked: {
if ( !removeDropDown.open ) {
removeDropDown.open = true
}
else {
removeDropDown.open = false
}
}
}
Rectangle {
id: divider
width: 1
height: parent.height
anchors.right: removeDropDown.left
color: UM.Theme.getColor("lining")
}
Column {
id: options
anchors {
top: removeButton.bottom
left: parent.left
right: parent.right
}
height: childrenRect.height
visible: removeDropDown.open
Button {
id: disableButton
text: "Remove"
height: 30
width: parent.width
onClicked: {
removeDropDown.open = false;
manager.removePlugin( model.id );
}
}
}
}
*/
/*
Button {
id: enableButton
visible: !model.enabled && model.status == "installed"
onClicked: manager.enablePlugin( model.id );
text: "Enable"
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 30
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: UM.Theme.getColor("text")
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
Button {
id: updateButton
visible: model.status == "installed" && model.can_upgrade && model.enabled
// visible: model.already_installed
text: {
// If currently downloading:
if ( manager.isDownloading && pluginList.activePlugin == model ) {
return catalog.i18nc( "@action:button", "Cancel" );
} else {
return catalog.i18nc("@action:button", "Update");
}
}
style: ButtonStyle {
background: Rectangle {
color: UM.Theme.getColor("primary")
implicitWidth: 96
implicitHeight: 30
// radius: 4
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: "white"
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
Button {
id: externalControls
visible: model.status == "available" ? true : false
text: {
// If currently downloading:
if ( manager.isDownloading && pluginList.activePlugin == model ) {
return catalog.i18nc( "@action:button", "Cancel" );
} else {
return catalog.i18nc("@action:button", "Install");
}
}
onClicked: {
if ( manager.isDownloading && pluginList.activePlugin == model ) {
manager.cancelDownload();
} else {
pluginList.activePlugin = model;
manager.downloadAndInstallPlugin( model.file_location );
}
}
style: ButtonStyle {
background: Rectangle {
color: "transparent"
implicitWidth: 96
implicitHeight: 30
border {
width: 1
color: UM.Theme.getColor("lining")
}
}
label: Text {
verticalAlignment: Text.AlignVCenter
color: "grey"
text: control.text
horizontalAlignment: Text.AlignHCenter
}
}
}
*/
ProgressBar {
id: progressbar
minimumValue: 0;
maximumValue: 100
anchors.left: installButton.left
anchors.right: installButton.right
anchors.top: installButton.bottom
anchors.topMargin: 4
value: manager.isDownloading ? manager.downloadProgress : 0
visible: manager.isDownloading && pluginList.activePlugin == model
style: ProgressBarStyle {
background: Rectangle {
color: "lightgray"
implicitHeight: 6
}
progress: Rectangle {
color: UM.Theme.getColor("primary")
}
}
}
}
}
}

View file

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

View file

@ -1,7 +0,0 @@
{
"name": "Plugin Browser",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 4,
"description": "Find, manage and install new plugins."
}

View file

@ -28,6 +28,13 @@ vertex =
}
fragment =
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif // GL_FRAGMENT_PRECISION_HIGH
#endif // GL_ES
varying lowp vec4 v_color;
varying float v_line_type;

View file

@ -28,6 +28,13 @@ vertex =
}
fragment =
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif // GL_FRAGMENT_PRECISION_HIGH
#endif // GL_ES
varying lowp vec4 v_color;
varying float v_line_type;

View file

@ -13,6 +13,13 @@ vertex =
}
fragment =
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif // GL_FRAGMENT_PRECISION_HIGH
#endif // GL_ES
uniform sampler2D u_layer0;
uniform sampler2D u_layer1;
uniform sampler2D u_layer2;

View file

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

View file

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

View file

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="-122.4 196.9 50.3 74.1"
enable-background="new -122.4 196.9 50.3 74.1" xml:space="preserve">
<title>logo</title>
<desc>Created with Sketch.</desc>
<g id="homepage" sketch:type="MSPage">
<g id="Home-menu" transform="translate(-35.000000, -37.000000)" sketch:type="MSArtboardGroup">
<g id="hero-3" transform="translate(-792.000000, -68.000000)" sketch:type="MSLayerGroup">
<g id="Group-2">
</g>
</g>
<g id="Menu" sketch:type="MSLayerGroup">
<g id="logo" transform="translate(35.000000, 37.845203)" sketch:type="MSShapeGroup">
<g id="Robot" transform="translate(51.265823, 0.000000)">
<path id="Fill-23" fill="#000000" d="M-139.9,203.6c-0.3,0-0.6,0-0.9,0.1c-0.3,0.1-0.5,0.2-0.7,0.4c0,0,0,0,0,0c0,0,0,0,0,0
c-0.1,0.1-0.2,0.2-0.3,0.3c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.3c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.1,0.4
c0,0,0,0,0,0c0,0.1,0,0.3,0,0.4c0,0.3,0.1,0.6,0.2,0.9c0.1,0.3,0.3,0.5,0.5,0.7c0.2,0.2,0.5,0.4,0.7,0.5
c0.3,0.1,0.6,0.2,0.9,0.2c0.1,0,0.2,0,0.3,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.2,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.2-0.1
c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0.3-0.1,0.5-0.2,0.7-0.4c0.1-0.1,0.2-0.2,0.3-0.3c0.1-0.1,0.2-0.3,0.2-0.4
c0.1-0.3,0.2-0.5,0.2-0.8s-0.1-0.6-0.2-0.9c-0.1-0.2-0.2-0.3-0.3-0.5c0,0,0,0,0,0c-0.1-0.1-0.1-0.1-0.2-0.2
c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.2-0.4-0.2C-139.3,203.7-139.6,203.6-139.9,203.6"/>
<path id="Fill-24" fill="#000000" d="M-138.4,211.3c-0.1-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2-0.1-0.3-0.1l-11.4-0.3
c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0.1-0.2,0.1c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0.1-0.1,0.1-0.1,0.2c0,0,0,0,0,0c0,0,0,0,0,0
c0,0.1-0.1,0.1-0.1,0.2v0.7c0,0.1,0,0.2,0.1,0.3c0,0.1,0.1,0.1,0.2,0.2c0.1,0.1,0.1,0.1,0.2,0.1c0.1,0,0.2,0.1,0.3,0.1
l11.4,0.2c0.1,0,0.2,0,0.3,0c0.1,0,0.1-0.1,0.2-0.1c0.1-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.2,0.1-0.2v-0.6c0-0.1,0-0.2-0.1-0.2
C-138.3,211.5-138.4,211.4-138.4,211.3"/>
<path id="Fill-25" fill="#000000" d="M-151,207.2c0.2,0.2,0.5,0.4,0.9,0.6c0.3,0.1,0.7,0.2,1,0.2c0.1,0,0.2,0,0.4,0
c0,0,0.1,0,0.1,0c0.1,0,0.2,0,0.3-0.1c0,0,0,0,0,0c0.1,0,0.2,0,0.2-0.1c0,0,0,0,0,0c0.1,0,0.3-0.1,0.4-0.2c0,0,0,0,0,0
c0.1-0.1,0.3-0.2,0.4-0.3c0.2-0.2,0.4-0.5,0.6-0.8c0.1-0.3,0.2-0.6,0.2-0.9c0-0.3-0.1-0.7-0.2-1c-0.1-0.3-0.3-0.6-0.6-0.8
c-0.2-0.2-0.5-0.4-0.8-0.6c-0.3-0.1-0.7-0.2-1-0.2c-0.4,0-0.7,0-1,0.1c-0.3,0.1-0.6,0.3-0.8,0.4c0,0-0.1,0-0.1,0.1
c0,0-0.1,0.1-0.1,0.1c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.1,0.2-0.2,0.2c0,0.1-0.1,0.1-0.1,0.2c0,0,0,0,0,0c0,0,0,0,0,0
c-0.1,0.1-0.1,0.3-0.1,0.4c0,0,0,0,0,0c0,0.1-0.1,0.3-0.1,0.5c0,0.3,0.1,0.7,0.2,1C-151.4,206.7-151.3,207-151,207.2"/>
<path id="Fill-26" fill="#000000" d="M-139.8,235.5h0.9c0.6,0,2.1,0.3,2.1,0.3s-1.5,0.3-2.1,0.3h-0.9c0,0-0.1,0-0.1,0l-0.1,0.8
l-0.1-0.8c-0.7-0.1-1.7-0.2-1.7-0.2s1-0.2,1.7-0.2l-0.3-1.6l-3.2-0.1l-0.2,1.9l1.9,0.1l-1.9,0.1l-0.2,1.8h0.4
c0.9,0,3.1,0.3,3.1,0.3s-2.2,0.3-3.1,0.3h-0.4l-0.1,0.7l-0.1-0.7h-0.7c-0.9,0-3.1-0.3-3.1-0.3s2.2-0.3,3.1-0.3h0.7l-0.2-1.8
l-4.6,0.3l-0.4,1.9l-0.4-1.9l-3.6-0.4l3.6-0.4l0.4-1.8l-5.1-0.2l9.8-0.4l-0.1-1.6l-3-0.2l-0.3,1.6l-0.3-1.6l-5.9-0.4l5.9-0.4
l0.4-1.7l0.3,1.6l3-0.2l0.2-2.1c-0.9-0.1-1.6-0.2-1.6-0.2s0.8-0.1,1.7-0.2l0.2-1.6h-0.6c-1.1,0-3.7-0.4-3.7-0.4
s2.6-0.4,3.7-0.4h0.7l0.1-1.2l0.1,1.2h0.6c1.1,0,3.8,0.4,3.8,0.4s-2.7,0.4-3.8,0.4h-0.5l0.2,1.5c0.4,0,0.8-0.1,1.1-0.1h1.5
c1,0,3.7,0.4,3.7,0.4c0,0-2.6,0.4-3.7,0.4h-1.5c-0.3,0-0.7,0-1.1-0.1l0.2,2l2.5,0.7l-2.5,0.7l-0.1,1.5l3.1-0.1l0.5-3.4l0.5,3.4
l4.2,0.5l-4.2,0.5L-139.8,235.5L-139.8,235.5L-139.8,235.5z M-150.9,227.5h1c0.7,0,2.4,0.3,2.4,0.3s-1.7,0.3-2.4,0.3h-1
c-0.7,0-2.3-0.3-2.3-0.3S-151.6,227.5-150.9,227.5L-150.9,227.5z M-137.4,230.5h0.1c0.4,0,1.3,0.3,1.3,0.3s-0.9,0.3-1.3,0.3
h-0.6c-0.4,0-1.3-0.3-1.3-0.3s0.9-0.3,1.3-0.3H-137.4L-137.4,230.5z M-131.8,221.6c0-0.1,0-0.1-0.1-0.2c0-0.1-0.1-0.1-0.1-0.2
c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.3,0l-25,0.1c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0.1-0.2,0.1c-0.1,0.1-0.1,0.1-0.2,0.2
c0,0.1-0.1,0.2-0.1,0.2c-0.2,1.8-0.3,3.6-0.3,5.4c-0.1,1.8-0.1,3.6-0.1,5.4c0,1.8,0,3.6,0.1,5.4c0.1,1.8,0.2,3.6,0.3,5.4
c0,0.1,0,0.2,0.1,0.2c0,0.1,0.1,0.1,0.2,0.2c0.1,0.1,0.2,0.1,0.2,0.1c0.2,0.1,6.9,0.4,6.9,0.4c0.5-0.3,1-0.6,1.5-0.8
c0.5-0.2,1-0.4,1.6-0.6c0.3-0.1,0.5-0.2,0.8-0.2c0,0,0,0,0,0c0.2,0,0.3-0.1,0.5-0.1c0.1,0,0.2-0.1,0.3-0.1c0,0,0,0,0,0h0
c0,0,0,0,0.1,0c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0.1,0,0.2,0,0.3,0c0.1,0,0.1,0,0.2,0c0.3,0,0.6-0.1,0.8-0.1
c0.3,0,0.5,0,0.8,0c0.1,0,0.1,0,0.2,0c0.2,0,0.4,0,0.6,0h0c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0h0c0.1,0,0.3,0,0.4,0
c0.1,0,0.2,0,0.3,0c2,0.2,3.8,1.1,3.8,1.1c0.5-0.1,6-1.3,6.1-1.3c0.1,0,0.2-0.1,0.2-0.1c0.1-0.1,0.1-0.1,0.2-0.2
c0-0.1,0.1-0.1,0.1-0.2c0.1-1.7,0.2-3.3,0.3-5c0.1-1.7,0.1-3.3,0.1-5c0-1.7,0-3.3-0.1-5C-131.6,224.9-131.7,223.2-131.8,221.6
L-131.8,221.6z"/>
<path id="Fill-27" fill="#000000" d="M-149.4,233.6l0.4,1.8l4.5,0.3l-0.2-1.9L-149.4,233.6"/>
<path id="Fill-28" fill="#000000" d="M-142.3,260.2c-0.2,0.3-0.2,2.2-0.1,2.6c0.1,0.5,2.1,0.1,2.1,0.1s-1.1-2.1,0.7-2.7
c0.4-0.1,1.6-0.4,2.9-0.6c1.9-0.4,4.1-0.7,4.1-0.7s-1.1-0.3-2.7-0.1l-0.3,0l-0.3,0h0l-3.7,0.5
C-141.4,259.6-142,259.8-142.3,260.2"/>
<path id="Fill-29" fill="#000000" d="M-128.1,243.8v-1.5c0,0,0,0,0,0c0.1-0.1,0.1-0.1,0.2-0.2c0.1-0.1,0.3-0.2,0.4-0.3
c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.2-0.1-0.4c0-0.1-0.1-0.3-0.2-0.4c-0.1-0.1-0.2-0.3-0.3-0.3c0,0-0.1-0.1-0.1-0.1v-3.2
c0,0,0.1,0,0.1,0c0.3-0.1,0.6-0.2,0.8-0.4c0,0.1,0.1,0.1,0.1,0.2c0.2,0.3,0.5,0.6,0.7,1.1c0.2,0.4,0.4,0.9,0.5,1.5
C-125.8,239.5-125.2,243-128.1,243.8L-128.1,243.8z M-128.1,234.4c0.3-0.1,0.8-0.2,1.1-0.3c0.1,0,0.2-0.1,0.3-0.1v0.3
c-0.1,0-0.2,0.1-0.2,0.1c-0.4,0.2-0.9,0.4-1.2,0.5V234.4L-128.1,234.4z M-128.1,232.8c0.3,0,0.7-0.1,1.1-0.1
c0.1,0,0.3,0,0.4-0.1v0.3c-0.1,0-0.2,0.1-0.4,0.1c-0.4,0.1-0.8,0.2-1.1,0.3V232.8L-128.1,232.8z M-128.1,231.1
c0.4,0,1,0,1.5,0.1v0.2c-0.5,0.1-1,0.2-1.5,0.3V231.1L-128.1,231.1z M-128.1,229.4c0.3,0.1,0.7,0.1,1.1,0.2
c0.1,0,0.3,0.1,0.4,0.1v0.3c-0.1,0-0.3,0-0.4,0c-0.4,0-0.7,0-1,0V229.4L-128.1,229.4z M-128.1,227.6c0.4,0.1,0.8,0.2,1.2,0.4
c0.1,0,0.2,0.1,0.3,0.1v0.3c-0.1,0-0.2-0.1-0.4-0.1c-0.4-0.1-0.8-0.1-1.1-0.2V227.6L-128.1,227.6z M-128.1,221.4
C-128.1,221.4-128,221.4-128.1,221.4c0.5,0.3,1,1.6,1.3,2.2c0.3,0.6,0.4,1.3,0.4,2.1c0,0.1,0,0.1,0,0.2c0,0.1-0.1,0.1-0.1,0.1
c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.2,0c0,0-0.1,0-0.2,0s-0.1,0-0.2,0c0,0-0.1,0-0.1,0c-0.3,0-0.6,0-0.9,0L-128.1,221.4
L-128.1,221.4z M-129.9,221.4v22.7c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.3-0.3,0.5-0.5,0.7c-0.2,0.2-0.5,0.4-0.7,0.5
c-0.3,0.1-0.6,0.2-0.9,0.2l-1.5,0.1c0,0,0,0,0,0l-0.1-0.3c-0.1-0.4-0.5-0.7-0.8-0.7l-1.6,0.1l-19,1.2c-0.4,0-0.8,0.3-0.9,0.7
l-0.4,0.9c0,0,0,0,0,0l-1.3,0.1c-0.4,0-0.7,0-1-0.1c-0.3-0.1-0.6-0.2-0.9-0.4c-0.2-0.2-0.4-0.4-0.6-0.7
c-0.1-0.3-0.2-0.6-0.2-0.9v-25c0-0.3,0.1-0.7,0.2-0.9c0.1-0.3,0.3-0.6,0.6-0.8c0.2-0.2,0.5-0.4,0.9-0.5
c0.3-0.1,0.6-0.2,0.9-0.2c0,0,0.1,0,0.1,0l1.2,0c0-0.1,0-0.1,0-0.2l0-0.4c0-0.4,0.3-0.7,0.7-0.7l11.4,0.2l1.1,0l2.7,0l1.7,0
l5.2,0.1c0.3,0,0.6,0.2,0.7,0.6c0,0.1,0,0.1,0,0.2l0,0.2c0,0,0,0.1,0,0.1l0.8,0c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.7,0.5
c0.2,0.2,0.4,0.4,0.5,0.7C-130,220.7-129.9,221-129.9,221.4L-129.9,221.4L-129.9,221.4z M-137.6,255.8v-6.4c0,0,0-0.1,0-0.1
s0,0,0-0.1c0.1,0,0.3,0,0.4,0l0,2.1c0.7-0.6,1.4-1.2,2.3-0.9c0.4,0.1,0.7,0.3,1,0.4v4.6L-137.6,255.8L-137.6,255.8z
M-141.1,258.1l5.5-0.6l1.7-0.2c0.3,0,0.5,0,0.7,0.1c0.2,0.1,0.4,0.2,0.6,0.4c0.2,0.2,0.3,0.4,0.4,0.7c0,0,0,0.1,0,0.1
c0.1,0.2,0.1,0.5,0.1,0.8v3c-0.4,0.1-1.1,0.1-1.8,0.3c-2.4,0.3-6.1,0.8-7.4,1c-2,0.3-1.9-0.5-1.9-0.5v-2.4c0-0.3,0-0.5,0.1-0.8
c0-0.1,0-0.1,0.1-0.2c0.1-0.3,0.2-0.6,0.4-0.8c0.2-0.2,0.4-0.4,0.6-0.6C-141.6,258.2-141.4,258.1-141.1,258.1L-141.1,258.1z
M-140.6,251.9c0.6,0.4,1.4,0.8,2.2,0.4l0.2-0.1l-0.4,3.7l-3.2,0.3v-6.4c0,0,0-0.1,0-0.2c0.1,0,0.2,0,0.4,0l0,3
C-141.2,252.2-140.8,251.7-140.6,251.9L-140.6,251.9z M-131.6,262.9c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1c0,0,0.1,0.1,0.1,0.1
c0,0.1,0,0.1,0,0.2v0.4c0,0.1,0,0.1,0,0.2c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1,0.1l-11.8,1.6
c-0.1,0-0.1,0-0.2,0c0,0-0.1,0-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0-0.1,0-0.1,0-0.2v-0.5c0-0.1,0-0.1,0-0.2c0-0.1,0-0.1,0.1-0.2
c0,0,0.1-0.1,0.1-0.1c0,0,0.1-0.1,0.2-0.1l9.4-1.3l0.1,0l0.2,0l0.8-0.1l0.8-0.1L-131.6,262.9L-131.6,262.9z M-145.2,265.7
c0,0.1,0,0.1,0,0.2c0,0.1-0.1,0.1-0.1,0.2c0,0.1-0.1,0.1-0.1,0.1c0,0-0.1,0.1-0.2,0.1l-12.6,1.7c-0.4,0-0.5-0.5-0.3-1l-7.5-6.2
l8.2,5.7c0,0,0.1,0,0.3,0l11.9-1.6c0.1,0,0.1,0,0.2,0c0,0,0.1,0,0.1,0.1c0,0,0.1,0.1,0.1,0.1c0,0.1,0,0.1,0,0.2V265.7
L-145.2,265.7z M-165.1,245.8c-0.1,0-0.2,0-0.2,0.1c-0.1,0-0.2,0-0.3,0.1c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.2,0h0c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0c0,0,0,0,0,0
c0,0,0,0,0,0c-0.2,0-0.4-0.1-0.6-0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.2,0c0,0-0.1,0-0.1-0.1c0,0-0.1,0-0.1,0
c0,0,0,0,0,0c-0.1,0-0.1,0-0.2-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0h0c-0.1,0-0.1-0.1-0.2-0.1c0,0,0,0,0,0
c0,0-0.1-0.1-0.1-0.1c0,0,0,0,0,0l0,0c0,0-0.1,0-0.1-0.1c0,0,0,0-0.1,0c0,0,3.1-0.1,3.1-0.1s-1.3-0.8-1.6-1.1
c-0.5-0.5,0.7-2.9,0.8-3.2l-2.4-1.1l-0.5-0.2l0.6,0.1l2.7,0.3c-0.2-0.3-1.6-0.6-2.3-0.9c-0.3-0.1-0.4-0.2-0.3-0.3c0,0,0,0,0,0
c0.1,0,0.3,0,0.5,0.1c0.4,0,0.9,0,1.6,0c1.4,0,2.3-0.1,2.9-0.2c0,0,0,0.1,0.1,0.1c0.2,0.4,0.4,1,0.6,1.6
c0.2,0.6,0.4,1.2,0.4,1.8c0.1,0.6,0.1,1.2-0.1,1.7h0c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2-0.1-0.4-0.1c-0.1,0-0.2-0.1-0.3-0.1
c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0-0.2-0.1-0.3-0.1c0,0,0-0.1,0-0.2c0-0.1,0-0.2-0.1-0.2c0-0.1,0-0.2-0.1-0.3
c0-0.1-0.1-0.2-0.1-0.2c0-0.1-0.1-0.2-0.1-0.3c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.1,0-0.2,0
c-0.2,0-0.3,0.1-0.4,0.2c-0.1,0.1-0.3,0.3-0.4,0.4c-0.1,0.2-0.2,0.3-0.2,0.5c-0.1,0.2-0.1,0.3-0.1,0.4c0,0.1,0.1,0.2,0.2,0.3
c0.1,0.1,0.3,0.2,0.4,0.3c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0,0.1,0.1,0.2,0.1c0.2,0.1,0.4,0.2,0.5,0.2c0,0.1,0,0.2,0,0.3
c0,0.1,0,0.2,0,0.4c0,0.1,0,0.2-0.1,0.3c0,0.1-0.1,0.2-0.2,0.2C-165,245.7-165.1,245.8-165.1,245.8L-165.1,245.8z
M-163.9,235.5l0,0.3c-0.1,0-0.1,0.1-0.2,0.1c-0.7,0.4-1.5,0.6-1.6,0.6c-0.1,0-0.7,0.1-1.2,0.1c-0.3,0-0.5,0-0.7,0
c-0.3,0-0.4,0-0.5,0c-0.1,0-0.8-0.1-1.4-0.3l0-0.4c0.6,0,1.3,0,1.4,0c0,0,0.2,0,0.5,0c0.2,0,0.4,0,0.7,0c0.6,0,1.1-0.1,1.2-0.1
c0.2,0,0.9-0.1,1.6-0.3C-164.1,235.5-164,235.5-163.9,235.5L-163.9,235.5z M-164.2,234.4c-0.7,0.2-1.4,0.4-1.5,0.4
c-0.1,0-0.6,0.1-1.1,0.1c-0.3,0-0.6,0-0.8,0c-0.2,0-0.3,0-0.3,0c-0.1,0-0.8-0.1-1.5-0.3c0,0-0.1,0-0.1,0l0-0.3c0,0,0.1,0,0.1,0
c0.7,0,1.4-0.1,1.5-0.1c0,0,0.2,0,0.3,0c0.2,0,0.5,0,0.8,0c0.5,0,1-0.1,1.1-0.1c0.1,0,0.8,0,1.5,0c0.1,0,0.2,0,0.3,0l0,0.3
C-164,234.3-164.1,234.4-164.2,234.4L-164.2,234.4z M-169.7,232.6c0.7-0.1,1.7-0.3,1.9-0.3l0.1,0l2,0c0.2,0,1.2,0.1,1.9,0.2
v0.2c-0.7,0.1-1.7,0.3-1.9,0.3l-2,0l-0.1,0c-0.2,0-1.2-0.1-1.9-0.2V232.6L-169.7,232.6z M-163.8,231v0.3c-0.1,0-0.2,0-0.4,0
c-0.7,0-1.5,0-1.6,0c-0.1,0-0.6,0-1.2,0c-0.3,0-0.5,0-0.7,0c-0.2,0-0.4,0-0.5,0c-0.2,0-0.9-0.1-1.6,0v-0.4
c0.7-0.2,1.4-0.3,1.6-0.4c0,0,0.2,0,0.5,0c0.2,0,0.5,0,0.7,0c0.6,0,1.1,0,1.2,0c0.2,0,0.9,0.1,1.6,0.4
C-164,230.9-163.9,230.9-163.8,231L-163.8,231z M-164.1,229.6c-0.7-0.2-1.6-0.2-1.7-0.2c-0.1,0-0.7-0.1-1.3-0.1
c-0.2,0-0.4,0-0.6,0c-0.4,0-0.6,0-0.7,0l-0.6,0c-0.2,0-0.5,0-0.7,0.1V229c0.2-0.1,0.5-0.1,0.7-0.2c0.3-0.1,0.6-0.1,0.7-0.1
c0.1,0,0.3,0,0.7,0c0.2,0,0.4,0,0.6,0c0.6,0,1.2,0.1,1.3,0.1c0.2,0,1,0.2,1.7,0.5c0.1,0,0.2,0.1,0.3,0.1v0.2
C-163.9,229.6-164,229.6-164.1,229.6L-164.1,229.6z M-170.2,226.5c0-0.1,0-0.4,0-0.4c0.2-1.6,4.1-6,4.1-6s1.7-0.5,2.4-0.5
c0.5,0,1,0.4,0.9,1.6c0,0.2-0.4,5-0.4,5c0,0,0,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1c-0.7,0.6-5.9,0.6-6.4,0.5
C-170.1,226.9-170.2,226.7-170.2,226.5L-170.2,226.5z M-151.5,257.2l0-6.5c0,0,0.1,0,0.4,0l0,2.2c0.7-0.6,1.5-1.2,2.4-1
c0.5,0.1,0.9,0.3,1.3,0.6v4.4L-151.5,257.2L-151.5,257.2z M-154.7,253.4c0.6,0.5,1.4,0.9,2.3,0.4l0.4-0.2l-0.4,3.7l-3.5,0.4
v-5.6l0-1c0,0,0.2,0,0.5,0l0,3.1C-155.4,253.6-155,253.2-154.7,253.4L-154.7,253.4z M-158.1,262.2c0,0,0-0.3,0.1-0.4
c0-0.1,0-0.2,0.1-0.3c0.1-0.3,0.3-0.6,0.5-0.8c0.2-0.2,0.4-0.5,0.7-0.6c0.3-0.2,0.5-0.3,0.8-0.3l7.9-0.9c0.3,0,0.6,0,0.8,0.1
c0.2,0.1,0.5,0.2,0.7,0.4c0.2,0.2,0.3,0.4,0.4,0.7c0,0.1,0,0.1,0.1,0.2c0.1,0.2,0.1,0.5,0.1,0.7v3.2l-0.4,0l-10.1,1.3l-0.5,0.1
c0,0-1,0.1-1.2-0.6v-1.7c-0.4-0.3-6.4-5.2-6.4-5.2L-158.1,262.2L-158.1,262.2z M-158.8,206.5l1.2-0.1l0.5,0c0,0,0,0,0.1,0
c0,0,0,0,0.1,0c0.1,0,0.2,0,0.3,0.1c0.6,0.4,1,1.7,1,3.4c0,2-0.6,3.6-1.3,3.6v0h0c0,0,0,0,0,0h0c0,0,0,0,0,0h0l-1.3,0l-1.6,0.1
l-0.3,0c0,0,0,0,0.1,0c0.4-0.1,0.7-0.8,0.9-1.8c0.1-0.5,0.1-1.1,0.1-1.7c0-0.7-0.1-1.3-0.2-1.8c-0.2-1-0.6-1.6-0.9-1.6
L-158.8,206.5L-158.8,206.5z M-153.3,196.9c0.4-0.1,0.9-0.1,0.9,0.3c0,0.4,0,1.3,0,1.8h0c-0.3,0.1-0.6,0.3-0.9,0.4V196.9
L-153.3,196.9z M-151.5,200.9c0.3-0.1,0.7-0.2,1-0.1l8.4,0.5l2.8,0.2l0.6,0c0.3,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.8,0.5
c0.2,0.2,0.4,0.5,0.5,0.8c0.1,0.3,0.2,0.6,0.2,0.9v10.7c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.3-0.3,0.5-0.5,0.7l-3.9-0.1l-11.7-0.2
c-0.2-0.2-0.3-0.4-0.4-0.6c-0.1-0.3-0.2-0.6-0.2-1v-11.2c0-0.3,0.1-0.7,0.2-0.9c0.1-0.3,0.3-0.5,0.6-0.7
C-152.2,201.2-151.9,201-151.5,200.9L-151.5,200.9z M-139.3,197c0.4-0.1,0.8-0.1,0.8,0.3c0,0.5,0,1.6,0,2.1c0,0,0,0,0,0l-0.8,0
V197L-139.3,197z M-172.7,226.4c-0.2,1.8,0.9,2.2,0.9,2.2v5.8c0,0.1,0,0.1,0,0.2l0,1.2c0,0,0,0.1,0,0.1v0.7
c0,0-0.1,0.1-0.1,0.1c-0.4,0.4-0.7,0.9-1,1.4c-0.4,0.7-0.6,1.3-0.7,2c-0.1,0.9-0.1,1.7,0.2,2.5c0.1,0.3,0.3,0.7,0.5,1
c0.9,1.5,2,2.5,3,3.2c1.8,1.3,5,1,5,1v6.7c-0.1,0.1-0.1,0.1-0.2,0.2c0,0-0.1,0.1-0.1,0.1c-0.2,0.2-0.3,0.3-0.5,0.5
c0,0-0.1,0.1-0.1,0.1c-0.2,0.2-0.3,0.5-0.4,0.7c0,0.1-0.1,0.1-0.1,0.2c-0.1,0.2-0.2,0.4-0.2,0.6c0,0.1,0,0.2-0.1,0.3
c0,0.1,0,0.1,0,0.2c0,0.2,0,0.4,0,0.6v2.1c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c-0.2,0.2-0.3,0.4-0.4,0.7c0,0,0,0.1,0,0.1
c-0.1,0.3-0.1,0.5-0.1,0.8v0.4c0,0.1,0,0.3,0,0.4c0,0.1,0.1,0.3,0.1,0.4c0.1,0.1,0.1,0.3,0.2,0.4c0.1,0.1,0.2,0.3,0.3,0.4
l6.1,5.5c0,0,0,0,0.1,0.1c0.3,0.2,0.6,0.4,0.9,0.5c0.3,0.1,0.7,0.1,1,0.1c0.1,0,0.2,0,0.3,0l12.9-1.8c0.4,0,0.7-0.2,1-0.3
c0.4,0.1,0.7,0.1,1.1,0l11.8-1.6c0.4-0.1,0.8-0.2,1.2-0.4c0.3-0.2,0.5-0.4,0.7-0.7c0.2-0.2,0.3-0.5,0.4-0.8
c0.1-0.3,0.1-0.5,0.1-0.8v-0.4c0-0.3-0.1-0.6-0.2-0.8c-0.1-0.3-0.3-0.5-0.5-0.8c0,0,0,0-0.1-0.1v-2.4c0-1.4-0.4-3.3-2.2-3.8
v-6.8c0.6-0.1,3.7-0.4,3.7-2.7c0.9-0.1,3.4-0.6,4.4-4.2c0.2-0.8,0.3-1.7,0.2-2.6c-0.1-0.7-0.3-1.4-0.6-2
c-0.2-0.5-0.5-1-0.9-1.4c-0.1-0.1-0.1-0.1-0.2-0.2v-8.3c0.2-0.1,0.8-0.4,0.8-1.7c0-0.8-1.9-5.4-2.5-6.1c-0.7-0.8-2-1.8-2.8-2.1
c-0.2-0.1-0.4-0.2-0.5-0.2c-0.6-0.2-1.2-0.3-1.8-0.3c-0.1-0.3-0.4-0.5-0.7-0.5l-1.1,0v-2.6c1.2-0.5,1.9-1.7,1.9-3.3
c0-0.7-0.1-2.5-1.2-3.4c-0.2-0.2-0.4-0.3-0.6-0.4v-2.5l0-0.2c0-0.6-0.1-1.1-0.3-1.6c-0.2-0.5-0.5-1-0.9-1.4
c-0.4-0.4-0.9-0.8-1.4-1c-0.3-0.2-0.7-0.3-1-0.3V197c0-0.5-0.9-1.3-3.8-0.3c0,0,0,0,0,0c-0.4,0.1-0.6,0.5-0.6,0.8v1.7l-8.2-0.5
c-0.4,0-0.8,0-1.2,0.1v-1.9c0-0.5-0.9-1.3-3.8-0.3c-0.4,0.1-0.7,0.5-0.7,0.8v3.9c0,0.1,0,0.3,0,0.3l-3.8,3.1
c-0.4,0-0.9,0.1-1.5,0.2c-1.9,0.4-2.6,3.1-2.6,5.3c0,2,0.5,3.5,1.3,4.4v2.3l-3.8,1.1C-167.3,218.3-172.5,224.6-172.7,226.4
L-172.7,226.4z"/>
<path id="Fill-30" fill="#000000" d="M-157,265.1c0.5,0,4.2-0.5,4.2-0.5s-0.8-0.2-0.5-1.6c0.2-1,2.1-1.4,3.4-1.7
c1.3-0.3,3.1-0.5,3.1-0.5s-1.1-0.3-2.7-0.1l-0.3,0l-0.3,0h0c0,0-5.7,0.7-7.1,1.3c-0.4,0.2-0.4,1.9-0.4,2.7
C-157.6,265.1-157.3,265.1-157,265.1"/>
<path id="Fill-31" fill="#000000" d="M-156,260.4c-0.6,0.1-1,0.9-1,0.9l5-0.7C-152.1,260.5-153.5,260.1-156,260.4"/>
<path id="Fill-32" fill="#000000" d="M-138.6,258.8c0,0-1.4-0.4-2.4-0.3c-0.9,0.1-1.2,1-1.2,1
C-141.7,259-138.7,258.8-138.6,258.8"/>
<path id="Fill-33" fill="#000000" d="M-165.5,226.1c0,0-1.8-1-1.5-1.6c0.5-0.9,1.9-0.6,2.4-1.2c0.8-1,0.6-2.5,0.6-2.5
s-1.7-0.3-2.2,0.3c-0.8,1-3,4.2-3.2,4.6C-170.2,226.7-165.5,226.1-165.5,226.1"/>
<path id="Fill-34" fill="#000000" d="M-157,212.9c0.2,0,0.5-0.6,0.7-1.7c0,0-0.1,0-0.1,0c-0.1-0.1-0.3-0.1-0.4-0.2
c-0.1,0-0.1-0.1-0.2-0.1c-0.2-0.2-0.2-0.4-0.4-0.6c-0.3-0.2-0.6-0.2-0.9-0.1c0,0-0.3,0.1-0.4-0.2c0,0.2,0,0.3,0,0.5
c0,0.5-0.1,1.1-0.2,1.4c-0.1,0.3-0.1,0.6-0.2,0.8c0,0.1-0.1,0.2-0.1,0.3L-157,212.9C-157.1,212.9-157.1,212.9-157,212.9"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,93 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import UM 1.1 as UM
Window
{
id: base
property var selection: null
title: catalog.i18nc("@title", "Toolbox")
modality: Qt.ApplicationModal
width: 720 * screenScaleFactor
height: 640 * screenScaleFactor
minimumWidth: 720 * screenScaleFactor
maximumWidth: 720 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
color: UM.Theme.getColor("sidebar")
UM.I18nCatalog
{
id: catalog
name:"cura"
}
Item
{
anchors.fill: parent
ToolboxHeader
{
id: header
}
Item
{
id: mainView
width: parent.width
z: -1
anchors
{
top: header.bottom
bottom: footer.top
}
// TODO: This could be improved using viewFilter instead of viewCategory
ToolboxLoadingPage
{
id: viewLoading
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "loading"
}
ToolboxDownloadsPage
{
id: viewDownloads
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "overview"
}
ToolboxDetailPage
{
id: viewDetail
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "detail"
}
ToolboxAuthorPage
{
id: viewAuthor
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "author"
}
ToolboxInstalledPage
{
id: installedPluginList
visible: toolbox.viewCategory == "installed"
}
}
ToolboxFooter
{
id: footer
visible: toolbox.restartRequired
height: toolbox.restartRequired ? UM.Theme.getSize("toolbox_footer").height : 0
}
// TODO: Clean this up:
Connections
{
target: toolbox
onShowLicenseDialog:
{
licenseDialog.pluginName = toolbox.getLicenseDialogPluginName();
licenseDialog.licenseContent = toolbox.getLicenseDialogLicenseContent();
licenseDialog.pluginFileLocation = toolbox.getLicenseDialogPluginFileLocation();
licenseDialog.show();
}
}
ToolboxLicenseDialog
{
id: licenseDialog
}
}
}

View file

@ -0,0 +1,138 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: page
property var details: base.selection
anchors.fill: parent
ToolboxBackColumn
{
id: sidebar
}
Rectangle
{
id: header
anchors
{
left: sidebar.right
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
height: UM.Theme.getSize("toolbox_detail_header").height
Image
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
fillMode: Image.PreserveAspectFit
source: details.icon_url || "../images/logobot.svg"
anchors
{
top: parent.top
left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
}
Label
{
id: title
anchors
{
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.name
font: UM.Theme.getFont("large")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
}
Label
{
id: description
text: details.description
anchors
{
top: title.bottom
left: title.left
topMargin: UM.Theme.getSize("default_margin").height
}
}
Column
{
id: properties
anchors
{
top: description.bottom
left: description.left
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
width: childrenRect.width
Label
{
text: catalog.i18nc("@label", "Contact") + ":"
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text_medium")
}
}
Column
{
id: values
anchors
{
top: description.bottom
left: properties.right
leftMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
Label
{
text:
{
if (details.email)
{
return "<a href=\"mailto:"+details.email+"\">"+details.name+"</a>"
}
else
{
return "<a href=\""+details.website+"\">"+details.name+"</a>"
}
}
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
onLinkActivated: Qt.openUrlExternally(link)
}
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
}
}
ToolboxDetailList
{
anchors
{
top: header.bottom
bottom: page.bottom
left: header.left
right: page.right
}
}
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: sidebar
height: parent.height
width: UM.Theme.getSize("toolbox_back_column").width
anchors
{
top: parent.top
left: parent.left
topMargin: UM.Theme.getSize("wide_margin").height
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
}
Button
{
id: button
text: catalog.i18nc("@action:button", "Back")
UM.RecolorImage
{
id: backArrow
anchors
{
verticalCenter: parent.verticalCenter
left: parent.left
rightMargin: UM.Theme.getSize("default_margin").width
}
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize
{
width: width
height: height
}
color: button.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")
source: UM.Theme.getIcon("arrow_left")
}
width: UM.Theme.getSize("toolbox_back_button").width
height: UM.Theme.getSize("toolbox_back_button").height
onClicked:
{
toolbox.viewPage = "overview"
toolbox.filterModelByProp("packages", "type", toolbox.viewCategory)
}
style: ButtonStyle
{
background: Rectangle
{
color: "transparent"
}
label: Label
{
id: labelStyle
text: control.text
color: control.hovered ? UM.Theme.getColor("primary") : UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
horizontalAlignment: Text.AlignRight
width: control.width
}
}
}
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: detailList
ScrollView
{
frameVisible: false
anchors.fill: detailList
style: UM.Theme.styles.scrollview
Column
{
anchors
{
right: parent.right
topMargin: UM.Theme.getSize("wide_margin").height
bottomMargin: UM.Theme.getSize("wide_margin").height
top: parent.top
}
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
spacing: UM.Theme.getSize("default_margin").height
Repeater
{
model: toolbox.packagesModel
delegate: ToolboxDetailTile {}
}
}
}
}

View file

@ -0,0 +1,157 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: page
property var details: base.selection
anchors.fill: parent
width: parent.width
ToolboxBackColumn
{
id: sidebar
}
Item
{
id: header
anchors
{
left: sidebar.right
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
height: UM.Theme.getSize("toolbox_detail_header").height
Image
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
fillMode: Image.PreserveAspectFit
source: details.icon_url || "../images/logobot.svg"
anchors
{
top: parent.top
left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
}
Label
{
id: title
anchors
{
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.name
font: UM.Theme.getFont("large")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
}
Column
{
id: properties
anchors
{
top: title.bottom
left: title.left
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
width: childrenRect.width
Label
{
text: catalog.i18nc("@label", "Version") + ":"
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text_medium")
}
Label
{
text: catalog.i18nc("@label", "Last updated") + ":"
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text_medium")
}
Label
{
text: catalog.i18nc("@label", "Author") + ":"
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text_medium")
}
}
Column
{
id: values
anchors
{
top: title.bottom
left: properties.right
leftMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
Label
{
text: details.version
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
}
Label
{
text:
{
var date = new Date(details.last_updated)
return date.toLocaleString(Qt.locale())
}
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
}
Label
{
text:
{
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")
color: UM.Theme.getColor("text")
onLinkActivated: Qt.openUrlExternally(link)
}
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
}
}
ToolboxDetailList
{
anchors
{
top: header.bottom
bottom: page.bottom
left: header.left
right: page.right
}
}
}

View file

@ -0,0 +1,274 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: tile
property bool installed: toolbox.isInstalled(model.id)
width: detailList.width - UM.Theme.getSize("wide_margin").width
// TODO: Without this line, every instance of this object has 0 height. With
// it, QML spits out tons of bugs claiming a binding loop (not true). Why?
// Because QT is garbage.
height: Math.max( UM.Theme.getSize("toolbox_detail_tile").height, childrenRect.height + UM.Theme.getSize("default_margin").height)
Item
{
id: normalData
height: childrenRect.height
anchors
{
left: parent.left
right: controls.left
rightMargin: UM.Theme.getSize("default_margin").width
top: parent.top
}
Label
{
id: packageName
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
text: model.name
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
}
Label
{
anchors.top: packageName.bottom
width: parent.width
text:
{
if (model.description.length > 235)
{
if (model.description.substring(234, 235) == " ")
{
return model.description.substring(0, 234) + "..."
}
else
{
return model.description.substring(0, 235) + "..."
}
}
return model.description
}
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
}
}
Item
{
id: controls
anchors.right: tile.right
anchors.top: tile.top
width: childrenRect.width
height: childrenRect.height
Button
{
id: installButton
text:
{
if (installed)
{
return catalog.i18nc("@action:button", "Installed")
}
else
{
if ( toolbox.isDownloading && toolbox.activePackage == model )
{
return catalog.i18nc("@action:button", "Cancel")
}
else
{
return catalog.i18nc("@action:button", "Install")
}
}
}
enabled:
{
if (installed)
{
return true
}
if ( toolbox.isDownloading )
{
return toolbox.activePackage == model ? true : false
}
else
{
return true
}
}
opacity: enabled ? 1.0 : 0.5
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: 96
implicitHeight: 30
color:
{
if (installed)
{
return UM.Theme.getColor("action_button_disabled")
}
else
{
if ( control.hovered )
{
return UM.Theme.getColor("primary_hover")
}
else
{
return UM.Theme.getColor("primary")
}
}
}
}
label: Label
{
text: control.text
color:
{
if (installed)
{
return UM.Theme.getColor("action_button_disabled_text")
}
else
{
if ( control.hovered )
{
return UM.Theme.getColor("button_text_hover")
}
else
{
return UM.Theme.getColor("button_text")
}
}
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font: UM.Theme.getFont("default_bold")
}
}
onClicked:
{
if (installed)
{
toolbox.viewCategory = "installed"
}
else
{
// if ( toolbox.isDownloading && toolbox.activePackage == model )
if ( toolbox.isDownloading )
{
toolbox.cancelDownload();
}
else
{
toolbox.activePackage = model
// toolbox.activePackage = model;
if ( model.can_upgrade )
{
// toolbox.downloadAndInstallPlugin( model.update_url );
}
else
{
toolbox.startDownload( model.download_url );
}
}
}
}
}
}
Item
{
id: supportedConfigsChart
anchors.top: normalData.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
height: visible ? childrenRect.height : 0
width: normalData.width
visible: model.type == "material" && model.supported_configs.length > 0
Label
{
id: compatibilityHeading
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
text: catalog.i18nc("@label", "Compatibility")
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}
Column
{
id: compatibilityLabels
anchors
{
top: compatibilityHeading.bottom
topMargin: UM.Theme.getSize("default_margin").height
bottomMargin: UM.Theme.getSize("default_margin").height
}
width: childrenRect.width
Label
{
text: catalog.i18nc("@label", "Machines") + ":"
font: UM.Theme.getFont("small")
}
Label
{
text: catalog.i18nc("@label", "Print Cores") + ":"
font: UM.Theme.getFont("small")
}
Label
{
text: catalog.i18nc("@label", "Quality Profiles") + ":"
font: UM.Theme.getFont("small")
}
}
Column
{
id: compatibilityValues
anchors
{
left: compatibilityLabels.right
leftMargin: UM.Theme.getSize("default_margin").height
top: compatibilityLabels.top
bottom: compatibilityLabels.bottom
}
Label
{
text: "Thingy"
font: UM.Theme.getFont("very_small")
}
Label
{
text: "Thingy"
font: UM.Theme.getFont("very_small")
}
Label
{
text: "Thingy"
font: UM.Theme.getFont("very_small")
}
}
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: tile.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: tile.bottom
}
Connections
{
target: toolbox
onInstallChanged: installed = toolbox.isInstalled(model.id)
}
}

View file

@ -0,0 +1,42 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import UM 1.1 as UM
Column
{
// HACK: GridLayouts don't render to the correct height with odd numbers of
// items, so if odd, add some extra space.
height: grid.model.items.length % 2 == 0 ? childrenRect.height : childrenRect.height + UM.Theme.getSize("toolbox_thumbnail_small").height
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
Label
{
id: heading
text: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Maker Choices") : catalog.i18nc("@label", "Community Plugins")
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
GridLayout
{
id: grid
property var model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
width: parent.width
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").height
rowSpacing: UM.Theme.getSize("default_margin").width
Repeater
{
model: grid.model
delegate: ToolboxDownloadsGridTile
{
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
}
}
}
}

View file

@ -0,0 +1,99 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import UM 1.1 as UM
Item
{
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 - 26
height: UM.Theme.getSize("toolbox_thumbnail_small").height - 26
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../images/logobot.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
}
onClicked:
{
base.selection = model
switch(toolbox.viewCategory)
{
case "material":
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_id", model.id)
break
default:
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
break
}
}
}
}

View file

@ -0,0 +1,38 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
ScrollView
{
frameVisible: false
width: parent.width
height: parent.height
style: UM.Theme.styles.scrollview
Column
{
width: parent.width - 2 * padding
spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height
height: childrenRect.height + 2 * padding
ToolboxDownloadsShowcase
{
id: showcase
width: parent.width
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
}
ToolboxDownloadsGrid
{
id: allPlugins
width: parent.width
}
}
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Column
{
height: childrenRect.height
spacing: UM.Theme.getSize("toolbox_showcase_spacing").width
width: parent.width
Label
{
id: heading
text: catalog.i18nc("@label", "Featured")
width: parent.width
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Row
{
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
anchors
{
horizontalCenter: parent.horizontalCenter
}
Repeater
{
model: {
if ( toolbox.viewCategory == "plugin" )
{
return toolbox.pluginsShowcaseModel
}
if ( toolbox.viewCategory == "material" )
{
return toolbox.materialsShowcaseModel
}
}
delegate: ToolboxDownloadsShowcaseTile {}
}
}
}

View file

@ -0,0 +1,90 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
width: UM.Theme.getSize("toolbox_thumbnail_large").width
height: childrenRect.height
Rectangle
{
id: highlight
anchors.fill: parent
opacity: 0.0
color: UM.Theme.getColor("primary")
}
Rectangle
{
id: thumbnail
color: "white"
width: UM.Theme.getSize("toolbox_thumbnail_large").width
height: UM.Theme.getSize("toolbox_thumbnail_large").height
border
{
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
anchors
{
top: parent.top
horizontalCenter: parent.horizontalCenter
}
Image {
anchors.centerIn: parent
width: UM.Theme.getSize("toolbox_thumbnail_large").width - 2 * UM.Theme.getSize("default_margin").width
height: UM.Theme.getSize("toolbox_thumbnail_large").height - 2 * UM.Theme.getSize("default_margin").height
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../images/logobot.svg"
}
}
Label
{
text: model.name
anchors
{
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
height: UM.Theme.getSize("toolbox_heading_label").height
width: parent.width
wrapMode: Text.WordWrap
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("medium_bold")
}
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
}
onClicked:
{
base.selection = model
switch(toolbox.viewCategory)
{
case "material":
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_name", model.name)
break
default:
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
break
}
}
}
}

View file

@ -0,0 +1,68 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
id: footer
width: parent.width
anchors.bottom: parent.bottom
height: visible ? Math.floor(UM.Theme.getSize("toolbox_footer").height) : 0
Label
{
visible: toolbox.restartRequired
text: catalog.i18nc("@info", "You will need to restart Cura before changes in plugins have effect.")
height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
verticalAlignment: Text.AlignVCenter
anchors
{
top: restartButton.top
left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width
right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width
}
}
Button
{
id: restartButton
text: catalog.i18nc("@info:button", "Quit Cura")
anchors
{
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
visible: toolbox.restartRequired
iconName: "dialog-restart"
onClicked: toolbox.restart()
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_footer_button").width
implicitHeight: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
}
label: Text
{
color: UM.Theme.getColor("button_text")
font: UM.Theme.getFont("default_bold")
text: control.text
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
ToolboxShadow
{
visible: toolbox.restartRequired
anchors.bottom: footer.top
reversed: true
}
}

View file

@ -0,0 +1,62 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.1 as UM
Item
{
id: header
width: parent.width
height: UM.Theme.getSize("toolbox_header").height
Row
{
id: bar
spacing: UM.Theme.getSize("default_margin").width
height: childrenRect.height
width: childrenRect.width
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
}
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Plugins")
active: toolbox.viewCategory == "plugin"
onClicked:
{
toolbox.filterModelByProp("packages", "type", "plugin")
toolbox.viewCategory = "plugin"
toolbox.viewPage = "overview"
}
}
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Materials")
active: toolbox.viewCategory == "material"
onClicked:
{
toolbox.filterModelByProp("authors", "package_types", "material")
toolbox.viewCategory = "material"
toolbox.viewPage = "overview"
}
}
}
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Installed")
active: toolbox.viewCategory == "installed"
anchors
{
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
}
onClicked: toolbox.viewCategory = "installed"
}
ToolboxShadow
{
anchors.top: bar.bottom
}
}

View file

@ -0,0 +1,106 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
ScrollView
{
id: page
frameVisible: false
width: parent.width
height: parent.height
style: UM.Theme.styles.scrollview
Column
{
spacing: UM.Theme.getSize("default_margin").height
anchors
{
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
bottomMargin: UM.Theme.getSize("wide_margin").height
top: parent.top
}
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
Label
{
visible: toolbox.pluginsInstalledModel.items.length > 0
width: parent.width
text: catalog.i18nc("@title:tab", "Plugins")
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Rectangle
{
visible: toolbox.pluginsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: childrenRect.height
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
}
Repeater
{
id: materialList
model: toolbox.pluginsInstalledModel
delegate: ToolboxInstalledTile {}
}
}
}
Label
{
visible: toolbox.materialsInstalledModel.items.length > 0
width: page.width
text: catalog.i18nc("@title:tab", "Materials")
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Rectangle
{
visible: toolbox.materialsInstalledModel.items.length > 0
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: Math.max( UM.Theme.getSize("wide_margin").height, childrenRect.height)
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
}
Repeater
{
id: pluginList
model: toolbox.materialsInstalledModel
delegate: ToolboxInstalledTile {}
}
}
}
}
}

View file

@ -0,0 +1,219 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
property bool canUpdate: false
property bool isEnabled: true
height: UM.Theme.getSize("toolbox_installed_tile").height
anchors
{
left: parent.left
right: parent.right
}
Rectangle
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
}
Column
{
id: pluginInfo
property var color: isEnabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
height: parent.height
anchors
{
left: parent.left
top: parent.top
right: authorInfo.left
topMargin: UM.Theme.getSize("default_margin").height
rightMargin: UM.Theme.getSize("default_margin").width
}
Label
{
text: model.name
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default_bold")
color: pluginInfo.color
}
Text
{
text: model.description
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
clip: true
wrapMode: Text.WordWrap
color: pluginInfo.color
elide: Text.ElideRight
}
}
Column
{
id: authorInfo
height: parent.height
width: Math.floor(UM.Theme.getSize("toolbox_action_button").width * 1.25)
anchors
{
top: parent.top
topMargin: UM.Theme.getSize("default_margin").height
right: pluginActions.left
rightMargin: UM.Theme.getSize("default_margin").width
}
Label
{
text:
{
if (model.author_email)
{
return "<a href=\"mailto:" + model.author_email + "?Subject=Cura: " + model.name + "\">" + model.author_name + "</a>"
}
else
{
return model.author_name
}
}
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
onLinkActivated: Qt.openUrlExternally("mailto:" + model.author_email + "?Subject=Cura: " + model.name + " Plugin")
color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
}
}
Column
{
id: pluginActions
width: childrenRect.width
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").height
anchors
{
top: parent.top
right: parent.right
topMargin: UM.Theme.getSize("default_margin").height
}
Button {
id: removeButton
text:
{
if (model.is_bundled)
{
return isEnabled ? catalog.i18nc("@action:button", "Disable") : catalog.i18nc("@action:button", "Enable")
}
else
{
return catalog.i18nc("@action:button", "Uninstall")
}
}
enabled: !toolbox.isDownloading
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color: "transparent"
border
{
width: UM.Theme.getSize("default_lining").width
color: UM.Theme.getColor("lining")
}
}
label: Text
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked:
{
if (model.is_bundled)
{
if (toolbox.isEnabled(model.id))
{
toolbox.disable(model.id)
}
else
{
toolbox.enable(model.id)
}
}
else
{
toolbox.uninstall( model.id )
}
}
}
Button
{
id: updateButton
text: catalog.i18nc("@action:button", "Update")
visible: canUpdate
style: ButtonStyle
{
background: Rectangle
{
implicitWidth: UM.Theme.getSize("toolbox_action_button").width
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
}
label: Label
{
text: control.text
color: control.hovered ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_text_hover")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font: UM.Theme.getFont("default_bold")
}
}
onClicked:
{
toolbox.update(model.id);
}
}
ProgressBar
{
id: progressbar
anchors
{
left: updateButton.left
right: updateButton.right
top: updateButton.bottom
topMargin: Math.floor(UM.Theme.getSize("default_margin") / 4)
}
value: toolbox.isDownloading ? toolbox.downloadProgress : 0
visible: toolbox.isDownloading
style: ProgressBarStyle
{
background: Rectangle
{
color: UM.Theme.getColor("lining")
implicitHeight: Math.floor(UM.Theme.getSize("toolbox_progress_bar").height)
}
progress: Rectangle
{
color: UM.Theme.getColor("primary")
}
}
}
}
Connections
{
target: toolbox
onEnabledChanged: isEnabled = toolbox.isEnabled(model.id)
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
}
}

View file

@ -0,0 +1,72 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM
UM.Dialog
{
title: catalog.i18nc("@title:window", "Plugin License Agreement")
minimumWidth: UM.Theme.getSize("license_window_minimum").width
minimumHeight: UM.Theme.getSize("license_window_minimum").height
width: minimumWidth
height: minimumHeight
property var pluginName;
property var licenseContent;
property var pluginFileLocation;
Item
{
anchors.fill: parent
Label
{
id: licenseTitle
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text: licenseDialog.pluginName + 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?")
wrapMode: Text.Wrap
}
TextArea
{
id: licenseText
anchors.top: licenseTitle.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true
text: licenseDialog.licenseContent
}
}
rightButtons:
[
Button
{
id: acceptButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Accept")
onClicked:
{
licenseDialog.close();
toolbox.install(licenseDialog.pluginFileLocation);
}
},
Button
{
id: declineButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Decline")
onClicked:
{
licenseDialog.close();
}
}
]
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Rectangle
{
id: page
width: parent.width
height: parent.height
color: "transparent"
Label
{
text: catalog.i18nc("@info", "Fetching packages...")
anchors
{
centerIn: parent
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
Rectangle
{
property bool reversed: false
width: parent.width
height: 8
gradient: Gradient
{
GradientStop
{
position: reversed ? 1.0 : 0.0
color: reversed ? Qt.rgba(0,0,0,0.05) : Qt.rgba(0,0,0,0.2)
}
GradientStop
{
position: reversed ? 0.0 : 1.0
color: Qt.rgba(0,0,0,0)
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Button
{
property bool active: false
style: ButtonStyle
{
background: Rectangle
{
color: "transparent"
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
Rectangle
{
visible: control.active
color: UM.Theme.getColor("sidebar_header_highlight_hover")
anchors.bottom: parent.bottom
width: parent.width
height: UM.Theme.getSize("sidebar_header_highlight").height
}
}
label: Text
{
text: control.text
color:
{
if(control.hovered)
{
return UM.Theme.getColor("topbar_button_text_hovered");
}
if(control.active)
{
return UM.Theme.getColor("topbar_button_text_active");
}
else
{
return UM.Theme.getColor("topbar_button_text_inactive");
}
}
font: control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}

View file

@ -0,0 +1,93 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import re
from typing import Dict
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
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):
def __init__(self, parent = None):
super().__init__(parent)
self._metadata = None
self.addRoleName(Qt.UserRole + 1, "id")
self.addRoleName(Qt.UserRole + 2, "name")
self.addRoleName(Qt.UserRole + 3, "email")
self.addRoleName(Qt.UserRole + 4, "website")
self.addRoleName(Qt.UserRole + 5, "package_count")
self.addRoleName(Qt.UserRole + 6, "package_types")
self.addRoleName(Qt.UserRole + 7, "icon_url")
self.addRoleName(Qt.UserRole + 8, "description")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str,str]
def setMetadata(self, data):
self._metadata = data
self._update()
def _update(self):
items = []
for author in self._metadata:
items.append({
"id": author["author_id"],
"name": author["display_name"],
"email": author["email"] if "email" in author else None,
"website": author["website"],
"package_count": author["package_count"] if "package_count" in author else 0,
"package_types": author["package_types"] if "package_types" in author else [],
"icon_url": author["icon_url"] if "icon_url" in author else None,
"description": "Material and quality profiles from {author_name}".format( author_name = author["display_name"])
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if key is "package_types":
key_filter = lambda item, value = value: value in item["package_types"]
elif "*" in value:
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value)
else:
key_filter = lambda item, key = key, value = value: self._matchString(item, key, value)
items = filter(key_filter, items)
# Execute all filters.
filtered_items = list(items)
filtered_items.sort(key = lambda k: k["name"])
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:
if filter_dict != self._filter:
self._filter = filter_dict
self._update()
@pyqtProperty("QVariantMap", fset = setFilter, constant = True)
def filter(self) -> Dict[str, str]:
return self._filter
# Check to see if a container matches with a regular expression
def _matchRegExp(self, metadata, property_name, value):
if property_name not in metadata:
return False
value = re.escape(value) #Escape for regex patterns.
value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match.
if self._ignore_case:
value_pattern = re.compile(value, re.IGNORECASE)
else:
value_pattern = re.compile(value)
return value_pattern.match(str(metadata[property_name]))
# Check to see if a container matches with a string
def _matchString(self, metadata, property_name, value):
if property_name not in metadata:
return False
return value.lower() == str(metadata[property_name]).lower()

View file

@ -0,0 +1,106 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import re
from typing import Dict
from PyQt5.QtCore import Qt, pyqtProperty
from UM.Application import Application
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 PackagesModel(ListModel):
def __init__(self, parent = None):
super().__init__(parent)
self._metadata = None
self.addRoleName(Qt.UserRole + 1, "id")
self.addRoleName(Qt.UserRole + 2, "type")
self.addRoleName(Qt.UserRole + 3, "name")
self.addRoleName(Qt.UserRole + 4, "version")
self.addRoleName(Qt.UserRole + 5, "author_id")
self.addRoleName(Qt.UserRole + 6, "author_name")
self.addRoleName(Qt.UserRole + 7, "author_email")
self.addRoleName(Qt.UserRole + 8, "description")
self.addRoleName(Qt.UserRole + 9, "icon_url")
self.addRoleName(Qt.UserRole + 10, "image_urls")
self.addRoleName(Qt.UserRole + 11, "download_url")
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "supported_configs")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
def setMetadata(self, data):
self._metadata = data
self._update()
def _update(self):
items = []
for package in self._metadata:
print(package["author"])
items.append({
"id": package["package_id"],
"type": package["package_type"],
"name": package["display_name"],
"version": package["package_version"],
"author_id": package["author"]["author_id"] if "author_id" in package["author"] else package["author"]["name"],
"author_name": package["author"]["display_name"] if "display_name" in package["author"] else package["author"]["name"],
"author_email": package["author"]["email"] if "email" in package["author"] else "None",
"description": package["description"],
"icon_url": package["icon_url"] if "icon_url" in package else None,
"image_urls": package["image_urls"] if "image_urls" in package else None,
"download_url": package["download_url"] if "download_url" in package else None,
"last_updated": package["last_updated"] if "last_updated" in package else None,
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
"supported_configs": package["supported_configs"] if "supported_configs" in package else []
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if "*" in value:
key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
else:
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)
items = filter(key_filter, items)
# Execute all filters.
filtered_items = list(items)
filtered_items.sort(key = lambda k: k["name"])
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:
if filter_dict != self._filter:
self._filter = filter_dict
self._update()
@pyqtProperty("QVariantMap", fset = setFilter, constant = True)
def filter(self) -> Dict[str, str]:
return self._filter
# Check to see if a container matches with a regular expression
def _matchRegExp(self, metadata, property_name, value):
if property_name not in metadata:
return False
value = re.escape(value) #Escape for regex patterns.
value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match.
if self._ignore_case:
value_pattern = re.compile(value, re.IGNORECASE)
else:
value_pattern = re.compile(value)
return value_pattern.match(str(metadata[property_name]))
# Check to see if a container matches with a string
def _matchString(self, metadata, property_name, value):
if property_name not in metadata:
return False
return value.lower() == str(metadata[property_name]).lower()

View file

@ -0,0 +1,515 @@
# Copyright (c) 2018 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
from typing import Dict
import json
import os
import tempfile
import platform
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from UM.Application import Application
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Bindings.PluginsModel import PluginsModel
from UM.Extension import Extension
from UM.i18n import i18nCatalog
from UM.Version import Version
from cura.CuraApplication import CuraApplication
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
i18n_catalog = i18nCatalog("cura")
## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension):
def __init__(self, parent=None):
super().__init__(parent)
self._application = Application.getInstance()
self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._packages_version = self._plugin_registry.APIVersion
self._api_version = 1
self._api_url = "https://api-staging.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version)
# Network:
self._get_packages_request = None
self._get_showcase_request = None
self._download_request = None
self._download_reply = None
self._download_progress = 0
self._is_downloading = False
self._network_manager = None
self._request_header = [
b"User-Agent",
str.encode(
"%s/%s (%s %s)" % (
Application.getInstance().getApplicationName(),
Application.getInstance().getVersion(),
platform.system(),
platform.machine(),
)
)
]
self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url))
}
# Data:
self._metadata = {
"authors": [],
"packages": [],
"plugins_showcase": [],
"plugins_installed": [],
"materials_showcase": [],
"materials_installed": []
}
# Models:
self._models = {
"authors": AuthorsModel(self),
"packages": PackagesModel(self),
"plugins_showcase": PackagesModel(self),
"plugins_available": PackagesModel(self),
"plugins_installed": PackagesModel(self),
"materials_showcase": AuthorsModel(self),
"materials_available": PackagesModel(self),
"materials_installed": PackagesModel(self)
}
# These properties are for keeping track of the UI state:
# ----------------------------------------------------------------------
# View category defines which filter to use, and therefore effectively
# which category is currently being displayed. For example, possible
# values include "plugin" or "material", but also "installed".
self._view_category = "plugin"
# View page defines which type of page layout to use. For example,
# possible values include "overview", "detail" or "author".
self._view_page = "loading"
# Active package refers to which package is currently being downloaded,
# installed, or otherwise modified.
self._active_package = None
self._dialog = None
self._restart_required = False
# variables for the license agreement dialog
self._license_dialog_plugin_name = ""
self._license_dialog_license_content = ""
self._license_dialog_plugin_file_location = ""
self._restart_dialog_message = ""
Application.getInstance().initializationFinished.connect(self._onAppInitialized)
# Signals:
# --------------------------------------------------------------------------
# Downloading changes
activePackageChanged = pyqtSignal()
onDownloadProgressChanged = pyqtSignal()
onIsDownloadingChanged = pyqtSignal()
restartRequiredChanged = pyqtSignal()
installChanged = pyqtSignal()
enabledChanged = pyqtSignal()
# UI changes
viewChanged = pyqtSignal()
detailViewChanged = pyqtSignal()
filterChanged = pyqtSignal()
metadataChanged = pyqtSignal()
showLicenseDialog = pyqtSignal()
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str:
return self._license_dialog_plugin_name
@pyqtSlot(result = str)
def getLicenseDialogPluginFileLocation(self) -> str:
return self._license_dialog_plugin_file_location
@pyqtSlot(result = str)
def getLicenseDialogLicenseContent(self) -> str:
return self._license_dialog_license_content
def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str):
self._license_dialog_plugin_name = plugin_name
self._license_dialog_license_content = license_content
self._license_dialog_plugin_file_location = plugin_file_location
self.showLicenseDialog.emit()
# This is a plugin, so most of the components required are not ready when
# this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self):
self._package_manager = Application.getInstance().getCuraPackageManager()
@pyqtSlot()
def browsePackages(self):
# Create the network manager:
# This was formerly its own function but really had no reason to be as
# it was never called more than once ever.
if self._network_manager:
self._network_manager.finished.disconnect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged)
# Make remote requests:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
self._makeRequestByType("plugins_showcase")
self._makeRequestByType("materials_showcase")
# Gather installed packages:
self._updateInstalledModels()
if not self._dialog:
self._dialog = self._createDialog("Toolbox.qml")
self._dialog.show()
# Apply enabled/disabled state to installed plugins
self.enabledChanged.emit()
def _createDialog(self, qml_name: str):
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name)
dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self})
return dialog
@pyqtSlot()
def _updateInstalledModels(self):
all_packages = self._package_manager.getAllInstalledPackagesInfo()
if "plugin" in all_packages:
self._metadata["plugins_installed"] = all_packages["plugin"]
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self.metadataChanged.emit()
if "material" in all_packages:
self._metadata["materials_installed"] = all_packages["material"]
self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
self.metadataChanged.emit()
@pyqtSlot(str)
def install(self, file_path: str):
self._package_manager.installPackage(file_path)
self.installChanged.emit()
self._updateInstalledModels()
self.metadataChanged.emit()
self._restart_required = True
self.restartRequiredChanged.emit()
@pyqtSlot(str)
def uninstall(self, plugin_id: str):
self._package_manager.removePackage(plugin_id)
self.installChanged.emit()
self._updateInstalledModels()
self.metadataChanged.emit()
self._restart_required = True
self.restartRequiredChanged.emit()
@pyqtSlot(str)
def enable(self, plugin_id: str):
self._plugin_registry.enablePlugin(plugin_id)
self.enabledChanged.emit()
Logger.log("i", "%s was set as 'active'.", plugin_id)
self._restart_required = True
self.restartRequiredChanged.emit()
@pyqtSlot(str)
def disable(self, plugin_id: str):
self._plugin_registry.disablePlugin(plugin_id)
self.enabledChanged.emit()
Logger.log("i", "%s was set as 'deactive'.", plugin_id)
self._restart_required = True
self.restartRequiredChanged.emit()
@pyqtProperty(bool, notify = metadataChanged)
def dataReady(self):
return self._packages_model is not None
@pyqtProperty(bool, notify = restartRequiredChanged)
def restartRequired(self):
return self._restart_required
@pyqtSlot()
def restart(self):
self._package_manager._removeAllScheduledPackages()
CuraApplication.getInstance().windowClosed()
# Checks
# --------------------------------------------------------------------------
@pyqtSlot(str, result = bool)
def canUpdate(self, package_id: str) -> bool:
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
return False
remote_package = None
for package in self._metadata["packages"]:
if package["package_id"] == package_id:
remote_package = package
if remote_package is None:
return False
local_version = local_package["package_version"]
remote_version = remote_package["package_version"]
return Version(remote_version) > Version(local_version)
@pyqtSlot(str, result = bool)
def isInstalled(self, package_id: str) -> bool:
return self._package_manager.isPackageInstalled(package_id)
@pyqtSlot(str, result = bool)
def isEnabled(self, package_id: str) -> bool:
if package_id in self._plugin_registry.getActivePlugins():
return True
return False
def loadingComplete(self) -> bool:
populated = 0
for list in self._metadata.items():
if len(list) > 0:
populated += 1
if populated == len(self._metadata.items()):
return True
return False
# Make API Calls
# --------------------------------------------------------------------------
def _makeRequestByType(self, type: str):
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
request = QNetworkRequest(self._request_urls[type])
request.setRawHeader(*self._request_header)
self._network_manager.get(request)
@pyqtSlot(str)
def startDownload(self, url: str):
Logger.log("i", "Toolbox: Attempting to download & install package from %s.", url)
url = QUrl(url)
self._download_request = QNetworkRequest(url)
self._download_request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy)
self._download_request.setRawHeader(*self._request_header)
self._download_reply = self._network_manager.get(self._download_request)
self.setDownloadProgress(0)
self.setIsDownloading(True)
self._download_reply.downloadProgress.connect(self._onDownloadProgress)
@pyqtSlot()
def cancelDownload(self):
Logger.log("i", "Toolbox: User cancelled the download of a plugin.")
self.resetDownload()
return
def resetDownload(self):
self._download_reply.abort()
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
self._download_reply = None
self._download_request = None
self.setDownloadProgress(0)
self.setIsDownloading(False)
# Handlers for Network Events
# --------------------------------------------------------------------------
def _onNetworkAccessibleChanged(self, accessible: int):
if accessible == 0:
self.resetDownload()
def _onRequestFinished(self, reply: QNetworkReply):
if reply.error() == QNetworkReply.TimeoutError:
Logger.log("w", "Got a timeout.")
self.resetDownload()
return
if reply.error() == QNetworkReply.HostNotFoundError:
Logger.log("w", "Unable to reach server.")
self.resetDownload()
return
if reply.operation() == QNetworkAccessManager.GetOperation:
for type, url in self._request_urls.items():
if reply.url() == url:
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Check for errors:
if "errors" in json_data:
for error in json_data["errors"]:
Logger.log("e", "%s", error["title"])
return
# Create model and apply metadata:
if not self._models[type]:
Logger.log("e", "Could not find the %s model.", type)
break
# HACK: Eventually get rid of the code from here...
if type is "plugins_showcase" or type is "materials_showcase":
self._metadata["plugins_showcase"] = json_data["data"]["plugin"]["packages"]
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
self._metadata["materials_showcase"] = json_data["data"]["material"]["authors"]
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
else:
# ...until here.
# This hack arises for multiple reasons but the main
# one is because there are not separate API calls
# for different kinds of showcases.
self._metadata[type] = json_data["data"]
self._models[type].setMetadata(self._metadata[type])
# Do some auto filtering
# TODO: Make multiple API calls in the future to handle this
if type is "packages":
self._models[type].setFilter({"type": "plugin"})
if type is "authors":
self._models[type].setFilter({"package_types": "material"})
self.metadataChanged.emit()
if self.loadingComplete() is True:
self.setViewPage("overview")
return
except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for %s.", type)
break
else:
# Ignore any operation that is not a get operation
pass
def _onDownloadProgress(self, bytes_sent: int, bytes_total: int):
if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100
self.setDownloadProgress(new_progress)
if bytes_sent == bytes_total:
self.setIsDownloading(False)
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
# must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
file_path = self._temp_plugin_file.name
# write first and close, otherwise on Windows, it cannot read the file
self._temp_plugin_file.write(self._download_reply.readAll())
self._temp_plugin_file.close()
self._onDownloadComplete(file_path)
return
def _onDownloadComplete(self, file_path: str):
Logger.log("i", "Toolbox: Download complete.")
try:
package_info = self._package_manager.getPackageInfo(file_path)
except:
Logger.logException("w", "Toolbox: Package file [%s] was not a valid CuraPackage.", file_path)
return
license_content = self._package_manager.getPackageLicense(file_path)
if license_content is not None:
self.openLicenseDialog(package_info["package_id"], license_content, file_path)
return
self.install(file_path)
return
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
def setDownloadProgress(self, progress: int):
if progress != self._download_progress:
self._download_progress = progress
self.onDownloadProgressChanged.emit()
@pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
def downloadProgress(self) -> int:
return self._download_progress
def setIsDownloading(self, is_downloading: bool):
if self._is_downloading != is_downloading:
self._is_downloading = is_downloading
self.onIsDownloadingChanged.emit()
@pyqtProperty(bool, fset = setIsDownloading, notify = onIsDownloadingChanged)
def isDownloading(self) -> bool:
return self._is_downloading
def setActivePackage(self, package: dict):
self._active_package = package
self.activePackageChanged.emit()
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> dict:
return self._active_package
def setViewCategory(self, category: str = "plugin"):
self._view_category = category
self.viewChanged.emit()
@pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
def viewCategory(self) -> str:
return self._view_category
def setViewPage(self, page: str = "overview"):
self._view_page = page
self.viewChanged.emit()
@pyqtProperty(str, fset = setViewPage, notify = viewChanged)
def viewPage(self) -> str:
return self._view_page
# Expose Models:
# --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged)
def authorsModel(self) -> AuthorsModel:
return self._models["authors"]
@pyqtProperty(QObject, notify = metadataChanged)
def packagesModel(self) -> PackagesModel:
return self._models["packages"]
@pyqtProperty(QObject, notify = metadataChanged)
def pluginsShowcaseModel(self) -> PackagesModel:
return self._models["plugins_showcase"]
@pyqtProperty(QObject, notify = metadataChanged)
def pluginsInstalledModel(self) -> PackagesModel:
return self._models["plugins_installed"]
@pyqtProperty(QObject, notify = metadataChanged)
def materialsShowcaseModel(self) -> PackagesModel:
return self._models["materials_showcase"]
@pyqtProperty(QObject, notify = metadataChanged)
def materialsInstalledModel(self) -> PackagesModel:
return self._models["materials_installed"]
# Filter Models:
# --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
def filterModelByProp(self, modelType: str, filterType: str, parameter: str):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", modelType)
return
self._models[modelType].setFilter({ filterType: parameter })
self.filterChanged.emit()
@pyqtSlot()
def removeFilters(self, modelType: str):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", modelType)
return
self._models[modelType].setFilter({})
self.filterChanged.emit()

View file

View file

@ -0,0 +1,43 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
## Upgrades configurations from the state they were in at version 3.2 to the
# state they should be in at version 3.3.
class VersionUpgrade33to34(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 3.2 format.
#
# Since the format may change, this is implemented for the 3.2 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
# \param serialised The serialised form of a CFG file.
# \return The version number stored in the CFG file.
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version
## Upgrades instance containers to have the new version
# number.
def upgradeInstanceContainer(self, serialized, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# Update version number.
parser["general"]["version"] = "4"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]

View file

@ -0,0 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import VersionUpgrade33to34
upgrade = VersionUpgrade33to34.VersionUpgrade33to34()
def getMetaData():
return {
"version_upgrade": {
# From To Upgrade function
("definition_changes", 3000004): ("definition_changes", 4000004, upgrade.upgradeInstanceContainer),
("quality_changes", 3000004): ("quality_changes", 4000004, upgrade.upgradeInstanceContainer),
("user", 3000004): ("user", 4000004, upgrade.upgradeInstanceContainer),
},
"sources": {
"definition_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"}
},
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
}
}
}
def register(app):
return { "version_upgrade": upgrade }

View file

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 3.3 to 3.4",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
"api": 4,
"i18n-catalog": "cura"
}

View file

@ -13,6 +13,13 @@ vertex =
}
fragment =
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif // GL_FRAGMENT_PRECISION_HIGH
#endif // GL_ES
uniform sampler2D u_layer0; //Default pass.
uniform sampler2D u_layer1; //Selection pass.
uniform sampler2D u_layer2; //X-ray pass.

View file

@ -3288,6 +3288,18 @@
"settable_per_mesh": false,
"settable_per_extruder": false
},
"retraction_combing_max_distance":
{
"label": "Max Comb Distance With No Retract",
"description": "When non-zero, combing travel moves that are longer than this distance will use retraction.",
"unit": "mm",
"type": "float",
"default_value": 0,
"minimum_value": "0",
"enabled": "resolveOrValue('retraction_combing') != 'off'",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"travel_retract_before_outer_wall":
{
"label": "Retract Before Outer Wall",

View file

@ -68,7 +68,7 @@ Item
property alias configureSettingVisibility: configureSettingVisibilityAction
property alias browsePlugins: browsePluginsAction
property alias browsePackages: browsePackagesAction
UM.I18nCatalog{id: catalog; name:"cura"}
@ -429,8 +429,8 @@ Item
Action
{
id: browsePluginsAction
text: catalog.i18nc("@action:menu", "Browse plugins...")
id: browsePackagesAction
text: catalog.i18nc("@action:menu", "Browse packages...")
iconName: "plugins_browse"
}

View file

@ -271,9 +271,9 @@ UM.MainWindow
Menu
{
id: plugin_menu
title: catalog.i18nc("@title:menu menubar:toplevel", "P&lugins")
title: catalog.i18nc("@title:menu menubar:toplevel", "&Toolbox")
MenuItem { action: Cura.Actions.browsePlugins }
MenuItem { action: Cura.Actions.browsePackages }
}
Menu
@ -323,31 +323,6 @@ UM.MainWindow
{
if (drop.urls.length > 0)
{
// As the drop area also supports plugins, first check if it's a plugin that was dropped.
if (drop.urls.length == 1)
{
if (PluginRegistry.isPluginFile(drop.urls[0]))
{
// Try to install plugin & close.
var result = PluginRegistry.installPlugin(drop.urls[0]);
pluginInstallDialog.text = result.message;
if (result.status == "ok")
{
pluginInstallDialog.icon = StandardIcon.Information;
}
else if (result.status == "duplicate")
{
pluginInstallDialog.icon = StandardIcon.Warning;
}
else
{
pluginInstallDialog.icon = StandardIcon.Critical;
}
pluginInstallDialog.open();
return;
}
}
openDialog.handleOpenFileUrls(drop.urls);
}
}
@ -669,9 +644,9 @@ UM.MainWindow
// show the plugin browser dialog
Connections
{
target: Cura.Actions.browsePlugins
target: Cura.Actions.browsePackages
onTriggered: {
curaExtensions.callExtensionMethod("Plugin Browser", "browsePlugins")
curaExtensions.callExtensionMethod("Toolbox", "browsePackages")
}
}
@ -814,14 +789,6 @@ UM.MainWindow
}
}
MessageDialog
{
id: pluginInstallDialog
title: catalog.i18nc("@window:title", "Install Plugin");
standardButtons: StandardButton.Ok
modality: Qt.ApplicationModal
}
MessageDialog {
id: infoMultipleFilesWithGcodeDialog
title: catalog.i18nc("@title:window", "Open File(s)")

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_pri3

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Extra Fine
definition = abax_pri3

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_pri3

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_pri5

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Extra Fine
definition = abax_pri5

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_pri5

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_titan

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Extra Fine
definition = abax_titan

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Fine
definition = abax_titan

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Draft
definition = anycubic_i3_mega

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High
definition = anycubic_i3_mega

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = anycubic_i3_mega

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High Quality
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = builder_premium_small

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Coarse
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Extra Coarse
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = High
definition = cartesio

View file

@ -1,5 +1,5 @@
[general]
version = 3
version = 4
name = Normal
definition = cartesio

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