Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ae826ab
fix: Correct variable replacement
gureckyfrantisek Mar 30, 2026
8f67aa1
Merge branch 'main' into issue-7212
echoix Apr 15, 2026
4c70abc
wxGUI/gmodeler: backward compatibility for curly braces
gureckyfrantisek Apr 15, 2026
5a1f52d
wxGUI/gmodeler: parsing without curly braces fix
gureckyfrantisek Apr 15, 2026
5b128c6
Merge branch 'main' into issue-7212
gureckyfrantisek Apr 29, 2026
20ccd1c
regex: Allow only both or none curly braces
gureckyfrantisek Apr 29, 2026
bb54a2a
Merge branch 'main' into issue-7212
gureckyfrantisek May 7, 2026
952c3dd
fix: Proper map name from model
gureckyfrantisek May 15, 2026
d7eb782
fix: Map variable replacement after Python
gureckyfrantisek May 15, 2026
d9c9070
fix: Map variable replacement on Display change
gureckyfrantisek May 15, 2026
2876d04
Merge branch 'main' into issue-7212
gureckyfrantisek May 23, 2026
67e0ed0
Merge branch 'main' into issue-7212
landam Jun 12, 2026
a93b0cc
Apply suggestions from code review
echoix Jun 13, 2026
0e4ca9e
Merge branch 'main' into issue-7212
gureckyfrantisek Jun 18, 2026
b96413f
fix: Run params getter
gureckyfrantisek Jun 18, 2026
89b02fd
fix: Adding maps in standalone mode
gureckyfrantisek Jun 24, 2026
431a057
Merge branch 'main' into issue-7212
gureckyfrantisek Jun 24, 2026
df9f10b
fix: Resolve review comment
gureckyfrantisek Jun 25, 2026
1c51b56
Merge branch 'main' into issue-7212
gureckyfrantisek Jun 25, 2026
9aa664f
check StandaloneGrassInterface ASAP
landam Jun 25, 2026
f59604a
check StandaloneGrassInterface before calling GetLayerList()
landam Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions gui/wxpython/gmodeler/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from gui_core.wrap import TextEntryDialog as wxTextEntryDialog, NewId, Menu
from gui_core.forms import GUI
from core.gcmd import GException, GError
from core.giface import StandaloneGrassInterface

from gmodeler.model_items import (
ModelRelation,
Expand Down Expand Up @@ -482,22 +483,34 @@ def OnHasDisplay(self, event):
shape.SetHasDisplay(event.IsChecked())
self.frame.canvas.Refresh()

if isinstance(self.frame._giface, StandaloneGrassInterface):
return

model = self.frame.GetModel()
run_params = model.GetRunParams()
resolved = {}
if run_params and "variables" in run_params:
for p in run_params["variables"]["params"]:
name = p.get("name", "")
value = p.get("value", "")
if name and value:
resolved[name] = value

layer_list = self.frame._giface.GetLayerList()
try:
if event.IsChecked():
# add map layer to display
self.frame._giface.GetLayerList().AddLayer(
layer_list.AddLayer(
ltype=shape.GetPrompt(),
name=shape.GetValue(),
name=shape.GetResolvedValue(resolved),
checked=True,
cmd=shape.GetDisplayCmd(),
cmd=shape.GetDisplayCmd(resolved),
)
else:
# remove map layer(s) from display
layers = self.frame._giface.GetLayerList().GetLayersByName(
shape.GetValue()
)
layers = layer_list.GetLayersByName(shape.GetResolvedValue(resolved))
for layer in layers:
self.frame._giface.GetLayerList().DeleteLayer(layer)
layer_list.DeleteLayer(layer)

except GException as e:
GError(parent=self, message="{}".format(e))
Expand Down
31 changes: 21 additions & 10 deletions gui/wxpython/gmodeler/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,10 @@ def Validate(self):
sval = pattern.search(value)
if not sval:
continue
var = sval.group(2).strip()[2:-1] # strip '%{...}'
s = sval.group(2).strip()
var = (
s[2:-1] if s.startswith("%{") else s[1:]
) # strip curly braces only if present
found = False
for v in variables:
if var.startswith(v):
Expand Down Expand Up @@ -539,7 +542,8 @@ def _substituteFile(self, item, params=None, checkOnly=False):
write = False
variables = self.GetVariables()
for variable in variables:
pattern = re.compile("%{" + variable + "}")
# curly braces are optional
pattern = re.compile(r"%(?:\{" + variable + r"\}|" + variable + r")")
value = ""
if params and "variables" in params:
for p in params["variables"]["params"]:
Expand All @@ -560,7 +564,10 @@ def _substituteFile(self, item, params=None, checkOnly=False):
pattern = re.compile(r"(.*)(%\{.+})(.*)")
sval = pattern.search(data)
if sval:
var = sval.group(2).strip()[2:-1] # ignore '%{...}'
s = sval.group(2).strip()
var = (
s[2:-1] if s.startswith("%{") else s[1:]
) # strip curly braces only if present
cmd = item.GetLog(string=False)[0]
errList.append(cmd + ": " + _("undefined variable '%s'") % var)

Expand Down Expand Up @@ -690,7 +697,10 @@ def Run(self, log, onDone, parent=None):
# substitute variables in condition
variables = self.GetVariables()
for variable in variables:
pattern = re.compile("%{" + variable + "}")
# curly braces are optional
pattern = re.compile(
r"%(?:\{" + variable + r"\}|" + variable + r")"
)
if not pattern.search(cond):
continue
value = ""
Expand All @@ -713,7 +723,8 @@ def Run(self, log, onDone, parent=None):
# split condition
# TODO: this part needs some better solution
condVar, condText = (x.strip() for x in re.split(r"\s* in \s*", cond))
pattern = re.compile("%{" + condVar + "}")
# curly braces are optional
pattern = re.compile(r"%(?:\{" + condVar + r"\}|" + condVar + r")")
# for vars()[condVar] in eval(condText): ?
vlist = []
if condText[0] == "`" and condText[-1] == "`":
Expand Down Expand Up @@ -742,12 +753,12 @@ def Run(self, log, onDone, parent=None):

if delInterData:
self.DeleteIntermediateData(log)
# store run params
self._runParams = params
Comment thread
gureckyfrantisek marked this conversation as resolved.

# discard values
if params:
for item in params.values():
for p in item["params"]:
p["value"] = ""
def GetRunParams(self):
"""Get the models run parameters"""
return getattr(self, "_runParams", None)

def DeleteIntermediateData(self, log):
"""Delete intermediate data"""
Expand Down
10 changes: 6 additions & 4 deletions gui/wxpython/gmodeler/model_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def _writeItem(self, item, ignoreBlock=True, variables={}):
# substitute condition
cond = item.GetLabel()
for variable in self.model.GetVariables():
pattern = re.compile("%{" + variable + "}")
# curly braces are optional
pattern = re.compile(r"%(?:\{" + variable + r"\}|" + variable + r")")
if pattern.search(cond):
value = variables[variable].get("value", "")
if variables[variable].get("type", "string") == "string":
Expand Down Expand Up @@ -988,14 +989,15 @@ def _substitutePythonParamValue(
# check for variables
formattedVar = False
for var in variables["vars"]:
pattern = re.compile("%{" + var + "}")
found = pattern.search(value)
# curly braces are optional
Comment thread
landam marked this conversation as resolved.
pattern = re.compile(r"%(?:\{" + var + r"\}|" + var + r")")
found = pattern.search(parameterizedValue)
if found:
foundVar = True
if found.end() != len(value):
formattedVar = True
parameterizedValue = pattern.sub(
"{options['" + var + "']}", value
"{options['" + var + "']}", parameterizedValue
)
else:
parameterizedValue = f'options["{var}"]'
Expand Down
28 changes: 24 additions & 4 deletions gui/wxpython/gmodeler/model_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ def GetLog(self, string=True, substitute=None):

# order variables by length
for variable in sorted(variables, key=len, reverse=True):
pattern = re.compile("%{" + variable + "}")
# curly braces are optional
pattern = re.compile(r"%(?:\{" + variable + r"\}|" + variable + r")")
value = ""
if substitute and "variables" in substitute:
for p in substitute["variables"]["params"]:
Expand Down Expand Up @@ -678,8 +679,27 @@ def Update(self):
self._setPen()
self.SetLabel()

def GetDisplayCmd(self):
"""Get display command as list"""
def GetResolvedValue(self, resolved=None):
"""Get value with model substituted variables
:param resolved: dict mapping variable name to resolved value,
or None to return the raw value
"""
if not resolved:
return self.value
value = self.value

# find the variable in resolved
for variable, var_value in resolved.items():
pattern = re.compile(r"%(?:\{" + variable + r"\}|" + variable + r")")
value = pattern.sub(var_value, value)
# return substituted value
return value

def GetDisplayCmd(self, resolved=None):
"""Get display command as list
:param resolved: dict mapping variable name to resolved value,
or None to return the raw value
"""
cmd = []
if self.prompt == "raster":
cmd.append("d.rast")
Expand All @@ -689,7 +709,7 @@ def GetDisplayCmd(self):
msg = "Unsupported display prompt: {}".format(self.prompt)
raise GException(msg)

cmd.append("map=" + self.value)
cmd.append("map=" + self.GetResolvedValue(resolved))

return cmd

Expand Down
62 changes: 55 additions & 7 deletions gui/wxpython/gmodeler/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from core.debug import Debug
from core.gcmd import GMessage, GException, GWarning, GError
from core.settings import UserSettings
from core.giface import Notification
from core.giface import Notification, StandaloneGrassInterface

from gui_core.widgets import GNotebook
from gui_core.goutput import GConsoleWindow
Expand Down Expand Up @@ -408,23 +408,37 @@ def OnModelDone(self, event):
# delete intermediate data
self._deleteIntermediateData()

# display data if required
# store resolved variables
run_params = self.model.GetRunParams()
resolved = {}
if run_params and "variables" in run_params:
for p in run_params["variables"]["params"]:
name = p.get("name", "")
value = p.get("value", "")
if name and value:
resolved[name] = value

# display data if required and is possible
if isinstance(self._giface, StandaloneGrassInterface):
return

layer_list = self._giface.GetLayerList()
for data in self.model.GetData():
if not data.HasDisplay():
continue

# remove existing map layers first
layers = self._giface.GetLayerList().GetLayersByName(data.GetValue())
layers = layer_list.GetLayersByName(data.GetResolvedValue(resolved))
if layers:
for layer in layers:
self._giface.GetLayerList().DeleteLayer(layer)
layer_list.DeleteLayer(layer)

# add new map layer
self._giface.GetLayerList().AddLayer(
layer_list.AddLayer(
ltype=data.GetPrompt(),
name=data.GetValue(),
name=data.GetResolvedValue(resolved),
checked=True,
cmd=data.GetDisplayCmd(),
cmd=data.GetDisplayCmd(resolved),
)

def _switchPageHandler(self, event, notification):
Expand Down Expand Up @@ -1808,6 +1822,40 @@ def OnDone(self, event):
try_remove(self.filename)
self.filename = None

# store resolved variables
model = self.parent.GetModel()
run_params = model.GetRunParams()
resolved = {}
if run_params and "variables" in run_params:
for p in run_params["variables"]["params"]:
name = p.get("name", "")
value = p.get("value", "")
if name and value:
resolved[name] = value

# display data if required and is possible
if isinstance(self.parent._giface, StandaloneGrassInterface):
return

layer_list = self.parent._giface.GetLayerList()
for data in model.GetData():
if not data.HasDisplay():
continue

# remove existing map layers first
layers = layer_list.GetLayersByName(data.GetResolvedValue(resolved))
if layers:
for layer in layers:
layer_list.DeleteLayer(layer)

# add new map layer
layer_list.AddLayer(
ltype=data.GetPrompt(),
name=data.GetResolvedValue(resolved),
checked=True,
cmd=data.GetDisplayCmd(resolved),
)

def OnChangeScriptType(self, event):
new_script_type = self.script_type_box.GetStringSelection()

Expand Down
Loading