DPF solution for force and moment reactions
Hi all. I am looking for a DPF solution in mechanical that is equal to a force and moment reaction probe. I would like to be able to scope to either geometry, a contact, or to a construction surface that cuts through geometry.
I am more interested in auto export of the data than contours in Mechanical, so creation of a .csv file is fine through a python code object. No need to use a python result for plotting. I would use this code to expand to multiple scopings, for example 100 different contacts.
thanks for any help.
Answers
-
Hi @Mike.Thompson Why not use the Mechanical automation API and actually insert a Force and Moment reaction probe?
This also seems to be doable via DPF, @Vishnu shared an example:
0 -
@Pernelle Marone-Hitz, thanks for the response.
The reason for not using the mechanical objects is it can get quite cumbersome when you have lots of locations to evaluate. I have actually done this automation in the Bolt Tools Add On specifically, but I was wondering precisely if there is a better way to do this more effectively in DPF, especially when the user only wants to export the data to an external file for all locations and time points. The other issue, is I do not believe we have a robust API for getting the tabular time history data from the probe objects, other than activating it and getting it directly from the UI control. I don't like this solution.
To the solution you referenced, I think we are getting into a nuance of terminology in FE. I believe the DPF operator is getting specifically "reaction forces" from the results file, which are ONLY existing on nodes that have a DOF constraint. What I am looking for is really like FSUM of element-nodal forces. This is what is done in Mechanical when you have a construction surface splitting through elements, or when you have a contact scoping and use the underlaying elements for the force extraction.
Based on the above, I think the operator would actually be based on the element-nodal forces, but if anybody has a good working example of this it would be great. I think the "work" is really to get the DPF scopings done correctly so you get the right "FSUM" calculation from the element-nodal forces operator.0 -
@Ayush Kumar @Paul Profizi @Ramdane Please have a look at this DPF force postprocessing question / enhancement request.
0 -
@Mike.Thompson If I understood your question right, the following code should provide you an FSUM for all the nodes in the named selection "NODES". Let me know if this is what you were looking for.
import mech_dpf
import Ans.DataProcessing as dpf ds = dpf.DataSources(Model.Analyses[0].ResultFileName)
ms = dpf.operators.scoping.on_named_selection(data_sources=ds, named_selection_name="NODES")
ms_scop = ms.outputs.mesh_scoping.GetData() enfo = dpf.operators.result.element_nodal_forces(data_sources=ds, mesh_scoping=ms_scop) """
Perform a component-wise sum of all nodes """ enfo_sum = dpf.operators.math.accumulate_over_label_fc(enfo) """ Resultant of vector """ fsum = dpf.operators.math.norm(enfo_sum)
fsum_val = fsum.outputs.field.GetData().Data[0]0 -
@Ayush Kumar .
The above code makes WB/Mech. crash (2023 R2). Any help is much appreciated.
Thanks
Erik0 -
@Erik Kostson Thanks for reporting this, seems like
accumulate_over_label_fc
causes it to crash. But it works well with 24R1, so I guess it is fixed now.1 -
@Erik Kostson , yes I believe this acuumulate over label operator had a bug that has been resolved in the next release. For use in releases with the bug, users would likely need to use the operator "component_selector_fc" to select the individual XYZ components, and then do the accumulate on those individually. You could then merge them back to a vector fields container with: merge_fields_containers
1 -
@Mike.Thompson , if you could do what you described to the script above (so it works) would be surely appreciated by users.
0 -
@Erik Kostson ,
This is my attempt at the code. I didn't merge the fields containers at the end because it really is just single values (summation of ENFO) at that point. I just keep it as a list of fields containers that hold these summed values across time. "SummedENFO_FCs" is a list of 3 fields containers for XYZ respectively.1 -
@Mike.Thompson Following your comment "I think the "work" is really to get the DPF scopings done correctly", I recently got a request to develop a DPF solution to find the element/node scopings that pass through multiple parallel construction planes (defined by normal and origin) for a similar workflow, so we don't need to rely on worksheet to generate the named selection. Please see the code below:
import mech_dpf import Ans.DataProcessing as dpf mech_dpf.setExtAPI(ExtAPI) import os import time # User inputs filepath = DataModel.AnalysisList[0].ResultFileName normal_vector = [1.0, 1.0, 1.0] # All below need to be the same unit as the solver unit system origin_location = [0.5, 0.5, 0.5] # Use this to get parallel sections defined by an offset additional_offsets = [ 0. , 0.25 , 0.5 ] ##################################################################################################### #Data sources dataSources = dpf.DataSources() dataSources.SetResultFilePath(filepath) # Get Mesh st = time.time() op = dpf.operators.mesh.mesh_provider() # operator instanciation op.inputs.data_sources.Connect(dataSources) my_mesh = op.outputs.mesh.GetData() mesh_info = op.outputs.getmesh() print( 'Get full mesh took ' , time.time() -st , 's' ) ################################################################### # Extract a sub-section of the mesh by element IDs st = time.time() ns = DataModel.GetObjectsByName('X+')[0] my_mesh_scoping = dpf.Scoping() my_mesh_scoping.Ids = ns.Ids my_mesh_scoping.Location = 'Elemental' op = dpf.operators.mesh.mesh_extraction() # operator instantiation op.inputs.mesh.Connect(mesh_info ) op.inputs.mesh_scoping.Connect(my_mesh_scoping) sub_mesh = op.outputs.getmeshed_region() print( 'Get sub mesh took ' , time.time() -st , 's' ) ################################################################### normal=dpf.FieldsFactory.CreateVectorField(1,3,"overall") normal.Add( 1 , normal_vector ) origin=dpf.FieldsFactory.CreateVectorField(1,3,"overall") origin.Add( 1 , origin_location ) origin.Unit = mesh_info.Unit # Same as the mesh unit # Compute level set wrt normal and initial origin st = time.time() op = dpf.operators.mesh.make_plane_levelset() # operator instantiation op.inputs.coordinates.Connect(sub_mesh) op.inputs.normal.Connect(normal) op.inputs.origin.Connect(origin) lvlset = op.outputs.field.GetData() print( 'Get levelset took ' , time.time() -st , 's' ) # Parallel offsets for offset in additional_offsets: # Do not recalculate the level set for parallel offsets, just add the offset st = time.time() op = dpf.operators.math.add_constant() # operator instantiation op.inputs.field.Connect(lvlset) op.inputs.ponderation.Connect( -offset ) lvlset_curr = op.outputs.field.GetData() # Filter all nodes on the positive side op = dpf.operators.filter.scoping_high_pass() # operator instantiation op.inputs.field.Connect(lvlset_curr) op.inputs.threshold.Connect(0.) positive_nodes = set( op.outputs.getscoping().Ids ) # Get the abs value op = dpf.operators.math.absolute_value_by_component() # operator instantiation op.inputs.field.Connect(lvlset_curr) abs_ = op.outputs.field.GetData() # Get the sign of the field, approximated by x / |x| op = dpf.operators.math.component_wise_divide() # operator instantiation op.inputs.fieldA.Connect(lvlset_curr) op.inputs.fieldB.Connect(abs_) sign = op.outputs.field.GetData() # Average nodal sign (+1 / -1) to elemental sign op = dpf.operators.averaging.nodal_to_elemental() # operator instantiation op.inputs.field.Connect(sign) sign_ele = op.outputs.field.GetData() # If all nodes of the element is above/below the plane, its elemental sign is +1 / -1 # Anything in between is therefore passing the plane op = dpf.operators.filter.scoping_band_pass() # operator instantiation op.inputs.field.Connect(sign_ele) op.inputs.min_threshold.Connect(-1.) op.inputs.max_threshold.Connect(1.)# optional passed_eles = list( op.outputs.getscoping().Ids ) print( 'Get elements took ' , time.time() -st , 's' ) print( 'Found ' + str(len(passed_eles)) + ' elements' ) #print( passed_eles ) # From the nodes of the elements passing the plane, select the ones on the positive side st = time.time() all_involved_nodes = set([]) for eid in passed_eles: en = sub_mesh.ElementById( eid ).NodeIds for n in en: if n in positive_nodes: all_involved_nodes.add( n ) print( 'Get nodes took ' , time.time() -st , 's' ) print( 'Found ' + str(len(all_involved_nodes)) + ' nodes' ) #print( (all_involved_nodes) )
0