# Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only """pyside6-qml tool implementation. This tool mimics the capabilities of qml runtime utility for python and enables quick protyping with python modules""" import argparse import importlib.util import logging import sys import os from pathlib import Path from pprint import pprint from typing import List, Set from PySide6.QtCore import QCoreApplication, Qt, QLibraryInfo, QUrl, SignalInstance from PySide6.QtGui import QGuiApplication, QSurfaceFormat from PySide6.QtQml import QQmlApplicationEngine, QQmlComponent from PySide6.QtQuick import QQuickView, QQuickItem from PySide6.QtWidgets import QApplication def import_qml_modules(qml_parent_path: Path, module_paths: List[Path] = []): ''' Import all the python modules in the qml_parent_path. This way all the classes containing the @QmlElement/@QmlNamedElement are also imported Parameters: qml_parent_path (Path): Parent directory of the qml file module_paths (int): user give import paths obtained through cli ''' search_dir_paths = [] search_file_paths = [] if not module_paths: search_dir_paths.append(qml_parent_path) else: for module_path in module_paths: if module_path.is_dir(): search_dir_paths.append(module_path) elif module_path.exists() and module_path.suffix == ".py": search_file_paths.append(module_path) def import_module(import_module_paths: Set[Path]): """Import the modules in 'import_module_paths'""" for module_path in import_module_paths: module_name = module_path.name[:-3] _spec = importlib.util.spec_from_file_location(f"{module_name}", module_path) _module = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(module=_module) modules_to_import = set() for search_path in search_dir_paths: possible_modules = list(search_path.glob("**/*.py")) for possible_module in possible_modules: if possible_module.is_file() and possible_module.name != "__init__.py": module_parent = str(possible_module.parent) if module_parent not in sys.path: sys.path.append(module_parent) modules_to_import.add(possible_module) for search_path in search_file_paths: sys.path.append(str(search_path.parent)) modules_to_import.add(search_path) import_module(import_module_paths=modules_to_import) def print_configurations(): return "Built-in configurations \n\t default \n\t resizeToItem" if __name__ == "__main__": parser = argparse.ArgumentParser( description="This tools mimics the capabilities of qml runtime utility by directly" " invoking QQmlEngine/QQuickView. It enables quick prototyping with qml files.", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument( "file", type=lambda p: Path(p).absolute(), help="Path to qml file to display", ) parser.add_argument( "--module-paths", "-I", type=lambda p: Path(p).absolute(), nargs="+", help="Specify space separated folder/file paths where the Qml classes are defined. By" " default,the parent directory of the qml_path is searched recursively for all .py" " files and they are imported. Otherwise only the paths give in module paths are" " searched", ) parser.add_argument( "--list-conf", action="version", help="List the built-in configurations.", version=print_configurations() ) parser.add_argument( "--apptype", "-a", choices=["core", "gui", "widget"], default="gui", help="Select which application class to use. Default is gui", ) parser.add_argument( "--config", "-c", choices=["default", "resizeToItem"], default="default", help="Select the built-in configurations.", ) parser.add_argument( "--rhi", "-r", choices=["vulkan", "metal", "d3dll", "gl"], help="Set the backend for the Qt graphics abstraction (RHI).", ) parser.add_argument( "--core-profile", action="store_true", help="Force use of OpenGL Core Profile.", ) parser.add_argument( '-v', '--verbose', help="Print information about what qml is doing, like specific file URLs being loaded.", action="store_const", dest="loglevel", const=logging.INFO, ) gl_group = parser.add_mutually_exclusive_group(required=False) gl_group.add_argument( "--gles", action="store_true", help="Force use of GLES (AA_UseOpenGLES)", ) gl_group.add_argument( "--desktop", action="store_true", help="Force use of desktop OpenGL (AA_UseDesktopOpenGL)", ) gl_group.add_argument( "--software", action="store_true", help="Force use of software rendering(AA_UseSoftwareOpenGL)", ) gl_group.add_argument( "--disable-context-sharing", action="store_true", help=" Disable the use of a shared GL context for QtQuick Windows", ) args = parser.parse_args() apptype = args.apptype qquick_present = False with open(args.file) as myfile: if 'import QtQuick' in myfile.read(): qquick_present = True # no import QtQuick => QQCoreApplication if not qquick_present: apptype = "core" import_qml_modules(args.file.parent, args.module_paths) logging.basicConfig(level=args.loglevel) logging.info(f"qml: {QLibraryInfo.build()}") logging.info(f"qml: Using built-in configuration: {args.config}") if args.rhi: os.environ['QSG_RHI_BACKEND'] = args.rhi logging.info(f"qml: loading {args.file}") qml_file = QUrl.fromLocalFile(str(args.file)) if apptype == "gui": if args.gles: logging.info("qml: Using attribute AA_UseOpenGLES") QCoreApplication.setAttribute(Qt.AA_UseOpenGLES) elif args.desktop: logging.info("qml: Using attribute AA_UseDesktopOpenGL") QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) elif args.software: logging.info("qml: Using attribute AA_UseSoftwareOpenGL") QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) # context-sharing is enabled by default if not args.disable_context_sharing: logging.info("qml: Using attribute AA_ShareOpenGLContexts") QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) if apptype == "core": logging.info("qml: Core application") app = QCoreApplication(sys.argv) elif apptype == "widgets": logging.info("qml: Widget application") app = QApplication(sys.argv) else: logging.info("qml: Gui application") app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() # set OpenGLContextProfile if apptype == "gui" and args.core_profile: logging.info("qml: Set profile for QSurfaceFormat as CoreProfile") surfaceFormat = QSurfaceFormat() surfaceFormat.setStencilBufferSize(8) surfaceFormat.setDepthBufferSize(24) surfaceFormat.setVersion(4, 1) surfaceFormat.setProfile(QSurfaceFormat.CoreProfile) QSurfaceFormat.setDefaultFormat(surfaceFormat) # in the case of QCoreApplication we print the attributes of the object created via # QQmlComponent and exit if apptype == "core": component = QQmlComponent(engine, qml_file) obj = component.create() filtered_attributes = {k: v for k, v in vars(obj).items() if type(v) is not SignalInstance} logging.info("qml: component object attributes are") pprint(filtered_attributes) del engine sys.exit(0) engine.load(qml_file) rootObjects = engine.rootObjects() if not rootObjects: sys.exit(-1) qquick_view = False if isinstance(rootObjects[0], QQuickItem) and qquick_present: logging.info("qml: loading with QQuickView") viewer = QQuickView() viewer.setSource(qml_file) if args.config != "resizeToItem": viewer.setResizeMode(QQuickView.SizeRootObjectToView) else: viewer.setResizeMode(QQuickView.SizeViewToRootObject) viewer.show() qquick_view = True if not qquick_view: logging.info("qml: loading with QQmlApplicationEngine") if args.config == "resizeToItem": logging.info("qml: Not a QQuickview item. resizeToItem is done by default") sys.exit(app.exec())