Merge pull request #1243 from fieldOfView/feature_material_cost

Implement material cost
This commit is contained in:
Ghostkeeper 2017-01-24 15:54:57 +01:00 committed by GitHub
commit a8efde9450
6 changed files with 242 additions and 54 deletions

View file

@ -223,6 +223,10 @@ class CuraApplication(QtApplication):
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
Preferences.getInstance().addPreference("cura/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}")
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",

View file

@ -7,12 +7,14 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
from UM.Settings import ContainerRegistry
import cura.Settings.ExtruderManager
import math
import os.path
import unicodedata
import json
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -52,6 +54,7 @@ class PrintInformation(QObject):
self._material_lengths = []
self._material_weights = []
self._material_costs = []
self._pre_sliced = False
@ -65,6 +68,12 @@ class PrintInformation(QObject):
Application.getInstance().globalContainerStackChanged.connect(self._setAbbreviatedMachineName)
Application.getInstance().fileLoaded.connect(self.setJobName)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._active_material_container = None
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged)
self._onActiveMaterialChanged()
currentPrintTimeChanged = pyqtSignal()
preSlicedChanged = pyqtSignal()
@ -93,28 +102,82 @@ class PrintInformation(QObject):
def materialWeights(self):
return self._material_weights
materialCostsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialCostsChanged)
def materialCosts(self):
return self._material_costs
def _onPrintDurationMessage(self, total_time, material_amounts):
self._current_print_time.setDuration(total_time)
self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts
self._calculateInformation()
def _calculateInformation(self):
# Material amount is sent as an amount of mm^3, so calculate length from that
r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
self._material_lengths = []
self._material_weights = []
self._material_costs = []
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
extruder_stacks = list(cura.Settings.ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId()))
for index, amount in enumerate(material_amounts):
for index, amount in enumerate(self._material_amounts):
## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
# list comprehension filtering to solve this for us.
material = None
if extruder_stacks: # Multi extrusion machine
extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0]
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
material = extruder_stack.findContainer({"type": "material"})
else: # Machine with no extruder stacks
density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0)
material = Application.getInstance().getGlobalContainerStack().findContainer({"type": "material"})
self._material_weights.append(float(amount) * float(density) / 1000)
weight = float(amount) * float(density) / 1000
cost = 0
if material:
material_guid = material.getMetaDataEntry("GUID")
if material_guid in material_preference_values:
material_values = material_preference_values[material_guid]
weight_per_spool = float(material_values["spool_weight"] if material_values and "spool_weight" in material_values else 0)
cost_per_spool = float(material_values["spool_cost"] if material_values and "spool_cost" in material_values else 0)
if weight_per_spool != 0:
cost = cost_per_spool * weight / weight_per_spool
else:
cost = 0
self._material_weights.append(weight)
self._material_lengths.append(round((amount / (math.pi * r ** 2)) / 1000, 2))
self._material_costs.append(cost)
self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
self.materialCostsChanged.emit()
def _onPreferencesChanged(self, preference):
if preference != "cura/material_settings":
return
self._calculateInformation()
def _onActiveMaterialChanged(self):
if self._active_material_container:
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
self._active_material_container = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)[0]
if self._active_material_container:
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
def _onMaterialMetaDataChanged(self):
self._calculateInformation()
@pyqtSlot(str)
def setJobName(self, name):

View file

@ -26,6 +26,7 @@ Rectangle {
property variant printDuration: PrintInformation.currentPrintTime
property variant printMaterialLengths: PrintInformation.materialLengths
property variant printMaterialWeights: PrintInformation.materialWeights
property variant printMaterialCosts: PrintInformation.materialCosts
height: childrenRect.height
color: "transparent"
@ -133,7 +134,8 @@ Rectangle {
}
}
Label{
Label
{
id: boundingSpec
anchors.top: jobNameRow.bottom
anchors.right: parent.right
@ -144,17 +146,20 @@ Rectangle {
text: Printer.getSceneBoundingBoxString
}
Rectangle {
Rectangle
{
id: specsRow
anchors.top: boundingSpec.bottom
anchors.right: parent.right
height: UM.Theme.getSize("jobspecs_line").height
Item{
Item
{
width: parent.width
height: parent.height
UM.RecolorImage {
UM.RecolorImage
{
id: timeIcon
anchors.right: timeSpec.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width/2
@ -166,7 +171,8 @@ Rectangle {
color: UM.Theme.getColor("text_subtext")
source: UM.Theme.getIcon("print_time")
}
Label{
Label
{
id: timeSpec
anchors.right: lengthIcon.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
@ -175,7 +181,8 @@ Rectangle {
color: UM.Theme.getColor("text_subtext")
text: (!base.printDuration || !base.printDuration.valid) ? catalog.i18nc("@label", "00h 00min") : base.printDuration.getDisplayString(UM.DurationFormat.Short)
}
UM.RecolorImage {
UM.RecolorImage
{
id: lengthIcon
anchors.right: lengthSpec.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width/2
@ -187,7 +194,8 @@ Rectangle {
color: UM.Theme.getColor("text_subtext")
source: UM.Theme.getIcon("category_material")
}
Label{
Label
{
id: lengthSpec
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@ -197,21 +205,40 @@ Rectangle {
{
var lengths = [];
var weights = [];
var costs = [];
var someCostsKnown = false;
if(base.printMaterialLengths) {
for(var index = 0; index < base.printMaterialLengths.length; index++) {
if(base.printMaterialLengths[index] > 0) {
for(var index = 0; index < base.printMaterialLengths.length; index++)
{
if(base.printMaterialLengths[index] > 0)
{
lengths.push(base.printMaterialLengths[index].toFixed(2));
weights.push(String(Math.floor(base.printMaterialWeights[index])));
costs.push(base.printMaterialCosts[index].toFixed(2));
if(base.printMaterialCosts[index] > 0)
{
someCostsKnown = true;
}
}
}
if(lengths.length == 0) {
}
if(lengths.length == 0)
{
lengths = ["0.00"];
weights = ["0"];
costs = ["0.00"];
}
if(someCostsKnown)
{
return catalog.i18nc("@label", "%1 m / ~ %2 g / ~ %4 %3").arg(lengths.join(" + "))
.arg(weights.join(" + ")).arg(costs.join(" + ")).arg(UM.Preferences.getValue("cura/currency"));
}
else
{
return catalog.i18nc("@label", "%1 m / ~ %2 g").arg(lengths.join(" + ")).arg(weights.join(" + "));
}
}
}
}
}
}

View file

@ -129,6 +129,19 @@ UM.PreferencesPage
currentIndex -= 1;
}
}
Label
{
id: currencyLabel
text: catalog.i18nc("@label","Currency:")
anchors.verticalCenter: languageComboBox.verticalCenter
}
TextField
{
id: currencyField
text: UM.Preferences.getValue("cura/currency")
onTextChanged: UM.Preferences.setValue("cura/currency", text)
}
}
Label

View file

@ -15,14 +15,19 @@ TabView
property QtObject properties;
property bool editingEnabled: false;
property string currency: UM.Preferences.getValue("general/currency") ? UM.Preferences.getValue("general/currency") : "€"
property string currency: UM.Preferences.getValue("cura/currency") ? UM.Preferences.getValue("cura/currency") : "€"
property real firstColumnWidth: width * 0.45
property real secondColumnWidth: width * 0.45
property string containerId: ""
property var materialPreferenceValues: UM.Preferences.getValue("cura/material_settings") ? JSON.parse(UM.Preferences.getValue("cura/material_settings")) : {}
property double spoolLength: calculateSpoolLength()
property real costPerMeter: calculateCostPerMeter()
Tab
{
title: catalog.i18nc("@title","Information")
anchors
{
leftMargin: UM.Theme.getSize("default_margin").width
@ -35,6 +40,7 @@ TabView
{
anchors.fill: parent
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
flickableItem.flickableDirection: Flickable.VerticalFlick
Flow
{
@ -112,64 +118,78 @@ TabView
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") }
ReadOnlySpinBox
{
width: base.secondColumnWidth;
value: properties.density;
id: densitySpinBox
width: base.secondColumnWidth
value: properties.density
decimals: 2
suffix: " g/cm³"
stepSize: 0.01
readOnly: !base.editingEnabled;
readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("properties/density", properties.density, value)
onValueChanged: updateCostPerMeter()
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") }
ReadOnlySpinBox
{
width: base.secondColumnWidth;
value: properties.diameter;
id: diameterSpinBox
width: base.secondColumnWidth
value: properties.diameter
decimals: 2
suffix: " mm"
stepSize: 0.01
readOnly: !base.editingEnabled;
readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("properties/diameter", properties.diameter, value)
onValueChanged: updateCostPerMeter()
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") }
SpinBox
{
width: base.secondColumnWidth;
value: properties.spool_cost;
prefix: base.currency
enabled: false
id: spoolCostSpinBox
width: base.secondColumnWidth
value: base.getMaterialPreferenceValue(properties.guid, "spool_cost")
prefix: base.currency + " "
decimals: 2
maximumValue: 1000
onEditingFinished: base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value))
onValueChanged: updateCostPerMeter()
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") }
SpinBox
{
width: base.secondColumnWidth;
value: properties.spool_weight;
suffix: "g";
stepSize: 10
enabled: false
id: spoolWeightSpinBox
width: base.secondColumnWidth
value: base.getMaterialPreferenceValue(properties.guid, "spool_weight")
suffix: " g"
stepSize: 100
decimals: 0
maximumValue: 10000
onEditingFinished: base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value))
onValueChanged: updateCostPerMeter()
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") }
SpinBox
Label
{
width: base.secondColumnWidth;
value: parseFloat(properties.spool_length);
suffix: "m";
enabled: false
width: base.secondColumnWidth
text: "~ %1 m".arg(Math.round(base.spoolLength))
verticalAlignment: Qt.AlignVCenter
height: parent.rowHeight
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") }
SpinBox
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter") }
Label
{
width: base.secondColumnWidth;
value: parseFloat(properties.cost_per_meter);
suffix: catalog.i18nc("@label", "%1/m".arg(base.currency));
enabled: false
width: base.secondColumnWidth
text: "~ %1 %2/m".arg(base.costPerMeter.toFixed(2)).arg(base.currency)
verticalAlignment: Qt.AlignVCenter
height: parent.rowHeight
}
Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
@ -200,6 +220,12 @@ TabView
onEditingFinished: base.setMetaDataEntry("adhesion_info", properties.adhesion_info, text)
}
}
function updateCostPerMeter()
{
base.spoolLength = calculateSpoolLength(diameterSpinBox.value, densitySpinBox.value, spoolWeightSpinBox.value);
base.costPerMeter = calculateCostPerMeter(spoolCostSpinBox.value);
}
}
}
@ -259,6 +285,44 @@ TabView
}
}
function calculateSpoolLength(diameter, density, spoolWeight)
{
if(!diameter)
{
diameter = properties.diameter;
}
if(!density)
{
density = properties.density;
}
if(!spoolWeight)
{
spoolWeight = base.getMaterialPreferenceValue(properties.guid, "spool_weight");
}
if (diameter == 0 || density == 0 || spoolWeight == 0)
{
return 0;
}
var area = Math.PI * Math.pow(diameter / 2, 2); // in mm2
var volume = (spoolWeight / density); // in cm3
return volume / area; // in m
}
function calculateCostPerMeter(spoolCost)
{
if(!spoolCost)
{
spoolCost = base.getMaterialPreferenceValue(properties.guid, "spool_cost");
}
if (spoolLength == 0)
{
return 0;
}
return spoolCost / spoolLength;
}
// Tiny convenience function to check if a value really changed before trying to set it.
function setMetaDataEntry(entry_name, old_value, new_value)
{
@ -268,6 +332,32 @@ TabView
}
}
function setMaterialPreferenceValue(material_guid, entry_name, new_value)
{
if(!(material_guid in materialPreferenceValues))
{
materialPreferenceValues[material_guid] = {};
}
if(entry_name in materialPreferenceValues[material_guid] && materialPreferenceValues[material_guid][entry_name] == new_value)
{
// value has not changed
return
}
materialPreferenceValues[material_guid][entry_name] = new_value;
// store preference
UM.Preferences.setValue("cura/material_settings", JSON.stringify(materialPreferenceValues));
}
function getMaterialPreferenceValue(material_guid, entry_name)
{
if(material_guid in materialPreferenceValues && entry_name in materialPreferenceValues[material_guid])
{
return materialPreferenceValues[material_guid][entry_name];
}
return 0;
}
function setName(old_value, new_value)
{
if(old_value != new_value)

View file

@ -185,17 +185,6 @@ UM.ManagementPage
height: childrenRect.height
Label { text: materialProperties.name; font: UM.Theme.getFont("large"); }
Button
{
id: editButton
anchors.right: parent.right;
text: catalog.i18nc("@action:button", "Edit");
iconName: "document-edit";
enabled: base.currentItem != null && !base.currentItem.readOnly
checkable: enabled
}
}
MaterialView
@ -209,7 +198,7 @@ UM.ManagementPage
bottom: parent.bottom
}
editingEnabled: editButton.checkable && editButton.checked;
editingEnabled: base.currentItem != null && !base.currentItem.readOnly
properties: materialProperties
containerId: base.currentItem != null ? base.currentItem.id : ""
@ -219,6 +208,7 @@ UM.ManagementPage
{
id: materialProperties
property string guid: "00000000-0000-0000-0000-000000000000"
property string name: "Unknown";
property string profile_type: "Unknown";
property string supplier: "Unknown";
@ -344,6 +334,7 @@ UM.ManagementPage
return
}
materialProperties.name = currentItem.name;
materialProperties.guid = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "GUID");
if(currentItem.metadata != undefined && currentItem.metadata != null)
{