Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4a67736
[ui] Attribute: Output attribute currently displayed in viewer2D, now…
nicolas-lambert-tc May 9, 2025
cfd2b1e
[ui] Attribute: Attribute actually displayed in the 3dViewer are flag…
nicolas-lambert-tc May 9, 2025
ccecece
[ui] Attribute: Add an item to attribute's contextMenu (showInViewpor…
nicolas-lambert-tc May 12, 2025
a263af4
[ui] AttributeEditor: Add tests
nicolas-lambert-tc May 13, 2025
0735350
[ui] Attribute: Show an icon on displayable attribute
nicolas-lambert-tc May 14, 2025
26d1916
[ui] Attribute: Refacto showInViewport to showInViewer for clarity an…
nicolas-lambert-tc May 14, 2025
16d5251
[ui]: Attribute: refacto, remove the empty lines
nicolas-lambert-tc May 14, 2025
ae20d61
[ui] Atribute: adapt the attribute-viewer icon on the attribute-navig…
nicolas-lambert-tc May 15, 2025
0ef098f
[ui] Attribute: Prefer double quotes insead of single quotes when pos…
nicolas-lambert-tc May 21, 2025
818e735
[ui] Attriubte: Remove trailing spaces
nicolas-lambert-tc May 21, 2025
51dc094
[ui] Attribute: Use a static declarative menuItem instead of an imper…
nicolas-lambert-tc May 21, 2025
00913e1
Use visibility instead of opacity
nicolas-lambert-tc May 21, 2025
16ffc83
[ui] Attribute: Fix the eye icon alignment
nicolas-lambert-tc May 21, 2025
2488f3e
Apply suggestions from code review (Fix the old 3d typo and useless c…
nicolas-lambert-tc May 22, 2025
2608695
Apply suggestions from code review (Layout adjustment on connection a…
nicolas-lambert-tc May 22, 2025
64f3f85
[ui] Attribute: Add a tooltip to explain the eye icon utility
nicolas-lambert-tc May 22, 2025
0cd5769
[GraphEditor] AttributeItemDelegate: Center attribute labels vertically
cbentejac May 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions meshroom/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class Attribute(BaseObject):
"""
"""
stringIsLinkRe = re.compile(r'^\{[A-Za-z]+[A-Za-z0-9_.\[\]]*\}$')

VALID_IMAGE_SEMANTICS = ["image", "imageList", "sequence"]
VALID_3D_EXTENSIONS = [".obj", ".stl", ".fbx", ".gltf", ".abc", ".ply"]

def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
"""
Expand Down Expand Up @@ -446,6 +449,27 @@ def updateInternals(self):
# Emit if the enable status has changed
self.setEnabled(self.getEnabled())

def _is3D(self) -> bool:
""" Return True if the current attribute is considered as a 3d file """

if self.desc.semantic == "3d":
return True

# If the attribute is a File attribute, it is an instance of str and can be iterated over
hasSupportedExt = isinstance(self.value, str) and any(ext in self.value for ext in Attribute.VALID_3D_EXTENSIONS)
if hasSupportedExt:
return True

return False

def _is2D(self) -> bool:
""" Return True if the current attribute is considered as a 2d file """

if not self.desc.semantic:
return False

return next((imageSemantic for imageSemantic in Attribute.VALID_IMAGE_SEMANTICS if self.desc.semantic == imageSemantic), None) is not None

name = Property(str, getName, constant=True)
fullName = Property(str, getFullName, constant=True)
fullNameToNode = Property(str, getFullNameToNode, constant=True)
Expand All @@ -458,6 +482,8 @@ def updateInternals(self):
type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True)
isReadOnly = Property(bool, _isReadOnly, constant=True)
is3D = Property(bool, _is3D, constant=True)
is2D = Property(bool, _is2D, constant=True)

# Description of the attribute
descriptionChanged = Signal()
Expand Down
14 changes: 2 additions & 12 deletions meshroom/core/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -1617,18 +1617,8 @@
Return True if at least one attribute is a File that can be loaded in the 3D Viewer,
False otherwise.
"""
# List of supported extensions, taken from Viewer3DSettings
supportedExts = ['.obj', '.stl', '.fbx', '.gltf', '.abc', '.ply']
for attr in self._attributes:
if not attr.enabled or not attr.isOutput:
continue
if attr.desc.semantic == "3d":
return True
# If the attribute is a File attribute, it is an instance of str and can be iterated over
hasSupportedExt = isinstance(attr.value, str) and any(ext in attr.value for ext in supportedExts)
if hasSupportedExt:
return True
return False

return next((attr for attr in self._attributes if attr.enabled and attr.isOutput and attr.is3D), None) is not None

Check warning on line 1621 in meshroom/core/node.py

View check run for this annotation

Codecov / codecov/patch

meshroom/core/node.py#L1621

Added line #L1621 was not covered by tests

name = Property(str, getName, constant=True)
defaultLabel = Property(str, getDefaultLabel, constant=True)
Expand Down
39 changes: 36 additions & 3 deletions meshroom/ui/qml/Application.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1105,22 +1105,45 @@ Page {
for (var i = 0; i < node.attributes.count; i++) {
var attr = node.attributes.at(i)
if (attr.isOutput && attr.desc.semantic !== "image")
if (!alreadyDisplay || attr.desc.semantic == "3D")
if (!alreadyDisplay || attr.desc.semantic == "3d") {
if (workspaceView.viewIn3D(attr, mouse))
alreadyDisplay = true
}

}
}

function viewIn2D(attribute, mouse) {
workspaceView.viewer2D.tryLoadNode(attribute.node)
workspaceView.viewer2D.setAttributeName(attribute.name)
}

function viewIn3D(attribute, mouse) {
if (!panel3dViewer || (!attribute.node.has3DOutput && !attribute.node.hasAttribute("useBoundingBox")))

if (!panel3dViewer || (!attribute.node.has3DOutput && !attribute.node.hasAttribute("useBoundingBox"))) {
return false
}
var loaded = panel3dViewer.viewer3D.view(attribute)

// solo media if Control modifier was held
if (loaded && mouse && mouse.modifiers & Qt.ControlModifier)
if (loaded && mouse && mouse.modifiers & Qt.ControlModifier) {
panel3dViewer.viewer3D.solo(attribute)
}
return loaded
}

function viewAttributeInViewer(mouse, attribute) {
/* Display the current attribute in the corresponding viewer */

if (attribute.is2D) {
workspaceView.viewIn2D(attribute, mouse)
}

else if (attribute.is3D) {
workspaceView.viewIn3D(attribute, mouse)
}

}
}

MSplitView {
Expand Down Expand Up @@ -1417,8 +1440,18 @@ Page {
}
}


onShowAttributeInViewer: function(attribute) {
workspaceView.viewAttributeInViewer(null, attribute)
}

onAttributeDoubleClicked: function(mouse, attribute) {
workspaceView.viewAttributeInViewer(mouse, attribute)
}

}
}
}
}

}
4 changes: 4 additions & 0 deletions meshroom/ui/qml/GraphEditor/AttributeEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ListView {
signal attributeDoubleClicked(var mouse, var attribute)
signal inAttributeClicked(var srcItem, var mouse, var inAttributes)
signal outAttributeClicked(var srcItem, var mouse, var outAttributes)
signal showInViewer(var attribute)

implicitHeight: contentHeight

Expand Down Expand Up @@ -53,6 +54,9 @@ ListView {
root.outAttributeClicked(srcItem, mouse, outAttributes)
}

onShowInViewer: function(attr) {
root.showInViewer(attr)
}
}

onActiveChanged: height = active ? item.implicitHeight : -spacing
Expand Down
48 changes: 37 additions & 11 deletions meshroom/ui/qml/GraphEditor/AttributeItemDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ RowLayout {
signal doubleClicked(var mouse, var attr)
signal inAttributeClicked(var srcItem, var mouse, var inAttributes)
signal outAttributeClicked(var srcItem, var mouse, var outAttributes)
signal showInViewer(var attr)

spacing: 2

Expand Down Expand Up @@ -64,11 +65,11 @@ RowLayout {

property bool shouldBeVisible: (object != undefined && object.isLinkNested)

text: shouldBeVisible ? MaterialIcons.login : " "
text: MaterialIcons.login
enabled: shouldBeVisible
font.pointSize: 8
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
topPadding: 7
Layout.fillHeight: true
visible: shouldBeVisible

MouseArea {
anchors.fill: parent
Expand All @@ -78,7 +79,7 @@ RowLayout {
root.inAttributeClicked(navButtonIn, mouse, object.linkedInAttributes)
}
}

}

Label {
Expand All @@ -87,6 +88,7 @@ RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
horizontalAlignment: attribute.isOutput ? Qt.AlignRight : Qt.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Label.ElideRight
padding: 5
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
Expand Down Expand Up @@ -129,7 +131,7 @@ RowLayout {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onDoubleClicked: function(mouse) { root.doubleClicked(mouse, root.attribute) }
onDoubleClicked: function(mouse) { root.doubleClicked(mouse, root.attribute) }

property Component menuComp: Menu {
id: paramMenu
Expand Down Expand Up @@ -180,6 +182,18 @@ RowLayout {
text: "Open File"
onClicked: Qt.openUrlExternally(Filepath.stringToUrl(attribute.evalValue))
}

MenuItem {
visible: attribute.isOutput && (attribute.is2D || attribute.is3D)
height: visible ? implicitHeight : 0
text: {
if (attribute.is2D)
return "Show in 2D Viewer"
return "Show in 3D Viewer"
}
onClicked: root.showInViewer(attribute)
}

}

onClicked: function(mouse) {
Expand All @@ -193,26 +207,38 @@ RowLayout {
}
}

MaterialLabel {
property bool isDisplayable: attribute.isOutput && (attribute.is2D || attribute.is3D)
property bool isDisplayed: attribute === _reconstruction.displayedAttr2D || _reconstruction.displayedAttrs3D.count && _reconstruction.displayedAttrs3D.contains(attribute)
text: isDisplayed ? MaterialIcons.visibility : MaterialIcons.visibility_off
enabled: isDisplayed
visible: isDisplayable
ToolTip.text: `This attribute is displayable in the ${attribute.is2D ? "2D" : "3D"} viewer.`

padding: 4
font.pointSize: 8
}

MaterialToolButton {
id: navButtonOut

property bool shouldBeVisible: (attribute != undefined && attribute.hasOutputConnections)

text: shouldBeVisible ? MaterialIcons.logout : " "
text: MaterialIcons.logout
font.pointSize: 8
enabled: shouldBeVisible
Layout.alignment: Qt.AlignTop | Qt.AlignRight
topPadding: 7
Layout.fillHeight: true
visible: shouldBeVisible

MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton

onClicked: function(mouse) {
root.outAttributeClicked(navButtonOut, mouse, attribute.linkedOutAttributes)
onClicked: function(mouse) {
root.outAttributeClicked(navButtonOut, mouse, attribute.linkedOutAttributes)
}
}


}

Expand Down
2 changes: 2 additions & 0 deletions meshroom/ui/qml/GraphEditor/NodeEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Panel {
signal attributeDoubleClicked(var mouse, var attribute)
signal inAttributeClicked(var srcItem, var mouse, var inAttributes)
signal outAttributeClicked(var srcItem, var mouse, var outAttributes)
signal showAttributeInViewer(var attribute)
signal upgradeRequest()

title: "Node" + (node !== null ? " - <b>" + node.label + "</b>" + (node.label !== node.defaultLabel ? " (" + node.defaultLabel + ")" : "") : "")
Expand Down Expand Up @@ -302,6 +303,7 @@ Panel {
readOnly: root.readOnly || root.isCompatibilityNode
onAttributeDoubleClicked: function(mouse, attribute) { root.attributeDoubleClicked(mouse, attribute) }
onUpgradeRequest: root.upgradeRequest()
onShowInViewer: function (attribute) {root.showAttributeInViewer(attribute)}
filterText: searchBar.text

onInAttributeClicked: function(srcItem, mouse, inAttributes) {
Expand Down
15 changes: 15 additions & 0 deletions meshroom/ui/qml/Viewer/Viewer2D.qml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ FocusScope {
return []
}

function setAttributeName(attrName) {
outputAttribute.setName(attrName)
}

onDisplayedNodeChanged: {
if (!displayedNode) {
root.source = ""
Expand Down Expand Up @@ -379,6 +383,10 @@ FocusScope {
}
}

onDisplayedAttrChanged: {
_reconstruction.displayedAttr2D = displayedAttr
}

Connections {
target: _reconstruction
function onSelectedViewIdChanged() {
Expand Down Expand Up @@ -1604,6 +1612,13 @@ FocusScope {
root.source = getImageFile()
root.sequence = getSequence()
}

function setName(attrName) {
const attrIndex = outputAttribute.names.indexOf(attrName)
if (attrIndex > -1) {
outputAttribute.currentIndex = attrIndex
}
}
}

MaterialToolButton {
Expand Down
10 changes: 8 additions & 2 deletions meshroom/ui/qml/Viewer3D/MediaLibrary.qml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Entity {
"label": label ? label : Filepath.basename(pathStr),
"section": "External"
}))

}

function view(attribute) {
Expand All @@ -115,15 +116,16 @@ Entity {
return
}

var attrLabel = attribute.isOutput ? "" : attribute.fullName.replace(attribute.node.name, "")
var section = attribute.node.label

// Add file to the internal ListModel
m.mediaModel.append(
makeElement({
"label": section + attrLabel,
"label": `${section}.${attribute.label}`,
"section": section,
"attribute": attribute
}))

}

function remove(index) {
Expand Down Expand Up @@ -384,11 +386,15 @@ Entity {
onObjectAdded: function(index, object) {
// Notify object that it is now fully instantiated
object.fullyInstantiated = true
_reconstruction.displayedAttrs3D.append(object.modelSource)
}

onObjectRemoved: function(index, object) {
if (m.sourceToEntity[object.modelSource])

delete m.sourceToEntity[object.modelSource]
_reconstruction.displayedAttrs3D.remove(object.modelSource)
}

}
}
11 changes: 10 additions & 1 deletion meshroom/ui/reconstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ def __init__(self, undoStack: commands.UndoStack, taskManager: TaskManager, defa
self._activeNodes = meshroom.common.DictModel(keyAttrName="nodeType")
self.initActiveNodes()

# initialize activeAttributes (attributes currently visible in some viewers)
self._displayedAttr2D = None
self._displayedAttrs3D = meshroom.common.ListModel()

# - CameraInit
self._cameraInit = None # current CameraInit node
self._cameraInits = QObjectListModel(parent=self) # all CameraInit nodes
Expand Down Expand Up @@ -512,7 +516,6 @@ def __del__(self):
def setActive(self, active):
self._active = active

@Slot()
def clear(self):
self.clearActiveNodes()
super().clear()
Expand Down Expand Up @@ -1065,6 +1068,12 @@ def setBuildingIntrinsics(self, value):
buildingIntrinsics = Property(bool, lambda self: self._buildingIntrinsics, notify=buildingIntrinsicsChanged)
liveSfmManager = Property(QObject, lambda self: self._liveSfmManager, constant=True)

displayedAttr2DChanged = Signal()
displayedAttr2D = makeProperty(QObject, "_displayedAttr2D", displayedAttr2DChanged)

displayedAttrs3DChanged = Signal()
displayedAttrs3D = Property(QObject, lambda self: self._displayedAttrs3D, notify=displayedAttrs3DChanged)

@Slot(QObject)
def setActiveNode(self, node, categories=True, inputs=True):
""" Set node as the active node of its type and of its categories.
Expand Down
Loading