diff --git a/docs/source/caps2tacs/main.rst b/docs/source/caps2tacs/main.rst index da1f49cc0..64cb74668 100644 --- a/docs/source/caps2tacs/main.rst +++ b/docs/source/caps2tacs/main.rst @@ -9,7 +9,7 @@ won't work until after the full install so you can ignore that if you source it export ESP_ROOT=~/packages/ESP123/EngSketchPad export PYTHONPATH=${PYTHONPATH}:$ESP_ROOT/pyESP - source $ESP_ROOT/ESPenv.shell + source $ESP_ROOT/ESPenv.sh alias csm='$ESP_ROOT/bin/serveCSM' The installation on a Linux machine proceeds as follows. Note if the ESP/OpenCASCADE version changes @@ -39,13 +39,13 @@ Important things to include in your CSM file are included below (for reference s * Configuration parameters such as ``cfgpmtr nribs 10`` which are integer variables usually fixed during optimization. * Design parameters such as ``despmtr span 10`` which are real number variables that can be tied to a TACS shape variable. * ``capsGroup`` attributes define regions of the geometry with the same property cars, denoted ``capsGroup $rib1``, etc. -We often use pattern statements to define these, and sometimes a user-defined primitive ``udprim editAttr``. -* ``capsConstraint`` attributes define regions of the geometry intended to have the same structural constraints, e.g. ``capsConstraint fix``. -These constraints include Temperature and elastic constraints as of right now. -* ``capsLoad`` attributes define regions of the geometry with the same fixed loads. This is useful in simple structural analyses. -Note that aerodynamic loads cannot be setup this way (see the funtofem github for how to do this). -* ``capsAIM`` attribute - this specifies for a body the analysis tools or AIMs using this CSM file. Note that we often add the -``tacsAIM, egadsTessAIM`` here for structural analyses. + We often use pattern statements to define these, and sometimes a user-defined primitive ``udprim editAttr``. +* ``capsConstraint`` attributes define regions of the geometry intended to have the same structural constraints, e.g. ``capsConstraint fix``. + These constraints include Temperature and elastic constraints as of right now. +* ``capsLoad`` attributes define regions of the geometry with the same fixed loads. + This is useful in simple structural analyses. Note that aerodynamic loads cannot be setup this way (see the funtofem github for how to do this). +* ``capsAIM`` attribute - this specifies for a body the analysis tools or AIMs using this CSM file. + Note that we often add the ``tacsAIM, egadsTessAIM`` here for structural analyses. * Occasionally ``capsMesh`` attribute - which can be used to set alternative auto-mesh settings on different sections of the geometry. The main ``TacsModel`` object supervises the process of TACS analysis and geometry updates for optimization. @@ -73,18 +73,17 @@ hyperparameters by running a small analysis / using egadsAIM view routine until There are certain objects that must be setup and registered to the tacs model before running a TACS analysis. If these are not correctly specified, then the tacs model will throw an error in the setup check phase. -* Material properties - you must setup one or more material objects for use in the element property definitions. -Available material types include Isotropic and Orthotropic (Anisotropic can be added directly through the underlying tacsAIM). -Several common materials are saved as class methods such as aluminum, steel, titanium, carbon fiber. +* Material properties - you must setup one or more material objects for use in the element property definitions. + Available material types include Isotropic and Orthotropic (Anisotropic can be added directly through the underlying tacsAIM). Several common materials are saved as class methods such as aluminum, steel, titanium, carbon fiber. * Element Properties - currently caps2tacs only supports shell elements for use in aerodynamic structures. -Element properties can be setup directly through a `ShellProperty` object or indirectly through the `ThicknessVariable` object. -The names of shell properties or the thickness variables must be tied to a `capsGroup` setup in the CSM file. + Element properties can be setup directly through a `ShellProperty` object or indirectly through the `ThicknessVariable` object. + The names of shell properties or the thickness variables must be tied to a `capsGroup` setup in the CSM file. * Constraints - elastic and thermal constraints are available in caps2tacs. Common instances are the ``PinConstraint`` and ``TemperatureConstraint``. -The name/capsConstraint input to these constraint objects must match the ``capsConstraint`` attributes in the CSM file. + The name/capsConstraint input to these constraint objects must match the ``capsConstraint`` attributes in the CSM file. * Loads - for static problems ``GridForce`` and ``Pressure`` load objects are available whose names must match the ``capsLoad`` attributes. -Note that for aerodynamic structures only coupling with a CFD software such as through the ``FUNtoFEM repo (see below`` can setup aerodynamic loads. + Note that for aerodynamic structures only coupling with a CFD software such as through the ``FUNtoFEM repo (see below`` can setup aerodynamic loads. * Output Functionals for optimization - functionals such as ``ksfailure``, ``mass``, ``temperature``, ``compliance`` -are available for use in structural optimizations. + are available for use in structural optimizations. Once all of the above structural analysis setup objects are provided, the TacsModel is ready for analysis. You can then run the setup method which completes the setup phase. Then, the routines ``pre_analysis()`` generates a mesh, @@ -106,7 +105,7 @@ on a coarse mesh of a symmetric NACA 0012 wing structure. 2. An unsteady analysis with a vertical distributed load varying sinusoidally in time. 3. A sizing optimization which finds the optimal panel thicknesses to hold fixed aero loads. 4. A sizing and shape optimization which optimizes the panel thicknesses and location of ribs -and spars inside the wing to hold fixed aero loads. + and spars inside the wing to hold fixed aero loads. 5. A steady analysis, using the AFLR AIM for meshing, with a vertical distributed load. The sizing optimization shown below resulted in about a 40\% drop in weight from the equal thickness design. Notice diff --git a/setup.py b/setup.py index 0f51abee6..838d79eb5 100644 --- a/setup.py +++ b/setup.py @@ -104,6 +104,7 @@ def get_mpi_flags(): "testing": ["testflo>=1.4.7"], "docs": ["sphinx", "breathe", "sphinxcontrib-programoutput"], "mphys": ["mphys>=1.1.0", "openmdao>=3.25.0"], + "caps2tacs": ["imageio>=2.16.1"], } # Add an optional dependency that concatenates all others diff --git a/tacs/caps2tacs/gif_writer.py b/tacs/caps2tacs/gif_writer.py index f61d7e96f..86757b6d5 100644 --- a/tacs/caps2tacs/gif_writer.py +++ b/tacs/caps2tacs/gif_writer.py @@ -7,15 +7,16 @@ 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 __init__(self, duration: int = 20): + # duration of each frame + self._duration = duration 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: + with imageio.get_writer(gif_filepath, mode="I", duration=self._duration) as writer: path_dir = os.listdir(path) path_dir = sorted(path_dir) for image_file in path_dir: diff --git a/tacs/caps2tacs/tacs_model.py b/tacs/caps2tacs/tacs_model.py index f7a24dd2b..fb3821028 100644 --- a/tacs/caps2tacs/tacs_model.py +++ b/tacs/caps2tacs/tacs_model.py @@ -14,6 +14,10 @@ from typing import List from tacs.pytacs import pyTACS +# optional funtofem dependency for shape variable animations +# will still work without funtofem +import importlib +f2f_loader = importlib.util.find_spec("funtofem") class TacsModel: MESH_AIMS = ["egads", "aflr"] @@ -273,6 +277,41 @@ def createTACSProbs(self, addFunctions: bool = True): ) return self.SPs + def _convert_shape_vars_dict(self, shape_vars_dict:dict): + """convert the shape variable dict keys from str or funtofem variables to Tacs Shape Variables if necessary""" + # convert shape variable dict to caps2tacs ShapeVariable keys if necessary + shape_vars_dict2 = {} + first_key = list(shape_vars_dict.keys())[0] + if isinstance(first_key, ShapeVariable): + shape_vars_dict2 = shape_vars_dict + elif isinstance(first_key, str): + # get matching caps2tacs shape variable for each string + for key in shape_vars_dict: + for var in self.tacs_aim.shape_variables: + if var.name == key: + shape_vars_dict2[var] = shape_vars_dict[key] + break # go to next key + elif f2f_loader is not None: # optional funtofem dependency + # import has to go here to prevent circular import + from funtofem import Variable + + if isinstance(first_key, Variable): + # get matching caps2tacs shape variable for each F2F shape variable + for f2f_var in shape_vars_dict: + for tacs_var in self.tacs_aim.shape_variables: + if f2f_var.name == tacs_var.name: + + # copy the value list to the new dictionary + shape_vars_dict2[tacs_var] = shape_vars_dict[f2f_var] + break # go to next variable + + else: + raise AssertionError(f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries.") + else: + raise AssertionError(f"The datatype {type(first_key)} is not allowed in caps2tacs shape variable dictionaries.") + + return shape_vars_dict2 + def animate_shape_vars(self, shape_vars_dict: dict): """ Animate the shape variables in ESP/CAPS. @@ -284,6 +323,7 @@ def animate_shape_vars(self, shape_vars_dict: dict): e.g. if you wish to animate over the ShapeVariable objects var1, and var2 create the following shape_vars_dict + NOTE : the keys can be str, ShapeVariable or funtofem variable objects objects shape_vars_dict = { var1 : [_*0.1 for _ in range(1,4)], var2 : [_*0.05 for _ in range(-3,4)], @@ -301,6 +341,9 @@ def animate_shape_vars(self, shape_vars_dict: dict): for shape_var in self.tacs_aim.shape_variables: shape_var._active = False + # convert the shape variables dict keys from str or F2F variable to Tacs Shape Variables + shape_vars_dict = self._convert_shape_vars_dict(shape_vars_dict) + for shape_var in shape_vars_dict: value_list = shape_vars_dict[shape_var] @@ -309,6 +352,9 @@ def animate_shape_vars(self, shape_vars_dict: dict): shape_var._active = True self.tacs_aim.setup_aim() + # save the original value + orig_value = shape_var.value * 1.0 + for i, value in enumerate(value_list): shape_var.value = value self.geometry.despmtr[shape_var.name].value = value @@ -328,8 +374,14 @@ def animate_shape_vars(self, shape_vars_dict: dict): self.SPs[caseID].writeSensFile( evalFuncs=None, tacsAim=self.tacs_aim, ) + + del self.SPs self.tacs_aim.post_analysis() + # return to the original value + shape_var.value = orig_value + self.geometry.despmtr[shape_var.name].value = orig_value + # make the shape variable inactive again shape_var._active = False print(