Skip to content

Commit

Permalink
Graphical Modeler: add support for PyGRASS API (#3369)
Browse files Browse the repository at this point in the history
  • Loading branch information
landam committed Jan 24, 2024
1 parent e276098 commit 0ad33cc
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 24 deletions.
6 changes: 6 additions & 0 deletions gui/wxpython/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ def _defaultSettings(self):
"height": 100,
},
},
"grassAPI": {"selection": 0}, # script package
},
"mapswipe": {
"cursor": {
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion gui/wxpython/gmodeler/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions gui/wxpython/gmodeler/g.gui.gmodeler.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@ <h3>Using the Python editor</h3>
<i>Figure: Python editor in the wxGUI Graphical Modeler - set to PyWPS.</i>
</center>

<p>
By default GRASS script package API is used
(<tt>grass.script.core.run_command()</tt>). This can be changed in the
settings. Alternatively also PyGRASS API is supported
(<tt>grass.pygrass.modules.Module</tt>).

<h3>Defining loops</h3>
In the example below the <a href="https://e4ftl01.cr.usgs.gov/MOLT/MOD13Q1.006/">MODIS MOD13Q1</a>
(NDVI) satellite data products are used in a loop. The original data are
Expand Down
80 changes: 59 additions & 21 deletions gui/wxpython/gmodeler/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()

Expand All @@ -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):
Expand Down Expand Up @@ -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)
)
Expand Down Expand Up @@ -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}",
Expand All @@ -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,
)
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()
Expand All @@ -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")
Expand Down Expand Up @@ -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"
)
Expand All @@ -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))
Expand Down
46 changes: 44 additions & 2 deletions gui/wxpython/gmodeler/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 0ad33cc

Please sign in to comment.