import QtQuick 2.9 import Qt3D.Core 2.1 import Qt3D.Render 2.1 import Utils 1.0 /** * MediaLibrary is an Entity that loads and manages a list of 3D media. * It also uses an internal cache to instantly reload media. */ Entity { id: root readonly property alias model: m.mediaModel property int renderMode property bool pickingEnabled: false readonly property alias count: instantiator.count // number of instantiated media delegates // For TransformGizmo in BoundingBox property DefaultCameraController sceneCameraController property Layer frontLayerComponent property var window /// Camera to consider for positionning property Camera camera: null /// True while at least one media is being loaded readonly property bool loading: { for(var i=0; i instantiatedEntity.currentNode ? instantiatedEntity.currentNode.attribute("input").value: "") currentSource = Qt.binding(() => rawSource) finalSource = Qt.binding(() => rawSource) } camera: root.camera renderMode: root.renderMode enabled: visible // QObject.destroyed signal is not accessible // Use the object as NodeInstantiator model to be notified of its deletion NodeInstantiator { model: attribute delegate: Entity { objectName: "DestructionWatcher [" + model.toString() + "]" } onObjectRemoved: remove(index) } property bool alive: attribute ? attribute.node.alive : false onAliveChanged: { if(!alive && index >= 0) remove(index) } // 'visible' property drives media loading request onVisibleChanged: { // always request media loading if visible if(model.visible) model.requested = true; // only cancel loading request if media is not valid // (a media won't be unloaded if already loaded, only hidden) else if(!model.valid) model.requested = false; } function updateCacheAndModel(forceRequest) { // don't cache explicitly unloaded media if(model.requested && object && dependencyReady) { // cache current object if(cache.add(Filepath.urlToString(mediaLoader.source), object)); object = null; } updateModel(forceRequest); } function updateModel(forceRequest) { // update model's source path if input is an attribute if(attribute) { model.source = rawSource; } // auto-restore entity if raw source is in cache model.requested = forceRequest || (!model.valid && model.requested) || cache.contains(rawSource); model.valid = Filepath.exists(rawSource) && dependencyReady; } Component.onCompleted: { // keep 'source' -> 'entity' reference m.sourceToEntity[modelSource] = instantiatedEntity; // always request media loading when delegate has been created updateModel(true); // if external media failed to open, remove element from model if(!attribute && !object) remove(index) } onCurrentSourceChanged: { updateCacheAndModel(false) // Avoid the bounding box to disappear when we move it after a mesh already computed if(instantiatedEntity.hasBoundingBox && !currentSource) model.visible = true } onFinalSourceChanged: { // update media visibility // (useful if media was explicitly unloaded or hidden but loaded back from cache) model.visible = model.requested; var cachedObject = cache.pop(rawSource); cached = cachedObject !== undefined; if(cached) { object = cachedObject; // only change cached object parent if mediaLoader has been fully instantiated // by the NodeInstantiator; otherwise re-parenting will fail silently and the object will disappear... // see "onFullyInstantiatedChanged" and parent NodeInstantiator's "onObjectAdded" if(fullyInstantiated) { object.parent = mediaLoader; } } mediaLoader.source = Filepath.stringToUrl(finalSource); if(object) { // bind media info to corresponding model roles // (test for object validity to avoid error messages right after object has been deleted) var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount"]; boundProperties.forEach( function(prop){ model[prop] = Qt.binding(function() { return object ? object[prop] : 0; }); }) } else if(finalSource && status === Component.Ready) { // source was valid but no loader was created, remove element // check if component is ready to avoid removing element from the model before adding instance to the node remove(index) } } onFullyInstantiatedChanged: { // delayed reparenting of object coming from the cache if(object) object.parent = mediaLoader; } onStatusChanged: { model.status = status // remove model entry for external media that failed to load if(status === SceneLoader.Error && !model.attribute) remove(index); } components: [ ObjectPicker { enabled: mediaLoader.enabled && pickingEnabled hoverEnabled: false onPressed: root.pressed(pick) } ] } // Transform: display a TransformGizmo for SfMTransform node only // note: use a NodeInstantiator to evaluate if the current node is a SfMTransform node and if the transform mode is set to Manual NodeInstantiator { id: sfmTransformGizmoInstantiator active: instantiatedEntity.hasTransform model: 1 SfMTransformGizmo { id: sfmTransformGizmoEntity sceneCameraController: root.sceneCameraController frontLayerComponent: root.frontLayerComponent window: root.window currentSfMTransformNode: instantiatedEntity.currentNode enabled: mediaLoader.visible && instantiatedEntity.displayTransform Component.onCompleted: { mediaLoader.drawInputSource() // Because we are sure we want to show the input in MANUAL mode only Scene3DHelper.addComponent(mediaLoader, sfmTransformGizmoEntity.objectTransform) // Add the transform to the media to see real-time transformations } } } // BoundingBox: display bounding box for MESHING computation // note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active NodeInstantiator { id: boundingBoxInstantiator active: instantiatedEntity.hasBoundingBox model: 1 MeshingBoundingBox { sceneCameraController: root.sceneCameraController frontLayerComponent: root.frontLayerComponent window: root.window currentMeshingNode: instantiatedEntity.currentNode enabled: mediaLoader.visible && instantiatedEntity.displayBoundingBox } } } onObjectAdded: { // notify object that it is now fully instantiated object.fullyInstantiated = true; } onObjectRemoved: { if(m.sourceToEntity[object.modelSource]) delete m.sourceToEntity[object.modelSource] } } }