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}:FR1>+<LC17500:FR1>"))
# 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()
3.5 Stracting results and printing in Altair ASCII Format#
import NaxToPy as n2p
import numpy as np
def print_results(stresses, strains, element_ids):
"""Prints stresses and strains to an Altair ASCII format file."""
# Tensor – XX,YY,ZZ,XY,YZ,ZX. More info in https://help.altair.com/hwdesktop/hwd/topics/reference/hwdref/rules_guidelines_r.htm
lc_set = set([key[0] for key in stresses.keys()])
mask = ~np.isnan(next(iter(stresses.values())))
element_ids_clean = (np.array(element_ids))[mask]
with open("combined_results.hwascii", "w") as f:
f.write("ALTAIR ASCII FILE\n")
for i, result in enumerate([stresses, strains]):
for key in result.keys():
result[key] = result[key][mask]
for lc in lc_set:
f.writelines(get_header(i, lc))
for eid, xx, yy, xy in zip(element_ids_clean, result[(lc, 0, "XX")], result[(lc, 0, "YY")], result[(lc, 0, "XY")]):
f.write(f"{eid} {xx} {yy} 0.0 {xy} 0.0 0.0\n")
f.write("\n\n")
def get_header(i, lc):
if i == 0:
return [
f"$SUBCASE = {abs(lc)} \"subcase {lc}\"\n" + \
f"$BINDING = ELEMENT\n" + \
f"$COLUMN_INFO = ENTITY_ID\n" + \
f"$RESULT_TYPE = STRESSES(t)\n"
]
else:
return [
f"$SUBCASE = {abs(lc)} \"subcase {lc}\"\n" + \
f"$BINDING = ELEMENT\n" + \
f"$COLUMN_INFO = ENTITY_ID\n" + \
f"$RESULT_TYPE = STRAINS(t)\n"
]
def main():
"""Main function combine load cases, extract stresses and strains, and print to Altair ASCII format file."""
# Loading mesh and results
model = n2p.load_model(r"C:\zz_pruebas\remaches\several\fasteners_several_1_shell.bdf")
# Loading more results
model.import_results_from_files([
r"C:\zz_pruebas\remaches\several\fasteners_several_1_shell.op2",
r"C:\zz_pruebas\remaches\several\fasteners_several_2_shell.op2"
])
lc_thermal = model.LoadCases[0]
lcs_mechanical = model.LoadCases[1:]
lc_incr = []
for lc in lcs_mechanical:
# Combination of the thermal load case with each of the other load cases
new_lc = model.new_derived_loadcase(lc.Name + "+Thermal", f"<LC{lc.ID}:FR1>+<LC{lc_thermal.ID}:FR1>")
# Keep the combination load case and its increment in a list
lc_incr.append((new_lc, new_lc.ActiveN2PIncrement))
stresses = model.get_result_by_LCs_Incr(lc_incr, "STRESSES", ["XX", "YY", "XY"], ["Z1", "Z2"])
strains = model.get_result_by_LCs_Incr(lc_incr, "STRAINS", ["XX", "YY", "XY"])
elements = [ele.ID for ele in model.get_elements()+model.get_connectors()]
print_results(stresses, strains, elements)
if __name__ == "__main__":
main()
© 2025 Idaero Solutions S.L.
All rights reserved. This document is licensed under the terms of the LICENSE of the NaxToPy package.