DPF solution for force and moment reactions

Mike.Thompson
Mike.Thompson Member, Employee Posts: 345
25 Answers 100 Comments 25 Likes First Anniversary
✭✭✭✭

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.

Tagged:

Answers

  • Pernelle Marone-Hitz
    Pernelle Marone-Hitz Member, Moderator, Employee Posts: 871
    100 Answers 500 Comments 250 Likes First Anniversary
    ✭✭✭✭

    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:

  • Mike.Thompson
    Mike.Thompson Member, Employee Posts: 345
    25 Answers 100 Comments 25 Likes First Anniversary
    ✭✭✭✭

    @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.

  • Pernelle Marone-Hitz
    Pernelle Marone-Hitz Member, Moderator, Employee Posts: 871
    100 Answers 500 Comments 250 Likes First Anniversary
    ✭✭✭✭

    @Ayush Kumar @Paul Profizi @Ramdane Please have a look at this DPF force postprocessing question / enhancement request.

  • Ayush Kumar
    Ayush Kumar Member, Moderator, Employee Posts: 456
    100 Answers 250 Likes 100 Comments Second Anniversary
    ✭✭✭✭
    edited August 2023

    @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]

  • Erik Kostson
    Erik Kostson Member, Employee Posts: 233
    50 Answers 100 Comments Second Anniversary 25 Likes
    ✭✭✭✭

    @Ayush Kumar .
    The above code makes WB/Mech. crash (2023 R2). Any help is much appreciated.
    Thanks
    Erik

  • Ayush Kumar
    Ayush Kumar Member, Moderator, Employee Posts: 456
    100 Answers 250 Likes 100 Comments Second Anniversary
    ✭✭✭✭

    @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.

  • Mike.Thompson
    Mike.Thompson Member, Employee Posts: 345
    25 Answers 100 Comments 25 Likes First Anniversary
    ✭✭✭✭

    @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

  • Erik Kostson
    Erik Kostson Member, Employee Posts: 233
    50 Answers 100 Comments Second Anniversary 25 Likes
    ✭✭✭✭
    edited November 2023

    @Mike.Thompson , if you could do what you described to the script above (so it works) would be surely appreciated by users.

  • Mike.Thompson
    Mike.Thompson Member, Employee Posts: 345
    25 Answers 100 Comments 25 Likes First Anniversary
    ✭✭✭✭
    edited November 2023

    @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.

  • Jimmy He
    Jimmy He Member, Employee Posts: 20
    10 Comments First Answer First Anniversary Name Dropper
    ✭✭✭✭

    @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) )