QGIS/python/plugins/processing/core/ProcessingConfig.py
2024-11-29 15:38:02 +01:00

490 lines
16 KiB
Python

"""
***************************************************************************
ProcessingConfig.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = "Victor Olaya"
__date__ = "August 2012"
__copyright__ = "(C) 2012, Victor Olaya"
import os
import tempfile
from qgis.PyQt.QtCore import QCoreApplication, QObject, pyqtSignal
from qgis.core import (
NULL,
QgsApplication,
QgsSettings,
QgsVectorFileWriter,
QgsRasterFileWriter,
QgsProcessingUtils,
)
from processing.tools.system import defaultOutputFolder
import processing.tools.dataobjects
from multiprocessing import cpu_count
class SettingsWatcher(QObject):
settingsChanged = pyqtSignal()
settingsWatcher = SettingsWatcher()
class ProcessingConfig:
OUTPUT_FOLDER = "OUTPUTS_FOLDER"
RASTER_STYLE = "RASTER_STYLE"
VECTOR_POINT_STYLE = "VECTOR_POINT_STYLE"
VECTOR_LINE_STYLE = "VECTOR_LINE_STYLE"
VECTOR_POLYGON_STYLE = "VECTOR_POLYGON_STYLE"
FILTER_INVALID_GEOMETRIES = "FILTER_INVALID_GEOMETRIES"
PREFER_FILENAME_AS_LAYER_NAME = "prefer-filename-as-layer-name"
KEEP_DIALOG_OPEN = "KEEP_DIALOG_OPEN"
PRE_EXECUTION_SCRIPT = "PRE_EXECUTION_SCRIPT"
POST_EXECUTION_SCRIPT = "POST_EXECUTION_SCRIPT"
SHOW_CRS_DEF = "SHOW_CRS_DEF"
WARN_UNMATCHING_CRS = "WARN_UNMATCHING_CRS"
SHOW_PROVIDERS_TOOLTIP = "SHOW_PROVIDERS_TOOLTIP"
SHOW_ALGORITHMS_KNOWN_ISSUES = "SHOW_ALGORITHMS_KNOWN_ISSUES"
MAX_THREADS = "MAX_THREADS"
DEFAULT_OUTPUT_RASTER_LAYER_EXT = "default-output-raster-ext"
DEFAULT_OUTPUT_VECTOR_LAYER_EXT = "default-output-vector-ext"
TEMP_PATH = "temp-path"
RESULTS_GROUP_NAME = "RESULTS_GROUP_NAME"
VECTOR_FEATURE_COUNT = "VECTOR_FEATURE_COUNT"
settings = {}
settingIcons = {}
@staticmethod
def initialize():
icon = QgsApplication.getThemeIcon("/processingAlgorithm.svg")
ProcessingConfig.settingIcons["General"] = icon
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.KEEP_DIALOG_OPEN,
ProcessingConfig.tr("Keep dialog open after running an algorithm"),
True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.PREFER_FILENAME_AS_LAYER_NAME,
ProcessingConfig.tr("Prefer output filename for layer names"),
True,
hasSettingEntry=True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.SHOW_PROVIDERS_TOOLTIP,
ProcessingConfig.tr("Show tooltip when there are disabled providers"),
True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.OUTPUT_FOLDER,
ProcessingConfig.tr("Output folder"),
defaultOutputFolder(),
valuetype=Setting.FOLDER,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.SHOW_CRS_DEF,
ProcessingConfig.tr("Show layer CRS definition in selection boxes"),
True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.WARN_UNMATCHING_CRS,
ProcessingConfig.tr(
"Warn before executing if parameter CRS's do not match"
),
True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES,
ProcessingConfig.tr("Show algorithms with known issues"),
False,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.RASTER_STYLE,
ProcessingConfig.tr("Style for raster layers"),
"",
valuetype=Setting.FILE,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.VECTOR_POINT_STYLE,
ProcessingConfig.tr("Style for point layers"),
"",
valuetype=Setting.FILE,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.VECTOR_LINE_STYLE,
ProcessingConfig.tr("Style for line layers"),
"",
valuetype=Setting.FILE,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.VECTOR_POLYGON_STYLE,
ProcessingConfig.tr("Style for polygon layers"),
"",
valuetype=Setting.FILE,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.PRE_EXECUTION_SCRIPT,
ProcessingConfig.tr("Pre-execution script"),
"",
valuetype=Setting.FILE,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.POST_EXECUTION_SCRIPT,
ProcessingConfig.tr("Post-execution script"),
"",
valuetype=Setting.FILE,
)
)
invalidFeaturesOptions = [
ProcessingConfig.tr("Do not filter (better performance)"),
ProcessingConfig.tr("Skip (ignore) features with invalid geometries"),
ProcessingConfig.tr("Stop algorithm execution when a geometry is invalid"),
]
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.FILTER_INVALID_GEOMETRIES,
ProcessingConfig.tr("Invalid features filtering"),
invalidFeaturesOptions[2],
valuetype=Setting.SELECTION,
options=invalidFeaturesOptions,
)
)
threads = (
QgsApplication.maxThreads()
) # if user specified limit for rendering, lets keep that as default here, otherwise max
threads = (
cpu_count() if threads == -1 else threads
) # if unset, maxThreads() returns -1
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.MAX_THREADS,
ProcessingConfig.tr("Max Threads"),
threads,
valuetype=Setting.INT,
)
)
extensions = QgsVectorFileWriter.supportedFormatExtensions()
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.DEFAULT_OUTPUT_VECTOR_LAYER_EXT,
ProcessingConfig.tr("Default output vector layer extension"),
QgsVectorFileWriter.supportedFormatExtensions()[0],
valuetype=Setting.SELECTION_STORE_STRING,
options=extensions,
hasSettingEntry=True,
)
)
extensions = QgsRasterFileWriter.supportedFormatExtensions()
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.DEFAULT_OUTPUT_RASTER_LAYER_EXT,
ProcessingConfig.tr("Default output raster layer extension"),
"tif",
valuetype=Setting.SELECTION_STORE_STRING,
options=extensions,
hasSettingEntry=True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.TEMP_PATH,
ProcessingConfig.tr("Override temporary output folder path"),
None,
valuetype=Setting.FOLDER,
placeholder=ProcessingConfig.tr("Leave blank for default"),
hasSettingEntry=True,
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.RESULTS_GROUP_NAME,
ProcessingConfig.tr("Results group name"),
"",
valuetype=Setting.STRING,
placeholder=ProcessingConfig.tr(
"Leave blank to avoid loading results in a predetermined group"
),
)
)
ProcessingConfig.addSetting(
Setting(
ProcessingConfig.tr("General"),
ProcessingConfig.VECTOR_FEATURE_COUNT,
ProcessingConfig.tr("Show feature count for output vector layers"),
False,
)
)
@staticmethod
def setGroupIcon(group, icon):
ProcessingConfig.settingIcons[group] = icon
@staticmethod
def getGroupIcon(group):
if group == ProcessingConfig.tr("General"):
return QgsApplication.getThemeIcon("/processingAlgorithm.svg")
if group in ProcessingConfig.settingIcons:
return ProcessingConfig.settingIcons[group]
else:
return QgsApplication.getThemeIcon("/processingAlgorithm.svg")
@staticmethod
def addSetting(setting):
ProcessingConfig.settings[setting.name] = setting
@staticmethod
def removeSetting(name):
del ProcessingConfig.settings[name]
@staticmethod
def getSettings():
"""Return settings as a dict with group names as keys and lists of settings as values"""
settings = {}
for setting in list(ProcessingConfig.settings.values()):
if setting.group not in settings:
group = []
settings[setting.group] = group
else:
group = settings[setting.group]
group.append(setting)
return settings
@staticmethod
def readSettings():
for setting in list(ProcessingConfig.settings.values()):
setting.read()
@staticmethod
def getSetting(name, readable=False):
if name not in list(ProcessingConfig.settings.keys()):
return None
v = ProcessingConfig.settings[name].value
try:
if v == NULL:
v = None
except:
pass
if ProcessingConfig.settings[name].valuetype != Setting.SELECTION:
return v
if readable:
return v
return ProcessingConfig.settings[name].options.index(v)
@staticmethod
def setSettingValue(name, value):
if name in list(ProcessingConfig.settings.keys()):
if ProcessingConfig.settings[name].valuetype == Setting.SELECTION:
ProcessingConfig.settings[name].setValue(
ProcessingConfig.settings[name].options[value]
)
else:
ProcessingConfig.settings[name].setValue(value)
ProcessingConfig.settings[name].save()
@staticmethod
def tr(string, context=""):
if context == "":
context = "ProcessingConfig"
return QCoreApplication.translate(context, string)
class Setting:
"""A simple config parameter that will appear on the config dialog."""
STRING = 0
FILE = 1
FOLDER = 2
SELECTION = 3
FLOAT = 4
INT = 5
MULTIPLE_FOLDERS = 6
SELECTION_STORE_STRING = 7
def __init__(
self,
group,
name,
description,
default,
hidden=False,
valuetype=None,
validator=None,
options=None,
placeholder="",
hasSettingEntry=False,
):
"""
hasSettingEntry is true if the given setting is part of QgsSettingsRegistry entries
"""
self.group = group
self.name = name
self.qname = (
"qgis/configuration/" if hasSettingEntry else "Processing/Configuration/"
) + self.name
self.description = description
self.default = default
self.hidden = hidden
self.valuetype = valuetype
self.options = options
self.placeholder = placeholder
if self.valuetype is None:
if isinstance(default, int):
self.valuetype = self.INT
elif isinstance(default, float):
self.valuetype = self.FLOAT
if validator is None:
if self.valuetype == self.FLOAT:
def checkFloat(v):
try:
float(v)
except ValueError:
raise ValueError(
self.tr("Wrong parameter value:\n{0}").format(v)
)
validator = checkFloat
elif self.valuetype == self.INT:
def checkInt(v):
try:
int(v)
except ValueError:
raise ValueError(
self.tr("Wrong parameter value:\n{0}").format(v)
)
validator = checkInt
elif self.valuetype in [self.FILE, self.FOLDER]:
def checkFileOrFolder(v):
if v and not os.path.exists(v):
raise ValueError(
self.tr("Specified path does not exist:\n{0}").format(v)
)
validator = checkFileOrFolder
elif self.valuetype == self.MULTIPLE_FOLDERS:
def checkMultipleFolders(v):
folders = v.split(";")
for f in folders:
if f and not os.path.exists(f):
raise ValueError(
self.tr("Specified path does not exist:\n{0}").format(f)
)
validator = checkMultipleFolders
else:
def validator(x):
return True
self.validator = validator
self.value = default
def setValue(self, value):
self.validator(value)
self.value = value
def read(self, qsettings=None):
if not qsettings:
qsettings = QgsSettings()
value = qsettings.value(self.qname, None)
if value is not None:
if isinstance(self.value, bool):
value = str(value).lower() == str(True).lower()
if self.valuetype == self.SELECTION:
try:
self.value = self.options[int(value)]
except:
self.value = self.options[0]
else:
self.value = value
def save(self, qsettings=None):
if not qsettings:
qsettings = QgsSettings()
if self.value == self.default:
qsettings.remove(self.qname)
return
if self.valuetype == self.SELECTION:
qsettings.setValue(self.qname, self.options.index(self.value))
else:
qsettings.setValue(self.qname, self.value)
def __str__(self):
return self.name + "=" + str(self.value)
def tr(self, string, context=""):
if context == "":
context = "ProcessingConfig"
return QCoreApplication.translate(context, string)