From cc4a24f5f33ac54b63c32b29c1c1455faa78cb76 Mon Sep 17 00:00:00 2001 From: Yann Lanthony Date: Tue, 9 Jan 2018 18:54:57 +0100 Subject: [PATCH] [ui] 3D Viewer: Alembic support * optional and only available if AlembicEntity QML plugin is in QML import path * 3D viewer can load a 3D model and an Alembic file at the same time * add contextual menu options to unload model/Alembic --- meshroom/ui/qml/Viewer/AlembicLoader.qml | 9 +++ meshroom/ui/qml/Viewer/Viewer3D.qml | 71 ++++++++++++++++++++++-- meshroom/ui/qml/WorkspaceView.qml | 7 ++- meshroom/ui/qml/main.qml | 5 +- 4 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 meshroom/ui/qml/Viewer/AlembicLoader.qml diff --git a/meshroom/ui/qml/Viewer/AlembicLoader.qml b/meshroom/ui/qml/Viewer/AlembicLoader.qml new file mode 100644 index 00000000..85c635dd --- /dev/null +++ b/meshroom/ui/qml/Viewer/AlembicLoader.qml @@ -0,0 +1,9 @@ +import AlembicEntity 1.0 + +/** + * Support for Alembic files in Qt3d. + * Create this component dynamically to test for AlembicEntity plugin availability. + */ +AlembicEntity { + id: root +} diff --git a/meshroom/ui/qml/Viewer/Viewer3D.qml b/meshroom/ui/qml/Viewer/Viewer3D.qml index c89321ee..1d82873a 100644 --- a/meshroom/ui/qml/Viewer/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer/Viewer3D.qml @@ -10,10 +10,15 @@ import Qt3D.Input 2.1 as Qt3DInput // to avoid clash with Controls2 Action FocusScope { id: root - property alias status: scene.status property alias source: modelLoader.source + property alias abcSource: modelLoader.abcSource + readonly property alias loading: modelLoader.loading + // Alembic optional support => won't be available if AlembicEntity plugin is not available + readonly property Component abcLoaderComp: Qt.createComponent("AlembicLoader.qml") + readonly property bool supportAlembic: abcLoaderComp.status == Component.Ready + // functions function resetCameraCenter() { mainCamera.viewCenter = Qt.vector3d(0.0, 0.0, 0.0); @@ -104,7 +109,20 @@ FocusScope { function clear() { - source = 'no_file' + clearScene() + clearAbc() + } + + function clearScene() + { + source = 'no_file' // only way to force unloading of valid scene + source = '' + } + + function clearAbc() + { + abcSource = '' // does not work yet by itself + abcLoaderEntity.reload() // need to re-create the alembic loader } Scene3D { @@ -202,9 +220,11 @@ FocusScope { Entity { id: modelLoader property string source + property url abcSource // SceneLoader status is not reliable when loading a 3D file - property bool loading + property bool loading: false onSourceChanged: loading = true + onAbcSourceChanged: { if(root.supportAlembic) loading = true } components: [scene, transform, picker] @@ -234,17 +254,45 @@ FocusScope { modelLoader.loading = false; if(scene.status == SceneLoader.Ready) { - setupMaterialSwitchers(parent) + setupMaterialSwitchers(modelLoader) } } } + + Entity { + id: abcLoaderEntity + // Instantiate the AlembicEntity dynamically + // to avoid import errors if the plugin is not available + property Entity abcLoader: undefined + + Component.onCompleted: reload() + function reload() { + if(!root.supportAlembic) // Alembic plugin not available + return + + // destroy previously created entity + if(abcLoader != undefined) + abcLoader.destroy() + + abcLoader = abcLoaderComp.createObject(abcLoaderEntity, { + 'url': Qt.binding(function() { return modelLoader.abcSource } ), + 'particleSize': Qt.binding(function() { return 0.01 * transform.scale }) + }); + // urlChanged signal is emitted once the Alembic file is loaded + // set the 'loading' property to false when it's emitted + // TODO: AlembicEntity should expose a status + abcLoader.onUrlChanged.connect(function(){ modelLoader.loading = false }) + modelLoader.loading = false + } + } + Locator3D { enabled: locatorCheckBox.checked } } Grid3D { enabled: gridCheckBox.checked } - } } + Pane { background: Rectangle { color: palette.base; opacity: 0.5; radius: 2 } Column { @@ -296,5 +344,18 @@ FocusScope { text: "Reset View" onTriggered: resetCameraPosition() } + MenuSeparator {} + MenuItem { + height: visible ? implicitHeight : 0 + text: "Unload Model" + visible: root.source != '' + onTriggered: clearScene() + } + MenuItem { + height: visible ? implicitHeight : 0 + text: "Unload Alembic" + visible: abcSource != '' + onTriggered: clearAbc() + } } } diff --git a/meshroom/ui/qml/WorkspaceView.qml b/meshroom/ui/qml/WorkspaceView.qml index 3e5ed54f..63fc8067 100644 --- a/meshroom/ui/qml/WorkspaceView.qml +++ b/meshroom/ui/qml/WorkspaceView.qml @@ -29,7 +29,10 @@ Item { // Load a 3D media file in the 3D viewer function load3DMedia(filepath) { - viewer3D.source = filepath + if(Filepath.extension(filepath) === ".abc") + viewer3D.abcSource = filepath + else + viewer3D.source = filepath } SystemPalette { id: palette } @@ -73,7 +76,7 @@ Item { anchors.fill: parent DropArea { anchors.fill: parent - onDropped: viewer3D.source = drop.urls[0] + onDropped: load3DMedia(drop.urls[0]) } } diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index da414601..6bd52273 100755 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -19,6 +19,9 @@ ApplicationWindow { font.pointSize: 9 property variant node: null + // supported 3D files extensions + readonly property var _3dFileExtensions: ['.obj', '.abc'] + onClosing: { // make sure document is saved before exiting application close.accepted = false @@ -356,7 +359,7 @@ ApplicationWindow { var attr = node.attributes.at(i) if(attr.isOutput && attr.desc.type === "File" - && Filepath.extension(attr.value) === ".obj") + && _3dFileExtensions.indexOf(Filepath.extension(attr.value)) > - 1 ) { workspaceView.load3DMedia(attr.value) break // only load first model found