Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to animate shape variables to a gif in caps2tacs #267

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions examples/caps_wing/7_animate_shape_vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Sean Engelstad, November 2023
GT SMDO Lab, Dr. Graeme Kennedy
Caps to TACS example

The purpose of this file is to show how to animate shape variables in the structure.
Once you run this script it will generate .f5 files in the capsStruct/Scratch/tacs work directory.
You'll need to use f5tovtk to convert to *.vtk files and open up each group of shape_var_..vtk files
using the .. shortcut which opens up a group of files at once. For each of these groups of vtk files,
click file -> save animation and save all the png files in a subfolder. Then, either use the GifWriter in the
caps2tacs module or copy the GifWriter script into that directory and use it to make a gif. Repeat for each shape variable.
"""

from tacs import caps2tacs
from mpi4py import MPI
import openmdao.api as om

# --------------------------------------------------------------#
# Setup CAPS Problem
# --------------------------------------------------------------#
comm = MPI.COMM_WORLD
tacs_model = caps2tacs.TacsModel.build(csm_file="large_naca_wing.csm", comm=comm)
tacs_model.mesh_aim.set_mesh( # need a refined-enough mesh for the derivative test to pass
edge_pt_min=15,
edge_pt_max=20,
global_mesh_size=0.01,
max_surf_offset=0.01,
max_dihedral_angle=5,
).register_to(
tacs_model
)

aluminum = caps2tacs.Isotropic.aluminum().register_to(tacs_model)

# setup the thickness design variables + automatic shell properties
nribs = int(tacs_model.get_config_parameter("nribs"))
nspars = int(tacs_model.get_config_parameter("nspars"))
nOML = nribs - 1
for irib in range(1, nribs + 1):
caps2tacs.ThicknessVariable(
caps_group=f"rib{irib}", value=0.03, material=aluminum
).register_to(tacs_model)
for ispar in range(1, nspars + 1):
caps2tacs.ThicknessVariable(
caps_group=f"spar{ispar}", value=0.03, material=aluminum
).register_to(tacs_model)
for iOML in range(1, nOML + 1):
caps2tacs.ThicknessVariable(
caps_group=f"OML{iOML}", value=0.1, material=aluminum
).register_to(tacs_model)

# register one shape variable rib_a1
rib_a1 = caps2tacs.ShapeVariable("rib_a1", value=1.0).register_to(tacs_model)
caps2tacs.ShapeVariable("rib_a2", value=0.0).register_to(tacs_model)
spar_a1 = caps2tacs.ShapeVariable("spar_a1", value=1.0).register_to(tacs_model)
caps2tacs.ShapeVariable("spar_a2", value=0.0).register_to(tacs_model)

# add constraints and loads
caps2tacs.PinConstraint("root").register_to(tacs_model)
caps2tacs.GridForce("OML", direction=[0, 0, 1.0], magnitude=10.0).register_to(
tacs_model
)

# add analysis functions to the model
caps2tacs.AnalysisFunction.mass().register_to(tacs_model)

# run the pre analysis to build tacs input files
tacs_model.setup(include_aim=True)

shape_var_dict = {
rib_a1: [_ * 0.1 for _ in range(6, 14)],
spar_a1: [_ * 0.1 for _ in range(6, 14)],
}
tacs_model.animate_shape_vars(shape_var_dict)
1 change: 1 addition & 0 deletions tacs/caps2tacs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
if openmdao_loader is not None:
from .tacs_component import *

from .gif_writer import *
from .analysis_function import *
from .constraints import *
from .egads_aim import *
Expand Down
31 changes: 31 additions & 0 deletions tacs/caps2tacs/gif_writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
__all__ = ["GifWriter"]
import imageio, os


class GifWriter:
"""
module to write gifs from a set of pngs
"""

def __init__(self, frames_per_second: int = 4):
self._fps = frames_per_second

def __call__(self, gif_filename: str, path: str):
"""
call on current path to create gif of given filename
"""
gif_filepath = os.path.join(path, gif_filename)
with imageio.get_writer(gif_filepath, mode="I", fps=self._fps) as writer:
path_dir = os.listdir(path)
path_dir = sorted(path_dir)
for image_file in path_dir:
print(image_file)
if ".png" in image_file:
image = imageio.imread(image_file)
writer.append_data(image)


# example of how to use the GifWriter
# if __name__ == "__main__":
# my_writer = GifWriter(frames_per_second=4)
# my_writer("sizing1.gif", os.getcwd())
10 changes: 5 additions & 5 deletions tacs/caps2tacs/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def aluminum(cls):
cp=903,
kappa=237,
)

@classmethod
def titanium(cls):
return cls(
Expand Down Expand Up @@ -282,8 +282,8 @@ def carbon_fiber(cls):
alpha1=-0.3e-6,
alpha2=28e-6,
rho=1.6e3,
kappa1=14.5, #W/m-K
kappa2=4.8, #W/m-K
kappa3=4.8, #W/m-K
cp=1130.0 # J / kg-K
kappa1=14.5, # W/m-K
kappa2=4.8, # W/m-K
kappa3=4.8, # W/m-K
cp=1130.0, # J / kg-K
)
53 changes: 53 additions & 0 deletions tacs/caps2tacs/tacs_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,59 @@ def createTACSProbs(self, addFunctions: bool = True):
)
return self.SPs

def animate_shape_vars(self, shape_vars_dict: dict):
"""
Animate the shape variables in ESP/CAPS.

Parameters
----------
shape_vars_dict: dict[ShapeVariable : list[float]]
dict of the list of values for each shape variable to take

e.g. if you wish to animate over the ShapeVariable objects var1, and var2
create the following shape_vars_dict
shape_vars_dict = {
var1 : [_*0.1 for _ in range(1,4)],
var2 : [_*0.05 for _ in range(-3,4)],
}
"""
# make an analysis function if none have been made
if len(self.analysis_functions) == 0:
if self.comm.rank == 0:
print(
"Adding mass analysis function to enable caps2tacs postAnalysis for animating shape variables..."
)
AnalysisFunction.mass().register_to(self)

# initialize all function and derivatives as zero

for shape_var in shape_vars_dict:
value_list = shape_vars_dict[shape_var]
for i, value in enumerate(value_list):
shape_var.value = value
self.geometry.despmtr[shape_var.name].value = value

self.tacs_aim.pre_analysis()
self.SPs = self.createTACSProbs(addFunctions=True)
for caseID in self.SPs:
# note we don't solve the forward / adjoint analysis here
# as we only care about visualizing the change in the structural shape / mesh
# not the actual structural analysis results

self.SPs[caseID].writeSolution(
baseName=shape_var.name,
outputDir=self.tacs_aim.analysis_dir,
number=i,
)
self.SPs[caseID].writeDummySensFile(
tacsAim=self.tacs_aim,
)
self.tacs_aim.post_analysis()
print(
f"Done animating caps2tacs shape variables.. the f5 files are found in {self.tacs_aim.analysis_dir}."
)
print("Use the GifWriter script in examples/caps_wing/archive to ")

def pre_analysis(self):
"""
call tacs aim pre_analysis to build TACS input files and mesh
Expand Down
43 changes: 43 additions & 0 deletions tacs/problems/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,3 +935,46 @@ class which handles the sensitivity file writing for ESP/CAPS shape derivatives
hdl.write("1\n")
hdl.write(f"{struct_sens[idx].real}\n")
return

def writeDummySensFile(self, tacsAim):
"""
write a blank / dummy SensFile for caps2tacs to enable animating shape variables
using GIF animations (without having to run an analysis just building each mesh)

Parameters
----------
tacsAim : tacs.caps2tacs.TacsAIM
class which handles the sensitivity file writing for ESP/CAPS shape derivatives

"""

num_funcs = 1
assert tacsAim is not None
num_struct_dvs = len(tacsAim.thickness_variables)
num_nodes = self.meshLoader.bdfInfo.nnodes

if self.comm.rank == 0:
# open the sens file nastran_CAPS.sens and write coordinate derivatives
# and any other struct derivatives to it
with open(tacsAim.sens_file_path, "w") as hdl:
for func_name in ["mass"]:
sean-engelstad marked this conversation as resolved.
Show resolved Hide resolved
hdl.write(f"{num_funcs} {num_struct_dvs}\n")

# write the func name, value and nnodes
hdl.write(f"{func_name}\n")
hdl.write(f"{0.0}\n")
hdl.write(f"{num_nodes}\n")

# write the coordinate derivatives for the given function
for bdf_ind in range(num_nodes):
nastran_node = bdf_ind + 1
hdl.write(f"{nastran_node} 0.0 0.0 0.0\n")

# write any struct derivatives if there are struct derivatives
if num_struct_dvs > 0:
for idx, thick_var in enumerate(tacsAim.thickness_variables):
# assumes these are sorted in tacs aim wrapper
hdl.write(f"{thick_var.name}\n")
hdl.write("1\n")
hdl.write("0.0\n")
return
Loading