diff --git a/apps/dash-aerosandbox/.gitignore b/apps/dash-aerosandbox/.gitignore deleted file mode 100644 index 3e884ed9e..000000000 --- a/apps/dash-aerosandbox/.gitignore +++ /dev/null @@ -1,126 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -/.idea/ diff --git a/apps/dash-aerosandbox/README.md b/apps/dash-aerosandbox/README.md index 6e0082825..11b16d860 100644 --- a/apps/dash-aerosandbox/README.md +++ b/apps/dash-aerosandbox/README.md @@ -9,4 +9,4 @@ An interactive demo of AeroSandbox, powered by Dash! Work in progress. 2. Run `app.py` to launch a local Dash server to host the Dash app. A link will appear in your console; click this to use the Dash app. ## Illustration -![Screenshot of Demo](assets/screenshot.png) +![Screenshot of Demo](assets/github/screenshot.png) diff --git a/apps/dash-aerosandbox/app.py b/apps/dash-aerosandbox/app.py index 8583714c2..da3bd40f7 100644 --- a/apps/dash-aerosandbox/app.py +++ b/apps/dash-aerosandbox/app.py @@ -1,108 +1,48 @@ -import plotly.express as px -import plotly.graph_objects as go -import plotly.subplots as sub import dash -import dash_core_components as dcc -import dash_html_components as html +from dash import Dash, html, dcc, Input, Output, State, callback, callback_context import dash_bootstrap_components as dbc -from dash.dependencies import Input, Output, State -import aerosandbox as asb -import casadi as cas -from airplane import make_airplane -import numpy as np -import pandas as pd + + +from utils.components import ( + header, + key_parameters_card, + commands_card, + aerodynamic_performance_card, + figure_card, +) +from utils.figures import analysis_and_display app = dash.Dash(external_stylesheets=[dbc.themes.MINTY]) app.title = "Aircraft CFD" server = app.server + app.layout = dbc.Container( [ dbc.Row( - [ - dbc.Col( - [ - html.H2("Solar Aircraft Design with AeroSandbox and Dash"), - html.H5("Peter Sharpe"), - ], - width=True, - ), - # dbc.Col([ - # html.Img(src="assets/MIT-logo-red-gray-72x38.svg", alt="MIT Logo", height="30px"), - # ], width=1) - ], - align="end", + header( + app, + "white", + "Solar Aircraft Design with AeroSandbox and Dash", + "Peter Sharpe", + ), ), html.Hr(), dbc.Row( [ dbc.Col( [ - html.Div( - [ - html.H5("Key Parameters"), - html.P("Number of booms:"), - dcc.Slider( - id="n_booms", - min=1, - max=3, - step=1, - value=3, - marks={1: "1", 2: "2", 3: "3",}, - ), - html.P("Wing Span [m]:"), - dcc.Input(id="wing_span", value=43, type="number"), - html.P("Angle of Attack [deg]:"), - dcc.Input(id="alpha", value=7.0, type="number"), - ] - ), + key_parameters_card("n_booms", "wing_span", "alpha"), html.Hr(), - html.Div( - [ - html.H5("Commands"), - dbc.Button( - "Display (1s)", - id="display_geometry", - color="primary", - style={"margin": "5px"}, - n_clicks_timestamp="0", - ), - dbc.Button( - "LL Analysis (3s)", - id="run_ll_analysis", - color="secondary", - style={"margin": "5px"}, - n_clicks_timestamp="0", - ), - dbc.Button( - "VLM Analysis (15s)", - id="run_vlm_analysis", - color="secondary", - style={"margin": "5px"}, - n_clicks_timestamp="0", - ), - ] + commands_card( + "display_geometry", "run_ll_analysis", "run_vlm_analysis" ), html.Hr(), - html.Div( - [ - html.H5("Aerodynamic Performance"), - dbc.Spinner(html.P(id="output"), color="primary",), - ] - ), + aerodynamic_performance_card("output"), ], width=3, ), - dbc.Col( - [ - # html.Div(id='display') - dbc.Spinner( - dcc.Graph(id="display", style={"height": "80vh"}), - color="primary", - ) - ], - width=True, - ), + figure_card("display"), ] ), html.Hr(), @@ -128,125 +68,28 @@ ) -def make_table(dataframe): - return dbc.Table.from_dataframe( - dataframe, bordered=True, hover=True, responsive=True, striped=True, style={} - ) - - -@app.callback( - [Output("display", "figure"), Output("output", "children")], - [ - Input("display_geometry", "n_clicks_timestamp"), - Input("run_ll_analysis", "n_clicks_timestamp"), - Input("run_vlm_analysis", "n_clicks_timestamp"), - ], - [State("n_booms", "value"), State("wing_span", "value"), State("alpha", "value"),], +@callback( + Output("display", "figure"), + Output("output", "children"), + Input("display_geometry", "n_clicks_timestamp"), + Input("run_ll_analysis", "n_clicks_timestamp"), + Input("run_vlm_analysis", "n_clicks_timestamp"), + State("n_booms", "value"), + State("wing_span", "value"), + State("alpha", "value"), ) -def display_geometry( - display_geometry, run_ll_analysis, run_vlm_analysis, n_booms, wing_span, alpha, +def run_display_geometry( + display_geometry, + run_ll_analysis, + run_vlm_analysis, + n_booms, + wing_span, + alpha, ): - ### Figure out which button was clicked - try: - button_pressed = np.argmax( - np.array( - [ - float(display_geometry), - float(run_ll_analysis), - float(run_vlm_analysis), - ] - ) - ) - assert button_pressed is not None - except: - button_pressed = 0 - - ### Make the airplane - airplane = make_airplane(n_booms=n_booms, wing_span=wing_span,) - op_point = asb.OperatingPoint(density=0.10, velocity=20, alpha=alpha,) - if button_pressed == 0: - # Display the geometry - figure = airplane.draw(show=False, colorbar_title=None) - output = "Please run an analysis to display the data." - elif button_pressed == 1: - # Run an analysis - opti = cas.Opti() # Initialize an analysis/optimization environment - ap = asb.Casll1( - airplane=airplane, op_point=op_point, opti=opti, run_setup=False - ) - ap.setup(verbose=False) - # Solver options - p_opts = {} - s_opts = {} - # s_opts["mu_strategy"] = "adaptive" - opti.solver("ipopt", p_opts, s_opts) - # Solve - try: - sol = opti.solve() - except RuntimeError: - sol = opti.debug - raise Exception("An error occurred!") - - figure = ap.draw(show=False) # Generates figure - - output = make_table( - pd.DataFrame( - { - "Figure": ["CL", "CD", "CDi", "CDp", "L/D"], - "Value": [ - sol.value(ap.CL), - sol.value(ap.CD), - sol.value(ap.CDi), - sol.value(ap.CDp), - sol.value(ap.CL / ap.CD), - ], - } - ) - ) - - elif button_pressed == 2: - # Run an analysis - opti = cas.Opti() # Initialize an analysis/optimization environment - ap = asb.Casvlm1( - airplane=airplane, op_point=op_point, opti=opti, run_setup=False - ) - ap.setup(verbose=False) - # Solver options - p_opts = {} - s_opts = {} - # s_opts["mu_strategy"] = "adaptive" - opti.solver("ipopt", p_opts, s_opts) - # Solve - try: - sol = opti.solve() - except RuntimeError: - sol = opti.debug - raise Exception("An error occurred!") - - figure = ap.draw(show=False) # Generates figure - - output = make_table( - pd.DataFrame( - { - "Figure": ["CL", "CDi", "L/Di"], - "Value": [ - sol.value(ap.CL), - sol.value(ap.CDi), - sol.value(ap.CL / ap.CDi), - ], - } - ) - ) - - figure.update_layout( - autosize=True, - # width=1000, - # height=700, - margin=dict(l=0, r=0, b=0, t=0,), + return analysis_and_display( + display_geometry, run_ll_analysis, run_vlm_analysis, n_booms, wing_span, alpha ) - return (figure, output) - try: # wrapping this, since a forum post said it may be deprecated at some point. app.title = "Aircraft Design with Dash" diff --git a/apps/dash-aerosandbox/assets/css/app.css b/apps/dash-aerosandbox/assets/css/app.css new file mode 100644 index 000000000..63fddf0bc --- /dev/null +++ b/apps/dash-aerosandbox/assets/css/app.css @@ -0,0 +1,63 @@ +/* Header */ +.header { + height: 10vh; + display: flex; + padding-left: 2%; + padding-right: 2%; + font-family: playfair display, sans-serif; + font-weight: bold; +} + +.header .header-title { + font-size: 5vh; +} + +.subheader-title { + font-size: 1.5vh; +} + +.header-logos { + margin-left: auto; +} + +.header-logos img { + margin-left: 3vh !important; + max-height: 5vh; +} + + +/* Demo button css */ +.demo-button { + font-size: 1.5vh; + font-family: Open Sans, sans-serif; + text-decoration: none; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + border-radius: 8px; + font-weight: 700; + -webkit-padding-start: 1rem; + padding-inline-start: 1rem; + -webkit-padding-end: 1rem; + padding-inline-end: 1rem; + color: #ffffff; + letter-spacing: 1.5px; + border: solid 1.5px transparent; + box-shadow: 2px 1000px 1px #0c0c0c inset; + background-image: linear-gradient(135deg, #7A76FF, #7A76FF, #7FE4FF); + -webkit-background-size: 200% 100%; + background-size: 200% 100%; + -webkit-background-position: 99%; + background-position: 99%; + background-origin: border-box; + transition: all .4s ease-in-out; + padding-top: 1vh; + padding-bottom: 1vh; + vertical-align: super; +} + +.demo-button:hover { + color: #7A76FF; + background-position: 0%; +} \ No newline at end of file diff --git a/apps/dash-aerosandbox/assets/screenshot.png b/apps/dash-aerosandbox/assets/github/screenshot.png similarity index 100% rename from apps/dash-aerosandbox/assets/screenshot.png rename to apps/dash-aerosandbox/assets/github/screenshot.png diff --git a/apps/dash-aerosandbox/assets/MIT-logo-red-gray-72x38.svg b/apps/dash-aerosandbox/assets/images/MIT-logo-red-gray-72x38.svg similarity index 100% rename from apps/dash-aerosandbox/assets/MIT-logo-red-gray-72x38.svg rename to apps/dash-aerosandbox/assets/images/MIT-logo-red-gray-72x38.svg diff --git a/apps/dash-aerosandbox/assets/favicon.ico b/apps/dash-aerosandbox/assets/images/favicon.ico similarity index 100% rename from apps/dash-aerosandbox/assets/favicon.ico rename to apps/dash-aerosandbox/assets/images/favicon.ico diff --git a/apps/dash-aerosandbox/assets/images/plotly-logo-light-theme.png b/apps/dash-aerosandbox/assets/images/plotly-logo-light-theme.png new file mode 100644 index 000000000..4920c6e34 Binary files /dev/null and b/apps/dash-aerosandbox/assets/images/plotly-logo-light-theme.png differ diff --git a/apps/dash-aerosandbox/gitignore b/apps/dash-aerosandbox/gitignore new file mode 100644 index 000000000..90ecc9b06 --- /dev/null +++ b/apps/dash-aerosandbox/gitignore @@ -0,0 +1,191 @@ +# .gitignore specifies the files that shouldn't be included +# in version control and therefore shouldn't be included when +# deploying an application to Dash Enterprise +# This is a very exhaustive list! +# This list was based off of https://github.com/github/gitignore + +# Ignore data that is generated during the runtime of an application +# This folder is used by the "Large Data" sample applications +runtime_data/ +data/ + +# Omit SQLite databases that may be produced by dash-snapshots in development +*.db + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +# Jupyter Notebook + +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# IPython +profile_default/ +ipython_config.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + + +# macOS General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# History files +.Rhistory +.Rapp.history + +# Session Data files +.RData + +# User-specific files +.Ruserdata + +# Example code in package build process +*-Ex.R + +# Output files from R CMD check +/*.Rcheck/ + +# RStudio files +.Rproj.user/ + +# produced vignettes +vignettes/*.html +vignettes/*.pdf + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# knitr and R markdown default cache directories +*_cache/ +/cache/ + +# Temporary files created by R markdown +*.utf8.md +*.knit.md + +# R Environment Variables +.Renviron + +# Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# SublineText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings \ No newline at end of file diff --git a/apps/dash-aerosandbox/requirements.txt b/apps/dash-aerosandbox/requirements.txt index 17877d736..77500b120 100644 --- a/apps/dash-aerosandbox/requirements.txt +++ b/apps/dash-aerosandbox/requirements.txt @@ -1,12 +1,8 @@ aerosandbox==1.1.20 -plotly>= 4.6.0 -dash>= 1.9 -dash_core_components>=1.8.0 -dash_html_components>=1.0.2 -dash_bootstrap_components>=0.8.0 -numpy>=1.18 -casadi>=3.5 -gunicorn -pandas -seaborn +dash_bootstrap_components==1.1.0 +dash==2.4.1 +pandas==1.4.2 +gunicorn==20.1.0 +casadi==3.5.5 +seaborn==0.11.2 importlib-resources diff --git a/apps/dash-aerosandbox/runtime.txt b/apps/dash-aerosandbox/runtime.txt index 6919bf9ed..5fc4376c1 100644 --- a/apps/dash-aerosandbox/runtime.txt +++ b/apps/dash-aerosandbox/runtime.txt @@ -1 +1,2 @@ -python-3.7.6 +python-3.8.0 + diff --git a/apps/dash-aerosandbox/utils/components.py b/apps/dash-aerosandbox/utils/components.py new file mode 100644 index 000000000..c3654f40c --- /dev/null +++ b/apps/dash-aerosandbox/utils/components.py @@ -0,0 +1,127 @@ +from dash import html, dcc +import dash_bootstrap_components as dbc + + +def header( + app, header_color, header, subheader=None, header_background_color="transparent" +): + left_headers = html.Div( + [ + html.Div(header, className="header-title"), + html.Div(subheader, className="subheader-title"), + ], + style={"color": header_color}, + ) + + logo = html.Img(src=app.get_asset_url("assets/images/plotly-logo-light-theme.png")) + logo_link = html.A(logo, href="https://plotly.com/get-demo/", target="_blank") + demo_link = html.A( + "LEARN MORE", + href="https://plotly.com/dash/", + target="_blank", + className="demo-button", + ) + right_logos = html.Div([demo_link, logo_link], className="header-logos") + + return html.Div( + [left_headers, right_logos], + className="header", + style={"background-color": header_background_color}, + ) + + +def key_parameters_card(booms_id, wing_span_id, alpha_id): + return html.Div( + [ + html.H5("Key Parameters"), + html.P("Number of booms:"), + dcc.Slider( + id=booms_id, + min=1, + max=3, + step=1, + value=3, + marks={ + 1: "1", + 2: "2", + 3: "3", + }, + ), + html.P("Wing Span [m]:"), + dcc.Input(id=wing_span_id, value=43, type="number"), + html.P("Angle of Attack [deg]:"), + dcc.Input(id=alpha_id, value=7.0, type="number"), + ] + ) + + +def commands_card(display_geometry_id, run_ll_analysis_id, run_vlm_analysis_id): + return html.Div( + [ + html.H5("Commands"), + dbc.Button( + "Display (1s)", + id="display_geometry", + color="primary", + style={"margin": "5px"}, + n_clicks_timestamp="0", + ), + dbc.Button( + "LL Analysis (3s)", + id="run_ll_analysis", + color="secondary", + style={"margin": "5px"}, + n_clicks_timestamp="0", + ), + dbc.Button( + "VLM Analysis (15s)", + id="run_vlm_analysis", + color="secondary", + style={"margin": "5px"}, + n_clicks_timestamp="0", + ), + ] + ) + + +def aerodynamic_performance_card(output_id): + return html.Div( + [ + html.H5("Aerodynamic Performance"), + dbc.Spinner( + html.P(id=output_id), + color="primary", + ), + ] + ) + + +def figure_card(display_id): + return dbc.Col( + [ + # html.Div(id='display') + dbc.Spinner( + dcc.Graph(id=display_id, style={"height": "80vh"}), + color="primary", + ) + ], + width=True, + ) + + +# def footer_card(): +# return html.P( +# [ +# html.A( +# "Source code", +# href="https://github.com/peterdsharpe/AeroSandbox-Interactive-Demo", +# ), +# ". Aircraft design tools powered by ", +# html.A("AeroSandbox", href="https://peterdsharpe.github.com/AeroSandbox"), +# ". Build beautiful UIs for your scientific computing apps with ", +# html.A("Plot.ly ", href="https://plotly.com/"), +# "and ", +# html.A("Dash", href="https://plotly.com/dash/"), +# "!", +# ] +# ) diff --git a/apps/dash-aerosandbox/utils/figures.py b/apps/dash-aerosandbox/utils/figures.py new file mode 100644 index 000000000..8dd101ffc --- /dev/null +++ b/apps/dash-aerosandbox/utils/figures.py @@ -0,0 +1,132 @@ +import aerosandbox as asb +import dash_bootstrap_components as dbc +import numpy as np +import casadi as cas +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +import plotly.subplots as sub + +from utils.helper_functions import make_table, make_airplane + + +def analysis_and_display( + display_geometry, + run_ll_analysis, + run_vlm_analysis, + n_booms, + wing_span, + alpha, +): + ### Figure out which button was clicked + try: + button_pressed = np.argmax( + np.array( + [ + float(display_geometry), + float(run_ll_analysis), + float(run_vlm_analysis), + ] + ) + ) + assert button_pressed is not None + except: + button_pressed = 0 + + ### Make the airplane + airplane = make_airplane( + n_booms=n_booms, + wing_span=wing_span, + ) + op_point = asb.OperatingPoint( + density=0.10, + velocity=20, + alpha=alpha, + ) + if button_pressed == 0: + # Display the geometry + figure = airplane.draw(show=False, colorbar_title=None) + output = "Please run an analysis to display the data." + elif button_pressed == 1: + # Run an analysis + opti = cas.Opti() # Initialize an analysis/optimization environment + ap = asb.Casll1( + airplane=airplane, op_point=op_point, opti=opti, run_setup=False + ) + ap.setup(verbose=False) + # Solver options + p_opts = {} + s_opts = {} + # s_opts["mu_strategy"] = "adaptive" + opti.solver("ipopt", p_opts, s_opts) + # Solve + try: + sol = opti.solve() + except RuntimeError: + sol = opti.debug + raise Exception("An error occurred!") + + figure = ap.draw(show=False) # Generates figure + + output = make_table( + pd.DataFrame( + { + "Figure": ["CL", "CD", "CDi", "CDp", "L/D"], + "Value": [ + sol.value(ap.CL), + sol.value(ap.CD), + sol.value(ap.CDi), + sol.value(ap.CDp), + sol.value(ap.CL / ap.CD), + ], + } + ) + ) + + elif button_pressed == 2: + # Run an analysis + opti = cas.Opti() # Initialize an analysis/optimization environment + ap = asb.Casvlm1( + airplane=airplane, op_point=op_point, opti=opti, run_setup=False + ) + ap.setup(verbose=False) + # Solver options + p_opts = {} + s_opts = {} + # s_opts["mu_strategy"] = "adaptive" + opti.solver("ipopt", p_opts, s_opts) + # Solve + try: + sol = opti.solve() + except RuntimeError: + sol = opti.debug + raise Exception("An error occurred!") + + figure = ap.draw(show=False) # Generates figure + + output = make_table( + pd.DataFrame( + { + "Figure": ["CL", "CDi", "L/Di"], + "Value": [ + sol.value(ap.CL), + sol.value(ap.CDi), + sol.value(ap.CL / ap.CDi), + ], + } + ) + ) + + figure.update_layout( + autosize=True, + # width=1000, + # height=700, + margin=dict( + l=0, + r=0, + b=0, + t=0, + ), + ) + + return (figure, output) diff --git a/apps/dash-aerosandbox/airplane.py b/apps/dash-aerosandbox/utils/helper_functions.py similarity index 97% rename from apps/dash-aerosandbox/airplane.py rename to apps/dash-aerosandbox/utils/helper_functions.py index d3a82293d..26a60bb2c 100644 --- a/apps/dash-aerosandbox/airplane.py +++ b/apps/dash-aerosandbox/utils/helper_functions.py @@ -1,3 +1,5 @@ +import dash_bootstrap_components as dbc + import aerosandbox as asb from aerosandbox.library.airfoils import e216 import numpy as np @@ -7,8 +9,15 @@ naca0008 = asb.Airfoil("naca0008") +def make_table(dataframe): + return dbc.Table.from_dataframe( + dataframe, bordered=True, hover=True, responsive=True, striped=True, style={} + ) + + def make_airplane( - n_booms, wing_span, + n_booms, + wing_span, ): # n_booms = 3 diff --git a/apps/dash-aerosandbox/tests.py b/apps/dash-aerosandbox/utils/tests.py similarity index 73% rename from apps/dash-aerosandbox/tests.py rename to apps/dash-aerosandbox/utils/tests.py index 95ee4b41f..355576d9b 100644 --- a/apps/dash-aerosandbox/tests.py +++ b/apps/dash-aerosandbox/utils/tests.py @@ -1,13 +1,20 @@ import aerosandbox as asb import casadi as cas -from airplane import make_airplane +from utils.helper_functions import make_airplane n_booms = 1 wing_span = 40 alpha = 5 -airplane = make_airplane(n_booms=n_booms, wing_span=wing_span,) -op_point = asb.OperatingPoint(density=0.10, velocity=20, alpha=alpha,) +airplane = make_airplane( + n_booms=n_booms, + wing_span=wing_span, +) +op_point = asb.OperatingPoint( + density=0.10, + velocity=20, + alpha=alpha, +) ### LL # Run an analysis