Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeEditor: Flag attributes with invalid values #2141

Merged
merged 6 commits into from
Aug 21, 2023
Merged
21 changes: 21 additions & 0 deletions meshroom/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
self._value = copy.copy(attributeDesc.value)
self._label = attributeDesc.label
self._enabled = True
self._validValue = True

# invalidation value for output attributes
self._invalidationValue = ""
Expand Down Expand Up @@ -147,6 +148,24 @@
""" Value for which the attribute should be ignored during the UID computation. """
return self.attributeDesc.uidIgnoreValue

def getValidValue(self):
"""
Get the status of _validValue:
- If it is a function, execute it and return the result
- Otherwise, simply return its value
"""
if isinstance(self.desc.validValue, types.FunctionType):
try:
return self.desc.validValue(self.node)
except Exception:
return True
return self._validValue

def setValidValue(self, value):
if self._validValue == value:
return
self._validValue = value

def _get_value(self):
if self.isLink:
return self.getLinkParam().value
Expand All @@ -168,11 +187,11 @@
# and parent node belongs to a graph
# Output attributes value are set internally during the update process,
# which is why we don't trigger any update in this case
# TODO: update only the nodes impacted by this change

Check notice on line 190 in meshroom/core/attribute.py

View check run for this annotation

codefactor.io / CodeFactor

meshroom/core/attribute.py#L190

unresolved comment '# TODO: update only the nodes impacted by this change' (C100)
# TODO: only update the graph if this attribute participates to a UID

Check notice on line 191 in meshroom/core/attribute.py

View check run for this annotation

codefactor.io / CodeFactor

meshroom/core/attribute.py#L191

unresolved comment '# TODO: only update the graph if this attribute participates to a UID' (C100)
if self.isInput:
self.requestGraphUpdate()
# TODO: only call update of the node if the attribute is internal

Check notice on line 194 in meshroom/core/attribute.py

View check run for this annotation

codefactor.io / CodeFactor

meshroom/core/attribute.py#L194

unresolved comment '# TODO: only call update of the node if the attribute is internal' (C100)
# Internal attributes are set as inputs
self.requestNodeUpdate()

Expand Down Expand Up @@ -338,6 +357,8 @@
enabledChanged = Signal()
enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged)
uidIgnoreValue = Property(Variant, getUidIgnoreValue, constant=True)
validValueChanged = Signal()
validValue = Property(bool, getValidValue, setValidValue, notify=validValueChanged)


def raiseIfLink(func):
Expand Down
25 changes: 16 additions & 9 deletions meshroom/core/desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class Attribute(BaseObject):
"""
"""

def __init__(self, name, label, description, value, advanced, semantic, uid, group, enabled, uidIgnoreValue=None):
def __init__(self, name, label, description, value, advanced, semantic, uid, group, enabled, uidIgnoreValue=None,
validValue=True, errorMessage=""):
super(Attribute, self).__init__()
self._name = name
self._label = label
Expand All @@ -26,6 +27,8 @@ def __init__(self, name, label, description, value, advanced, semantic, uid, gro
self._enabled = enabled
self._semantic = semantic
self._uidIgnoreValue = uidIgnoreValue
self._validValue = validValue
self._errorMessage = errorMessage

name = Property(str, lambda self: self._name, constant=True)
label = Property(str, lambda self: self._label, constant=True)
Expand All @@ -37,6 +40,8 @@ def __init__(self, name, label, description, value, advanced, semantic, uid, gro
enabled = Property(Variant, lambda self: self._enabled, constant=True)
semantic = Property(str, lambda self: self._semantic, constant=True)
uidIgnoreValue = Property(Variant, lambda self: self._uidIgnoreValue, constant=True)
validValue = Property(Variant, lambda self: self._validValue, constant=True)
errorMessage = Property(str, lambda self: self._errorMessage, constant=True)
type = Property(str, lambda self: self.__class__.__name__, constant=True)

def validateValue(self, value):
Expand Down Expand Up @@ -205,9 +210,9 @@ def retrieveChildrenUids(self):
class Param(Attribute):
"""
"""
def __init__(self, name, label, description, value, uid, group, advanced, semantic, enabled, uidIgnoreValue=None):
def __init__(self, name, label, description, value, uid, group, advanced, semantic, enabled, uidIgnoreValue=None, validValue=True, errorMessage=""):
super(Param, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
uidIgnoreValue=uidIgnoreValue)
uidIgnoreValue=uidIgnoreValue, validValue=validValue, errorMessage=errorMessage)


class File(Attribute):
Expand Down Expand Up @@ -253,9 +258,10 @@ def checkValueTypes(self):
class IntParam(Param):
"""
"""
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True):
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True, validValue=True, errorMessage=""):
self._range = range
super(IntParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
super(IntParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
validValue=validValue, errorMessage=errorMessage)

def validateValue(self, value):
# handle unsigned int values that are translated to int by shiboken and may overflow
Expand All @@ -275,9 +281,10 @@ def checkValueTypes(self):
class FloatParam(Param):
"""
"""
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True):
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True, validValue=True, errorMessage=""):
self._range = range
super(FloatParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
super(FloatParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
validValue=validValue, errorMessage=errorMessage)

def validateValue(self, value):
try:
Expand Down Expand Up @@ -334,9 +341,9 @@ def checkValueTypes(self):
class StringParam(Param):
"""
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, uidIgnoreValue=None):
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, uidIgnoreValue=None, validValue=True, errorMessage=""):
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
uidIgnoreValue=uidIgnoreValue)
uidIgnoreValue=uidIgnoreValue, validValue=validValue, errorMessage=errorMessage)

def validateValue(self, value):
if not isinstance(value, str):
Expand Down
21 changes: 17 additions & 4 deletions meshroom/nodes/aliceVision/LdrToHdrCalibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class LdrToHdrCalibration(desc.AVCommandLineNode):
range=(0, 15, 1),
uid=[],
group="user", # not used directly on the command line
errorMessage="The set number of brackets is not a multiple of the number of input images.\n"
"Errors will occur during the computation."
),
desc.IntParam(
name="nbBrackets",
Expand All @@ -68,7 +70,7 @@ class LdrToHdrCalibration(desc.AVCommandLineNode):
"It is detected automatically from input Viewpoints metadata if 'userNbBrackets' is 0,\n"
"else it is equal to 'userNbBrackets'.",
value=0,
range=(0, 10, 1),
range=(0, 15, 1),
uid=[0],
group="bracketsParams"
),
Expand Down Expand Up @@ -181,13 +183,24 @@ def update(cls, node):
if "userNbBrackets" not in node.getAttributes().keys():
# Old version of the node
return
if node.userNbBrackets.value != 0:
node.nbBrackets.value = node.userNbBrackets.value
return
node.userNbBrackets.validValue = True # Reset the status of "userNbBrackets"

cameraInitOutput = node.input.getLinkParam(recursive=True)
if not cameraInitOutput:
node.nbBrackets.value = 0
return
if node.userNbBrackets.value != 0:
# The number of brackets has been manually forced: check whether it is valid or not
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute("viewpoints"):
viewpoints = cameraInitOutput.node.viewpoints.value
# The number of brackets should be a multiple of the number of input images
if (len(viewpoints) % node.userNbBrackets.value != 0):
node.userNbBrackets.validValue = False
else:
node.userNbBrackets.validValue = True
node.nbBrackets.value = node.userNbBrackets.value
return

if not cameraInitOutput.node.hasAttribute("viewpoints"):
if cameraInitOutput.node.hasAttribute("input"):
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
Expand Down
21 changes: 17 additions & 4 deletions meshroom/nodes/aliceVision/LdrToHdrMerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class LdrToHdrMerge(desc.AVCommandLineNode):
range=(0, 15, 1),
uid=[],
group="user", # not used directly on the command line
errorMessage="The set number of brackets is not a multiple of the number of input images.\n"
"Errors will occur during the computation."
),
desc.IntParam(
name="nbBrackets",
Expand All @@ -67,7 +69,7 @@ class LdrToHdrMerge(desc.AVCommandLineNode):
"It is detected automatically from input Viewpoints metadata if 'userNbBrackets'\n"
"is 0, else it is equal to 'userNbBrackets'.",
value=0,
range=(0, 10, 1),
range=(0, 15, 1),
uid=[0],
group="bracketsParams"
),
Expand Down Expand Up @@ -267,13 +269,24 @@ def update(cls, node):
if "userNbBrackets" not in node.getAttributes().keys():
# Old version of the node
return
if node.userNbBrackets.value != 0:
node.nbBrackets.value = node.userNbBrackets.value
return
node.userNbBrackets.validValue = True # Reset the status of "userNbBrackets"

cameraInitOutput = node.input.getLinkParam(recursive=True)
if not cameraInitOutput:
node.nbBrackets.value = 0
return
if node.userNbBrackets.value != 0:
# The number of brackets has been manually forced: check whether it is valid or not
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute("viewpoints"):
viewpoints = cameraInitOutput.node.viewpoints.value
# The number of brackets should be a multiple of the number of input images
if (len(viewpoints) % node.userNbBrackets.value != 0):
node.userNbBrackets.validValue = False
else:
node.userNbBrackets.validValue = True
node.nbBrackets.value = node.userNbBrackets.value
return

if not cameraInitOutput.node.hasAttribute("viewpoints"):
if cameraInitOutput.node.hasAttribute("input"):
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
Expand Down
21 changes: 17 additions & 4 deletions meshroom/nodes/aliceVision/LdrToHdrSampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class LdrToHdrSampling(desc.AVCommandLineNode):
range=(0, 15, 1),
uid=[],
group="user", # not used directly on the command line
errorMessage="The set number of brackets is not a multiple of the number of input images.\n"
"Errors will occur during the computation."
),
desc.IntParam(
name="nbBrackets",
Expand All @@ -82,7 +84,7 @@ class LdrToHdrSampling(desc.AVCommandLineNode):
"It is detected automatically from input Viewpoints metadata if 'userNbBrackets'\n"
"is 0, else it is equal to 'userNbBrackets'.",
value=0,
range=(0, 10, 1),
range=(0, 15, 1),
uid=[0],
group="bracketsParams"
),
Expand Down Expand Up @@ -207,13 +209,24 @@ def update(cls, node):
# Old version of the node
return
node.outliersNb = 0 # Reset the number of detected outliers
if node.userNbBrackets.value != 0:
node.nbBrackets.value = node.userNbBrackets.value
return
node.userNbBrackets.validValue = True # Reset the status of "userNbBrackets"

cameraInitOutput = node.input.getLinkParam(recursive=True)
if not cameraInitOutput:
node.nbBrackets.value = 0
return
if node.userNbBrackets.value != 0:
# The number of brackets has been manually forced: check whether it is valid or not
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute("viewpoints"):
viewpoints = cameraInitOutput.node.viewpoints.value
# The number of brackets should be a multiple of the number of input images
if (len(viewpoints) % node.userNbBrackets.value != 0):
node.userNbBrackets.validValue = False
else:
node.userNbBrackets.validValue = True
node.nbBrackets.value = node.userNbBrackets.value
return

if not cameraInitOutput.node.hasAttribute("viewpoints"):
if cameraInitOutput.node.hasAttribute("input"):
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
Expand Down
51 changes: 41 additions & 10 deletions meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.0
import QtQuick.Dialogs 1.3
import MaterialIcons 2.2
import Utils 1.0

Expand All @@ -23,9 +23,22 @@ RowLayout {

spacing: 2

function updateAttributeLabel()
{
background.color = attribute.validValue ? Qt.darker(palette.window, 1.1) : Qt.darker(Colors.red, 1.5)

if (attribute.desc) {
var tooltip = ""
if (!attribute.validValue && attribute.desc.errorMessage !== "")
tooltip += "<i><b>Error: </b>" + Format.plainToHtml(attribute.desc.errorMessage) + "</i><br><br>"
tooltip += "<b> " + attribute.desc.name + "</b><br>" + Format.plainToHtml(attribute.desc.description)

parameterTooltip.text = tooltip
}
}

Pane {
background: Rectangle { color: Qt.darker(parent.palette.window, 1.1) }
background: Rectangle { id: background; color: object.validValue ? Qt.darker(parent.palette.window, 1.1) : Qt.darker(Colors.red, 1.5) }
padding: 0
Layout.preferredWidth: labelWidth || implicitWidth
Layout.fillHeight: true
Expand All @@ -34,6 +47,7 @@ RowLayout {
spacing: 0
width: parent.width
height: parent.height

Label {
id: parameterLabel

Expand All @@ -44,20 +58,29 @@ RowLayout {
padding: 5
wrapMode: Label.WrapAtWordBoundaryOrAnywhere

text: attribute.label
text: object.label

// Tooltip hint with attribute's description
ToolTip.text: "<b>" + object.desc.name + "</b><br>" + Format.plainToHtml(object.desc.description)
ToolTip.visible: parameterMA.containsMouse
ToolTip.delay: 800
ToolTip {
id: parameterTooltip

text: {
var tooltip = ""
if (!object.validValue && object.desc.errorMessage !== "")
tooltip += "<i><b>Error: </b>" + Format.plainToHtml(object.desc.errorMessage) + "</i><br><br>"
tooltip += "<b>" + object.desc.name + "</b><br>" + Format.plainToHtml(object.desc.description)
return tooltip
}
visible: parameterMA.containsMouse
delay: 800
}

// make label bold if attribute's value is not the default one
font.bold: !object.isOutput && !object.isDefault

// make label italic if attribute is a link
font.italic: object.isLink


MouseArea {
id: parameterMA
anchors.fill: parent
Expand All @@ -74,7 +97,10 @@ RowLayout {
MenuItem {
text: "Reset To Default Value"
enabled: root.editable && !attribute.isDefault
onTriggered: _reconstruction.resetAttribute(attribute)
onTriggered: {
_reconstruction.resetAttribute(attribute)
updateAttributeLabel()
}
}

MenuSeparator {
Expand Down Expand Up @@ -129,12 +155,15 @@ RowLayout {
case "IntParam":
case "FloatParam":
_reconstruction.setAttribute(root.attribute, Number(value))
updateAttributeLabel()
break;
case "File":
_reconstruction.setAttribute(root.attribute, value)
break;
default:
_reconstruction.setAttribute(root.attribute, value.trim())
updateAttributeLabel()
break;
}
}

Expand Down Expand Up @@ -370,6 +399,7 @@ RowLayout {
onEditingFinished: setTextFieldAttribute(text)
onAccepted: {
setTextFieldAttribute(text)

// When the text is too long, display the left part
// (with the most important values and cut the floating point details)
ensureVisible(0)
Expand Down Expand Up @@ -400,12 +430,13 @@ RowLayout {
snapMode: Slider.SnapAlways

onPressedChanged: {
if(!pressed)
if (!pressed) {
_reconstruction.setAttribute(attribute, formattedValue)
updateAttributeLabel()
}
}
}
}

}
}

Expand Down
1 change: 1 addition & 0 deletions meshroom/ui/qml/MaterialIcons/MaterialIcons.qml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ QtObject {
readonly property string crop_portrait: "\ue3c5"
readonly property string crop_rotate: "\ue437"
readonly property string crop_square: "\ue3c6"
readonly property string dangerous: "\ue99a"
readonly property string dashboard: "\ue871"
readonly property string data_usage: "\ue1af"
readonly property string date_range: "\ue916"
Expand Down