8. Post Processing

In this section, we will provide several example scripts that demonstrate how to interact with the Postprocessor environment using the PyMpc API. The classes, functions, and methods available in PyMpc enable users to access and customize various components of the Postprocessor document, including databases, plots, chart data sets, and charts.

With PyMpc, users can not only modify existing elements but also add new ones, such as plot groups, plots, chart data, and charts. Furthermore, users can import values from external sources, allowing for comparisons and checks to be performed within STKO.

These capabilities provide a high degree of flexibility and customization, enabling users to tailor the Postprocessor environment to their specific needs and workflows. The example scripts presented in this section will illustrate the various ways in which PyMpc can be used to interact with and customize the Postprocessor environment.

8.1. Document

The following example will guide you through the various interactions possible with the Postprocessor document. This script uses the thread utilities described in the ThreadUtils section. While the script explicitly calls these classes and functions, they could be stored in an external file (e.g., ThreadUtils.py) and imported to create a more concise version of the code. This exercise can help you practice importing external functions in Python.

To access the Postprocessor document, you can use the following code snippet. You have the option to use a dialog to keep the script alive until the thread completes, or simply use an event loop. Then, you can retrieve the Postprocessor document and assign it to a variable named doc.

This approach allows you to interact with the Postprocessor document in a flexible and customizable way, leveraging the power of Python scripting and the ThreadUtils library.

from PyMpc import *
import numpy as np
from time import sleep
from PySide2.QtCore import Qt, QObject, Signal, Slot, QThread, QEventLoop
from PySide2.QtWidgets import QDialog, QLabel, QProgressBar, QVBoxLayout, QApplication

use_dialog = True # True = Dialog, False = Event Loop

doc = App.postDocument()
Clear all existing setups in the Postprocessor and open a database.

doc.clearPlotGroups()
doc.clearCharts()
doc.clearChartData()
doc.commitChanges()
doc.dirty = True
doc.select(None)

App.runCommand(OpenDatabase, C:/VBShared/di_michele/micro_inplane.mpco)

Create the simple QObject that wraps the user-defined function, and calls this function waiting for it to finish, as shown in section ThreadUtils. and then create the slots to be called on the main GUI thread, to add a plot group, chart data and charts.

Next, define the slots that will be called on the main GUI thread to perform the following actions:

  • Add a plot group to the Postprocessor document.

  • Add chart data to the plot group.

  • Add charts to the plot group.

By using the QBlockingSlot class and defining these slots, you can ensure that the GUI remains responsive while the user-defined function is executed on a separate thread.

Here is an example code snippet that demonstrates this approach:

class QBlockingSlot(QObject):
    requestCall = Signal(tuple)
    done = Signal()
    def __init__(self, function, parent = None):
        super(QBlockingSlot, self).__init__(parent)
        self.requestCall.connect(self.run)
        self.function = function
    def run(self, data):
        self.function(data)
        self.done.emit()
    def call(self, data):
        loop = QEventLoop()
        self.done.connect(loop.quit)
        self.requestCall.emit(data)
        loop.exec_()

def addPlotGroup_function(pgroup):
    doc.addPlotGroup(pgroup)
    doc.commitChanges()
    doc.dirty = True
    doc.select(pgroup)
    doc.setActivePlotGroup(pgroup)
    info = MpcOdpRegeneratorUpdateInfo()
    info.onlyTexture = True
    pgroup.update(info)

addPlotGroup = QBlockingSlot(addPlotGroup_function)

def addChartData_function(cdata):
    doc.addChartData(cdata)
    doc.commitChanges()
    doc.dirty = True
    doc.select(cdata)

addChartData = QBlockingSlot(addChartData_function)

def addChart_function(chart):
    doc.addChart(chart)
    doc.commitChanges()
    doc.dirty = True
    doc.select(chart)

addChart = QBlockingSlot(addChart_function)

Then define the worker class. It will run the lengthy function on a working thread and emit signals for calling the connected slots on the main thread. The lengthy function is this case consists in the creation of 10 new items in terms of plot groups, chart data sets, chart items to insert in the charts and finally the charts.

class Worker(QObject):
   sendPercentage = Signal(int)
   finished = Signal()

   @Slot()
   def run(self):
       for iter in range(10):
           pgroup = MpcOdpGroup()
           pgroup.id = doc.genNextIdForPlotGroup()
           pgroup.name = "Test Plot Group %i" % iter
           addPlotGroup.call(pgroup)

           cdata = MpcChartData()
           cdata.id = doc.genNextIdForChartData()
           cdata.name = "Test Chart Data %i" % iter
           cdata.xLabel = "XData"
           cdata.xLabel = "YData"
           x = np.linspace(0.0, 4.0*float(iter), 1000)
           y = np.sin(x) * np.logspace(1.0, 1.0+float(iter)/10.0, 1000)
           cdata.x = Math.double_array(x.tolist())
           cdata.y = Math.double_array(y.tolist())
           addChartData.call(cdata)
           sleep(0.2)

           cdata_item = MpcChartDataGraphicItem(cdata)
           cdata_item.color = MpcQColor(255, 150, 0, 255)
           cdata_item.thickness = 1.5
           cdata_item.penStyle = MpcQPenStyle.SolidLine

           chart = MpcChart()
           chart.id = doc.genNextIdForChart()
           chart.name = "Test Chart"
           chart.addItem(cdata_item)
           addChart.call(chart)
           sleep(0.2)
           self.sendPercentage.emit(int(float(iter+1)/10.0*100.0))

       self.finished.emit()

Create a dialog or an event loop that will block the script till the end. Then create the thread, the worker, and move the worker to the thread.

if use_dialog:
    dialog = QDialog()
    dialog.setLayout(QVBoxLayout())
    dialog.layout().addWidget(QLabel("Work in progres. Please wait"))
    pbar = QProgressBar()
    pbar.setRange(1, 100)
    pbar.setTextVisible(True)
    dialog.layout().addWidget(pbar)
else:
    loop = QEventLoop()

Create the thread, the worker, and move the worker to the thread

thread = QThread()
worker = Worker()
worker.moveToThread(thread)
Set up worker and thread connections

worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.started.connect(worker.run)
thread.finished.connect(thread.deleteLater)
 ... for dialog or event loop
if use_dialog:
    worker.sendPercentage.connect(pbar.setValue)
    thread.finished.connect(dialog.accept)
else:
    thread.finished.connect(loop.quit)
Start the thread and run dialog or event loop

thread.start()

if use_dialog:
    dialog.exec_()
else:
    loop.exec_()

8.2. Output data plot

Note

Coming soon.

8.3. Results

Results that can be created and manipulated in the Python API, are called virtual, as they are not stored when created, regardless of their content (information on fibers, nodes, or elements). A new result is called virtual in the sense that it does not belong to a database but it is calculated each time it is called. This can be an advantage as they do not occupy any storage space. On the other hand, it has to be kept in mind that calling such results could be costly in terms of computational capacity (cost of the reference of other results, plus the cost of the operation the user has set to calculate on it or with it).

Note

Examples on results coming soon.