diff --git a/gui/wxpython/core/settings.py b/gui/wxpython/core/settings.py
index 6316f37a75d..df2d6eebb8e 100644
--- a/gui/wxpython/core/settings.py
+++ b/gui/wxpython/core/settings.py
@@ -733,6 +733,7 @@ def _defaultSettings(self):
"height": 100,
},
},
+ "grassAPI": {"selection": 0}, # script package
},
"mapswipe": {
"cursor": {
@@ -875,6 +876,11 @@ def _internalSettings(self):
_("circle"),
)
+ self.internalSettings["modeler"]["grassAPI"]["choices"] = (
+ _("Script package"),
+ _("PyGRASS"),
+ )
+
def ReadSettingsFile(self, settings=None):
"""Reads settings file (mapset, location, gisdbase)"""
if settings is None:
diff --git a/gui/wxpython/gmodeler/frame.py b/gui/wxpython/gmodeler/frame.py
index a3aa2ca2399..d906aed1853 100644
--- a/gui/wxpython/gmodeler/frame.py
+++ b/gui/wxpython/gmodeler/frame.py
@@ -2103,7 +2103,13 @@ def RefreshScript(self):
return False
fd = tempfile.TemporaryFile(mode="r+")
- self.write_object(fd, self.parent.GetModel())
+ grassAPI = UserSettings.Get(group="modeler", key="grassAPI", subkey="selection")
+ self.write_object(
+ fd,
+ self.parent.GetModel(),
+ grassAPI="script" if grassAPI == 0 else "pygrass",
+ )
+
fd.seek(0)
self.body.SetText(fd.read())
fd.close()
diff --git a/gui/wxpython/gmodeler/g.gui.gmodeler.html b/gui/wxpython/gmodeler/g.gui.gmodeler.html
index c7f8be58a22..a6067c19aa8 100644
--- a/gui/wxpython/gmodeler/g.gui.gmodeler.html
+++ b/gui/wxpython/gmodeler/g.gui.gmodeler.html
@@ -372,6 +372,12 @@
Using the Python editor
Figure: Python editor in the wxGUI Graphical Modeler - set to PyWPS.
+
+By default GRASS script package API is used
+(grass.script.core.run_command()). This can be changed in the
+settings. Alternatively also PyGRASS API is supported
+(grass.pygrass.modules.Module).
+
Defining loops
In the example below the MODIS MOD13Q1
(NDVI) satellite data products are used in a loop. The original data are
diff --git a/gui/wxpython/gmodeler/model.py b/gui/wxpython/gmodeler/model.py
index fb191ebe2b2..4789ba156a6 100644
--- a/gui/wxpython/gmodeler/model.py
+++ b/gui/wxpython/gmodeler/model.py
@@ -19,7 +19,7 @@
- model::WritePythonFile
- model::ModelParamDialog
-(C) 2010-2018 by the GRASS Development Team
+(C) 2010-2024 by the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
@@ -2664,11 +2664,12 @@ def _getItemFlags(self, item, opts, variables):
class WritePyWPSFile(WriteScriptFile):
"""Class for exporting model to PyWPS script."""
- def __init__(self, fd, model):
+ def __init__(self, fd, model, grassAPI="script"):
"""Class for exporting model to PyWPS script."""
self.fd = fd
self.model = model
self.indent = 8
+ self.grassAPI = grassAPI
self._writePyWPS()
@@ -2683,8 +2684,15 @@ def _writePyWPS(self):
import os
import atexit
import tempfile
-from grass.script import run_command
-from pywps import Process, LiteralInput, ComplexInput, ComplexOutput, Format
+"""
+ )
+ if self.grassAPI == "script":
+ self.fd.write("from grass.script import run_command\n")
+ else:
+ self.fd.write("from grass.pygrass.modules import Module\n")
+
+ self.fd.write(
+ r"""from pywps import Process, LiteralInput, ComplexInput, ComplexOutput, Format
class Model(Process):
@@ -2868,7 +2876,10 @@ def _writeHandler(self):
def _writePythonAction(self, item, variables={}, intermediates=None):
"""Write model action to Python file"""
task = GUI(show=None).ParseCommand(cmd=item.GetLog(string=False))
- strcmd = "\n%srun_command(" % (" " * self.indent)
+ strcmd = "\n%s%s(" % (
+ " " * self.indent,
+ "run_command" if self.grassAPI == "script" else "Module",
+ )
self.fd.write(
strcmd + self._getPythonActionCmd(item, task, len(strcmd) - 1, variables)
)
@@ -2917,6 +2928,7 @@ def _writePythonAction(self, item, variables={}, intermediates=None):
else:
overwrite_string = ""
+ strcmd_len = len(strcmd.strip())
self.fd.write(
"""
{run_command}"{cmd}",
@@ -2928,14 +2940,14 @@ def _writePythonAction(self, item, variables={}, intermediates=None):
""".format(
run_command=strcmd,
cmd=command,
- indent1=" " * (self.indent + 12),
+ indent1=" " * (self.indent + strcmd_len),
input=param_request,
- indent2=" " * (self.indent + 12),
- indent3=" " * (self.indent + 16),
- indent4=" " * (self.indent + 16),
+ indent2=" " * (self.indent + strcmd_len),
+ indent3=" " * (self.indent * 2 + strcmd_len),
+ indent4=" " * (self.indent * 2 + strcmd_len),
out=param_request,
format_ext=extension,
- indent5=" " * (self.indent + 12),
+ indent5=" " * (self.indent + strcmd_len),
format=format,
overwrite_string=overwrite_string,
)
@@ -3061,14 +3073,17 @@ def _getSupportedFormats(prompt):
class WritePythonFile(WriteScriptFile):
- def __init__(self, fd, model):
+ def __init__(self, fd, model, grassAPI="script"):
"""Class for exporting model to Python script
:param fd: file descriptor
+ :param model: model to translate
+ :param grassAPI: script or pygrass
"""
self.fd = fd
self.model = model
self.indent = 4
+ self.grassAPI = grassAPI
self._writePython()
@@ -3188,9 +3203,13 @@ def _writePython(self):
import os
import atexit
-from grass.script import parser, run_command
+from grass.script import parser
"""
)
+ if self.grassAPI == "script":
+ self.fd.write("from grass.script import run_command\n")
+ else:
+ self.fd.write("from grass.pygrass.modules import Module\n")
# cleanup()
rast, vect, rast3d, msg = self.model.GetIntermediateData()
@@ -3199,26 +3218,27 @@ def _writePython(self):
def cleanup():
"""
)
+ run_command = "run_command" if self.grassAPI == "script" else "Module"
if rast:
self.fd.write(
- r""" run_command("g.remove", flags="f", type="raster",
+ r""" %s("g.remove", flags="f", type="raster",
name=%s)
"""
- % ",".join(map(lambda x: '"' + x + '"', rast))
+ % (run_command, ",".join(map(lambda x: '"' + x + '"', rast)))
)
if vect:
self.fd.write(
- r""" run_command("g.remove", flags="f", type="vector",
+ r""" %s("g.remove", flags="f", type="vector",
name=%s)
"""
- % ",".join(map(lambda x: '"' + x + '"', vect))
+ % (run_command, ",".join(map(lambda x: '"' + x + '"', vect)))
)
if rast3d:
self.fd.write(
- r""" run_command("g.remove", flags="f", type="raster_3d",
+ r""" %s("g.remove", flags="f", type="raster_3d",
name=%s)
"""
- % ",".join(map(lambda x: '"' + x + '"', rast3d))
+ % (run_command, ",".join(map(lambda x: '"' + x + '"', rast3d)))
)
if not rast and not vect and not rast3d:
self.fd.write(" pass\n")
@@ -3260,7 +3280,10 @@ def getParameterizedFlags(paramFlags, itemFlags):
def _writePythonAction(self, item, variables={}, intermediates=None):
"""Write model action to Python file"""
task = GUI(show=None).ParseCommand(cmd=item.GetLog(string=False))
- strcmd = "%srun_command(" % (" " * self.indent)
+ strcmd = "%s%s(" % (
+ " " * self.indent,
+ "run_command" if self.grassAPI == "script" else "Module",
+ )
self.fd.write(
strcmd + self._getPythonActionCmd(item, task, len(strcmd), variables) + "\n"
)
@@ -3278,16 +3301,31 @@ def _getPythonActionCmd(self, item, task, cmdIndent, variables={}):
for p in opts["params"]:
name = p.get("name", None)
value = p.get("value", None)
+ ptype = p.get("type", "string")
+
+ if (
+ self.grassAPI == "pygrass"
+ and (p.get("multiple", False) is True or len(p.get("key_desc", [])) > 1)
+ and "," in value
+ ):
+ value = value.split(",")
+ if ptype == "integer":
+ value = list(map(int, value))
+ elif ptype == "float":
+ value = list(map(float, value))
if (name and value) or (name in parameterizedParams):
- ptype = p.get("type", "string")
foundVar = False
if name in parameterizedParams:
foundVar = True
value = 'options["{}"]'.format(self._getParamName(name, item))
- if foundVar or ptype != "string":
+ if (
+ foundVar
+ or isinstance(value, list)
+ or (ptype != "string" and len(p.get("key_desc", [])) < 2)
+ ):
params.append("{}={}".format(name, value))
else:
params.append('{}="{}"'.format(name, value))
diff --git a/gui/wxpython/gmodeler/preferences.py b/gui/wxpython/gmodeler/preferences.py
index 8a8638931a7..750960d94de 100644
--- a/gui/wxpython/gmodeler/preferences.py
+++ b/gui/wxpython/gmodeler/preferences.py
@@ -7,7 +7,7 @@
- preferences::PreferencesDialog
- preferences::PropertiesDialog
-(C) 2010-2013 by the GRASS Development Team
+(C) 2010-2024 by the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
@@ -48,9 +48,9 @@ def _createGeneralPage(self, notebook):
"""Create notebook page for action settings"""
panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
notebook.AddPage(page=panel, text=_("General"))
+ border = wx.BoxSizer(wx.VERTICAL)
# colors
- border = wx.BoxSizer(wx.VERTICAL)
box = StaticBox(parent=panel, id=wx.ID_ANY, label=" %s " % _("Item properties"))
sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
@@ -84,6 +84,48 @@ def _createGeneralPage(self, notebook):
border=3,
)
+ # Python editor
+ box = StaticBox(parent=panel, id=wx.ID_ANY, label=" %s " % _("Python editor"))
+ sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+ gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
+
+ row = 0
+ gridSizer.Add(
+ StaticText(parent=panel, id=wx.ID_ANY, label=_("GRASS API:")),
+ flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
+ pos=(row, 0),
+ )
+ grassAPI = wx.Choice(
+ parent=panel,
+ id=wx.ID_ANY,
+ size=(150, -1),
+ choices=self.settings.Get(
+ group="modeler",
+ key="grassAPI",
+ subkey="choices",
+ settings_type="internal",
+ ),
+ name="GetSelection",
+ )
+ grassAPI.SetSelection(
+ self.settings.Get(group="modeler", key="grassAPI", subkey="selection")
+ )
+ self.winId["modeler:grassAPI:selection"] = grassAPI.GetId()
+
+ gridSizer.Add(
+ grassAPI, flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, pos=(row, 1)
+ )
+
+ gridSizer.AddGrowableCol(0)
+ sizer.Add(gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
+ border.Add(
+ sizer,
+ proportion=0,
+ flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
+ border=3,
+ )
+
panel.SetSizer(border)
return panel