Converting a Python Result to an ACT Extension
I recently got the request of converting a Python result a user had created to an extension.
Why would you want to do that? Well, simply to more easily share your work with your colleagues. The Python Result can be used to create a prototype of a specific post-processing, but might be harder to share, especially if you have details properties associated to it.
Below I'm showing you how to go from one to the other, with the necessary pieces of code. In this example, the Python Result has two properties: a float input to get the display time and a dropdown with a list of named selections to choose from. It will then display the deformation on the selection at the specified time - nothing complex, but that's not the purpose of this discussion. Also, the Python Result has two additional properties to provide the mean and maximum value of the result being displayed.
Let's start with the Python Results' property provider where you define the properties to be shown in the details view. This is the code you would put under the "Property Provider" tab of your Python Result.
def reload_props(): this.PropertyProvider = None # comment the following return to allow the rest of the function definition to be executed and add properties # return """ Some sample code is provided below that shows how to: 1. Create an instance of the Provider. The Provider class is used to add custom properties to the details. 2. Use the Provider instance to add custom properties. 3. Configure those custom properties. """ # Create the property instance provider = Provider() # Create a group named Group 1. group = provider.AddGroup("My Properties") # Create a property with control type Expression and a property with control type Double, and add it to the Group 1 # Property for seleciton of a time value time_prop = group.AddProperty("Display Time", Control.Double) # DropDown to select a named selection selection_prop = group.AddProperty("Named selection", Control.Options) # Retrieve all named selections # Fill the dropdown with the list of named selections all_named_sel=DataModel.GetObjectsByType(DataModelObjectCategory.NamedSelection) ns_list={} idx=1 for ns in all_named_sel: ns_list[idx]=ns.Name idx+=1 selection_prop.Options = ns_list # Second group for some results group2 = provider.AddGroup("My Results") # Mean value mean_prop = group2.AddProperty("Mean value", Control.Double) mean_prop.ParameterType = ParameterType.Output # Maximum value max_prop = group2.AddProperty("Maximum value", Control.Double) max_prop.ParameterType = ParameterType.Output # Connects the provider instance back to the object by setting the PropertyProvider member on this, 'this' being the # current instance of the Python Code object. this.PropertyProvider = provider
And here's the script part of the Python Result:
def post_started(sender, analysis):# Do not edit this line define_dpf_workflow(analysis) # Uncomment this function to enable retrieving results from the table/chart # def table_retrieve_result(value):# Do not edit this line # import mech_dpf # import Ans.DataProcessing as dpf # wf = dpf.Workflow(this.WorkflowId) # wf.Connect('contour_selector', value) # this.Evaluate() def define_dpf_workflow(analysis): import mech_dpf import Ans.DataProcessing as dpf mech_dpf.setExtAPI(ExtAPI) dataSource = dpf.DataSources(analysis.ResultFileName) u = dpf.operators.result.displacement() nrm = dpf.operators.math.norm_fc(u) # Get properties time=float(this.GetCustomPropertyByPath("My Properties/Display Time").Value) selection_name=this.GetCustomPropertyByPath("My Properties/Named selection").ValueString # Get selection corresponding to user choice if selection_name!='': scoping_op=dpf.operators.scoping.on_named_selection(data_sources=dataSource,named_selection_name=selection_name.ToUpper()) scoping=scoping_op.outputs.getmesh_scoping() u.inputs.mesh_scoping.Connect(scoping) u.inputs.data_sources.Connect(dataSource) u.inputs.time_scoping.Connect(time) # Set output results max=dpf.operators.min_max.min_max(nrm) maxval=max.outputs.getfield_max().Data[0] nrmdata=nrm.outputs.getfields_container()[0].Data mean=sum(list(nrmdata))/len(nrmdata) this.GetCustomPropertyByPath("My Results/Mean value").Value=mean this.GetCustomPropertyByPath("My Results/Maximum value").Value=maxval dpf_workflow = dpf.Workflow() dpf_workflow.Add(u) dpf_workflow.Add(nrm) # dpf_workflow.SetInputName(u, 0, 'time') # dpf_workflow.Connect('time', timeScop) dpf_workflow.SetOutputContour(nrm) dpf_workflow.Record('wf_id', False) this.WorkflowId = dpf_workflow.GetRecordedId()
Now read the next comment to see how this is transformed into an ACT extension.
Comments
-
Let's look at the conversion. An ACT extension in its scripted form is made at minimum from a XML file that describes how an object looks like and a Python File with callback functions.
A callback function is a function called upon a certain event triggered by the user. In our case, we'll have three of them: one when the user inserts the result under Solution (createNewResult), one when the user activates the new object (fillNamedSelections) and one when the user evaluates the object (evaluateNewResult).
Let's start with the XML File
<extension version="1" name="New Result"> <guid shortid="New Result">8fe89783-7da1-4679-b2cf-7f381f2434e9</guid> <script src="new_result.py" /> <interface context="Mechanical"> <images>images</images> <!-- Create a toolbar and add a button to insert a ACT load --> <toolbar name="New Result" caption="Conversion from Python Result"> <entry name="New Result" icon="results"> <callbacks> <!-- This will add the ACT object to the tree --> <onclick>createNewResult</onclick> </callbacks> </entry> </toolbar> </interface> <simdata context="Mechanical"> <result name="new_result" version="1" caption="New Result" icon="result" location="node" type="scalar" timehistory="False"> <callbacks> <evaluate>evaluateNewResult</evaluate> </callbacks> <property name="Geometry" caption="Geometry" control="scoping"></property> <property name="NamedSelectionChoice" caption="Named Selection" control="select" default="Margin"> <callbacks> <onactivate>fillNamedSelections</onactivate> </callbacks> </property> <property name="MeanValue" caption="Mean Value" control="float" isparameter="True" readonly="True"></property> <property name="MaximumValue" caption="Maximum Value" control="float" isparameter="True" readonly="True"></property> </result> </simdata> </extension>
In this file, we define:
<Interface>
: the context we are in (Mechanical), The Toolbar that will host our new button and what happens when we click on this button (<onclick>
callback)<simdata>
: we define a new result (so Mechanical knows where to put the object) and its properties and callbacks. As you can see, we define a selection list (for the named selections) and two properties, Mean Value and Maximum Value. But no display time! This is indeed a default property of a result object we don't have to define again. The callbacks are<onactivate>
to fill the list of selections when the object is displayed and<evaluate>
when the user evaluates the result
Now on to the companion Python file. Note this file must be placed in a folder with the same name as your xml file. So your file structure will look like this (note the Python filename matching the
<script src>
in the xml file:extension.xml
extension (folder)
-- new_result.pyAnd here is the code. I copied it from the Python Results script. I have added comments to show what you need to modify to make it work as an extension. The two biggest differences are in the way results are collected for display (dpf Worfklow for the Python result vs collector in an extension) and the way properties are set or retrieved.
import units import math import os def createNewResult(analysis): """ The method is called when the toolbar button is clicked. Keyword arguments: analysis -- the active analysis """ # Add a "weldplot" object [defined in XML] in solution tree of the analyis. analysis.CreateResultObject("new_result", ExtAPI.ExtensionManager.CurrentExtension) def fillNamedSelections(result,property): named_selections = ExtAPI.DataModel.Components property.Options.Clear() for ns in named_selections: result.Properties["NamedSelectionChoice"].Options.Add(ns.Name) def evaluateNewResult(result,stepInfo,collector): """ The method is called when the "Evaluate" action button is clicked or from the generateChildren method. Keyword arguments: obj -- the object to avaluate """ reader = result.Analysis.GetResultsData() ##### Modification to Python result # Define analysis analysis=result.Analysis # Code copied from Python result import mech_dpf import Ans.DataProcessing as dpf mech_dpf.setExtAPI(ExtAPI) dataSource = dpf.DataSources(analysis.ResultFileName) u = dpf.operators.result.displacement() nrm = dpf.operators.math.norm_fc(u) ##### Modification to Python result # Get Properties #### Was this: ## Get properties #time=float(this.GetCustomPropertyByPath("My Properties/Display Time").Value) #selection_name=this.GetCustomPropertyByPath("My Properties/Named selection").ValueString #### Becomes: time=stepInfo.Time selection_name=result.Properties["NamedSelectionChoice"].Value ExtAPI.Log.WriteMessage(str(selection_name)) # Get selection corresponding to user choice if selection_name!='': scoping_op=dpf.operators.scoping.on_named_selection(data_sources=dataSource,named_selection_name=selection_name.ToUpper()) scoping=scoping_op.outputs.getmesh_scoping() u.inputs.mesh_scoping.Connect(scoping) u.inputs.data_sources.Connect(dataSource) u.inputs.time_scoping.Connect(time) # Set output results max=dpf.operators.min_max.min_max(nrm) maxval=max.outputs.getfield_max().Data[0] nrmdata=nrm.outputs.getfields_container()[0].Data mean=sum(list(nrmdata))/len(nrmdata) ##### Modification to Python result #### Was this: #this.GetCustomPropertyByPath("My Results/Mean value").Value=mean #this.GetCustomPropertyByPath("My Results/Maximum value").Value=maxval #### Becomes: result.Properties["MeanValue"].Value=mean result.Properties["MaximumValue"].Value=maxval ##### Modification to Python result ### Remove all "Workflow related lines" and replace with this # Get field to display nrm_field=nrm.outputs.getfields_container()[0] # Retrieve data and node ids node_ids=nrm_field.ScopingIds nrm_data=nrm_field.Data # Fill Collector for Mechanical to display values for i in range(len(node_ids)): collector.SetValues(node_ids[i],[nrm_data[i]])
1 -
@Pierre Thieffry this is good information and a good example, but one thing that I have found is that the evaluateNewResult is called for each time point when Calculate Time History is set to True. It is called a final time again for the specified time point. This can slow things down. I have tried using streams so that I am not repeatedly connecting to the data source, but because the final call may be at any time, I don't know when to release the stream which causes the RST to be locked and you cannot clear the results. Since we can extract all time points into a Field Container for the result, is there a way to improve the efficiency so that we can just do the DPF call once?
0