Merge branch 'master' into WIP_improve_initialization

This commit is contained in:
Diego Prado Gesto 2018-05-14 15:15:02 +02:00 committed by GitHub
commit 8ad409ff55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 1810 additions and 778 deletions

View file

@ -15,6 +15,7 @@ from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
@ -25,6 +26,15 @@ from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["curaproject.3mf"]
)
)
try:
if not MYPY:
import xml.etree.cElementTree as ET

View file

@ -66,8 +66,8 @@ Generate a cube mesh to prevent support material generation in specific areas of
*Real bridging - smartavionics
New experimental feature that detects bridges, adjusting the print speed, slow and fan speed to enhance print quality on bridging parts.
*Updated CuraEngine executable - thopiekar
The CuraEngine executable now contains a dedicated icon, author information and a license.
*Updated CuraEngine executable - thopiekar & Ultimaker B.V. ❤️
The CuraEngine executable contains a dedicated icon, author and license info on Windows now. The icon has been designed by Ultimaker B.V.
*Use RapidJSON and ClipperLib from system libraries
Application updated to use verified copies of libraries, reducing maintenance time keeping them up to date (the operating system is now responsible), as well as reducing the amount of code shipped (as necessary code is already on the users system).

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
@ -22,12 +22,16 @@ from cura.Settings.ExtruderManager import ExtruderManager
import numpy
import math
import re
from typing import Dict, List, NamedTuple, Optional, Union
from collections import namedtuple
# This parser is intented for interpret the common firmware codes among all the different flavors
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", float)])
## This parser is intended to interpret the common firmware codes among all the
# different flavors
class FlavorParser:
def __init__(self):
def __init__(self) -> None:
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
self._cancelled = False
self._message = None
@ -44,19 +48,18 @@ class FlavorParser:
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
def _clearValues(self):
def _clearValues(self) -> None:
self._extruder_number = 0
self._extrusion_length_offset = [0]
self._layer_type = LayerPolygon.Inset0Type
self._layer_number = 0
self._previous_z = 0
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
self._center_is_zero = False
self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91)
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
@staticmethod
def _getValue(line, code):
def _getValue(line: str, code: str) -> Optional[Union[str, int, float]]:
n = line.find(code)
if n < 0:
return None
@ -71,29 +74,29 @@ class FlavorParser:
except:
return None
def _getInt(self, line, code):
def _getInt(self, line: str, code: str) -> Optional[int]:
value = self._getValue(line, code)
try:
return int(value)
except:
return None
def _getFloat(self, line, code):
def _getFloat(self, line: str, code: str) -> Optional[float]:
value = self._getValue(line, code)
try:
return float(value)
except:
return None
def _onHideMessage(self, message):
def _onHideMessage(self, message: str) -> None:
if message == self._message:
self._cancelled = True
@staticmethod
def _getNullBoundingBox():
def _getNullBoundingBox() -> AxisAlignedBox:
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
def _createPolygon(self, layer_thickness, path, extruder_offsets):
def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool:
countvalid = 0
for point in path:
if point[5] > 0:
@ -139,12 +142,12 @@ class FlavorParser:
this_layer.polygons.append(this_poly)
return True
def _createEmptyLayer(self, layer_number):
def _createEmptyLayer(self, layer_number: int) -> None:
self._layer_data_builder.addLayer(layer_number)
self._layer_data_builder.setLayerHeight(layer_number, 0)
self._layer_data_builder.setLayerThickness(layer_number, 0)
def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness):
def _calculateLineWidth(self, current_point: Position, previous_point: Position, current_extrusion: float, previous_extrusion: float, layer_thickness: float) -> float:
# Area of the filament
Af = (self._filament_diameter / 2) ** 2 * numpy.pi
# Length of the extruded filament
@ -166,7 +169,7 @@ class FlavorParser:
return 0.35
return line_width
def _gCode0(self, position, params, path):
def _gCode0(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
x, y, z, f, e = position
if self._is_absolute_positioning:
@ -202,7 +205,7 @@ class FlavorParser:
_gCode1 = _gCode0
## Home the head.
def _gCode28(self, position, params, path):
def _gCode28(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
@ -211,20 +214,20 @@ class FlavorParser:
position.e)
## Set the absolute positioning
def _gCode90(self, position, params, path):
def _gCode90(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
self._is_absolute_positioning = True
self._is_absolute_extrusion = True
return position
## Set the relative positioning
def _gCode91(self, position, params, path):
def _gCode91(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
self._is_absolute_positioning = False
self._is_absolute_extrusion = False
return position
## Reset the current position to the values specified.
# For example: G92 X10 will set the X to 10 without any physical motion.
def _gCode92(self, position, params, path):
def _gCode92(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
if params.e is not None:
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
@ -236,7 +239,7 @@ class FlavorParser:
params.f if params.f is not None else position.f,
position.e)
def processGCode(self, G, line, position, path):
def processGCode(self, G: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
func = getattr(self, "_gCode%s" % G, None)
line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None:
@ -257,27 +260,25 @@ class FlavorParser:
f = float(item[1:]) / 60
if item[0] == "E":
e = float(item[1:])
if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)):
self._center_is_zero = True
params = self._position(x, y, z, f, e)
return func(position, params, path)
return position
def processTCode(self, T, line, position, path):
def processTCode(self, T: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
self._extruder_number = T
if self._extruder_number + 1 > len(position.e):
self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1))
position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
return position
def processMCode(self, M, line, position, path):
def processMCode(self, M: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
pass
_type_keyword = ";TYPE:"
_layer_keyword = ";LAYER:"
## For showing correct x, y offsets for each extruder
def _extruderOffsets(self):
def _extruderOffsets(self) -> Dict[int, List[float]]:
result = {}
for extruder in ExtruderManager.getInstance().getExtruderStacks():
result[int(extruder.getMetaData().get("position", "0"))] = [
@ -285,7 +286,7 @@ class FlavorParser:
extruder.getProperty("machine_nozzle_offset_y", "value")]
return result
def processGCodeStream(self, stream):
def processGCodeStream(self, stream: str) -> Optional[CuraSceneNode]:
Logger.log("d", "Preparing to load GCode")
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
@ -453,10 +454,9 @@ class FlavorParser:
Logger.log("w", "File doesn't contain any valid layers")
settings = Application.getInstance().getGlobalContainerStack()
machine_width = settings.getProperty("machine_width", "value")
machine_depth = settings.getProperty("machine_depth", "value")
if not self._center_is_zero:
if not settings.getProperty("machine_center_is_zero", "value"):
machine_width = settings.getProperty("machine_width", "value")
machine_depth = settings.getProperty("machine_depth", "value")
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
Logger.log("d", "GCode loading finished")

View file

@ -5,10 +5,20 @@ from UM.FileHandler.FileReader import FileReader
from UM.Mesh.MeshReader import MeshReader
from UM.i18n import i18nCatalog
from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura")
from . import MarlinFlavorParser, RepRapFlavorParser
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-gcode-file",
comment = "Cura GCode File",
suffixes = ["gcode", "gcode.gz"]
)
)
# Class for loading and parsing G-code files
class GCodeReader(MeshReader):

View file

@ -66,9 +66,9 @@ class GCodeWriter(MeshWriter):
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
gcode_dict = getattr(scene, "gcode_dict")
if not gcode_dict:
if not hasattr(scene, "gcode_dict"):
return False
gcode_dict = getattr(scene, "gcode_dict")
gcode_list = gcode_dict.get(active_build_plate, None)
if gcode_list is not None:
has_settings = False

View file

@ -56,8 +56,6 @@ class MachineSettingsAction(MachineAction):
if self._isEmptyDefinitionChanges(definition_changes_id):
return
self._container_registry.removeContainer(definition_changes_id)
def _reset(self):
if not self._global_container_stack:
return

View file

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

View file

@ -12,11 +12,14 @@ Window
property var selection: null
title: catalog.i18nc("@title", "Toolbox")
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
width: 720 * screenScaleFactor
height: 640 * screenScaleFactor
minimumWidth: 720 * screenScaleFactor
maximumWidth: 720 * screenScaleFactor
minimumHeight: 350 * screenScaleFactor
minimumWidth: width
maximumWidth: minimumWidth
minimumHeight: height
maximumHeight: minimumHeight
color: UM.Theme.getColor("sidebar")
UM.I18nCatalog
{
@ -76,7 +79,7 @@ Window
{
id: footer
visible: toolbox.restartRequired
height: toolbox.restartRequired ? UM.Theme.getSize("toolbox_footer").height : 0
height: visible ? UM.Theme.getSize("toolbox_footer").height : 0
}
// TODO: Clean this up:
Connections

View file

@ -0,0 +1,29 @@
// 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
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: Label
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -9,7 +9,7 @@ import UM 1.1 as UM
Item
{
id: page
property var details: base.selection
property var details: base.selection || {}
anchors.fill: parent
ToolboxBackColumn
{
@ -32,6 +32,7 @@ Item
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
fillMode: Image.PreserveAspectFit
source: details.icon_url || "../images/logobot.svg"
mipmap: true
anchors
{
top: parent.top
@ -53,7 +54,7 @@ Item
rightMargin: UM.Theme.getSize("wide_margin").width
bottomMargin: UM.Theme.getSize("default_margin").height
}
text: details.name
text: details.name || ""
font: UM.Theme.getFont("large")
wrapMode: Text.WordWrap
width: parent.width
@ -62,7 +63,7 @@ Item
Label
{
id: description
text: details.description
text: details.description || ""
anchors
{
top: title.bottom
@ -114,6 +115,7 @@ Item
}
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
}
}

View file

@ -8,6 +8,7 @@ import UM 1.1 as UM
Item
{
property var packageData
anchors.topMargin: UM.Theme.getSize("default_margin").height
height: visible ? childrenRect.height : 0
visible: packageData.type == "material" && packageData.has_configs
@ -36,8 +37,8 @@ Item
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: styleData.elideMode
text: styleData.value
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default_bold")
}
@ -55,8 +56,8 @@ Item
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: styleData.elideMode
text: styleData.value
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}
@ -67,8 +68,8 @@ Item
Label
{
anchors.verticalCenter: parent.verticalCenter
elide: styleData.elideMode
text: styleData.value
elide: Text.ElideRight
text: styleData.value || ""
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -33,6 +33,7 @@ Item
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
fillMode: Image.PreserveAspectFit
source: details.icon_url || "../images/logobot.svg"
mipmap: true
anchors
{
top: parent.top
@ -54,8 +55,9 @@ Item
rightMargin: UM.Theme.getSize("wide_margin").width
bottomMargin: UM.Theme.getSize("default_margin").height
}
text: details.name
text: details.name || ""
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
@ -113,7 +115,7 @@ Item
text:
{
var date = new Date(details.last_updated)
return date.toLocaleString(Qt.locale())
return date.toLocaleString(UM.Preferences.getValue("general/language"))
}
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
@ -133,6 +135,7 @@ Item
}
font: UM.Theme.getFont("very_small")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
onLinkActivated: Qt.openUrlExternally(link)
}
}

View file

@ -10,9 +10,8 @@ Item
{
id: tile
property bool installed: toolbox.isInstalled(model.id)
property var packageData: model
width: detailList.width - UM.Theme.getSize("wide_margin").width
height: Math.max(UM.Theme.getSize("toolbox_detail_tile").height, childrenRect.height + UM.Theme.getSize("default_margin").height)
height: normalData.height + compatibilityChart.height + 4 * UM.Theme.getSize("default_margin").height
Item
{
id: normalData
@ -46,6 +45,7 @@ Item
font: UM.Theme.getFont("default")
}
}
Item
{
id: controls
@ -76,58 +76,9 @@ Item
}
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) //Don't allow installing while another download is running.
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")
}
}
property alias installed: tile.installed
style: UM.Theme.styles.toolbox_action_button
onClicked:
{
if (installed)
@ -164,6 +115,7 @@ Item
id: compatibilityChart
anchors.top: normalData.bottom
width: normalData.width
packageData: model
}
Rectangle

View file

@ -9,9 +9,7 @@ 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
height: childrenRect.height
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
Label
@ -36,6 +34,7 @@ Column
delegate: ToolboxDownloadsGridTile
{
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns
Layout.preferredHeight: UM.Theme.getSize("toolbox_thumbnail_small").height
}
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
@ -34,10 +34,11 @@ Item
Image
{
anchors.centerIn: parent
width: UM.Theme.getSize("toolbox_thumbnail_small").width - 26
height: UM.Theme.getSize("toolbox_thumbnail_small").height - 26
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
fillMode: Image.PreserveAspectFit
source: model.icon_url || "../images/logobot.svg"
mipmap: true
}
}
Column

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
@ -9,7 +9,7 @@ import UM 1.1 as UM
Item
{
width: UM.Theme.getSize("toolbox_thumbnail_large").width
height: childrenRect.height
height: thumbnail.height + packageName.height
Rectangle
{
id: highlight
@ -40,14 +40,16 @@ Item
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"
mipmap: true
}
}
Label
{
id: packageName
text: model.name
anchors
{
bottom: parent.bottom
top: thumbnail.bottom
horizontalCenter: parent.horizontalCenter
}
verticalAlignment: Text.AlignVCenter

View file

@ -14,8 +14,7 @@ Item
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.")
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
verticalAlignment: Text.AlignVCenter
anchors
@ -38,7 +37,6 @@ Item
right: parent.right
rightMargin: UM.Theme.getSize("wide_margin").width
}
visible: toolbox.restartRequired
iconName: "dialog-restart"
onClicked: toolbox.restart()
style: ButtonStyle
@ -49,7 +47,7 @@ Item
implicitHeight: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
}
label: Text
label: Label
{
color: UM.Theme.getColor("button_text")
font: UM.Theme.getFont("default_bold")
@ -61,7 +59,7 @@ Item
}
ToolboxShadow
{
visible: toolbox.restartRequired
visible: footer.visible
anchors.bottom: footer.top
reversed: true
}

View file

@ -24,7 +24,8 @@ Item
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Plugins")
active: toolbox.viewCategory == "plugin"
active: toolbox.viewCategory == "plugin" && enabled
enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
onClicked:
{
toolbox.filterModelByProp("packages", "type", "plugin")
@ -35,7 +36,8 @@ Item
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Materials")
active: toolbox.viewCategory == "material"
active: toolbox.viewCategory == "material" && enabled
enabled: toolbox.viewPage != "loading" && toolbox.viewPage != "errored"
onClicked:
{
toolbox.filterModelByProp("authors", "package_types", "material")

View file

@ -1,21 +1,18 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Item
{
height: UM.Theme.getSize("toolbox_installed_tile").height
width: parent.width
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")
@ -23,197 +20,86 @@ Item
height: UM.Theme.getSize("default_lining").height
anchors.bottom: parent.bottom
}
Column
Row
{
id: pluginInfo
property var color: isEnabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining")
id: tileRow
height: parent.height
anchors
width: parent.width
spacing: UM.Theme.getSize("default_margin").width
topPadding: UM.Theme.getSize("default_margin").height
CheckBox
{
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")
}
}
id: disableButton
checked: isEnabled
visible: model.type == "plugin"
width: visible ? UM.Theme.getSize("checkbox").width : 0
enabled: !toolbox.isDownloading
style: ButtonStyle
style: UM.Theme.styles.checkbox
onClicked: toolbox.isEnabled(model.id) ? toolbox.disable(model.id) : toolbox.enable(model.id)
}
Column
{
id: pluginInfo
topPadding: UM.Theme.getSize("default_margin").height / 2
property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text")
width: tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0))
Label
{
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
}
text: model.name
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold")
color: pluginInfo.color
}
onClicked:
Label
{
if (model.is_bundled)
text: model.description
maximumLineCount: 3
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
color: pluginInfo.color
}
}
Column
{
id: authorInfo
width: Math.floor(UM.Theme.getSize("toolbox_action_button").width * 1.25)
Label
{
text:
{
if (toolbox.isEnabled(model.id))
if (model.author_email)
{
toolbox.disable(model.id)
return "<a href=\"mailto:" + model.author_email + "?Subject=Cura: " + model.name + "\">" + model.author_name + "</a>"
}
else
{
toolbox.enable(model.id)
return model.author_name
}
}
else
{
toolbox.uninstall(model.id)
}
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")
linkColor: UM.Theme.getColor("text_link")
}
}
Button
ToolboxInstalledTileActions
{
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);
}
id: pluginActions
}
ProgressBar
Connections
{
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")
}
}
target: toolbox
onEnabledChanged: isEnabled = toolbox.isEnabled(model.id)
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
}
}
Connections
{
target: toolbox
onEnabledChanged: isEnabled = toolbox.isEnabled(model.id)
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
}
}
}

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.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.1 as UM
Column
{
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
Item
{
width: parent.width
height: childrenRect.height
visible: canUpdate
Button
{
id: updateButton
text: catalog.i18nc("@action:button", "Update")
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
width: parent.width
value: toolbox.isDownloading ? toolbox.downloadProgress : 0
visible: toolbox.isDownloading
style: ProgressBarStyle
{
background: Rectangle
{
color: "transparent"
implicitHeight: UM.Theme.getSize("toolbox_action_button").height
}
progress: Rectangle
{
// TODO Define a good color that fits the purpuse
color: "blue"
opacity: 0.5
}
}
}
}
Button
{
id: removeButton
text: catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled
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: Label
{
text: control.text
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
onClicked: toolbox.uninstall(model.id)
}
}

View file

@ -42,7 +42,7 @@ UM.Dialog
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true
text: licenseDialog.licenseContent
text: licenseDialog.licenseContent || ""
}
}
rightButtons:

View file

@ -25,7 +25,7 @@ Button
height: UM.Theme.getSize("sidebar_header_highlight").height
}
}
label: Text
label: Label
{
text: control.text
color:
@ -43,7 +43,7 @@ Button
return UM.Theme.getColor("topbar_button_text_inactive");
}
}
font: control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}

View file

@ -28,8 +28,9 @@ class PackagesModel(ListModel):
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, "has_configs")
self.addRoleName(Qt.UserRole + 15, "supported_configs")
self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 15, "has_configs")
self.addRoleName(Qt.UserRole + 16, "supported_configs")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
@ -52,20 +53,26 @@ class PackagesModel(ListModel):
configs_model = ConfigsModel()
configs_model.setConfigs(package["data"]["supported_configs"])
if "author_id" not in package["author"] or "display_name" not in package["author"]:
package["author"]["author_id"] = ""
package["author"]["display_name"] = ""
# raise Exception("Detected a package with malformed author data.")
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"],
"author_id": package["author"]["author_id"],
"author_name": package["author"]["display_name"],
"author_email": package["author"]["email"] if "email" in package["author"] else None,
"description": package["description"] if "description" in package else None,
"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,
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
"has_configs": has_configs,
"supported_configs": configs_model
})

View file

@ -13,19 +13,17 @@ from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkRepl
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
import cura
from cura.CuraApplication import CuraApplication
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
from .ConfigsModel import ConfigsModel
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) -> None:
@ -34,7 +32,7 @@ class Toolbox(QObject, Extension):
self._application = Application.getInstance()
self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._packages_version = self._plugin_registry.APIVersion
self._packages_version = self._getPackagesVersion()
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)
@ -66,22 +64,22 @@ class Toolbox(QObject, Extension):
# Data:
self._metadata = {
"authors": [],
"packages": [],
"plugins_showcase": [],
"plugins_installed": [],
"materials_showcase": [],
"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),
"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)
}
@ -154,6 +152,13 @@ class Toolbox(QObject, Extension):
def _onAppInitialized(self) -> None:
self._package_manager = Application.getInstance().getCuraPackageManager()
def _getPackagesVersion(self) -> int:
if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraPackagesVersion"):
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraPackagesVersion
@pyqtSlot()
def browsePackages(self) -> None:
# Create the network manager:
@ -244,7 +249,6 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def restart(self):
self._package_manager._removeAllScheduledPackages()
CuraApplication.getInstance().windowClosed()
# Checks
@ -326,8 +330,8 @@ class Toolbox(QObject, Extension):
# Handlers for Network Events
# --------------------------------------------------------------------------
def _onNetworkAccessibleChanged(self, accessible: int) -> None:
if accessible == 0:
def _onNetworkAccessibleChanged(self, network_accessibility: QNetworkAccessManager.NetworkAccessibility) -> None:
if network_accessibility == QNetworkAccessManager.NotAccessible:
self.resetDownload()
def _onRequestFinished(self, reply: QNetworkReply) -> None:
@ -347,50 +351,55 @@ class Toolbox(QObject, Extension):
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"))
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
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")
# 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)
except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for %s.", 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")
else:
self.setViewPage("errored")
self.resetDownload()
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
@ -403,7 +412,7 @@ class Toolbox(QObject, Extension):
if bytes_sent == bytes_total:
self.setIsDownloading(False)
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
# <ust not delete the temporary file on Windows
# 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
@ -450,7 +459,7 @@ class Toolbox(QObject, Extension):
self._active_package = package
self.activePackageChanged.emit()
@pyqtProperty("QVariantMap", fset = setActivePackage, notify = activePackageChanged)
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> Optional[Dict[str, Any]]:
return self._active_package

View file

@ -11,6 +11,16 @@ except ImportError:
from UM.i18n import i18nCatalog #To translate the file format description.
from UM.Mesh.MeshWriter import MeshWriter #For the binary mode flag.
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-stl-file",
comment = "Cura UFP File",
suffixes = ["ufp"]
)
)
i18n_catalog = i18nCatalog("cura")

View file

@ -188,14 +188,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
b"name": system_info["name"].encode("utf-8"),
b"address": address.encode("utf-8"),
b"firmware_version": system_info["firmware"].encode("utf-8"),
b"manual": b"true"
b"manual": b"true",
b"machine": str(system_info['hardware']["typeid"]).encode("utf-8")
}
if "hardware" in system_info and 'typeid' in system_info["hardware"]:
properties[b"machine"] = str(system_info['hardware']["typeid"]).encode("utf-8")
else:
properties[b"machine"] = system_info["variant"].encode("utf-8")
if has_cluster_capable_firmware:
# Cluster needs an additional request, before it's completed.
properties[b"incomplete"] = b"true"

View file

@ -1,7 +1,7 @@
{
"name": "UM3 Network Connection",
"author": "Ultimaker B.V.",
"description": "Manages network connections to Ultimaker 3 printers",
"description": "Manages network connections to Ultimaker 3 printers.",
"version": "1.0.0",
"api": 4,
"i18n-catalog": "cura"

View file

@ -2,7 +2,7 @@
"name": "Ultimaker machine actions",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
"api": 4,
"i18n-catalog": "cura"
}

View file

@ -2,7 +2,7 @@
"name": "UserAgreement",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Ask the user once if he/she agrees with our license",
"description": "Ask the user once if he/she agrees with our license.",
"api": 4,
"i18n-catalog": "cura"
}