March 25, 2021

Integrate any Engineering Software Easily with a New Configurable and Flexible User Block

Introduction

pSeven allows engineers to integrate any CAD/CAE software packages their company use into a single workflow and to eliminate exhaustive repetitive tasks by automating file and data exchange between them.

There are three different ways of software integration in pSeven:

  1. Direct integration ― allows integration using program-specific blocks, which are intended to provide an easy configurable connector between pSeven and a specific software tool (SolidWorks, Creo, NX, ANSYS, etc.)
  2. Generic integration ― allows integration using command line interface. It provides greater flexibility than direct integration blocks, but often requires more configuration.
  3. Integration using User Blocks ― a new integration method released in the 6.17 version. It allows users to integrate any software in their workflow. User block allows user to combine the advantages of two previous integration ways: convenient configuration of direct integration with an interface between a pSeven workflow and a software and a great flexibility of generic integration.

User block integration implementation is presented in this Tech Tip article.

userblock-1

Fig. 1. User Block in pSeven

An example of using User block in a rotation disk problem

User block is a full-featured block template, which user can use to extend the pSeven’s block library with new integrations. User block interface is similar to an integration type blocks and provides some of their components. Most notably, the parameters tree, which allows the user who configures the block to review the list of inputs and outputs list and set the block ports with couple of clicks. Block also provides error handling support, sandbox directories, and other common block features.

The amount of required custom code is minimal: the block just needs two Python functions, which return the tree of parameters in edit time and process the input data in runtime.

Below, we will use a Rotating Disk project from a standard pSeven example (3.1) to demonstrate the implementation of the User block.

It is worth noting that this article contains only a brief guide about User block. To see more information, please visit User block section of the pSeven user manual.

userblock-2

Fig. 2. Drawing of the rotating disk

Workflow with rotating disk calculation has 10 input variables: 8 geometry parameters (r1, r2, r3, r4, r5, r6, t1, t3, t5) and 2 physical parameters (Mass of blades “Mb” and rotation rate “N”). A solver calculates 3 output variables: max stress (“Seqv_max”), mass and displacements (“u2”) of the rotating disk.

As we use the self-made solver, the standard scheme (Text block - Program block - Text block) of generic integration is implemented in the example.

The first Text block of this workflow loads a template file and substitutes values of input variables in specified fields, then outputs modified file to a port. Program block receives this input file and starts the program. After simulation is done, result file is transferred to another text block, which parses and outputs all the results.

userblock-3

Fig. 3. Rotating disk calculation workflow

Our task is to replace described generic integration scheme with single easy-to-use one User block.

Adding a block to the library

First, we need to add our new block to pSeven library using create_block.py script located in pSeven dir\scripts\, where pSeven dir is the pSeven installation directory.

Our command will be as follows:

cd /d "C:\Program Files\DATADVANCE\pSeven-6.19"
python.exe scripts\create_block.py RotatingDisk --description "My new block."

where "C:\Program Files\DATADVANCE\pSeven-6.19" — default pSeven installation directory.

After using this command create_block.py script will create RotatingDisk directory in pSeven dir\blocks\. The user_block.py module in this directory will contain two functions which you have to implement, as this module define all the functionality of this block. Subsequently we will show how to implement these functions. Once you restart pSeven, you will see the new block in the block library pane:

userblock-4

Fig. 4. RotatingDisk user block in the block library pane

Tree build

The first function we need is a tree build function, which provides input and output parameters to the user interface of the block. The user can then select tree nodes to map them to block’s input and output ports, which you can read and write at the run-time.

The tree can be static (entirely defined by block implementation), or the block can generate it by parsing some file selected by the user. In our case, we will use a static tree, as all input and output parameters of model are known in advance. In that way, we only need to create a tree with a specific structure. After that, all the necessary parameters will be shown in the configuration tab.

The tree is a list of dictionaries that has the following structure:

tree = [
          {   # the MyNode node
              "id": ["MyNode"],  # has no ancestors except the tree root
              "children": [      # all children ids must mention MyNode
                                 # as their ancestor
                  {   # the first child of MyNode is a node
                      "id": ["MyNode", "FisrtChild"],
                      "children": [ ... ]
                  },
                  {   # the second child of MyNode is a leaf
                      "id": ["MyNode", "LeafChild"],
                      ...  # no children but there are more keys here
                  }
              ]
          },
          AnotherNode,
          ...
      ]

See User block manual to get more information about structure of the parameter tree. The final tree build function will only consist of a static tree. Expandexpand_more

def tree_build(file_path, **kwargs):
  # Generate tree
  tree = [
    {"id": ["Geometry"],
      "children": [
        {"id": ["Geometry", "r1"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r2"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r3"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r4"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r5"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r6"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t1"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t3"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t5"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"}
      ]
    },
    {"id": ["Physical parameters"],
      "children": [
         {"id": ["Physical parameters", "Mb"],
         "readonly": False,
         "type": "RealScalar",
         "units": "kg"},
         {"id": ["Physical parameters", "N"],
         "readonly": False,
         "type": "RealScalar",
         "units": "rpm"}
      ]
    },
    {"id": ["Outputs"],
      "children": [
         {"id": ["Outputs", "Seqv_max"],
         "readonly": True,
         "type": "RealScalar",
         "units": "MPa"},
         {"id": ["Outputs", "mass"],
         "readonly": True,
         "type": "RealScalar",
         "units": "kg"},
         {"id": ["Outputs", "u2"],
         "readonly": True,
         "type": "RealScalar",
         "units": "mm"}
      ]
    }
  ]

  metadata = {}

  return tree, metadata

Again, in general case such description can be a result of model preprocessing, when the available parameters are extracted from text files or using simulation tool API.

userblock-5

Fig. 5. Parameters tree of the User block

After tree build function is implemented, you can open your block and tap Build Tree button, then all parameters of the model will be shown on the pane like in a direct integration block (Fig. 5.).

Step function

Next to implement is a step function which contains block runtime logic. The block calls this function every time it is executed in the workflow.

First of all, we should obtain all received input values in dictionary format, where the key is a parameter name and the value is a value of parameter. We use input_read() method to get port values.

# Obtain all received inputs in dict format
  input_data = {}
  for input_name, input_details in inputs.iteritems():
  	input_data.update({input_details["tree_node"][1]:
    api.input_read(input_name)}

 

Then we use this dictionary to generate input file for solver:

program_input = """
GEOMETRY:
#r	h
{r1}	{t1}
{r2}	{t1}
{r3}	{t3}
{r4}	{t3}
{r5}	{t5}
{r6}	{t5}

PHYSICAL PARAMETERS:
rho = 4430
E = 113.8e9
mu = 0.342

Mb = {Mb}
n = {N}

BOUNDARY CONDITIONS:
#u1 = 0.015
p1 = 0.0


NUMERICAL METHOD:
method = 'ode'
mesh_divs = 100
min_section_div = 3

OUTPUT:
outputs = {{'Seqv_max','mass','u2'}}
""".format(r1=input_data['r1'],r2=input_data['r2'],r3=input_data['r3'],
	r4=input_data['r4'],r5=input_data['r5'],r6=input_data['r6'],
	t1=input_data['t1'],t3=input_data['t3'],t5=input_data['t5'],
	Mb=input_data['Mb'],N=input_data['N'])

  program_input_file = 'input.txt'
  with open(program_input_file, 'w') as f:
  	f.write(program_input)

After that we set the path to our solver and start the calculation using generated input data. The path to the solver is set in an option pane of the block dialog, field Executable file path. You can get this path in the script using kwargs.get (“exec_file_path”, “”) command.

  # Get the path specified by the Executable file path option.
  exec_file_path = kwargs.get("exec_file_path", "")

  # Start the calculation
  results = subprocess.check_output([exec_file_path, '-i',   program_input_file])

 

userblock-6

Fig. 6. Path to the solver

After calculation is done, we need to parse solver output. All result data is saved in dictionary parsed_results

 # Obtain output results
  results = results.splitlines()
  parsed_results = {}
  for line in results:
  	if line.strip() != '':
  		parsed_results[line.split()[0]] = float(line.split()[-1])

Using output_write() method we set output port values:

 # Write results to the ports
  for name in outputs.keys():
    api.output_write(name, parsed_results[output_data[name]])

 

As a result, we have ready-to-run user block with a direct interface between a workflow and the solver, which allows to run a simulation of the rotating disk and use its results of stress and displacement later in the workflow.

userblock-7

Fig. 7. Implemented rotating disk User block

An entire code of the implemented User block is as follows: expandexpand_more

import os
import subprocess

def tree_build(file_path, **kwargs):
  # Generate tree
  tree = [
    {"id": ["Geometry"],
      "children": [
        {"id": ["Geometry", "r1"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r2"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r3"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r4"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r5"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "r6"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t1"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t3"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"},
        {"id": ["Geometry", "t5"],
         "readonly": False,
         "type": "RealScalar",
         "units": "m"}
      ]
    },
    {"id": ["Physical parameters"],
      "children": [
         {"id": ["Physical parameters", "Mb"],
         "readonly": False,
         "type": "RealScalar",
         "units": "kg"},
         {"id": ["Physical parameters", "N"],
         "readonly": False,
         "type": "RealScalar",
         "units": "rpm"}
      ]
    },
    {"id": ["Outputs"],
      "children": [
         {"id": ["Outputs", "Seqv_max"],
         "readonly": True,
         "type": "RealScalar",
         "units": "MPa"},
         {"id": ["Outputs", "mass"],
         "readonly": True,
         "type": "RealScalar",
         "units": "kg"},
         {"id": ["Outputs", "u2"],
         "readonly": True,
         "type": "RealScalar",
         "units": "mm"}
      ]
    }
  ]

  metadata = {}

  return tree, metadata

def step(api, file_path, inputs, outputs, metadata, **kwargs):
  # Print all received inputs to the pSeven console.
  import logging
  log = logging.getLogger("UserBlock")  # sets the log source name
  for name in inputs.keys():
    msg = "Input: {}, Value: {}".format(name, api.input_read(name))
    log.info(msg)

  # Obtain all received inputs in dict format
  input_data = {}
  for input_name, input_details in inputs.iteritems():
  	input_data.update({input_details["tree_node"][1]:
    api.input_read(input_name)})

  # Get names of the output ports
  output_data = {}
  for output_name, output_details in outputs.iteritems():
  	output_data.update({output_name: output_details["tree_node"][1]})

  # Generate input data for the solver and write it to input file
  program_input = """
GEOMETRY:
#r	h
{r1}	{t1}
{r2}	{t1}
{r3}	{t3}
{r4}	{t3}
{r5}	{t5}
{r6}	{t5}

PHYSICAL PARAMETERS:
rho = 4430
E = 113.8e9
mu = 0.342

Mb = {Mb}
n = {N}

BOUNDARY CONDITIONS:
#u1 = 0.015
p1 = 0.0


NUMERICAL METHOD:
method = 'ode'
mesh_divs = 100
min_section_div = 3

OUTPUT:
outputs = {{'Seqv_max','mass','u2'}}
""".format(r1=input_data['r1'],r2=input_data['r2'],r3=input_data['r3'],
	r4=input_data['r4'],r5=input_data['r5'],r6=input_data['r6'],
	t1=input_data['t1'],t3=input_data['t3'],t5=input_data['t5'],
	Mb=input_data['Mb'],N=input_data['N'])

  log.info('Program input:\n{}'.format(program_input))

  program_input_file = 'input.txt'
  with open(program_input_file, 'w') as f:
  	f.write(program_input)

  # Get the path specified by the Executable file path option.
  exec_file_path = kwargs.get("exec_file_path", "")

  # Start the calculation
  results = subprocess.check_output([exec_file_path, '-i', program_input_file])
  log.info('Program output:\n{}'.format(results))

  # Obtain output results
  results = results.splitlines()
  parsed_results = {}
  for line in results:
  	if line.strip() != '':
  		parsed_results[line.split()[0]] = float(line.split()[-1])

  # Write results to the ports
  for name in outputs.keys():
    api.output_write(name, parsed_results[output_data[name]])

Block update and sharing

When updating pSeven, it is necessary to upgrade user blocks from the previous installation to make them available in the updated version. To upgrade a user block from a previous version of pSeven:

  • Back up the block directory and .p7block file from the previous installation of pSeven. In our case, the block directory is RotatingDisk, and name of the .p7block file is RotatingDisk.6.18.400.p7block. Default location of these files is "C:\Program Files\DATADVANCE\pSeven-6.19\blocks\".
  • Back up the projects that include workflows with user blocks. 
  • Install a new version of pSeven, but do not run it yet.
  • Copy the block directory and .p7block file to pSeven dir\blocks\in the new installation
    ("C:\Program Files\DATADVANCE\pSeven-6.20\blocks\" as for version 6.20 of pSeven).
  • Run create_block.py --upgrade-blocks in the new installation using the pSeven’s in-built Python. Therefore, the full command will be as follows:
    cd /d "C:\Program Files\DATADVANCE\pSeven-6.20"
    python.exe scripts\create_block.py --upgrade-blocks.
    

After the script finishes, you can continue working with projects from the previous version in the updated pSeven.

Do not open existing projects with user blocks in the updated version of pSeven before adding and upgrading user blocks in the new pSeven installation. Otherwise, user blocks may be automatically removed from a workflow when you open it in the new pSeven installation.

Note that it is also possible to re-implement a block in the new version of pSeven — you can add a new block and copy code from the old block. A strong disadvantage of this method is that the block will get a new identifier, so pSeven will not be able to recognize the new block as an upgraded version of some previously available block. Consequently, the new version of pSeven will remove your block from workflows on open, since block’s version in a workflow will have an identifier that is not found in the block library of the new pSeven.

To share your blocks with other users who have the same version of pSeven as yours, you just need to copy the block directory (in our case RotatingDisk) and .p7block file (RotatingDisk.6.18.400.p7block) to the pSeven dir\blocks\ of another computer.

Conclusions

User block is a convenient integration tool that allows users to create interface between a pSeven workflow and any software they use.

This tech tip demonstrates an example of implementation of such block. Compared to the generic integration approach, which used to be the only method available to integrate some specific, rare or in-house software, you can now reuse and share the custom integration block with your team. As we see, block requires a minimum amount of code in Python, only two functions that define all the functionality of the block. In that way, knowing the exchange formats or API of the simulation tool you want to integrate it is possible to create your own User block and include it into pSeven block library for everyday convenient use.

We encourage you to share your User blocks with our development team if you feel that your custom integration block fits right in the upcoming pSeven releases.

Interested in the solution?

Click to request a free 30-day demo.

Request demo