mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-05 20:31:56 +02:00
[ui] add and use QmlInstantEngine for QML hot reload
This commit is contained in:
parent
4d265a08e2
commit
81398e63a6
2 changed files with 188 additions and 2 deletions
|
@ -5,14 +5,18 @@ from PySide2.QtGui import QGuiApplication
|
||||||
from PySide2.QtQml import QQmlApplicationEngine
|
from PySide2.QtQml import QQmlApplicationEngine
|
||||||
|
|
||||||
from meshroom.ui.reconstruction import Reconstruction
|
from meshroom.ui.reconstruction import Reconstruction
|
||||||
|
from meshroom.ui.utils import QmlInstantEngine
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QGuiApplication(sys.argv)
|
app = QGuiApplication(sys.argv)
|
||||||
url = os.path.join(os.path.dirname(__file__), "qml", "main.qml")
|
|
||||||
engine = QQmlApplicationEngine()
|
|
||||||
|
|
||||||
|
qmlDir = os.path.join(os.path.dirname(__file__), "qml")
|
||||||
|
url = os.path.join(qmlDir, "main.qml")
|
||||||
|
engine = QmlInstantEngine()
|
||||||
|
engine.addFilesFromDirectory(qmlDir)
|
||||||
|
engine.setWatching(os.environ.get("MESHROOM_INSTANT_CODING", False))
|
||||||
r = Reconstruction()
|
r = Reconstruction()
|
||||||
engine.rootContext().setContextProperty("_reconstruction", r)
|
engine.rootContext().setContextProperty("_reconstruction", r)
|
||||||
|
|
||||||
|
|
182
meshroom/ui/utils.py
Executable file
182
meshroom/ui/utils.py
Executable file
|
@ -0,0 +1,182 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from PySide2.QtCore import QFileSystemWatcher, QUrl, Slot
|
||||||
|
from PySide2.QtQml import QQmlApplicationEngine
|
||||||
|
|
||||||
|
|
||||||
|
class QmlInstantEngine(QQmlApplicationEngine):
|
||||||
|
"""
|
||||||
|
QmlInstantEngine is an utility class helping developing QML applications.
|
||||||
|
It reloads itself whenever one of the watched source files is modified.
|
||||||
|
As it consumes resources, make sure to disable file watching in production mode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sourceFile="", watching=True, verbose=False, parent=None):
|
||||||
|
"""
|
||||||
|
watching -- Defines whether the watcher is active (default: True)
|
||||||
|
verbose -- if True, output log infos (default: False)
|
||||||
|
"""
|
||||||
|
super(QmlInstantEngine, self).__init__(parent)
|
||||||
|
|
||||||
|
self._fileWatcher = QFileSystemWatcher() # Internal Qt File Watcher
|
||||||
|
self._sourceFile = ""
|
||||||
|
self._watchedFiles = [] # Internal watched files list
|
||||||
|
self._verbose = verbose # Verbose bool
|
||||||
|
self._watching = False #
|
||||||
|
self._extensions = ["qml", "js"] # File extensions that defines files to watch when adding a folder
|
||||||
|
|
||||||
|
self._rootItem = None
|
||||||
|
|
||||||
|
def onObjectCreated(root, url):
|
||||||
|
# Restore root item geometry
|
||||||
|
if self._rootItem:
|
||||||
|
root.setGeometry(self._rootItem.geometry())
|
||||||
|
self._rootItem.deleteLater()
|
||||||
|
self._rootItem = root
|
||||||
|
|
||||||
|
self.objectCreated.connect(onObjectCreated)
|
||||||
|
|
||||||
|
# Update the watching status
|
||||||
|
self.setWatching(watching)
|
||||||
|
|
||||||
|
if sourceFile:
|
||||||
|
self.load(sourceFile)
|
||||||
|
|
||||||
|
def load(self, sourceFile):
|
||||||
|
self._sourceFile = sourceFile
|
||||||
|
super(QmlInstantEngine, self).load(sourceFile)
|
||||||
|
|
||||||
|
def setWatching(self, watchValue):
|
||||||
|
"""
|
||||||
|
Enable (True) or disable (False) the file watching.
|
||||||
|
Tip: file watching should be enable only when developing.
|
||||||
|
"""
|
||||||
|
if self._watching is watchValue:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._watching = watchValue
|
||||||
|
# Enable the watcher
|
||||||
|
if self._watching:
|
||||||
|
# 1. Add internal list of files to the internal Qt File Watcher
|
||||||
|
self.addFiles(self._watchedFiles)
|
||||||
|
# 2. Connect 'filechanged' signal
|
||||||
|
self._fileWatcher.fileChanged.connect(self.onFileChanged)
|
||||||
|
|
||||||
|
# Disabling the watcher
|
||||||
|
else:
|
||||||
|
# 1. Remove all files in the internal Qt File Watcher
|
||||||
|
self._fileWatcher.removePaths(self._watchedFiles)
|
||||||
|
# 2. Disconnect 'filechanged' signal
|
||||||
|
self._fileWatcher.fileChanged.disconnect(self.onFileChanged)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def watchedExtensions(self):
|
||||||
|
""" Returns the list of extensions used when using addFilesFromDirectory. """
|
||||||
|
return self._extensions
|
||||||
|
|
||||||
|
@watchedExtensions.setter
|
||||||
|
def watchedExtensions(self, extensions):
|
||||||
|
""" Set the list of extensions to search for when using addFilesFromDirectory. """
|
||||||
|
self._extensions = extensions
|
||||||
|
|
||||||
|
def setVerbose(self, verboseValue):
|
||||||
|
""" Activate (True) or desactivate (False) the verbose. """
|
||||||
|
self._verbose = verboseValue
|
||||||
|
|
||||||
|
def addFile(self, filename):
|
||||||
|
"""
|
||||||
|
Add the given 'filename' to the watched files list.
|
||||||
|
'filename' can be an absolute or relative path (str and QUrl accepted)
|
||||||
|
"""
|
||||||
|
# Deal with QUrl type
|
||||||
|
# NOTE: happens when using the source() method on a QQuickView
|
||||||
|
if isinstance(filename, QUrl):
|
||||||
|
filename = filename.path()
|
||||||
|
|
||||||
|
# Make sure the file exists
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
raise ValueError("addFile: file %s doesn't exist." % filename)
|
||||||
|
|
||||||
|
# Return if the file is already in our internal list
|
||||||
|
if filename in self._watchedFiles:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add this file to the internal files list
|
||||||
|
self._watchedFiles.append(filename)
|
||||||
|
# And, if watching is active, add it to the internal watcher as well
|
||||||
|
if self._watching:
|
||||||
|
if self._verbose:
|
||||||
|
print("instantcoding: addPath", filename)
|
||||||
|
self._fileWatcher.addPath(filename)
|
||||||
|
|
||||||
|
def addFiles(self, filenames):
|
||||||
|
"""
|
||||||
|
Add the given 'filenames' to the watched files list.
|
||||||
|
filenames -- a list of absolute or relative paths (str and QUrl accepted)
|
||||||
|
"""
|
||||||
|
# Convert to list
|
||||||
|
if not isinstance(filenames, list):
|
||||||
|
filenames = [filenames]
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
self.addFile(filename)
|
||||||
|
|
||||||
|
def addFilesFromDirectory(self, dirname, recursive=False):
|
||||||
|
"""
|
||||||
|
Add files from the given directory name 'dirname'.
|
||||||
|
dirname -- an absolute or a relative path
|
||||||
|
recursive -- if True, will search inside each subdirectories recursively.
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(dirname):
|
||||||
|
raise RuntimeError("addFilesFromDirectory : %s is not a valid directory." % dirname)
|
||||||
|
|
||||||
|
if recursive:
|
||||||
|
for dirpath, dirnames, filenames in os.walk(dirname):
|
||||||
|
for filename in filenames:
|
||||||
|
# Removing the starting dot from extension
|
||||||
|
if os.path.splitext(filename)[1][1:] in self._extensions:
|
||||||
|
self.addFile(os.path.join(dirpath, filename))
|
||||||
|
else:
|
||||||
|
filenames = os.listdir(dirname)
|
||||||
|
filenames = [os.path.join(dirname, filename) for filename in filenames if
|
||||||
|
os.path.splitext(filename)[1][1:] in self._extensions]
|
||||||
|
self.addFiles(filenames)
|
||||||
|
|
||||||
|
def removeFile(self, filename):
|
||||||
|
"""
|
||||||
|
Remove the given 'filename' from the watched file list.
|
||||||
|
Tip: make sure to use relative or absolute path according to how you add this file.
|
||||||
|
"""
|
||||||
|
if filename in self._watchedFiles:
|
||||||
|
self._watchedFiles.remove(filename)
|
||||||
|
if self._watching:
|
||||||
|
self._fileWatcher.removePath(filename)
|
||||||
|
|
||||||
|
def getRegisteredFiles(self):
|
||||||
|
""" Returns the list of watched files """
|
||||||
|
return self._watchedFiles
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def onFileChanged(self, filepath):
|
||||||
|
""" Handle changes in a watched file. """
|
||||||
|
if self._verbose:
|
||||||
|
print("Source file changed : ", filepath)
|
||||||
|
# Clear the QQuickEngine cache
|
||||||
|
self.clearComponentCache()
|
||||||
|
# Remove the modified file from the watched list
|
||||||
|
self.removeFile(filepath)
|
||||||
|
cptTry = 0
|
||||||
|
|
||||||
|
# Make sure file is available before doing anything
|
||||||
|
# NOTE: useful to handle editors (Qt Creator) that deletes the source file and
|
||||||
|
# creates a new one when saving
|
||||||
|
while not os.path.exists(filepath) and cptTry < 10:
|
||||||
|
time.sleep(0.1)
|
||||||
|
cptTry += 1
|
||||||
|
|
||||||
|
print("Reloading ", self._sourceFile)
|
||||||
|
self.load(self._sourceFile)
|
||||||
|
|
||||||
|
# Finally, read the modified file to the watch system
|
||||||
|
self.addFile(filepath)
|
Loading…
Add table
Add a link
Reference in a new issue