qml.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # Copyright (C) 2018 The Qt Company Ltd.
  2. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
  3. from __future__ import annotations
  4. """pyside6-qml tool implementation. This tool mimics the capabilities of qml runtime utility
  5. for python and enables quick protyping with python modules"""
  6. import argparse
  7. import importlib.util
  8. import logging
  9. import sys
  10. import os
  11. from pathlib import Path
  12. from pprint import pprint
  13. from PySide6.QtCore import QCoreApplication, Qt, QLibraryInfo, QUrl, SignalInstance
  14. from PySide6.QtGui import QGuiApplication, QSurfaceFormat
  15. from PySide6.QtQml import QQmlApplicationEngine, QQmlComponent
  16. from PySide6.QtQuick import QQuickView, QQuickItem
  17. from PySide6.QtWidgets import QApplication
  18. def import_qml_modules(qml_parent_path: Path, module_paths: list[Path] = []):
  19. '''
  20. Import all the python modules in the qml_parent_path. This way all the classes
  21. containing the @QmlElement/@QmlNamedElement are also imported
  22. Parameters:
  23. qml_parent_path (Path): Parent directory of the qml file
  24. module_paths (int): user give import paths obtained through cli
  25. '''
  26. search_dir_paths = []
  27. search_file_paths = []
  28. if not module_paths:
  29. search_dir_paths.append(qml_parent_path)
  30. else:
  31. for module_path in module_paths:
  32. if module_path.is_dir():
  33. search_dir_paths.append(module_path)
  34. elif module_path.exists() and module_path.suffix == ".py":
  35. search_file_paths.append(module_path)
  36. def import_module(import_module_paths: set[Path]):
  37. """Import the modules in 'import_module_paths'"""
  38. for module_path in import_module_paths:
  39. module_name = module_path.name[:-3]
  40. _spec = importlib.util.spec_from_file_location(f"{module_name}", module_path)
  41. _module = importlib.util.module_from_spec(_spec)
  42. _spec.loader.exec_module(module=_module)
  43. modules_to_import = set()
  44. for search_path in search_dir_paths:
  45. possible_modules = list(search_path.glob("**/*.py"))
  46. for possible_module in possible_modules:
  47. if possible_module.is_file() and possible_module.name != "__init__.py":
  48. module_parent = str(possible_module.parent)
  49. if module_parent not in sys.path:
  50. sys.path.append(module_parent)
  51. modules_to_import.add(possible_module)
  52. for search_path in search_file_paths:
  53. sys.path.append(str(search_path.parent))
  54. modules_to_import.add(search_path)
  55. import_module(import_module_paths=modules_to_import)
  56. def print_configurations():
  57. return "Built-in configurations \n\t default \n\t resizeToItem"
  58. if __name__ == "__main__":
  59. parser = argparse.ArgumentParser(
  60. description="This tools mimics the capabilities of qml runtime utility by directly"
  61. " invoking QQmlEngine/QQuickView. It enables quick prototyping with qml files.",
  62. formatter_class=argparse.RawTextHelpFormatter
  63. )
  64. parser.add_argument(
  65. "file",
  66. type=lambda p: Path(p).absolute(),
  67. help="Path to qml file to display",
  68. )
  69. parser.add_argument(
  70. "--module-paths", "-I",
  71. type=lambda p: Path(p).absolute(),
  72. nargs="+",
  73. help="Specify space separated folder/file paths where the Qml classes are defined. By"
  74. " default,the parent directory of the qml_path is searched recursively for all .py"
  75. " files and they are imported. Otherwise only the paths give in module paths are"
  76. " searched",
  77. )
  78. parser.add_argument(
  79. "--list-conf",
  80. action="version",
  81. help="List the built-in configurations.",
  82. version=print_configurations()
  83. )
  84. parser.add_argument(
  85. "--apptype", "-a",
  86. choices=["core", "gui", "widget"],
  87. default="gui",
  88. help="Select which application class to use. Default is gui",
  89. )
  90. parser.add_argument(
  91. "--config", "-c",
  92. choices=["default", "resizeToItem"],
  93. default="default",
  94. help="Select the built-in configurations.",
  95. )
  96. parser.add_argument(
  97. "--rhi", "-r",
  98. choices=["vulkan", "metal", "d3dll", "gl"],
  99. help="Set the backend for the Qt graphics abstraction (RHI).",
  100. )
  101. parser.add_argument(
  102. "--core-profile",
  103. action="store_true",
  104. help="Force use of OpenGL Core Profile.",
  105. )
  106. parser.add_argument(
  107. '-v', '--verbose',
  108. help="Print information about what qml is doing, like specific file URLs being loaded.",
  109. action="store_const", dest="loglevel", const=logging.INFO,
  110. )
  111. gl_group = parser.add_mutually_exclusive_group(required=False)
  112. gl_group.add_argument(
  113. "--gles",
  114. action="store_true",
  115. help="Force use of GLES (AA_UseOpenGLES)",
  116. )
  117. gl_group.add_argument(
  118. "--desktop",
  119. action="store_true",
  120. help="Force use of desktop OpenGL (AA_UseDesktopOpenGL)",
  121. )
  122. gl_group.add_argument(
  123. "--software",
  124. action="store_true",
  125. help="Force use of software rendering(AA_UseSoftwareOpenGL)",
  126. )
  127. gl_group.add_argument(
  128. "--disable-context-sharing",
  129. action="store_true",
  130. help=" Disable the use of a shared GL context for QtQuick Windows",
  131. )
  132. args = parser.parse_args()
  133. apptype = args.apptype
  134. qquick_present = False
  135. with open(args.file) as myfile:
  136. if 'import QtQuick' in myfile.read():
  137. qquick_present = True
  138. # no import QtQuick => QQCoreApplication
  139. if not qquick_present:
  140. apptype = "core"
  141. import_qml_modules(args.file.parent, args.module_paths)
  142. logging.basicConfig(level=args.loglevel)
  143. logging.info(f"qml: {QLibraryInfo.build()}")
  144. logging.info(f"qml: Using built-in configuration: {args.config}")
  145. if args.rhi:
  146. os.environ['QSG_RHI_BACKEND'] = args.rhi
  147. logging.info(f"qml: loading {args.file}")
  148. qml_file = QUrl.fromLocalFile(str(args.file))
  149. if apptype == "gui":
  150. if args.gles:
  151. logging.info("qml: Using attribute AA_UseOpenGLES")
  152. QCoreApplication.setAttribute(Qt.AA_UseOpenGLES)
  153. elif args.desktop:
  154. logging.info("qml: Using attribute AA_UseDesktopOpenGL")
  155. QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL)
  156. elif args.software:
  157. logging.info("qml: Using attribute AA_UseSoftwareOpenGL")
  158. QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)
  159. # context-sharing is enabled by default
  160. if not args.disable_context_sharing:
  161. logging.info("qml: Using attribute AA_ShareOpenGLContexts")
  162. QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
  163. if apptype == "core":
  164. logging.info("qml: Core application")
  165. app = QCoreApplication(sys.argv)
  166. elif apptype == "widgets":
  167. logging.info("qml: Widget application")
  168. app = QApplication(sys.argv)
  169. else:
  170. logging.info("qml: Gui application")
  171. app = QGuiApplication(sys.argv)
  172. engine = QQmlApplicationEngine()
  173. # set OpenGLContextProfile
  174. if apptype == "gui" and args.core_profile:
  175. logging.info("qml: Set profile for QSurfaceFormat as CoreProfile")
  176. surfaceFormat = QSurfaceFormat()
  177. surfaceFormat.setStencilBufferSize(8)
  178. surfaceFormat.setDepthBufferSize(24)
  179. surfaceFormat.setVersion(4, 1)
  180. surfaceFormat.setProfile(QSurfaceFormat.CoreProfile)
  181. QSurfaceFormat.setDefaultFormat(surfaceFormat)
  182. # in the case of QCoreApplication we print the attributes of the object created via
  183. # QQmlComponent and exit
  184. if apptype == "core":
  185. component = QQmlComponent(engine, qml_file)
  186. obj = component.create()
  187. filtered_attributes = {k: v for k, v in vars(obj).items() if type(v) is not SignalInstance}
  188. logging.info("qml: component object attributes are")
  189. pprint(filtered_attributes)
  190. del engine
  191. sys.exit(0)
  192. engine.load(qml_file)
  193. rootObjects = engine.rootObjects()
  194. if not rootObjects:
  195. sys.exit(-1)
  196. qquick_view = False
  197. if isinstance(rootObjects[0], QQuickItem) and qquick_present:
  198. logging.info("qml: loading with QQuickView")
  199. viewer = QQuickView()
  200. viewer.setSource(qml_file)
  201. if args.config != "resizeToItem":
  202. viewer.setResizeMode(QQuickView.SizeRootObjectToView)
  203. else:
  204. viewer.setResizeMode(QQuickView.SizeViewToRootObject)
  205. viewer.show()
  206. qquick_view = True
  207. if not qquick_view:
  208. logging.info("qml: loading with QQmlApplicationEngine")
  209. if args.config == "resizeToItem":
  210. logging.info("qml: Not a QQuickview item. resizeToItem is done by default")
  211. exit_code = app.exec()
  212. del engine
  213. sys.exit(exit_code)