coprocessing.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. r"""
  2. This module is designed for use in co-processing Python scripts. It provides a
  3. class, Pipeline, which is designed to be used as the base-class for Python
  4. pipeline. Additionally, this module has several other utility functions that are
  5. appropriate for co-processing.
  6. """
  7. # for Python2 print statmements to output like Python3 print statements
  8. from __future__ import print_function
  9. from paraview import simple, servermanager
  10. from paraview.detail import exportnow
  11. import math
  12. # If the user created a filename in a location that doesn't exist by default we'll
  13. # make the directory for them. This can be changed though by setting createDirectoriesIfNeeded
  14. # to False.
  15. createDirectoriesIfNeeded = True
  16. # -----------------------------------------------------------------------------
  17. class CoProcessor(object):
  18. """Base class for co-processing Pipelines.
  19. paraview.cpstate Module can be used to dump out ParaView states as
  20. co-processing pipelines. Those are typically subclasses of this. The
  21. subclasses must provide an implementation for the CreatePipeline() method.
  22. **Cinema Tracks**
  23. CoProcessor maintains user-defined information for the Cinema generation in
  24. __CinemaTracks. This information includes track parameter values, data array
  25. names, etc. __CinemaTracks holds this information in the following structure::
  26. {
  27. proxy_reference : {
  28. 'ControlName' : [value_1, value_2, ..., value_n],
  29. 'arraySelection' : ['ArrayName_1', ..., 'ArrayName_n']
  30. }
  31. }
  32. __CinemaTracks is populated when defining the co-processing pipline through
  33. paraview.cpstate. paraview.cpstate uses accessor instances to set values and
  34. array names through the RegisterCinemaTrack and AddArrayssToCinemaTrack
  35. methods of this class.
  36. """
  37. def __init__(self):
  38. self.__PipelineCreated = False
  39. # __ProducersMap will have a list of the producers/channels that need to be
  40. # created by the adaptor for this pipeline. The adaptor may be able to generate
  41. # other channels as well though.
  42. self.__ProducersMap = {}
  43. self.__WritersList = []
  44. self.__ViewsList = []
  45. self.__EnableLiveVisualization = False
  46. self.__LiveVisualizationFrequency = 1;
  47. self.__LiveVisualizationLink = None
  48. # __CinemaTracksList is just for Spec-A compatibility (will be deprecated
  49. # when porting Spec-A to pv_introspect. Use __CinemaTracks instead.
  50. self.__CinemaTracksList = []
  51. self.__CinemaTracks = {}
  52. self.__InitialFrequencies = {}
  53. self.__PrintEnsightFormatString = False
  54. self.__TimeStepToStartOutputAt=0
  55. self.__ForceOutputAtFirstCall=False
  56. self.__FirstTimeStepIndex = None
  57. # a list of arrays requested for each channel, e.g. {'input': ["a point data array name", 0], ["a cell data array name", 1]}
  58. self.__RequestedArrays = None
  59. self.__ImageRootDirectory = ""
  60. self.__DataRootDirectory = ""
  61. self.__CinemaDHelper = None
  62. def SetPrintEnsightFormatString(self, enable):
  63. """If outputting ExodusII files with the purpose of reading them into
  64. Ensight, print a message on process 0 on what to use for the 'Set string'
  65. input to properly read the generated files into Ensight."""
  66. self.__PrintEnsightFormatString = enable
  67. def SetUpdateFrequencies(self, frequencies):
  68. """Set the frequencies at which the pipeline needs to be updated.
  69. Typically, this is called by the subclass once it has determined what
  70. timesteps co-processing will be needed to be done.
  71. frequencies is a map, with key->string name of for the simulation
  72. input, and value is a list of frequencies.
  73. """
  74. if type(frequencies) != dict:
  75. raise RuntimeError (
  76. "Incorrect argument type: %s, must be a dict" % type(frequencies))
  77. self.__InitialFrequencies = frequencies
  78. def SetRequestedArrays(self, channelname, requestedarrays):
  79. """Set which arrays this script will request from the adaptor for the given channel name.
  80. """
  81. if not self.__RequestedArrays:
  82. self.__RequestedArrays = {}
  83. self.__RequestedArrays[channelname] = requestedarrays
  84. def SetInitialOutputOptions(self, timeStepToStartOutputAt, forceOutputAtFirstCall):
  85. """Set the frequencies at which the pipeline needs to be updated.
  86. Typically, this is called by the subclass once it has determined what
  87. timesteps co-processing will be needed to be done.
  88. frequencies is a map, with key->string name of for the simulation
  89. input, and value is a list of frequencies.
  90. """
  91. self.__TimeStepToStartOutputAt=timeStepToStartOutputAt
  92. self.__ForceOutputAtFirstCall=forceOutputAtFirstCall
  93. def EnableLiveVisualization(self, enable, frequency = 1):
  94. """Call this method to enable live-visualization. When enabled,
  95. DoLiveVisualization() will communicate with ParaView server if possible
  96. for live visualization. Frequency specifies how often the
  97. communication happens (default is every second)."""
  98. self.__EnableLiveVisualization = enable
  99. self.__LiveVisualizationFrequency = frequency
  100. def CreatePipeline(self, datadescription):
  101. """This methods must be overridden by subclasses to create the
  102. visualization pipeline."""
  103. raise RuntimeError ("Subclasses must override this method.")
  104. def LoadRequestedData(self, datadescription):
  105. """Call this method in RequestDataDescription co-processing pass to mark
  106. the datadescription with information about what fields and grids are
  107. required for this pipeline for the given timestep, if any.
  108. Default implementation uses the update-frequencies set using
  109. SetUpdateFrequencies() to determine if the current timestep needs to
  110. be processed and then requests all fields. Subclasses can override
  111. this method to provide additional customizations. If there is a Live
  112. connection that can also override the initial frequencies."""
  113. # if this is a time step to do live then only the channels that were requested when
  114. # generating the script will be made available even though the adaptor may be able
  115. # to provide other channels. similarly, if only specific arrays were requested when
  116. # generating the script then only those arrays will be provided to the Live connection.
  117. # note that we want the pipeline built before we do the actual first live connection.
  118. if self.__EnableLiveVisualization and self.NeedToOutput(datadescription, self.__LiveVisualizationFrequency) \
  119. and self.__LiveVisualizationLink:
  120. if self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()):
  121. if self.__RequestedArrays:
  122. for key in self.__RequestedArrays:
  123. for v in self.__RequestedArrays[key]:
  124. inputDescription = datadescription.GetInputDescriptionByName(key)
  125. if inputDescription:
  126. inputDescription.AddField(v[0], v[1])
  127. elif self.__InitialFrequencies:
  128. # __ProducersMap will not be filled up until after the first call to
  129. # DoCoProcessing so we rely on __InitialFrequencies initially but then
  130. # __ProducersMap after that as __InitialFrequencies will be cleared out.
  131. for key in self.__InitialFrequencies:
  132. inputDescription = datadescription.GetInputDescriptionByName(key)
  133. if inputDescription:
  134. inputDescription.AllFieldsOn()
  135. inputDescription.GenerateMeshOn()
  136. else:
  137. for key in self.__ProducersMap:
  138. inputDescription = datadescription.GetInputDescriptionByName(key)
  139. if inputDescription:
  140. inputDescription.AllFieldsOn()
  141. inputDescription.GenerateMeshOn()
  142. return
  143. # if we haven't processed the pipeline yet in DoCoProcessing() we
  144. # must use the initial frequencies to figure out if there's
  145. # work to do this time/timestep. If we don't have Live enabled
  146. # we know that the output frequencies aren't changed and can
  147. # just use the initial frequencies.
  148. if self.__ForceOutputAtFirstCall or self.__InitialFrequencies or not self.__EnableLiveVisualization:
  149. if self.__RequestedArrays:
  150. for key in self.__RequestedArrays:
  151. for v in self.__RequestedArrays[key]:
  152. inputDescription = datadescription.GetInputDescriptionByName(key)
  153. if inputDescription:
  154. inputDescription.AddField(v[0], v[1])
  155. elif self.__InitialFrequencies:
  156. for key in self.__InitialFrequencies:
  157. freqs = self.__InitialFrequencies.get(key, [])
  158. if self.__EnableLiveVisualization or self.IsInModulo(datadescription, freqs):
  159. inputDescription = datadescription.GetInputDescriptionByName(key)
  160. if inputDescription:
  161. inputDescription.AllFieldsOn()
  162. inputDescription.GenerateMeshOn()
  163. else:
  164. # the catalyst pipeline may have been changed by a Live connection
  165. # so we need to regenerate the frequencies
  166. from paraview import cpstate
  167. frequencies = {}
  168. for writer in self.__WritersList:
  169. frequency = writer.parameters.GetProperty("WriteFrequency").GetElement(0)
  170. if self.NeedToOutput(datadescription, frequency) or datadescription.GetForceOutput() == True:
  171. writerinputs = cpstate.locate_simulation_inputs(writer)
  172. for writerinput in writerinputs:
  173. if self.__RequestedArrays:
  174. for key in self.__RequestedArrays:
  175. for v in self.__RequestedArrays[key]:
  176. datadescription.GetInputDescriptionByName(writerinput).AddField(v[0], v[1])
  177. else:
  178. datadescription.GetInputDescriptionByName(writerinput).AllFieldsOn()
  179. datadescription.GetInputDescriptionByName(writerinput).GenerateMeshOn()
  180. for view in self.__ViewsList:
  181. if (view.cpFrequency and self.NeedToOutput(datadescription, view.cpFrequency)) or \
  182. datadescription.GetForceOutput() == True:
  183. viewinputs = cpstate.locate_simulation_inputs_for_view(view)
  184. for viewinput in viewinputs:
  185. if self.__RequestedArrays:
  186. for key in self.__RequestedArrays:
  187. for v in self.__RequestedArrays[key]:
  188. datadescription.GetInputDescriptionByName(viewinput).AddField(v[0], v[1])
  189. else:
  190. datadescription.GetInputDescriptionByName(viewinput).AllFieldsOn()
  191. datadescription.GetInputDescriptionByName(viewinput).GenerateMeshOn()
  192. def UpdateProducers(self, datadescription):
  193. """This method will update the producers in the pipeline. If the
  194. pipeline is not created, it will be created using
  195. self.CreatePipeline().
  196. """
  197. if not self.__PipelineCreated:
  198. self.CreatePipeline(datadescription)
  199. self.__PipelineCreated = True
  200. if self.__EnableLiveVisualization:
  201. # we don't want to use __InitialFrequencies any more with live viz
  202. self.__InitialFrequencies = None
  203. self.__FixupWriters()
  204. else:
  205. simtime = datadescription.GetTime()
  206. for name, producer in self.__ProducersMap.items():
  207. producer.GetClientSideObject().SetOutput(
  208. datadescription.GetInputDescriptionByName(name).GetGrid(),
  209. simtime)
  210. def WriteData(self, datadescription):
  211. """This method will update all writes present in the pipeline, as
  212. needed, to generate the output data files, respecting the
  213. write-frequencies set on the writers."""
  214. timestep = datadescription.GetTimeStep()
  215. for writer in self.__WritersList:
  216. frequency = writer.parameters.GetProperty(
  217. "WriteFrequency").GetElement(0)
  218. if self.NeedToOutput(datadescription, frequency) or datadescription.GetForceOutput() == True:
  219. fileName = writer.parameters.GetProperty("FileName").GetElement(0)
  220. paddingamount = writer.parameters.GetProperty("PaddingAmount").GetElement(0)
  221. helperName = writer.GetXMLName()
  222. if helperName == "ExodusIIWriter":
  223. ts = "."+str(timestep).rjust(paddingamount, '0')
  224. writer.FileName = fileName + ts
  225. else:
  226. ts = str(timestep).rjust(paddingamount, '0')
  227. writer.FileName = fileName.replace("%t", ts)
  228. if '/' in writer.FileName and createDirectoriesIfNeeded:
  229. oktowrite = [1.]
  230. import vtk
  231. comm = vtk.vtkMultiProcessController.GetGlobalController()
  232. if comm.GetLocalProcessId() == 0:
  233. import os
  234. newDir = writer.FileName[0:writer.FileName.rfind('/')]
  235. try:
  236. os.makedirs(newDir)
  237. except OSError:
  238. if not os.path.isdir(newDir):
  239. print ("ERROR: Cannot make directory for", writer.FileName, ". No data will be written.")
  240. oktowrite[0] = 0.
  241. comm.Broadcast(oktowrite, 1, 0)
  242. if oktowrite[0] == 0:
  243. # we can't make the directory so no reason to update the pipeline
  244. return
  245. writer.UpdatePipeline(datadescription.GetTime())
  246. self.__AppendToCinemaDTable(timestep, "writer_%s" % self.__WritersList.index(writer), writer.FileName)
  247. self.__FinalizeCinemaDTable()
  248. def WriteImages(self, datadescription, rescale_lookuptable=False,
  249. image_quality=None, padding_amount=0):
  250. """This method will update all views, if present and write output
  251. images, as needed.
  252. **Parameters**
  253. datadescription
  254. Catalyst data-description object
  255. rescale_lookuptable (bool, optional)
  256. If True, when all lookup tables
  257. are rescaled using current data ranges before saving the images.
  258. Defaults to False.
  259. image_quality (int, optional)
  260. If specified, should be a value in
  261. the range (0, 100) that specifies the image quality. For JPEG, 0
  262. is low quality i.e. max compression, 100 is best quality i.e.
  263. least compression. For legacy reasons, this is inverted for PNG
  264. (which uses lossless compression). For PNG, 0 is no compression
  265. i.e maximum image size, while 100 is most compressed and hence
  266. least image size.
  267. If not specified, for saving PNGs 0 is assumed to minimize
  268. performance impact.
  269. padding_amount (int, optional)
  270. Amount to pad the time index by.
  271. """
  272. timestep = datadescription.GetTimeStep()
  273. cinema_dirs = []
  274. for view in self.__ViewsList:
  275. if (view.cpFrequency and self.NeedToOutput(datadescription, view.cpFrequency)) or \
  276. datadescription.GetForceOutput() == True:
  277. fname = view.cpFileName
  278. ts = str(timestep).rjust(padding_amount, '0')
  279. fname = fname.replace("%t", ts)
  280. if view.cpFitToScreen != 0:
  281. view.ViewTime = datadescription.GetTime()
  282. if view.IsA("vtkSMRenderViewProxy") == True:
  283. view.ResetCamera()
  284. elif view.IsA("vtkSMContextViewProxy") == True:
  285. view.ResetDisplay()
  286. else:
  287. print (' do not know what to do with a ', view.GetClassName())
  288. view.ViewTime = datadescription.GetTime()
  289. if rescale_lookuptable:
  290. self.RescaleDataRange(view, datadescription.GetTime())
  291. cinemaOptions = view.cpCinemaOptions
  292. if cinemaOptions and 'camera' in cinemaOptions:
  293. if 'composite' in view.cpCinemaOptions and view.cpCinemaOptions['composite'] == True:
  294. dirname, filelist = self.UpdateCinema(view, datadescription,
  295. specLevel="B")
  296. else:
  297. dirname, filelist = self.UpdateCinema(view, datadescription,
  298. specLevel="A")
  299. if dirname:
  300. self.__AppendCViewToCinemaDTable(timestep, "view_%s" % self.__ViewsList.index(view), filelist)
  301. cinema_dirs.append(dirname)
  302. else:
  303. if '/' in fname and createDirectoriesIfNeeded:
  304. oktowrite = [1.]
  305. import vtk
  306. comm = vtk.vtkMultiProcessController.GetGlobalController()
  307. if comm.GetLocalProcessId() == 0:
  308. import os
  309. newDir = fname[0:fname.rfind('/')]
  310. try:
  311. os.makedirs(newDir)
  312. except OSError:
  313. if not os.path.isdir(newDir):
  314. print ("ERROR: Cannot make directory for", fname, ". No image will be output.")
  315. oktowrite[0] = 0.
  316. comm.Broadcast(oktowrite, 1, 0)
  317. if oktowrite[0] == 0:
  318. # we can't make the directory so no reason to update the pipeline
  319. return
  320. if image_quality is None and fname.endswith('png'):
  321. # for png quality = 0 means no compression. compression can be a potentially
  322. # very costly serial operation on process 0
  323. quality = 0
  324. elif image_quality is not None:
  325. quality = int(image_quality)
  326. else:
  327. # let simple.SaveScreenshot pick a default.
  328. quality = None
  329. if fname.endswith('png') and view.cpCompression is not None and view.cpCompression != -1 :
  330. simple.SaveScreenshot(fname, view,
  331. CompressionLevel=view.cpCompression,
  332. ImageResolution=view.ViewSize)
  333. else:
  334. simple.SaveScreenshot(fname, view,
  335. magnification=view.cpMagnification,
  336. quality=quality)
  337. self.__AppendToCinemaDTable(timestep, "view_%s" % self.__ViewsList.index(view), fname)
  338. if len(cinema_dirs) > 1:
  339. import paraview.tpl.cinema_python.adaptors.paraview.pv_introspect as pv_introspect
  340. pv_introspect.make_workspace_file("cinema\\", cinema_dirs)
  341. self.__FinalizeCinemaDTable()
  342. def DoLiveVisualization(self, datadescription, hostname, port):
  343. """This method execute the code-stub needed to communicate with ParaView
  344. for live-visualization. Call this method only if you want to support
  345. live-visualization with your co-processing module."""
  346. if not self.__EnableLiveVisualization:
  347. return
  348. if not self.__LiveVisualizationLink and self.__EnableLiveVisualization:
  349. # Create the vtkLiveInsituLink i.e. the "link" to the visualization processes.
  350. self.__LiveVisualizationLink = servermanager.vtkLiveInsituLink()
  351. # Tell vtkLiveInsituLink what host/port must it connect to
  352. # for the visualization process.
  353. self.__LiveVisualizationLink.SetHostname(hostname)
  354. self.__LiveVisualizationLink.SetInsituPort(int(port))
  355. # Initialize the "link"
  356. self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager())
  357. if self.__EnableLiveVisualization and self.NeedToOutput(datadescription, self.__LiveVisualizationFrequency):
  358. if not self.__LiveVisualizationLink.Initialize(servermanager.ActiveConnection.Session.GetSessionProxyManager()):
  359. return
  360. time = datadescription.GetTime()
  361. timeStep = datadescription.GetTimeStep()
  362. # stay in the loop while the simulation is paused
  363. while True:
  364. # Update the simulation state, extracts and simulationPaused
  365. # from ParaView Live
  366. self.__LiveVisualizationLink.InsituUpdate(time, timeStep)
  367. # sources need to be updated by insitu
  368. # code. vtkLiveInsituLink never updates the pipeline, it
  369. # simply uses the data available at the end of the
  370. # pipeline, if any.
  371. for source in simple.GetSources().values():
  372. source.UpdatePipeline(time)
  373. # push extracts to the visualization process.
  374. self.__LiveVisualizationLink.InsituPostProcess(time, timeStep)
  375. if (self.__LiveVisualizationLink.GetSimulationPaused()):
  376. # This blocks until something changes on ParaView Live
  377. # and then it continues the loop. Returns != 0 if LIVE side
  378. # disconnects
  379. if (self.__LiveVisualizationLink.WaitForLiveChange()):
  380. break;
  381. else:
  382. break
  383. def CreateProducer(self, datadescription, inputname):
  384. """Creates a producer proxy for the grid. This method is generally used in
  385. CreatePipeline() call to create producers."""
  386. # Check that the producer name for the input given is valid for the
  387. # current setup.
  388. if not datadescription.GetInputDescriptionByName(inputname):
  389. raise RuntimeError ("Simulation input name '%s' does not exist" % inputname)
  390. grid = datadescription.GetInputDescriptionByName(inputname).GetGrid()
  391. if not grid:
  392. # we have a description of this channel but we don't need the grid so return
  393. return
  394. if inputname in self.__ProducersMap:
  395. raise RuntimeError("CreateProducer is being called multiple times for input '%s'" % inputname)
  396. producer = simple.PVTrivialProducer(guiName=inputname)
  397. producer.add_attribute("cpSimulationInput", inputname)
  398. # mark this as an input proxy so we can use cpstate.locate_simulation_inputs()
  399. # to find it
  400. producer.SMProxy.cpSimulationInput = inputname
  401. # we purposefully don't set the time for the PVTrivialProducer here.
  402. # when we update the pipeline we will do it then.
  403. producer.GetClientSideObject().SetOutput(grid, datadescription.GetTime())
  404. if grid.IsA("vtkImageData") == True or \
  405. grid.IsA("vtkStructuredGrid") == True or \
  406. grid.IsA("vtkRectilinearGrid") == True:
  407. extent = datadescription.GetInputDescriptionByName(inputname).GetWholeExtent()
  408. producer.WholeExtent= [ extent[0], extent[1], extent[2], extent[3], extent[4], extent[5] ]
  409. # Save the producer for easy access in UpdateProducers() call.
  410. self.__ProducersMap[inputname] = producer
  411. producer.UpdatePipeline(datadescription.GetTime())
  412. return producer
  413. def CreateTemporalProducer(self, datadescription, inputname):
  414. """Python access to a temporal cache object associated with a specific
  415. one simulation product. Much like CreateProducer, only this ends up with
  416. a temporal cache filter instead of a PVTrivialProducer."""
  417. if not datadescription.GetInputDescriptionByName(inputname):
  418. raise RuntimeError ("Simulation input name '%s' does not exist" % inputname)
  419. idd = datadescription.GetInputDescriptionByName(inputname)
  420. cache = idd.GetTemporalCache()
  421. if not cache:
  422. raise RuntimeError ("I see no cache for '%s'" % inputname)
  423. return
  424. return servermanager._getPyProxy(cache)
  425. def ProcessExodusIIWriter(self, writer):
  426. """Extra work for the ExodusII writer to avoid undesired warnings
  427. and print out a message on how to read the files into Ensight."""
  428. # Disable the warning about not having meta data available since we can
  429. # use this writer for vtkDataSets
  430. writer.IgnoreMetaDataWarning = 1
  431. # optionally print message so that people know what file string to use to open in Ensight
  432. if self.__PrintEnsightFormatString:
  433. pm = servermanager.vtkProcessModule.GetProcessModule()
  434. pid = pm.GetGlobalController().GetLocalProcessId()
  435. if pid == 0:
  436. nump = pm.GetGlobalController().GetNumberOfProcesses()
  437. if nump == 1:
  438. print("Ensight 'Set string' input is '", writer.FileName, ".*'", sep="")
  439. else:
  440. print("Ensight 'Set string' input is '", writer.FileName, ".*."+str(nump)+ \
  441. ".<"+str(nump)+":%0."+str(len(str(nump-1)))+"d>'", sep="")
  442. def RegisterWriter(self, writer, filename, freq, paddingamount=0, **params):
  443. """Registers a writer proxy. This method is generally used in
  444. CreatePipeline() to register writers. All writes created as such will
  445. write the output files appropriately in WriteData() is called.
  446. params should be empty as of ParaView 5.9 but is passed in for
  447. backwards compatibility."""
  448. writerParametersProxy = self.WriterParametersProxy(
  449. writer, filename, freq, paddingamount)
  450. writer.FileName = filename
  451. writer.add_attribute("parameters", writerParametersProxy)
  452. for p in params:
  453. v = params[p]
  454. if writer.GetProperty(p) is not None:
  455. wp = writer.GetProperty(p)
  456. wp.SetData(v)
  457. self.__WritersList.append(writer)
  458. helperName = writer.GetXMLName()
  459. if helperName == "ExodusIIWriter":
  460. self.ProcessExodusIIWriter(writer)
  461. return writer
  462. def WriterParametersProxy(self, writer, filename, freq, paddingamount):
  463. """Creates a client only proxy that will be synchronized with ParaView
  464. Live, allowing a user to set the filename and frequency.
  465. """
  466. controller = servermanager.ParaViewPipelineController()
  467. # assume that a client only proxy with the same name as a writer
  468. # is available in "insitu_writer_parameters"
  469. # Since coprocessor sometimes pass writer as a custom object and not
  470. # a proxy, we need to handle that. Just creating any arbitrary writer
  471. # proxy to store the parameters it acceptable. So let's just do that
  472. # when the writer is not a proxy.
  473. writerIsProxy = isinstance(writer, servermanager.Proxy)
  474. helperName = writer.GetXMLName() if writerIsProxy else "XMLPImageDataWriter"
  475. proxy = servermanager.ProxyManager().NewProxy(
  476. "insitu_writer_parameters", helperName)
  477. controller.PreInitializeProxy(proxy)
  478. if writerIsProxy:
  479. # it's possible that the writer can take in multiple input connections
  480. # so we need to go through all of them. the try/except block seems
  481. # to be the best way to figure out if there are multiple input connections
  482. try:
  483. length = len(writer.Input)
  484. for i in range(length):
  485. proxy.GetProperty("Input").AddInputConnection(
  486. writer.Input[i].SMProxy, 0)
  487. except:
  488. proxy.GetProperty("Input").SetInputConnection(
  489. 0, writer.Input.SMProxy, 0)
  490. proxy.GetProperty("FileName").SetElement(0, filename)
  491. proxy.GetProperty("WriteFrequency").SetElement(0, freq)
  492. proxy.GetProperty("PaddingAmount").SetElement(0, paddingamount)
  493. controller.PostInitializeProxy(proxy)
  494. controller.RegisterPipelineProxy(proxy)
  495. return proxy
  496. def RegisterCinemaTrack(self, name, proxy, smproperty, valrange):
  497. """
  498. Register a point of control (filter's property) that will be varied over in a cinema export.
  499. """
  500. if not isinstance(proxy, servermanager.Proxy):
  501. raise RuntimeError ("Invalid 'proxy' argument passed to RegisterCinemaTrack.")
  502. self.__CinemaTracksList.append({"name":name, "proxy":proxy, "smproperty":smproperty, "valrange":valrange})
  503. proxyDefinitions = self.__CinemaTracks[proxy] if (proxy in self.__CinemaTracks) else {}
  504. proxyDefinitions[smproperty] = valrange
  505. self.__CinemaTracks[proxy] = proxyDefinitions
  506. return proxy
  507. def AddArraysToCinemaTrack(self, proxy, propertyName, arrayNames):
  508. ''' Register user-defined target arrays by name. '''
  509. if not isinstance(proxy, servermanager.Proxy):
  510. raise RuntimeError ("Invalid 'proxy' argument passed to AddArraysToCinemaTrack.")
  511. proxyDefinitions = self.__CinemaTracks[proxy] if (proxy in self.__CinemaTracks) else {}
  512. proxyDefinitions[propertyName] = arrayNames
  513. self.__CinemaTracks[proxy] = proxyDefinitions
  514. return proxy
  515. def RegisterView(self, view, filename, freq, fittoscreen, magnification, width, height,
  516. cinema=None, compression=None):
  517. """Register a view for image capture with extra meta-data such
  518. as magnification, size and frequency."""
  519. if not isinstance(view, servermanager.Proxy):
  520. raise RuntimeError ("Invalid 'view' argument passed to RegisterView.")
  521. view.add_attribute("cpFileName", filename)
  522. view.add_attribute("cpFrequency", freq)
  523. view.add_attribute("cpFitToScreen", fittoscreen)
  524. view.add_attribute("cpMagnification", magnification)
  525. view.add_attribute("cpCinemaOptions", cinema)
  526. view.add_attribute("cpCompression", compression)
  527. view.ViewSize = [ width, height ]
  528. self.__ViewsList.append(view)
  529. return view
  530. def CreateWriter(self, proxy_ctor, filename, freq):
  531. """**DEPRECATED!!! Use RegisterWriter instead**
  532. Creates a writer proxy. This method is generally used in
  533. reatePipeline() to create writers. All writes created as such will
  534. write the output files appropriately in WriteData() is called.
  535. """
  536. writer = proxy_ctor()
  537. return self.RegisterWriter(writer, filename, freq)
  538. def CreateView(self, proxy_ctor, filename, freq, fittoscreen, magnification, width, height):
  539. """**DEPRECATED!!! Use RegisterView instead**
  540. Create a CoProcessing view for image capture with extra meta-data
  541. such as magnification, size and frequency.
  542. """
  543. view = proxy_ctor()
  544. return self.RegisterView(view, filename, freq, fittoscreen, magnification, width, height, None)
  545. def Finalize(self):
  546. for writer in self.__WritersList:
  547. if hasattr(writer, 'Finalize'):
  548. writer.Finalize()
  549. for view in self.__ViewsList:
  550. if hasattr(view, 'Finalize'):
  551. view.Finalize()
  552. def RescaleDataRange(self, view, time):
  553. """DataRange can change across time, sometime we want to rescale the
  554. color map to match to the closer actual data range."""
  555. reps = view.Representations
  556. for rep in reps:
  557. if not hasattr(rep, 'Visibility') or \
  558. not rep.Visibility or \
  559. not hasattr(rep, 'MapScalars') or \
  560. not rep.MapScalars or \
  561. not rep.LookupTable:
  562. # rep is either not visible or not mapping scalars using a LUT.
  563. continue;
  564. input = rep.Input
  565. input.UpdatePipeline(time) #make sure range is up-to-date
  566. lut = rep.LookupTable
  567. colorArrayInfo = rep.GetArrayInformationForColorArray()
  568. if not colorArrayInfo:
  569. import sys
  570. datarange = [sys.float_info.max, -sys.float_info.max]
  571. else:
  572. if lut.VectorMode != 'Magnitude' or \
  573. colorArrayInfo.GetNumberOfComponents() == 1:
  574. datarange = colorArrayInfo.GetComponentRange(lut.VectorComponent)
  575. else:
  576. # -1 corresponds to the magnitude.
  577. datarange = colorArrayInfo.GetComponentRange(-1)
  578. from paraview.vtk import vtkDoubleArray
  579. import paraview.servermanager
  580. pm = paraview.servermanager.vtkProcessModule.GetProcessModule()
  581. globalController = pm.GetGlobalController()
  582. localarray = vtkDoubleArray()
  583. localarray.SetNumberOfTuples(2)
  584. localarray.SetValue(0, -datarange[0]) # negate so that MPI_MAX gets min instead of doing a MPI_MIN and MPI_MAX
  585. localarray.SetValue(1, datarange[1])
  586. globalarray = vtkDoubleArray()
  587. globalarray.SetNumberOfTuples(2)
  588. globalController.AllReduce(localarray, globalarray, 0)
  589. globaldatarange = [-globalarray.GetValue(0), globalarray.GetValue(1)]
  590. rgbpoints = lut.RGBPoints.GetData()
  591. numpts = len(rgbpoints)//4
  592. if globaldatarange[0] != rgbpoints[0] or globaldatarange[1] != rgbpoints[(numpts-1)*4]:
  593. # rescale all of the points
  594. oldrange = rgbpoints[(numpts-1)*4] - rgbpoints[0]
  595. newrange = globaldatarange[1] - globaldatarange[0]
  596. # only readjust if the new range isn't zero.
  597. if newrange != 0:
  598. newrgbpoints = list(rgbpoints)
  599. # if the old range isn't 0 then we use that ranges distribution
  600. if oldrange != 0:
  601. for v in range(numpts-1):
  602. newrgbpoints[v*4] = globaldatarange[0]+(rgbpoints[v*4] - rgbpoints[0])*newrange/oldrange
  603. # avoid numerical round-off, at least with the last point
  604. newrgbpoints[(numpts-1)*4] = globaldatarange[1]
  605. else: # the old range is 0 so the best we can do is to space the new points evenly
  606. for v in range(numpts+1):
  607. newrgbpoints[v*4] = globaldatarange[0]+v*newrange/(1.0*numpts)
  608. lut.RGBPoints.SetData(newrgbpoints)
  609. def UpdateCinema(self, view, datadescription, specLevel):
  610. """ called from catalyst at each timestep to add to the cinema database """
  611. if not view.IsA("vtkSMRenderViewProxy") == True:
  612. return
  613. try:
  614. import paraview.tpl.cinema_python.adaptors.explorers as explorers
  615. import paraview.tpl.cinema_python.adaptors.paraview.pv_explorers as pv_explorers
  616. import paraview.tpl.cinema_python.adaptors.paraview.pv_introspect as pv_introspect
  617. except ImportError as e:
  618. import paraview
  619. paraview.print_error("Cannot import cinema")
  620. paraview.print_error(e)
  621. return
  622. #figure out where to put this store
  623. import os.path
  624. vfname = view.cpFileName
  625. extension = os.path.splitext(vfname)[1]
  626. vfname = vfname[0:vfname.rfind("_")] #strip _num.ext
  627. fname = os.path.join(os.path.dirname(vfname),
  628. "cinema",
  629. os.path.basename(vfname),
  630. "info.json")
  631. def float_limiter(x):
  632. #a shame, but needed to make sure python, javascript and (directory/file)name agree
  633. if isinstance(x, (float)):
  634. return '%.6e' % x #arbitrarily chose 6 significant digits
  635. else:
  636. return x
  637. #what time?
  638. time = datadescription.GetTime()
  639. view.ViewTime = time
  640. formatted_time = float_limiter(time)
  641. #ensure that cinema operates on the specified view
  642. simple.SetActiveView(view)
  643. # Include camera information in the user defined parameters.
  644. # pv_introspect uses __CinemaTracks to customize the exploration.
  645. co = view.cpCinemaOptions
  646. camType = co["camera"]
  647. if "phi" in co:
  648. self.__CinemaTracks["phi"] = co["phi"]
  649. if "theta" in co:
  650. self.__CinemaTracks["theta"] = co["theta"]
  651. if "roll" in co:
  652. self.__CinemaTracks["roll"] = co["roll"]
  653. tracking_def = {}
  654. if "tracking" in co:
  655. tracking_def = co['tracking']
  656. #figure out what we show now
  657. pxystate= pv_introspect.record_visibility()
  658. # a conservative global bounds for consistent z scaling
  659. minbds, maxbds = pv_introspect.max_bounds()
  660. #make sure depth rasters are consistent
  661. view.MaxClipBounds = [minbds, maxbds, minbds, maxbds, minbds, maxbds]
  662. view.LockBounds = 1
  663. disableValues = False if 'noValues' not in co else co['noValues']
  664. if specLevel=="B":
  665. p = pv_introspect.inspect(skip_invisible=True)
  666. else:
  667. p = pv_introspect.inspect(skip_invisible=False)
  668. fs = pv_introspect.make_cinema_store(p, fname, view,
  669. forcetime = formatted_time,
  670. userDefined = self.__CinemaTracks,
  671. specLevel = specLevel,
  672. camType = camType,
  673. extension = extension,
  674. disableValues = disableValues)
  675. #all nodes participate, but only root can writes out the files
  676. pm = servermanager.vtkProcessModule.GetProcessModule()
  677. pid = pm.GetPartitionId()
  678. enableFloatVal = False if 'floatValues' not in co else co['floatValues']
  679. new_files = {}
  680. ret = pv_introspect.explore(fs, p, iSave = (pid == 0),
  681. currentTime = {'time':formatted_time},
  682. userDefined = self.__CinemaTracks,
  683. specLevel = specLevel,
  684. camType = camType,
  685. tracking = tracking_def,
  686. floatValues = enableFloatVal,
  687. disableValues = disableValues)
  688. if pid == 0:
  689. fs.save()
  690. new_files[vfname] = ret;
  691. view.LockBounds = 0
  692. #restore what we showed
  693. pv_introspect.restore_visibility(pxystate)
  694. return os.path.basename(vfname), new_files
  695. def IsInModulo(self, datadescription, frequencies):
  696. """
  697. Return True if the given timestep in datadescription is in one of the provided frequencies
  698. or output is forced. This can be interpreted as follow::
  699. isFM = IsInModulo(timestep-timeStepToStartOutputAt, [2,3,7])
  700. is similar to::
  701. isFM = (timestep-timeStepToStartOutputAt % 2 == 0) or (timestep-timeStepToStartOutputAt % 3 == 0) or (timestep-timeStepToStartOutputAt % 7 == 0)
  702. The timeStepToStartOutputAt is the first timestep that will potentially be output.
  703. """
  704. timestep = datadescription.GetTimeStep()
  705. if timestep < self.__TimeStepToStartOutputAt and not self.__ForceOutputAtFirstCall:
  706. return False
  707. for frequency in frequencies:
  708. if frequency > 0 and self.NeedToOutput(datadescription, frequency):
  709. return True
  710. return False
  711. def NeedToOutput(self, datadescription, frequency):
  712. """
  713. Return True if we need to output based on the input timestep, frequency and forceOutput. Checks based
  714. __FirstTimeStepIndex, __FirstTimeStepIndex, __ForceOutputAtFirstCall and __TimeStepToStartOutputAt
  715. member variables.
  716. """
  717. if datadescription.GetForceOutput() == True:
  718. return True
  719. timestep = datadescription.GetTimeStep()
  720. if self.__FirstTimeStepIndex == None:
  721. self.__FirstTimeStepIndex = timestep
  722. if self.__ForceOutputAtFirstCall and self.__FirstTimeStepIndex == timestep:
  723. return True
  724. if self.__TimeStepToStartOutputAt <= timestep and (timestep-self.__TimeStepToStartOutputAt) % frequency == 0:
  725. return True
  726. return False
  727. def EnableCinemaDTable(self):
  728. """ Enable the normally disabled cinema D table export feature """
  729. self.__CinemaDHelper = exportnow.CinemaDHelper(True,
  730. self.__ImageRootDirectory)
  731. def __AppendCViewToCinemaDTable(self, time, producer, filelist):
  732. """
  733. This is called every time catalyst writes any cinema image result
  734. to update the Cinema D index of outputs table.
  735. Note, we aggregate file operations later with __FinalizeCinemaDTable.
  736. """
  737. if self.__CinemaDHelper is None:
  738. return
  739. import vtk
  740. comm = vtk.vtkMultiProcessController.GetGlobalController()
  741. if comm.GetLocalProcessId() == 0:
  742. self.__CinemaDHelper.AppendCViewToCinemaDTable(time, producer, filelist)
  743. def __AppendToCinemaDTable(self, time, producer, filename):
  744. """
  745. This is called every time catalyst writes any data file or screenshot
  746. to update the Cinema D index of outputs table.
  747. Note, we aggregate file operations later with __FinalizeCinemaDTable.
  748. """
  749. if self.__CinemaDHelper is None:
  750. return
  751. import vtk
  752. comm = vtk.vtkMultiProcessController.GetGlobalController()
  753. if comm.GetLocalProcessId() == 0:
  754. self.__CinemaDHelper.AppendToCinemaDTable(time, producer, filename)
  755. def __FinalizeCinemaDTable(self):
  756. if self.__CinemaDHelper is None:
  757. return
  758. import vtk
  759. comm = vtk.vtkMultiProcessController.GetGlobalController()
  760. if comm.GetLocalProcessId() == 0:
  761. self.__CinemaDHelper.WriteNow()
  762. def SetRootDirectory(self, root_directory):
  763. """ Makes Catalyst put all output under this directory. """
  764. self.SetImageRootDirectory(root_directory)
  765. self.SetDataRootDirectory(root_directory)
  766. def SetImageRootDirectory(self, root_directory):
  767. """Specify root directory for image extracts"""
  768. if root_directory and not root_directory.endswith("/"):
  769. root_directory = root_directory + "/"
  770. self.__ImageRootDirectory = root_directory
  771. def SetDataRootDirectory(self, root_directory):
  772. """Specify root directory for data extracts"""
  773. if root_directory and not root_directory.endswith("/"):
  774. root_directory = root_directory + "/"
  775. self.__DataRootDirectory = root_directory
  776. def __FixupWriters(self):
  777. """ Called once to ensure that all writers obey the root directory directive """
  778. if self.__ImageRootDirectory:
  779. for view in self.__ViewsList:
  780. view.cpFileName = self.__ImageRootDirectory + view.cpFileName
  781. if self.__DataRootDirectory:
  782. for writer in self.__WritersList:
  783. fileName = self.__DataRootDirectory + writer.parameters.GetProperty("FileName").GetElement(0)
  784. writer.parameters.GetProperty("FileName").SetElement(0, fileName)
  785. writer.parameters.FileName = fileName