From c00a2d19ec78abf419fa867036a13acf90cb2bae Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 22 Feb 2023 21:10:49 +0100 Subject: [PATCH] [Material] fix major bug of wrong decimals This PR fixes the long-standing major bug that for languages using ',' as decimal separator the material editor was useless. (bug no. 10 in https://forum.freecad.org/viewtopic.php?t=56912) the issue was that the value was handled as text. This PR: - using a spinbox instead if a text edit to modify numbers (has also the advantage to be able to change values by spinning) - sets min/max properly for all number data types - also avoid rounding artifacts when changing Float values --- src/Mod/Material/MaterialEditor.py | 99 ++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/src/Mod/Material/MaterialEditor.py b/src/Mod/Material/MaterialEditor.py index a3b05b6945..1b79561f75 100644 --- a/src/Mod/Material/MaterialEditor.py +++ b/src/Mod/Material/MaterialEditor.py @@ -25,6 +25,7 @@ __author__ = "Yorik van Havre, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" import os +import sys from PySide import QtCore, QtGui, QtSvg import FreeCAD @@ -617,6 +618,8 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): def __init__(self): "" + self.matproperty = "" + self.Value = "" super(MaterialsDelegate, self).__init__() def createEditor(self, parent, option, index): @@ -635,7 +638,7 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): row = index.row() PP = group.child(row, 0) - matproperty = PP.text().replace(" ", "") # remove spaces + self.matproperty = PP.text().replace(" ", "") # remove spaces TT = group.child(row, 2) if TT: @@ -644,9 +647,9 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): Type = "String" VV = group.child(row, 1) - Value = VV.text() + self.Value = VV.text() - editor = matProperWidget(parent, matproperty, Type, Value) + editor = matProperWidget(parent, self.matproperty, Type, self.Value) elif column == 0: if group.text() == "User defined": @@ -676,11 +679,39 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): lineEdit = editor.children()[1] item.setText(lineEdit.text()) + elif Type == "Float": + # avoid rounding artifacts + inputValue = float('%.6g' % editor.value()) + item.setText(str(inputValue)) + + elif Type == "Quantity": + if not hasattr(FreeCAD.Units, self.matproperty): + FreeCAD.Console.PrintError( + "Error: property '{}' is a quantity but has no unit defined\n" + .format(self.matproperty) + ) + return + # we must use the unit of the input value because the + # "Gui::QuantitySpinBox" uses e.g. for the density always the mm-based unit + # kg/mm^3, also when the input value is in kg/^3. + # E.g. when the input is e.g. "7875 kg/m^3" and we would pass "7875" as rawValue + # and "kg/m^3" as unit, we would get "7875 kg/mm^3" as result. If we try to be + # clever and input "7875e-6" as rawValue and "kg/m^3" as unit we get + # "7875e-6 kg/m^3" as result. If we input "7875e-6" as rawValue and "kg/mm^3" + # as unit we get also "7875e-6 kg/m^3" as result. + if not self.Value: + # for empty (not yet set properties) we use the matproperty unit + unit = getattr(FreeCAD.Units, self.matproperty) + quantity = FreeCAD.Units.Quantity(1, unit) + item.setText(str(editor.value()) + " " + quantity.getUserPreferred()[2]) + else: + # since we checked we have a quantity, we can use split() to get the unit + item.setText(str(editor.value()) + " " + self.Value.split()[1]) + else: super(MaterialsDelegate, self).setEditorData(editor, index) - # ************************************************************************************************ # ************************************************************************************************ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, @@ -715,9 +746,26 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, lineEdit.setText(Value) elif Type == "Quantity": - widget = ui.createWidget("Gui::InputField") + # We don't use a Gui::QuantitySpinBox widget because depending on the value, + # the unit might change. For some inputs there is no way to do this right, + # see the comment above in "def setEditorData". Moreover it is error-prone to provide + # a unit as in the Gui::QuantitySpinBox widget because users likely delete the unit or + # change it accidentally. It is therefore better not to display the unit while editing. + widget = ui.createWidget("Gui::DoubleSpinBox") + + if minimum is None: + widget.setMinimum(-1*sys.float_info.max) + else: + widget.setMinimum(minimum) + if maximum is None: + widget.setMaximum(sys.float_info.max) + else: + widget.setMaximum(maximum) + + # we must increase the digits before we can set the value + # 6 is sufficient as some metarial cards use e.g. "0.000011" + widget.setDecimals(6) - # set quantity if possible # for properties with an underscored number (vectorial values), # we must strip the part after the first underscore to obtain the bound unit # since in cardutils.py in def get_material_template @@ -727,30 +775,36 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, matpropertyNum = matproperty.rstrip('0123456789') matproperty = matpropertyNum - if hasattr(FreeCAD.Units, matproperty): - unit = getattr(FreeCAD.Units, matproperty) - quantity = FreeCAD.Units.Quantity(1, unit) - widget.setProperty("unit", quantity.getUserPreferred()[2]) - else: - FreeCAD.Console.PrintWarning( - "Not known unit for property: {}. Probably the Quantity does not have a unit.\n" - .format(matproperty) - ) + if Value: + widget.setValue(float(Value.split()[0])) elif Type == "Integer": widget = ui.createWidget("Gui::UIntSpinBox") + if minimum is None: + widget.setMinimum(0) + else: + widget.setMinimum(minimum) + if maximum is None: + widget.setMaximum(sys.maxsize) + else: + widget.setMaximum(maximum) elif Type == "Float": - widget = ui.createWidget("Gui::PrefDoubleSpinBox") + widget = ui.createWidget("Gui::DoubleSpinBox") # the magnetic permeability is the parameter for which many decimals matter # the most however, even for this, 6 digits are sufficient widget.setDecimals(6) # for almost all Float parameters of materials a step of 1 would be too large widget.setSingleStep(0.1) if minimum is None: - widget.setProperty("minimum", -1e12) + widget.setMinimum(sys.float_info.min) + else: + widget.setMinimum(minimum) if maximum is None: - widget.setProperty("maximum", 1e12) + widget.setMaximum(sys.float_info.max) + else: + widget.setMaximum(maximum) + widget.setValue(float(Value)) elif Type == "Enumerator": widget = ui.createWidget("Gui::PrefComboBox") @@ -773,15 +827,6 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, else: widget = QtGui.QLineEdit() - if minimum is not None: - widget.setProperty("minimum", minimum) - if maximum is not None: - widget.setProperty("maximum", maximum) - if stepsize is not None: - widget.setProperty("stepsize", stepsize) - if precision is not None: - widget.setProperty("precision", precision) - widget.setProperty("Type", Type) widget.setParent(parent)