Skip to content

Commit

Permalink
wxGUI/gmodeler: Support actinia export (#3005)
Browse files Browse the repository at this point in the history
  • Loading branch information
pesekon2 authored May 15, 2024
1 parent dcfbefe commit c17eaff
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 29 deletions.
38 changes: 26 additions & 12 deletions gui/wxpython/gmodeler/g.gui.gmodeler.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ <h2>DESCRIPTION</h2>
<li>save model properties to a file (<i>GRASS Model File|*.gxm</i>)</li>
<li>export model to Python script</li>
<li>export model to Python script in the form of a PyWPS process</li>
<li>export model to an actinia process</li>
<li>export model to image file</li>
</ul>

Expand All @@ -57,7 +58,7 @@ <h3>Main dialog</h3>
(<font size="2" color="red">2</font>) Load model from file,
(<font size="2" color="red">3</font>) Save current model to file,
(<font size="2" color="red">4</font>) Export model to image,
(<font size="2" color="red">5</font>) Export model to Python script,
(<font size="2" color="red">5</font>) Export model to a (Python/PyWPS/actinia) script,
(<font size="2" color="red">6</font>) Add command (GRASS module) to model,
(<font size="2" color="red">7</font>) Add data to model,
(<font size="2" color="red">8</font>) Manually define relation between
Expand All @@ -82,9 +83,9 @@ <h3>Main dialog</h3>
<p>
There is also a lower menu bar in the Graphical modeler dialog where one can
manage model items, visualize commands, add or manage model variables,
define default values and descriptions. The Python editor dialog window
allows seeing workflows written in Python code, either as a basic Python
script, or as a PyWPS script. The rightmost tab of the bottom menu is
define default values and descriptions. The Script editor dialog window
allows seeing and exporting workflows as basic Python scripts, as PyWPS
scripts, or as actinia processes. The rightmost tab of the bottom menu is
automatically triggered when the model is activated and shows all the steps
of running GRASS modeler modules; in the case some errors occur in
the calculation process, they are are written at that place.
Expand Down Expand Up @@ -220,7 +221,7 @@ <h3>Managing model parameters</h3>
</center>

<p>
The final model, the list of all model items, and the Python code window with
The final model, the list of all model items, and the Script editor window with
<i>Save</i> and <i>Run</i> option are shown in the figures below.

<center>
Expand All @@ -237,7 +238,7 @@ <h3>Managing model parameters</h3>
<a href="g_gui_gmodeler_python.png">
<img src="g_gui_gmodeler_python.png" width="600" height="330"></a>
<br>
<i>Figure: Items with Python editor window.</i>
<i>Figure: Items with Script editor window.</i>
</center>

<p>
Expand Down Expand Up @@ -345,8 +346,8 @@ <h3>Handling intermediate data</h3>
<i>Figure: Usage and definition of intermediate data in model.</i>
</center>

<h3>Using the Python editor</h3>
By using the Python editor in the Graphical Modeler the user can add Python code and then
<h3>Using the Script editor</h3>
By using the Script editor in the Graphical Modeler, the user can add Python code and then
run it with <i>Run</i> button or just save it as a Python script <tt>*.py</tt>.
The result is shown in the Figure below:

Expand All @@ -356,11 +357,11 @@ <h3>Using the Python editor</h3>
<a href="g_gui_gmodeler_python_code_result.png">
<img src="g_gui_gmodeler_python_code_result.png" height="500"></a>
<br>
<i>Figure: Python editor in the wxGUI Graphical Modeler.</i>
<i>Figure: Script editor in the wxGUI Graphical Modeler.</i>
</center>

In the <i>Script type</i> combobox, the user can also choose a PyWPS export
instead of a basic Python script. A PyWPS process based on the model will be
The second option in the <i>Script type</i> combobox exports a PyWPS script
instead of a basic Python one. A PyWPS process based on the model will be
generated then; for the PyWPS script, the <i>Run</i> button is disabled as
users are expected to include this script in their web processing service and
not to run it on itself.
Expand All @@ -369,7 +370,20 @@ <h3>Using the Python editor</h3>
<a href="g_gui_gmodeler_pywps_code.png">
<img src="g_gui_gmodeler_pywps_code.png" width="500"></a>
<br>
<i>Figure: Python editor in the wxGUI Graphical Modeler - set to PyWPS.</i>
<i>Figure: Script editor in the wxGUI Graphical Modeler - set to PyWPS.</i>
</center>

The third option in the <i>Script type</i> combobox exports an actinia process
chain (non-parameterized model) or an actinia template (parameterized model).
An actinia JSON based on the model will be generated then; as for the PyWPS
script, the <i>Run</i> button is disabled as users are expected to include this
JSON in their web processing service and not to run it on itself.

<center>
<a href="g_gui_gmodeler_actinia_code.png">
<img src="g_gui_gmodeler_actinia_code.png" width="500"></a>
<br>
<i>Figure: Script editor in the wxGUI Graphical Modeler - set to actinia.</i>
</center>

<p>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gui/wxpython/gmodeler/g_gui_gmodeler_items.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gui/wxpython/gmodeler/g_gui_gmodeler_python.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gui/wxpython/gmodeler/g_gui_gmodeler_python_code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gui/wxpython/gmodeler/g_gui_gmodeler_python_code_result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified gui/wxpython/gmodeler/g_gui_gmodeler_pywps_code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
166 changes: 161 additions & 5 deletions gui/wxpython/gmodeler/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- model::ModelComment
- model::ProcessModelFile
- model::WriteModelFile
- model::WriteActiniaFile
- model::WritePyWPSFile
- model::WritePythonFile
- model::ModelParamDialog
Expand All @@ -27,7 +28,7 @@
(>=v2). Read the file COPYING that comes with GRASS for details.
@author Martin Landa <landa.martin gmail.com>
@PyWPS, Python parameterization Ondrej Pesek <pesej.ondrek gmail.com>
@actinia, PyWPS, Python parameterization Ondrej Pesek <pesej.ondrek gmail.com>
"""

import os
Expand Down Expand Up @@ -2636,6 +2637,7 @@ def __init__(self, fd, model):
self.fd = None
self.model = None
self.indent = None
self.grassAPI = None

# call method_write...()

Expand Down Expand Up @@ -2701,12 +2703,17 @@ def _writePythonComment(self, item):
for line in item.GetLabel().splitlines():
self.fd.write("#" + line + "\n")

def _getParamName(self, parameter_name, item):
return "{module_nickname}_{param_name}".format(
module_nickname=self._getModuleNickname(item),
param_name=parameter_name,
)

@staticmethod
def _getParamName(parameter_name, item):
return "{module_name}{module_id}_{param_name}".format(
def _getModuleNickname(item):
return "{module_name}{module_id}".format(
module_name=re.sub("[^a-zA-Z]+", "", item.GetLabel()),
module_id=item.GetId(),
param_name=parameter_name,
)

def _getItemFlags(self, item, opts, variables):
Expand Down Expand Up @@ -2743,6 +2750,155 @@ def _getItemFlags(self, item, opts, variables):
return item_true_flags, item_parameterized_flags, item_params


class WriteActiniaFile(WriteScriptFile):
"""Class for exporting model to an actinia script."""

def __init__(self, fd, model, grassAPI=None):
"""Class for exporting model to actinia script."""
self.fd = fd
self.model = model
self.indent = 2

self._writeActinia()

def _writeActinia(self):
"""Write actinia model to file."""
properties = self.model.GetProperties()

description = properties["description"]

self.fd.write(
f"""{{
{' ' * self.indent * 1}"id": "model",
{' ' * self.indent * 1}"description": "{'""'.join(description.splitlines())}",
{' ' * self.indent * 1}"version": "1",
"""
)

parameterized = False
module_list_str = ""
for item in self.model.GetItems(ModelAction):
parameterizedParams = item.GetParameterizedParams()
if len(parameterizedParams["params"]) > 0:
parameterized = True

module_list_str += self._getPythonAction(item, parameterizedParams)
module_list_str += f"{' ' * self.indent * 3}}},\n"

if parameterized is True:
self.fd.write(f'{" " * self.indent * 1}"template": {{\n')
self.fd.write(
f"""{' ' * self.indent * 2}"list": [
"""
)
else:
self.fd.write(
f"""{' ' * self.indent}"list": [
"""
)

# module_list_str[:-2] to get rid of the trailing comma and newline
self.fd.write(module_list_str[:-2] + "\n")

if parameterized is True:
self.fd.write(f"{' ' * self.indent * 2}]\n{' ' * self.indent * 1}}}\n}}")
else:
self.fd.write(f"{' ' * self.indent * 1}]\n}}")

def _getPythonAction(self, item, variables={}, intermediates=None):
"""Write model action to Python file"""
task = GUI(show=None).ParseCommand(cmd=item.GetLog(string=False))
strcmd = f"{' ' * self.indent * 3}{{\n"

return (
strcmd + self._getPythonActionCmd(item, task, len(strcmd), variables) + "\n"
)

def _getPythonActionCmd(self, item, task, cmdIndent, variables={}):
opts = task.get_options()

ret = ""
parameterizedParams = [v["name"] for v in variables["params"]]

flags, itemParameterizedFlags, params = self._getItemFlags(
item, opts, variables
)
inputs = []
outputs = []

if len(itemParameterizedFlags) > 0:
dlg = wx.MessageDialog(
self.model.canvas,
message=_(
f"Module {task.get_name()} in your model contains "
f"parameterized flags. actinia does not support "
f"parameterized flags. The following flags are therefore "
f"not being written in the generated json: "
f"{itemParameterizedFlags}"
),
caption=_("Warning"),
style=wx.OK_DEFAULT | wx.ICON_WARNING,
)
dlg.ShowModal()
dlg.Destroy()

for p in opts["params"]:
name = p.get("name", None)
value = p.get("value", None)

if (name and value) or (name in parameterizedParams):

if name in parameterizedParams:
parameterizedParam = self._getParamName(name, item)
default_val = p.get("value", "")

if len(default_val) > 0:
parameterizedParam += f"|default({default_val})"

value = f"{{{{ {parameterizedParam} }}}}"

param_string = f'{{"param": "{name}", "value": "{value}"}}'
age = p.get("age", "old")
if age == "new":
outputs.append(param_string)
else:
inputs.append(param_string)

ret += f'{" " * self.indent * 4}"module": "{task.get_name()}",\n'
ret += f'{" " * self.indent * 4}"id": "{self._getModuleNickname(item)}",\n'

# write flags
if flags:
ret += f'{" " * self.indent * 4}"flags": "{flags}",\n'

# write inputs and outputs
if len(inputs) > 0:
ret += self.write_params("inputs", inputs)
else:
ret += "}"

if len(outputs) > 0:
ret += self.write_params("outputs", outputs)

# ret[:-2] to get rid of the trailing comma and newline
# (to make the json valid)
return ret[:-2]

def write_params(self, param_type, params):
"""Write the full list of parameters of one type.
:param param_type: type of parameters (inputs or outputs)
:params: list of the parameters
"""
ret = f'{" " * self.indent * 4}"{param_type}": [\n'
for opt in params[:-1]:
ret += f"{' ' * self.indent * 5}{opt},\n"
ret += f"{' ' * self.indent * 5}{params[-1]}\n"
ret += f"{' ' * self.indent * 4}],\n"

return ret


class WritePyWPSFile(WriteScriptFile):
"""Class for exporting model to PyWPS script."""

Expand Down Expand Up @@ -2786,7 +2942,7 @@ def __init__(self):
""" # noqa: E501
)

for item in self.model.GetItems():
for item in self.model.GetItems(ModelAction):
self._write_input_outputs(item, self.model.GetIntermediateData()[:3])

self.fd.write(
Expand Down
Loading

0 comments on commit c17eaff

Please sign in to comment.