diff --git a/.github/scripts/build_windows_executables.bat b/.github/scripts/build_windows_executables.bat
new file mode 100644
index 0000000000..70257fcb69
--- /dev/null
+++ b/.github/scripts/build_windows_executables.bat
@@ -0,0 +1,42 @@
+@call "C:\Program Files (x86)\Intel\oneAPI\setvars-vcvarsall.bat" %VS_VER%
+
+for /f "tokens=* usebackq" %%f in (`dir /b "C:\Program Files (x86)\Intel\oneAPI\compiler\" ^| findstr /V latest ^| sort`) do @set "LATEST_VERSION=%%f"
+@call "C:\Program Files (x86)\Intel\oneAPI\compiler\%LATEST_VERSION%\env\vars.bat"
+
+@REM Make the script that generates the git version description ignore dirty
+@REM since building the Visual Studio projects modifies files
+powershell -command "(Get-Content -Path '.\vs-build\CreateGitVersion.bat') -replace '--dirty', '' | Set-Content -Path '.\vs-build\CreateGitVersion.bat'"
+
+echo on
+
+@REM Build all solutions
+devenv vs-build/AeroDisk/AeroDisk_Driver.sln /Build "Release|x64"
+devenv vs-build/AeroDyn/AeroDyn_Driver.sln /Build "Release|x64"
+devenv vs-build/AeroDyn/AeroDyn_Driver.sln /Build "Release_OpenMP|x64"
+devenv vs-build/AeroDyn_Inflow_c_binding/AeroDyn_Inflow_c_binding.sln /Build "Release|x64"
+devenv vs-build/AeroDyn_Inflow_c_binding/AeroDyn_Inflow_c_binding.sln /Build "Release_OpenMP|x64"
+devenv vs-build/BeamDyn/BeamDyn-w-registry.sln /Build "Release|x64"
+devenv vs-build/Discon/Discon.sln /Build "Release|x64"
+devenv vs-build/FAST-farm/FAST-Farm.sln /Build "Release|x64"
+devenv vs-build/FAST-farm/FAST-Farm.sln /Build "Release_OpenMP|x64"
+devenv vs-build/HydroDyn/HydroDynDriver.sln /Build "Release|x64"
+devenv vs-build/HydroDyn_c_binding/HydroDyn_c_binding.sln /Build "Release|x64"
+devenv vs-build/InflowWind_c_binding/InflowWind_c_binding.sln /Build "Release|x64"
+devenv vs-build/InflowWind/InflowWind_driver.sln /Build "Release|x64"
+devenv vs-build/InflowWind/InflowWind_driver.sln /Build "Release_OpenMP|x64"
+devenv vs-build/MoorDyn/MoorDynDriver.sln /Build "Release|x64"
+devenv vs-build/MoorDyn_c_binding/MoorDyn_c_binding.sln /Build "Release|x64"
+devenv vs-build/FAST/FAST.sln /Build "Release|x64"
+devenv vs-build/SeaState/SeaStateDriver.sln /Build "Release|x64"
+devenv vs-build/SimpleElastoDyn/SimpleElastoDyn_Driver.sln /Build "Release|x64"
+devenv vs-build/SubDyn/SubDyn.sln /Build "Release|x64"
+devenv vs-build/TurbSim/TurbSim.vfproj /Build "Release|x64"
+devenv vs-build/UnsteadyAero/UnsteadyAero.sln /Build "Release|x64"
+
+@REM Build MATLAB solution last
+devenv vs-build/FAST/FAST.sln /Build "Release_Matlab|x64"
+
+@REM Copy controllers to bin directory
+xcopy .\reg_tests\r-test\glue-codes\openfast\5MW_Baseline\ServoData\*.dll .\build\bin\ /y
+
+exit /b %ERRORLEVEL%
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 87240dbce4..adf3513704 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,11 +11,20 @@ on:
types:
- released
+ push:
+ paths-ignore:
+ - 'LICENSE'
+ - 'README.rst'
+ - 'docs/**'
+ - 'share/**'
jobs:
+
+ # Disabled as publish-to-pypi is working correctly
publish-to-pypi-test:
runs-on: ubuntu-latest
- if: github.event_name == 'workflow_dispatch'
+ if: false
+ # if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v3
@@ -44,7 +53,7 @@ jobs:
publish-to-pypi:
runs-on: ubuntu-latest
- if: github.event_name == 'release'
+ if: github.event_name == 'release'
steps:
- uses: actions/checkout@v3
@@ -71,9 +80,11 @@ jobs:
run: hatch publish
working-directory: openfast_io
+ # Disabled as it fails due to huge memory requirements
docker-build-and-push:
runs-on: ubuntu-latest
- if: github.event_name == 'release'
+ if: false
+ # if: github.event_name == 'release'
timeout-minutes: 500
env:
DOCKERFILE_PATH: share/docker/Dockerfile
@@ -123,3 +134,87 @@ jobs:
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
+
+ build-windows-executables:
+ runs-on: windows-2022
+ if: github.event_name == 'workflow_dispatch' || github.event_name == 'release'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ fetch-depth: 0
+
+ - name: Install Intel oneAPI BaseKit (Windows)
+ shell: cmd
+ env:
+ URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/7dff44ba-e3af-4448-841c-0d616c8da6e7/w_BaseKit_p_2024.1.0.595_offline.exe
+ COMPONENTS: intel.oneapi.win.mkl.devel
+ run: |
+ curl.exe --output %TEMP%\webimage.exe --url %URL% --retry 5 --retry-delay 5
+ start /b /wait %TEMP%\webimage.exe -s -x -f webimage_extracted --log extract.log
+ del %TEMP%\webimage.exe
+ webimage_extracted\bootstrapper.exe -s --action install --components=%COMPONENTS% --eula=accept -p=NEED_VS2017_INTEGRATION=0 -p=NEED_VS2019_INTEGRATION=0 -p=NEED_VS2022_INTEGRATION=1 --log-dir=.
+ set installer_exit_code=%ERRORLEVEL%
+ rd /s/q "webimage_extracted"
+ exit /b %installer_exit_code%
+
+ - name: Install Intel oneAPI HPCKit (Windows)
+ shell: cmd
+ env:
+ URL: https://registrationcenter-download.intel.com/akdlm/IRC_NAS/c95a3b26-fc45-496c-833b-df08b10297b9/w_HPCKit_p_2024.1.0.561_offline.exe
+ COMPONENTS: intel.oneapi.win.ifort-compiler
+ run: |
+ curl.exe --output %TEMP%\webimage.exe --url %URL% --retry 5 --retry-delay 5
+ start /b /wait %TEMP%\webimage.exe -s -x -f webimage_extracted --log extract.log
+ del %TEMP%\webimage.exe
+ webimage_extracted\bootstrapper.exe -s --action install --components=%COMPONENTS% --eula=accept -p=NEED_VS2017_INTEGRATION=0 -p=NEED_VS2019_INTEGRATION=0 -p=NEED_VS2022_INTEGRATION=1 --log-dir=.
+ set installer_exit_code=%ERRORLEVEL%
+ rd /s/q "webimage_extracted"
+ exit /b %installer_exit_code%
+
+ - name: Set up MATLAB
+ id: setup-matlab
+ uses: matlab-actions/setup-matlab@v2
+ with:
+ products: Simulink
+
+ - name: Build Executables
+ env:
+ MATLAB_ROOT: ${{ steps.setup-matlab.outputs.matlabroot }}
+ run: .github/scripts/build_windows_executables.bat
+
+ - name: Test version output
+ run: |
+ build/bin/openfast_x64.exe -h
+ build/bin/TurbSim_x64.exe -h
+ build/bin/FAST.Farm_x64.exe -h
+
+ - name: Build MATLAB Mex File
+ uses: matlab-actions/run-command@v2
+ with:
+ command: |
+ mexname = 'FAST_SFunc';
+ mex('-largeArrayDims', ...
+ '-v', ...
+ ['-L' fullfile('build','bin')], ...
+ ['-I' fullfile('modules','openfast-library','src')], ... % "FAST_Library.h"
+ ['-I' fullfile('modules','supercontroller','src')], ... % "SuperController_Types.h"
+ ['-I' fullfile('modules','externalinflow','src')], ... % "ExternalInflow_Types.h"
+ ['-I' fullfile('modules','extloads','src')], ... % "ExtLoadsDX_Types.h"
+ ['-I' fullfile(matlabroot,'simulink','include')], ...
+ ['-I' fullfile(matlabroot,'extern','include')], ...
+ ['COMPFLAGS=$COMPFLAGS -MT -DS_FUNCTION_NAME=' mexname], ...
+ '-lOpenFAST-Simulink_x64', ...
+ '-outdir', fullfile('build','bin'), ...
+ '-output', mexname, ...
+ fullfile('glue-codes','simulink','src','FAST_SFunc.c'));
+
+ - name: Upload executables
+ uses: actions/upload-artifact@v4
+ with:
+ name: openfast-binaries
+ path: |
+ build/bin/*.exe
+ build/bin/*.dll
+ build/bin/*.mexw64
diff --git a/docs/changelogs/v4.0.4.md b/docs/changelogs/v4.0.4.md
new file mode 100644
index 0000000000..6dcc91776b
--- /dev/null
+++ b/docs/changelogs/v4.0.4.md
@@ -0,0 +1,94 @@
+**Feature or improvement description**
+Pull request to merge `rc-4.0.4` into `main` and create a tagged release for v4.0.4
+
+See the milestone and project pages for additional information
+
+ https://github.com/OpenFAST/openfast/milestone/20
+
+Test results, if applicable
+See GitHub Actions
+
+### Release checklist:
+- [ ] Update the documentation version in docs/conf.py
+- [ ] Update the versions in docs/source/user/api\_change.rst
+- [ ] Update version info in openfast\_io/pyproject.toml
+- [ ] Verify readthedocs builds correctly
+- [ ] Create an annotated tag in OpenFAST during merge (mark as most recent if necessary)
+- [ ] Create a merge commit in r-test and add a corresponding annotated tag
+- [ ] Upload Docker image
+- [ ] Compile executables for Windows builds
+ - [ ] `AeroDisk_Driver_x64.exe`
+ - [ ] `AeroDyn_Driver_x64.exe`
+ - [ ] `AeroDyn_Driver_x64_OpenMP.exe`
+ - [ ] `AeroDyn_Inflow_c_binding_x64.dll`
+ - [ ] `AeroDyn_Inflow_c_binding_x64_OpenMP.dll`
+ - [ ] `BeamDyn_Driver_x64.exe`
+ - [ ] `DISCON.dll (x64)`
+ - [ ] `DISCON_ITIBarge.dll (x64)`
+ - [ ] `DISCON_OC3Hywind.dll (x64)`
+ - [ ] `DISCON_SC.dll (x64)`
+ - [ ] `FAST.Farm_x64.exe`
+ - [ ] `FAST.Farm_x64_OMP.exe`
+ - [ ] `FAST_SFunc.mexw64`
+ - [ ] `HydroDynDriver_x64.exe`
+ - [ ] `HydroDyn_C_Binding_x64.dll`
+ - [ ] `IinflowWind_c_binding_x64.dll`
+ - [ ] `InflowWind_Driver_x64.exe`
+ - [ ] `InflowWind_Driver_x64_OpenMP.exe`
+ - [ ] `MoorDyn_Driver_x64.exe`
+ - [ ] `MoorDyn_c_binding_x64.dll`
+ - [ ] `OpenFAST-Simulink_x64.dll`
+ - [ ] `openfast_x64.exe`
+ - [ ] `SeaStateDriver_x64.exe`
+ - [ ] `SimpleElastoDyn_x64.exe`
+ - [ ] `SubDyn_x64.exe`
+ - [ ] `Turbsim_x64.exe`
+ - [ ] `UnsteadyAero_x64.exe`
+
+# Changelog
+
+## Overview
+
+This release includes several bug fixes and improvements for _OpenFAST_, GitHub actions, and _openfast\_io_.
+
+## General
+
+### CMake build system
+
+### GitHub actions
+
+#2778 Backport of GitHub Action to build windows executables on release (@deslaughter) -- backport of #2636
+
+
+### openfast_io
+
+#2779 MD: Backport of PR #2658 -- openfast\_IO MoorDyn compatibility (@RyanDavies19)
+
+#2777 bug fix #2762 (@mayankchetan)
+
+#2658 openfast\_IO MoorDyn compatibilityModule (@RyanDavies19)
+
+
+
+## Solvers
+
+### Simulink
+
+#2785 Avoid ending program when called as a shared library (@bjonkman) -- backport of #2671
+
+
+
+## Module changes
+
+### OpenFAST library
+
+#2780 backport of #2760: bug-fix: Adjust steady-state solver small angle assumptions (@bjonkman)
+
+#2776 Fix FAST\_ExtInfw\_Restart APIC++ API (@marchdf)
+
+
+
+## Input file changes
+
+No input file changes since v4.0.0
+
diff --git a/docs/conf.py b/docs/conf.py
index bb9d65f674..6e5c983e0f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -138,7 +138,7 @@ def runDoxygen(sourcfile, doxyfileIn, doxyfileOut):
# The short X.Y version.
version = u'4.0'
# The full version, including alpha/beta/rc tags.
-release = u'v4.0.3'
+release = u'v4.0.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/source/user/api_change.rst b/docs/source/user/api_change.rst
index 06492245a2..054ac176ea 100644
--- a/docs/source/user/api_change.rst
+++ b/docs/source/user/api_change.rst
@@ -10,6 +10,12 @@ The line number corresponds to the resulting line number after all changes are i
Thus, be sure to implement each in order so that subsequent line numbers are correct.
+OpenFAST v4.0.3 to OpenFAST v4.0.4
+----------------------------------
+
+No input file changes were made.
+
+
OpenFAST v4.0.2 to OpenFAST v4.0.3
----------------------------------
diff --git a/glue-codes/openfast-cpp/src/OpenFAST.cpp b/glue-codes/openfast-cpp/src/OpenFAST.cpp
index 33a534f8dc..b064c29bee 100644
--- a/glue-codes/openfast-cpp/src/OpenFAST.cpp
+++ b/glue-codes/openfast-cpp/src/OpenFAST.cpp
@@ -642,7 +642,6 @@ void fast::OpenFAST::init() {
tmpRstFileRoot,
&AbortErrLev,
&turbineData[iTurb].dt,
- &turbineData[iTurb].inflowType,
&turbineData[iTurb].numBlades,
&turbineData[iTurb].numVelPtsBlade,
&turbineData[iTurb].numVelPtsTwr,
diff --git a/modules/moordyn/src/MoorDyn.f90 b/modules/moordyn/src/MoorDyn.f90
index 80d21a7810..0e1f1ae812 100644
--- a/modules/moordyn/src/MoorDyn.f90
+++ b/modules/moordyn/src/MoorDyn.f90
@@ -34,7 +34,7 @@ MODULE MoorDyn
PRIVATE
- TYPE(ProgDesc), PARAMETER :: MD_ProgDesc = ProgDesc( 'MoorDyn', 'v2.2.2', '2024-01-16' )
+ TYPE(ProgDesc), PARAMETER :: MD_ProgDesc = ProgDesc( 'MoorDyn', 'v2.3.8', '2025-02-27' )
INTEGER(IntKi), PARAMETER :: wordy = 0 ! verbosity level. >1 = more console output
diff --git a/modules/moordyn/src/MoorDyn_IO.f90 b/modules/moordyn/src/MoorDyn_IO.f90
index 4eb8a6e2a0..4b32b2a898 100644
--- a/modules/moordyn/src/MoorDyn_IO.f90
+++ b/modules/moordyn/src/MoorDyn_IO.f90
@@ -1583,7 +1583,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg )
if ( p%NumOuts > 0_IntKi .and. p%MDUnOut > 0 ) then
! Write the output parameters to the file
- Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format?
+ Frmt = '(F10.4,'//TRIM(Int2LStr(p%NumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format?
WRITE(p%MDUnOut,Frmt) Time, ( p%Delim, y%WriteOutput(I), I=1,p%NumOuts )
END IF
@@ -1605,9 +1605,9 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg )
+ m%LineList(I)%N*SUM(m%LineList(I)%OutFlagList(10:18))
if (m%LineList(I)%OutFlagList(2) == 1) THEN ! if node positions are included, make them using a float format for higher precision
- Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES15.7E2),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES15.7E2))'
+ Frmt = '(F10.4,'//TRIM(Int2LStr(3*(m%LineList(I)%N + 1)))//'(A1,ES15.7),'//TRIM(Int2LStr(LineNumOuts - 3*(m%LineList(I)%N - 1)))//'(A1,ES15.7))'
else
- Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format?
+ Frmt = '(F10.4,'//TRIM(Int2LStr(LineNumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format?
end if
L = 1 ! start of index of line output file at first entry 12345.7890
@@ -1755,7 +1755,7 @@ SUBROUTINE MDIO_WriteOutputs( Time, p, m, y, ErrStat, ErrMsg )
+ m%RodList(I)%N*SUM(m%RodList(I)%OutFlagList(12:18))
- Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES15.7E2))' ! should evenutally use user specified format?
+ Frmt = '(F10.4,'//TRIM(Int2LStr(RodNumOuts))//'(A1,ES15.7))' ! should evenutally use user specified format?
L = 1 ! start of index of line output file at first entry
diff --git a/modules/openfast-library/src/FAST_Library.h b/modules/openfast-library/src/FAST_Library.h
index 4fc1e3124b..744d845063 100644
--- a/modules/openfast-library/src/FAST_Library.h
+++ b/modules/openfast-library/src/FAST_Library.h
@@ -16,7 +16,7 @@
EXTERNAL_ROUTINE void FAST_AllocateTurbines(int * iTurb, int *ErrStat, char *ErrMsg);
EXTERNAL_ROUTINE void FAST_DeallocateTurbines(int *ErrStat, char *ErrMsg);
-EXTERNAL_ROUTINE void FAST_ExtInfw_Restart(int * iTurb, const char *CheckpointRootName, int *AbortErrLev, double * dt, int * InflowType,
+EXTERNAL_ROUTINE void FAST_ExtInfw_Restart(int * iTurb, const char *CheckpointRootName, int *AbortErrLev, double * dt,
int * NumBl, int * NumBlElem, int * NumTwrElem, int * n_t_global,
ExtInfw_InputType_t* ExtInfw_Input, ExtInfw_OutputType_t* ExtInfw_Output, SC_DX_InputType_t* SC_DX_Input, SC_DX_OutputType_t* SC_DX_Output,
int *ErrStat, char *ErrMsg);
diff --git a/modules/openfast-library/src/FAST_SS_Solver.f90 b/modules/openfast-library/src/FAST_SS_Solver.f90
index f4ea398e61..ffd64c979a 100644
--- a/modules/openfast-library/src/FAST_SS_Solver.f90
+++ b/modules/openfast-library/src/FAST_SS_Solver.f90
@@ -586,8 +586,11 @@ SUBROUTINE SteadyStateSolve_Residual(caseData, p_FAST, y_FAST, m_FAST, ED, BD, A
INTEGER(IntKi) , INTENT( OUT) :: ErrStat !< Error status of the operation
CHARACTER(*) , INTENT( OUT) :: ErrMsg !< Error message if ErrStat /= ErrID_None
+ REAL(R8Ki) :: Orientation(3,3)
+ INTEGER(IntKi) :: k, node
INTEGER(IntKi) :: ErrStat2
INTEGER(IntKi) :: Indx_u_start
+ INTEGER(IntKi) :: Indx_u_angle_start(p_FAST%NumBl_Lin)
CHARACTER(ErrMsgLen) :: ErrMsg2
CHARACTER(*), PARAMETER :: RoutineName = 'SteadyStateSolve_Residual'
@@ -608,12 +611,23 @@ SUBROUTINE SteadyStateSolve_Residual(caseData, p_FAST, y_FAST, m_FAST, ED, BD, A
!..................
! Pack the output "residual vector" with these state derivatives and new inputs:
!..................
- CALL Create_SS_Vector( p_FAST, y_FAST, U_Resid, AD, ED, BD, InputIndex, STATE_PRED )
+ CALL Create_SS_Vector( p_FAST, y_FAST, U_Resid, AD, ED, BD, InputIndex, STATE_PRED, Indx_u_angle_start )
! Make the inputs a residual (subtract from previous inputs)
Indx_u_start = y_FAST%Lin%Glue%SizeLin(LIN_ContSTATE_COL) + 1
U_Resid(Indx_u_start : ) = u_in(Indx_u_start : ) - U_Resid(Indx_u_start : )
+ ! we need to make a special case for the orientation matrices
+ do k=1,p_FAST%NumBl_Lin
+ Indx_u_start = Indx_u_angle_start(k)
+ do node=1, AD%Input(InputIndex)%rotors(1)%BladeMotion(k)%NNodes
+ Orientation = EulerConstruct( u_in( Indx_u_start:Indx_u_start+2 ) )
+ Orientation = MATMUL(TRANSPOSE( AD%Input(InputIndex)%rotors(1)%BladeMotion(k)%Orientation(:,:,node)), Orientation)
+ U_Resid(Indx_u_start:Indx_u_start+2) = EulerExtract(Orientation)
+ Indx_u_start = Indx_u_start + 3
+ end do
+ end do
+
END SUBROUTINE SteadyStateSolve_Residual
!----------------------------------------------------------------------------------------------------------------------------------
!> This subroutine saves the current states so they can be used to compute the residual.
@@ -794,7 +808,7 @@ END SUBROUTINE Precondition_Jmat
!----------------------------------------------------------------------------------------------------------------------------------
!> This routine basically packs the relevant parts of the modules' inputs and states for use in the steady-state solver.
-SUBROUTINE Create_SS_Vector( p_FAST, y_FAST, u, AD, ED, BD, InputIndex, StateIndex )
+SUBROUTINE Create_SS_Vector( p_FAST, y_FAST, u, AD, ED, BD, InputIndex, StateIndex, IndxOrientStart )
!..................................................................................................................................
TYPE(FAST_ParameterType) , INTENT(IN ) :: p_FAST !< Glue-code simulation parameters
TYPE(FAST_OutputFileType), INTENT(IN ) :: y_FAST !< Output variables for the glue code
@@ -804,6 +818,7 @@ SUBROUTINE Create_SS_Vector( p_FAST, y_FAST, u, AD, ED, BD, InputIndex, StateInd
TYPE(AeroDyn_Data), INTENT(INOUT) :: AD !< AeroDyn data
INTEGER(IntKi), INTENT(IN ) :: InputIndex
INTEGER(IntKi), INTENT(IN ) :: StateIndex
+ INTEGER(IntKi), optional, INTENT( OUT) :: IndxOrientStart(p_FAST%NumBl_Lin)
! local variables:
INTEGER :: n
@@ -904,8 +919,9 @@ SUBROUTINE Create_SS_Vector( p_FAST, y_FAST, u, AD, ED, BD, InputIndex, StateInd
end do
end do
+ if (PRESENT(IndxOrientStart)) IndxOrientStart(k) = n ! keep track of index for AD orientation
do node = 1, AD%Input(InputIndex)%rotors(1)%BladeMotion(k)%NNodes
- CALL DCM_LogMap( AD%Input(InputIndex)%rotors(1)%BladeMotion(k)%Orientation(:,:,node), u(n:n+2), ErrStat2, ErrMsg2 )
+ u(n:n+2) = EulerExtract( AD%Input(InputIndex)%rotors(1)%BladeMotion(k)%Orientation(:,:,node) )
n = n+3
end do
diff --git a/modules/openfast-library/src/FAST_Subs.f90 b/modules/openfast-library/src/FAST_Subs.f90
index f8d11cb72a..1b0050734c 100644
--- a/modules/openfast-library/src/FAST_Subs.f90
+++ b/modules/openfast-library/src/FAST_Subs.f90
@@ -9615,12 +9615,16 @@ SUBROUTINE ExitThisProgram( p_FAST, y_FAST, m_FAST, ED, SED, BD, SrvD, AD, ADsk,
SimMsg = TRIM(FAST_Ver%Name)//' encountered an error '//TRIM(SimMsg)//'.'//NewLine//' Simulation error level: '//TRIM(GetErrStr(ErrorLevel))
+#if (defined COMPILE_SIMULINK || defined COMPILE_LABVIEW)
+ ! When built as a shared library/dll, don't end the program.
+ CALL WrScr(trim(SimMsg))
+#else
if (StopTheProgram) then
CALL ProgAbort( trim(SimMsg), TrapErrors=.FALSE., TimeWait=3._ReKi ) ! wait 3 seconds (in case they double-clicked and got an error)
else
CALL WrScr(trim(SimMsg))
end if
-
+#endif
END IF
!............................................................................................................................
diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py
index c7a5db763c..4500610412 100644
--- a/openfast_io/openfast_io/FAST_reader.py
+++ b/openfast_io/openfast_io/FAST_reader.py
@@ -31,6 +31,22 @@ def readline_filterComments(f):
read = False
return line
+def readline_ignoreComments(f, char = '#'): # see line 64 in NWTC_IO.f90
+ """
+ returns line before comment character
+
+ Args:
+ f: file handle
+ char: comment character
+
+ Returns:
+ line: content of next line in the file before comment character
+ """
+
+ line = f.readline().strip().split(char)
+
+ return line[0]
+
def read_array(f,len,split_val=None,array_type=str):
"""
Read an array of values from a line in a file
@@ -2879,13 +2895,8 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Rod_Name'] = []
self.fst_vt['MoorDyn']['Body_ID'] = []
self.fst_vt['MoorDyn']['Rod_ID'] = []
- self.fst_vt['MoorDyn']['ChannelID'] = []
-
# MoorDyn
- f.readline()
- f.readline()
- self.fst_vt['MoorDyn']['Echo'] = bool_read(f.readline().split()[0])
data_line = f.readline()
while data_line:
@@ -2903,25 +2914,48 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Diam'] = []
self.fst_vt['MoorDyn']['MassDen'] = []
self.fst_vt['MoorDyn']['EA'] = []
+ self.fst_vt['MoorDyn']['NonLinearEA'] = []
self.fst_vt['MoorDyn']['BA_zeta'] = []
self.fst_vt['MoorDyn']['EI'] = []
self.fst_vt['MoorDyn']['Cd'] = []
self.fst_vt['MoorDyn']['Ca'] = []
self.fst_vt['MoorDyn']['CdAx'] = []
self.fst_vt['MoorDyn']['CaAx'] = []
- data_line = f.readline().strip().split()
- while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
+ self.fst_vt['MoorDyn']['Cl'] = []
+ self.fst_vt['MoorDyn']['dF'] = []
+ self.fst_vt['MoorDyn']['cF'] = []
+ data_line = readline_filterComments(f).split()
+ while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same.
self.fst_vt['MoorDyn']['Name'].append(str(data_line[0]))
self.fst_vt['MoorDyn']['Diam'].append(float(data_line[1]))
self.fst_vt['MoorDyn']['MassDen'].append(float(data_line[2]))
- self.fst_vt['MoorDyn']['EA'].append(float(data_line[3]))
- self.fst_vt['MoorDyn']['BA_zeta'].append(float(data_line[4]))
+ self.fst_vt['MoorDyn']['EA'].append([float_read(dl) for dl in data_line[3].split('|')])
+ self.fst_vt['MoorDyn']['BA_zeta'].append([float(dl) for dl in data_line[4].split('|')])
self.fst_vt['MoorDyn']['EI'].append(float(data_line[5]))
self.fst_vt['MoorDyn']['Cd'].append(float(data_line[6]))
self.fst_vt['MoorDyn']['Ca'].append(float(data_line[7]))
self.fst_vt['MoorDyn']['CdAx'].append(float(data_line[8]))
self.fst_vt['MoorDyn']['CaAx'].append(float(data_line[9]))
- data_line = f.readline().strip().split()
+ if len(data_line) == 10:
+ self.fst_vt['MoorDyn']['Cl'].append(None)
+ self.fst_vt['MoorDyn']['dF'].append(None)
+ self.fst_vt['MoorDyn']['cF'].append(None)
+ elif (len(data_line) == 11):
+ self.fst_vt['MoorDyn']['Cl'].append(float(data_line[10]))
+ self.fst_vt['MoorDyn']['dF'].append(None)
+ self.fst_vt['MoorDyn']['cF'].append(None)
+ elif (len(data_line) == 13):
+ self.fst_vt['MoorDyn']['Cl'].append(float(data_line[10]))
+ self.fst_vt['MoorDyn']['dF'].append(float(data_line[11]))
+ self.fst_vt['MoorDyn']['cF'].append(float(data_line[12]))
+
+ if type(self.fst_vt['MoorDyn']['EA'][0]) is str:
+ EA_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['EA'][0]))
+ self.fst_vt['MoorDyn']['NonLinearEA'].append(self.read_NonLinearEA(EA_file))
+ else:
+ self.fst_vt['MoorDyn']['NonLinearEA'].append(None) # Empty to keep track of which non-linear EA files go with what line
+
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join
elif 'rodtypes' in data_line or 'roddictionary' in data_line:
@@ -2935,7 +2969,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Rod_CdEnd'] = []
self.fst_vt['MoorDyn']['Rod_CaEnd'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['Rod_Name'].append(data_line[0])
self.fst_vt['MoorDyn']['Rod_Diam'].append(float(data_line[1]))
@@ -2944,7 +2978,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Rod_Ca'].append(float(data_line[4]))
self.fst_vt['MoorDyn']['Rod_CdEnd'].append(float(data_line[5]))
self.fst_vt['MoorDyn']['Rod_CaEnd'].append(float(data_line[6]))
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
@@ -2967,7 +3001,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Body_CdA'] = []
self.fst_vt['MoorDyn']['Body_Ca'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['Body_ID'].append(int(data_line[0]))
self.fst_vt['MoorDyn']['Body_Attachment'].append(data_line[1])
@@ -2983,7 +3017,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Body_Volume'].append(float(data_line[11]))
self.fst_vt['MoorDyn']['Body_CdA'].append([float(dl) for dl in data_line[12].split('|')])
self.fst_vt['MoorDyn']['Body_Ca'].append([float(dl) for dl in data_line[13].split('|')])
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
@@ -3002,7 +3036,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Rod_NumSegs'] = []
self.fst_vt['MoorDyn']['RodOutputs'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['Rod_ID'].append(data_line[0])
self.fst_vt['MoorDyn']['Rod_Type'].append(data_line[1])
@@ -3015,7 +3049,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['Zb'].append(float(data_line[8]))
self.fst_vt['MoorDyn']['Rod_NumSegs'].append(int(data_line[9]))
self.fst_vt['MoorDyn']['RodOutputs'].append(data_line[10])
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
elif 'points' in data_line or 'connectionproperties' in data_line or \
@@ -3033,7 +3067,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['V'] = []
self.fst_vt['MoorDyn']['CdA'] = []
self.fst_vt['MoorDyn']['CA'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['Point_ID'].append(int(data_line[0]))
self.fst_vt['MoorDyn']['Attachment'].append(str(data_line[1]))
@@ -3044,7 +3078,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['V'].append(float(data_line[6]))
self.fst_vt['MoorDyn']['CdA'].append(float(data_line[7]))
self.fst_vt['MoorDyn']['CA'].append(float(data_line[8]))
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
elif 'lines' in data_line or 'lineproperties' in data_line or 'linelist' in data_line:
@@ -3059,7 +3093,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['UnstrLen'] = []
self.fst_vt['MoorDyn']['NumSegs'] = []
self.fst_vt['MoorDyn']['Outputs'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['Line_ID'].append(int(data_line[0]))
self.fst_vt['MoorDyn']['LineType'].append(str(data_line[1]))
@@ -3068,8 +3102,27 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['UnstrLen'].append(float(data_line[4]))
self.fst_vt['MoorDyn']['NumSegs'].append(int(data_line[5]))
self.fst_vt['MoorDyn']['Outputs'].append(str(data_line[6]))
- data_line = f.readline().strip().split()
- data_line = ''.join(data_line) # re-join for reading next section uniformly
+ data_line = readline_filterComments(f).split()
+ data_line = ''.join(data_line) # re-join for reading next section uniformly
+
+ elif 'failure' in data_line.lower():
+ f.readline()
+ f.readline()
+
+ self.fst_vt['MoorDyn']['Failure_ID'] = []
+ self.fst_vt['MoorDyn']['Failure_Point'] = []
+ self.fst_vt['MoorDyn']['Failure_Line(s)'] = []
+ self.fst_vt['MoorDyn']['FailTime'] = []
+ self.fst_vt['MoorDyn']['FailTen'] = []
+ data_line = readline_filterComments(f).split()
+ while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
+ self.fst_vt['MoorDyn']['Failure_ID'].append(int(data_line[0]))
+ self.fst_vt['MoorDyn']['Failure_Point'].append(data_line[1])
+ self.fst_vt['MoorDyn']['Failure_Line(s)'].append([int(dl) for dl in data_line[2].split(',')])
+ self.fst_vt['MoorDyn']['FailTime'].append(float(data_line[3]))
+ self.fst_vt['MoorDyn']['FailTen'].append(float(data_line[4]))
+ data_line = readline_filterComments(f).split()
+ data_line = ''.join(data_line) # re-join for reading next section uniformly
elif 'control' in data_line.lower():
f.readline()
@@ -3079,7 +3132,7 @@ def read_MoorDyn(self, moordyn_file):
self.fst_vt['MoorDyn']['ChannelID'] = []
self.fst_vt['MoorDyn']['Lines_Control'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
self.fst_vt['MoorDyn']['ChannelID'].append(int(data_line[0]))
# Line(s) is a list of mooring lines, spaces are allowed between commas
@@ -3093,46 +3146,87 @@ def read_MoorDyn(self, moordyn_file):
control_lines.remove('')
self.fst_vt['MoorDyn']['Lines_Control'].append(control_lines)
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
+ elif 'external' in data_line.lower():
+ f.readline()
+ f.readline()
+
+ self.fst_vt['MoorDyn']['External_ID'] = []
+ self.fst_vt['MoorDyn']['Object'] = []
+ self.fst_vt['MoorDyn']['Fext'] = []
+ self.fst_vt['MoorDyn']['Blin'] = []
+ self.fst_vt['MoorDyn']['Bquad'] = []
+ self.fst_vt['MoorDyn']['CSys'] = []
+ data_line = readline_filterComments(f).split()
+ while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
+ self.fst_vt['MoorDyn']['External_ID'].append(int(data_line[0]))
+ self.fst_vt['MoorDyn']['Object'].append(data_line[1])
+ self.fst_vt['MoorDyn']['Fext'].append([float(dl) for dl in data_line[2].split('|')])
+ self.fst_vt['MoorDyn']['Blin'].append([float(dl) for dl in data_line[3].split('|')])
+ self.fst_vt['MoorDyn']['Bquad'].append([float(dl) for dl in data_line[4].split('|')])
+ self.fst_vt['MoorDyn']['CSys'].append(data_line[5])
+ data_line = readline_filterComments(f).split()
+ data_line = ''.join(data_line) # re-join for reading next section uniformly
elif 'options' in data_line:
# MoorDyn lets options be written in any order
# Solver options
- self.fst_vt['MoorDyn']['options'] = [] # keep list of MoorDyn options
-
- string_options = ['WaterKin']
+ self.fst_vt['MoorDyn']['option_values'] = []
+ self.fst_vt['MoorDyn']['option_names'] = [] # keep list of MoorDyn options
+ self.fst_vt['MoorDyn']['option_descriptions'] = []
- data_line = f.readline().strip().split()
+ data_line = readline_filterComments(f).split()
while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
- raw_value = data_line[0]
- option_name = data_line[1]
-
- self.fst_vt['MoorDyn']['options'].append(option_name)
- if option_name in string_options:
- self.fst_vt['MoorDyn'][option_name] = raw_value.strip('"')
+ option_value = data_line[0].upper() # MD is case insensitive
+ option_name = data_line[1].upper() # MD is case insensitive
+ if len(data_line) > 2:
+ option_description = ' '.join(data_line[2:])
else:
- self.fst_vt['MoorDyn'][option_name] = float(raw_value)
+ option_description = '-'
- data_line = f.readline().strip().split()
+ if option_name.upper() == 'WATERKIN':
+ self.fst_vt['MoorDyn']['WaterKin'] = option_value.strip('"')
+ WaterKin_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['WaterKin']))
+ self.read_WaterKin(WaterKin_file)
+
+ self.fst_vt['MoorDyn']['option_values'].append(float_read(option_value.strip('"'))) # some options values can be strings or floats
+ self.fst_vt['MoorDyn']['option_names'].append(option_name)
+ self.fst_vt['MoorDyn']['option_descriptions'].append(option_description)
+
+ data_line = readline_filterComments(f).split()
data_line = ''.join(data_line) # re-join for reading next section uniformly
elif 'outputs' in data_line:
- self.read_outlist_freeForm(f,'MoorDyn')
+ data_line = readline_filterComments(f)
+
+ while (data_line[0] and data_line[0][:3] != '---') and not ('END' in data_line): # OpenFAST searches for --- and END, so we'll do the same
+
+ if '"' in data_line:
+ pattern = r'"?(.*?)"?' # grab only the text between quotes
+ data_line = re.findall(pattern, data_line)[0]
+
+ channels = data_line.split(',') # split on commas
+ channels = [c.strip() for c in channels] # strip whitespace
+ for c in channels:
+ self.fst_vt['outlist']['MoorDyn'][c] = True
+ data_line = readline_filterComments(f)
f.close()
break
- if 'WaterKin' in self.fst_vt['MoorDyn']['options']:
- WaterKin_file = os.path.normpath(os.path.join(os.path.dirname(moordyn_file), self.fst_vt['MoorDyn']['WaterKin']))
- self.read_WaterKin(WaterKin_file)
+ else: # we must be in the header section, unlimited lines of text allowed here so skip until we hit the first line w/ keywords 'Line Types' or 'Line Dictionary'
+ data_line = f.readline()
def read_WaterKin(self,WaterKin_file):
- print('here')
+
+ self.fst_vt['WaterKin']['z-depth'] = []
+ self.fst_vt['WaterKin']['x-current'] = []
+ self.fst_vt['WaterKin']['y-current'] = []
f = open(WaterKin_file)
f.readline()
@@ -3152,7 +3246,30 @@ def read_WaterKin(self,WaterKin_file):
self.fst_vt['WaterKin']['Z_Grid'] = read_array(f,None,split_val='-',array_type=float)
f.readline()
self.fst_vt['WaterKin']['CurrentMod'] = int_read(f.readline().split()[0])
+ f.readline()
+ f.readline()
+ data_line = readline_filterComments(f).split()
+ while data_line[0] and data_line[0][:3] != '---': # OpenFAST searches for ---, so we'll do the same
+ self.fst_vt['WaterKin']['z-depth'].append(float(data_line[0]))
+ self.fst_vt['WaterKin']['x-current'].append(float(data_line[1]))
+ self.fst_vt['WaterKin']['y-current'].append(float(data_line[2]))
+ data_line = readline_filterComments(f).split()
+ f.close()
+
+ def read_NonLinearEA(self,Stiffness_file): # read and return the nonlinear line stiffness lookup table for a given line
+
+ f = open(Stiffness_file)
+ f.readline()
+ f.readline()
+ f.readline()
+ NonLinearEA = {"Strain" : [], "Tension" : []}
+ data_line = readline_filterComments(f).split()
+ while data_line:
+ NonLinearEA['Strain'].append([float(data_line[0])])
+ NonLinearEA['Tension'].append([float(data_line[1])])
+ data_line = readline_filterComments(f).split()
f.close()
+ return NonLinearEA
def execute(self):
@@ -3254,7 +3371,7 @@ def execute(self):
bd_file1 = os.path.normpath(os.path.join(self.FAST_directory, self.fst_vt['Fst']['BDBldFile(1)']))
bd_file2 = os.path.normpath(os.path.join(self.FAST_directory, self.fst_vt['Fst']['BDBldFile(2)']))
bd_file3 = os.path.normpath(os.path.join(self.FAST_directory, self.fst_vt['Fst']['BDBldFile(3)']))
- if os.path.exists(bd_file1):
+ if os.path.isfile(bd_file1):
# if the files are the same then we only need to read it once, need to handle the cases where we have a 2 or 1 bladed rotor
# Check unique BeamDyn blade files and read only once if identical
if bd_file1 == bd_file2 and bd_file1 == bd_file3:
diff --git a/openfast_io/openfast_io/FAST_vars_out.py b/openfast_io/openfast_io/FAST_vars_out.py
index f1c9fea3be..ea11689d3b 100644
--- a/openfast_io/openfast_io/FAST_vars_out.py
+++ b/openfast_io/openfast_io/FAST_vars_out.py
@@ -9577,10 +9577,12 @@
""" MoorDyn """
# THIS IS NOT A COMPLETE LIST!
# the "flexible naming system" discussed on page 7-8 of the documentation is not included
-# http://www.matt-hall.ca/files/MoorDyn-Users-Guide-2017-08-16.pdf
+# https://moordyn.readthedocs.io/en/latest/inputs.html#id5
# also assuming that like other OpenFAST variables, it is limited to 9 output locations per veriable, i.e. FairTen1-FairTen9
# TODO: Handle the flexible outputs for moordyn. This will require a different approach than the current dictionary structure.
+# Right now a hackish way is used in the reader
+# Update the message in FAST_writer.write_MoorDyn() when this is finished
MoorDyn = {}
MoorDyn['FairTen1'] = False # (); ;
diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py
index 98c6677ecd..89917a8af5 100644
--- a/openfast_io/openfast_io/FAST_writer.py
+++ b/openfast_io/openfast_io/FAST_writer.py
@@ -30,35 +30,49 @@ def auto_format(f, var):
elif isinstance(var, float):
f.write('{: 2.15e}\n'.format(var))
-def float_default_out(val):
+def float_default_out(val, trim = False):
"""
Formatted float output when 'default' is an option
args:
val: value to be formatted
+ trim: trim the whitespace from the returned value
returns:
formatted value
"""
if type(val) is float:
- return '{: 22f}'.format(val)
+ if trim:
+ return '{:.4f}'.format(val)
+ else:
+ return '{: 22f}'.format(val)
else:
- return '{:<22}'.format(val)
+ if trim:
+ return '{:}'.format(val)
+ else:
+ return '{:<22}'.format(val)
-def int_default_out(val):
+def int_default_out(val, trim = False):
"""
Formatted int output when 'default' is an option
args:
val: value to be formatted
+ trim: trim the whitespace from the returned value
returns:
formatted value
"""
if type(val) is float:
- return '{:<22d}'.format(val)
+ if trim:
+ return '{:d}'.format(val)
+ else:
+ return '{:<22d}'.format(val)
else:
- return '{:<22}'.format(val)
+ if trim:
+ return '{:}'.format(val)
+ else:
+ return '{:<22}'.format(val)
def get_dict(vartree, branch):
"""
@@ -236,7 +250,7 @@ def execute(self):
self.write_MAP()
elif self.fst_vt['Fst']['CompMooring'] == 3:
self.write_MoorDyn()
- if 'options' in self.fst_vt['MoorDyn'] and 'WaterKin' in self.fst_vt['MoorDyn']['options']:
+ if 'option_names' in self.fst_vt['MoorDyn'] and 'WATERKIN' in self.fst_vt['MoorDyn']['option_names']:
self.write_WaterKin(os.path.join(self.FAST_runDirectory,self.fst_vt['MoorDyn']['WaterKin_file']))
# # look at if the the self.fst_vt['BeamDyn'] is an array, if so, loop through the array
@@ -2334,30 +2348,35 @@ def write_MoorDyn(self):
f.write('--------------------- MoorDyn Input File ------------------------------------\n')
f.write('Generated with OpenFAST_IO\n')
- f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['Echo'], 'Echo', '- echo the input file data (flag)\n'))
+ f.write('Comments from seed file have been dropped. Output Channels are not yet fully supported\n')
+ f.write('NOTE: MoorDyn does not use ECHO, instead use WriteLog of 0, 1, 2, or 3 in the options list \n')
f.write('----------------------- LINE TYPES ------------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['Name', 'Diam', 'MassDen', 'EA', 'BA/-zeta', 'EI', 'Cd', 'Ca', 'CdAx', 'CaAx']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(m)', '(kg/m)', '(N)', '(N-s/-)', '(-)', '(-)', '(-)', '(-)', '(-)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['Name', 'Diam', 'MassDen', 'EA', 'BA/-zeta', 'EI', 'Cd', 'Ca', 'CdAx', 'CaAx', 'Cl (optional)', 'dF (optional)', 'cF (optional)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(m)', '(kg/m)', '(N)', '(N-s/-)', '(N-m^2)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)', '(-)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Name'])):
ln = []
- ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Name'][i]))
+ ln.append('{:<11}'.format(self.fst_vt['MoorDyn']['Name'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Diam'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['MassDen'][i]))
- ln.append('{:^11.4e}'.format(self.fst_vt['MoorDyn']['EA'][i]))
- ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['BA_zeta'][i]))
+ ln.append('|'.join([float_default_out(a, trim=True) for a in self.fst_vt['MoorDyn']['EA'][i]]))
+ ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['BA_zeta'][i]]))
ln.append('{:^11.4e}'.format(self.fst_vt['MoorDyn']['EI'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Cd'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Ca'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CdAx'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CaAx'][i]))
f.write(" ".join(ln) + '\n')
+
+ if self.fst_vt['MoorDyn']['NonLinearEA'][i] != None:
+ self.write_NonLinearEA(self.fst_vt['MoorDyn']['EA'][i][0], self.fst_vt['MoorDyn']['Name'][i], self.fst_vt['MoorDyn']['NonLinearEA'][i])
+
if 'Rod_Name' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Rod_Name']:
f.write('----------------------- ROD TYPES ------------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['TypeName', 'Diam', 'Mass/m', 'Cd', 'Ca', 'CdEnd', 'CaEnd']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(name)', '(m)', '(kg/m)', '(-)', '(-)', '(-)', '(-)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['TypeName', 'Diam', 'Mass/m', 'Cd', 'Ca', 'CdEnd', 'CaEnd']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(name)', '(m)', '(kg/m)', '(-)', '(-)', '(-)', '(-)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Rod_Name'])):
ln = []
- ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Name'][i]))
+ ln.append('{:<11}'.format(self.fst_vt['MoorDyn']['Rod_Name'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_Diam'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_MassDen'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Rod_Cd'][i]))
@@ -2369,11 +2388,11 @@ def write_MoorDyn(self):
if 'Body_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Body_ID']:
f.write('----------------------- BODIES ------------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'Attachement', 'X0', 'Y0', 'Z0', 'r0', 'p0','y0','Mass','CG*','I*','Volume','CdA*','Ca*']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(#)', '(word)', '(m)', '(m)', '(m)', '(deg)', '(deg)','(deg)','(kg)','(m)','(kg-m^2)','(m^3)','m^2','(kg/m^3)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'Attachement', 'X0', 'Y0', 'Z0', 'r0', 'p0','y0','Mass','CG*','I*','Volume','CdA*','Ca*']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(#)', '(word)', '(m)', '(m)', '(m)', '(deg)', '(deg)','(deg)','(kg)','(m)','(kg-m^2)','(m^3)','m^2','(kg/m^3)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Body_ID'])):
ln = []
- ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Body_ID'][i]))
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Body_ID'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Body_Attachment'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['X0'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Y0'][i]))
@@ -2391,11 +2410,11 @@ def write_MoorDyn(self):
if 'Rod_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Rod_ID']:
f.write('----------------------- RODS ------------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'RodType', 'Attachment', 'Xa', 'Ya', 'Za', 'Xb','Yb','Zb','NumSegs','RodOutputs']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(#)', '(name)', '(word/ID)', '(m)', '(m)', '(m)', '(m)','(m)','(m)','(-)','(-)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'RodType', 'Attachment', 'Xa', 'Ya', 'Za', 'Xb','Yb','Zb','NumSegs','RodOutputs']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(#)', '(name)', '(word/ID)', '(m)', '(m)', '(m)', '(m)','(m)','(m)','(-)','(-)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Rod_ID'])):
ln = []
- ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_ID'][i]))
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Rod_ID'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Type'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Rod_Attachment'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Xa'][i]))
@@ -2410,11 +2429,11 @@ def write_MoorDyn(self):
f.write('---------------------- POINTS --------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['ID', 'Attachment', 'X', 'Y', 'Z', 'M', 'V', 'CdA', 'CA']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(-)', '(m)', '(m)', '(m)', '(kg)', '(m^3)', '(m^2)', '(-)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['ID', 'Attachment', 'X', 'Y', 'Z', 'M', 'V', 'CdA', 'CA']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(-)', '(m)', '(m)', '(m)', '(kg)', '(m^3)', '(m^2)', '(-)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Point_ID'])):
ln = []
- ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Point_ID'][i]))
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Point_ID'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Attachment'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['X'][i]))
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['Y'][i]))
@@ -2425,11 +2444,11 @@ def write_MoorDyn(self):
ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['CA'][i]))
f.write(" ".join(ln) + '\n')
f.write('---------------------- LINES --------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['Line', 'LineType', 'AttachA', 'AttachB', 'UnstrLen', 'NumSegs', 'Outputs']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['(-)', '(-)', '(-)', '(-)', '(m)', '(-)', '(-)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['Line', 'LineType', 'AttachA', 'AttachB', 'UnstrLen', 'NumSegs', 'Outputs']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['(-)', '(-)', '(-)', '(-)', '(m)', '(-)', '(-)']])+'\n')
for i in range(len(self.fst_vt['MoorDyn']['Line_ID'])):
ln = []
- ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['Line_ID'][i]))
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Line_ID'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['LineType'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['AttachA'][i]))
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['AttachB'][i]))
@@ -2438,41 +2457,58 @@ def write_MoorDyn(self):
ln.append('{:^11}'.format(self.fst_vt['MoorDyn']['Outputs'][i]))
f.write(" ".join(ln) + '\n')
+ if 'Failure_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['Failure_ID']: # there are failure inputs
+ f.write('---------------------- FAILURE ----------------------\n')
+ f.write('FailureID Point Line(s) FailTime FailTen\n')
+ f.write('() () (,) (s or 0) (N or 0)\n')
+ for i_line in range(len(self.fst_vt['MoorDyn']['Failure_ID'])):
+ ln = []
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['Failure_ID'][i_line]))
+ ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Failure_Point'][i_line]))
+ ln.append('{:^11s}'.format(','.join(['{:^d}'.format(a) for a in self.fst_vt['MoorDyn']['Failure_Line(s)'][i_line]])))
+ ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTime'][i_line]))
+ ln.append('{:^11.4f}'.format(self.fst_vt['MoorDyn']['FailTen'][i_line]))
+ f.write(" ".join(ln) + '\n')
+
if 'ChannelID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['ChannelID']: # There are control inputs
f.write('---------------------- CONTROL ---------------------------------------\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['ChannelID', 'Line(s)']])+'\n')
- f.write(" ".join(['{:^11s}'.format(i) for i in ['()', '(,)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['ChannelID', 'Line(s)']])+'\n')
+ f.write(" ".join(['{:<11s}'.format(i) for i in ['()', '(,)']])+'\n')
for i_line in range(len(self.fst_vt['MoorDyn']['ChannelID'])):
ln = []
- ln.append('{:^11d}'.format(self.fst_vt['MoorDyn']['ChannelID'][i_line]))
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['ChannelID'][i_line]))
ln.append(','.join(self.fst_vt['MoorDyn']['Lines_Control'][i_line]))
f.write(" ".join(ln) + '\n')
- f.write('---------------------- SOLVER OPTIONS ---------------------------------------\n')
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['dtM'], 'dtM', '- time step to use in mooring integration (s)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['kbot'], 'kbot', '- bottom stiffness (Pa/m)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['cbot'], 'cbot', '- bottom damping (Pa-s/m)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['dtIC'], 'dtIC', '- time interval for analyzing convergence during IC gen (s)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['TmaxIC'], 'TmaxIC', '- max time for ic gen (s)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['CdScaleIC'], 'CdScaleIC', '- factor by which to scale drag coefficients during dynamic relaxation (-)\n'))
- f.write('{:<22} {:<11} {:}'.format(self.fst_vt['MoorDyn']['threshIC'], 'threshIC', '- threshold for IC convergence (-)\n'))
- if 'options' in self.fst_vt['MoorDyn']:
- if 'inertialF' in self.fst_vt['MoorDyn']['options']:
- f.write('{:<22d} {:<11} {:}'.format(int(self.fst_vt['MoorDyn']['inertialF']), 'inertialF', '- Compute the inertial forces (0: no, 1: yes). Switch to 0 if you get: Warning: extreme pitch moment from body-attached Rod.\n'))
-
- if 'WaterKin' in self.fst_vt['MoorDyn']['options']:
+ if 'External_ID' in self.fst_vt['MoorDyn'] and self.fst_vt['MoorDyn']['External_ID']: # there are external load outputs
+ f.write('---------------------- EXTERNAL LOADS --------------------------------\n')
+ f.write('ID Object Fext Blin Bquad CSys\n')
+ f.write('(#) (name) (N) (Ns/m) (Ns^2/m^2) (-)\n')
+ for i_line in range(len(self.fst_vt['MoorDyn']['External_ID'])):
+ ln = []
+ ln.append('{:<7d}'.format(self.fst_vt['MoorDyn']['External_ID'][i_line]))
+ ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['Object'][i_line]))
+ ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Fext'][i_line]]))
+ ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Blin'][i_line]]))
+ ln.append('|'.join(['{:^.4f}'.format(a) for a in self.fst_vt['MoorDyn']['Bquad'][i_line]]))
+ ln.append('{:^11s}'.format(self.fst_vt['MoorDyn']['CSys'][i_line]))
+ f.write(" ".join(ln) + '\n')
+
+ f.write('---------------------- SOLVER OPTIONS ---------------------------------------\n')
+ for i in range(len(self.fst_vt['MoorDyn']['option_values'])):
+
+ if 'WATERKIN' in self.fst_vt['MoorDyn']['option_names'][i]:
self.fst_vt['MoorDyn']['WaterKin_file'] = self.FAST_namingOut + '_WaterKin.dat'
- f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', 'WaterKin', '- WaterKin input file\n'))
+ f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['MoorDyn']['WaterKin_file']+'"', self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_descriptions'][i]+'\n'))
+ else: # if not waterkin handle normally
+ f.write('{:<22} {:<11} {:}'.format(float_default_out(self.fst_vt['MoorDyn']['option_values'][i]), self.fst_vt['MoorDyn']['option_names'][i], self.fst_vt['MoorDyn']['option_descriptions'][i]+'\n'))
-
- # f.write('{:^11s} {:<11} {:}'.format(self.fst_vt['MoorDyn']['WaterKin'], 'WaterKin', 'Handling of water motion (0=off, 1=on)\n'))
f.write('------------------------ OUTPUTS --------------------------------------------\n')
outlist = self.get_outlist(self.fst_vt['outlist'], ['MoorDyn'])
for channel_list in outlist:
for i in range(len(channel_list)):
- f.write('"' + channel_list[i] + '"\n')
- f.write('END\n')
- f.write('------------------------- need this line --------------------------------------\n')
+ f.write(channel_list[i] + '\n')
+ f.write('------------------------- END --------------------------------------\n')
f.flush()
os.fsync(f)
@@ -2481,8 +2517,8 @@ def write_MoorDyn(self):
def write_WaterKin(self,WaterKin_file):
f = open(WaterKin_file, 'w')
- f.write('MoorDyn v2 (Feb 2022) Waves and Currents input file set up for USFLOWT\n')
- f.write('Wave kinematics that will have an impact over the cans and the mooring lines.\n')
+ f.write('MoorDyn v2 Waves and Currents input file set up\n')
+ f.write('This file was written by FAST_writer.py, comments from seed file have been dropped.\n')
f.write('--------------------------- WAVES -------------------------------------\n')
f.write('{:<22} {:<11} {:}'.format(self.fst_vt['WaterKin']['WaveKinMod'], 'WaveKinMod', '- type of wave input {0 no waves; 3 set up grid of wave data based on time series}\n'))
f.write('{:<22} {:<11} {:}'.format(self.fst_vt['WaterKin']['WaveKinFile'], 'WaveKinFile', '- file containing wave elevation time series at 0,0,0\n'))
@@ -2495,16 +2531,30 @@ def write_WaterKin(self,WaterKin_file):
f.write('{:<22} {:}'.format(self.fst_vt['WaterKin']['Z_Type'], '- Z wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -Zlim, Zlim, num)\n'))
f.write('{:<22} {:}'.format(', '.join(['{:.3f}'.format(i) for i in self.fst_vt['WaterKin']['Z_Grid']]), '- Z wave grid point data separated by commas\n'))
f.write('--------------------------- CURRENT -------------------------------------\n')
- f.write('0 CurrentMod - type of current input {0 no current; 1 steady current profile described below} \n')
+ f.write('{:} CurrentMod - type of current input (0 no current; 1 steady current profile described below) \n'.format(self.fst_vt['WaterKin']['CurrentMod']))
f.write('z-depth x-current y-current\n')
f.write('(m) (m/s) (m/s)\n')
+ if self.fst_vt['WaterKin']['z-depth']:
+ for i in range(len(self.fst_vt['WaterKin']['z-depth'])):
+ f.write('{:.3f} {:.3f} {:.3f}'.format(self.fst_vt['WaterKin']['z-depth'][i], self.fst_vt['WaterKin']['x-current'][i], self.fst_vt['WaterKin']['y-current'][i]))
f.write('--------------------- need this line ------------------\n')
f.flush()
os.fsync(f)
f.close()
+ def write_NonLinearEA(self,Stiffness_file, Linetype, NonLinearEA):
+ f = open(Stiffness_file, 'w')
+ f.write('---{:}---\n'.format(Linetype))
+ f.write('Strain Tension\n')
+ f.write('(-) (N)\n')
+ for i in range(len(NonLinearEA["Strain"])):
+ f.write('{:.3f} {:.3f}'.format(NonLinearEA["Strain"][i], NonLinearEA["Tension"][i]))
+
+ f.flush()
+ os.fsync(f)
+ f.close()
def write_StC(self,StC_vt,StC_filename):
@@ -2631,7 +2681,7 @@ def write_StC(self,StC_vt,StC_filename):
'glue-codes', 'openfast',
'5MW_Land_BD_DLL_WTurb') # Path to fst directory files
- check_rtest_cloned(os.path.join(fast.FAST_directory, fast.FAST_InputFile))
+ check_rtest_cloned(os.path.join(fast.FAST_directory))
fast.execute()
diff --git a/openfast_io/pyproject.toml b/openfast_io/pyproject.toml
index 7595361ef7..6eb3e86f8d 100644
--- a/openfast_io/pyproject.toml
+++ b/openfast_io/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "openfast_io"
# dynamic = ["version"]
-version = "4.0.3"
+version = "4.0.4"
description = "Readers and writers for OpenFAST files."
license = {file = "../LICENSE"}
authors = [
diff --git a/vs-build/OpenFAST-Simulink/OpenFAST-Simulink.vfproj b/vs-build/OpenFAST-Simulink/OpenFAST-Simulink.vfproj
index 1b7110e1d7..655b2435a6 100644
--- a/vs-build/OpenFAST-Simulink/OpenFAST-Simulink.vfproj
+++ b/vs-build/OpenFAST-Simulink/OpenFAST-Simulink.vfproj
@@ -16,7 +16,7 @@
-
+
@@ -36,7 +36,7 @@
-
+