ProcessingConfig.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. """
  2. ***************************************************************************
  3. ProcessingConfig.py
  4. ---------------------
  5. Date : August 2012
  6. Copyright : (C) 2012 by Victor Olaya
  7. Email : volayaf at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Victor Olaya'
  18. __date__ = 'August 2012'
  19. __copyright__ = '(C) 2012, Victor Olaya'
  20. import os
  21. import tempfile
  22. from qgis.PyQt.QtCore import QCoreApplication, QObject, pyqtSignal
  23. from qgis.core import (NULL,
  24. QgsApplication,
  25. QgsSettings,
  26. QgsVectorFileWriter,
  27. QgsRasterFileWriter,
  28. QgsProcessingUtils)
  29. from processing.tools.system import defaultOutputFolder
  30. import processing.tools.dataobjects
  31. from multiprocessing import cpu_count
  32. class SettingsWatcher(QObject):
  33. settingsChanged = pyqtSignal()
  34. settingsWatcher = SettingsWatcher()
  35. class ProcessingConfig:
  36. OUTPUT_FOLDER = 'OUTPUTS_FOLDER'
  37. RASTER_STYLE = 'RASTER_STYLE'
  38. VECTOR_POINT_STYLE = 'VECTOR_POINT_STYLE'
  39. VECTOR_LINE_STYLE = 'VECTOR_LINE_STYLE'
  40. VECTOR_POLYGON_STYLE = 'VECTOR_POLYGON_STYLE'
  41. FILTER_INVALID_GEOMETRIES = 'FILTER_INVALID_GEOMETRIES'
  42. PREFER_FILENAME_AS_LAYER_NAME = 'prefer-filename-as-layer-name'
  43. KEEP_DIALOG_OPEN = 'KEEP_DIALOG_OPEN'
  44. PRE_EXECUTION_SCRIPT = 'PRE_EXECUTION_SCRIPT'
  45. POST_EXECUTION_SCRIPT = 'POST_EXECUTION_SCRIPT'
  46. SHOW_CRS_DEF = 'SHOW_CRS_DEF'
  47. WARN_UNMATCHING_CRS = 'WARN_UNMATCHING_CRS'
  48. SHOW_PROVIDERS_TOOLTIP = 'SHOW_PROVIDERS_TOOLTIP'
  49. SHOW_ALGORITHMS_KNOWN_ISSUES = 'SHOW_ALGORITHMS_KNOWN_ISSUES'
  50. MAX_THREADS = 'MAX_THREADS'
  51. DEFAULT_OUTPUT_RASTER_LAYER_EXT = 'default-output-raster-layer-ext'
  52. DEFAULT_OUTPUT_VECTOR_LAYER_EXT = 'default-output-vector-layer-ext'
  53. TEMP_PATH = 'temp-path'
  54. RESULTS_GROUP_NAME = 'RESULTS_GROUP_NAME'
  55. VECTOR_FEATURE_COUNT = 'VECTOR_FEATURE_COUNT'
  56. settings = {}
  57. settingIcons = {}
  58. @staticmethod
  59. def initialize():
  60. icon = QgsApplication.getThemeIcon("/processingAlgorithm.svg")
  61. ProcessingConfig.settingIcons['General'] = icon
  62. ProcessingConfig.addSetting(Setting(
  63. ProcessingConfig.tr('General'),
  64. ProcessingConfig.KEEP_DIALOG_OPEN,
  65. ProcessingConfig.tr('Keep dialog open after running an algorithm'), True))
  66. ProcessingConfig.addSetting(Setting(
  67. ProcessingConfig.tr('General'),
  68. ProcessingConfig.PREFER_FILENAME_AS_LAYER_NAME,
  69. ProcessingConfig.tr('Prefer output filename for layer names'), True,
  70. hasSettingEntry=True))
  71. ProcessingConfig.addSetting(Setting(
  72. ProcessingConfig.tr('General'),
  73. ProcessingConfig.SHOW_PROVIDERS_TOOLTIP,
  74. ProcessingConfig.tr('Show tooltip when there are disabled providers'), True))
  75. ProcessingConfig.addSetting(Setting(
  76. ProcessingConfig.tr('General'),
  77. ProcessingConfig.OUTPUT_FOLDER,
  78. ProcessingConfig.tr('Output folder'), defaultOutputFolder(),
  79. valuetype=Setting.FOLDER))
  80. ProcessingConfig.addSetting(Setting(
  81. ProcessingConfig.tr('General'),
  82. ProcessingConfig.SHOW_CRS_DEF,
  83. ProcessingConfig.tr('Show layer CRS definition in selection boxes'), True))
  84. ProcessingConfig.addSetting(Setting(
  85. ProcessingConfig.tr('General'),
  86. ProcessingConfig.WARN_UNMATCHING_CRS,
  87. ProcessingConfig.tr("Warn before executing if parameter CRS's do not match"), True))
  88. ProcessingConfig.addSetting(Setting(
  89. ProcessingConfig.tr('General'),
  90. ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES,
  91. ProcessingConfig.tr("Show algorithms with known issues"), False))
  92. ProcessingConfig.addSetting(Setting(
  93. ProcessingConfig.tr('General'),
  94. ProcessingConfig.RASTER_STYLE,
  95. ProcessingConfig.tr('Style for raster layers'), '',
  96. valuetype=Setting.FILE))
  97. ProcessingConfig.addSetting(Setting(
  98. ProcessingConfig.tr('General'),
  99. ProcessingConfig.VECTOR_POINT_STYLE,
  100. ProcessingConfig.tr('Style for point layers'), '',
  101. valuetype=Setting.FILE))
  102. ProcessingConfig.addSetting(Setting(
  103. ProcessingConfig.tr('General'),
  104. ProcessingConfig.VECTOR_LINE_STYLE,
  105. ProcessingConfig.tr('Style for line layers'), '',
  106. valuetype=Setting.FILE))
  107. ProcessingConfig.addSetting(Setting(
  108. ProcessingConfig.tr('General'),
  109. ProcessingConfig.VECTOR_POLYGON_STYLE,
  110. ProcessingConfig.tr('Style for polygon layers'), '',
  111. valuetype=Setting.FILE))
  112. ProcessingConfig.addSetting(Setting(
  113. ProcessingConfig.tr('General'),
  114. ProcessingConfig.PRE_EXECUTION_SCRIPT,
  115. ProcessingConfig.tr('Pre-execution script'), '',
  116. valuetype=Setting.FILE))
  117. ProcessingConfig.addSetting(Setting(
  118. ProcessingConfig.tr('General'),
  119. ProcessingConfig.POST_EXECUTION_SCRIPT,
  120. ProcessingConfig.tr('Post-execution script'), '',
  121. valuetype=Setting.FILE))
  122. invalidFeaturesOptions = [ProcessingConfig.tr('Do not filter (better performance)'),
  123. ProcessingConfig.tr('Skip (ignore) features with invalid geometries'),
  124. ProcessingConfig.tr('Stop algorithm execution when a geometry is invalid')]
  125. ProcessingConfig.addSetting(Setting(
  126. ProcessingConfig.tr('General'),
  127. ProcessingConfig.FILTER_INVALID_GEOMETRIES,
  128. ProcessingConfig.tr('Invalid features filtering'),
  129. invalidFeaturesOptions[2],
  130. valuetype=Setting.SELECTION,
  131. options=invalidFeaturesOptions))
  132. threads = QgsApplication.maxThreads() # if user specified limit for rendering, lets keep that as default here, otherwise max
  133. threads = cpu_count() if threads == -1 else threads # if unset, maxThreads() returns -1
  134. ProcessingConfig.addSetting(Setting(
  135. ProcessingConfig.tr('General'),
  136. ProcessingConfig.MAX_THREADS,
  137. ProcessingConfig.tr('Max Threads'), threads,
  138. valuetype=Setting.INT))
  139. extensions = QgsVectorFileWriter.supportedFormatExtensions()
  140. ProcessingConfig.addSetting(Setting(
  141. ProcessingConfig.tr('General'),
  142. ProcessingConfig.DEFAULT_OUTPUT_VECTOR_LAYER_EXT,
  143. ProcessingConfig.tr('Default output vector layer extension'),
  144. QgsVectorFileWriter.supportedFormatExtensions()[0],
  145. valuetype=Setting.SELECTION,
  146. options=extensions,
  147. hasSettingEntry=True))
  148. extensions = QgsRasterFileWriter.supportedFormatExtensions()
  149. ProcessingConfig.addSetting(Setting(
  150. ProcessingConfig.tr('General'),
  151. ProcessingConfig.DEFAULT_OUTPUT_RASTER_LAYER_EXT,
  152. ProcessingConfig.tr('Default output raster layer extension'),
  153. 'tif',
  154. valuetype=Setting.SELECTION,
  155. options=extensions,
  156. hasSettingEntry=True))
  157. ProcessingConfig.addSetting(Setting(
  158. ProcessingConfig.tr('General'),
  159. ProcessingConfig.TEMP_PATH,
  160. ProcessingConfig.tr('Override temporary output folder path'), None,
  161. valuetype=Setting.FOLDER,
  162. placeholder=ProcessingConfig.tr('Leave blank for default'),
  163. hasSettingEntry=True))
  164. ProcessingConfig.addSetting(Setting(
  165. ProcessingConfig.tr('General'),
  166. ProcessingConfig.RESULTS_GROUP_NAME,
  167. ProcessingConfig.tr("Results group name"),
  168. "",
  169. valuetype=Setting.STRING,
  170. placeholder=ProcessingConfig.tr("Leave blank to avoid loading results in a predetermined group")
  171. ))
  172. ProcessingConfig.addSetting(Setting(
  173. ProcessingConfig.tr('General'),
  174. ProcessingConfig.VECTOR_FEATURE_COUNT,
  175. ProcessingConfig.tr('Show feature count for output vector layers'), False))
  176. @staticmethod
  177. def setGroupIcon(group, icon):
  178. ProcessingConfig.settingIcons[group] = icon
  179. @staticmethod
  180. def getGroupIcon(group):
  181. if group == ProcessingConfig.tr('General'):
  182. return QgsApplication.getThemeIcon("/processingAlgorithm.svg")
  183. if group in ProcessingConfig.settingIcons:
  184. return ProcessingConfig.settingIcons[group]
  185. else:
  186. return QgsApplication.getThemeIcon("/processingAlgorithm.svg")
  187. @staticmethod
  188. def addSetting(setting):
  189. ProcessingConfig.settings[setting.name] = setting
  190. @staticmethod
  191. def removeSetting(name):
  192. del ProcessingConfig.settings[name]
  193. @staticmethod
  194. def getSettings():
  195. '''Return settings as a dict with group names as keys and lists of settings as values'''
  196. settings = {}
  197. for setting in list(ProcessingConfig.settings.values()):
  198. if setting.group not in settings:
  199. group = []
  200. settings[setting.group] = group
  201. else:
  202. group = settings[setting.group]
  203. group.append(setting)
  204. return settings
  205. @staticmethod
  206. def readSettings():
  207. for setting in list(ProcessingConfig.settings.values()):
  208. setting.read()
  209. @staticmethod
  210. def getSetting(name, readable=False):
  211. if name not in list(ProcessingConfig.settings.keys()):
  212. return None
  213. v = ProcessingConfig.settings[name].value
  214. try:
  215. if v == NULL:
  216. v = None
  217. except:
  218. pass
  219. if ProcessingConfig.settings[name].valuetype != Setting.SELECTION:
  220. return v
  221. if readable:
  222. return v
  223. return ProcessingConfig.settings[name].options.index(v)
  224. @staticmethod
  225. def setSettingValue(name, value):
  226. if name in list(ProcessingConfig.settings.keys()):
  227. if ProcessingConfig.settings[name].valuetype == Setting.SELECTION:
  228. ProcessingConfig.settings[name].setValue(ProcessingConfig.settings[name].options[value])
  229. else:
  230. ProcessingConfig.settings[name].setValue(value)
  231. ProcessingConfig.settings[name].save()
  232. @staticmethod
  233. def tr(string, context=''):
  234. if context == '':
  235. context = 'ProcessingConfig'
  236. return QCoreApplication.translate(context, string)
  237. class Setting:
  238. """A simple config parameter that will appear on the config dialog.
  239. """
  240. STRING = 0
  241. FILE = 1
  242. FOLDER = 2
  243. SELECTION = 3
  244. FLOAT = 4
  245. INT = 5
  246. MULTIPLE_FOLDERS = 6
  247. def __init__(self, group, name, description, default, hidden=False, valuetype=None,
  248. validator=None, options=None, placeholder="", hasSettingEntry=False):
  249. """
  250. hasSettingEntry is true if the given setting is part of QgsSettingsRegistry entries
  251. """
  252. self.group = group
  253. self.name = name
  254. self.qname = ("qgis/configuration/" if hasSettingEntry else "Processing/Configuration/") + self.name
  255. self.description = description
  256. self.default = default
  257. self.hidden = hidden
  258. self.valuetype = valuetype
  259. self.options = options
  260. self.placeholder = placeholder
  261. if self.valuetype is None:
  262. if isinstance(default, int):
  263. self.valuetype = self.INT
  264. elif isinstance(default, float):
  265. self.valuetype = self.FLOAT
  266. if validator is None:
  267. if self.valuetype == self.FLOAT:
  268. def checkFloat(v):
  269. try:
  270. float(v)
  271. except ValueError:
  272. raise ValueError(self.tr('Wrong parameter value:\n{0}').format(v))
  273. validator = checkFloat
  274. elif self.valuetype == self.INT:
  275. def checkInt(v):
  276. try:
  277. int(v)
  278. except ValueError:
  279. raise ValueError(self.tr('Wrong parameter value:\n{0}').format(v))
  280. validator = checkInt
  281. elif self.valuetype in [self.FILE, self.FOLDER]:
  282. def checkFileOrFolder(v):
  283. if v and not os.path.exists(v):
  284. raise ValueError(self.tr('Specified path does not exist:\n{0}').format(v))
  285. validator = checkFileOrFolder
  286. elif self.valuetype == self.MULTIPLE_FOLDERS:
  287. def checkMultipleFolders(v):
  288. folders = v.split(';')
  289. for f in folders:
  290. if f and not os.path.exists(f):
  291. raise ValueError(self.tr('Specified path does not exist:\n{0}').format(f))
  292. validator = checkMultipleFolders
  293. else:
  294. def validator(x):
  295. return True
  296. self.validator = validator
  297. self.value = default
  298. def setValue(self, value):
  299. self.validator(value)
  300. self.value = value
  301. def read(self, qsettings=None):
  302. if not qsettings:
  303. qsettings = QgsSettings()
  304. value = qsettings.value(self.qname, None)
  305. if value is not None:
  306. if isinstance(self.value, bool):
  307. value = str(value).lower() == str(True).lower()
  308. if self.valuetype == self.SELECTION:
  309. try:
  310. self.value = self.options[int(value)]
  311. except:
  312. self.value = self.options[0]
  313. else:
  314. self.value = value
  315. def save(self, qsettings=None):
  316. if not qsettings:
  317. qsettings = QgsSettings()
  318. if self.valuetype == self.SELECTION:
  319. qsettings.setValue(self.qname, self.options.index(self.value))
  320. else:
  321. qsettings.setValue(self.qname, self.value)
  322. def __str__(self):
  323. return self.name + '=' + str(self.value)
  324. def tr(self, string, context=''):
  325. if context == '':
  326. context = 'ProcessingConfig'
  327. return QCoreApplication.translate(context, string)