model.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457
  1. """!
  2. @package gmodeler.model
  3. @brief wxGUI Graphical Modeler (base classes)
  4. Classes:
  5. - model::Model
  6. - model::ModelObject
  7. - model::ModelAction
  8. - model::ModelData
  9. - model::ModelRelation
  10. - model::ModelItem
  11. - model::ModelLoop
  12. - model::ModelCondition
  13. (C) 2010-2011 by the GRASS Development Team
  14. This program is free software under the GNU General Public License
  15. (>=v2). Read the file COPYING that comes with GRASS for details.
  16. @author Martin Landa <landa.martin gmail.com>
  17. """
  18. import os
  19. import getpass
  20. import copy
  21. import re
  22. try:
  23. import xml.etree.ElementTree as etree
  24. except ImportError:
  25. import elementtree.ElementTree as etree # Python <= 2.4
  26. import wx
  27. from wx.lib import ogl
  28. from core.globalvar import ETCWXDIR
  29. from core.gcmd import GMessage, GException, GError, RunCommand
  30. from gmodeler.dialogs import ModelParamDialog
  31. from grass.script import core as grass
  32. from grass.script import task as gtask
  33. class Model(object):
  34. """!Class representing the model"""
  35. def __init__(self, canvas = None):
  36. self.items = list() # list of actions/loops/...
  37. # model properties
  38. self.properties = { 'name' : _("model"),
  39. 'description' : _("Script generated by wxGUI Graphical Modeler."),
  40. 'author' : getpass.getuser() }
  41. # model variables
  42. self.variables = dict()
  43. self.variablesParams = dict()
  44. self.canvas = canvas
  45. def GetCanvas(self):
  46. """!Get canvas or None"""
  47. return self.canvas
  48. def GetItems(self, objType = None):
  49. """!Get list of model items
  50. @param objType Object type to filter model objects
  51. """
  52. if not objType:
  53. return self.items
  54. result = list()
  55. for item in self.items:
  56. if isinstance(item, objType):
  57. result.append(item)
  58. return result
  59. def GetItem(self, aId):
  60. """!Get item of given id
  61. @param aId item id
  62. @return Model* instance
  63. @return None if no item found
  64. """
  65. ilist = self.GetItems()
  66. for item in ilist:
  67. if item.GetId() == aId:
  68. return item
  69. return None
  70. def GetNumItems(self, actionOnly = False):
  71. """!Get number of items"""
  72. if actionOnly:
  73. return len(self.GetItems(objType = ModelAction))
  74. return len(self.GetItems())
  75. def GetNextId(self):
  76. """!Get next id (data ignored)
  77. @return next id to be used (default: 1)
  78. """
  79. if len(self.items) < 1:
  80. return 1
  81. currId = self.items[-1].GetId()
  82. if currId > 0:
  83. return currId + 1
  84. return 1
  85. def GetProperties(self):
  86. """!Get model properties"""
  87. return self.properties
  88. def GetVariables(self, params = False):
  89. """!Get model variables"""
  90. if params:
  91. return self.variablesParams
  92. return self.variables
  93. def SetVariables(self, data):
  94. """!Set model variables"""
  95. self.variables = data
  96. def Reset(self):
  97. """!Reset model"""
  98. self.items = list()
  99. def RemoveItem(self, item):
  100. """!Remove item from model
  101. @return list of related items to remove/update
  102. """
  103. relList = list()
  104. upList = list()
  105. if not isinstance(item, ModelData):
  106. self.items.remove(item)
  107. if isinstance(item, ModelAction):
  108. for rel in item.GetRelations():
  109. relList.append(rel)
  110. data = rel.GetData()
  111. if len(data.GetRelations()) < 2:
  112. relList.append(data)
  113. else:
  114. upList.append(data)
  115. elif isinstance(item, ModelData):
  116. for rel in item.GetRelations():
  117. relList.append(rel)
  118. if rel.GetFrom() == self:
  119. relList.append(rel.GetTo())
  120. else:
  121. relList.append(rel.GetFrom())
  122. elif isinstance(item, ModelLoop):
  123. for rel in item.GetRelations():
  124. relList.append(rel)
  125. for action in self.GetItems():
  126. action.UnSetBlock(item)
  127. return relList, upList
  128. def FindAction(self, aId):
  129. """!Find action by id"""
  130. alist = self.GetItems(objType = ModelAction)
  131. for action in alist:
  132. if action.GetId() == aId:
  133. return action
  134. return None
  135. def GetData(self):
  136. """!Get list of data items"""
  137. result = list()
  138. dataItems = self.GetItems(objType = ModelData)
  139. for action in self.GetItems(objType = ModelAction):
  140. for rel in action.GetRelations():
  141. dataItem = rel.GetData()
  142. if dataItem not in result:
  143. result.append(dataItem)
  144. if dataItem in dataItems:
  145. dataItems.remove(dataItem)
  146. # standalone data
  147. if dataItems:
  148. result += dataItems
  149. return result
  150. def FindData(self, value, prompt):
  151. """!Find data item in the model
  152. @param value value
  153. @param prompt prompt
  154. @return ModelData instance
  155. @return None if not found
  156. """
  157. for data in self.GetData():
  158. if data.GetValue() == value and \
  159. data.GetPrompt() == prompt:
  160. return data
  161. return None
  162. def LoadModel(self, filename):
  163. """!Load model definition stored in GRASS Model XML file (gxm)
  164. @todo Validate against DTD
  165. Raise exception on error.
  166. """
  167. dtdFilename = os.path.join(ETCWXDIR, "xml", "grass-gxm.dtd")
  168. # parse workspace file
  169. try:
  170. gxmXml = ProcessModelFile(etree.parse(filename))
  171. except StandardError, e:
  172. raise GException(e)
  173. if self.canvas:
  174. win = self.canvas.parent
  175. if gxmXml.pos:
  176. win.SetPosition(gxmXml.pos)
  177. if gxmXml.size:
  178. win.SetSize(gxmXml.size)
  179. # load properties
  180. self.properties = gxmXml.properties
  181. self.variables = gxmXml.variables
  182. # load model.GetActions()
  183. for action in gxmXml.actions:
  184. actionItem = ModelAction(parent = self,
  185. x = action['pos'][0],
  186. y = action['pos'][1],
  187. width = action['size'][0],
  188. height = action['size'][1],
  189. task = action['task'],
  190. id = action['id'])
  191. if action['disabled']:
  192. actionItem.Enable(False)
  193. self.AddItem(actionItem)
  194. actionItem.SetValid(actionItem.GetTask().get_options())
  195. actionItem.GetLog() # substitute variables (-> valid/invalid)
  196. # load data & relations
  197. for data in gxmXml.data:
  198. dataItem = ModelData(parent = self,
  199. x = data['pos'][0],
  200. y = data['pos'][1],
  201. width = data['size'][0],
  202. height = data['size'][1],
  203. prompt = data['prompt'],
  204. value = data['value'])
  205. dataItem.SetIntermediate(data['intermediate'])
  206. for rel in data['rels']:
  207. actionItem = self.FindAction(rel['id'])
  208. if rel['dir'] == 'from':
  209. relation = ModelRelation(parent = self, fromShape = dataItem,
  210. toShape = actionItem, param = rel['name'])
  211. else:
  212. relation = ModelRelation(parent = self, fromShape = actionItem,
  213. toShape = dataItem, param = rel['name'])
  214. relation.SetControlPoints(rel['points'])
  215. actionItem.AddRelation(relation)
  216. dataItem.AddRelation(relation)
  217. if self.canvas:
  218. dataItem.Update()
  219. # load loops
  220. for loop in gxmXml.loops:
  221. loopItem = ModelLoop(parent = self,
  222. x = loop['pos'][0],
  223. y = loop['pos'][1],
  224. width = loop['size'][0],
  225. height = loop['size'][1],
  226. text = loop['text'],
  227. id = loop['id'])
  228. self.AddItem(loopItem)
  229. # load conditions
  230. for condition in gxmXml.conditions:
  231. conditionItem = ModelCondition(parent = self,
  232. x = condition['pos'][0],
  233. y = condition['pos'][1],
  234. width = condition['size'][0],
  235. height = condition['size'][1],
  236. text = condition['text'],
  237. id = condition['id'])
  238. self.AddItem(conditionItem)
  239. # define loops & if/else items
  240. for loop in gxmXml.loops:
  241. alist = list()
  242. for aId in loop['items']:
  243. action = self.GetItem(aId)
  244. alist.append(action)
  245. loopItem = self.GetItem(loop['id'])
  246. loopItem.SetItems(alist)
  247. for action in loopItem.GetItems():
  248. action.SetBlock(loopItem)
  249. for condition in gxmXml.conditions:
  250. conditionItem = self.GetItem(condition['id'])
  251. for b in condition['items'].keys():
  252. alist = list()
  253. for aId in condition['items'][b]:
  254. action = self.GetItem(aId)
  255. alist.append(action)
  256. conditionItem.SetItems(alist, branch = b)
  257. items = conditionItem.GetItems()
  258. for b in items.keys():
  259. for action in items[b]:
  260. action.SetBlock(conditionItem)
  261. def AddItem(self, newItem):
  262. """!Add item to the list"""
  263. iId = newItem.GetId()
  264. i = 0
  265. for item in self.items:
  266. if item.GetId() > iId:
  267. self.items.insert(i, newItem)
  268. return
  269. i += 1
  270. self.items.append(newItem)
  271. def IsValid(self):
  272. """Return True if model is valid"""
  273. if self.Validate():
  274. return False
  275. return True
  276. def Validate(self):
  277. """!Validate model, return None if model is valid otherwise
  278. error string"""
  279. errList = list()
  280. variables = self.GetVariables().keys()
  281. pattern = re.compile(r'(.*)(%.+\s?)(.*)')
  282. for action in self.GetItems(objType = ModelAction):
  283. cmd = action.GetLog(string = False)
  284. task = menuform.GUI(show = None).ParseCommand(cmd = cmd)
  285. errList += map(lambda x: cmd[0] + ': ' + x, task.get_cmd_error())
  286. # check also variables
  287. for opt in cmd[1:]:
  288. if '=' not in opt:
  289. continue
  290. key, value = opt.split('=', 1)
  291. sval = pattern.search(value)
  292. if sval:
  293. var = sval.group(2).strip()[1:] # ignore '%'
  294. if var not in variables:
  295. report = True
  296. for item in filter(lambda x: isinstance(x, ModelLoop), action.GetBlock()):
  297. if var in item.GetText():
  298. report = False
  299. break
  300. if report:
  301. errList.append(_("%s: undefined variable '%s'") % (cmd[0], var))
  302. ### TODO: check variables in file only optionally
  303. ### errList += self._substituteFile(action, checkOnly = True)
  304. return errList
  305. def _substituteFile(self, item, params = None, checkOnly = False):
  306. """!Subsitute variables in command file inputs
  307. @param checkOnly tuble - True to check variable, don't touch files
  308. @return list of undefined variables
  309. """
  310. errList = list()
  311. self.fileInput = dict()
  312. # collect ascii inputs
  313. for p in item.GetParams()['params']:
  314. if p.get('element', '') == 'file' and \
  315. p.get('prompt', '') == 'input' and \
  316. p.get('age', '') == 'old':
  317. filename = p.get('value', p.get('default', ''))
  318. if filename and \
  319. mimetypes.guess_type(filename)[0] == 'text/plain':
  320. self.fileInput[filename] = None
  321. for finput in self.fileInput:
  322. # read lines
  323. fd = open(finput, "r")
  324. try:
  325. data = self.fileInput[finput] = fd.read()
  326. finally:
  327. fd.close()
  328. # substitute variables
  329. write = False
  330. variables = self.GetVariables()
  331. for variable in variables:
  332. pattern = re.compile('%' + variable)
  333. value = ''
  334. if params and 'variables' in params:
  335. for p in params['variables']['params']:
  336. if variable == p.get('name', ''):
  337. if p.get('type', 'string') == 'string':
  338. value = p.get('value', '')
  339. else:
  340. value = str(p.get('value', ''))
  341. break
  342. if not value:
  343. value = variables[variable].get('value', '')
  344. data = pattern.sub(value, data)
  345. if not checkOnly:
  346. write = True
  347. pattern = re.compile(r'(.*)(%.+\s?)(.*)')
  348. sval = pattern.search(data)
  349. if sval:
  350. var = sval.group(2).strip()[1:] # ignore '%'
  351. cmd = item.GetLog(string = False)[0]
  352. errList.append(_("%s: undefined variable '%s'") % (cmd, var))
  353. if not checkOnly:
  354. if write:
  355. fd = open(finput, "w")
  356. try:
  357. fd.write(data)
  358. finally:
  359. fd.close()
  360. else:
  361. self.fileInput[finput] = None
  362. return errList
  363. def OnPrepare(self, item, params):
  364. self._substituteFile(item, params, checkOnly = False)
  365. def RunAction(self, item, params, log, onDone, onPrepare = None, statusbar = None):
  366. """!Run given action
  367. @param item action item
  368. @param params parameters dict
  369. @param log logging window
  370. @param onDone on-done method
  371. @param onPrepare on-prepare method
  372. @param statusbar wx.StatusBar instance or None
  373. """
  374. name = item.GetName()
  375. if name in params:
  376. paramsOrig = item.GetParams(dcopy = True)
  377. item.MergeParams(params[name])
  378. if statusbar:
  379. statusbar.SetStatusText(_('Running model...'), 0)
  380. data = { 'item' : item,
  381. 'params' : copy.deepcopy(params) }
  382. log.RunCmd(command = item.GetLog(string = False, substitute = params),
  383. onDone = onDone, onPrepare = self.OnPrepare, userData = data)
  384. if name in params:
  385. item.SetParams(paramsOrig)
  386. def Run(self, log, onDone, parent = None):
  387. """!Run model
  388. @param log logging window (see goutput.GMConsole)
  389. @param onDone on-done method
  390. @param parent window for messages or None
  391. """
  392. if self.GetNumItems() < 1:
  393. GMessage(parent = parent,
  394. message = _('Model is empty. Nothing to run.'))
  395. return
  396. statusbar = None
  397. if isinstance(parent, wx.Frame):
  398. statusbar = parent.GetStatusBar()
  399. # validation
  400. if statusbar:
  401. statusbar.SetStatusText(_('Validating model...'), 0)
  402. errList = self.Validate()
  403. if statusbar:
  404. statusbar.SetStatusText('', 0)
  405. if errList:
  406. dlg = wx.MessageDialog(parent = parent,
  407. message = _('Model is not valid. Do you want to '
  408. 'run the model anyway?\n\n%s') % '\n'.join(errList),
  409. caption = _("Run model?"),
  410. style = wx.YES_NO | wx.NO_DEFAULT |
  411. wx.ICON_QUESTION | wx.CENTRE)
  412. ret = dlg.ShowModal()
  413. if ret != wx.ID_YES:
  414. return
  415. # parametrization
  416. params = self.Parameterize()
  417. if params:
  418. dlg = ModelParamDialog(parent = parent,
  419. params = params)
  420. dlg.CenterOnParent()
  421. ret = dlg.ShowModal()
  422. if ret != wx.ID_OK:
  423. dlg.Destroy()
  424. return
  425. err = dlg.GetErrors()
  426. if err:
  427. GError(parent = parent, message = unicode('\n'.join(err)))
  428. return
  429. err = list()
  430. for key, item in params.iteritems():
  431. for p in item['params']:
  432. if p.get('value', '') == '':
  433. err.append((key, p.get('name', ''), p.get('description', '')))
  434. if err:
  435. GError(parent = parent,
  436. message = _("Variables below not defined:") + \
  437. "\n\n" + unicode('\n'.join(map(lambda x: "%s: %s (%s)" % (x[0], x[1], x[2]), err))))
  438. return
  439. log.cmdThread.SetId(-1)
  440. for item in self.GetItems():
  441. if not item.IsEnabled():
  442. continue
  443. if isinstance(item, ModelAction):
  444. if item.GetBlockId():
  445. continue
  446. self.RunAction(item, params, log, onDone)
  447. elif isinstance(item, ModelLoop):
  448. cond = item.GetText()
  449. # substitute variables in condition
  450. variables = self.GetVariables()
  451. for variable in variables:
  452. pattern = re.compile('%' + variable)
  453. if pattern.search(cond):
  454. value = ''
  455. if params and 'variables' in params:
  456. for p in params['variables']['params']:
  457. if variable == p.get('name', ''):
  458. value = p.get('value', '')
  459. break
  460. if not value:
  461. value = variables[variable].get('value', '')
  462. if not value:
  463. continue
  464. vtype = variables[variable].get('type', 'string')
  465. if vtype == 'string':
  466. value = '"' + value + '"'
  467. cond = pattern.sub(value, cond)
  468. # split condition
  469. condVar, condText = map(lambda x: x.strip(), re.split('\s*in\s*', cond))
  470. pattern = re.compile('%' + condVar)
  471. ### for vars()[condVar] in eval(condText): ?
  472. if condText[0] == '`' and condText[-1] == '`':
  473. # run command
  474. cmd, dcmd = utils.CmdToTuple(condText[1:-1].split(' '))
  475. ret = RunCommand(cmd,
  476. read = True,
  477. **dcmd)
  478. if ret:
  479. vlist = ret.splitlines()
  480. else:
  481. vlist = eval(condText)
  482. if 'variables' not in params:
  483. params['variables'] = { 'params' : [] }
  484. varDict = { 'name' : condVar, 'value' : '' }
  485. params['variables']['params'].append(varDict)
  486. for var in vlist:
  487. for action in item.GetItems():
  488. if not isinstance(action, ModelAction) or \
  489. not action.IsEnabled():
  490. continue
  491. varDict['value'] = var
  492. self.RunAction(item = action, params = params,
  493. log = log, onDone = onDone)
  494. params['variables']['params'].remove(varDict)
  495. # discard values
  496. if params:
  497. for item in params.itervalues():
  498. for p in item['params']:
  499. p['value'] = ''
  500. if params:
  501. dlg.Destroy()
  502. def DeleteIntermediateData(self, log):
  503. """!Detele intermediate data"""
  504. rast, vect, rast3d, msg = self.GetIntermediateData()
  505. if rast:
  506. log.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
  507. if rast3d:
  508. log.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
  509. if vect:
  510. log.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
  511. def GetIntermediateData(self):
  512. """!Get info about intermediate data"""
  513. rast = list()
  514. rast3d = list()
  515. vect = list()
  516. for data in self.GetData():
  517. if not data.IsIntermediate():
  518. continue
  519. name = data.GetValue()
  520. prompt = data.GetPrompt()
  521. if prompt == 'raster':
  522. rast.append(name)
  523. elif prompt == 'vector':
  524. vect.append(name)
  525. elif prompt == 'rast3d':
  526. rast3d.append(name)
  527. msg = ''
  528. if rast:
  529. msg += '\n\n%s: ' % _('Raster maps')
  530. msg += ', '.join(rast)
  531. if rast3d:
  532. msg += '\n\n%s: ' % _('3D raster maps')
  533. msg += ', '.join(rast3d)
  534. if vect:
  535. msg += '\n\n%s: ' % _('Vector maps')
  536. msg += ', '.join(vect)
  537. return rast, vect, rast3d, msg
  538. def Update(self):
  539. """!Update model"""
  540. for item in self.items:
  541. item.Update()
  542. def IsParameterized(self):
  543. """!Return True if model is parameterized"""
  544. if self.Parameterize():
  545. return True
  546. return False
  547. def Parameterize(self):
  548. """!Return parameterized options"""
  549. result = dict()
  550. idx = 0
  551. if self.variables:
  552. params = list()
  553. result["variables"] = { 'flags' : list(),
  554. 'params' : params,
  555. 'idx' : idx }
  556. for name, values in self.variables.iteritems():
  557. gtype = values.get('type', 'string')
  558. if gtype in ('raster', 'vector', 'mapset', 'file'):
  559. gisprompt = True
  560. prompt = gtype
  561. if gtype == 'raster':
  562. element = 'cell'
  563. else:
  564. element = gtype
  565. ptype = 'string'
  566. else:
  567. gisprompt = False
  568. prompt = None
  569. element = None
  570. ptype = gtype
  571. params.append({ 'gisprompt' : gisprompt,
  572. 'multiple' : False,
  573. 'description' : values.get('description', ''),
  574. 'guidependency' : '',
  575. 'default' : '',
  576. 'age' : None,
  577. 'required' : True,
  578. 'value' : values.get('value', ''),
  579. 'label' : '',
  580. 'guisection' : '',
  581. 'key_desc' : '',
  582. 'values' : list(),
  583. 'parameterized' : False,
  584. 'values_desc' : list(),
  585. 'prompt' : prompt,
  586. 'element' : element,
  587. 'type' : ptype,
  588. 'name' : name })
  589. idx += 1
  590. for action in self.GetItems(objType = ModelAction):
  591. if not action.IsEnabled():
  592. continue
  593. name = action.GetName()
  594. params = action.GetParams()
  595. for f in params['flags']:
  596. if f.get('parameterized', False):
  597. if name not in result:
  598. result[name] = { 'flags' : list(),
  599. 'params': list(),
  600. 'idx' : idx }
  601. result[name]['flags'].append(f)
  602. for p in params['params']:
  603. if p.get('parameterized', False):
  604. if name not in result:
  605. result[name] = { 'flags' : list(),
  606. 'params': list(),
  607. 'idx' : idx }
  608. result[name]['params'].append(p)
  609. if name in result:
  610. idx += 1
  611. self.variablesParams = result # record parameters
  612. return result
  613. class ModelObject:
  614. def __init__(self, id = -1):
  615. self.id = id
  616. self.rels = list() # list of ModelRelations
  617. self.isEnabled = True
  618. self.inBlock = list() # list of related loops/conditions
  619. def __del__(self):
  620. pass
  621. def GetId(self):
  622. """!Get id"""
  623. return self.id
  624. def AddRelation(self, rel):
  625. """!Record new relation
  626. """
  627. self.rels.append(rel)
  628. def GetRelations(self, fdir = None):
  629. """!Get list of relations
  630. @param fdir True for 'from'
  631. """
  632. if fdir is None:
  633. return self.rels
  634. result = list()
  635. for rel in self.rels:
  636. if fdir == 'from':
  637. if rel.GetFrom() == self:
  638. result.append(rel)
  639. else:
  640. if rel.GetTo() == self:
  641. result.append(rel)
  642. return result
  643. def IsEnabled(self):
  644. """!Get True if action is enabled, otherwise False"""
  645. return self.isEnabled
  646. def Enable(self, enabled = True):
  647. """!Enable/disable action"""
  648. self.isEnabled = enabled
  649. self.Update()
  650. def Update(self):
  651. pass
  652. def SetBlock(self, item):
  653. """!Add object to the block (loop/condition)
  654. @param item reference to ModelLoop or ModelCondition which
  655. defines loops/condition
  656. """
  657. if item not in self.inBlock:
  658. self.inBlock.append(item)
  659. def UnSetBlock(self, item):
  660. """!Remove object from the block (loop/consition)
  661. @param item reference to ModelLoop or ModelCondition which
  662. defines loops/codition
  663. """
  664. if item in self.inBlock:
  665. self.inBlock.remove(item)
  666. def GetBlock(self):
  667. """!Get list of related ModelObject(s) which defines block
  668. (loop/condition)
  669. @return list of ModelObjects
  670. """
  671. return self.inBlock
  672. def GetBlockId(self):
  673. """!Get list of related ids which defines block
  674. @return list of ids
  675. """
  676. ret = list()
  677. for mo in self.inBlock:
  678. ret.append(mo.GetId())
  679. return ret
  680. class ModelAction(ModelObject, ogl.RectangleShape):
  681. """!Action class (GRASS module)"""
  682. def __init__(self, parent, x, y, id = -1, cmd = None, task = None, width = None, height = None):
  683. ModelObject.__init__(self, id)
  684. self.parent = parent
  685. self.task = task
  686. if not width:
  687. width = UserSettings.Get(group='modeler', key='action', subkey=('size', 'width'))
  688. if not height:
  689. height = UserSettings.Get(group='modeler', key='action', subkey=('size', 'height'))
  690. if cmd:
  691. self.task = GUI(show = None).ParseCommand(cmd = cmd)
  692. else:
  693. if task:
  694. self.task = task
  695. else:
  696. self.task = None
  697. self.propWin = None
  698. self.data = list() # list of connected data items
  699. self.isValid = False
  700. self.isParameterized = False
  701. if self.parent.GetCanvas():
  702. ogl.RectangleShape.__init__(self, width, height)
  703. self.SetCanvas(self.parent)
  704. self.SetX(x)
  705. self.SetY(y)
  706. self.SetPen(wx.BLACK_PEN)
  707. self._setPen()
  708. self._setBrush()
  709. self.SetId(id)
  710. if self.task:
  711. self.SetValid(self.task.get_options())
  712. def _setBrush(self, running = False):
  713. """!Set brush"""
  714. if running:
  715. color = UserSettings.Get(group='modeler', key='action',
  716. subkey=('color', 'running'))
  717. elif not self.isEnabled:
  718. color = UserSettings.Get(group='modeler', key='disabled',
  719. subkey='color')
  720. elif self.isValid:
  721. color = UserSettings.Get(group='modeler', key='action',
  722. subkey=('color', 'valid'))
  723. else:
  724. color = UserSettings.Get(group='modeler', key='action',
  725. subkey=('color', 'invalid'))
  726. wxColor = wx.Color(color[0], color[1], color[2])
  727. self.SetBrush(wx.Brush(wxColor))
  728. def _setPen(self):
  729. """!Set pen"""
  730. if self.isParameterized:
  731. width = int(UserSettings.Get(group='modeler', key='action',
  732. subkey=('width', 'parameterized')))
  733. else:
  734. width = int(UserSettings.Get(group='modeler', key='action',
  735. subkey=('width', 'default')))
  736. pen = self.GetPen()
  737. pen.SetWidth(width)
  738. self.SetPen(pen)
  739. def SetId(self, id):
  740. """!Set id"""
  741. self.id = id
  742. cmd = self.task.get_cmd(ignoreErrors = True)
  743. if cmd and len(cmd) > 0:
  744. self.ClearText()
  745. self.AddText('(%d) %s' % (self.id, cmd[0]))
  746. else:
  747. self.AddText('(%d) <<%s>>' % (self.id, _("unknown")))
  748. def SetProperties(self, params, propwin):
  749. """!Record properties dialog"""
  750. self.task.params = params['params']
  751. self.task.flags = params['flags']
  752. self.propWin = propwin
  753. def GetPropDialog(self):
  754. """!Get properties dialog"""
  755. return self.propWin
  756. def GetLog(self, string = True, substitute = None):
  757. """!Get logging info
  758. @param string True to get cmd as a string otherwise a list
  759. @param substitute dictionary of parameter to substitute or None
  760. """
  761. cmd = self.task.get_cmd(ignoreErrors = True, ignoreRequired = True,
  762. ignoreDefault = False)
  763. # substitute variables
  764. if substitute:
  765. variables = []
  766. if 'variables' in substitute:
  767. for p in substitute['variables']['params']:
  768. variables.append(p.get('name', ''))
  769. else:
  770. variables = self.parent.GetVariables()
  771. for variable in variables:
  772. pattern= re.compile('%' + variable)
  773. value = ''
  774. if substitute and 'variables' in substitute:
  775. for p in substitute['variables']['params']:
  776. if variable == p.get('name', ''):
  777. if p.get('type', 'string') == 'string':
  778. value = p.get('value', '')
  779. else:
  780. value = str(p.get('value', ''))
  781. break
  782. if not value:
  783. value = variables[variable].get('value', '')
  784. if not value:
  785. continue
  786. for idx in range(len(cmd)):
  787. if pattern.search(cmd[idx]):
  788. cmd[idx] = pattern.sub(value, cmd[idx])
  789. break
  790. idx += 1
  791. if string:
  792. if cmd is None:
  793. return ''
  794. else:
  795. return ' '.join(cmd)
  796. return cmd
  797. def GetName(self):
  798. """!Get name"""
  799. cmd = self.task.get_cmd(ignoreErrors = True)
  800. if cmd and len(cmd) > 0:
  801. return cmd[0]
  802. return _('unknown')
  803. def GetParams(self, dcopy = False):
  804. """!Get dictionary of parameters"""
  805. if dcopy:
  806. return copy.deepcopy(self.task.get_options())
  807. return self.task.get_options()
  808. def GetTask(self):
  809. """!Get grassTask instance"""
  810. return self.task
  811. def SetParams(self, params):
  812. """!Set dictionary of parameters"""
  813. self.task.params = params['params']
  814. self.task.flags = params['flags']
  815. def MergeParams(self, params):
  816. """!Merge dictionary of parameters"""
  817. if 'flags' in params:
  818. for f in params['flags']:
  819. self.task.set_flag(f['name'],
  820. f.get('value', False))
  821. if 'params' in params:
  822. for p in params['params']:
  823. self.task.set_param(p['name'],
  824. p.get('value', ''))
  825. def SetValid(self, options):
  826. """!Set validity for action
  827. @param options dictionary with flags and params (gtask)
  828. """
  829. self.isValid = True
  830. self.isParameterized = False
  831. for f in options['flags']:
  832. if f.get('parameterized', False):
  833. self.IsParameterized = True
  834. break
  835. for p in options['params']:
  836. if self.isValid and p.get('required', False) and \
  837. p.get('value', '') == '' and \
  838. p.get('default', '') == '':
  839. self.isValid = False
  840. if not self.isParameterized and p.get('parameterized', False):
  841. self.isParameterized = True
  842. if self.parent.GetCanvas():
  843. self._setBrush()
  844. self._setPen()
  845. def IsValid(self):
  846. """!Check validity (all required parameters set)"""
  847. return self.isValid
  848. def IsParameterized(self):
  849. """!Check if action is parameterized"""
  850. return self.isParameterized
  851. def FindData(self, name):
  852. """!Find data item by name"""
  853. for rel in self.GetRelations():
  854. data = rel.GetData()
  855. if name == rel.GetName() and name in data.GetName():
  856. return data
  857. return None
  858. def Update(self, running = False):
  859. """!Update action"""
  860. if running:
  861. self._setBrush(running = True)
  862. else:
  863. self._setBrush()
  864. self._setPen()
  865. def OnDraw(self, dc):
  866. """!Draw action in canvas"""
  867. self._setBrush()
  868. self._setPen()
  869. ogl.RectangleShape.OnDraw(self, dc)
  870. class ModelData(ModelObject, ogl.EllipseShape):
  871. def __init__(self, parent, x, y, value = '', prompt = '', width = None, height = None):
  872. """Data item class
  873. @param parent window parent
  874. @param x, y position of the shape
  875. @param fname, tname list of parameter names from / to
  876. @param value value
  877. @param prompt type of GIS element
  878. @param width,height dimension of the shape
  879. """
  880. ModelObject.__init__(self)
  881. self.parent = parent
  882. self.value = value
  883. self.prompt = prompt
  884. self.intermediate = False
  885. self.propWin = None
  886. if not width:
  887. width = UserSettings.Get(group='modeler', key='data', subkey=('size', 'width'))
  888. if not height:
  889. height = UserSettings.Get(group='modeler', key='data', subkey=('size', 'height'))
  890. if self.parent.GetCanvas():
  891. ogl.EllipseShape.__init__(self, width, height)
  892. self.SetCanvas(self.parent)
  893. self.SetX(x)
  894. self.SetY(y)
  895. self.SetPen(wx.BLACK_PEN)
  896. self._setBrush()
  897. self._setText()
  898. def IsIntermediate(self):
  899. """!Checks if data item is intermediate"""
  900. return self.intermediate
  901. def SetIntermediate(self, im):
  902. """!Set intermediate flag"""
  903. self.intermediate = im
  904. def OnDraw(self, dc):
  905. pen = self.GetPen()
  906. pen.SetWidth(1)
  907. if self.intermediate:
  908. pen.SetStyle(wx.SHORT_DASH)
  909. else:
  910. pen.SetStyle(wx.SOLID)
  911. self.SetPen(pen)
  912. ogl.EllipseShape.OnDraw(self, dc)
  913. def GetLog(self, string = True):
  914. """!Get logging info"""
  915. name = list()
  916. for rel in self.GetRelations():
  917. name.append(rel.GetName())
  918. if name:
  919. return '/'.join(name) + '=' + self.value + ' (' + self.prompt + ')'
  920. else:
  921. return self.value + ' (' + self.prompt + ')'
  922. def GetName(self):
  923. """!Get list of names"""
  924. name = list()
  925. for rel in self.GetRelations():
  926. name.append(rel.GetName())
  927. return name
  928. def GetPrompt(self):
  929. """!Get prompt"""
  930. return self.prompt
  931. def SetPrompt(self, prompt):
  932. """!Set prompt
  933. @param prompt
  934. """
  935. self.prompt = prompt
  936. def GetValue(self):
  937. """!Get value"""
  938. return self.value
  939. def SetValue(self, value):
  940. """!Set value
  941. @param value
  942. """
  943. self.value = value
  944. self._setText()
  945. for direction in ('from', 'to'):
  946. for rel in self.GetRelations(direction):
  947. if direction == 'from':
  948. action = rel.GetTo()
  949. else:
  950. action = rel.GetFrom()
  951. task = GUI(show = None).ParseCommand(cmd = action.GetLog(string = False))
  952. task.set_param(rel.GetName(), self.value)
  953. action.SetParams(params = task.get_options())
  954. def GetPropDialog(self):
  955. """!Get properties dialog"""
  956. return self.propWin
  957. def SetPropDialog(self, win):
  958. """!Get properties dialog"""
  959. self.propWin = win
  960. def _setBrush(self):
  961. """!Set brush"""
  962. if self.prompt == 'raster':
  963. color = UserSettings.Get(group = 'modeler', key = 'data',
  964. subkey = ('color', 'raster'))
  965. elif self.prompt == 'raster3d':
  966. color = UserSettings.Get(group = 'modeler', key = 'data',
  967. subkey = ('color', 'raster3d'))
  968. elif self.prompt == 'vector':
  969. color = UserSettings.Get(group = 'modeler', key = 'data',
  970. subkey = ('color', 'vector'))
  971. else:
  972. color = UserSettings.Get(group = 'modeler', key = 'action',
  973. subkey = ('color', 'invalid'))
  974. wxColor = wx.Color(color[0], color[1], color[2])
  975. self.SetBrush(wx.Brush(wxColor))
  976. def _setPen(self):
  977. """!Set pen"""
  978. isParameterized = False
  979. for rel in self.GetRelations('from'):
  980. if rel.GetTo().IsParameterized():
  981. isParameterized = True
  982. break
  983. if not isParameterized:
  984. for rel in self.GetRelations('to'):
  985. if rel.GetFrom().IsParameterized():
  986. isParameterized = True
  987. break
  988. if isParameterized:
  989. width = int(UserSettings.Get(group = 'modeler', key = 'action',
  990. subkey = ('width', 'parameterized')))
  991. else:
  992. width = int(UserSettings.Get(group = 'modeler', key = 'action',
  993. subkey = ('width', 'default')))
  994. pen = self.GetPen()
  995. pen.SetWidth(width)
  996. self.SetPen(pen)
  997. def _setText(self):
  998. """!Update text"""
  999. self.ClearText()
  1000. name = []
  1001. for rel in self.GetRelations():
  1002. name.append(rel.GetName())
  1003. self.AddText('/'.join(name))
  1004. if self.value:
  1005. self.AddText(self.value)
  1006. else:
  1007. self.AddText(_('<not defined>'))
  1008. def Update(self):
  1009. """!Update action"""
  1010. self._setBrush()
  1011. self._setPen()
  1012. self._setText()
  1013. class ModelRelation(ogl.LineShape):
  1014. """!Data - action relation"""
  1015. def __init__(self, parent, fromShape, toShape, param = ''):
  1016. self.fromShape = fromShape
  1017. self.toShape = toShape
  1018. self.param = param
  1019. self.parent = parent
  1020. self._points = None
  1021. if self.parent.GetCanvas():
  1022. ogl.LineShape.__init__(self)
  1023. def __del__(self):
  1024. if self in self.fromShape.rels:
  1025. self.fromShape.rels.remove(self)
  1026. if self in self.toShape.rels:
  1027. self.toShape.rels.remove(self)
  1028. def GetFrom(self):
  1029. """!Get id of 'from' shape"""
  1030. return self.fromShape
  1031. def GetTo(self):
  1032. """!Get id of 'to' shape"""
  1033. return self.toShape
  1034. def GetData(self):
  1035. """!Get related ModelData instance
  1036. @return ModelData instance
  1037. @return None if not found
  1038. """
  1039. if isinstance(self.fromShape, ModelData):
  1040. return self.fromShape
  1041. elif isinstance(self.toShape, ModelData):
  1042. return self.toShape
  1043. return None
  1044. def GetName(self):
  1045. """!Get parameter name"""
  1046. return self.param
  1047. def ResetShapes(self):
  1048. """!Reset related objects"""
  1049. self.fromShape.ResetControlPoints()
  1050. self.toShape.ResetControlPoints()
  1051. self.ResetControlPoints()
  1052. def SetControlPoints(self, points):
  1053. """!Set control points"""
  1054. self._points = points
  1055. def GetControlPoints(self):
  1056. """!Get list of control points"""
  1057. return self._points
  1058. def _setPen(self):
  1059. """!Set pen"""
  1060. pen = self.GetPen()
  1061. pen.SetWidth(1)
  1062. pen.SetStyle(wx.SOLID)
  1063. self.SetPen(pen)
  1064. def OnDraw(self, dc):
  1065. """!Draw relation"""
  1066. self._setPen()
  1067. ogl.LineShape.OnDraw(self, dc)
  1068. def SetName(self, param):
  1069. self.param = param
  1070. class ModelItem(ModelObject):
  1071. def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
  1072. """!Abstract class for loops and conditions"""
  1073. ModelObject.__init__(self, id)
  1074. self.parent = parent
  1075. self.text = text
  1076. self.items = items # list of items in the loop
  1077. def GetText(self):
  1078. """!Get loop text"""
  1079. return self.text
  1080. def GetItems(self):
  1081. """!Get items (id)"""
  1082. return self.items
  1083. def SetId(self, id):
  1084. """!Set loop id"""
  1085. self.id = id
  1086. def SetText(self, cond):
  1087. """!Set loop text (condition)"""
  1088. self.text = cond
  1089. self.ClearText()
  1090. self.AddText('(' + str(self.id) + ') ' + self.text)
  1091. def GetLog(self):
  1092. """!Get log info"""
  1093. if self.text:
  1094. return _("Condition: ") + self.text
  1095. else:
  1096. return _("Condition: not defined")
  1097. def AddRelation(self, rel):
  1098. """!Record relation"""
  1099. self.rels.append(rel)
  1100. def Clear(self):
  1101. """!Clear object, remove rels"""
  1102. self.rels = list()
  1103. class ModelLoop(ModelItem, ogl.RectangleShape):
  1104. def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
  1105. """!Defines a loop"""
  1106. ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
  1107. if not width:
  1108. width = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'width'))
  1109. if not height:
  1110. height = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'height'))
  1111. if self.parent.GetCanvas():
  1112. ogl.RectangleShape.__init__(self, width, height)
  1113. self.SetCanvas(self.parent)
  1114. self.SetX(x)
  1115. self.SetY(y)
  1116. self.SetPen(wx.BLACK_PEN)
  1117. self.SetCornerRadius(100)
  1118. if text:
  1119. self.AddText('(' + str(self.id) + ') ' + text)
  1120. else:
  1121. self.AddText('(' + str(self.id) + ')')
  1122. self._setBrush()
  1123. def _setBrush(self):
  1124. """!Set brush"""
  1125. if not self.isEnabled:
  1126. color = UserSettings.Get(group='modeler', key='disabled',
  1127. subkey='color')
  1128. else:
  1129. color = UserSettings.Get(group='modeler', key='loop',
  1130. subkey=('color', 'valid'))
  1131. wxColor = wx.Color(color[0], color[1], color[2])
  1132. self.SetBrush(wx.Brush(wxColor))
  1133. def Enable(self, enabled = True):
  1134. """!Enable/disable action"""
  1135. for item in self.items:
  1136. if not isinstance(item, ModelAction):
  1137. continue
  1138. item.Enable(enabled)
  1139. ModelObject.Enable(self, enabled)
  1140. def Update(self):
  1141. self._setBrush()
  1142. def GetName(self):
  1143. """!Get name"""
  1144. return _("loop")
  1145. def SetItems(self, items):
  1146. """!Set items (id)"""
  1147. self.items = items
  1148. class ModelCondition(ModelItem, ogl.PolygonShape):
  1149. def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '',
  1150. items = { 'if' : [], 'else' : [] }):
  1151. """!Defines a if-else condition"""
  1152. ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
  1153. if not width:
  1154. self.width = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'width'))
  1155. else:
  1156. self.width = width
  1157. if not height:
  1158. self.height = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'height'))
  1159. else:
  1160. self.height = height
  1161. if self.parent.GetCanvas():
  1162. ogl.PolygonShape.__init__(self)
  1163. points = [(0, - self.height / 2),
  1164. (self.width / 2, 0),
  1165. (0, self.height / 2),
  1166. (- self.width / 2, 0)]
  1167. self.Create(points)
  1168. self.SetCanvas(self.parent)
  1169. self.SetX(x)
  1170. self.SetY(y)
  1171. self.SetPen(wx.BLACK_PEN)
  1172. if text:
  1173. self.AddText('(' + str(self.id) + ') ' + text)
  1174. else:
  1175. self.AddText('(' + str(self.id) + ')')
  1176. def GetName(self):
  1177. """!Get name"""
  1178. return _("if-else")
  1179. def GetWidth(self):
  1180. """!Get object width"""
  1181. return self.width
  1182. def GetHeight(self):
  1183. """!Get object height"""
  1184. return self.height
  1185. def SetItems(self, items, branch = 'if'):
  1186. """!Set items (id)
  1187. @param items list of items
  1188. @param branch 'if' / 'else'
  1189. """
  1190. if branch in ['if', 'else']:
  1191. self.items[branch] = items