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("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True) Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False) Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
Preferences.getInstance().addPreference("cura/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}")
for key in [ for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path", "dialog_profile_path",

View file

@ -7,12 +7,14 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application from UM.Application import Application
from UM.Qt.Duration import Duration from UM.Qt.Duration import Duration
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Settings import ContainerRegistry
import cura.Settings.ExtruderManager import cura.Settings.ExtruderManager
import math import math
import os.path import os.path
import unicodedata import unicodedata
import json
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -52,6 +54,7 @@ class PrintInformation(QObject):
self._material_lengths = [] self._material_lengths = []
self._material_weights = [] self._material_weights = []
self._material_costs = []
self._pre_sliced = False self._pre_sliced = False
@ -65,6 +68,12 @@ class PrintInformation(QObject):
Application.getInstance().globalContainerStackChanged.connect(self._setAbbreviatedMachineName) Application.getInstance().globalContainerStackChanged.connect(self._setAbbreviatedMachineName)
Application.getInstance().fileLoaded.connect(self.setJobName) 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() currentPrintTimeChanged = pyqtSignal()
preSlicedChanged = pyqtSignal() preSlicedChanged = pyqtSignal()
@ -93,28 +102,82 @@ class PrintInformation(QObject):
def materialWeights(self): def materialWeights(self):
return self._material_weights return self._material_weights
materialCostsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialCostsChanged)
def materialCosts(self):
return self._material_costs
def _onPrintDurationMessage(self, total_time, material_amounts): def _onPrintDurationMessage(self, total_time, material_amounts):
self._current_print_time.setDuration(total_time) self._current_print_time.setDuration(total_time)
self.currentPrintTimeChanged.emit() 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 # Material amount is sent as an amount of mm^3, so calculate length from that
r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2 r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
self._material_lengths = [] self._material_lengths = []
self._material_weights = [] 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())) 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 ## 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. # list comprehension filtering to solve this for us.
material = None
if extruder_stacks: # Multi extrusion machine if extruder_stacks: # Multi extrusion machine
extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0] extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0]
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0) density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
material = extruder_stack.findContainer({"type": "material"})
else: # Machine with no extruder stacks else: # Machine with no extruder stacks
density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0) 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_lengths.append(round((amount / (math.pi * r ** 2)) / 1000, 2))
self._material_costs.append(cost)
self.materialLengthsChanged.emit() self.materialLengthsChanged.emit()
self.materialWeightsChanged.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) @pyqtSlot(str)
def setJobName(self, name): def setJobName(self, name):

View file

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

View file

@ -129,6 +129,19 @@ UM.PreferencesPage
currentIndex -= 1; 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 Label

View file

@ -15,14 +15,19 @@ TabView
property QtObject properties; property QtObject properties;
property bool editingEnabled: false; 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 firstColumnWidth: width * 0.45
property real secondColumnWidth: width * 0.45 property real secondColumnWidth: width * 0.45
property string containerId: "" 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 Tab
{ {
title: catalog.i18nc("@title","Information") title: catalog.i18nc("@title","Information")
anchors anchors
{ {
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
@ -35,6 +40,7 @@ TabView
{ {
anchors.fill: parent anchors.fill: parent
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
flickableItem.flickableDirection: Flickable.VerticalFlick
Flow Flow
{ {
@ -112,64 +118,78 @@ TabView
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Density") }
ReadOnlySpinBox ReadOnlySpinBox
{ {
width: base.secondColumnWidth; id: densitySpinBox
value: properties.density; width: base.secondColumnWidth
value: properties.density
decimals: 2 decimals: 2
suffix: "g/cm³" suffix: " g/cm³"
stepSize: 0.01 stepSize: 0.01
readOnly: !base.editingEnabled; readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("properties/density", properties.density, value) 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") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Diameter") }
ReadOnlySpinBox ReadOnlySpinBox
{ {
width: base.secondColumnWidth; id: diameterSpinBox
value: properties.diameter; width: base.secondColumnWidth
value: properties.diameter
decimals: 2 decimals: 2
suffix: "mm" suffix: " mm"
stepSize: 0.01 stepSize: 0.01
readOnly: !base.editingEnabled; readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("properties/diameter", properties.diameter, value) 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") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament Cost") }
SpinBox SpinBox
{ {
width: base.secondColumnWidth; id: spoolCostSpinBox
value: properties.spool_cost; width: base.secondColumnWidth
prefix: base.currency value: base.getMaterialPreferenceValue(properties.guid, "spool_cost")
enabled: false 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") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") }
SpinBox SpinBox
{ {
width: base.secondColumnWidth; id: spoolWeightSpinBox
value: properties.spool_weight; width: base.secondColumnWidth
suffix: "g"; value: base.getMaterialPreferenceValue(properties.guid, "spool_weight")
stepSize: 10 suffix: " g"
enabled: false 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") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") }
SpinBox Label
{ {
width: base.secondColumnWidth; width: base.secondColumnWidth
value: parseFloat(properties.spool_length); text: "~ %1 m".arg(Math.round(base.spoolLength))
suffix: "m"; verticalAlignment: Qt.AlignVCenter
enabled: false height: parent.rowHeight
} }
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter (Approx.)") } Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Cost per Meter") }
SpinBox Label
{ {
width: base.secondColumnWidth; width: base.secondColumnWidth
value: parseFloat(properties.cost_per_meter); text: "~ %1 %2/m".arg(base.costPerMeter.toFixed(2)).arg(base.currency)
suffix: catalog.i18nc("@label", "%1/m".arg(base.currency)); verticalAlignment: Qt.AlignVCenter
enabled: false height: parent.rowHeight
} }
Item { width: parent.width; height: UM.Theme.getSize("default_margin").height } 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) 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. // Tiny convenience function to check if a value really changed before trying to set it.
function setMetaDataEntry(entry_name, old_value, new_value) 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) function setName(old_value, new_value)
{ {
if(old_value != new_value) if(old_value != new_value)

View file

@ -185,17 +185,6 @@ UM.ManagementPage
height: childrenRect.height height: childrenRect.height
Label { text: materialProperties.name; font: UM.Theme.getFont("large"); } 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 MaterialView
@ -209,7 +198,7 @@ UM.ManagementPage
bottom: parent.bottom bottom: parent.bottom
} }
editingEnabled: editButton.checkable && editButton.checked; editingEnabled: base.currentItem != null && !base.currentItem.readOnly
properties: materialProperties properties: materialProperties
containerId: base.currentItem != null ? base.currentItem.id : "" containerId: base.currentItem != null ? base.currentItem.id : ""
@ -219,6 +208,7 @@ UM.ManagementPage
{ {
id: materialProperties id: materialProperties
property string guid: "00000000-0000-0000-0000-000000000000"
property string name: "Unknown"; property string name: "Unknown";
property string profile_type: "Unknown"; property string profile_type: "Unknown";
property string supplier: "Unknown"; property string supplier: "Unknown";
@ -344,6 +334,7 @@ UM.ManagementPage
return return
} }
materialProperties.name = currentItem.name; materialProperties.name = currentItem.name;
materialProperties.guid = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "GUID");
if(currentItem.metadata != undefined && currentItem.metadata != null) if(currentItem.metadata != undefined && currentItem.metadata != null)
{ {