3. Results#

3.1 Results Structure#

"""
In this code it's shown how the results are organized in Load Cases, Increments, Results, Components and Sections.

N2PModelContent

└───N2PLoadCase

    ├───N2PIncrement
    └───N2PResult     

        └───N2PComponent

            └───N2PSection
"""
import NaxToPy as n2p

model = n2p.load_model(r"C:\Data\models\subcase\subcase_17500.op2")

# Inside a N2PModelContent (model), there is a list of N2PLoadCases
lcs = model.LoadCases

# A specific load case can be retrieved using its id
lc_17500 = model.get_load_case(17500)

# A N2PLoadcase has a dictionary of N2PResults and a list of N2PIncrements
results = lc_17500.Results
incrs = lc_17500.Increments

# Each load case has an active increment. By default is the last one
active_incr = lc_17500.ActiveN2PIncrement
incr_1 = lc_17500.get_increments(1)

# The active increment of the load case can be changed. Equivalent method
lc_17500.set_increment(1)
lc_17500.ActiveN2PIncrement = incr_1

# To select a N2PResult use the next method:
stresses = lc_17500.get_result("STRESSES")

# Each result has a dictionary of components
comps = stresses.Components

# To select a component:
xx = stresses.get_component("XX")

# Each component has a list of sections
sections = xx.Sections

# All in one line:
z1 = model.get_load_case(17500).set_increment(1).get_result("STRESSES").get_component("XX").Sections[0]

3.2 Asking for Results#

"""
Obtain results in NaxToPy there are two options. 
    1. From N2PComponent using get_result_ndarray()
    2. From N2PModelContent using get_result_by_LCs_Incr()
"""
import NaxToPy as n2p

# Load a N2PModelContent
model = n2p.load_model(r"C:\Data\models\subcase\subcase_17500.op2")
model.import_results_from_files([
    r"C:\Data\models\subcase\subcase_17501.op2",
    r"C:\Data\models\subcase\subcase_17502.op2",
    r"C:\Data\models\subcase\subcase_17503.op2"
])

# Get a N2PComponent
x = model.get_load_case(17500).get_result("DISPLACEMENTS").get_component("X")

# Get the array of displacements in x. IT IS A TUPLE!
x_results = x.get_result_ndarray()

x_array    = x_results[0]  # In position 0 of the tuple is the actual array
x_position = x_results[1]  # In the position 1 is the location of the results

# get_result_ndarray() can have multiple optional arguments for asking the results in different ways:
xx = model.get_load_case(17500).get_result("STRESSES").get_component("XX")
xx.get_result_ndarray(sections=["Z1, Z2"], aveSections=-3, cornerData=False, coordsys=-1)

# The arguments may be:
#     - The sections where the results are needed
#     - The type of average if several sections are asked
#     - If results are in corner data (element-nodal)
#     - If corner data is asked, aveNodes and variation set the relationship of the results in the nodes
#     - The coordinate system can be set selecting using the id of one of the existing coordinates, the material
#       coordinate system (-1), the global coordinate system (0), an user defined in that moment (-10 and setting v1, v2)
#       or a user defined system set with the method N2PModelContent.
#     - v1 and v2 set a user defined coordinate system. v1 is x axis and v2 set the xt plane
model.get_load_case(17500).get_result("STRESSES").get_component("XY").get_result_ndarray(coordsys=-10, v1=(0,0,-1), v2=(0,0.5,0.5))

# Using a different user defined coordinate system for each element:
model.set_user_coord_sys(
    [ [39900000,0,1,0,0,0,1,0], 
      [39900001,0,0.3,0.2,0,0,1,0], 
      [39900002,0,0,1,0,1,1,0], 
      [39900003,0,2,0,1,0.4,1,3] ], 
    "ELEMENTS"
)
model.get_load_case(17500).get_result("STRESSES").get_component("YY").get_result_ndarray(coordsys=-20)

# The second way to call the result arrays is using get_result_by_LCs_Incr(). This N2PModelContent method
# is really useful when there are lots of load cases, as it can load them in parallel.

# Select the loadcase-increment needed:
lcs_incr = [(17500, 1),(17501, 1),(17502, 1),(17503, 1)]

# Call the method. It requires a result (only one result) and a component or a list of components
result = model.get_result_by_LCs_Incr(lcs_incr, "STRESSES", "XX")
results = model.get_result_by_LCs_Incr(lcs_incr, "STRESSES", ["XX", "XY", "YY"])
# They return a dictionary with the results asked

# Optional arguments are the same as get_result_ndarray()
results_mat = model.get_result_by_LCs_Incr(lcs_incr, "STRESSES", ["XX", "XY", "YY"], coordsys=-1)

3.3 Load Case Combination#

"""
The script shows how to generate derived load cases. These load cases can be:
    - Combination of load cases
    - Envelope of load cases
"""
import NaxToPy as n2p

model = n2p.load_model(r"C:\Data\models\subcase\subcase_17500.op2")
model.import_results_from_files([
    r"C:\Data\models\subcase\subcase_17501.op2",
    r"C:\Data\models\subcase\subcase_17502.op2",
    r"C:\Data\models\subcase\subcase_17503.op2"
])

print(model.LoadCases)

# To create a new derived load case by combination of them. It requires two arguments:
# - name: A str for the name of the load case
# - formula: a str with the combination of the load cases. Each load case is introduced using
#   <> and inside <> LCXX:FRYY where XX is the id of the loadcase and YY the id of the increment/frame
#   operations are applied to this "variables".
dlc_1 = model.new_derived_loadcase("Derived-1", "<LC17500:FR1>+2*<LC17501:FR1>+0.5*<LC17502:FR1>")

# There are python tools to write it faster and eficiently. Imagine LC 17500 is the thermal and the 
# other are the mechanical. As i want ultimate combination y multiply by 1.5 only mechanical:
ultimate_lcs = list()
for lc in model.LoadCases[1:]:
    ultimate_lcs.append(model.new_derived_loadcase(f"{lc.ID}_UL", f"1.5*<LC{lc.ID}:FR:1>+<LC17500:FR:1>"))
# The id of the derived loadcase will be negative and are created internally. Derived load cases only have
# one increment, with id 0.

# Now it can be used as an original load case:
x = dlc_1.get_result("DISPLACEMENTS").get_component("X").get_result_ndarray()
derived_derived_lc = model.new_derived_loadcase("Derived-1", "<LC-1:FR0>+<LC17501:FR1>") # Id -1 and increment 0
yy = model.get_result_by_LCs_Incr((-1, 0), "STRESSES", "YY")

print(model.LoadCases)

# To create an envelope load case it requires two arguments (name, formula) plus two optional (criteria, envelgroup).
# formula is similar to a combination loadcase but the lc_incr are separated by commas. The criteria may be the ExtremeMax
# (selects max absolute, by default), max, min, range nad ExtremeMin. envelgroup set if the results must be the critical value,
# the critical load case or the critical increment.
env = model.new_envelope_loadcase("env_1", "<LC17500:FR1>,<LC17501:FR1>", criteria="ExtremeMax", envelgroup="ByContour")

# using python tools is easy to build long formulas. Formula with all the loadcases (including combination ones)
formula = ",".join([f"<LC{lc.ID}:FR{lc.N2ActiveIncrement.ID}>" for lc in model.LoadCases])
env2_val = model.new_envelope_loadcase("env_2", formula, envelgroup="ByContour")
env3_val = model.new_envelope_loadcase("env_3", formula, envelgroup="ByLoadCaseID")

results = env3_val.get_result("STRESSES").get_component("VON_MISES_DERIVED").get_result_ndarray()
final_resuts = model.get_load_case(results[0][10])

# Now they can be use as any other load case
env2_val.get_result("STRESSES").get_component("XX").get_result_ndarray()[0]
env3_val.get_result("STRESSES").get_component("XX").get_result_ndarray()[0]

3.4 Derived Components#

"""Derived components can be cretaed using existing ones"""
import NaxToPy as n2p

model = n2p.load_model(r"C:\Data\models\subcase\subcase_17500.op2")
model.import_results_from_files([
    r"C:\Data\models\subcase\subcase_17501.op2",
    r"C:\Data\models\subcase\subcase_17502.op2",
    r"C:\Data\models\subcase\subcase_17503.op2"
])

lc_17500 = model.LoadCases[0]
stresses = lc_17500.get_result("STRESSES")

# The derived components are created from a N2PResult. It requires a name and the formula.
# In the formula, the base components are selected using <> and placing inside "CPMT_" plus
# the name of the result, plus ":" plus the name of the component.
stresses.new_derived_component("vonmisses", "sqrt(<CMPT_STRESSES:XX>^2+<CMPT_STRESSES:YY>^2-<CMPT_STRESSES:XX>*<CMPT_STRESSES:YY>+3*<CMPT_STRESSES:XY>^2)")

# Now this component can be used as the originals are used
vonmisses = stresses.get_component("vonmisses").get_result_ndarray()

# Derived components are really useful in the case of envelope load cases. This is cause if the original component
# VON_MISSES is asked in a envelope load case it will return the maximum of the von mises, what it is wrong. The 
# value is conservative, but not true, because the actual value requires to calculate the maximum of the  base components
# of the stress tensor (XX, XY, YY in the plane) and then with those values calculate the von mises.
formula = ",".join([f"<LC{lc.ID}:FR{lc.N2ActiveIncrement.ID}>" for lc in model.LoadCases])
env_val = model.new_envelope_loadcase("env", formula, envelgroup="ByContour")
stresses_env = env_val.get_result("STRESSES")
vonmisses_env = stresses_env.new_derived_component("vonmisses_env", "sqrt(<CMPT_STRESSES:XX>^2+<CMPT_STRESSES:YY>^2-<CMPT_STRESSES:XX>*<CMPT_STRESSES:YY>+3*<CMPT_STRESSES:XY>^2)")

vonmisses_array = vonmisses_env.get_result_ndarray()

idaero-logo
© 2025 Idaero Solutions S.L.

All rights reserved. This document is licensed under the terms of the LICENSE of the NaxToPy package.