qtqmlqt3d

How to create Undo/Redo operations in Qt3D?


I created some entities using qt3d in QML. For example, this code shows a Scene3D element that declares RootEntity which is another QML element that contains the scene graph:

Scene3D
{
    id : scene3d
    anchors.fill: parent
    focus: true
    aspects: ["render", "logic", "input"]
    hoverEnabled: true
    cameraAspectRatioMode: Scene3D.AutomaticAspectRatio


    antialiasing: true

    RootEntity
    {
        id:root
    }

}

RootEntity.qml:

Entity {
id:root

property double x : 0.0


Camera {
    id: mainCamera
    projectionType: CameraLens.PerspectiveProjection
    fieldOfView: 45
    aspectRatio: 16/9
    nearPlane : 0.1
    farPlane : 1000.0
    position: Qt.vector3d(0.0, 4.49373, -3.78577)
    upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
    viewCenter: Qt.vector3d(0.0, 0.5, 0.0)

}

OrbitCameraController
{
    id: mainCameraController
    camera: mainCamera
}

components: [
    RenderSettings {

        Viewport {
            normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
            RenderSurfaceSelector {
                CameraSelector {
                    id: cameraSelector
                    camera: mainCamera
                    FrustumCulling {
                        ClearBuffers {
                            buffers: ClearBuffers.AllBuffers
                            clearColor: "#444449"
                            NoDraw {}
                        }
                        LayerFilter {
                            filterMode: LayerFilter.DiscardAnyMatchingLayers
                            layers: [topLayer]
                        }
                        LayerFilter {
                            filterMode: LayerFilter.AcceptAnyMatchingLayers
                            layers: [topLayer]
                            ClearBuffers {
                                buffers: ClearBuffers.DepthBuffer
                            }
                        }
                    }
                }
            }
        }      
    },
    InputSettings {}
]

Layer {
    id: topLayer
    recursive: true
}

ListModel {
    id: entityModel
    ListElement { x:0;y:0;z:0 }
}

NodeInstantiator
{
    id:instance

    model: entityModel

    delegate: Entity {
        id: sphereEntity
        components: [
            SphereMesh
            {
                id:sphereMesh
                radius: 0.3
            },

            PhongMaterial
            {
                id: materialSphere
                ambient:"red"
            },

            Transform {
                id: transform
                translation:Qt.vector3d(x, y, z)
            }
        ]
    }
}

MouseDevice
{
    id: mouseDev
}

MouseHandler
{
    id: mouseHandler
    sourceDevice: mouseDev

    onPressed:
    {
        x++;
        entityModel.append({"x":x,"y":0.0,"z": Math.random()})
    }
}
}

Output screenshot

When the mouse is clicked in my Scene3D, one sphere is displayed.

I don't know how to delete a specific Entity or create undo/redo effect by hitting Ctrl+Z and Ctrl+Shift+Z in Qt3d. Thanks.


Solution

  • One approach is to maintain a global list of Qt.vector3d elements and use it to record the position of the spheres that are removed with the "Undo" operation:

    The "Redo" operation simply does the opposite:

    RootEntity.qml:

    import QtQuick 2.0
    
    import QtQml.Models 2.15
    
    import Qt3D.Core 2.12
    import Qt3D.Render 2.12
    import Qt3D.Extras 2.12
    import Qt3D.Input 2.12
    
    Entity {
        id: root
    
        // global list of Qt.vector3d elements that store the location of the spheres that are removed
        property variant removedSpheres : []
    
        // x-coordinate of the next sphere that will be added
        property double x : 0.0
    
        Camera {
            id: mainCamera
            projectionType: CameraLens.PerspectiveProjection
            fieldOfView: 45
            aspectRatio: 16/9
            nearPlane : 0.1
            farPlane : 1000.0
            position: Qt.vector3d(0.0, 4.49373, -3.78577)
            upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
            viewCenter: Qt.vector3d(0.0, 0.5, 0.0)
        }
    
        OrbitCameraController {
            id: mainCameraController
            camera: mainCamera
        }
    
        components: [
            RenderSettings {
    
                Viewport {
                    normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
                    RenderSurfaceSelector {
                        CameraSelector {
                            id: cameraSelector
                            camera: mainCamera
                            FrustumCulling {
                                ClearBuffers {
                                    buffers: ClearBuffers.AllBuffers
                                    clearColor: "#444449"
                                    NoDraw {}
                                }
                                LayerFilter {
                                    filterMode: LayerFilter.DiscardAnyMatchingLayers
                                    layers: [topLayer]
                                }
                                LayerFilter {
                                    filterMode: LayerFilter.AcceptAnyMatchingLayers
                                    layers: [topLayer]
                                    ClearBuffers {
                                        buffers: ClearBuffers.DepthBuffer
                                    }
                                }
                            }
                        }
                    }
                }
            },
            InputSettings {}
        ]
    
        Layer {
            id: topLayer
            recursive: true
        }
    
        ListModel {
            id: entityModel
    
            ListElement { x: 0; y: 0; z: 0 }
        }
    
        NodeInstantiator {
            id: instance
    
            model: entityModel
    
            delegate: Entity {
                id: sphereEntity
    
                components: [
                    SphereMesh { id:sphereMesh; radius: 0.3 },
    
                    PhongMaterial { id: materialSphere; ambient:"red" },
    
                    Transform { id: transform; translation:Qt.vector3d(x, y, z) }
                ]
            }
        }
    
        MouseDevice {
            id: mouseDev
        }
    
        MouseHandler {
            id: mouseHandler
            sourceDevice: mouseDev
    
            onPressed:
            {
                if (mouse.button === Qt.LeftButton)
                {
                    console.log("LeftButton: new sphere")
    
                    // add new sphere
                    entityModel.append( {"x" : ++root.x, "y" : 0.0, "z" : Math.random()} )
                }
    
                if (mouse.button === Qt.MiddleButton)
                {
                    console.log("MiddleButton: clear spheres")
    
                    // removes all spheres (can't be undone)
                    root.x = 0;
                    entityModel.clear();
                    removedSpheres.length = 0;
                }
            }
        }
    
        KeyboardDevice {
            id: keyboardDev
        }
    
        KeyboardHandler {
            id: keyboardHandler
            sourceDevice: keyboardDev
            focus: true
    
            onPressed: {
                // handle CTRL+Z: undo
                if (event.key === Qt.Key_Z && (event.modifiers & Qt.ControlModifier))
                {
                    console.log("CTRL+Z")
    
                    // remove the last sphere added to the screen
                    let lastIdx = entityModel.count - 1;
                    if (lastIdx >= 0)
                    {
                        // save sphere position before removal
                        removedSpheres.push(Qt.vector3d(entityModel.get(lastIdx).x, entityModel.get(lastIdx).y, entityModel.get(lastIdx).z));
    
                        // remove sphere from the model
                        entityModel.remove(lastIdx);
                    }
                }
    
                // handle CTRL+Y: redo
                if (event.key === Qt.Key_Y && (event.modifiers & Qt.ControlModifier))
                {
                    console.log("CTRL+Y")
    
                    // add the last sphere removed back into the model
                    if (removedSpheres.length > 0)
                    {
                        // add the sphere
                        let lastIdx = removedSpheres.length - 1;
                        entityModel.append( {"x" : removedSpheres[lastIdx].x, "y" : removedSpheres[lastIdx].y, "z" : removedSpheres[lastIdx].z} )
    
                        // erase the last item added to removedSpheres
                        removedSpheres.pop()
                    }
                }
            }
        }
    
    }