Repository: FloatingArrayDesign/MoorDyn (language: python)

Issue:
Default argument error with C API: Waves.h
When using load library in Matlab to load the C API, the following error message is produced: 

```
------Error in the Model, Error-Causes/Messsages are :
Building libmoordyn_thunk_maci64 failed.  Compiler output is:
/usr/bin/xcrun -sdk macosx14.0 clang -I"/Applications/MATLAB_R2023a.app/extern/include" -fno-common -arch x86_64 -mmacosx-version-min=10.15 -fexceptions -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk -I"/Users/rdavies/work/wecsim_libs/WEC-Sim_Applications/Mooring/MoorDyn" -I"/Users/rdavies/work/wecsim_libs/WEC-Sim/source/functions/moorDyn" "libmoordyn_thunk_maci64.c" -o "libmoordyn_thunk_maci64.dylib" -bundle 
In file included from libmoordyn_thunk_maci64.c:27:
In file included from /Users/rdavies/work/wecsim_libs/WEC-Sim/source/functions/moorDyn/MoorDyn2.h:39:
/Users/rdavies/work/wecsim_libs/WEC-Sim/source/functions/moorDyn/Waves.h:77:59: error: C does not support default arguments
                                        MoorDynSeafloor seafloor = 0);
```

I worked around it by removing the seafloor input at line 76 in Waves.h and at line 899 in Waves.cpp. At line 909  in Waves.cpp I replaced the seafloor variable with 0. This a temporary fix because it removes the capability, but necessary for use of the C API. 

Note: we are not using the Matlab wrapper with WECSim because of compatibility issues with older versions of Matlab  
Docs update for failure
Docs update for MDF new feature: line failures
Unnecessary point in input file for lines using only rods
I have a MoorDyn input-file defining a cable with bending stiffness: [exampleinput.txt](https://github.com/FloatingArrayDesign/MoorDyn/files/14508108/exampleinput.txt)
To define the cable, I only use rods as connections. It would not need any points. If I delete the points-section from the input-file, I obtain the following error, and MoorDyn does not run:

`ERR source\MoorDyn2.cpp:952 moordyn::MoorDyn::ReadInFile(): Reading lines without defined points`
`Error (-3) during the Mooring System creation:`
`Exception while reading the input file`

Therefore, I defined a point in the input-file which is actually not needed in the simulation.
I am using MoorDyn v2.0.0, 2023-09-18 and v2.2.2 on Windows 10.



Discussion / hints:
Yeah, @AlexWKinley we should be careful of not adding C++ stuff on the .h files.

We are fixing this for 2.2.2
Since this require changing the ABI, I decided to move this to the next minor release, 2.3.0



Fix patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6077d357..36d358fb 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,3 +5,4 @@ updates:
     directory: "/"
     schedule:
       interval: "weekly"
+    target-branch: "dev"
diff --git a/.github/workflows/check-version.yml b/.github/workflows/check-version.yml
index 738520ec..2ee719dd 100644
--- a/.github/workflows/check-version.yml
+++ b/.github/workflows/check-version.yml
@@ -21,7 +21,7 @@ jobs:
         echo "moordyn_version=$major.$minor.$patch" >> $GITHUB_OUTPUT
 
     - name: Tag check
-      uses: mukunku/tag-exists-action@v1.4.0
+      uses: mukunku/tag-exists-action@v1.6.0
       id: checkTag
       with:
         tag: "v${{steps.moordyn_version.outputs.moordyn_version}}"
diff --git a/.github/workflows/python-wheels-manylinux-arch.yml b/.github/workflows/python-wheels-manylinux-arch.yml
index 47a12862..cfd5b783 100644
--- a/.github/workflows/python-wheels-manylinux-arch.yml
+++ b/.github/workflows/python-wheels-manylinux-arch.yml
@@ -29,7 +29,7 @@ jobs:
       - uses: actions/checkout@v4
 
       # Used to host cibuildwheel
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: '3.x' 
 
@@ -42,7 +42,7 @@ jobs:
         shell: bash
 
       - name: download pre-built VTK static library
-        uses: suisei-cn/actions-download-file@v1.4.0
+        uses: suisei-cn/actions-download-file@v1.6.0
         with:
           url: https://github.com/sanguinariojoe/vtk-builds/releases/download/VTK-${{env.VTK_VERSION_MAJOR}}.${{env.VTK_VERSION_MINOR}}.${{env.VTK_VERSION_PATCH}}-static/vtk-manylinux2014_${{inputs.arch}}.tar.gz
           target: ${{github.workspace}}/
@@ -53,7 +53,7 @@ jobs:
           platforms: all
 
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.16.2
+        uses: pypa/cibuildwheel@v2.17.0
         with:
             output-dir: dist
         env:
@@ -67,7 +67,7 @@ jobs:
             mkdir -p vtk &&
             tar -xvzf vtk-manylinux2014_`uname -m`.tar.gz -C vtk/
 
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         id: build_wheels
         with:
           name: python-wheels-${{inputs.arch}}
diff --git a/.github/workflows/python-wheels.yml b/.github/workflows/python-wheels.yml
index 8bb95694..12dc85f4 100644
--- a/.github/workflows/python-wheels.yml
+++ b/.github/workflows/python-wheels.yml
@@ -25,7 +25,7 @@ jobs:
       - uses: actions/checkout@v4
 
       # Used to host cibuildwheel
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: '3.x' 
 
@@ -38,21 +38,21 @@ jobs:
         shell: bash
 
       - name: download pre-built VTK static library (non-Linux)
-        uses: suisei-cn/actions-download-file@v1.4.0
+        uses: suisei-cn/actions-download-file@v1.6.0
         with:
           url: https://github.com/sanguinariojoe/vtk-builds/releases/download/VTK-${{env.VTK_VERSION_MAJOR}}.${{env.VTK_VERSION_MINOR}}.${{env.VTK_VERSION_PATCH}}-static/vtk-${{runner.os}}-x86_64.tar.gz
           target: ${{github.workspace}}/
         if: runner.os != 'Linux'
 
       - name: download pre-built VTK static library (Linux-x86_64)
-        uses: suisei-cn/actions-download-file@v1.4.0
+        uses: suisei-cn/actions-download-file@v1.6.0
         with:
           url: https://github.com/sanguinariojoe/vtk-builds/releases/download/VTK-${{env.VTK_VERSION_MAJOR}}.${{env.VTK_VERSION_MINOR}}.${{env.VTK_VERSION_PATCH}}-static/vtk-manylinux2014_x86_64.tar.gz
           target: ${{github.workspace}}/
         if: runner.os == 'Linux'
 
       - name: download pre-built VTK static library (Linux-i686)
-        uses: suisei-cn/actions-download-file@v1.4.0
+        uses: suisei-cn/actions-download-file@v1.6.0
         with:
           url: https://github.com/sanguinariojoe/vtk-builds/releases/download/VTK-${{env.VTK_VERSION_MAJOR}}.${{env.VTK_VERSION_MINOR}}.${{env.VTK_VERSION_PATCH}}-static/vtk-manylinux2014_i686.tar.gz
           target: ${{github.workspace}}/
@@ -79,7 +79,7 @@ jobs:
         if: runner.os == 'Linux'
 
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.16.2
+        uses: pypa/cibuildwheel@v2.17.0
         with:
             output-dir: dist
         env:
@@ -93,7 +93,7 @@ jobs:
             mkdir -p vtk &&
             tar -xvzf vtk-manylinux2014_`uname -m`.tar.gz -C vtk/
 
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         id: build_wheels
         with:
           name: python-wheels-x86_64
@@ -131,25 +131,25 @@ jobs:
       - uses: actions/checkout@v4
 
       - name: Download x86_64 dists
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: python-wheels-x86_64
           path: dist/
 
       - name: Download aarch64 dists
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: python-wheels-aarch64
           path: dist/
 
       - name: Download ppc64le dists
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: python-wheels-ppc64le
           path: dist/
 
       - name: Download s390x dists
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: python-wheels-s390x
           path: dist/
diff --git a/.github/workflows/python-wrapper.yml b/.github/workflows/python-wrapper.yml
new file mode 100644
index 00000000..da136713
--- /dev/null
+++ b/.github/workflows/python-wrapper.yml
@@ -0,0 +1,62 @@
+name: Python wrapper testing
+
+on:
+  push:
+    branches: [ $default-branch, master ]
+  pull_request:
+    branches:
+      - "**"
+
+permissions: write-all
+
+env:
+  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+  BUILD_TYPE: Release
+  VTK_VERSION_MAJOR: 9
+  VTK_VERSION_MINOR: 2
+  VTK_VERSION_PATCH: 6
+
+jobs:
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+        matrix:
+            os: [ubuntu-22.04]
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - name: Setup Python
+      uses: actions/setup-python@v5
+      id: setup-python
+
+    - name: Install Python dependencies
+      run: pip install --upgrade build pytest
+
+    - name: Install VTK
+      run: |
+        sudo apt-get -qq update
+        sudo apt-get -qq -y install libvtk9-dev
+
+    - name: Create install folder
+      run: |
+          mkdir -p ${{github.workspace}}/install
+
+    - name: Configure CMake
+      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/install -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DEXTERNAL_EIGEN:BOOL=OFF -DPYTHON_WRAPPER:BOOL=ON -DFORTRAN_WRAPPER:BOOL=OFF -DRUST_WRAPPER:BOOL=OFF -DUSE_VTK=ON -DBUILD_TESTING=ON
+
+    - name: Build
+      id: build
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+    - name: Install
+      run: cmake --install ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+    - name: Set env variables for the tests
+      run: |
+        echo "LD_LIBRARY_PATH=${{github.workspace}}/build/source/" >> $GITHUB_ENV
+
+    - name: Test
+      working-directory: ${{github.workspace}}/build
+      # We are just testing in Linux
+      run: ctest -C ${{env.BUILD_TYPE}} -R python-tests --output-on-failure
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 617f55ef..416ed1de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.10)
 set(MOORDYN_MAJOR_VERSION 2)
-set(MOORDYN_MINOR_VERSION 2)
-set(MOORDYN_PATCH_VERSION 2)
+set(MOORDYN_MINOR_VERSION 3)
+set(MOORDYN_PATCH_VERSION 0)
 set(MOORDYN_VERSION ${MOORDYN_MAJOR_VERSION}.${MOORDYN_MINOR_VERSION})
 project(Moordyn VERSION ${MOORDYN_VERSION})
 
diff --git a/README.md b/README.md
index 463170a8..09dabb86 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,8 @@ MoorDyn is implemented both in Fortran and in C++. The Fortran version of MoorDy
 
 Both forms of MoorDyn feature the same underlying mooring model, use the same input and output conventions, and are being updated and improved in parallel. They follow the same version numbering, with a "C" or "F" suffix for differentiation.
 
-Further information on MoorDyn can be found on the [documentation site](https://moordyn.readthedocs.io/en/latest/). MoorDyn-F is available in the [OpenFAST repository](https://github.com/OpenFAST/openfast/tree/main/modules/moordyn). MoorDyn-C is available in this repository.
+Further information on MoorDyn can be found on the [documentation site](https://moordyn.readthedocs.io/en/latest/). MoorDyn-F is available in the [OpenFAST repository](https://github.com/OpenFAST/openfast/tree/main/modules/moordyn). MoorDyn-C is available in this repository with the following three maintained branches. The master branch represents the most recent release of 
+MoorDyn-C. The dev branch contains new features currently in development. The v1 branch is the now deprecated version one of MoorDyn-C. 
 
 ## Acknowledgments
 
diff --git a/docs/compiling.rst b/docs/compiling.rst
index b7d29af4..a42e683f 100644
--- a/docs/compiling.rst
+++ b/docs/compiling.rst
@@ -16,48 +16,42 @@ OpenFAST/Fast.Farm or with its own driver), please refer to the
 MoorDyn-F in a standalone form can be compiled as moordyn_driver inside of the build folder.
 
 MoorDyn-C is available on `GitHub <https://github.com/FloatingArrayDesign/moordyn/>`_. The 
-latest version can be found in the V2 branch. MoorDyn-C v1 is currently in the master branch
+latest release can be found in the master branch. MoorDyn-C v1 is currently in the master branch
 of this repo, and it can be compiled using these :ref:`instructions <compile_simple>`. 
 
 The following sections describe how to install MoorDyn-C 
+:ref:`using the pre-compiled libraries <releases>`,
 :ref:`using the python wrapper <compile_python>`, how to 
 :ref:`compile all features <CMake_compile>`, and how to 
 :ref:`compile a simple library <compile_simple>` (version 1 method). These compile instructions
 are broken down by operating system. 
 
-.. Binaries and installers
-.. -----------------------
+Pre-Compiled Releases
+---------------------
+.. _releases:
 
-.. .. _starting_binaries:
+Pre-compiled releases can be found at the
+`releases page <https://github.com/FloatingArrayDesign/MoorDyn/releases>`_. These releases contain the 
+libraries and header files required by driver scripts. These provide the same libraries as compiling 
+as a :ref:`simple library <compile_simple>` and as compiling with :ref:`cmake <CMake_compile>`. Typically 
+if you are just looking for the compiled libraries, this is the easiest way to obtain them.
 
-.. Two different cases must be considered when installing binaries, the
-.. :ref:`C/C++ libraries <_starting_binaries_lib>` (with the Fortran wrapper), and
-.. the :ref:`Python package <_starting_binaries_python>`.
+The lastest release corresponds to the current state of the master branch of MoorDyn. The releases 
+contain the source code, as well as three executables (OS dependent) that install the libraries and 
+header files. The executables correspond to the following OS:
 
-.. C/C++ Library
-.. ^^^^^^^^^^^^^
+* Windows: Moordyn-X.Y.Z-win64.exe (with X.Y.Z replaced by the specific version)
+* Linux: Moordyn-X.Y.Z-Linux.sh 
+* MacOS: Moordyn-X.Y.Z-Darwin.sh
 
-.. .. _starting_binaries_lib:
+To install the MoorDyn libraries, download the appropriate executable for your library and execute it.  
 
-.. To install the C/C++ library (and the Fortran wrappers), please clone the V2 repository. Pre-compiled releases can be found at
-.. `Releases page <https://github.com/mattEhall/MoorDyn/releases>`_. 
-.. Along this line you would probably want to consider either the latest version
-.. identified with a number, or the one named "nightly".
-.. The former is the latest stable version, while the latter is the latest version
-.. uploaded to the repository, which tends to be a bit less stable.
+NOTE: When you download the self-extracting files for Linux and MacOS they
+cannot be launched until you give them execution permissions. This can be done by executing the following command:
 
-.. Once you already chosen a release, click on the assets and select the most
-.. appropriate one for your platform.
-.. More specifically, if you are in Windows you probably want to download and
-.. execute Moordyn-X.Y.Z-win64.exe (with X.Y.Z replaced by the specific version),
-.. in Linux you can download and execute Moordyn-X.Y.Z-Linux.sh and
-.. in MacOS you can download and execute Moordyn-X.Y.Z-Darwin.sh.
-
-.. NOTE: When you download the self-extracting files for Linux and MacOS they
-.. cannot be launched until you give them execution permissions.
-
-.. Now you can checkout
-.. :ref:`how to integrate MoorDyn in your project <_starting_using>` below.
+.. code-block:: bash
+   
+   chmod u=rwx <executable name>
 
 Compile as a Simple Library
 ---------------------------
@@ -374,9 +368,8 @@ Install as Python module
 
 .. _compile_python:
 
-MoorDyn-C is available as python module. This module is not always update with the most 
-recent version of MoorDyn-C on GitHub, so it is important to check the date of the last 
-release at https://pypi.org/project/moordyn/. To install, type
+MoorDyn-C is available as python module. This module is up to date with the master branch of
+MoorDyn-C on GitHub and can be found at https://pypi.org/project/moordyn/. To install, type
 
 .. code-block:: bash
 
@@ -385,8 +378,9 @@ release at https://pypi.org/project/moordyn/. To install, type
 in your system terminal. Pip will take care of everything by you.
 
 **If you want to use the most up to date version of MoorDyn-C as a python module**, follow the 
-instructions in the :ref:`CMake compile section <CMake_compile>`. Once you have succesfully 
-compiled MoorDyn on your system, change to `MoorDyn/build/wrappers/python/` and execute the 
-following command `pip install ./`. This will build the python module locally from 
-the source code you have installed. In order to update this module in the future you will need 
-to update your local source code and follow the same steps above.
+instructions in the :ref:`CMake compile section <CMake_compile>` using the dev branch of 
+MoorDyn-C. Once you have succesfully compiled MoorDyn on your system, change to 
+`MoorDyn/build/wrappers/python/` and execute the following command `pip install ./`. This will 
+build the python module locally from the source code you have installed. In order to update 
+this module in the future you will need to update your local source code and follow the same 
+steps above.
diff --git a/docs/conf.py b/docs/conf.py
index 9319686b..74f85d92 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -47,7 +47,7 @@
 author = 'Matt Hall'
 
 # The full version, including alpha/beta/rc tags
-release = '2.1.0'
+release = '2.2.2'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/drivers.rst b/docs/drivers.rst
index c8bcdefd..434b5fb4 100644
--- a/docs/drivers.rst
+++ b/docs/drivers.rst
@@ -22,41 +22,56 @@ MoorDyn to be run simultaneously, while MoorDyn-F v2 and MoorDyn-C/F v1 only all
 instance at a time. Any driver function will call at least the following MoorDyn 
 functions:
 
-* Create (MoorDyn-C v2 only)
-* Initialize
-* Step
+* Create - MoorDyn-C v2 only
+* Initialize (r, rd)
+* Step (r, rd, t ,dt)
 * Close
 
 The initialize function takes the state vectors at time 0 and sets up MoorDyn for a time 
-series simulation. It will also find the steady state of the system if the input file 
-calls for it. The step function takes the state vectors (r - positions and 
-rd - velocities) at a given time, the time, and the time step size. The step function 
-needs to be called for each time step in your timeseries. The close function clears up 
-memory. For both the step and the initialize functions, the input state vector size needs 
+series simulation. It will also find the steady state of the system if the input file calls for it.
+The r and rd vectors in theinitalize function represent the inital status of the system and should 
+match what is defined in the input file. 
+
+The step function takes the state vectors (r - positions and rd - time dervative of r) at a given 
+time, the time, and the time step size. The step function will integrate MoorDyn the ammount you 
+provide with dt. If dt > dtM, then MoorDyn will substep using dtM as the internal timestep to 
+integrate one dt timestep. If dt < dtM then MoorDyn will use dt as the internal timestep to 
+integrate one dt timestep. The value of rd is assumed constant for the dt timestep, as well as the 
+derived acceleration (relevant for the inertial response of coupled objects). For that reason, dt 
+should be set as small as possible to achieve a reasonable runtime.
+
+The close function clears up memory and safely destroys the MoorDyn system. 
+
+For both the step and the initialize functions, the input state vector size needs 
 to correspond to the DOF of the coupled object or objects. The input vector is 1D with a 
 length of: degree of freedom of coupled object * number of coupled objects. If you have 
 multiple different types of coupled objects then the order in the state vector is 
-body (6 DOF), rod (6 DOF for cantilevered/coupled, 3 DOF for pinned), and then 
-points (3 DOF). The same order applies for the state derivative input vector, with each 
-value being the time derivative of the respective value. The degrees of freedom are as 
-follows:
+body (6 DOF), rod (6 DOF), and then points (3 DOF). The same order applies for the state 
+derivative input vector, with each value being the time derivative of the respective value. 
+The degrees of freedom are as follows (all relative to the global reference frame):
 
  - Bodies and cantilevered/coupled Rods: cartesian positions followed by the Euler angles 
-   relative to the non-inertial reference frame. 
- - Pinned rods: Euler angles relative to the non-inertial reference frame.
- - Coupled points: cartesian positions relative to the non-inertial reference frame.  
+   relative to the global reference frame. 
+ - Pinned bodies and rods: cartesian positions relative to the global reference frame.
+ - Coupled points: cartesian positions relative to the global reference frame.  
 
-
-For example, the r vector for a coupled body and coupled point would be:
+For example, the state vector for a coupled body and coupled point would be:
 
    [ x1, y1, z1, roll1, pitch1, yaw1, x2, y2, z2 ]
 
+The state derivative vector represents the time derivate of the values in the state vector. It is multiplied by the 
+internal timestep to get the new positions of the system. For rotational degrees of freedom the state derivative is
+time derivatives of the angles. The rotation matrix in both MD-F and MD-C used to describe the 
+objects orientation is given by R = X(roll)Y(pitch)Z(yaw). In the examples below, the state vector is referred to 
+as x and the state derivative as xd.
+
 Driving MoorDyn-C v1 is a similar process as MoorDyn v2. MoorDyn-C v1 has no built in 
 couplings and needs to be driven based on the C API in the MoorDyn.h file. An example on 
 how to do this in python is provided at the end of the 
-:ref:`python section <python_wrapper>`.
+:ref:`python section <python_wrapper>`. 
 
-Note: the dt value that you give to the step function needs to be the same value that you specify as dtM in the input file. 
+Note: For coupled pinned bodies and rods the state vector still needs to be size 6, and MoorDyn will just 
+ignore the rotational values provided. 
 
 MoorDyn-C Coupling
 ------------------
@@ -85,7 +100,7 @@ control:
     system = moordyn.Create("Mooring/lines.txt")
 
     # 3 coupled points x 3 components per point = 9 DoF
-    dx = [0] * 9
+    xd = [0] * 9
     # Get the initial positions from the system itself
     x = []
     for i in range(3):
@@ -94,13 +109,13 @@ control:
         x = x + moordyn.GetPointPos(point)
 
     # Setup the initial condition
-    moordyn.Init(system, x, dx)
+    moordyn.Init(system, x, xd)
 
     # Make the points move at 0.5 m/s to the positive x direction
     for i in range(3):
-        dx[3 * i] = 0.5
+        xd[3 * i] = 0.5
     t, dt = 0.0, 0.5
-    f = moordyn.Step(system, x, dx, t, dt)
+    f = moordyn.Step(system, x, xd, t, dt)
 
     # Print the position and tension of the line nodes
     n_lines = moordyn.GetNumberLines(system)
@@ -292,8 +307,8 @@ this time developed in C:
             return 1;
 
         // 3 coupled points x 3 components per point = 9 DoF
-        double x[9], dx[9];
-        memset(dx, 0.0, sizeof(double));
+        double x[9], xd[9];
+        memset(xd, 0.0, sizeof(double));
         // Get the initial positions from the system itself
         for (unsigned int i = 0; i < 3; i++) {
             // 4 = first fairlead id
@@ -306,7 +321,7 @@ this time developed in C:
         }
 
         // Setup the initial condition
-        err = MoorDyn_Init(system, x, dx);
+        err = MoorDyn_Init(system, x, xd);
         if (err != MOORDYN_SUCCESS) {
             MoorDyn_Close(system);
             return 1;
@@ -314,10 +329,10 @@ this time developed in C:
 
         // Make the points move at 0.5 m/s to the positive x direction
         for (unsigned int i = 0; i < 3; i++)
-            dx[3 * i] = 0.5;
+            xd[3 * i] = 0.5;
         double t = 0.0, dt = 0.5;
         double f[9];
-        err = MoorDyn_Step(system, x, dx, f, &t, &dt);
+        err = MoorDyn_Step(system, x, xd, f, &t, &dt);
         if (err != MOORDYN_SUCCESS) {
             MoorDyn_Close(system);
             return 1;
@@ -575,7 +590,7 @@ the following:
 
     %% 3 coupled points x 3 components per point = 9 DoF
     x = zeros(9,1);
-    dx = zeros(9,1);
+    xd = zeros(9,1);
     %% Get the initial positions from the system itself
     for i=1:3
         %% 4 = first fairlead id
@@ -584,15 +599,15 @@ the following:
     end
 
     %% Setup the initial condition
-    MoorDynM_Init(system, x, dx);
+    MoorDynM_Init(system, x, xd);
 
     %% Make the points move at 0.5 m/s to the positive x direction
     for i=1:3
-        dx(1 + 3 * (i - 1)) = 0.5;
+        xd(1 + 3 * (i - 1)) = 0.5;
     end
     t = 0.0;
     dt = 0.5;
-    [t, f] = MoorDynM_Step(system, x, dx, t, dt);
+    [t, f] = MoorDynM_Step(system, x, xd, t, dt);
 
     %% Print the position and tension of the line nodes
     n_lines = MoorDynM_GetNumberLines(system);
diff --git a/docs/inputs.rst b/docs/inputs.rst
index 0bcef1fc..b9f82b43 100644
--- a/docs/inputs.rst
+++ b/docs/inputs.rst
@@ -75,20 +75,20 @@ Point Properties
 The Point Properties section defines the point node points which mooring lines can be connected to.
 
 .. code-block:: none
-   :emphasize-lines: 2
- 
-   ----------------------- POINT PROPERTIES ----------------------------------------------
-   Node      Type      X        Y         Z        M        V        FX       FY      FZ     CdA   CA
-   (-)       (-)      (m)      (m)       (m)      (kg)     (m^3)    (kN)     (kN)    (kN)   (m^2)  (-)
-   1         Vessel     0.0     0      -10.00       0        0        0        0       0       0     0
-   2         Fixed    267.0     0      -70.00       0        0        0        0       0       0     0
-   3         Connect    0.0     0      -10.00       0        0        0        0       0       0     0
+  :emphasize-lines: 2
+
+  ----------------------- POINT PROPERTIES ----------------------------------------------
+  Node      Type      X        Y         Z        M        V        FX       FY      FZ     CdA   CA
+  (-)       (-)      (m)      (m)       (m)      (kg)     (m^3)    (kN)     (kN)    (kN)   (m^2)  (-)
+  1         Vessel     0.0     0      -10.00       0        0        0        0       0       0     0
+  2         Fixed    267.0     0      -70.00       0        0        0        0       0       0     0
+  3         Connect    0.0     0      -10.00       0        0        0        0       0       0     0
 
 The columns are as follows:
 
  - Node –  the ID number of the point (must be sequential starting with 1)
  - Type –  one of “Fixed”, “Vessel”, or “Connect”, as described :ref:`here <points>`
- - X, Y, Z –  Coordinates of the point (relative to inertial reference frame if “fixed” or “point”, 
+ - X, Y, Z –  Coordinates of the point (relative to global reference frame if “fixed” or “point”, 
    relative to platform reference frame if “vessel”).  In the case of “point” nodes, it is simply 
    an initial guess for position before MoorDyn calculates the equilibrium initial position.(m)
  - M – node mass in the case of clump weights (kg)
@@ -104,14 +104,14 @@ Lines list
 The Line Properties section defines each uniform-property section of mooring line to be simulated.
 
 .. code-block:: none
-   :emphasize-lines: 2
+  :emphasize-lines: 2
  
-   -------------------------- LINE PROPERTIES -------------------------------------------------
-   Line     LineType  UnstrLen  NumSegs    NodeAnch  NodeFair  Flags/Outputs
-   (-)      (-)       (m)         (-)       (-)       (-)      (-)
-   1        nylon     300.0        50        2         1        p
-   2        nylon     300.0        50        4         3        p
-   3        nylon     300.0        50        6         5        p
+  -------------------------- LINE PROPERTIES -------------------------------------------------
+  Line     LineType  UnstrLen  NumSegs    NodeAnch  NodeFair  Flags/Outputs
+  (-)      (-)       (m)         (-)       (-)       (-)      (-)
+  1        nylon     300.0        50        2         1        p
+  2        nylon     300.0        50        4         3        p
+  3        nylon     300.0        50        6         5        p
 
 The columns are as follows:
 
@@ -166,7 +166,7 @@ size) often need to be set by the user for proper operation. The list of possibl
 default value provided in parentheses) is:
 
  - dtM (0.001) – desired mooring model time step (s)
- - g (9.8) – gravitational constant (m/s^2)
+ - g (9.80665) – gravitational constant (m/s^2)
  - rhoW (1025.0)– water density (kg/m^3)
  - WtrDpth (0.0) – water depth (m)
  - SeafloorFile (none) – Relative path of a 3D Seafloor file
@@ -248,6 +248,10 @@ tables begin with two preset lines that contain the column names and the
 corresponding units. These lines are followed by any number of lines containing
 the entries in that section's table of inputs.
 
+Examples of input files for MoorDyn-C can be found in the `test directory <https://github.com/FloatingArrayDesign/MoorDyn/tree/master/tests/Mooring>`_ (note that these do not include outputs becasue they are for tests).
+
+Examples for MoorDyn-F can be found in the `OpenFAST tests <https://github.com/OpenFAST/r-test/tree/main/modules/moordyn>`_. 
+
 Front matter
 ^^^^^^^^^^^^
 
@@ -284,22 +288,24 @@ The columns in order are as follows:
    desired damping ratio (in fraction of critical) for the line type (and MoorDyn will set the BA 
    of each line accordingly)
  - EI – the line bent stiffness, product of elasticity modulus and inertia of the cross-sectional 
-   area (N)
+   area (N-m^2)
  - Cd –  transverse drag coefficient (with respect to frontal area, d*l)
  - Ca –  transverse added mass coefficient (with respect to line displacement)
  - CdAx –  tangential drag coefficient (with respect to surface area, π*d*l)
  - CaAx – tangential added mass coefficient (with respect to line displacement)
 
-Non-linear values for the stiffness (EA), internal damping (BA/-zeta) and bent
-stiffness (EI) are accepted.
-For this, a file name can be provided (located in the same folder as the
-main MoorDyn input file) instead of a number.
-Such file is a tabulated file with a time column and a data column, separated by a blank space.
-The columns that should be provided for each non-linear magnitude are the following:
+Note: Non-linear values for the stiffness (EA) are an option in MoorDyn. For this, a file name can be provided instead of a number. This file 
+must be located in the same folder as the main MoorDyn input file for MoorDyn-C or for MoorDyn-F 
+in the same folder as the executable calling MoorDyn-F, unless a path is specified. Such file is a 
+tabulated file with 3 header lines and then a strain column and a tension column separated by a blank space:
 
- - Stiffness: Strain rate - EA/Stretching rate (N)
- - Internal damping: Curvature - EI/Curvature (N-m^2)
- - Bent stiffness: Stretching rate - Damping coefficient/Stretching rate (N-s^2/s)
+.. code-block:: none
+
+  ----Polyester----
+  Strain    Tension
+  (-)       (N)
+  0.0       0.0
+  ...       ...
 
 Rod Types
 ^^^^^^^^^
@@ -338,21 +344,27 @@ This section (optional) describes the 6DOF body objects to be simulated.
 The columns are as follows:
  - ID –  the ID number of the Body (must be sequential starting with 1)
  - Attachment –  one of “Fixed”, “Vessel”, “Free”, etc, as described :ref:`here <bodies>`
- - X0/Y0/Z0 – Coordinates of the body relative to the inertial reference frame. Note that bodies 
+ - X0/Y0/Z0 – Coordinates of the body relative to the global reference frame. Note that bodies 
    must have Z0 <= 0 (m)
- - r0/p0/y0 – Orientation of the body relative to the inertial reference frame in Euler angles 
+ - r0/p0/y0 – Orientation of the body relative to the global reference frame in Euler angles 
    (deg)
  - Mass – Body mass not including attached rods and points. Typically used to account for above 
    surface mass such as a turbine (kg)
  - CG – Body center of gravity. If one value given, it is along the Z axis. To specify a coordinate 
    point, the XYZ values are listed separated by | with no spaces (m)
  - I – Body moment of inertia diagonals for the 3x3 inertia matrix. If one value given, it is used 
-   for all three values. To specify different values, the inputs are listed separated by | with no 
+   for all three values. To specify three different values, the inputs are listed separated by | with no 
    spaces (kg-m^2)
  - Volume – The body displaced volume used in buoyancy calculations excluding attached rod and 
    point volume contributions (m^3)
- - CdA – The body drag coefficient
- - Ca – The body added mass coefficient
+ - CdA – The product of drag coefficient and frontal area of body. If one value given, it is used 
+   for all six values. If two values are given, the first is used for the translational drag and 
+   the second for the rotational drag. If three values are given, they are used as both the 
+   translational and rotational drag coefficicents for the respective axis. If six values are given, 
+   they are used as the drag coefficicnets in the respective degree of freedom. To specify different 
+   values, the inputs are listed separated by | with no spaces (m^2)
+ - Ca – The body added mass coefficient. If one value given, it is used for all three values. To 
+   specify three different values, the inputs are listed separated by | with no spaces
 
 Rods list
 ^^^^^^^^^
@@ -371,8 +383,10 @@ The columns are as follows:
  - ID –  the ID number of the Rod (must be sequential starting with 1)
  - RodType
  - Attachment –  one of “Fixed”, “Vessel”, “Pinned”, etc, as described :ref:`here <rods>`
- - Xa/Ya/Za – Coordinates of the A end of the relative to the inertial reference frame (m)
- - Xb/Yb/Zb – Coordinates of the A end of the relative to the inertial reference frame (m)
+ - Xa/Ya/Za – Coordinates of the A end (relative to global reference frame if “fixed/point/free”,
+   or relative to platform/body reference frame if "body#"" or “body#pinned”) (m)
+ - Xb/Yb/Zb – Coordinates of the B end (relative to global reference frame if “fixed/free”,
+   or relative to platform/body reference frame if "body#"" or “body#pinned”) (m)
  - NumSegs - how many segments the rod is discretized into (it will have NumSegs+1 nodes total, 
    including its two end nodes)
  - RodOutputs - any data to be output in a dedicated output file for the rod.
@@ -406,14 +420,14 @@ This section (optional) describes the Point objects
  (#)   (word/ID) (m)     (m)   (m)    (kg)   (mˆ3)   (m^2)  (-)
  1     Fixed      -500    0     -150    0      0       0      0
  2     Coupled    0       0     -9      0      0       0      0
- 3    Body2      0       0     1.0     0      0       0      0
+ 3     Body2      0       0     1.0     0      0       0      0
  
 The columns are as follows:
 
  - ID –  the ID number of the point (must be sequential starting with 1)
- - Attachment –  one of “Fixed”, “Vessel”, “Connect”, etc, as described :ref:`here <points>`
- - X, Y, Z –  Coordinates of the point (relative to inertial reference frame if “fixed/point/free”,
-   or relative to platform/body reference frame if “vessel” or “body#”).  In the case of 
+ - Attachment –  one of “Fixed”, “Coupled”, “Free”, etc, as described :ref:`here <points>`
+ - X, Y, Z –  Coordinates of the point (relative to global reference frame if “fixed/point/free/coupled”,
+   or relative to body reference frame if “body#”).  In the case of 
    “point/free” nodes, it is simply an initial guess for position before MoorDyn calculates the 
    equilibrium initial position.  (m)
  - Mass – node mass in the case of clump weights (kg)
@@ -468,20 +482,26 @@ For example, outputting node positions and segment tensions could be achieved by
 this last column.  These outputs will go to a dedicated output file for each line only.  For 
 sending values to the global output file, use the Outputs section instead.
 
-Failure
-^^^^^^^
+Failure (MoorDyn-F only)
+^^^^^^^^^^^^^^^^^^^^^^^^
 
-This section (optional) describes the failure conditions of the system. 
+This section (optional) describes the failure conditions of the system. Line failures can only be
+triggered at an existing point or rod end where a line/lines are attached. Failures can be 
+triggered by a time or attachment tension threshold, which ever comes first. Users can specify
+multiple failures for a given point, but duplicate failure configurations will be ignored.
+If two lines attached to a point are listed to fail (failure 1 for example), then after the failure
+the lines will remain attached to each other by a free point. In this multi line case, if any line
+reaches the tension threshold then the failure will be triggered. The theory behind failures can be
+found :ref:`here <version2>`.
 
 .. code-block:: none
 
-    ---------------------- FAILURE ----------------------
-    Node    Line(s)   FailTime   FailTen
-    ()       (,)      (s or 0)   (N or 0)
-    any      1,2,3,4     0        1200e3
-    3        1           0        1200e3
-    R1a      1,2,3       12         0
-
+  ---------------------- FAILURE ----------------------
+  FailureID  Point    Line(s)   FailTime   FailTen
+  ()           ()       (,)      (s or 0)   (N or 0)
+  1          5        1,2,3,4     0         1200e3
+  2          3        1           0         1200e3
+  3         R1a       1,2,3       12          0
 
 Control (MoorDyn-F only)
 ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -491,11 +511,11 @@ system.
 
 .. code-block:: none
 
-   ---------------------- CONTROL ----------------------
-   ChannelID  Line(s)
-   ()       (,)     
-   1        1,2,3,4 
-   2        5
+  ---------------------- CONTROL ----------------------
+  ChannelID  Line(s)
+  ()       (,)     
+  1        1,2,3,4 
+  2        5
 
 Options
 ^^^^^^^
@@ -520,7 +540,7 @@ size) often needs to be set by the user for proper operation. The list of possib
  - writeLog (0 C, -1 F): If >0 a log file is written recording information. The bigger the number 
    the more verbose. Please, be mindful that big values would critically reduce the performance!
  - dtM (0.001 C): The time step (s). In MoorDyn-F if this is left blank it defaults to the 
-   :ref:`driver file <MDF_driver_in>` dtC value.  
+   :ref:`driver file <MDF_driver_in>` dtC value or the OpenFAST time step.   
  - tScheme (RK2): The time integrator. It should be one of Euler, Heun, RK2, RK4, AB2, AB3, AB4, 
    BEuler2, BEuler3, BEuler4, BEuler5, Midpoint2, Midpoint3, Midpoint4, Midpoint5. RK stands for 
    Runge-Kutta while AB stands for Adams-Bashforth
@@ -529,8 +549,8 @@ size) often needs to be set by the user for proper operation. The list of possib
  - WtrDpth (0.0): The water depth (m). In MoorDyn-F the bathymetry file path can be inputted here.
  - kBot (3.0e6): The bottom stiffness (Pa/m)
  - cBot (3.0e5): The bottom damping (Pa-s/m)
- - dtIC (1.0 C, 2.0 F): The time lapse between convergency checks during the initial condition 
-   computation (s)
+ - dtIC (1.0 C, 2.0 F): The threshold amount of time the system must be converged for to be 
+   considered stationary (s)
  - TmaxIC (120.0 C, 60.0 F): The maximum simulation time to run in order to find a stationary 
    initial condition (s)
  - CdScaleIC (5.0 C, 4.0 F): The damping scale factor during the initial condition computation
@@ -586,6 +606,9 @@ The following options from MoorDyn-F are not supported by MoorDyn-C:
  - MU_KA (0.0): Axial line coefficient of friction.
  - MC (1.0): Same as StatDynFricScale in MoorDyn-C.
  - CV (200.0): Same as FricDamp in MoorDyn-C.
+ - inertialF (0): Toggle to include inertial components in the returned forces from coupled 
+   bodies and rods. Transients in the acceleration passed into MoorDy-F by OpenFAST can result 
+   in large non-physical forces and moments which can cause instability in the model [0: on, 1: off]
 
 Outputs
 ^^^^^^^
@@ -600,8 +623,10 @@ shown below) for MoorDyn to know when to stop reading inputs.
  BODY1PX
  BODY1PY
  BODY1PZ
- BODY1ROLL
- BODY1PITCH
+ BODY1RX
+ BODY1RY
+ LINE1N15FX
+ LINE1NAPZ
  FAIRTEN1
  FAIRTEN2
  FAIRTEN3
@@ -611,7 +636,6 @@ shown below) for MoorDyn to know when to stop reading inputs.
  END
  ------------------------- need this line -------------------------------------
 
-
 The avaible output flags are decribed in the table below:
 
 ========= ============================ =========  ===========  ==============  ===========  ===========
@@ -634,9 +658,12 @@ SUB       Submergence (frac of length) [frac]                  Object
 ========= ============================ =========  ===========  ==============  ===========  ===========
 
 When a node number is specified, the output pertains to that node and its kinematics or associated 
-loads. When no node number is specified, the output pertains to the object as a whole and the 
-values are of the object’s reference point (about the reference point for rotations). Reference 
-Points:
+loads. In the output flag this node numer is given as [OBJECT#]N#[SUFFIX], i.e LINE1N15PX. When no 
+node number is specified, the output pertains to the object as a whole and the values are of the 
+object’s reference point (about the reference point for rotations). The output flag without a node 
+number looks like [OBJECT#][SUFFIX], i.e. ROD1SUB.
+
+Reference Points:
 
 -	Rods: End A (Node 0)
   - No z rotations for rods (rotations along axis of rod negligible)
@@ -652,6 +679,10 @@ Footnotes:
 - The tension on the Line n anchor can be output with the ANCHTEN[n] flag (see examples above)
 - Object indicates output is for whole object, Node indicates output is for node of object
 - Coupled/fixed bodies and points will output acceleration 0 because no forces are calculated
+- When looking at the rotational outputs of coupled pinned rods that are hanging near vertical, 
+  the best approach is to attach a rod to a zero-mass, zero-volume pinned body and output the body 
+  rotations. Hanging pinned rods are inverted (end A over end B) in MoorDyn and the output range 
+  for roll/pitch of rods is +/- 180 degrees. 
 - There are a couple additional outputs left over from OpenFAST conventions that don’t follow the 
   same format: FairTen and AnchTen. FairTen[n] is the same as Line[n]TenB. For example, the 
   fairlead tension of line 1 would be FAIRTEN1 or LINE1TENB.
@@ -683,42 +714,55 @@ one space.
 
  MoorDyn driver input file 
  another comment line
- FALSE               Echo             - Echo the input file data (flag) 
  ---------------------- ENVIRONMENTAL CONDITIONS ------------------------------- 
- 9.80665            Gravity          - Gravity (m/s^2) 
- 1025.0             rhoW             - Water density (kg/m^3) 
- 75.0              WtrDpth          - Water depth (m) 
+ 9.80665                 Gravity          - Gravity (m/s^2) 
+ 1025.0                  rhoW             - Water density (kg/m^3) 
+ 75.0                    WtrDpth          - Water depth (m) 
  ---------------------- MOORDYN ------------------------------------------------ 
- "<insert input file path>"	MDInputFile      - Primary MoorDyn input file name 
- (quoted string) 
- "Mooring/F"            OutRootName      - The name which prefixes all MoorDyn
- generated files (quoted string) 
- 10.0                  TMax             - Number of time steps in the simulations (-) 
- 0.001                 dtC              - TimeInterval for the simulation (sec) 
- 0                   InputsMode       - MoorDyn coupled object inputs (0: all inputs
- are zero for every timestep, 1: time-series inputs) (switch) 
- "PtfmMotions.dat" InputsFile       - Filename for the MoorDyn inputs file for when 
- InputsMod = 1 (quoted string) 
- 0                   NumTurbines      - Number of wind turbines (-) [>=1 to use
- FAST.Farm mode. 0 to use OpenFAST mode.] 
+ "<input file path>"     MDInputFile      - Primary MoorDyn input file name (quoted string) 
+ "Mooring/F"             OutRootName      - The name which prefixes all MoorDyn generated files (quoted string) 
+ 10.0                    TMax             - Number of time steps in the simulations (-) 
+ 0.001                   dtC              - TimeInterval for the simulation (sec) 
+ 0                       InputsMode       - MoorDyn coupled object inputs (0: all inputs are zero for every timestep, 1: time-series inputs) (switch) 
+ "PtfmMotions.dat"       InputsFile       - Filename for the MoorDyn inputs file for when InputsMod = 1 (quoted string) 
+ 0                       NumTurbines      - Number of wind turbines (-) [>=1 to use FAST.Farm mode. 0 to use OpenFAST mode] 
  ---------------------- Initial Positions -------------------------------------- 
  ref_X    ref_Y    surge_init   sway_init  heave_init  roll_init  pitch_init  yaw_init 
  (m)      (m)        (m)          (m)        (m)       (rad)       (rad)        (rad)
- [followed by MAX(1,NumTurbines) rows of data] 
- 0         0         0.0          0.0        0.0        0.0         0.0          0.0   
+ 0         0         0.0          0.0        0.0        0.0         0.0          0.0  
+ <followed by MAX(1,NumTurbines) rows of data>  
  END of driver input file
 
-If InputsMode is set to 1, MoorDyn-F will require a platform motions time series dataset of the 
-coupled object movements. For a single coupled body, the order of the data columns would look like 
-the following (the column header names are not read by MoorDyn):
+If InputsMode is set to 1, the MoorDyn-F driver will require a platform motions time series dataset of the 
+coupled object movements. The time units are seconds, the translational position units are meters, 
+and the orentation units are radians. For a single coupled body, the order of the data columns 
+would look like the following (lines beginning with # are treated as comments by MoorDyn):
 
 .. code-block:: none
 
- Time    PtfmSurge    PtfmSway    PtfmHeave    PtfmRoll    PtfmPitch    PtfmYaw
+ # Time(s)    PtfmSurge(m)    PtfmSway(m)    PtfmHeave(m)    PtfmRoll(rad)    PtfmPitch(rad)    PtfmYaw(rad)
 
 If there are multiple coupled objects then the general order of columns beyond the time column 
 follows the order of the state vector: Body degrees of freedom, rod degrees of freedom, and points 
-degrees of freedom.
+degrees of freedom. For coupled pinned bodies and rods the full 6DOF need to be provided, however the rotational 
+values will be ignored by by the MoorDyn-F driver (they can be set to zero).
+
+When using the MoorDyn driver in OpenFAST mode, the inital positions represents the offsets to the 
+global frame. When using OpenFAST mode with the positions set to 0's, then MoorDyn objects will be 
+simulated based on the positions defined in the MoorDyn input file. If a non-zero value is provided,
+it will be incorporated into the inital positions of coupled objects. For example, if the following 
+inital positions are given:
+
+.. code-block:: none
+  
+ ---------------------- Initial Positions -------------------------------------- 
+ ref_X    ref_Y    surge_init   sway_init  heave_init  roll_init  pitch_init  yaw_init 
+ (m)      (m)        (m)          (m)        (m)       (rad)       (rad)        (rad)
+ 0         0         10.0         0.0        0.0        0.0        20.0          0.0  
+
+Then a coupled body with a inital state defined in the input file as [0, 0, 0, 0, 0, 0]
+will have an inital state of [10, 0, 0, 20, 0, 0]. It is advised that for using the MoorDyn driver
+in OpenFAST mode that the Inital Positions are set to 0 unless the user has a reason to do otherwise.
 
 Seafloor/Bathymetry File 
 ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -791,38 +835,33 @@ Water Kinematics file (MoorDyn-F)
 .. _MDF_wtrkin:
 
 The file provided to MoorDyn-F for water Kinematics should have the following format, which 
-specifies the inputted waves and current. Details on this format can be found in the 
-:ref:`water kinematics section <waterkinematics>`.
+specifies the inputted waves and current. MoorDyn-F can handle a maximum of 100 lines of current
+data. Details on this format can be found in the :ref:`water kinematics section <waterkinematics>`.
 
 .. code-block:: none
 
  MoorDyn Waves and Currents input file
  ...any notes here...
  --------------------------- WAVES -------------------------------------
- 3                    WaveKinMod  - type of wave input {0 no waves; 3 set up grid of
- wave data based on time series} 
- "waveelev.dat"       WaveKinFile - file containing wave elevation time series at 
- 0,0,0
- 0.5                  dtWave      - time step to use in setting up wave kinematics  
- grid (s)
+ 3                    WaveKinMod  - type of wave input {0 no waves; 3 set up grid of wave data based on time series} 
+ "waveelev.dat"       WaveKinFile - file containing wave elevation time series at 0,0,0
+ 0.5                  dtWave      - time step to use in setting up wave kinematics grid (s)
  0                    WaveDir     - wave heading (deg)
- 1    - X wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
- -24, -18,-14,-10, -6,-2, 0, 4,  8,  12,  16,  20,  24,  28, 32, 36, 40, 45, 50, 55, 
- 60, 65, 70, 90, 120, 150   - X wave grid point data
- 1    - Y wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
- 0   - Y wave grid point data
- 1    - Z wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
- -600,-400,-200,-100,-40, -35,-30, -25, -20, -17,-16,-15, -14,-13,-12, -11,-10,-9, -
- 8,-7,-6, -5,-4, -3,-2, -1, 0  - Z wave grid point data
+ 2                                - X wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
+ -24, 150, 100                    - X wave grid point data
+ 2                                - Y wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
+ -100, 100, 5                     - Y wave grid point data
+ 2                                - Z wave input type (0: not used; 1: list values in ascending order; 2: uniform specified by -xlim, xlim, num)
+ -600, 0, 60                      - Z wave grid point data
  --------------------------- CURRENT -------------------------------------
  1                    CurrentMod  - type of current input {0 no current; 1 steady current profile described below} 
  z-depth     x-current      y-current
  (m)           (m/s)         (m/s)
  0.0             0.9          0.0
  150             0.5          0.0
- 1000             0.25         0.0
- 1500		0.2	      0.0
- 5000            0.15	      0.0
+ 1000            0.25         0.0
+ 1500		         0.2	        0.0
+ 5000            0.15	        0.0
  --------------------- need this line ------------------
 
 MoorDyn with FAST.Farm - Inputs
@@ -867,7 +906,7 @@ which turbine they attach to.
  2         Fixed     267.0    80      -70.00      0        0        0     0
  -------------------------- LINE PROPERTIES ----------------------------------
  2     NLines - the number of lines
- Line     LineType  UnstrLen   NumSegs    NodeA     NodeB  Flags/Outputs
+ Line    LineType  UnstrLen   NumSegs    NodeA     NodeB  Flags/Outputs
  (-)      (-)        (m)        (-)       (-)       (-)      (-)
  1     sharedchain  300.0        20        1         2        p
  2     anchorchain  300.0        20        1         3        p
diff --git a/docs/structure.rst b/docs/structure.rst
index d04bbb32..d4c14b3e 100644
--- a/docs/structure.rst
+++ b/docs/structure.rst
@@ -144,12 +144,13 @@ or to each other. (In MAP and older versions of MoorDyn, these objects were call
 Nodes). A Point has three degrees of freedom and can have any number of Lines attached to it. There 
 are three types of Points:
 
-- **Fixed/Anchor/Body**: their location is fixed to an XYZ coordinate (stationary) or a Body object. 
-  They can be used as anchor points or to attach mooring Lines to a Body.
-- **Coupled/Fairlead/Vessel**: they move under the control of the driver program/script.  
+- **Fixed/Anchor/Body**: their location is fixed to an XYZ coordinate (stationary) or a XYZ 
+  coordinate in the Body reference frame. They can be used as anchor points or to attach mooring 
+  lines to a Body.
+- **Coupled/Vessel**: they move under the control of the driver program/script.  
   They can be used as fairlead points when the platform is modeled externally.
   See :ref:`the coupling documentation <coupling>`.
-- **Free/Point/Connect**: they are free to move according to the forces acting on them, which 
+- **Free/Point**: they are free to move according to the forces acting on them, which 
   includes the tensions of attached lines as well as their own self weight and buoyancy, if 
   applicable.  
 
@@ -225,15 +226,15 @@ Rods can have 6, 3, or 0 DOF.
 
 - **Fixed**: Rods are full constrained, and their movement is defined by that of a body, the ground, 
   or a coupling point (0 DOF). 
-- **Pinned**: Rods are attached at end A to something else, whether that is a body, the ground, or 
-  a coupling point (3 DOF, all rotational). This type of Rod only has three rotational degrees of 
-  freedom, about end A.
 - **Coupled**: They move under the control of the calling program/script (constrained in all 6 DOF).
   See :ref:`the coupling documentation <coupling>`.
 - **Free**: Rods are unconstrained to move in all 6 DOF.  
+- **Pinned**: The rod end A translational movement is fixed while the rotational movement about end A is free.
+- **Coupled pinned**: The rod end A translational movement is coupled while the rotational movement about end A is free.
 
 Pinned or Fixed Rods attached to a body (e.g. body 1) are labelled "Body1Pinned" or "Body1". 
-Pinned or fixed rods that serve as a coupling point are labelled "CoupledPinned" or "Coupled"
+Rods attached to bodies have their coordinates defined in the body reference frame
+Pinned or fixed rods that serve as a coupling point are labelled "CoupledPinned" or "Coupled".
 
 A special case exists if a Rod is specified with zero elements: in that case it is given zero 
 length, and its end B coordinates in the input file are instead interpreted as vector components to 
@@ -345,9 +346,10 @@ properties are as follows:
 There are three possible types 
 for bodies:
 
-- **Coupled**: the body kinematics are controlled by the calling program.
 - **Free**: the body kinematics are computed by MoorDyn based on the applied loads and inertia
 - **Fixed**: the body is fixed, either to a particular location or to a connected, moving entity.
+- **Coupled**: the body kinematics are controlled by the calling program.
+- **Coupled pinned**: The body's translational movement is coupled while the rotational movement is free.
 
 Body objects have the following variables:
 
diff --git a/docs/theory.rst b/docs/theory.rst
index 1d3af3c5..e5fef507 100644
--- a/docs/theory.rst
+++ b/docs/theory.rst
@@ -129,5 +129,6 @@ Quaternion references:
 4. https://en.wikipedia.org/wiki/Quaternion#Hamilton_product
 
 MoorDyn-C Packages used:
-	Eigen: https://eigen.tuxfamily.org 
-	Catch2: https://github.com/catchorg/Catch2 
+ - Eigen: https://eigen.tuxfamily.org 
+ - Catch2: https://github.com/catchorg/Catch2
+ - KISSFFT: https://github.com/mborgerding/kissfft
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index 9b3c212a..b06e8e59 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -70,10 +70,10 @@ time step is a constant of the whole system, and thus should be selected
 considering the minimum natural period of all lines.
 
 Python errors
--------------
+^^^^^^^^^^^^^
 
 ModuleNotFoundError: No module named 'moordyn'
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+----------------------------------------------
 
 If you try to import MoorDyn into your Python file or shell and you receive the error 
 above, then the Python wrapper is not properly installed. First, if you compiled MoorDyn 
@@ -85,7 +85,7 @@ wrong version or you upgraded your Python installation. Either way, installing
 the MoorDyn Python wrapper again should fix the problem.
 
 ImportError
-^^^^^^^^^^^
+-----------
 The import error can show up in a number of ways when importing the MoorDyn module into 
 Python:
  
diff --git a/source/Body.cpp b/source/Body.cpp
index bcf52fb6..ffce1601 100644
--- a/source/Body.cpp
+++ b/source/Body.cpp
@@ -67,12 +67,15 @@ Body::setup(int number_in,
 	type = type_in;
 	outfile = outfile_pointer.get(); // make outfile point to the right place
 
+	// We better store the body initial position in all cases so the state is
+	// correctly initialized, and MoorDyn_GetBodyState() would be successfully
+	// used before calling MoorDyn_Init()
+	body_r6.head<3>() = r6_in.head<3>();
+	body_r6.tail<3>() = deg2rad * r6_in.tail<3>();
+
 	if (type == FREE) {
 		bodyM = M_in;
 		bodyV = V_in;
-
-		body_r6.head<3>() = r6_in.head<3>();
-		body_r6.tail<3>() = deg2rad * r6_in.tail<3>();
 		body_rCG = rCG_in;
 		bodyI = I_in;
 		bodyCdA = CdA_in;
@@ -80,17 +83,20 @@ Body::setup(int number_in,
 	} else if (type == FIXED){ // fixed bodies have no need for these variables other than position...
 		bodyM = 0.0;
 		bodyV = 0.0;
-		body_r6.head<3>() = r6_in.head<3>();
-		body_r6.tail<3>() = deg2rad * r6_in.tail<3>();
 		bodyI = vec::Zero();
 		bodyCdA = vec6::Zero();
 		bodyCa = vec6::Zero();
+	} else if (type == CPLDPIN){
+		bodyM = M_in;
+		bodyV = V_in;
+		body_rCG = rCG_in;
+		bodyI = I_in;
+		bodyCdA = CdA_in;
+		bodyCa = Ca_in;
     } else // coupled bodies have no need for these variables...
 	{
 		bodyM = 0.0;
 		bodyV = 0.0;
-
-		body_r6 = vec6::Zero();
 		body_rCG = vec::Zero();
 		bodyI = vec::Zero();
 		bodyCdA = vec6::Zero();
@@ -158,13 +164,13 @@ Body::addRod(Rod* rod, vec6 coords)
 };
 
 void
-Body::initializeUnfreeBody(vec6 r6_in, vec6 v6_in)
+Body::initializeUnfreeBody(vec6 r6_in, vec6 v6_in, vec6 a6_in)
 {
 	if (type == FREE) {
 		LOGERR << "Invalid initializator for a FREE body" << endl;
 		throw moordyn::invalid_value_error("Invalid body type");
 	}
-	initiateStep(r6_in, v6_in);
+	initiateStep(r6_in, v6_in, a6_in);
 	updateFairlead(0.0);
 
 	// If any Rod is fixed to the body (not pinned), initialize it now because
@@ -181,7 +187,7 @@ Body::initializeUnfreeBody(vec6 r6_in, vec6 v6_in)
 std::pair<XYZQuat, vec6>
 Body::initialize()
 {
-	if (type != FREE) {
+	if ((type != FREE) && (type != CPLDPIN)) {
 		LOGERR << "Invalid initializator for a non FREE body ("
 		       << TypeName(type) << ")" << endl;
 		throw moordyn::invalid_value_error("Invalid body type");
@@ -191,15 +197,17 @@ Body::initialize()
 	// initialized)
 	setDependentStates();
 
-	// If any Rod is fixed to the body (not pinned), initialize it now because
-	// otherwise it won't be initialized
-	for (auto attached : attachedR)
-		if (attached->type == Rod::FIXED)
+	if (type == FREE) { // Attached objects already initalized for coupled pinned body
+		// If any Rod is fixed to the body (not pinned), initialize it now because
+		// otherwise it won't be initialized
+		for (auto attached : attachedR)
+			if (attached->type == Rod::FIXED)
+				attached->initialize();
+		// If there's an attached Point, initialize it now because it won't be
+		// initialized otherwise
+		for (auto attached : attachedP)
 			attached->initialize();
-	// If there's an attached Point, initialize it now because it won't be
-	// initialized otherwise
-	for (auto attached : attachedP)
-		attached->initialize();
+	}
 
 	// create output file for writing output (and write channel header and units
 	// lines) if applicable
@@ -313,7 +321,13 @@ real
 Body::GetBodyOutput(OutChanProps outChan)
 {
 
-	vec3 rotations = rad2deg * Quat2Euler(r7.quat);
+	vec3 rotations;
+	vec6 Fout;
+
+	if ((outChan.QType == RX)||(outChan.QType == RY)||(outChan.QType == RZ))
+		rotations = rad2deg * Quat2Euler(r7.quat);
+	if ((outChan.QType == FX)||(outChan.QType == FY)||(outChan.QType == FZ)||(outChan.QType == MX)||(outChan.QType == MY)||(outChan.QType == MZ))
+		Fout = getFnet();
 
 	if (outChan.QType == PosX)
 		return r7.pos.x();
@@ -355,17 +369,17 @@ Body::GetBodyOutput(OutChanProps outChan)
 		return sqrt(F6net[0] * F6net[0] + F6net[1] * F6net[1] +
 		            F6net[2] * F6net[2]);
 	else if (outChan.QType == FX)
-		return F6net[0];
+		return Fout[0];
 	else if (outChan.QType == FY)
-		return F6net[1];
+		return Fout[1];
 	else if (outChan.QType == FZ)
-		return F6net[2];
+		return Fout[2];
 	else if (outChan.QType == MX)
-		return F6net[3];
+		return Fout[3];
 	else if (outChan.QType == MY)
-		return F6net[4];
+		return Fout[4];
 	else if (outChan.QType == MZ)
-		return F6net[5];
+		return Fout[5];
 	else {
 		LOGWRN << "Unrecognized output channel " << outChan.QType << endl;
 		return 0.0;
@@ -375,12 +389,20 @@ Body::GetBodyOutput(OutChanProps outChan)
 // called at the beginning of each coupling step to update the boundary
 // conditions (body kinematics) for the proceeding time steps
 void
-Body::initiateStep(vec6 r, vec6 rd)
+Body::initiateStep(vec6 r, vec6 rd, vec6 rdd)
 {
 	if (type == COUPLED) // if coupled, update boundary conditions
 	{
 		r_ves = r;
 		rd_ves = rd;
+		rdd_ves = rdd;
+		return;
+	}
+	if (type == CPLDPIN) // if coupled, update boundary conditions
+	{
+		r_ves(Eigen::seqN(0, 3)) = r(Eigen::seqN(0, 3));
+		rd_ves(Eigen::seqN(0, 3)) = rd(Eigen::seqN(0, 3));
+		rdd_ves(Eigen::seqN(0, 3)) = rdd(Eigen::seqN(0, 3));
 		return;
 	}
 	if (type == FIXED) // if fixed body, set the BCs to stationary
@@ -388,6 +410,7 @@ Body::initiateStep(vec6 r, vec6 rd)
 		if (bodyId == 0) r_ves = vec6::Zero(); // special ground body case
 		else r_ves = r;
 		rd_ves = vec6::Zero();
+		rdd_ves = vec6::Zero();
 		return;
 	}
 	LOGERR << "Body " << number << "is not of type COUPLED or FIXED." << endl;
@@ -402,6 +425,7 @@ Body::updateFairlead(real time)
 		// set Body kinematics based on BCs (linear model for now)
 		r7 = XYZQuat::fromVec6(r_ves + rd_ves * time);
 		v6 = rd_ves;
+		a6 = rdd_ves;
 
 		// calculate orientation matrix based on latest angles
 		OrMat = r7.quat.toRotationMatrix();
@@ -411,6 +435,14 @@ Body::updateFairlead(real time)
 
 		return;
 	}
+	if (type == CPLDPIN) {
+		// set Body translational kinematics based on BCs (linear model for now)
+		r7.pos = r_ves(Eigen::seqN(0, 3)) + rd_ves(Eigen::seqN(0, 3)) * time;
+		v6(Eigen::seqN(0, 3)) = rd_ves(Eigen::seqN(0, 3));
+		a6(Eigen::seqN(0, 3)) = rdd_ves(Eigen::seqN(0, 3));
+
+		return;
+	}
 	LOGERR << "The body is not a coupled/fixed one" << endl;
 	throw moordyn::invalid_value_error("Invalid body type");
 }
@@ -418,9 +450,17 @@ Body::updateFairlead(real time)
 void
 Body::setState(XYZQuat pos, vec6 vel)
 {
-	// set position and velocity vectors from state vector
-	r7 = pos;
-	v6 = vel;
+	if (type == FREE) {
+		// set position and velocity vectors from state vector
+		r7 = pos;
+		v6 = vel;
+	} else if (type == CPLDPIN) {
+		r7.quat = pos.quat;
+		v6(Eigen::seqN(3, 3)) = vel(Eigen::seqN(3, 3));
+	} else {
+		LOGERR << "Invalid body type: " << TypeName(type) << endl;
+		throw moordyn::invalid_value_error("Invalid body type");
+	}
 
 	// calculate orientation matrix based on latest angles
 	OrMat = r7.quat.toRotationMatrix();
@@ -432,26 +472,65 @@ Body::setState(XYZQuat pos, vec6 vel)
 std::pair<XYZQuat, vec6>
 Body::getStateDeriv()
 {
-	if (type != FREE) {
-		LOGERR << "The body is not a free one" << endl;
+	if ((type != FREE) && (type != CPLDPIN)) {
+		LOGERR << "getStateDeriv called for non-free body" << endl;
 		throw moordyn::invalid_value_error("Invalid body type");
 	}
 
 	// Get contributions from attached points (and lines attached to
 	// them) and store in FNet:
 	doRHS();
-
-	// solve for accelerations in [M]{a}={f}
-	a6 = solveMat6(M, F6net);
-
-	// NOTE; is the above still valid even though it includes rotational DOFs?
-	dPos.pos = v6.head<3>();
-	// this assumes that the angular velocity is about the global coordinates
-	// which is true for bodies
-	dPos.quat = 0.5 * (quaternion(0.0, v6[3], v6[4], v6[5]) * r7.quat).coeffs();
+	if (type == FREE){
+		// solve for accelerations in [M]{a}={f}
+		a6 = solveMat6(M, F6net);
+
+		// NOTE; is the above still valid even though it includes rotational DOFs?
+		dPos.pos = v6.head<3>();
+		// this assumes that the angular velocity is about the global coordinates
+		// which is true for bodies
+		dPos.quat = 0.5 * (quaternion(0.0, v6[3], v6[4], v6[5]) * r7.quat).coeffs();
+	} else { // Pinned body
+
+		// account for moment in response to acceleration due to inertial coupling (off-diagonal sub-matrix terms)
+		const vec Fnet_out3 = F6net(Eigen::seqN(3, 3)) - (M.bottomLeftCorner<3,3>() * a6(Eigen::seqN(0, 3)));
+
+		// For small systems it is usually faster to compute the inverse
+		// of the matrix. See
+		// https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html
+		const mat M_out3 = M(Eigen::seqN(3, 3), Eigen::seqN(3, 3));
+		a6(Eigen::seqN(3, 3)) = M_out3.inverse() * Fnet_out3;
+
+		// dxdt = V   (velocities)
+		dPos.pos = vec::Zero();
+		dPos.quat = 0.5 * (quaternion(0.0, v6[3], v6[4], v6[5]) * r7.quat).coeffs();
+	}
 	return std::make_pair(dPos, a6);
 };
 
+const vec6
+Body::getFnet() const
+{
+	vec6 F6_iner = vec6::Zero();
+	vec6 Fnet_out = vec6::Zero();
+
+	// this assumes doRHS() has already been called
+	if (type == COUPLED) { // if coupled rigidly
+		F6_iner = - M * a6; // Inertial terms
+		Fnet_out = F6net + F6_iner;
+	} else if (type == CPLDPIN) { // if coupled pinned
+		F6_iner(Eigen::seqN(0,3)) = (- M.topLeftCorner<3,3>() * a6(Eigen::seqN(0,3))) - (M.topRightCorner<3,3>() * a6(Eigen::seqN(3,3))); // Inertial term
+		Fnet_out(Eigen::seqN(0,3)) = F6net(Eigen::seqN(0,3)) + F6_iner(Eigen::seqN(0,3));
+		Fnet_out(Eigen::seqN(3,3)) = vec3::Zero();
+	} else {
+		// LOGERR << "Invalid body type: " << TypeName(type) << endl;
+		// throw moordyn::invalid_value_error("Invalid body type");
+
+		Fnet_out = F6net;
+	}
+
+	return Fnet_out;
+}
+
 //  this is the big function that calculates the forces on the body
 void
 Body::doRHS()
@@ -495,10 +574,10 @@ Body::doRHS()
 
 	// Rotational DOFs drag coefficients are also defined on bodyCdA
 	vec6 cda;
-	cda(Eigen::seqN(0, 3)) = OrMat.transpose() * bodyCdA.head<3>();
-	cda(Eigen::seqN(3, 3)) = OrMat.transpose() * bodyCdA.tail<3>();
+	cda(Eigen::seqN(0, 3)) = (OrMat * bodyCdA.head<3>().asDiagonal() * OrMat.transpose()) * vi.head<3>() * vi.head<3>().norm();
+	cda(Eigen::seqN(3, 3)) = (OrMat * bodyCdA.tail<3>().asDiagonal() * OrMat.transpose()) * vi.tail<3>() * vi.tail<3>().norm();
 	F6net +=
-	    0.5 * env->rho_w * vi.cwiseProduct(vi.cwiseAbs()).cwiseProduct(cda);
+	    0.5 * env->rho_w * cda;
 
 	// Get contributions from any points attached to the body
 	for (auto attached : attachedP) {
@@ -748,6 +827,60 @@ MoorDyn_GetBodyState(MoorDynBody b, double r[6], double rd[6])
 	return MOORDYN_SUCCESS;
 }
 
+int DECLDIR
+MoorDyn_GetBodyPos(MoorDynBody b, double r[3])
+{
+	CHECK_BODY(b);
+	moordyn::vec pos = ((moordyn::Body*)b)->getPosition();
+	moordyn::vec2array(pos, r);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetBodyAngle(MoorDynBody b, double r[3])
+{
+	CHECK_BODY(b);
+	moordyn::vec pos = ((moordyn::Body*)b)->getAngles();
+	moordyn::vec2array(pos, r);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetBodyVel(MoorDynBody b, double rd[3])
+{
+	CHECK_BODY(b);
+	moordyn::vec vel = ((moordyn::Body*)b)->getVelocity();
+	moordyn::vec2array(vel, rd);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetBodyAngVel(MoorDynBody b, double rd[3])
+{
+	CHECK_BODY(b);
+	moordyn::vec vel = ((moordyn::Body*)b)->getAngularVelocity();
+	moordyn::vec2array(vel, rd);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetBodyForce(MoorDynBody b, double f[6])
+{
+	CHECK_BODY(b);
+	moordyn::vec6 force = ((moordyn::Body*)b)->getFnet();
+	moordyn::vec62array(force, f);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetBodyM(MoorDynBody b, double m[6][6])
+{
+	CHECK_BODY(b);
+	moordyn::mat6 mass = ((moordyn::Body*)b)->getM();
+	moordyn::mat62array(mass, m);
+	return MOORDYN_SUCCESS;
+}
+
 int DECLDIR
 MoorDyn_SaveBodyVTK(MoorDynBody b, const char* filename)
 {
diff --git a/source/Body.h b/source/Body.h
index 042713c5..5679553f 100644
--- a/source/Body.h
+++ b/source/Body.h
@@ -73,6 +73,54 @@ extern "C"
 	 */
 	int DECLDIR MoorDyn_GetBodyState(MoorDynBody b, double r[6], double rd[6]);
 
+	/** @brief Get the body position
+	 * @param b The Moordyn body
+	 * @param r The output position (3dof)
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyPos(MoorDynBody b, double r[3]);
+
+	/** @brief Get the body angle
+	 * @param b The Moordyn body
+	 * @param r The output angles (3dof)
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyAngle(MoorDynBody b, double r[3]);
+
+	/** @brief Get the body velocity
+	 * @param b The Moordyn body
+	 * @param rd The output velocity (3dof)
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyVel(MoorDynBody b, double rd[3]);
+
+	/** @brief Get the body angular velocity
+	 * @param b The Moordyn body
+	 * @param rd The output angular velocity (3dof)
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyAngVel(MoorDynBody b, double rd[3]);
+
+	/** @brief Get the body angular velocity
+	 * @param b The Moordyn body
+	 * @param rd The output angular velocity (3dof)
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyForce(MoorDynBody b, double f[6]);
+
+	/** @brief Get the body mass and intertia matrix
+	 * @param b The Moordyn body
+	 * @param m The output mass matrix
+	 * @return MOORDYN_INVALID_VALUE if a NULL body is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 */
+	int DECLDIR MoorDyn_GetBodyM(MoorDynBody b, double m[6][6]);
+
 	/** @brief Save the point to a VTK (.vtp) file
 	 * @param b The Moordyn body
 	 * @param filename The output maximum tension module
diff --git a/source/Body.hpp b/source/Body.hpp
index 3d095ac4..d7c5e3a0 100644
--- a/source/Body.hpp
+++ b/source/Body.hpp
@@ -114,7 +114,7 @@ class Body final : public io::IO
 	real bodyV;
 	/// The inertia diagonal components
 	vec bodyI;
-	/// The drag coefficients
+	/// The product of the drag coefficient and the frontal area (m^2)
 	vec6 bodyCdA;
 	/// The added mass coefficients
 	vec6 bodyCa;
@@ -134,6 +134,8 @@ class Body final : public io::IO
 	vec6 r_ves;
 	/// fairlead velocity for coupled bodies [x/y/z]
 	vec6 rd_ves;
+	/// fairlead acceleration for coupled bodies [x/y/z]
+	vec6 rdd_ves;
 
 	/// total force and moment vector on body
 	vec6 F6net;
@@ -161,6 +163,8 @@ class Body final : public io::IO
 		FREE = 0,
 		/// Is fixed, either to a location or to another moving entity
 		FIXED = 1,
+		/// Is coupled pinned, i.e. translational dof are controlled by the user
+		CPLDPIN = 2,
 		// Some aliases
 		VESSEL = COUPLED,
 		ANCHOR = FIXED,
@@ -175,6 +179,8 @@ class Body final : public io::IO
 		switch (t) {
 			case COUPLED:
 				return "COUPLED";
+			case CPLDPIN:
+				return "CPLDPIN";	
 			case FREE:
 				return "FREE";
 			case FIXED:
@@ -226,7 +232,7 @@ class Body final : public io::IO
 	/** @brief Attach a rod to the body
 	 * @param rod The rod
 	 * @param coords vector indicating start (vals 0-2) and end (valse 3-5) of
-	 * rod
+	 * rod in the body reference frame
 	 * @throw moordyn::invalid_value_error If @p rod is NULL
 	 */
 	void addRod(Rod* rod, vec6 coords);
@@ -237,10 +243,11 @@ class Body final : public io::IO
 	 * moordyn::Body::FIXED
 	 * @param r The position (6 dof)
 	 * @param rd The velocity (6 dof)
+	 * @param rdd The acceleration (6 dof)
 	 * @throw moordyn::invalid_value_error If the body is of type
 	 * moordyn::Body::FREE
 	 */
-	void initializeUnfreeBody(vec6 r = vec6::Zero(), vec6 rd = vec6::Zero());
+	void initializeUnfreeBody(vec6 r = vec6::Zero(), vec6 rd = vec6::Zero(), vec6 rdd = vec6::Zero());
 
 	/** @brief Initialize the FREE point state
 	 * @return The 6-dof position (first) and the 6-dof velocity (second)
@@ -296,33 +303,33 @@ class Body final : public io::IO
 	/** @brief Get the body position
 	 * @return The body position
 	 */
-	inline vec getPosition() const { return r7.pos; }
+	inline const vec getPosition() const { return r7.pos; }
 
 	/** @brief Get the body Euler XYZ angles
 	 * @return The body Euler XYZ angles
 	 */
-	inline vec getAngles() const { return Quat2Euler(r7.quat); }
+	inline const vec getAngles() const { return Quat2Euler(r7.quat); }
 	// inline vec getAngles() const { return r6(Eigen::seqN(3, 3)); }
 
 	/** @brief Get the body velocity
 	 * @return The body velocity
 	 */
-	inline vec getVelocity() const { return v6(Eigen::seqN(0, 3)); }
+	inline const vec getVelocity() const { return v6(Eigen::seqN(0, 3)); }
 
 	/** @brief Get the body angular velocity
 	 * @return The body angular velocity
 	 */
-	inline vec getAngularVelocity() const { return v6(Eigen::seqN(3, 3)); }
+	inline const vec getAngularVelocity() const { return v6(Eigen::seqN(3, 3)); }
 
 	/** @brief Get the forces and moments exerted over the body
 	 * @return The net force
 	 */
-	inline vec6 getFnet() const { return F6net; }
+	const vec6 getFnet() const;
 
 	/** @brief Get the mass and intertia matrix
 	 * @return The mass and inertia matrix
 	 */
-	inline mat6 getM() const { return M; };
+	inline const mat6& getM() const { return M; };
 
 	/** @brief Get body output
 	 *
@@ -339,12 +346,13 @@ class Body final : public io::IO
 
 	/** @brief Called at the beginning of each coupling step to update the
 	 * boundary conditions (body kinematics) for the proceeding time steps
-	 * @param r The output position
-	 * @param rd The output velocity
+	 * @param r The input position
+	 * @param rd The input velocity
+	 * @param rdd The input velocity
 	 * @throw moordyn::invalid_value_error If the body is not of type
 	 * moordyn::Body::COUPLED or moordyn::Body::FIXED
 	 */
-	void initiateStep(vec6 r, vec6 rd);
+	void initiateStep(vec6 r_in, vec6 rd_in, vec6 rdd_in);
 
 	/** @brief Sets the kinematics based on the position and velocity of the
 	 * fairlead.
diff --git a/source/Line.cpp b/source/Line.cpp
index 6259e830..77f617b6 100644
--- a/source/Line.cpp
+++ b/source/Line.cpp
@@ -531,18 +531,21 @@ Line::GetLineOutput(OutChanProps outChan)
 		return rd[outChan.NodeID][1];
 	else if (outChan.QType == VelZ)
 		return rd[outChan.NodeID][2];
-	else if (outChan.QType == Ten)
+	else if (outChan.QType == Ten) {
+		if ((outChan.NodeID == 0) || (outChan.NodeID == N))
+			return getNodeForce(outChan.NodeID).norm();
 		return getNodeTen(outChan.NodeID).norm();
+	}
 	else if (outChan.QType == TenA)
-		return getNodeTen(0).norm();
+		return getNodeForce(0).norm();
 	else if (outChan.QType == TenB)
-		return getNodeTen(N).norm();
+		return getNodeForce(N).norm();
 	else if (outChan.QType == FX)
-		return Fnet[outChan.NodeID][0];
+		return getNodeForce(outChan.NodeID)[0];
 	else if (outChan.QType == FY)
-		return Fnet[outChan.NodeID][1];
+		return getNodeForce(outChan.NodeID)[1];
 	else if (outChan.QType == FZ)
-		return Fnet[outChan.NodeID][2];
+		return getNodeForce(outChan.NodeID)[2];
 	LOGWRN << "Unrecognized output channel " << outChan.QType << endl;
 	return 0.0;
 }
@@ -1512,7 +1515,9 @@ Line::drawGL(void)
 	double normTen;
 	double rgb[3];
 	for (int i = 0; i <= N; i++) {
-		double newTen = getNodeTen(i).norm();
+		const double newTen = ((i == 0) || (i == N)) ?
+			getNodeForce(i).norm() :
+			getNodeTen(i).norm();
 		if (newTen > maxTen)
 			maxTen = newTen;
 	}
@@ -1522,7 +1527,9 @@ Line::drawGL(void)
 	for (int i = 0; i <= N; i++) {
 		glVertex3d(r[i][0], r[i][1], r[i][2]);
 		if (i < N) {
-			normTen = getNodeTen(i).norm() / maxTen;
+			const double normTen = (((i == 0) || (i == N)) ?
+				getNodeForce(i).norm() :
+				getNodeTen(i).norm()) / maxTen;
 			ColorMap(normTen, rgb);
 			glColor3d(rgb[0], rgb[1], rgb[2]);
 		}
@@ -1537,14 +1544,18 @@ Line::drawGL2(void)
 	double normTen;
 	double rgb[3];
 	for (int i = 0; i <= N; i++) {
-		double newTen = getNodeTen(i).norm();
+		const double newTen = ((i == 0) || (i == N)) ?
+			getNodeForce(i).norm() :
+			(0.5 * (T[i] + T[i - 1])).norm();
 		if (newTen > maxTen)
 			maxTen = newTen;
 	}
 
 	// line
 	for (unsigned int i = 0; i < N; i++) {
-		normTen = 0.2 + 0.8 * pow(getNodeTen(i).norm() / maxTen, 4.0);
+		const double normTen = 0.2 + 0.8 * pow((((i == 0) || (i == N)) ?
+			getNodeForce(i).norm() :
+			getNodeTen(i).norm()) / maxTen, 4.0);
 		ColorMap(normTen, rgb);
 		glColor3d(rgb[0], rgb[1], rgb[2]);
 
@@ -1643,6 +1654,30 @@ MoorDyn_SetLineUnstretchedLengthVel(MoorDynLine l, double v)
 	return MOORDYN_SUCCESS;
 }
 
+int DECLDIR
+MoorDyn_IsLineConstantEA(MoorDynLine l, int* b)
+{
+	CHECK_LINE(l);
+	*b = (int)((moordyn::Line*)l)->isConstantEA();
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetLineConstantEA(MoorDynLine l, double* EA)
+{
+	CHECK_LINE(l);
+	*EA = ((moordyn::Line*)l)->getConstantEA();
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_SetLineConstantEA(MoorDynLine l, double EA)
+{
+	CHECK_LINE(l);
+	((moordyn::Line*)l)->setConstantEA(EA);
+	return MOORDYN_SUCCESS;
+}
+
 int DECLDIR
 MoorDyn_GetLineNodePos(MoorDynLine l, unsigned int i, double pos[3])
 {
@@ -1657,6 +1692,34 @@ MoorDyn_GetLineNodePos(MoorDynLine l, unsigned int i, double pos[3])
 	return err;
 }
 
+int DECLDIR
+MoorDyn_GetLineNodeVel(MoorDynLine l, unsigned int i, double vel[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec rd = ((moordyn::Line*)l)->getNodeVel(i);
+		moordyn::vec2array(rd, vel);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
+int DECLDIR
+MoorDyn_GetLineNodeForce(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeForce(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
 int DECLDIR
 MoorDyn_GetLineNodeTen(MoorDynLine l, unsigned int i, double ten[3])
 {
@@ -1671,6 +1734,76 @@ MoorDyn_GetLineNodeTen(MoorDynLine l, unsigned int i, double ten[3])
 	return err;
 }
 
+int DECLDIR
+MoorDyn_GetLineNodeBendStiff(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeBendStiff(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
+int DECLDIR
+MoorDyn_GetLineNodeWeight(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeWeight(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
+int DECLDIR
+MoorDyn_GetLineNodeDrag(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeDrag(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
+int DECLDIR
+MoorDyn_GetLineNodeFroudeKrilov(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeFroudeKrilov(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
+int DECLDIR
+MoorDyn_GetLineNodeSeabedForce(MoorDynLine l, unsigned int i, double f[3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec force = ((moordyn::Line*)l)->getNodeSeabedForce(i);
+		moordyn::vec2array(force, f);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
 int DECLDIR
 MoorDyn_GetLineNodeCurv(MoorDynLine l, unsigned int i, double* curv)
 {
@@ -1685,6 +1818,20 @@ MoorDyn_GetLineNodeCurv(MoorDynLine l, unsigned int i, double* curv)
 	return err;
 }
 
+int DECLDIR
+MoorDyn_GetLineNodeM(MoorDynLine l, unsigned int i, double m[3][3])
+{
+	CHECK_LINE(l);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::mat mass = ((moordyn::Line*)l)->getNodeM(i);
+		moordyn::mat2array(mass, m);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
 int DECLDIR
 MoorDyn_GetLineFairTen(MoorDynLine l, double* t)
 {
diff --git a/source/Line.h b/source/Line.h
index bb205830..e5898641 100644
--- a/source/Line.h
+++ b/source/Line.h
@@ -102,6 +102,42 @@ extern "C"
 	 */
 	int DECLDIR MoorDyn_SetLineUnstretchedLengthVel(MoorDynLine l, double v);
 
+	/** @brief Get whether the line is governed by a non-linear stiffness or a
+	 * constant one
+	 * @param l The Moordyn line
+	 * @param b 1 if the stiffness of the line is constant, 0 if a
+	 * non-linear stiffness has been set
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 * @see MoorDyn_GetLineConstantEA()
+	 * @see MoorDyn_SetLineConstantEA()
+	 */
+	int DECLDIR MoorDyn_IsLineConstantEA(MoorDynLine l, int* b);
+
+	/** @brief Get the constant stiffness of the line
+	 *
+	 * This value is useless if non-linear stiffness is considered
+	 * @param l The Moordyn line
+	 * @param EA The constant stiffness EA value
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 * @see MoorDyn_IsLineConstantEA()
+	 * @see MoorDyn_SetLineConstantEA()
+	 */
+	int DECLDIR MoorDyn_GetLineConstantEA(MoorDynLine l, double* EA);
+
+	/** @brief Set the constant stiffness of the line
+	 *
+	 * This value is useless if non-linear stiffness is considered
+	 * @param l The Moordyn line
+	 * @param EA The constant stiffness EA value
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided, MOORDYN_SUCCESS
+	 * otherwise
+	 * @see MoorDyn_IsLineConstantEA()
+	 * @see MoorDyn_GetLineConstantEA()
+	 */
+	int DECLDIR MoorDyn_SetLineConstantEA(MoorDynLine l, double EA);
+
 	/** @brief Get a line node position
 	 * @param l The Moordyn line
 	 * @param i The node index
@@ -113,7 +149,59 @@ extern "C"
 	                                   unsigned int i,
 	                                   double pos[3]);
 
+	/** @brief Get a line node velocity
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param vel The output node velocity
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeVel(MoorDynLine l,
+	                                   unsigned int i,
+	                                   double vel[3]);
+
+	/** @brief Get a line node force
+	 *
+	 * To get the components of the force use MoorDyn_GetLineNodeTen() ,
+	 * MoorDyn_GetLineNodeBendStiff(), MoorDyn_GetLineNodeWeight() ,
+	 * MoorDyn_GetLineNodeDrag() , MoorDyn_GetLineNodeFroudeKrilov() and
+	 * MoorDyn_GetLineNodeSeaBedForce()
+	 * @note The net force is \b not the sum of all the components that you
+	 * cat extract from the API. For instance, the tension contribution on the
+	 * internal nodes is the difference between the tensions of the adjacent
+	 * segments, while MoorDyn_GetLineNodeTen() is returning the averaged
+	 * value.
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param f The output node net force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeForce(MoorDynLine l,
+	                                     unsigned int i,
+	                                     double f[3]);
+
+
 	/** @brief Get a line node tension
+	 *
+	 * The node tension is the sum of the axial stiffness plus the internal
+	 * damping
+	 *
+	 * \f[
+	 *     \bar{T}(i) = E A \left(
+	 *         \frac{l(i) - l_0}{l_0}
+	 *         - \frac{\beta A}{l_0} \frac{\mathrm{d}l(i)}{\mathrm{d}t}
+	 *     \right) \bar{q}(i)
+	 * \f]
+	 *
+	 * with \f$l(i)\f$ and \f$l_0\f$ the stretched and unstretched segment
+	 * lengths respectively, \f$E\f$ the Young's modulus, \f$A\f$ the
+	 * transversal area, \f$\beta\f$ the internal damping coefficient and
+	 * \f$\bar{q}(i)\f$ the direction verctor of the segment.
+	 *
+	 * As can be appreciated, the node tension is computed at each segment. So
+	 * to get the node one the surrounding segments are averaged (in case of
+	 * line-ends the associated ending segment tension is returned)
 	 * @param l The Moordyn line
 	 * @param i The node index
 	 * @param t The output node tension
@@ -124,6 +212,176 @@ extern "C"
 	                                   unsigned int i,
 	                                   double t[3]);
 
+	/** @brief Get a line node bending stiffness force
+	 *
+	 * The bending stiffness is computed at each segment as
+	 *
+	 * \f[
+	 *     \bar{Bs}(i) = E I \frac{\bar{k}(i)}{l_0}
+	 * \f]
+	 *
+	 * with \f$l_0\f$ the unstretched segment length, \f$E\f$ the Young's
+	 * modulus, \f$I\f$ the segment normal axis inertia, and
+	 * \f$\bar{k}\f$ the curvature vector.
+	 *
+	 * To get the node bending stiffness forces the surrounding segments forces
+	 * are accumulated
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param t The output node force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeBendStiff(MoorDynLine l,
+	                                         unsigned int i,
+	                                         double t[3]);
+
+	/** @brief Get a line node weight and bouyancy
+	 *
+	 * This is computed at each segment as
+	 *
+	 * \f[
+	 *     \bar{W}(i) = A l_0 \left(\rho - F(i) \rho_w \right) \bar{g}
+	 * \f]
+	 *
+	 * with \f$l_0\f$ the unstretched segment length, \f$A\f$ the
+	 * transversal area, \f$\rho\f$ the line material density, \f$\rho_w\f$
+	 * the water density, \f$F(i)\f$ the portion of the segment submerged and
+	 * \f$\bar{g}\f$ the gravity acceleration.
+	 *
+	 * The weight and bounyancy force at any internal node is computed as the
+	 * average of the surrounding segments, while on the line-ends \b half of
+	 * the associated ending segment weight force is returned.
+	 *
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param f The output node force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeWeight(MoorDynLine l,
+	                                      unsigned int i,
+	                                      double f[3]);
+
+	/** @brief Get a line node drag force
+	 *
+	 * This is computed at each segment as
+	 *
+	 * \f[
+	 *     \bar{D}(i) = \frac{1}{2} l_0 F(i) \rho_w \left(
+	 *        Cd_t \pi d \vert \bar{v}_t(i) \vert \bar{v}_t(i)
+	 *        + Cd_n d \vert \bar{v}_n(i) \vert \bar{v}_n(i)
+	 *     \right)
+	 * \f]
+	 *
+	 * with \f$l_0\f$ the unstretched segment length, \f$F(i)\f$ the portion of
+	 * the segment submerged, \f$\rho_w\f$ the water density, \f$d\f$ the line
+	 * diameter, \f$Cd_t\f$ and \f$Cd_n\f$ the normal and tangential drag
+	 * coefficients, and \f$\bar{v}_t\f$ and \f$\bar{v}_n\f$ the tangential and
+	 * normal velocities:
+	 *
+	 * \f[
+	 *     \bar{v}_t = (\bar{v} - \bar{U}) \cdot \bar{q}
+	 * \f]
+	 * \f[
+	 *     \bar{v}_n = (\bar{v} - \bar{U}) - \bar{v}_t
+	 * \f]
+	 * 
+	 * with \f$\bar{v}\f$ the velocity, \f$\bar{U}\f$ the flow velocity and
+	 * \f$\bar{q}\f$ the direction verctor of the segment
+	 *
+	 * The drag at any internal node is computed as the average of the
+	 * surrounding segments, while on the line-ends \b half of the associated
+	 * ending segment drag force is returned.
+	 *
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param f The output node force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeDrag(MoorDynLine l,
+	                                    unsigned int i,
+	                                    double f[3]);
+
+	/** @brief Get a line node Froude Krilov force
+	 *
+	 * This is computed at each segment as
+	 *
+	 * \f[
+	 *     \bar{Fk}(i) = V(i) F(i) \rho_w \left(
+	 *        (1 + Ca_t) \frac{\mathrm{d} \bar{v}_t}{\mathrm{d} t}
+	 *        + (1 + Ca_n)  \frac{\mathrm{d} \bar{v}_n}{\mathrm{d} t}
+	 *     \right)
+	 * \f]
+	 *
+	 * with \f$V(i)\f$ the segment volume, \f$F(i)\f$ the portion of
+	 * the segment submerged, \f$\rho_w\f$ the water density,
+	 * \f$Cd_t\f$ and \f$Cd_n\f$ the normal and tangential drag coefficients,
+	 * and \f$\frac{\mathrm{d} \bar{v}_t}{\mathrm{d} t}\f$ and
+	 * \f$\frac{\mathrm{d} \bar{v}_n}{\mathrm{d} t}\f$ the tangential and
+	 * normal accelerations:
+	 *
+	 * \f[
+	 *     \frac{\mathrm{d} \bar{v}_t}{\mathrm{d} t} = 
+	 *          \frac{\mathrm{d} \bar{v}}{\mathrm{d} t} \cdot \bar{q}(i)
+	 * \f]
+	 * \f[
+	 *     \frac{\mathrm{d} \bar{v}_n}{\mathrm{d} t} =
+	 *          \frac{\mathrm{d} \bar{v}}{\mathrm{d} t} -
+	 *          \frac{\mathrm{d} \bar{v}_t}{\mathrm{d} t}
+	 * \f]
+	 * 
+	 * with \f$\bar{q}(i)\f$ the direction verctor of the segment
+	 *
+	 * The force at any internal node is computed as the average of the
+	 * surrounding segments, while on the line-ends \b half of the associated
+	 * ending segment Froude-Krylov force is returned.
+	 *
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param f The output node force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeFroudeKrilov(MoorDynLine l,
+	                                            unsigned int i,
+	                                            double f[3]);
+
+	/** @brief Get a line node seabed reaction
+	 *
+	 * This is computed at each segment as
+	 *
+	 * \f[
+	 *     \bar{B}(i) = l_0 d \left(
+	 *         Kb (\bar{r}_b - \bar{r}(i)) \cdot \bar{k}
+	 *         Cb \bar{v} \cdot \bar{k} +
+	 *     \right) \bar{k}
+	 * \f]
+	 *
+	 * with \f$l_0\f$ the unstretched segment length, \f$d\f$ the line diameter
+	 * \f$Kb\f$ and \f$Cb\f$ the bottom stiff and drag coefficients,
+	 * \f$\bar{r}_b\f$ the seabed position, \f$\bar{r}\f$ the position,
+	 * \f$\bar{v}\f$ the velocity and \f$\bar{k}\f$ the upward direction
+	 * vector.
+	 *
+	 * If bottom friction is configured, the force is added to this magnitude
+	 * as well.
+	 *
+	 * The reaction force at any internal node is computed as the
+	 * average of the surrounding segments, while on the line-ends \b half of
+	 * the associated ending segment weight force is returned.
+	 *
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param f The output node force
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeSeabedForce(MoorDynLine l,
+	                                           unsigned int i,
+	                                           double f[3]);
+
 	/** @brief Get a line curvature at a node
 	 * @param l The Moordyn line
 	 * @param i The node index
@@ -138,6 +396,19 @@ extern "C"
 	                                    unsigned int i,
 	                                    double* c);
 
+	/** @brief Get a line node mass matrix
+	 *
+	 * The mass matrix includes the node mass as well as the added mass
+	 * @param l The Moordyn line
+	 * @param i The node index
+	 * @param m The output mass matrix
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetLineNodeM(MoorDynLine l,
+	                                 unsigned int i,
+	                                 double m[3][3]);
+
 	/** @brief Get the tension module at the end point B (the fairlead)
 	 * @param l The Moordyn line
 	 * @param t The output node tension module
diff --git a/source/Line.hpp b/source/Line.hpp
index 7df36f9c..ca6dba65 100644
--- a/source/Line.hpp
+++ b/source/Line.hpp
@@ -396,45 +396,212 @@ class Line final : public io::IO
 		setUnstretchedLength(UnstrLen0 + dt * UnstrLend);
 	}
 
+	/** @brief Get whether the line is governed by a non-linear stiffness or a
+	 * constant one
+	 * @return true if the stiffness of the line is constant, false if a
+	 * non-linear stiffness has been set
+	 */
+	inline bool isConstantEA() const { return nEApoints > 0; }
+
+	/** @brief Get the constant stiffness of the line
+	 *
+	 * This value is useless if non-linear stiffness is considered
+	 * @return The constant stiffness EA value
+	 * @see ::IsConstantEA()
+	 */
+	inline moordyn::real getConstantEA() const { return E * A; }
+
+	/** @brief Set the constant stiffness of the line
+	 *
+	 * This value is useless if non-linear stiffness is considered
+	 * @param EA The constant stiffness EA value
+	 * @see ::IsConstantEA()
+	 */
+	inline void setConstantEA(moordyn::real EA) { E = EA / A; }
+
 	/** @brief Get the position of a node
 	 * @param i The line node index
 	 * @return The position
 	 * @throws invalid_value_error If the node index \p i is bigger than the
 	 * number of nodes, moordyn::Line::N + 1
 	 */
-	inline vec getNodePos(unsigned int i) const
+	inline const vec& getNodePos(unsigned int i) const
 	{
 		if (i > N) {
 			LOGERR << "Asking node " << i << " of line " << number
 			       << ", which only has " << N + 1 << " nodes" << std::endl;
 			throw moordyn::invalid_value_error("Invalid node index");
 		}
+		if (isnan(r[i].sum())) {
+			stringstream s;
+			s << "NaN detected" << endl
+			  << "Line " << number << " node positions:" << endl;
+			for (unsigned int j = 0; j <= N; j++)
+				s << j << " : " << r[j] << ";" << endl;
+			throw moordyn::nan_error(s.str().c_str());
+		}
 		return r[i];
 	}
 
-	/** @brief Get the tension in a node
+	/** @brief Get the velocity of a node
+	 * @param i The line node index
+	 * @return The velocity
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec& getNodeVel(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		if (isnan(rd[i].sum())) {
+			stringstream s;
+			s << "NaN detected" << endl
+			  << "Line " << number << " node velocities:" << endl;
+			for (unsigned int j = 0; j <= N; j++)
+				s << j << " : " << rd[j] << ";" << endl;
+			throw moordyn::nan_error(s.str().c_str());
+		}
+		return rd[i];
+	}
+
+	/** @brief Get the net force on a node
 	 *
-	 * smart (selective) function to get tension at any node including fairlead
-	 * or anchor (accounting for weight in these latter cases) (added Nov 15th)
+	 * The net force is the total force acting over a line node.
+	 * 
+	 * To get the different components of the force use ::getNodeTen() ,
+	 * ::getNodeBendStiff() , ::getNodeWeight() , ::getNodeDrag() ,
+	 * ::getNodeFroudeKrilov() and ::getNodeSeaBedForce()
+	 * @note The net force is \b not the sum of all the components that you
+	 * cat extract from the API. For instance, the tension contribution on the
+	 * internal nodes is the difference between the tensions of the adjacent
+	 * segments, while ::getNodeTen() is returning the average.
 	 * @param i The line node index
 	 * @return The tension
 	 * @throws invalid_value_error If the node index \p i is bigger than the
 	 * number of nodes, moordyn::Line::N + 1
 	 */
-	inline vec getNodeTen(unsigned int i) const
+	inline const vec& getNodeForce(unsigned int i) const
 	{
 		if (i > N) {
 			LOGERR << "Asking node " << i << " of line " << number
 			       << ", which only has " << N + 1 << " nodes" << std::endl;
 			throw moordyn::invalid_value_error("Invalid node index");
 		}
-		if ((i == 0) || (i == N))
-			return (
-			    Fnet[i] +
-			    vec(0.0, 0.0, M[i](0, 0) * (-env->g))); // <<< update to use W
+		return Fnet[i];
+	};
 
+	/** @brief Get the tension on a node, including the internal line damping
+	 * 
+	 * If it is an inner node, the average of the
+	 * tension at the surrounding segments is provided. If the node is a
+	 * line-end, the associated ending segment tension is provided
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec getNodeTen(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		if (i == 0)
+			return T[0] + Td[0];
+		else if (i == N)
+			return T[N - 1] + Td[N - 1];
 		// take average of tension in adjacent segments
-		return (0.5 * (T[i] + T[i - 1]));
+		return (0.5 * (T[i] + T[i - 1] + Td[i] + Td[i - 1]));
+	};
+
+	/** @brief Get the tension on a node, including the internal line damping
+	 * 
+	 * If it is an inner node, the average of the
+	 * tension at the surrounding segments is provided. If the node is a
+	 * line-end, the associated ending segment tension is provided
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec& getNodeBendStiff(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return Bs[i];
+	};
+
+	/** @brief Get the weight and bouyancy force acting on the node
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec& getNodeWeight(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return W[i];
+	};
+
+	/** @brief Get the drag force acting on the node
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec getNodeDrag(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return Dp[i] + Dq[i];
+	};
+
+	/** @brief Get the Froude-Krilov force acting on the node
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec getNodeFroudeKrilov(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return Ap[i] + Aq[i];
+	};
+
+	/** @brief Get the sea bed reaction force acting on the node
+	 *
+	 * This is eventually including the friction force
+	 * @param i The line node index
+	 * @return The tension
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const vec getNodeSeabedForce(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return B[i] + Bs[i];
 	};
 
 	/** @brief Get the line curvature at a node position
@@ -446,7 +613,7 @@ class Line final : public io::IO
 	 * (moordyn::Line::EI) is not zero. Otherwise the curvature of every single
 	 * node will be zero.
 	 */
-	inline real getNodeCurv(unsigned int i) const
+	inline const real& getNodeCurv(unsigned int i) const
 	{
 		if (i > N) {
 			LOGERR << "Asking node " << i << " of line " << number
@@ -456,6 +623,22 @@ class Line final : public io::IO
 		return Kurv[i];
 	}
 
+	/** @brief Get the mass and added mass matrix
+	 * @param i The line node index
+	 * @return The mass matrix
+	 * @throws invalid_value_error If the node index \p i is bigger than the
+	 * number of nodes, moordyn::Line::N + 1
+	 */
+	inline const mat& getNodeM(unsigned int i) const
+	{
+		if (i > N) {
+			LOGERR << "Asking node " << i << " of line " << number
+			       << ", which only has " << N + 1 << " nodes" << std::endl;
+			throw moordyn::invalid_value_error("Invalid node index");
+		}
+		return M[i];
+	}
+
 	/** @brief Get the array of coordinates of all nodes along the line
 	 * @return The positions array
 	 */
diff --git a/source/Misc.hpp b/source/Misc.hpp
index 90b3ccf6..8d725e19 100644
--- a/source/Misc.hpp
+++ b/source/Misc.hpp
@@ -142,10 +142,93 @@ typedef ivec3 ivec;
 /// COmplex numbers
 typedef std::complex<real> complex;
 
+inline vec3 
+canonicalEulerAngles(const quaternion& quat, int a0, int a1, int a2)
+{
+	// From issue #163: https://github.com/FloatingArrayDesign/MoorDyn/issues/163
+	mat3 coeff = quat.toRotationMatrix();
+	vec3 res{};
+	using Index = int;
+	using Scalar = real;
+	const Index odd = ((a0 + 1) % 3 == a1) ? 0 : 1;
+	const Index i = a0;
+	const Index j = (a0 + 1 + odd) % 3;
+	const Index k = (a0 + 2 - odd) % 3;
+	if (a0 == a2) {
+		// Proper Euler angles (same first and last axis).
+		// The i, j, k indices enable addressing the input matrix as the XYX
+		// archetype matrix (see Graphics Gems IV), where e.g. coeff(k, i) means
+		// third column, first row in the XYX archetype matrix:
+		//  c2      s2s1              s2c1
+		//  s2s3   -c2s1s3 + c1c3    -c2c1s3 - s1c3
+		// -s2c3    c2s1c3 + c1s3     c2c1c3 - s1s3
+		// Note: s2 is always positive.
+		Scalar s2 = Eigen::numext::hypot(coeff(j, i), coeff(k, i));
+		if (odd) {
+			res[0] = atan2(coeff(j, i), coeff(k, i));
+			// s2 is always positive, so res[1] will be within the canonical [0,
+			// pi] range
+			res[1] = atan2(s2, coeff(i, i));
+		} else {
+			// In the !odd case, signs of all three angles are flipped at the
+			// very end. To keep the solution within the canonical range, we
+			// flip the solution and make res[1] always negative here (since s2
+			// is always positive, -atan2(s2, c2) will always be negative). The
+			// final flip at the end due to !odd will thus make res[1] positive
+			// and canonical. NB: in the general case, there are two correct
+			// solutions, but only one is canonical. For proper Euler angles,
+			// flipping from one solution to the other involves flipping the
+			// sign of the second angle res[1] and adding/subtracting pi to the
+			// first and third angles. The addition/subtraction of pi to the
+			// first angle res[0] is handled here by flipping the signs of
+			// arguments to atan2, while the calculation of the third angle does
+			// not need special adjustment since it uses the adjusted res[0] as
+			// the input and produces a correct result.
+			res[0] = atan2(-coeff(j, i), -coeff(k, i));
+			res[1] = -atan2(s2, coeff(i, i));
+		}
+		// With a=(0,1,0), we have i=0; j=1; k=2, and after computing the first
+		// two angles, we can compute their respective rotation, and apply its
+		// inverse to M. Since the result must be a rotation around x, we have:
+		//
+		//  c2  s1.s2 c1.s2                   1  0   0
+		//  0   c1    -s1       *    M    =   0  c3  s3
+		//  -s2 s1.c2 c1.c2                   0 -s3  c3
+		//
+		//  Thus:  m11.c1 - m21.s1 = c3  &   m12.c1 - m22.s1 = s3
+		Scalar s1 = sin(res[0]);
+		Scalar c1 = cos(res[0]);
+		res[2] = atan2(c1 * coeff(j, k) - s1 * coeff(k, k),
+		               c1 * coeff(j, j) - s1 * coeff(k, j));
+	} else {
+		// Tait-Bryan angles (all three axes are different; typically used for
+		// yaw-pitch-roll calculations). The i, j, k indices enable addressing
+		// the input matrix as the XYZ archetype matrix (see Graphics Gems IV),
+		// where e.g. coeff(k, i) means third column, first row in the XYZ
+		// archetype matrix:
+		//  c2c3    s2s1c3 - c1s3     s2c1c3 + s1s3
+		//  c2s3    s2s1s3 + c1c3     s2c1s3 - s1c3
+		// -s2      c2s1              c2c1
+		res[0] = atan2(coeff(j, k), coeff(k, k));
+		Scalar c2 = Eigen::numext::hypot(coeff(i, i), coeff(i, j));
+		// c2 is always positive, so the following atan2 will always return a
+		// result in the correct canonical middle angle range [-pi/2, pi/2]
+		res[1] = atan2(-coeff(i, k), c2);
+		Scalar s1 = sin(res[0]);
+		Scalar c1 = cos(res[0]);
+		res[2] = atan2(s1 * coeff(k, i) - c1 * coeff(j, i),
+		               c1 * coeff(j, j) - s1 * coeff(k, j));
+	}
+	if (!odd) {
+		res = -res;
+	}
+	return res;
+}
+
 inline vec3
 Quat2Euler(const quaternion& q)
 {
-	return q.toRotationMatrix().eulerAngles(0, 1, 2);
+	return canonicalEulerAngles(q, 0, 1, 2); // 0, 1, 2 correspond to axes leading to XYZ rotation 
 }
 
 inline quaternion
@@ -589,7 +672,9 @@ split(const string& str, const char sep);
 inline vector<string>
 split(const string& s)
 {
-	return split(s, ' ');
+	vector<string> sout = split(s, ' ');
+	if (sout.size() == 1) return split(sout[0], '	');
+	else return sout;
 }
 
 /**
diff --git a/source/MoorDyn2.cpp b/source/MoorDyn2.cpp
index 682a4f14..f64a0a59 100644
--- a/source/MoorDyn2.cpp
+++ b/source/MoorDyn2.cpp
@@ -192,7 +192,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 
 	// Allocate past line fairlead tension array, which is used for convergence
 	// test during IC gen
-	const unsigned int convergence_iters = 10;
+	const unsigned int convergence_iters = 9; // 10 iterations, indexed 0-9
 	vector<real> FairTensLast_col(convergence_iters, 0.0);
 	for (unsigned int i = 0; i < convergence_iters; i++)
 		FairTensLast_col[i] = 1.0 * i;
@@ -214,35 +214,62 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 	int ix = 0;
 
 	for (auto l : CpldBodyIs) {
-		LOGMSG << "Initializing coupled Body " << l << " in " << x[ix] << ", "
+		LOGMSG << "Initializing coupled Body " << l+1 << " at " << x[ix] << ", "
 		       << x[ix + 1] << ", " << x[ix + 2] << "..." << endl;
-		// this calls initiateStep and updateFairlead, then initializes
-		// dependent Rods
+
 		// BUG: These conversions will not be needed in the future
-		vec6 r, rd;
-		moordyn::array2vec6(x + ix, r);
-		moordyn::array2vec6(xd + ix, rd);
-		BodyList[l]->initializeUnfreeBody(r, rd);
-		ix += 6;
+		vec6 r, rd, rdd;
+		if (BodyList[l]->type == Body::COUPLED){
+			moordyn::array2vec6(x + ix, r);
+			moordyn::array2vec6(xd + ix, rd);
+			// determine acceleration 
+			rdd = (rd - rd_b) / dtM0;
+			rd_b = rd;
+
+			ix += 6;
+		} else {
+			// for pinned body 3 entries will be taken
+			vec3 r3, rd3, rdd3;
+			moordyn::array2vec(x + ix, r3);
+			r(Eigen::seqN(0, 3)) = r3;
+			moordyn::array2vec(xd + ix, rd3);
+			rd(Eigen::seqN(0, 3)) = rd3;
+			// determine acceleration 
+		    rdd(Eigen::seqN(0, 3)) = (rd3 - rd3_b) / dtM0;
+			rd3_b = rd3;
+
+			ix += 3;
+		}
+		BodyList[l]->initializeUnfreeBody(r, rd, rdd);
 	}
 
 	for (auto l : CpldRodIs) {
-		LOGMSG << "Initializing coupled Rod " << l << " in " << x[ix] << ", "
+		LOGMSG << "Initializing coupled Rod " << l+1 << " at " << x[ix] << ", "
 		       << x[ix + 1] << ", " << x[ix + 2] << "..." << endl;
-		vec6 r, rd;
+		vec6 r, rd, rdd;
 		if (RodList[l]->type == Rod::COUPLED) {
+			// for cantilevered rods 6 entries will be taken
 			moordyn::array2vec6(x + ix, r);
 			moordyn::array2vec6(xd + ix, rd);
-			ix += 6; // for cantilevered rods 6 entries will be taken
+			// determine acceleration 
+		    rdd = (rd - rd_r) / dtM0;
+			rd_r = rd;
+
+			ix += 6;
 		} else {
-			vec3 r3, rd3;
+			// for pinned rods 3 entries will be taken
+			vec3 r3, rd3, rdd3;
 			moordyn::array2vec(x + ix, r3);
 			r(Eigen::seqN(0, 3)) = r3;
 			moordyn::array2vec(xd + ix, rd3);
 			rd(Eigen::seqN(0, 3)) = rd3;
-			ix += 3; // for pinned rods 3 entries will be taken
+			// determine acceleration 
+		    rdd(Eigen::seqN(0, 3)) = (rd3 - rd3_r) / dtM0;
+			rd3_r = rd3;
+
+			ix += 3;
 		}
-		RodList[l]->initiateStep(r, rd);
+		RodList[l]->initiateStep(r, rd, rdd);
 		RodList[l]->updateFairlead(0.0);
 		// call this just to set up the output file header
 		RodList[l]->initialize();
@@ -250,7 +277,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 
 	for (auto l : CpldPointIs) {
 		LOGMSG << "Initializing coupled Point " << l+1 << " at " << x[ix] << ", "
-		       << x[ix + 1] << ", " << x[ix + 2] << "..." << endl;
+		       << x[ix + 1] << ", " << x[ix + 2] << endl;
 		vec r, rd;
 		moordyn::array2vec(x + ix, r);
 		moordyn::array2vec(xd + ix, rd);
@@ -296,7 +323,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 	// vector to store tensions for analyzing convergence
 	vector<real> FairTens(LineList.size(), 0.0);
 
-	unsigned int iic = 0;
+	unsigned int iic = 1; // To match MDF indexing
 	real t = 0;
 	bool converged = true;
 	real max_error = 0.0;
@@ -308,7 +335,10 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 	real best_score = (std::numeric_limits<real>::max)();
 	real best_score_t = 0.0;
 	unsigned int best_score_line = 0;
-	while ((t < ICTmax) && (!skip_ic)) {
+
+	// //dtIC set to fraction of input so convergence is over dtIC
+	ICdt = ICdt / (convergence_iters+1);
+	while (((ICTmax-t) > 0.00000001) && (!skip_ic)) { // tol of 0.00000001 should be smaller than anything anyone puts in as a ICdt
 		// Integrate one ICD timestep (ICdt)
 		real t_target = ICdt;
 		real dt;
@@ -382,7 +412,7 @@ moordyn::MoorDyn::Init(const double* x, const double* xd, bool skip_ic)
 		if (converged) {
 			LOGMSG << "Fairlead tensions converged" << endl;
 		} else {
-			LOGWRN << "Fairlead tensions did not converged" << endl;
+			LOGWRN << "Fairlead tensions did not converge" << endl;
 		}
 		LOGMSG << "Remaining error after " << t << " s = " << 100.0 * max_error
 		       << "% on line " << max_error_line << endl;
@@ -480,7 +510,12 @@ moordyn::MoorDyn::Step(const double* x,
                        double& dt)
 {
 	// should check if wave kinematics have been set up if expected!
+	const auto default_precision{std::cout.precision()};
+	std::cout << std::fixed << setprecision(1);
 	LOGDBG << "t = " << t << "s     \r";
+	std::cout << std::defaultfloat << setprecision(default_precision);
+
+	cout << "\rt = " << t << " " << flush;
 
 	if (dt <= 0) {
 		// Nothing to do, just recover the forces if there are coupled DOFs
@@ -502,29 +537,55 @@ moordyn::MoorDyn::Step(const double* x,
 	// used later for extrapolating motions
 	for (auto l : CpldBodyIs) {
 		// BUG: These conversions will not be needed in the future
-		vec6 r, rd;
-		moordyn::array2vec6(x + ix, r);
-		moordyn::array2vec6(xd + ix, rd);
-		BodyList[l]->initiateStep(r, rd);
-		ix += 6;
+		vec6 r, rd, rdd;
+		if (BodyList[l]->type == Body::COUPLED){
+			moordyn::array2vec6(x + ix, r);
+			moordyn::array2vec6(xd + ix, rd);
+			// determine acceleration 
+			rdd = (rd - rd_b) / dtM0;
+			rd_b = rd;
+
+			ix += 6;
+		} else {
+			// for pinned body 3 entries will be taken
+			vec3 r3, rd3, rdd3;
+			moordyn::array2vec(x + ix, r3);
+			r(Eigen::seqN(0, 3)) = r3;
+			moordyn::array2vec(xd + ix, rd3);
+			rd(Eigen::seqN(0, 3)) = rd3;
+			// determine acceleration 
+		    rdd(Eigen::seqN(0, 3)) = (rd3 - rd3_b) / dt;
+			rd3_b = rd3;
+
+			ix += 3;
+		}
+		BodyList[l]->initiateStep(r, rd, rdd); // acceleration required for inertial terms
 	}
 	for (auto l : CpldRodIs) {
-		vec6 r, rd;
+		vec6 r, rd, rdd;
 		if (RodList[l]->type == Rod::COUPLED) {
 			// for cantilevered rods 6 entries will be taken
 			moordyn::array2vec6(x + ix, r);
 			moordyn::array2vec6(xd + ix, rd);
+			// determine acceleration 
+		    rdd = (rd - rd_r) / dt;
+			rd_r = rd;
+
 			ix += 6;
 		} else {
 			// for pinned rods 3 entries will be taken
-			vec3 r3, rd3;
+			vec3 r3, rd3, rdd3;
 			moordyn::array2vec(x + ix, r3);
 			r(Eigen::seqN(0, 3)) = r3;
 			moordyn::array2vec(xd + ix, rd3);
 			rd(Eigen::seqN(0, 3)) = rd3;
+			// determine acceleration 
+		    rdd(Eigen::seqN(0, 3)) = (rd3 - rd3_r) / dt;
+			rd3_r = rd3;
+
 			ix += 3;
 		}
-		RodList[l]->initiateStep(r, rd);
+		RodList[l]->initiateStep(r, rd, rdd); // acceleration required for inertial terms
 	}
 	for (auto l : CpldPointIs) {
 		vec r, rd;
@@ -948,10 +1009,6 @@ moordyn::MoorDyn::ReadInFile()
 			LOGERR << "Reading lines without defined line types" << endl;
 			return MOORDYN_INVALID_INPUT;
 		}
-		if (!PointList.size()) {
-			LOGERR << "Reading lines without defined points" << endl;
-			return MOORDYN_INVALID_INPUT;
-		}
 
 		// parse until the next header or the end of the file
 		while ((in_txt[i].find("---") == string::npos) && (i < in_txt.size())) {
@@ -1341,7 +1398,7 @@ moordyn::MoorDyn::ReadInFile()
 					} else if (let3 == "AX") {
 						dummy.QType = AccX;
 						dummy.Units = moordyn::UnitList[AccX];
-					} else if (let3 == "Ay") {
+					} else if (let3 == "AY") {
 						dummy.QType = AccY;
 						dummy.Units = moordyn::UnitList[AccY];
 					} else if (let3 == "AZ") {
@@ -1613,8 +1670,8 @@ moordyn::MoorDyn::readRodProps(string inputText)
 	obj->Can = atof(entries[4].c_str());
 	obj->CdEnd = atof(entries[5].c_str());
 	obj->CaEnd = atof(entries[6].c_str());
-	obj->Cdt = 0.0;
-	obj->Cat = 0.0;
+	obj->Cdt = 0.0; // No end effects from internal segments. CdEnd captures end effects.
+	obj->Cat = 0.0; // No end effects from internal segments. CaEnd captures end effects.
 
 	LOGDBG << "\t'" << obj->type << "'"
 	       << " - with id " << RodPropList.size() << endl
@@ -1742,6 +1799,16 @@ moordyn::MoorDyn::readBody(string inputText)
 		// it is coupled - controlled from outside
 		type = Body::COUPLED;
 		CpldBodyIs.push_back(ui_size(BodyList));
+	} else if (str::isOneOf(let1, { "VESSELPINNED", "VESPIN", "CPLDPIN", "COUPLEDPINNED" })) {
+		// if a pinned fairlead, add to list and add
+		type = Body::CPLDPIN;
+		CpldBodyIs.push_back(
+		    ui_size(BodyList)); // index of fairlead in BodyList vector
+		FreeBodyIs.push_back(
+		    ui_size(BodyList));    // also add this pinned body to the free
+		                          // list because it is half free
+		BodyStateIs.push_back(nX); // assign start index of this body's states
+		nX += 6;                  // add 6 state variables for each pinned Body
 	} else {
 		// it is free - controlled by MoorDyn
 		type = Body::FREE;
@@ -1833,7 +1900,7 @@ moordyn::MoorDyn::readRod(string inputText)
 		type = Rod::COUPLED;
 		CpldRodIs.push_back(
 		    ui_size(RodList)); // index of fairlead in RodList vector
-	} else if (str::isOneOf(let1, { "VESPIN", "CPLDPIN" })) {
+	} else if (str::isOneOf(let1, { "VESSELPINNED", "VESPIN", "CPLDPIN", "COUPLEDPINNED" })) {
 		// if a pinned fairlead, add to list and add
 		type = Rod::CPLDPIN;
 		CpldRodIs.push_back(
diff --git a/source/MoorDyn2.hpp b/source/MoorDyn2.hpp
index 55290e44..ff4f249c 100644
--- a/source/MoorDyn2.hpp
+++ b/source/MoorDyn2.hpp
@@ -388,8 +388,13 @@ class MoorDyn final : public io::IO
 		for (auto l : CpldBodyIs) {
 			// BUG: These conversions will not be needed in the future
 			const vec6 f_i = BodyList[l]->getFnet();
-			moordyn::vec62array(f_i, f + ix);
-			ix += 6;
+			if (BodyList[l]->type == Body::COUPLED) {
+				moordyn::vec62array(f_i, f + ix);
+				ix += 6; // for coupled bodies 6 entries will be taken
+			} else {
+				moordyn::vec2array(f_i(Eigen::seqN(0, 3)), f + ix);
+				ix += 3; // for pinned bodies 3 entries will be taken
+			}
 		}
 		for (auto l : CpldRodIs) {
 			const vec6 f_i = RodList[l]->getFnet();
@@ -502,6 +507,12 @@ class MoorDyn final : public io::IO
 	/// (if using env.WaveKin=1)
 	unsigned int npW;
 
+	/// Previous time step velocity for calculating coupled point acceleration (first time step assumed stationary)
+	vec6 rd_b = vec6::Zero(); // body
+	vec6 rd_r = vec6::Zero(); // coupled rod
+	vec3 rd3_r = vec3::Zero(); // coupled pinned rod
+	vec3 rd3_b = vec3::Zero(); // coupled pinned body
+
 	/// main output file
 	ofstream outfileMain;
 
diff --git a/source/Point.cpp b/source/Point.cpp
index 5aabda71..0cc62497 100644
--- a/source/Point.cpp
+++ b/source/Point.cpp
@@ -166,21 +166,6 @@ Point::initialize()
 	return std::make_pair(pos, vel);
 };
 
-// function to return net force on point (just to allow public reading of
-// Fnet)
-void
-Point::getFnet(vec& Fnet_out)
-{
-	Fnet_out = Fnet;
-};
-
-// function to return mass matrix of point
-void
-Point::getM(mat& M_out)
-{
-	M_out = M;
-};
-
 real
 Point::GetPointOutput(OutChanProps outChan)
 {
@@ -535,8 +520,7 @@ int DECLDIR
 MoorDyn_GetPointPos(MoorDynPoint point, double pos[3])
 {
 	CHECK_POINT(point);
-	moordyn::vec r, rd;
-	((moordyn::Point*)point)->getState(r, rd);
+	moordyn::vec r = ((moordyn::Point*)point)->getPosition();
 	moordyn::vec2array(r, pos);
 	return MOORDYN_SUCCESS;
 }
@@ -545,8 +529,7 @@ int DECLDIR
 MoorDyn_GetPointVel(MoorDynPoint point, double v[3])
 {
 	CHECK_POINT(point);
-	moordyn::vec r, rd;
-	((moordyn::Point*)point)->getState(r, rd);
+	moordyn::vec rd = ((moordyn::Point*)point)->getVelocity();
 	moordyn::vec2array(rd, v);
 	return MOORDYN_SUCCESS;
 }
@@ -555,12 +538,20 @@ int DECLDIR
 MoorDyn_GetPointForce(MoorDynPoint point, double f[3])
 {
 	CHECK_POINT(point);
-	moordyn::vec fnet;
-	((moordyn::Point*)point)->getFnet(fnet);
+	moordyn::vec fnet = ((moordyn::Point*)point)->getFnet();
 	moordyn::vec2array(fnet, f);
 	return MOORDYN_SUCCESS;
 }
 
+int DECLDIR
+MoorDyn_GetPointM(MoorDynPoint point, double m[3][3])
+{
+	CHECK_POINT(point);
+	moordyn::mat mass = ((moordyn::Point*)point)->getM();
+	moordyn::mat2array(mass, m);
+	return MOORDYN_SUCCESS;
+}
+
 int DECLDIR
 MoorDyn_GetPointNAttached(MoorDynPoint point, unsigned int* n)
 {
diff --git a/source/Point.h b/source/Point.h
index 03be3591..477119c0 100644
--- a/source/Point.h
+++ b/source/Point.h
@@ -93,6 +93,15 @@ extern "C"
 	 */
 	int DECLDIR MoorDyn_GetPointForce(MoorDynPoint point, double f[3]);
 
+	/** @brief Get the point mass matrix
+	 * @param point The Moordyn point
+	 * @param f The output force
+	 * @return MOORDYN_SUCCESS If the data is correctly set, an error code
+	 * otherwise
+	 * (see @ref moordyn_errors)
+	 */
+	int DECLDIR MoorDyn_GetPointM(MoorDynPoint point, double m[3][3]);
+
 	/** @brief Get the number of connected lines
 	 * @param point The Moordyn point
 	 * @param n The output number of connected lines
diff --git a/source/Point.hpp b/source/Point.hpp
index d2c65a1a..8882d872 100644
--- a/source/Point.hpp
+++ b/source/Point.hpp
@@ -247,7 +247,16 @@ class Point final : public io::IO
 	 */
 	std::pair<vec, vec> initialize();
 
+	/** @brief Get the point position
+	 * @return The position [x,y,z]
+	 */
 	inline const vec& getPosition() const { return r; }
+
+	/** @brief Get the point velocity
+	 * @return The velocity [x,y,z]
+	 */
+	inline const vec& getVelocity() const { return rd; }
+
 	/** @brief Get the point state
 	 * @param r_out The output position [x,y,z]
 	 * @param rd_out The output velocity [x,y,z]
@@ -266,12 +275,22 @@ class Point final : public io::IO
 	/** @brief Get the force on the point
 	 * @param Fnet_out The output force [x,y,z]
 	 */
-	void getFnet(vec& Fnet_out);
+	inline void getFnet(vec& Fnet_out) const { Fnet_out = Fnet; }
+
+	/** @brief Get the force on the point
+	 * @return The output force [x,y,z]
+	 */
+	inline const vec& getFnet() const { return Fnet; }
 
 	/** @brief Get the mass matrix
 	 * @param M_out The output mass matrix
 	 */
-	void getM(mat& M_out);
+	inline void getM(mat& M_out) const { M_out = M; }
+
+	/** @brief Get the mass matrix
+	 * @return The mass matrix
+	 */
+	inline const mat& getM() const { return M; };
 
 	/** @brief Get the output
 	 * @param outChan The query
diff --git a/source/Rod.cpp b/source/Rod.cpp
index fc352245..2cafc7b6 100644
--- a/source/Rod.cpp
+++ b/source/Rod.cpp
@@ -148,12 +148,13 @@ Rod::setup(int number_in,
 	if (type == FREE) {
 		// For an independent rod, set the position right off the bat
 		r7.pos = endCoords.head<3>();
-		r7.quat = quaternion::Identity();
+		r7.quat = quaternion::FromTwoVectors(q0, endCoords.tail<3>());
 		v6 = vec6::Zero();
 	} else if ((type == PINNED) || (type == CPLDPIN)) {
 		// for a pinned rod, just set the orientation (position will be set
-		// later by parent object)
-		r7 = XYZQuat::Zero();
+		// later by parent object) 
+		r7.pos = vec3::Zero(); 
+		r7.quat = quaternion::FromTwoVectors(q0, endCoords.tail<3>()); // TODO: Check this is right
 		v6(Eigen::seqN(3, 3)) = vec::Zero();
 	}
 	// otherwise (for a fixed rod) the positions will be set by the parent body
@@ -337,6 +338,12 @@ Rod::initialize()
 real
 Rod::GetRodOutput(OutChanProps outChan)
 {
+	
+	vec6 Fout;
+	if ((outChan.QType == FX)||(outChan.QType == FY)||(outChan.QType == FZ)||(outChan.QType == MX)||(outChan.QType == MY)||(outChan.QType == MZ))
+		Fout = getFnet();
+
+
 	if (outChan.NodeID == -1) {
 		if (outChan.QType == PosX)
 			return r7.pos.x();
@@ -373,17 +380,17 @@ Rod::GetRodOutput(OutChanProps outChan)
 		else if (outChan.QType == TenB)
 			return FextB.norm();
 		else if (outChan.QType == FX)
-			return F6net[0];
+			return Fout[0];
 		else if (outChan.QType == FY)
-			return F6net[1];
+			return Fout[1];
 		else if (outChan.QType == FZ)
-			return F6net[2];
+			return Fout[2];
 		else if (outChan.QType == MX)
-			return F6net[3];
+			return Fout[3];
 		else if (outChan.QType == MY)
-			return F6net[4];
+			return Fout[4];
 		else if (outChan.QType == MZ)
-			return F6net[5];
+			return Fout[5];
 		else if (outChan.QType == Sub) {
 			real VOFsum = 0.0;
 			for (unsigned int i = 0; i <= N; i++)
@@ -446,20 +453,22 @@ Rod::setState(XYZQuat pos, vec6 vel)
 }
 
 void
-Rod::initiateStep(vec6 rFairIn, vec6 rdFairIn)
+Rod::initiateStep(vec6 r_in, vec6 rd_in, vec6 rdd_in)
 {
 	if (type == COUPLED) {
 		// set Rod kinematics based on BCs (linear model for now)
-		r_ves = rFairIn;
-		rd_ves = rdFairIn;
+		r_ves = r_in;
+		rd_ves = rd_in;
+		rdd_ves = rdd_in;
 
 		// since this rod has no states and all DOFs have been set, pass its
 		// kinematics to dependent Lines
 		setDependentStates();
 	} else if (type == CPLDPIN) {
 		// set Rod *end A only* kinematics based on BCs (linear model for now)
-		r_ves(Eigen::seqN(0, 3)) = rFairIn(Eigen::seqN(0, 3));
-		rd_ves(Eigen::seqN(0, 3)) = rdFairIn(Eigen::seqN(0, 3));
+		r_ves(Eigen::seqN(0, 3)) = r_in(Eigen::seqN(0, 3));
+		rd_ves(Eigen::seqN(0, 3)) = rd_in(Eigen::seqN(0, 3));
+		rdd_ves(Eigen::seqN(0, 3)) = rdd_in(Eigen::seqN(0, 3));
 	} else {
 		LOGERR << "Invalid rod type: " << TypeName(type) << endl;
 		throw moordyn::invalid_value_error("Invalid rod type");
@@ -475,6 +484,7 @@ Rod::updateFairlead(real time)
 		// set Rod kinematics based on BCs (linear model for now)
 		r7 = XYZQuat::fromVec6(r_ves + rd_ves * time);
 		v6 = rd_ves;
+		acc6 = rdd_ves;
 
 		// since this rod has no states and all DOFs have been set, pass its
 		// kinematics to dependent Lines
@@ -483,6 +493,7 @@ Rod::updateFairlead(real time)
 		// set Rod *end A only* kinematics based on BCs (linear model for now)
 		r7.pos = r_ves(Eigen::seqN(0, 3)) + rd_ves(Eigen::seqN(0, 3)) * time;
 		v6(Eigen::seqN(0, 3)) = rd_ves(Eigen::seqN(0, 3));
+		acc6(Eigen::seqN(0, 3)) = rdd_ves(Eigen::seqN(0, 3));
 
 		// Rod is pinned so only end A is specified, rotations are left alone
 		// and will be handled, along with passing kinematics to dependent
@@ -505,7 +516,7 @@ Rod::setKinematics(vec6 r_in, vec6 rd_in)
 		// since this rod has no states and all DOFs have been set, pass its
 		// kinematics to dependent Lines
 		setDependentStates();
-	} else if (type == PINNED) // rod end A pinned to a body
+	} else if ((type == PINNED) || (type == CPLDPIN))// rod end A pinned to a body
 	{
 		// set Rod *end A only* kinematics based on BCs (linear model for now)
 		r7.pos = r_in(Eigen::seqN(0, 3));
@@ -650,72 +661,73 @@ Rod::getStateDeriv()
 			acc6 = solveMat6(M_out6, Fnet_out);
 		}
 	} else {
-		// Pinned rod, where the position rate of change is null (Dirichlet BC)
+		// Pinned rod
+
+		// account for moment in response to end A acceleration due to inertial coupling (off-diagonal sub-matrix terms)
+		const vec Fnet_out3 = Fnet_out(Eigen::seqN(3, 3)) - (M_out6.bottomLeftCorner<3,3>() * acc6(Eigen::seqN(0, 3)));
 
 		// For small systems it is usually faster to compute the inverse
 		// of the matrix. See
 		// https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html
-		const vec Fnet_out3 = Fnet_out(Eigen::seqN(3, 3));
 		const mat M_out3 = M_out6(Eigen::seqN(3, 3), Eigen::seqN(3, 3));
-		const vec acc = M_out3.inverse() * Fnet_out3;
+		acc6(Eigen::seqN(3, 3)) = M_out3.inverse() * Fnet_out3;
 
 		// dxdt = V   (velocities)
 		vel7.pos = vec::Zero();
 		vel7.quat =
 		    0.5 * (quaternion(0.0, v6[3], v6[4], v6[5]) * r7.quat).coeffs();
-		// dVdt = a   (accelerations)
-		acc6(Eigen::seqN(0, 3)) = vec::Zero();
-		acc6(Eigen::seqN(3, 3)) = acc;
 	}
 
 	return std::make_pair(vel7, acc6);
 }
 
-vec6
-Rod::getFnet()
+const vec6
+Rod::getFnet() const
 {
-	return F6net;
-	// >>>>>>>>>>>>> do I want to leverage getNetForceAndMass or a saved global
-	// to save comp time and code?  >>>>>>>> this function likely not used
-	// >>>>>>>>>>>
-
-	// Fnet_out is assumed to point to a size-3 array if the rod is pinned and
-	// size-6 array if the rod is fixed
+	// return F6net;
+	// // >>>>>>>>>>>>> do I want to leverage getNetForceAndMass or a saved global
+	// // to save comp time and code?  >>>>>>>> this function likely not used
+	// // >>>>>>>>>>>
 
-	int nDOF = 0;
+	// // Fnet_out is assumed to point to a size-3 array if the rod is pinned and
+	// // size-6 array if the rod is fixed
 
-	if (type == CPLDPIN) // if coupled pinned
-		nDOF = 3;
-	else if (type == COUPLED) // if coupled rigidly
-		nDOF = 6;
-	else {
-		LOGERR << "Invalid rod type: " << TypeName(type) << endl;
-		throw moordyn::invalid_value_error("Invalid rod type");
-	}
+	vec6 F6_iner = vec6::Zero();
+	vec6 Fnet_out = vec6::Zero();
 
 	// this assumes doRHS() has already been called
+	if (type == COUPLED) { // if coupled rigidly
+		F6_iner = - M6net * acc6; // Inertial terms
+		Fnet_out = F6net + F6_iner;
+	} else if (type == CPLDPIN) { // if coupled pinned
+		F6_iner(Eigen::seqN(0,3)) = (- M6net.topLeftCorner<3,3>() * acc6(Eigen::seqN(0,3))) - (M6net.topRightCorner<3,3>() * acc6(Eigen::seqN(3,3))); // Inertial term
+		Fnet_out(Eigen::seqN(0,3)) = F6net(Eigen::seqN(0,3)) + F6_iner(Eigen::seqN(0,3));
+		Fnet_out(Eigen::seqN(3,3)) = vec3::Zero();
+	} else {
+		// LOGERR << "Invalid rod type: " << TypeName(type) << endl;
+		// throw moordyn::invalid_value_error("Invalid rod type");
 
-	// make sure Fnet_out is zeroed first
-	vec6 Fnet_out = vec6::Zero();
+		Fnet_out = F6net;
+	}
 
-	// now go through each node's contributions, put them in body ref frame, and
-	// sum them
-	for (unsigned int i = 0; i <= N; i++) {
-		// position of a given node relative to the body reference point (global
-		// orientation frame)
-		vec rRel = r[i] - r[0];
+	// // now go through each node's contributions, put them in body ref frame, and
+	// // sum them
+	// for (unsigned int i = 0; i <= N; i++) {
+	// 	// position of a given node relative to the body reference point (global
+	// 	// orientation frame)
+	// 	vec rRel = r[i] - r[0];
 
-		vec6 F6_i; // 6dof force-moment from rod about body ref point (but
-		           // global orientation frame of course)
-		// convert segment net force into 6dof force about body ref point
-		F6_i.head<3>() = Fnet[i];
-		F6_i.tail<3>() = rRel.cross(Fnet[i]);
+	// 	vec6 F6_i; // 6dof force-moment from rod about body ref point (but
+	// 	           // global orientation frame of course)
+	// 	// convert segment net force into 6dof force about body ref point
+	// 	F6_i.head<3>() = Fnet[i];
+	// 	F6_i.tail<3>() = rRel.cross(Fnet[i]);
 
-		Fnet_out(Eigen::seqN(0, nDOF)) += F6_i(Eigen::seqN(0, nDOF));
-	}
+	// 	Fnet_out(Eigen::seqN(0, nDOF)) += F6_i(Eigen::seqN(0, nDOF));
+	// }
 
-	// add any moments applied from lines at either end (might be zero)
-	Fnet_out(Eigen::seqN(3, 3)) += Mext;
+	// // add any moments applied from lines at either end (might be zero)
+	// Fnet_out(Eigen::seqN(3, 3)) += Mext;
 
 	//  this is where we'd add inertial loads for coupled rods! <<<<<<<<<<<<
 
@@ -1665,6 +1677,24 @@ MoorDyn_GetRodType(MoorDynRod rod, int* t)
 	return MOORDYN_SUCCESS;
 }
 
+int DECLDIR
+MoorDyn_GetRodForce(MoorDynRod rod, double f[6])
+{
+	CHECK_ROD(rod);
+	moordyn::vec6 fnet = ((moordyn::Rod*)rod)->getFnet();
+	moordyn::vec62array(fnet, f);
+	return MOORDYN_SUCCESS;
+}
+
+int DECLDIR
+MoorDyn_GetRodM(MoorDynRod rod, double m[6][6])
+{
+	CHECK_ROD(rod);
+	moordyn::mat6 mass = ((moordyn::Rod*)rod)->getM();
+	moordyn::mat62array(mass, m);
+	return MOORDYN_SUCCESS;
+}
+
 int DECLDIR
 MoorDyn_GetRodN(MoorDynRod rod, unsigned int* n)
 {
@@ -1697,6 +1727,20 @@ MoorDyn_GetRodNodePos(MoorDynRod rod, unsigned int i, double pos[3])
 	return err;
 }
 
+int DECLDIR
+MoorDyn_GetRodNodeVel(MoorDynRod rod, unsigned int i, double vel[3])
+{
+	CHECK_ROD(rod);
+	moordyn::error_id err = MOORDYN_SUCCESS;
+	string err_msg;
+	try {
+		const moordyn::vec rd = ((moordyn::Rod*)rod)->getNodeVel(i);
+		moordyn::vec2array(rd, vel);
+	}
+	MOORDYN_CATCHER(err, err_msg);
+	return err;
+}
+
 int DECLDIR
 MoorDyn_SaveRodVTK(MoorDynRod l, const char* filename)
 {
diff --git a/source/Rod.h b/source/Rod.h
index 3e6d3506..75a5e198 100644
--- a/source/Rod.h
+++ b/source/Rod.h
@@ -63,6 +63,23 @@ extern "C"
 	 */
 	int DECLDIR MoorDyn_GetRodType(MoorDynRod l, int* t);
 
+	/** @brief Get the net force acting on the rod, as well as the moment at
+	 * end point A if the node is not pinned
+	 * @param l The Moordyn rod
+	 * @param f The output force
+	 * @return MOORDYN_INVALID_VALUE if a NULL point is provided,
+	 * MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetRodForce(MoorDynRod l, double f[6]);
+
+	/** @brief Get the total rod mass matrix
+	 * @param l The Moordyn rod
+	 * @param m The output mass matrix
+	 * @return MOORDYN_INVALID_VALUE if a NULL point is provided,
+	 * MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetRodM(MoorDynRod l, double m[6][6]);
+
 	/** @brief Get the line number of segments
 	 *
 	 * The number of nodes is equal to this value plus 1
@@ -82,7 +99,7 @@ extern "C"
 	 */
 	int DECLDIR MoorDyn_GetRodNumberNodes(MoorDynRod l, unsigned int* n);
 
-	/** @brief Get a line node position
+	/** @brief Get a rod node position
 	 * @param l The Moordyn rod
 	 * @param i The node index
 	 * @param pos The output node position
@@ -93,6 +110,17 @@ extern "C"
 	                                  unsigned int i,
 	                                  double pos[3]);
 
+	/** @brief Get a rod node velocity
+	 * @param l The Moordyn rod
+	 * @param i The node index
+	 * @param vel The output node velocity
+	 * @return MOORDYN_INVALID_VALUE if a NULL line is provided or if the node
+	 * index is bigger than the number of segments, MOORDYN_SUCCESS otherwise
+	 */
+	int DECLDIR MoorDyn_GetRodNodeVel(MoorDynRod l,
+	                                  unsigned int i,
+	                                  double vel[3]);
+
 	/** @brief Save the line to a VTK (.vtp) file
 	 * @param l The Moordyn rod
 	 * @param filename The output maximum tension module
diff --git a/source/Rod.hpp b/source/Rod.hpp
index ef5ff3c2..683e6812 100644
--- a/source/Rod.hpp
+++ b/source/Rod.hpp
@@ -203,6 +203,8 @@ class Rod final : public io::IO
 	vec6 r_ves;
 	/// fairlead velocity for coupled rods [x/y/z]
 	vec6 rd_ves;
+	/// fairlead acceleration for coupled rods [x/y/z]
+	vec6 rdd_ves;
 
 	// file stuff
 	/// Pointer to moordyn::MoorDyn::outfileMain
@@ -363,6 +365,14 @@ class Rod final : public io::IO
 			       << ", which only has " << N + 1 << " nodes" << std::endl;
 			throw moordyn::invalid_value_error("Invalid node index");
 		}
+		if (isnan(r[i].sum())) {
+			stringstream s;
+			s << "NaN detected" << endl
+			  << "Rod " << number << " node positions:" << endl;
+			for (unsigned int j = 0; j <= N; j++)
+				s << j << " : " << r[j] << ";" << endl;
+			throw moordyn::nan_error(s.str().c_str());
+		}
 		return r[i];
 	}
 
@@ -412,6 +422,7 @@ class Rod final : public io::IO
 	{
 		Cdn = Cdn * scaler;
 		Cdt = Cdt * scaler;
+		CdEnd = CdEnd * scaler;
 	}
 
 	/** @brief Set the line  simulation time
@@ -441,14 +452,15 @@ class Rod final : public io::IO
 
 	/** @brief Called at the beginning of each coupling step to update the
 	 * boundary conditions (rod kinematics) for the proceeding time steps
-	 * @param r The output position
-	 * @param rd The output velocity
+	 * @param r The input position
+	 * @param rd The input velocity
+	 * @param rdd The input velocity
 	 * @throw moordyn::invalid_value_error If the rod is not of type
 	 * moordyn::Rod::COUPLED or moordyn::Rod::CPLDPIN
 	 * @note If the rod is of type moordyn::Rod::CPLDPIN, then just 3 components
 	 * of @p r and @p rd are considered
 	 */
-	void initiateStep(vec6 r, vec6 rd);
+	void initiateStep(vec6 r_in, vec6 rd_in, vec6 rdd_in);
 
 	/** @brief Sets the kinematics
 	 *
@@ -488,13 +500,20 @@ class Rod final : public io::IO
 	 */
 	std::pair<XYZQuat, vec6> getStateDeriv();
 
-	/** @brief function to return net force on rod (and possibly moment at end A
-	 * if it's not pinned)
+	/** @brief Get the net force on rod (and possibly moment at end A if it's
+	 * not pinned)
+	 * @return The net force
+	 * @throw moordyn::invalid_value_error If the rod is not of type
+	 * moordyn::Rod::COUPLED or moordyn::Rod::CPLDPIN
+	 */
+	const vec6 getFnet() const;
+
+	/** @brief Get the rod mass matrix
 	 * @return The net force
 	 * @throw moordyn::invalid_value_error If the rod is not of type
 	 * moordyn::Rod::COUPLED or moordyn::Rod::CPLDPIN
 	 */
-	vec6 getFnet();
+	inline const mat6 getM() const { return M6net; }
 
 	/** @brief Calculate the force and mass contributions of the point on the
 	 * parent body
diff --git a/source/Time.cpp b/source/Time.cpp
index acffbc19..12aa4094 100644
--- a/source/Time.cpp
+++ b/source/Time.cpp
@@ -44,7 +44,7 @@ TimeSchemeBase<NSTATE, NDERIV>::Update(real t_local, unsigned int substep)
 
 	t_local += this->t_local;
 	for (auto obj : bodies) {
-		if (obj->type != Body::COUPLED)
+		if ((obj->type != Body::COUPLED) && (obj->type != Body::CPLDPIN))
 			continue;
 		obj->updateFairlead(t_local);
 	}
@@ -63,7 +63,7 @@ TimeSchemeBase<NSTATE, NDERIV>::Update(real t_local, unsigned int substep)
 	}
 
 	for (unsigned int i = 0; i < bodies.size(); i++) {
-		if (bodies[i]->type != Body::FREE)
+		if ((bodies[i]->type != Body::FREE) && (bodies[i]->type != Body::CPLDPIN))
 			continue;
 		bodies[i]->setState(r[substep].bodies[i].pos, r[substep].bodies[i].vel);
 	}
@@ -115,7 +115,7 @@ TimeSchemeBase<NSTATE, NDERIV>::CalcStateDeriv(unsigned int substep)
 	}
 
 	for (unsigned int i = 0; i < bodies.size(); i++) {
-		if (bodies[i]->type != Body::FREE)
+		if ((bodies[i]->type != Body::FREE) && (bodies[i]->type != Body::CPLDPIN))
 			continue;
 		std::tie(rd[substep].bodies[i].vel, rd[substep].bodies[i].acc) =
 		    bodies[i]->getStateDeriv();
@@ -127,7 +127,7 @@ TimeSchemeBase<NSTATE, NDERIV>::CalcStateDeriv(unsigned int substep)
 		obj->doRHS();
 	}
 	for (auto obj : rods) {
-		if ((obj->type != Rod::COUPLED) && (obj->type != Rod::CPLDPIN))
+		if (obj->type != Rod::COUPLED)
 			continue;
 		obj->doRHS();
 	}
diff --git a/source/Time.hpp b/source/Time.hpp
index 70446c9f..f8f2018e 100644
--- a/source/Time.hpp
+++ b/source/Time.hpp
@@ -494,7 +494,7 @@ class TimeSchemeBase : public TimeScheme
 		// integrator, no matter if they are free or not. Thus they can change
 		// types (mutate) without needing to micromanage them in the time scheme
 		for (unsigned int i = 0; i < bodies.size(); i++) {
-			if (bodies[i]->type != Body::FREE)
+			if ((bodies[i]->type != Body::FREE) && (bodies[i]->type != Body::CPLDPIN)) // Only fully coupled bodies are intialized in MD2.cpp
 				continue;
 			std::tie(r[0].bodies[i].pos, r[0].bodies[i].vel) =
 			    bodies[i]->initialize();
diff --git a/source/Waves.h b/source/Waves.h
index 35f69c8f..f3ebccc0 100644
--- a/source/Waves.h
+++ b/source/Waves.h
@@ -74,7 +74,7 @@ extern "C"
 	                                double Ud[3],
 	                                double* zeta,
 	                                double* PDyn,
-	                                MoorDynSeafloor seafloor = 0);
+	                                MoorDynSeafloor seafloor);
 
 	/** @brief Compute the wave number
 	 * @param Omega The wave angular frequency
diff --git a/wrappers/fortran/MoorDyn.f90 b/wrappers/fortran/MoorDyn.f90
index a906210b..56199cfa 100644
--- a/wrappers/fortran/MoorDyn.f90
+++ b/wrappers/fortran/MoorDyn.f90
@@ -31,32 +31,49 @@ module moordyn
              MoorDyn_ExternalWaveKinSet, MoorDyn_GetFASTtens, MoorDyn_Save, &
              MoorDyn_Load, MoorDyn_SaveVTK, &
              MoorDyn_GetWavesKin, &
-             MoorDyn_GetBodyState, MoorDyn_SaveBodyVTK, MoorDyn_UseBodyVTK, &
+             MoorDyn_GetBodyState, MoorDyn_GetBodyPos, MoorDyn_GetBodyAngle, &
+             MoorDyn_GetBodyVel, MoorDyn_GetBodyAngVel, MoorDyn_GetBodyForce, &
+             MoorDyn_GetBodyM, MoorDyn_SaveBodyVTK, MoorDyn_UseBodyVTK, &
+             MoorDyn_GetRodForce, MoorDyn_GetRodM, MoorDyn_GetRodNodePos, &
+             MoorDyn_GetRodNodeVel, MoorDyn_SaveRodVTK, &
              MoorDyn_GetPointPos, MoorDyn_GetPointVel, &
-             MoorDyn_GetPointForce, MoorDyn_SavePointVTK, &
-             MoorDyn_GetRodNodePos, MoorDyn_SaveRodVTK, &
-             MoorDyn_GetLineNodePos, MoorDyn_GetLineNodeTen, &
+             MoorDyn_GetPointForce, MoorDyn_GetPointM, MoorDyn_SavePointVTK, &
+             MoorDyn_GetLineNodePos, MoorDyn_GetLineNodeVel, &
+             MoorDyn_GetLineNodeForce, MoorDyn_GetLineNodeTen, &
+             MoorDyn_GetLineNodeBendStiff, MoorDyn_GetLineNodeWeight, &
+             MoorDyn_GetLineNodeDrag, MoorDyn_GetLineNodeFroudeKrilov, &
+             MoorDyn_GetLineNodeSeabedForce, MoorDyn_GetLineNodeM, &
              MoorDyn_SaveLineVTK
 
   public :: MD_Create, MD_NCoupledDOF, MD_SetVerbosity, MD_SetLogFile, &
             MD_SetLogLevel, MD_Log, MD_Init, MD_Init_NoIC, MD_Step, MD_Close, &
-            MD_GetWaves, MD_ExternalWaveKinInit, MD_ExternalWaveKinGetN, &
-            MD_ExternalWaveKinGetCoordinates, MD_ExternalWaveKinSet, &
-            MD_GetNumberBodies, MD_GetBody, MD_GetNumberRods, MD_GetRod, &
-            MD_GetNumberPoints, MD_GetPoint, MD_GetNumberLines, &
-            MD_GetLine, MD_GetFASTtens, MD_Save, MD_Load, MD_SaveVTK, &
+            MD_GetWaves, MD_GetSeafloor, MD_ExternalWaveKinInit, &
+            MD_ExternalWaveKinGetN, MD_ExternalWaveKinGetCoordinates, &
+            MD_ExternalWaveKinSet, MD_GetNumberBodies, MD_GetBody, &
+            MD_GetNumberRods, MD_GetRod, MD_GetNumberPoints, MD_GetPoint, &
+            MD_GetNumberLines, MD_GetLine, MD_GetFASTtens, MD_Save, MD_Load, &
+            MD_SaveVTK, &
             MD_GetWavesKin, &
-            MD_GetBodyID, MD_GetBodyType, MD_GetBodyState, MD_SaveBodyVTK, &
-            MD_UseBodyVTK, &
+            MD_GetDepthAt, MD_GetAverageDepth, MD_GetMinDepth, &
+            MD_GetBodyID, MD_GetBodyType, MD_GetBodyState, MD_GetBodyPos, &
+            MD_GetBodyAngle, MD_GetBodyVel, MD_GetBodyAngVel, &
+            MD_GetBodyForce, MD_GetBodyM, MD_SaveBodyVTK, MD_UseBodyVTK, &
+            MD_GetRodID, MD_GetRodType, MD_GetRodForce, MD_GetRodM, &
+            MD_GetRodN, MD_GetRodNumberNodes, MD_GetRodNodePos, &
+            MD_GetRodNodeVel, MD_SaveRodVTK, &
             MD_GetPointID, MD_GetPointType, MD_GetPointPos, &
-            MD_GetPointVel, MD_GetPointForce, MD_GetPointNAttached, &
-            MD_GetPointAttached, MD_SavePointVTK, &
-            MD_GetRodID, MD_GetRodType, MD_GetRodN, MD_GetRodNumberNodes, &
-            MD_GetRodNodePos, MD_SaveRodVTK, &
+            MD_GetPointVel, MD_GetPointForce, MD_GetPointM, &
+            MD_GetPointNAttached, MD_GetPointAttached, MD_SavePointVTK, &
             MD_GetLineID, MD_GetLineN, MD_GetLineNumberNodes, &
-            MD_GetLineUnstretchedLength, MD_GetLineNodePos, &
-            MD_GetLineNodeTen, MD_GetLineNodeCurv, MD_GetLineMaxTen, &
-            MD_GetLineFairTen, MD_SaveLineVTK
+            MD_GetLineUnstretchedLength, MD_SetLineUnstretchedLength, &
+            MD_SetLineUnstretchedLengthVel, MD_IsLineConstantEA, &
+            MD_GetLineConstantEA, MD_SetLineConstantEA, MD_GetLineNodePos, &
+            MD_GetLineNodeVel, MD_GetLineNodeForce, MD_GetLineNodeTen, &
+            MD_GetLineNodeBendStiff, MD_GetLineNodeWeight, &
+            MD_GetLineNodeDrag, MD_GetLineNodeFroudeKrilov, &
+            MD_GetLineNodeSeabedForce, MD_GetLineNodeCurv, &
+            MD_GetLineNodeM, MD_GetLineMaxTen, MD_GetLineFairTen, &
+            MD_SaveLineVTK
 
   interface
 
@@ -78,7 +95,7 @@ end function MD_NCoupledDOF
     integer(c_int) function MD_SetVerbosity(instance, n) bind(c, name='MD_SetVerbosity')
       import :: c_ptr, c_int
       type(c_ptr), value, intent(in) :: instance
-      integer(c_int), intent(out) :: n
+      integer(c_int), intent(in) :: n
     end function MD_SetVerbosity
 
     function MoorDyn_SetLogFile(instance, f) bind(c, name='MoorDyn_SetLogFile') result(rc)
@@ -91,7 +108,7 @@ end function MoorDyn_SetLogFile
     integer(c_int) function MD_SetLogLevel(instance, n) bind(c, name='MoorDyn_SetLogLevel')
       import :: c_ptr, c_int
       type(c_ptr), value, intent(in) :: instance
-      integer(c_int), intent(out) :: n
+      integer(c_int), intent(in) :: n
     end function MD_SetLogLevel
 
     function MoorDyn_Log(instance, n, f) bind(c, name='MoorDyn_Log') result(rc)
@@ -139,6 +156,11 @@ type(c_ptr) function MD_GetWaves(instance) bind(c, name='MoorDyn_GetWaves')
       type(c_ptr), value, intent(in) :: instance
     end function MD_GetWaves
 
+    type(c_ptr) function MD_GetSeafloor(instance) bind(c, name='MoorDyn_GetSeafloor')
+      import :: c_ptr
+      type(c_ptr), value, intent(in) :: instance
+    end function MD_GetSeafloor
+
     integer(c_int) function MD_ExternalWaveKinInit(instance, n) bind(c, name='MoorDyn_ExternalWaveKinInit')
       import :: c_ptr, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -250,7 +272,7 @@ end function MoorDyn_SaveVTK
     !                                Waves.h
     ! ==========================================================================
 
-    function MoorDyn_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn) bind(c, name='MoorDyn_GetWavesKin') result(rc)
+    function MoorDyn_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn, seafloor) bind(c, name='MoorDyn_GetWavesKin') result(rc)
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
       real(c_double), value, intent(in) :: x
@@ -261,9 +283,33 @@ function MoorDyn_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn) bind(c, na
       type(c_ptr), value, intent(in) :: ud
       real(c_double), intent(out) :: zeta
       real(c_double), intent(out) :: pdyn
+      type(c_ptr), value, intent(in) :: seafloor
       integer(c_int) :: rc
     end function MoorDyn_GetWavesKin
 
+    !                                Seafloor.h
+    ! ============================================================================
+
+    integer(c_int) function MD_GetDepthAt(instance, x, y, depth) bind(c, name='MoorDyn_GetDepthAt')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), value, intent(in) :: x
+      real(c_double), value, intent(in) :: y
+      real(c_double), intent(out) :: depth
+    end function MD_GetDepthAt
+
+    integer(c_int) function MD_GetAverageDepth(instance, depth) bind(c, name='MoorDyn_GetAverageDepth')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), intent(out) :: depth
+    end function MD_GetAverageDepth
+
+    integer(c_int) function MD_GetMinDepth(instance, depth) bind(c, name='MoorDyn_GetMinDepth')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), intent(out) :: depth
+    end function MD_GetMinDepth
+
     !                                Body.h
     ! ==========================================================================
 
@@ -287,6 +333,48 @@ function MoorDyn_GetBodyState(instance, r, rd) bind(c, name='MoorDyn_GetBodyStat
       integer(c_int) :: rc
     end function MoorDyn_GetBodyState
 
+    function MoorDyn_GetBodyPos(instance, r) bind(c, name='MoorDyn_GetBodyPos') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyPos
+
+    function MoorDyn_GetBodyAngle(instance, r) bind(c, name='MoorDyn_GetBodyAngle') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyAngle
+
+    function MoorDyn_GetBodyVel(instance, r) bind(c, name='MoorDyn_GetBodyVel') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyVel
+
+    function MoorDyn_GetBodyAngVel(instance, r) bind(c, name='MoorDyn_GetBodyAngVel') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyAngVel
+
+    function MoorDyn_GetBodyForce(instance, f) bind(c, name='MoorDyn_GetBodyForce') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: f
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyForce
+
+    function MoorDyn_GetBodyM(instance, m) bind(c, name='MoorDyn_GetBodyM') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: m
+      integer(c_int) :: rc
+    end function MoorDyn_GetBodyM
+
     function MoorDyn_SaveBodyVTK(instance, f) bind(c, name='MoorDyn_SaveBodyVTK') result(rc)
       import :: c_ptr, c_char, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -316,6 +404,20 @@ integer(c_int) function MD_GetRodType(instance, n) bind(c, name='MoorDyn_GetRodT
       integer(c_int), intent(out) :: n
     end function MD_GetRodType
 
+    function MoorDyn_GetRodForce(instance, f) bind(c, name='MoorDyn_GetRodForce') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: f
+      integer(c_int) :: rc
+    end function MoorDyn_GetRodForce
+
+    function MoorDyn_GetRodM(instance, m) bind(c, name='MoorDyn_GetRodM') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: m
+      integer(c_int) :: rc
+    end function MoorDyn_GetRodM
+
     integer(c_int) function MD_GetRodN(instance, n) bind(c, name='MoorDyn_GetRodN')
       import :: c_ptr, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -336,6 +438,14 @@ function MoorDyn_GetRodNodePos(instance, n, r) bind(c, name='MoorDyn_GetRodNodeP
       integer(c_int) :: rc
     end function MoorDyn_GetRodNodePos
 
+    function MoorDyn_GetRodNodeVel(instance, n, r) bind(c, name='MoorDyn_GetRodNodeVel') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetRodNodeVel
+
     function MoorDyn_SaveRodVTK(instance, f) bind(c, name='MoorDyn_SaveRodVTK') result(rc)
       import :: c_ptr, c_char, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -379,6 +489,13 @@ function MoorDyn_GetPointForce(instance, r) bind(c, name='MoorDyn_GetPointForce'
       integer(c_int) :: rc
     end function MoorDyn_GetPointForce
 
+    function MoorDyn_GetPointM(instance, m) bind(c, name='MoorDyn_GetPointM') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      type(c_ptr), value, intent(in) :: m
+      integer(c_int) :: rc
+    end function MoorDyn_GetPointM
+
     integer(c_int) function MD_GetPointNAttached(instance, n) bind(c, name='MoorDyn_GetPointNAttached')
       import :: c_ptr, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -421,12 +538,42 @@ integer(c_int) function MD_GetLineNumberNodes(instance, n) bind(c, name='MoorDyn
       integer(c_int), intent(out) :: n
     end function MD_GetLineNumberNodes
 
+    integer(c_int) function MD_IsLineConstantEA(instance, t) bind(c, name='MoorDyn_IsLineConstantEA')
+      import :: c_ptr, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), intent(out) :: t
+    end function MD_IsLineConstantEA
+
+    integer(c_int) function MD_GetLineConstantEA(instance, ea) bind(c, name='MoorDyn_GetLineConstantEA')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), intent(out) :: ea
+    end function MD_GetLineConstantEA
+
+    integer(c_int) function MD_SetLineConstantEA(instance, ea) bind(c, name='MoorDyn_SetLineConstantEA')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), intent(out) :: ea
+    end function MD_SetLineConstantEA
+
     integer(c_int) function MD_GetLineUnstretchedLength(instance, l) bind(c, name='MoorDyn_GetLineUnstretchedLength')
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
       real(c_double), intent(out) :: l
     end function MD_GetLineUnstretchedLength
 
+    integer(c_int) function MD_SetLineUnstretchedLength(instance, l) bind(c, name='MoorDyn_SetLineUnstretchedLength')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), value, intent(in) :: l
+    end function MD_SetLineUnstretchedLength
+
+    integer(c_int) function MD_SetLineUnstretchedLengthVel(instance, l) bind(c, name='MoorDyn_SetLineUnstretchedLengthVel')
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      real(c_double), value, intent(in) :: l
+    end function MD_SetLineUnstretchedLengthVel
+
     function MoorDyn_GetLineNodePos(instance, n, r) bind(c, name='MoorDyn_GetLineNodePos') result(rc)
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -435,6 +582,22 @@ function MoorDyn_GetLineNodePos(instance, n, r) bind(c, name='MoorDyn_GetLineNod
       integer(c_int) :: rc
     end function MoorDyn_GetLineNodePos
 
+    function MoorDyn_GetLineNodeVel(instance, n, r) bind(c, name='MoorDyn_GetLineNodeVel') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeVel
+
+    function MoorDyn_GetLineNodeForce(instance, n, r) bind(c, name='MoorDyn_GetLineNodeForce') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeForce
+
     function MoorDyn_GetLineNodeTen(instance, n, r) bind(c, name='MoorDyn_GetLineNodeTen') result(rc)
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -443,6 +606,46 @@ function MoorDyn_GetLineNodeTen(instance, n, r) bind(c, name='MoorDyn_GetLineNod
       integer(c_int) :: rc
     end function MoorDyn_GetLineNodeTen
 
+    function MoorDyn_GetLineNodeBendStiff(instance, n, r) bind(c, name='MoorDyn_GetLineNodeBendStiff') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeBendStiff
+
+    function MoorDyn_GetLineNodeWeight(instance, n, r) bind(c, name='MoorDyn_GetLineNodeWeight') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeWeight
+
+    function MoorDyn_GetLineNodeDrag(instance, n, r) bind(c, name='MoorDyn_GetLineNodeDrag') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeDrag
+
+    function MoorDyn_GetLineNodeFroudeKrilov(instance, n, r) bind(c, name='MoorDyn_GetLineNodeFroudeKrilov') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeFroudeKrilov
+
+    function MoorDyn_GetLineNodeSeabedForce(instance, n, r) bind(c, name='MoorDyn_GetLineNodeSeabedForce') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: r
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeSeabedForce
+
     integer(c_int) function MD_GetLineNodeCurv(instance, n, r) bind(c, name='MoorDyn_GetLineNodeCurv') result(rc)
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -450,6 +653,14 @@ integer(c_int) function MD_GetLineNodeCurv(instance, n, r) bind(c, name='MoorDyn
       real(c_double), intent(out) :: r
     end function MD_GetLineNodeCurv
 
+    function MoorDyn_GetLineNodeM(instance, n, m) bind(c, name='MoorDyn_GetLineNodeM') result(rc)
+      import :: c_ptr, c_double, c_int
+      type(c_ptr), value, intent(in) :: instance
+      integer(c_int), value, intent(in) :: n
+      type(c_ptr), value, intent(in) :: m
+      integer(c_int) :: rc
+    end function MoorDyn_GetLineNodeM
+
     integer(c_int) function MD_GetLineFairTen(instance, r) bind(c, name='MoorDyn_GetLineFairTen') result(rc)
       import :: c_ptr, c_double, c_int
       type(c_ptr), value, intent(in) :: instance
@@ -575,7 +786,7 @@ end function MD_SaveVTK
   !                                Waves.h
   ! ============================================================================
 
-  integer function MD_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn)
+  integer function MD_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn, seafloor)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
     real(c_double), value, intent(in) :: x
@@ -586,7 +797,8 @@ integer function MD_GetWavesKin(instance, x, y, z, t, u, ud, zeta, pdyn)
     real(c_double), intent(in), target :: ud(:)
     real(c_double), intent(out) :: zeta
     real(c_double), intent(out) :: pdyn
-    MD_GetWavesKin = MoorDyn_GetWavesKin(instance, x, y, z, t, c_loc(u), c_loc(ud), zeta, pdyn)
+    type(c_ptr), intent(in) :: seafloor
+    MD_GetWavesKin = MoorDyn_GetWavesKin(instance, x, y, z, t, c_loc(u), c_loc(ud), zeta, pdyn, seafloor)
   end function MD_GetWavesKin
 
   !                                Body.h
@@ -600,6 +812,48 @@ integer function MD_GetBodyState(instance, r, rd)
     MD_GetBodyState = MoorDyn_GetBodyState(instance, c_loc(r), c_loc(rd))
   end function MD_GetBodyState
 
+  integer function MD_GetBodyPos(instance, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: r(:)
+    MD_GetBodyPos = MoorDyn_GetBodyPos(instance, c_loc(r))
+  end function MD_GetBodyPos
+
+  integer function MD_GetBodyAngle(instance, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: r(:)
+    MD_GetBodyAngle = MoorDyn_GetBodyAngle(instance, c_loc(r))
+  end function MD_GetBodyAngle
+
+  integer function MD_GetBodyVel(instance, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: r(:)
+    MD_GetBodyVel = MoorDyn_GetBodyVel(instance, c_loc(r))
+  end function MD_GetBodyVel
+
+  integer function MD_GetBodyAngVel(instance, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: r(:)
+    MD_GetBodyAngVel = MoorDyn_GetBodyAngVel(instance, c_loc(r))
+  end function MD_GetBodyAngVel
+
+  integer function MD_GetBodyForce(instance, f)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: f(:)
+    MD_GetBodyForce = MoorDyn_GetBodyForce(instance, c_loc(f))
+  end function MD_GetBodyForce
+
+  integer function MD_GetBodyM(instance, m)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: m(:)
+    MD_GetBodyM = MoorDyn_GetBodyM(instance, c_loc(m))
+  end function MD_GetBodyM
+
   integer function MD_SaveBodyVTK(instance, f)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
@@ -617,6 +871,20 @@ end function MD_UseBodyVTK
   !                                Rod.h
   ! ============================================================================
 
+  integer function MD_GetRodForce(instance, f)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: f(:)
+    MD_GetRodForce = MoorDyn_GetRodForce(instance, c_loc(f))
+  end function MD_GetRodForce
+
+  integer function MD_GetRodM(instance, m)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: m(:)
+    MD_GetRodM = MoorDyn_GetRodM(instance, c_loc(m))
+  end function MD_GetRodM
+
   integer function MD_GetRodNodePos(instance, n, r)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
@@ -625,6 +893,14 @@ integer function MD_GetRodNodePos(instance, n, r)
     MD_GetRodNodePos = MoorDyn_GetRodNodePos(instance, n, c_loc(r))
   end function MD_GetRodNodePos
 
+  integer function MD_GetRodNodeVel(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetRodNodeVel = MoorDyn_GetRodNodeVel(instance, n, c_loc(r))
+  end function MD_GetRodNodeVel
+
   integer function MD_SaveRodVTK(instance, f)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
@@ -656,6 +932,13 @@ integer function MD_GetPointForce(instance, r)
     MD_GetPointForce = MoorDyn_GetPointForce(instance, c_loc(r))
   end function MD_GetPointForce
 
+  integer function MD_GetPointM(instance, m)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    real(c_double), intent(in), target :: m(:)
+    MD_GetPointM = MoorDyn_GetPointM(instance, c_loc(m))
+  end function MD_GetPointM
+
   integer function MD_SavePointVTK(instance, f)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
@@ -674,6 +957,22 @@ integer function MD_GetLineNodePos(instance, n, r)
     MD_GetLineNodePos = MoorDyn_GetLineNodePos(instance, n, c_loc(r))
   end function MD_GetLineNodePos
 
+  integer function MD_GetLineNodeVel(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeVel = MoorDyn_GetLineNodeVel(instance, n, c_loc(r))
+  end function MD_GetLineNodeVel
+
+  integer function MD_GetLineNodeForce(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeForce = MoorDyn_GetLineNodeForce(instance, n, c_loc(r))
+  end function MD_GetLineNodeForce
+
   integer function MD_GetLineNodeTen(instance, n, r)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
@@ -682,6 +981,54 @@ integer function MD_GetLineNodeTen(instance, n, r)
     MD_GetLineNodeTen = MoorDyn_GetLineNodeTen(instance, n, c_loc(r))
   end function MD_GetLineNodeTen
 
+  integer function MD_GetLineNodeBendStiff(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeBendStiff = MoorDyn_GetLineNodeBendStiff(instance, n, c_loc(r))
+  end function MD_GetLineNodeBendStiff
+
+  integer function MD_GetLineNodeWeight(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeWeight = MoorDyn_GetLineNodeWeight(instance, n, c_loc(r))
+  end function MD_GetLineNodeWeight
+
+  integer function MD_GetLineNodeDrag(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeDrag = MoorDyn_GetLineNodeDrag(instance, n, c_loc(r))
+  end function MD_GetLineNodeDrag
+
+  integer function MD_GetLineNodeFroudeKrilov(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeFroudeKrilov = MoorDyn_GetLineNodeFroudeKrilov(instance, n, c_loc(r))
+  end function MD_GetLineNodeFroudeKrilov
+
+  integer function MD_GetLineNodeSeabedForce(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeSeabedForce = MoorDyn_GetLineNodeSeabedForce(instance, n, c_loc(r))
+  end function MD_GetLineNodeSeabedForce
+
+  integer function MD_GetLineNodeM(instance, n, r)
+    use iso_c_binding
+    type(c_ptr), intent(in) :: instance
+    integer(c_int), value, intent(in) :: n
+    real(c_double), intent(in), target :: r(:)
+    MD_GetLineNodeM = MoorDyn_GetLineNodeM(instance, n, c_loc(r))
+  end function MD_GetLineNodeM
+
   integer function MD_SaveLineVTK(instance, f)
     use iso_c_binding
     type(c_ptr), intent(in) :: instance
diff --git a/wrappers/matlab/MoorDynM_GetAverageDepth.cpp b/wrappers/matlab/MoorDynM_GetAverageDepth.cpp
new file mode 100644
index 00000000..27142b99
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetAverageDepth.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynSeafloor, 1, 1)
+{
+	double depth;
+	const int err = MoorDyn_GetAverageDepth(instance, &depth);
+	MOORDYNM_CHECK_ERROR(err);
+	outputs[0] = factory.createScalar<double>(depth);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyAngVel.cpp b/wrappers/matlab/MoorDynM_GetBodyAngVel.cpp
new file mode 100644
index 00000000..4b34343f
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyAngVel.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	std::vector<double> r(3, 0.0);
+	const int err = MoorDyn_GetBodyAngVel(instance, r.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, r.size() }, r.data(), r.data() + r.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyAngle.cpp b/wrappers/matlab/MoorDynM_GetBodyAngle.cpp
new file mode 100644
index 00000000..abf26484
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyAngle.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	std::vector<double> r(3, 0.0);
+	const int err = MoorDyn_GetBodyAngle(instance, r.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, r.size() }, r.data(), r.data() + r.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyForce.cpp b/wrappers/matlab/MoorDynM_GetBodyForce.cpp
new file mode 100644
index 00000000..5c89afdd
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyForce.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	std::vector<double> f(6, 0.0);
+	const int err = MoorDyn_GetBodyForce(instance, f.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, f.size() }, f.data(), f.data() + f.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyM.cpp b/wrappers/matlab/MoorDynM_GetBodyM.cpp
new file mode 100644
index 00000000..cd97901f
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyM.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	double m[6][6];
+	const int err = MoorDyn_GetBodyM(instance, m);
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 6, 6 }, &(m[0][0]), &(m[0][0]) + 6 * 6);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyPos.cpp b/wrappers/matlab/MoorDynM_GetBodyPos.cpp
new file mode 100644
index 00000000..0850c0e0
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyPos.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	std::vector<double> r(3, 0.0);
+	const int err = MoorDyn_GetBodyPos(instance, r.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, r.size() }, r.data(), r.data() + r.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetBodyVel.cpp b/wrappers/matlab/MoorDynM_GetBodyVel.cpp
new file mode 100644
index 00000000..e828df53
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetBodyVel.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynBody, 1, 1)
+{
+	std::vector<double> r(3, 0.0);
+	const int err = MoorDyn_GetBodyVel(instance, r.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, r.size() }, r.data(), r.data() + r.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetDepthAt.cpp b/wrappers/matlab/MoorDynM_GetDepthAt.cpp
new file mode 100644
index 00000000..f8f8ab8f
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetDepthAt.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynSeafloor, 3, 1)
+{
+	const double x = inputs[1][0];
+	const double y = inputs[2][0];
+	double depth;
+	const int err = MoorDyn_GetDepthAt(instance, x, y, &depth);
+	MOORDYNM_CHECK_ERROR(err);
+	outputs[0] = factory.createScalar<double>(depth);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineConstantEA.cpp b/wrappers/matlab/MoorDynM_GetLineConstantEA.cpp
new file mode 100644
index 00000000..91c84699
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineConstantEA.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 1, 1)
+{
+	double EA;
+	const int err = MoorDyn_GetLineConstantEA(instance, &EA);
+	MOORDYNM_CHECK_ERROR(err);
+	outputs[0] = factory.createScalar<double>(EA);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeBendStiff.cpp b/wrappers/matlab/MoorDynM_GetLineNodeBendStiff.cpp
new file mode 100644
index 00000000..cef0bec5
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeBendStiff.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeBendStiff(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeDrag.cpp b/wrappers/matlab/MoorDynM_GetLineNodeDrag.cpp
new file mode 100644
index 00000000..f07719fc
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeDrag.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeDrag(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeForce.cpp b/wrappers/matlab/MoorDynM_GetLineNodeForce.cpp
new file mode 100644
index 00000000..1b9e4616
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeForce.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeForce(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeFroudeKrilov.cpp b/wrappers/matlab/MoorDynM_GetLineNodeFroudeKrilov.cpp
new file mode 100644
index 00000000..71b53c49
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeFroudeKrilov.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeFroudeKrilov(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeM.cpp b/wrappers/matlab/MoorDynM_GetLineNodeM.cpp
new file mode 100644
index 00000000..1ec0bfa0
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeM.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	double m[3][3];
+	const int err = MoorDyn_GetLineNodeM(instance, i, m);
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 3, 3 }, &(m[0][0]), &(m[0][0]) + 3 * 3);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeSeabedForce.cpp b/wrappers/matlab/MoorDynM_GetLineNodeSeabedForce.cpp
new file mode 100644
index 00000000..1e23918c
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeSeabedForce.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeSeabedForce(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeVel.cpp b/wrappers/matlab/MoorDynM_GetLineNodeVel.cpp
new file mode 100644
index 00000000..648015e1
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeVel.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeVel(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetLineNodeWeight.cpp b/wrappers/matlab/MoorDynM_GetLineNodeWeight.cpp
new file mode 100644
index 00000000..1b3e63c8
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetLineNodeWeight.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetLineNodeWeight(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetMinDepth.cpp b/wrappers/matlab/MoorDynM_GetMinDepth.cpp
new file mode 100644
index 00000000..c88abc9c
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetMinDepth.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynSeafloor, 1, 1)
+{
+	double depth;
+	const int err = MoorDyn_GetMinDepth(instance, &depth);
+	MOORDYNM_CHECK_ERROR(err);
+	outputs[0] = factory.createScalar<double>(depth);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetPointM.cpp b/wrappers/matlab/MoorDynM_GetPointM.cpp
new file mode 100644
index 00000000..19321ba9
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetPointM.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynPoint, 1, 1)
+{
+	double m[3][3];
+	const int err = MoorDyn_GetPointM(instance, m);
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 3, 3 }, &(m[0][0]), &(m[0][0]) + 3 * 3);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetRodForce.cpp b/wrappers/matlab/MoorDynM_GetRodForce.cpp
new file mode 100644
index 00000000..2020ab14
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetRodForce.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynRod, 1, 1)
+{
+	std::vector<double> f(6, 0.0);
+	const int err = MoorDyn_GetRodForce(instance, f.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, f.size() }, f.data(), f.data() + f.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetRodM.cpp b/wrappers/matlab/MoorDynM_GetRodM.cpp
new file mode 100644
index 00000000..4275f5ff
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetRodM.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynRod, 1, 1)
+{
+	double m[6][6];
+	const int err = MoorDyn_GetRodM(instance, m);
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 6, 6 }, &(m[0][0]), &(m[0][0]) + 6 * 6);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetRodNodeVel.cpp b/wrappers/matlab/MoorDynM_GetRodNodeVel.cpp
new file mode 100644
index 00000000..a01c05f0
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetRodNodeVel.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynRod, 2, 1)
+{
+	const unsigned int i = inputs[1][0];
+	std::vector<double> v(3, 0.0);
+	const int err = MoorDyn_GetRodNodeVel(instance, i, v.data());
+	MOORDYNM_CHECK_ERROR(err);
+
+	outputs[0] = factory.createArray<double>(
+	    { 1, v.size() }, v.data(), v.data() + v.size());
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetSeafloor.cpp b/wrappers/matlab/MoorDynM_GetSeafloor.cpp
new file mode 100644
index 00000000..97c2e1a7
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_GetSeafloor.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDyn, 1, 1)
+{
+	MoorDynSeafloor seafloor = MoorDyn_GetSeafloor(instance);
+	outputs[0] = factory.createScalar<uint64_t>(encode_ptr((void*)seafloor));
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_GetWavesKin.cpp b/wrappers/matlab/MoorDynM_GetWavesKin.cpp
index e825753d..19b7af27 100644
--- a/wrappers/matlab/MoorDynM_GetWavesKin.cpp
+++ b/wrappers/matlab/MoorDynM_GetWavesKin.cpp
@@ -37,17 +37,18 @@
 using namespace matlab::data;
 using matlab::mex::ArgumentList;
 
-MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynWaves, 4, 4)
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynWaves, 5, 4)
 {
 	const double x = inputs[1][0];
 	const double y = inputs[2][0];
 	const double z = inputs[3][0];
-
+	const uint64_t seafloor_ptr = inputs[4][0];
+	MoorDynSeafloor seafloor = (MoorDynSeafloor)decode_ptr(seafloor_ptr);
 	std::vector<double> u(3, 0.0);
 	std::vector<double> ud(3, 0.0);
 	double zeta, pdyn;
 	const int err = MoorDyn_GetWavesKin(
-	    instance, x, y, z, u.data(), ud.data(), &zeta, &pdyn);
+	    instance, x, y, z, u.data(), ud.data(), &zeta, &pdyn, seafloor);
 	MOORDYNM_CHECK_ERROR(err);
 
 	outputs[0] = factory.createArray<double>(
diff --git a/wrappers/matlab/MoorDynM_IsLineConstantEA.cpp b/wrappers/matlab/MoorDynM_IsLineConstantEA.cpp
new file mode 100644
index 00000000..fc619fa0
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_IsLineConstantEA.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 1, 1)
+{
+	int b;
+	const int err = MoorDyn_IsLineConstantEA(instance, &b);
+	MOORDYNM_CHECK_ERROR(err);
+	outputs[0] = factory.createScalar<int64_t>(b);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/matlab/MoorDynM_SetLineConstantEA.cpp b/wrappers/matlab/MoorDynM_SetLineConstantEA.cpp
new file mode 100644
index 00000000..7b54fa04
--- /dev/null
+++ b/wrappers/matlab/MoorDynM_SetLineConstantEA.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022, Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mex.hpp"
+#include "mexAdapter.hpp"
+
+#include "MoorDyn2.h"
+#include "moordyn_matlab.h"
+
+using namespace matlab::data;
+using matlab::mex::ArgumentList;
+
+MOORDYNM_MEX_FUNCTION_BEGIN(MoorDynLine, 2, 0)
+{
+	const double EA = inputs[1][0];
+	const int err = MoorDyn_SetLineConstantEA(instance, EA);
+	MOORDYNM_CHECK_ERROR(err);
+}
+MOORDYNM_MEX_FUNCTION_END
diff --git a/wrappers/python/cmoordyn.cpp b/wrappers/python/cmoordyn.cpp
index d6b10d03..e498cde2 100644
--- a/wrappers/python/cmoordyn.cpp
+++ b/wrappers/python/cmoordyn.cpp
@@ -35,6 +35,7 @@
 
 const char moordyn_capsule_name[] = "MoorDyn";
 const char waves_capsule_name[] = "MoorDynWaves";
+const char seafloor_capsule_name[] = "MoorDynSeafloor";
 const char body_capsule_name[] = "MoorDynBody";
 const char rod_capsule_name[] = "MoorDynRod";
 const char point_capsule_name[] = "MoorDynPoint";
@@ -396,6 +397,32 @@ get_waves(PyObject*, PyObject* args)
 	return PyCapsule_New((void*)waves, waves_capsule_name, NULL);
 }
 
+/** @brief Wrapper to MoorDyn_GetSeafloor() function
+ * @param args Python passed arguments
+ * @return A Python capsule
+ */
+static PyObject*
+get_seafloor(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDyn system =
+	    (MoorDyn)PyCapsule_GetPointer(capsule, moordyn_capsule_name);
+	if (!system)
+		return NULL;
+
+	MoorDynSeafloor seafloor = MoorDyn_GetSeafloor(system);
+	if (!seafloor) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn_GetSeafloor() failed");
+		return NULL;
+	}
+
+	return PyCapsule_New((void*)seafloor, seafloor_capsule_name, NULL);
+}
+
 /** @brief Wrapper to MoorDyn_ExternalWaveKinInit() function
  * @param args Python passed arguments
  * @return 0 in case of success, an error code otherwise
@@ -995,8 +1022,9 @@ waves_getkin(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 	double x, y, z;
+	PyObject* seafloor;
 
-	if (!PyArg_ParseTuple(args, "Oddd", &capsule, &x, &y, &z))
+	if (!PyArg_ParseTuple(args, "Oddd|O", &capsule, &x, &y, &z, &seafloor))
 		return NULL;
 
 	MoorDynWaves instance =
@@ -1004,8 +1032,17 @@ waves_getkin(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
+	MoorDynSeafloor seabed = NULL;
+	if (seafloor != Py_None) {
+		seabed = (MoorDynSeafloor)PyCapsule_GetPointer(
+			seafloor, seafloor_capsule_name);
+		if (!seabed)
+			return NULL;
+	}
+
 	double u[3], ud[3], zeta, pdyn;
-	const int err = MoorDyn_GetWavesKin(instance, x, y, z, u, ud, &zeta, &pdyn);
+	const int err = MoorDyn_GetWavesKin(
+		instance, x, y, z, u, ud, &zeta, &pdyn, seabed);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
@@ -1026,6 +1063,91 @@ waves_getkin(PyObject*, PyObject* args)
 	return lst;
 }
 
+//                                 Seafloor.h
+// =============================================================================
+
+/** @brief Wrapper to MoorDyn_GetDepthAt() function
+ * @param args Python passed arguments
+ * @return The depth
+ */
+static PyObject*
+seafloor_getdepth(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	double x, y;
+
+	if (!PyArg_ParseTuple(args, "Odd", &capsule, &x, &y))
+		return NULL;
+
+	MoorDynSeafloor instance =
+	    (MoorDynSeafloor)PyCapsule_GetPointer(capsule, seafloor_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double depth;
+	const int err = MoorDyn_GetDepthAt(instance, x, y, &depth);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyFloat_FromDouble(depth);
+}
+
+/** @brief Wrapper to MoorDyn_GetAverageDepth() function
+ * @param args Python passed arguments
+ * @return The depth
+ */
+static PyObject*
+seafloor_getavgdepth(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynSeafloor instance =
+	    (MoorDynSeafloor)PyCapsule_GetPointer(capsule, seafloor_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double depth;
+	const int err = MoorDyn_GetAverageDepth(instance, &depth);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyFloat_FromDouble(depth);
+}
+
+/** @brief Wrapper to MoorDyn_GetMinDepth() function
+ * @param args Python passed arguments
+ * @return The depth
+ */
+static PyObject*
+seafloor_getmindepth(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynSeafloor instance =
+	    (MoorDynSeafloor)PyCapsule_GetPointer(capsule, seafloor_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double depth;
+	const int err = MoorDyn_GetMinDepth(instance, &depth);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyFloat_FromDouble(depth);
+}
+
 //                                 Body.h
 // =============================================================================
 
@@ -1107,10 +1229,10 @@ body_get_state(PyObject*, PyObject* args)
 		return NULL;
 	}
 
-	PyObject* lst = PyTuple_New(4);
-	PyObject* pyr = PyTuple_New(3);
-	PyObject* pyrd = PyTuple_New(3);
-	for (unsigned int i = 0; i < 3; i++) {
+	PyObject* lst = PyTuple_New(2);
+	PyObject* pyr = PyTuple_New(6);
+	PyObject* pyrd = PyTuple_New(6);
+	for (unsigned int i = 0; i < 6; i++) {
 		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
 		PyTuple_SET_ITEM(pyrd, i, PyFloat_FromDouble(rd[i]));
 	}
@@ -1120,17 +1242,16 @@ body_get_state(PyObject*, PyObject* args)
 	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_SaveBodyVTK() function
+/** @brief Wrapper to MoorDyn_GetBodyPos() function
  * @param args Python passed arguments
- * @return 0 in case of success, an error code otherwise
+ * @return The velocity and the acceleration
  */
 static PyObject*
-body_save_vtk(PyObject*, PyObject* args)
+body_get_pos(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	char* filepath = NULL;
 
-	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
 	MoorDynBody instance =
@@ -1138,20 +1259,31 @@ body_save_vtk(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	return PyLong_FromLong(MoorDyn_SaveBodyVTK(instance, filepath));
+	double r[3];
+	const int err = MoorDyn_GetBodyPos(instance, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(r[i]));
+	}
+
+	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_UseBodyVTK() function
+/** @brief Wrapper to MoorDyn_GetBodyAngle() function
  * @param args Python passed arguments
- * @return 0 in case of success, an error code otherwise
+ * @return The velocity and the acceleration
  */
 static PyObject*
-body_use_vtk(PyObject*, PyObject* args)
+body_get_angle(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	char* filepath = NULL;
 
-	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
 	MoorDynBody instance =
@@ -1159,131 +1291,159 @@ body_use_vtk(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	return PyLong_FromLong(MoorDyn_UseBodyVTK(instance, filepath));
-}
+	double r[3];
+	const int err = MoorDyn_GetBodyAngle(instance, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
 
-//                                 Rod.h
-// =============================================================================
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(r[i]));
+	}
 
-/** @brief Wrapper to MoorDyn_GetRodID() function
+	return lst;
+}
+
+/** @brief Wrapper to MoorDyn_GetBodyVel() function
  * @param args Python passed arguments
- * @return The rod id
+ * @return The velocity and the acceleration
  */
 static PyObject*
-rod_get_id(PyObject*, PyObject* args)
+body_get_vel(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynRod instance =
-	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
 	if (!instance)
 		return NULL;
 
-	int n;
-	const int err = MoorDyn_GetRodID(instance, &n);
+	double r[3];
+	const int err = MoorDyn_GetBodyVel(instance, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(r[i]));
+	}
+
+	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_GetRodType() function
+/** @brief Wrapper to MoorDyn_GetBodyAngVel() function
  * @param args Python passed arguments
- * @return The rod type
+ * @return The velocity and the acceleration
  */
 static PyObject*
-rod_get_type(PyObject*, PyObject* args)
+body_get_angvel(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynRod instance =
-	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
 	if (!instance)
 		return NULL;
 
-	int n;
-	const int err = MoorDyn_GetRodType(instance, &n);
+	double r[3];
+	const int err = MoorDyn_GetBodyAngVel(instance, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(r[i]));
+	}
+
+	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_GetRodN() function
+/** @brief Wrapper to MoorDyn_GetBodyForce() function
  * @param args Python passed arguments
- * @return The number of segments
+ * @return The velocity and the acceleration
  */
 static PyObject*
-rod_get_n(PyObject*, PyObject* args)
+body_get_force(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynRod instance =
-	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
 	if (!instance)
 		return NULL;
 
-	unsigned int n;
-	const int err = MoorDyn_GetRodN(instance, &n);
+	double f[6];
+	const int err = MoorDyn_GetBodyForce(instance, f);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	PyObject* lst = PyTuple_New(6);
+	for (unsigned int i = 0; i < 6; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(f[i]));
+	}
+
+	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_GetRodNodePos() function
+/** @brief Wrapper to MoorDyn_GetBodyM() function
  * @param args Python passed arguments
- * @return The position
+ * @return The velocity and the acceleration
  */
 static PyObject*
-rod_get_node_pos(PyObject*, PyObject* args)
+body_get_m(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	int node;
 
-	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
+	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynRod instance =
-	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
 	if (!instance)
 		return NULL;
 
-	double r[3];
-	const int err = MoorDyn_GetRodNodePos(instance, node, r);
+	double m[6][6];
+	const int err = MoorDyn_GetBodyM(instance, m);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	PyObject* pyr = PyTuple_New(3);
-	for (unsigned int i = 0; i < 3; i++)
-		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+	PyObject* lst = PyTuple_New(6);
+	for (unsigned int i = 0; i < 6; i++) {
+		PyObject* sub = PyTuple_New(6);
+		for (unsigned int j = 0; j < 6; j++) {
+			PyTuple_SET_ITEM(sub, j, PyFloat_FromDouble(m[i][j]));
+		}
+		PyTuple_SET_ITEM(lst, i, sub);
+	}
 
-	return pyr;
+	return lst;
 }
 
-/** @brief Wrapper to MoorDyn_SaveRodVTK() function
+/** @brief Wrapper to MoorDyn_SaveBodyVTK() function
  * @param args Python passed arguments
  * @return 0 in case of success, an error code otherwise
  */
 static PyObject*
-rod_save_vtk(PyObject*, PyObject* args)
+body_save_vtk(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 	char* filepath = NULL;
@@ -1291,36 +1451,57 @@ rod_save_vtk(PyObject*, PyObject* args)
 	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
 		return NULL;
 
-	MoorDynRod instance =
-	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
 	if (!instance)
 		return NULL;
 
-	return PyLong_FromLong(MoorDyn_SaveRodVTK(instance, filepath));
+	return PyLong_FromLong(MoorDyn_SaveBodyVTK(instance, filepath));
 }
 
-//                              Point.h
+/** @brief Wrapper to MoorDyn_UseBodyVTK() function
+ * @param args Python passed arguments
+ * @return 0 in case of success, an error code otherwise
+ */
+static PyObject*
+body_use_vtk(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	char* filepath = NULL;
+
+	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+		return NULL;
+
+	MoorDynBody instance =
+	    (MoorDynBody)PyCapsule_GetPointer(capsule, body_capsule_name);
+	if (!instance)
+		return NULL;
+
+	return PyLong_FromLong(MoorDyn_UseBodyVTK(instance, filepath));
+}
+
+//                                 Rod.h
 // =============================================================================
 
-/** @brief Wrapper to MoorDyn_GetPointID() function
+/** @brief Wrapper to MoorDyn_GetRodID() function
  * @param args Python passed arguments
- * @return The point id
+ * @return The rod id
  */
 static PyObject*
-point_get_id(PyObject*, PyObject* args)
+rod_get_id(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
 	if (!instance)
 		return NULL;
 
 	int n;
-	const int err = MoorDyn_GetPointID(instance, &n);
+	const int err = MoorDyn_GetRodID(instance, &n);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
@@ -1329,219 +1510,773 @@ point_get_id(PyObject*, PyObject* args)
 	return PyLong_FromLong(n);
 }
 
-/** @brief Wrapper to MoorDyn_GetPointType() function
+/** @brief Wrapper to MoorDyn_GetRodType() function
  * @param args Python passed arguments
- * @return The point type
+ * @return The rod type
  */
 static PyObject*
-point_get_type(PyObject*, PyObject* args)
+rod_get_type(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
 	if (!instance)
 		return NULL;
 
-	int t;
-	const int err = MoorDyn_GetPointType(instance, &t);
+	int n;
+	const int err = MoorDyn_GetRodType(instance, &n);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(t);
+	return PyLong_FromLong(n);
 }
 
-/** @brief Wrapper to MoorDyn_GetPointPos() function
+/** @brief Wrapper to MoorDyn_GetRodForce() function
  * @param args Python passed arguments
- * @return The position
+ * @return The velocity and the acceleration
  */
 static PyObject*
-point_get_pos(PyObject*, PyObject* args)
+rod_get_force(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double f[6];
+	const int err = MoorDyn_GetRodForce(instance, f);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* lst = PyTuple_New(6);
+	for (unsigned int i = 0; i < 6; i++) {
+		PyTuple_SET_ITEM(lst, i, PyFloat_FromDouble(f[i]));
+	}
+
+	return lst;
+}
+
+/** @brief Wrapper to MoorDyn_GetRodM() function
+ * @param args Python passed arguments
+ * @return The velocity and the acceleration
+ */
+static PyObject*
+rod_get_m(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double m[6][6];
+	const int err = MoorDyn_GetRodM(instance, m);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* lst = PyTuple_New(6);
+	for (unsigned int i = 0; i < 6; i++) {
+		PyObject* sub = PyTuple_New(6);
+		for (unsigned int j = 0; j < 6; j++) {
+			PyTuple_SET_ITEM(sub, j, PyFloat_FromDouble(m[i][j]));
+		}
+		PyTuple_SET_ITEM(lst, i, sub);
+	}
+
+	return lst;
+}
+
+/** @brief Wrapper to MoorDyn_GetRodN() function
+ * @param args Python passed arguments
+ * @return The number of segments
+ */
+static PyObject*
+rod_get_n(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	unsigned int n;
+	const int err = MoorDyn_GetRodN(instance, &n);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(n);
+}
+
+/** @brief Wrapper to MoorDyn_GetRodNodePos() function
+ * @param args Python passed arguments
+ * @return The position
+ */
+static PyObject*
+rod_get_node_pos(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	int node;
+
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
+		return NULL;
+
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double r[3];
+	const int err = MoorDyn_GetRodNodePos(instance, node, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_GetRodNodeVel() function
+ * @param args Python passed arguments
+ * @return The position
+ */
+static PyObject*
+rod_get_node_vel(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	int node;
+
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
+		return NULL;
+
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double r[3];
+	const int err = MoorDyn_GetRodNodeVel(instance, node, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_SaveRodVTK() function
+ * @param args Python passed arguments
+ * @return 0 in case of success, an error code otherwise
+ */
+static PyObject*
+rod_save_vtk(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	char* filepath = NULL;
+
+	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+		return NULL;
+
+	MoorDynRod instance =
+	    (MoorDynRod)PyCapsule_GetPointer(capsule, rod_capsule_name);
+	if (!instance)
+		return NULL;
+
+	return PyLong_FromLong(MoorDyn_SaveRodVTK(instance, filepath));
+}
+
+//                              Point.h
+// =============================================================================
+
+/** @brief Wrapper to MoorDyn_GetPointID() function
+ * @param args Python passed arguments
+ * @return The point id
+ */
+static PyObject*
+point_get_id(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	int n;
+	const int err = MoorDyn_GetPointID(instance, &n);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(n);
+}
+
+/** @brief Wrapper to MoorDyn_GetPointType() function
+ * @param args Python passed arguments
+ * @return The point type
+ */
+static PyObject*
+point_get_type(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	int t;
+	const int err = MoorDyn_GetPointType(instance, &t);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(t);
+}
+
+/** @brief Wrapper to MoorDyn_GetPointPos() function
+ * @param args Python passed arguments
+ * @return The position
+ */
+static PyObject*
+point_get_pos(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double r[3];
+	const int err = MoorDyn_GetPointPos(instance, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_GetPointVel() function
+ * @param args Python passed arguments
+ * @return The velocity
+ */
+static PyObject*
+point_get_vel(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double r[3];
+	const int err = MoorDyn_GetPointVel(instance, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_GetPointForce() function
+ * @param args Python passed arguments
+ * @return The velocity
+ */
+static PyObject*
+point_get_force(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double r[3];
+	const int err = MoorDyn_GetPointForce(instance, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_GetPointM() function
+ * @param args Python passed arguments
+ * @return The velocity and the acceleration
+ */
+static PyObject*
+point_get_m(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double m[3][3];
+	const int err = MoorDyn_GetPointM(instance, m);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyObject* sub = PyTuple_New(3);
+		for (unsigned int j = 0; j < 3; j++) {
+			PyTuple_SET_ITEM(sub, j, PyFloat_FromDouble(m[i][j]));
+		}
+		PyTuple_SET_ITEM(lst, i, sub);
+	}
+
+	return lst;
+}
+
+/** @brief Wrapper to MoorDyn_GetPointNAttached() function
+ * @param args Python passed arguments
+ * @return The number of attached lines
+ */
+static PyObject*
+point_get_nattached(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	unsigned int n;
+	const int err = MoorDyn_GetPointNAttached(instance, &n);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(n);
+}
+
+/** @brief Wrapper to MoorDyn_GetPointNAttached() function
+ * @param args Python passed arguments
+ * @return The number of attached lines
+ */
+static PyObject*
+point_get_attached(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	unsigned int i;
+
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &i))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	MoorDynLine l;
+	int e;
+	const int err = MoorDyn_GetPointAttached(instance, i, &l, &e);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* pyv = PyTuple_New(2);
+	PyTuple_SET_ITEM(pyv, 0, PyCapsule_New((void*)l, line_capsule_name, NULL));
+	PyTuple_SET_ITEM(pyv, 1, PyLong_FromLong(e));
+	return pyv;
+}
+
+/** @brief Wrapper to MoorDyn_SavePointVTK() function
+ * @param args Python passed arguments
+ * @return 0 in case of success, an error code otherwise
+ */
+static PyObject*
+point_save_vtk(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	char* filepath = NULL;
+
+	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+		return NULL;
+
+	MoorDynPoint instance =
+	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	if (!instance)
+		return NULL;
+
+	return PyLong_FromLong(MoorDyn_SavePointVTK(instance, filepath));
+}
+
+//                                 Line.h
+// =============================================================================
+
+/** @brief Wrapper to MoorDyn_GetLineID() function
+ * @param args Python passed arguments
+ * @return The line id
+ */
+static PyObject*
+line_get_id(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
+	if (!instance)
+		return NULL;
+
+	int n;
+	const int err = MoorDyn_GetLineID(instance, &n);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(n);
+}
+
+/** @brief Wrapper to MoorDyn_GetLineN() function
+ * @param args Python passed arguments
+ * @return The number of segments
+ */
+static PyObject*
+line_get_n(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
+	if (!instance)
+		return NULL;
+
+	unsigned int n;
+	const int err = MoorDyn_GetLineN(instance, &n);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyLong_FromLong(n);
+}
+
+/** @brief Wrapper to MoorDyn_GetLineUnstretchedLength() function
+ * @param args Python passed arguments
+ * @return The unstretched length
+ */
+static PyObject*
+line_get_ulen(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+
+	if (!PyArg_ParseTuple(args, "O", &capsule))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double l;
+	const int err = MoorDyn_GetLineUnstretchedLength(instance, &l);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return PyFloat_FromDouble(l);
+}
+
+/** @brief Wrapper to MoorDyn_SetLineUnstretchedLength() function
+ * @param args Python passed arguments
+ * @return None
+ */
+static PyObject*
+line_set_ulen(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	double l;
+
+	if (!PyArg_ParseTuple(args, "Od", &capsule, &l))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
+	if (!instance)
+		return NULL;
+
+	const int err = MoorDyn_SetLineUnstretchedLength(instance, l);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	return Py_None;
+}
+
+/** @brief Wrapper to MoorDyn_SetLineUnstretchedLength() function
+ * @param args Python passed arguments
+ * @return None
+ */
+static PyObject*
+line_set_ulenv(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	double v;
+
+	if (!PyArg_ParseTuple(args, "Od", &capsule, &v))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	double r[3];
-	const int err = MoorDyn_GetPointPos(instance, r);
+	const int err = MoorDyn_SetLineUnstretchedLengthVel(instance, v);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	PyObject* pyr = PyTuple_New(3);
-	for (unsigned int i = 0; i < 3; i++)
-		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
-
-	return pyr;
+	return Py_None;
 }
 
-/** @brief Wrapper to MoorDyn_GetPointVel() function
+/** @brief Wrapper to MoorDyn_IsLineConstantEA() function
  * @param args Python passed arguments
- * @return The velocity
+ * @return The unstretched length
  */
 static PyObject*
-point_get_vel(PyObject*, PyObject* args)
+line_is_const_ea(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	double r[3];
-	const int err = MoorDyn_GetPointVel(instance, r);
+	int b;
+	const int err = MoorDyn_IsLineConstantEA(instance, &b);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	PyObject* pyr = PyTuple_New(3);
-	for (unsigned int i = 0; i < 3; i++)
-		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
-
-	return pyr;
+	return PyLong_FromLong(b);
 }
 
-/** @brief Wrapper to MoorDyn_GetPointForce() function
+/** @brief Wrapper to MoorDyn_GetLineConstantEA() function
  * @param args Python passed arguments
- * @return The velocity
+ * @return The unstretched length
  */
 static PyObject*
-point_get_force(PyObject*, PyObject* args)
+line_get_const_ea(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 
 	if (!PyArg_ParseTuple(args, "O", &capsule))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	double r[3];
-	const int err = MoorDyn_GetPointForce(instance, r);
+	double ea;
+	const int err = MoorDyn_GetLineConstantEA(instance, &ea);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	PyObject* pyr = PyTuple_New(3);
-	for (unsigned int i = 0; i < 3; i++)
-		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
-
-	return pyr;
+	return PyFloat_FromDouble(ea);
 }
 
-/** @brief Wrapper to MoorDyn_GetPointNAttached() function
+/** @brief Wrapper to MoorDyn_SetLineConstantEA() function
  * @param args Python passed arguments
- * @return The number of attached lines
+ * @return None
  */
 static PyObject*
-point_get_nattached(PyObject*, PyObject* args)
+line_set_const_ea(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
+	double ea;
 
-	if (!PyArg_ParseTuple(args, "O", &capsule))
+	if (!PyArg_ParseTuple(args, "Od", &capsule, &ea))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	unsigned int n;
-	const int err = MoorDyn_GetPointNAttached(instance, &n);
+	const int err = MoorDyn_SetLineConstantEA(instance, ea);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	return Py_None;
 }
 
-/** @brief Wrapper to MoorDyn_GetPointNAttached() function
+/** @brief Wrapper to MoorDyn_GetLineNodePos() function
  * @param args Python passed arguments
- * @return The number of attached lines
+ * @return The position
  */
 static PyObject*
-point_get_attached(PyObject*, PyObject* args)
+line_get_node_pos(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	unsigned int i;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "Oi", &capsule, &i))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	MoorDynLine l;
-	int e;
-	const int err = MoorDyn_GetPointAttached(instance, i, &l, &e);
+	double r[3];
+	const int err = MoorDyn_GetLineNodePos(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	PyObject* pyv = PyTuple_New(2);
-	PyTuple_SET_ITEM(pyv, 0, PyCapsule_New((void*)l, line_capsule_name, NULL));
-	PyTuple_SET_ITEM(pyv, 1, PyLong_FromLong(e));
-	return pyv;
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_SavePointVTK() function
+/** @brief Wrapper to MoorDyn_GetLineNodeVel() function
  * @param args Python passed arguments
- * @return 0 in case of success, an error code otherwise
+ * @return The position
  */
 static PyObject*
-point_save_vtk(PyObject*, PyObject* args)
+line_get_node_vel(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	char* filepath = NULL;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "Os", &capsule, &filepath))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
-	MoorDynPoint instance =
-	    (MoorDynPoint)PyCapsule_GetPointer(capsule, point_capsule_name);
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
 	if (!instance)
 		return NULL;
 
-	return PyLong_FromLong(MoorDyn_SavePointVTK(instance, filepath));
-}
+	double r[3];
+	const int err = MoorDyn_GetLineNodeVel(instance, node, r);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
 
-//                                 Line.h
-// =============================================================================
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
 
-/** @brief Wrapper to MoorDyn_GetLineID() function
+	return pyr;
+}
+
+/** @brief Wrapper to MoorDyn_GetLineNodeForce() function
  * @param args Python passed arguments
- * @return The line id
+ * @return The force
  */
 static PyObject*
-line_get_id(PyObject*, PyObject* args)
+line_get_node_force(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "O", &capsule))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
 	MoorDynLine instance =
@@ -1549,26 +2284,31 @@ line_get_id(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	int n;
-	const int err = MoorDyn_GetLineID(instance, &n);
+	double r[3];
+	const int err = MoorDyn_GetLineNodeForce(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_GetLineN() function
+/** @brief Wrapper to MoorDyn_GetLineNodeTen() function
  * @param args Python passed arguments
- * @return The number of segments
+ * @return The tension
  */
 static PyObject*
-line_get_n(PyObject*, PyObject* args)
+line_get_node_ten(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "O", &capsule))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
 	MoorDynLine instance =
@@ -1576,26 +2316,31 @@ line_get_n(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	unsigned int n;
-	const int err = MoorDyn_GetLineN(instance, &n);
+	double r[3];
+	const int err = MoorDyn_GetLineNodeTen(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyLong_FromLong(n);
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_GetLineUnstretchedLength() function
+/** @brief Wrapper to MoorDyn_GetLineNodeBendStiff() function
  * @param args Python passed arguments
- * @return The unstretched length
+ * @return The force
  */
 static PyObject*
-line_get_ulen(PyObject*, PyObject* args)
+line_get_node_bend(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "O", &capsule))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
 	MoorDynLine instance =
@@ -1603,27 +2348,31 @@ line_get_ulen(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	double l;
-	const int err = MoorDyn_GetLineUnstretchedLength(instance, &l);
+	double r[3];
+	const int err = MoorDyn_GetLineNodeBendStiff(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return PyFloat_FromDouble(l);
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_SetLineUnstretchedLength() function
+/** @brief Wrapper to MoorDyn_GetLineNodeWeight() function
  * @param args Python passed arguments
- * @return None
+ * @return The force
  */
 static PyObject*
-line_set_ulen(PyObject*, PyObject* args)
+line_get_node_w(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	double l;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "Od", &capsule, &l))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
 	MoorDynLine instance =
@@ -1631,26 +2380,31 @@ line_set_ulen(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	const int err = MoorDyn_SetLineUnstretchedLength(instance, l);
+	double r[3];
+	const int err = MoorDyn_GetLineNodeWeight(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return Py_None;
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_SetLineUnstretchedLength() function
+/** @brief Wrapper to MoorDyn_GetLineNodeDrag() function
  * @param args Python passed arguments
- * @return None
+ * @return The force
  */
 static PyObject*
-line_set_ulenv(PyObject*, PyObject* args)
+line_get_node_drag(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
-	double v;
+	int node;
 
-	if (!PyArg_ParseTuple(args, "Od", &capsule, &v))
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
 		return NULL;
 
 	MoorDynLine instance =
@@ -1658,21 +2412,26 @@ line_set_ulenv(PyObject*, PyObject* args)
 	if (!instance)
 		return NULL;
 
-	const int err = MoorDyn_SetLineUnstretchedLengthVel(instance, v);
+	double r[3];
+	const int err = MoorDyn_GetLineNodeDrag(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
 	}
 
-	return Py_None;
+	PyObject* pyr = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++)
+		PyTuple_SET_ITEM(pyr, i, PyFloat_FromDouble(r[i]));
+
+	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_GetLineNodePos() function
+/** @brief Wrapper to MoorDyn_GetLineNodeFroudeKrilov() function
  * @param args Python passed arguments
- * @return The position
+ * @return The force
  */
 static PyObject*
-line_get_node_pos(PyObject*, PyObject* args)
+line_get_node_froudekrylov(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 	int node;
@@ -1686,7 +2445,7 @@ line_get_node_pos(PyObject*, PyObject* args)
 		return NULL;
 
 	double r[3];
-	const int err = MoorDyn_GetLineNodePos(instance, node, r);
+	const int err = MoorDyn_GetLineNodeFroudeKrilov(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
@@ -1699,12 +2458,12 @@ line_get_node_pos(PyObject*, PyObject* args)
 	return pyr;
 }
 
-/** @brief Wrapper to MoorDyn_GetLineNodeTen() function
+/** @brief Wrapper to MoorDyn_GetLineNodeSeaBedForce() function
  * @param args Python passed arguments
- * @return The tension
+ * @return The force
  */
 static PyObject*
-line_get_node_force(PyObject*, PyObject* args)
+line_get_node_seabed(PyObject*, PyObject* args)
 {
 	PyObject* capsule;
 	int node;
@@ -1718,7 +2477,7 @@ line_get_node_force(PyObject*, PyObject* args)
 		return NULL;
 
 	double r[3];
-	const int err = MoorDyn_GetLineNodeTen(instance, node, r);
+	const int err = MoorDyn_GetLineNodeSeabedForce(instance, node, r);
 	if (err != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
 		return NULL;
@@ -1759,6 +2518,44 @@ line_get_node_curv(PyObject*, PyObject* args)
 	return PyFloat_FromDouble(t);
 }
 
+/** @brief Wrapper to MoorDyn_GetLineNodeM() function
+ * @param args Python passed arguments
+ * @return The force
+ */
+static PyObject*
+line_get_node_m(PyObject*, PyObject* args)
+{
+	PyObject* capsule;
+	int node;
+
+	if (!PyArg_ParseTuple(args, "Oi", &capsule, &node))
+		return NULL;
+
+	MoorDynLine instance =
+	    (MoorDynLine)PyCapsule_GetPointer(capsule, line_capsule_name);
+	if (!instance)
+		return NULL;
+
+	double m[3][3];
+	const int err = MoorDyn_GetLineNodeM(instance, node, m);
+	if (err != 0) {
+		PyErr_SetString(PyExc_RuntimeError, "MoorDyn reported an error");
+		return NULL;
+	}
+
+	PyObject* lst = PyTuple_New(3);
+	for (unsigned int i = 0; i < 3; i++) {
+		PyObject* sub = PyTuple_New(3);
+		for (unsigned int j = 0; j < 3; j++) {
+			PyTuple_SET_ITEM(sub, j, PyFloat_FromDouble(m[i][j]));
+		}
+		PyTuple_SET_ITEM(lst, i, sub);
+	}
+
+	return lst;
+}
+
+
 /** @brief Wrapper to MoorDyn_GetLineFairTen() function
  * @param args Python passed arguments
  * @return The tension magnitude
@@ -1867,6 +2664,10 @@ static PyMethodDef moordyn_methods[] = {
 	  METH_VARARGS,
 	  "deallocates the variables used by MoorDyn" },
 	{ "get_waves", get_waves, METH_VARARGS, "Get the waves manager instance" },
+	{ "get_seafloor",
+	   get_seafloor,
+	   METH_VARARGS,
+	   "Get the seafloor instance" },
 	{ "ext_wave_init",
 	  ext_wave_init,
 	  METH_VARARGS,
@@ -1920,9 +2721,27 @@ static PyMethodDef moordyn_methods[] = {
 	  METH_VARARGS,
 	  "Save a .vtm file of the whole system" },
 	{ "waves_getkin", waves_getkin, METH_VARARGS, "Get waves kinematics" },
+	{ "seafloor_getdepth",
+	  seafloor_getdepth,
+	  METH_VARARGS,
+	  "Get depth at a point" },
+	{ "seafloor_getavgdepth",
+	  seafloor_getavgdepth,
+	  METH_VARARGS,
+	  "Get the average depth" },
+	{ "seafloor_getmindepth",
+	  seafloor_getmindepth,
+	  METH_VARARGS,
+	  "Get shallowest depth" },
 	{ "body_get_id", body_get_id, METH_VARARGS, "Get the body id" },
 	{ "body_get_type", body_get_type, METH_VARARGS, "Get the body type" },
 	{ "body_get_state", body_get_state, METH_VARARGS, "Get the body state" },
+	{ "body_get_pos", body_get_pos, METH_VARARGS, "Get the body pos" },
+	{ "body_get_angle", body_get_angle, METH_VARARGS, "Get the body angle" },
+	{ "body_get_vel", body_get_vel, METH_VARARGS, "Get the body velocity" },
+	{ "body_get_angvel", body_get_angvel, METH_VARARGS, "Get the body ang vel" },
+	{ "body_get_force", body_get_force, METH_VARARGS, "Get the body force" },
+	{ "body_get_m", body_get_m, METH_VARARGS, "Get the body mass" },
 	{ "body_save_vtk",
 	  body_save_vtk,
 	  METH_VARARGS,
@@ -1933,11 +2752,17 @@ static PyMethodDef moordyn_methods[] = {
 	  "Load a representation model for the body" },
 	{ "rod_get_id", rod_get_id, METH_VARARGS, "Get the rod id" },
 	{ "rod_get_type", rod_get_type, METH_VARARGS, "Get the rod type" },
+	{ "rod_get_force", rod_get_force, METH_VARARGS, "Get the rod force" },
+	{ "rod_get_m", rod_get_m, METH_VARARGS, "Get the rod mass" },
 	{ "rod_get_n", rod_get_n, METH_VARARGS, "Get the rod number of segments" },
 	{ "rod_get_node_pos",
 	  rod_get_node_pos,
 	  METH_VARARGS,
 	  "Get a rod node position" },
+	{ "rod_get_node_vel",
+	  rod_get_node_vel,
+	  METH_VARARGS,
+	  "Get a rod node velocity" },
 	{ "rod_save_vtk",
 	  rod_save_vtk,
 	  METH_VARARGS,
@@ -1956,6 +2781,7 @@ static PyMethodDef moordyn_methods[] = {
 	  point_get_force,
 	  METH_VARARGS,
 	  "Get the point force" },
+	{ "point_get_m", point_get_m, METH_VARARGS, "Get the point mass matrix" },
 	{ "point_get_nattached",
 	  point_get_nattached,
 	  METH_VARARGS,
@@ -1985,18 +2811,62 @@ static PyMethodDef moordyn_methods[] = {
 	  line_set_ulenv,
 	  METH_VARARGS,
 	  "Set the line rate of change of unstretched length" },
+	{ "line_is_const_ea",
+	  line_is_const_ea,
+	  METH_VARARGS,
+	  "Check if the stiffness is constant" },
+	{ "line_get_const_ea",
+	  line_get_const_ea,
+	  METH_VARARGS,
+	  "Get the constant stiffness" },
+	{ "line_set_const_ea",
+	  line_set_const_ea,
+	  METH_VARARGS,
+	  "Set the constant stiffness" },
 	{ "line_get_node_pos",
 	  line_get_node_pos,
 	  METH_VARARGS,
 	  "Get a line node position" },
+	{ "line_get_node_vel",
+	  line_get_node_vel,
+	  METH_VARARGS,
+	  "Get a line node velocity" },
 	{ "line_get_node_force",
 	  line_get_node_force,
 	  METH_VARARGS,
+	  "Get a line node net force" },
+	{ "line_get_node_ten",
+	  line_get_node_ten,
+	  METH_VARARGS,
 	  "Get a line node tension" },
+	{ "line_get_node_bend",
+	  line_get_node_bend,
+	  METH_VARARGS,
+	  "Get a line node bending stiffness force" },
+	{ "line_get_node_w",
+	  line_get_node_w,
+	  METH_VARARGS,
+	  "Get a line node weight" },
+	{ "line_get_node_drag",
+	  line_get_node_drag,
+	  METH_VARARGS,
+	  "Get a line node drag force" },
+	{ "line_get_node_froudekrylov",
+	  line_get_node_froudekrylov,
+	  METH_VARARGS,
+	  "Get a line node Froude-Krylov force" },
+	{ "line_get_node_seabed",
+	  line_get_node_seabed,
+	  METH_VARARGS,
+	  "Get a line node seabed reaction" },
 	{ "line_get_node_curv",
 	  line_get_node_curv,
 	  METH_VARARGS,
 	  "Get a line node curvature" },
+	{ "line_get_node_m",
+	  line_get_node_m,
+	  METH_VARARGS,
+	  "Get a line node mass matrix" },
 	{ "line_get_fairlead_tension",
 	  line_get_fairlead_tension,
 	  METH_VARARGS,
diff --git a/wrappers/python/moordyn/moordyn.py b/wrappers/python/moordyn/moordyn.py
index 20fd52ff..adcbf295 100644
--- a/wrappers/python/moordyn/moordyn.py
+++ b/wrappers/python/moordyn/moordyn.py
@@ -257,6 +257,24 @@ def GetWaves(instance):
     return cmoordyn.get_waves(instance)
 
 
+def GetSeafloor(instance):
+    """Get the 3D seafloor instance
+
+    The is not None just if a SeafloorPath option was set in the input file.
+
+    Parameters:
+    instance (cmoordyn.MoorDyn): The MoorDyn instance
+
+    Returns:
+    cmoordyn.MoorDynSeafloor: The seafloor instance
+    """
+    import cmoordyn
+    try:
+        return cmoordyn.get_seafloor(instance)
+    except RuntimeError:
+        return None
+
+
 def ExternalWaveKinInit(instance):
     """Initializes the external Wave kinetics
 
@@ -553,11 +571,12 @@ def SaveVTK(instance, filename):
     import cmoordyn
     return cmoordyn.save_vtk(instance, filename)
 
+
 #                                  Waves.h
 #  =============================================================================
 
 
-def GetWavesKin(instance, x, y, z, t):
+def GetWavesKin(instance, x, y, z, t, seafloor=None):
     """ Get the wave kinematics
 
     Parameters:
@@ -566,6 +585,7 @@ def GetWavesKin(instance, x, y, z, t):
     y (float): The y coordinate
     z (float): The z coordinate
     t (float): The simulation time
+    seafloor (cmoordyn.MoorDynSeafloor): The 3D seafloor instance
 
     Returns:
     u: The velocity (a tuple with 3 components)
@@ -574,9 +594,58 @@ def GetWavesKin(instance, x, y, z, t):
     pdyn: The dynamic pressure
     """
     import cmoordyn
-    return cmoordyn.waves_getkin(instance, x, y, z, t)
+    return cmoordyn.waves_getkin(instance, x, y, z, t, seafloor)
+
+
+#                                  Seafloor.h
+#  =============================================================================
 
 
+def GetDepthAt(instance, x, y):
+    """ Get the depth of the seafloor at some x and y
+
+    Parameters:
+    instance (cmoordyn.MoorDynSeafloor): The 3D seafloor instance
+    x (float): The x coordinate
+    y (float): The y coordinate
+
+    Returns:
+    depth: The output seafloor depth at that (x, y) point
+    """
+    import cmoordyn
+    return cmoordyn.seafloor_getdepth(instance, x, y)
+    
+
+def GetAverageDepth(instance):
+    """ Get the average of depth of the seafloor
+
+    This value is calculated as the average value of every depth point
+
+    If the rectilinear grid is not even, this average may not be the actual
+    average depth of the surface the data describes.
+
+    Parameters:
+    instance (cmoordyn.MoorDynSeafloor): The 3D seafloor instance
+
+    Returns:
+    depth: The averaged seafloor depth
+    """
+    import cmoordyn
+    return cmoordyn.seafloor_getavgdepth(instance)
+    
+
+def GetMinDepth(instance):
+    """ The depth of the seafloor at the shallowest point
+
+    Parameters:
+    instance (cmoordyn.MoorDynSeafloor): The 3D seafloor instance
+
+    Returns:
+    depth: The minimum seafloor depth
+    """
+    import cmoordyn
+    return cmoordyn.seafloor_getmindepth(instance)
+
 #                                  Body.h
 #  =============================================================================
 
@@ -614,13 +683,91 @@ def GetBodyState(instance):
     instance (cmoordyn.MoorDynBody): The body instance
 
     Returns:
-    r: The position (a tuple with 3 components)
-    u: The velocity (a tuple with 3 components)
+    r: The position (a tuple with 6 components)
+    u: The velocity (a tuple with 6 components)
     """
     import cmoordyn
     return cmoordyn.body_get_state(instance)
 
 
+def GetBodyPos(instance):
+    """ Get the body position
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    r: The position (a tuple with 3 components)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_pos(instance)
+
+
+def GetBodyAngle(instance):
+    """ Get the body Euler XYZ angles
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    r: The angles (a tuple with 3 components)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_angle(instance)
+
+
+def GetBodyVel(instance):
+    """ Get the body velocity
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    r: The velocity (a tuple with 3 components)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_vel(instance)
+
+
+def GetBodyAngVel(instance):
+    """ Get the body angular velocity
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    r: The angular velocity (a tuple with 3 components)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_angvel(instance)
+
+
+def GetBodyForce(instance):
+    """ Get the net force on the body
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    f: The force and the moment (a tuple with 6 components)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_force(instance)
+
+
+def GetBodyM(instance):
+    """ Get the mass matrix of the body
+
+    Parameters:
+    instance (cmoordyn.MoorDynBody): The body instance
+
+    Returns:
+    m: The mass matrix (a 6x6 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.body_get_m(instance)
+
+
 def SaveBodyVTK(instance, filename):
     """ Save the body to a VTK (.vtp) file
 
@@ -666,7 +813,7 @@ def GetRodID(instance):
 
 
 def GetRodType(instance):
-    """ Get the rod id
+    """ Get the rod type
 
     Parameters:
     instance (cmoordyn.MoorDynRod): The rod instance
@@ -678,6 +825,32 @@ def GetRodType(instance):
     return cmoordyn.rod_get_type(instance)
 
 
+def GetRodForce(instance):
+    """ Get the net force and moment acting over the rod
+
+    Parameters:
+    instance (cmoordyn.MoorDynRod): The rod instance
+
+    Returns:
+    f: The rod force (a 6 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.rod_get_force(instance)
+
+
+def GetRodM(instance):
+    """ Get the rod mass matrix
+
+    Parameters:
+    instance (cmoordyn.MoorDynRod): The rod instance
+
+    Returns:
+    i: The rod mass matrix (a 6x6 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.rod_get_m(instance)
+
+
 def GetRodN(instance):
     """ Get the rod number of segments
 
@@ -699,12 +872,26 @@ def GetRodNodePos(instance, i):
     i (int): The node index
 
     Returns:
-    r: The node pos
+    r: The node position
     """
     import cmoordyn
     return cmoordyn.rod_get_node_pos(instance, i)
 
 
+def GetRodNodeVel(instance, i):
+    """ Get a rod node position
+
+    Parameters:
+    instance (cmoordyn.MoorDynRod): The rod instance
+    i (int): The node index
+
+    Returns:
+    r: The node velocity
+    """
+    import cmoordyn
+    return cmoordyn.rod_get_node_vel(instance, i)
+
+
 def SaveRodVTK(instance, filename):
     """ Save the rod to a VTK (.vtp) file
 
@@ -781,12 +968,25 @@ def GetPointForce(instance):
     instance (cmoordyn.MoorDynPoint): The point instance
 
     Returns:
-    v: The point force
+    f: The point force (a 3 components tuple)
     """
     import cmoordyn
     return cmoordyn.point_get_force(instance)
 
 
+def GetPointM(instance):
+    """ Get the point mass matrix
+
+    Parameters:
+    instance (cmoordyn.MoorDynPoint): The point instance
+
+    Returns:
+    m: The mass matrix (a 3x3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.point_get_m(instance)
+
+
 def GetPointNAttached(instance):
     """ Get the number of attached lines
 
@@ -893,6 +1093,49 @@ def SetLineUnstretchedLengthVel(instance, v):
     return cmoordyn.line_set_ulenv(instance, v)
 
 
+def IsLineConstantEA(instance):
+    """ Get whether the line is governed by a non-linear stiffness or a
+    constant one
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+
+    Returns:
+    b: True if the stiffness of the line is constant, False if a non-linear
+       stiffness has been set
+    """
+    import cmoordyn
+    return cmoordyn.line_is_const_ea(instance) != 0
+
+
+def GetLineConstantEA(instance):
+    """ Get the constant stiffness of the line
+    
+    This value is useless if non-linear stiffness is considered
+    
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+
+    Returns:
+    ea: The constant stiffness EA value
+    """
+    import cmoordyn
+    return cmoordyn.line_get_const_ea(instance)
+
+
+def SetLineConstantEA(instance, ea):
+    """ Set the constant stiffness of the line
+    
+    This value is useless if non-linear stiffness is considered
+    
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    EA (float): The constant stiffness EA value
+    """
+    import cmoordyn
+    return cmoordyn.line_set_const_ea(instance, EA)
+
+
 def GetLineNodePos(instance, i):
     """ Get a line node position
 
@@ -907,18 +1150,128 @@ def GetLineNodePos(instance, i):
     return cmoordyn.line_get_node_pos(instance, i)
 
 
+def GetLineNodeVel(instance, i):
+    """ Get a line node velocity
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    rd: The node velocity
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_vel(instance, i)
+
+
+def GetLineNodeForce(instance, i):
+    """ Get a line node net force
+
+    To get the components of the force use moordyn.GetLineNodeTen() ,
+    moordyn.GetLineNodeBendStiff(), moordyn.GetLineNodeWeight() ,
+    moordyn.GetLineNodeDrag() , moordyn.GetLineNodeFroudeKrilov() and
+    moordyn.GetLineNodeSeaBedForce()
+
+    Note that the net force is *NOT* the sum of all those components. For
+    instance, the tension contribution on the internal nodes is the difference
+    between the tensions of the adjacent segments, while
+    moordyn.GetLineNodeTen() is returning the averaged value.
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_force(instance, i)
+
+
 def GetLineNodeTen(instance, i):
     """ Get a line node tension
 
+    This comprises the axial stiffness as well as the internal damping
+
     Parameters:
     instance (cmoordyn.MoorDynLine): The line instance
     i (int): The node index
 
     Returns:
-    t: The node tension
+    t: The node tension (a 3 components tuple)
     """
     import cmoordyn
-    return cmoordyn.line_get_node_force(instance, i)
+    return cmoordyn.line_get_node_ten(instance, i)
+
+
+def GetLineNodeBendStiff(instance, i):
+    """ Get a line node bending stiffness force
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_bend(instance, i)
+
+
+def GetLineNodeWeight(instance, i):
+    """ Get a line node weight and bouyancy
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_w(instance, i)
+
+
+def GetLineNodeDrag(instance, i):
+    """ Get a line node drag force
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_drag(instance, i)
+
+
+def GetLineNodeFroudeKrilov(instance, i):
+    """ Get a line node Froude-Krilov force
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_froudekrylov(instance, i)
+
+
+def GetLineNodeSeabedForce(instance, i):
+    """ Get a line node seabed reaction
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    f: The node force (a 3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_froudekrylov(instance, i)
 
 
 def GetLineNodeCurv(instance, i):
@@ -935,6 +1288,20 @@ def GetLineNodeCurv(instance, i):
     return cmoordyn.line_get_node_curv(instance, i)
 
 
+def GetLineNodeM(instance, i):
+    """ Get a line node mass matrix
+
+    Parameters:
+    instance (cmoordyn.MoorDynLine): The line instance
+    i (int): The node index
+
+    Returns:
+    r: The node mass matrix (a 3x3 components tuple)
+    """
+    import cmoordyn
+    return cmoordyn.line_get_node_m(instance, i)
+
+
 def GetLineFairTen(instance):
     """ Get the tension magnitude at the fairlead of a line
 


Test patch:
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index c352129b..1db65889 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -104,7 +104,7 @@ jobs:
       if: runner.os == 'MacOS'
 
     - name: download pre-built VTK static library
-      uses: suisei-cn/actions-download-file@v1.4.0
+      uses: suisei-cn/actions-download-file@v1.6.0
       with:
         url: https://github.com/sanguinariojoe/vtk-builds/releases/download/VTK-${{env.VTK_VERSION_MAJOR}}.${{env.VTK_VERSION_MINOR}}.${{env.VTK_VERSION_PATCH}}-static/vtk-${{runner.os}}-x86_64.tar.gz
         target: ${{github.workspace}}/
@@ -154,7 +154,7 @@ jobs:
     - name: Test
       working-directory: ${{github.workspace}}/build
       # We are just testing in Linux
-      run: ctest -C ${{env.BUILD_TYPE}}
+      run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure
 
     - name: Create Installer
       working-directory: ${{github.workspace}}/build
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 8257f9bd..43b1c810 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -5,17 +5,24 @@ file(GLOB MooringConfigFiles ${CMAKE_SOURCE_DIR}/tests/Mooring/*.stl
 add_custom_target(Test_MooringConfigFiles)
 add_custom_command(TARGET Test_MooringConfigFiles PRE_BUILD
                    COMMAND ${CMAKE_COMMAND} -E
-                        copy_directory ${CMAKE_SOURCE_DIR}/tests/Mooring ${CMAKE_CURRENT_BINARY_DIR}/Mooring)
+                        copy_directory ${CMAKE_SOURCE_DIR}/tests/Mooring ${CMAKE_CURRENT_BINARY_DIR}/Mooring
+)
 if(WIN32)
     add_custom_command(TARGET Test_MooringConfigFiles POST_BUILD
-                       COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:moordyn> ${CMAKE_CURRENT_BINARY_DIR}/)
+                       COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:moordyn> ${CMAKE_CURRENT_BINARY_DIR}/
+    )
     if(${FORTRAN_WRAPPER})
         add_custom_command(TARGET Test_MooringConfigFiles POST_BUILD
-                           COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:moordynf> ${CMAKE_CURRENT_BINARY_DIR}/)
+                           COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:moordynf> ${CMAKE_CURRENT_BINARY_DIR}/
+        )
     endif()
 endif()
 
-set(TESTS 
+set(C_TESTS 
+    c_api
+)
+
+set(CPP_TESTS 
     minimal
     lifting
     pendulum
@@ -28,9 +35,6 @@ set(TESTS
     wavekin_4
     tripod
     seafloor
-)
-
-set(CPP_TESTS 
     wavekin
     wavekin_7
     bodies
@@ -39,11 +43,12 @@ set(CPP_TESTS
 set(CATCH2_TESTS 
     math_tests
     testDecomposeString
+    polyester
 )
 
-function(make_executable test_name)
+function(make_executable test_name, extension)
     set(TEST_PRIVATE_DEPS "moordyn;")
-    add_executable(${test_name} ${test_name}.cpp)
+    add_executable(${test_name} ${test_name}.${extension})
     if(USE_VTK AND MOORDYN_PACKAGE_IGNORE_VTK_DEPENDENCY)
         list(APPEND TEST_PRIVATE_DEPS VTK::CommonCore
                                          VTK::IOCore
@@ -60,25 +65,28 @@ function(make_executable test_name)
     add_dependencies(${test_name} Test_MooringConfigFiles)
 endfunction()
 
+function(make_c_executable test_name)
+    make_executable(${test_name}, "c")
+endfunction()
+
 function(make_cpp_executable test_name)
-    make_executable(${test_name})
+    make_executable(${test_name}, "cpp")
     target_include_directories(${test_name} PUBLIC ${CMAKE_BINARY_DIR}/)
 endfunction()
 
 function(make_catch2_executable test_name)
     make_cpp_executable(${test_name})
-    target_include_directories(${test_name} PUBLIC ${CMAKE_BINARY_DIR}/)
     target_link_libraries(${test_name} PRIVATE Catch2::Catch2WithMain)
 endfunction()
 
 if(USE_VTK)
-    set(TESTS "${TESTS};vtk")
+    set(CPP_TESTS "${CPP_TESTS};vtk")
 
     make_cpp_executable(wave_surface)
 endif()
 
-foreach(TEST ${TESTS})
-    make_executable(${TEST})
+foreach(TEST ${C_TESTS})
+    make_c_executable(${TEST})
     add_test(NAME ${TEST}
              COMMAND ${TEST})
 endforeach()
@@ -104,6 +112,17 @@ if(${FORTRAN_WRAPPER})
              COMMAND fortran_bindings)
 endif()
 
+if(${PYTHON_WRAPPER})
+    add_test (NAME python-tests
+        COMMAND ${Python_EXECUTABLE} -m pytest -s
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    )
+    set_tests_properties(python-tests
+        PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/wrappers/python/:$ENV{PYTHONPATH}")
+    set_tests_properties(python-tests
+        PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/source/:$ENV{LD_LIBRARY_PATH}")
+endif()
+
 if(${MATLAB_WRAPPER})
     configure_file("${CMAKE_CURRENT_SOURCE_DIR}/matlab_bindings.m.in"
                    "${CMAKE_CURRENT_BINARY_DIR}/matlab_bindings.m")
diff --git a/tests/Mooring/body_tests/bodyDrag.txt b/tests/Mooring/body_tests/bodyDrag.txt
new file mode 100644
index 00000000..60b24cda
--- /dev/null
+++ b/tests/Mooring/body_tests/bodyDrag.txt
@@ -0,0 +1,30 @@
+A simple test to ensure that body drag coefficients don't go negative in certain orientations
+------------------------- LINE TYPES --------------------------------------------------
+LineType    Diam      MassDenInAir    EA        BA/-zeta    EI      tbd   Can   Cat  Cdn   Cdt  
+(-)         (m)       (kg/m)          (N)       (Pa-s/-)    (n-m^2) (-)   (-)   (-)  (-)   (-)  
+spring      0.1       10.0            500.0     -0.4        0.0     0.0   0.0   0.0  0.0   0.00
+---------------------------- BODIES -----------------------------------------------------
+ID   Attachment  X0     Y0    Z0     r0      p0     y0     Mass  CG*   I*      Volume   CdA*   Ca
+(#)     (-)      (m)    (m)   (m)   (deg)   (deg)  (deg)   (kg)  (m)  (kg-m^2)  (m^3)   (m^2)  (-)
+1       free      0     0      0     0       -90      0      1e1     0    5        0     0.1    0
+----------------------- POINTS ----------------------------------------------
+Node      Type       X         Y          Z        M        V          CdA    CA
+(-)       (-)        (m)       (m)        (m)      (kg)     (m^3)      (m^2)  (-)
+1         Body1      0.0       0          0        0        0          0      0
+2         Fixed      0.0       0         -5        0        0          0      0
+-------------------------- LINES -------------------------------------------------
+Line     LineType NodeA     NodeB  UnstrLen  NumSegs     Flags/Outputs
+(-)      (-)       (-)       (-)   (m)         (-)          (-)
+1        spring      1         2    4.5        1           -
+-------------------------- SOLVER OPTIONS---------------------------------------------------
+2        writeLog     - Write a log file
+9.81        g            - No gravity
+0.002   dtM          - time step to use in mooring integration
+3.0e6    kb           - bottom stiffness
+3.0e5    cb           - bottom damping
+70       WtrDpth      - water depth
+3.0      ICDfac       - factor by which to scale drag coefficients during dynamic relaxation IC gen
+0.015    threshIC     - threshold for IC convergence
+0.0    TmaxIC       - threshold for IC convergence
+0.01     dtIC         - Time lapse between convergence tests (s)
+--------------------------- need this line -------------------------------------------------
diff --git a/tests/Mooring/body_tests/pinnedBody.txt b/tests/Mooring/body_tests/pinnedBody.txt
new file mode 100644
index 00000000..3eb0cdbe
--- /dev/null
+++ b/tests/Mooring/body_tests/pinnedBody.txt
@@ -0,0 +1,43 @@
+The points and line are dummy objects just to allow MD to run w/o error
+------------------------- LINE TYPES --------------------------------------------------
+LineType  Diam    MassDenInAir    EA       BA/-zeta    EI      Can   Cat  Cdn   Cdt 
+(-)       (m)       (kg/m)        (N)      (Pa-s/-)  (n-m^2)   (-)   (-)  (-)   (-)  
+nylon      0.124      35.0    5515288.0     -0.8      0.0     1.0   0.0  1.6   0.05
+---------------------- ROD TYPES ------------------------------------
+TypeName      Diam     Mass/m    Cd     Ca      CdEnd    CaEnd
+(name)        (m)      (kg/m)    (-)    (-)     (-)      (-)
+Can          2       5e3     0.6    1.0     1.2      1.0
+----------------------- BODIES ------------------------------------------------------
+ID   Attachment         X0      Y0     Z0     r0      p0     y0     Mass          CG*          I*      Volume   CdA*   Ca*
+(#)     (-)             (m)     (m)    (m)   (deg)   (deg)  (deg)   (kg)          (m)         (kg-m^2)  (m^3)   (m^2)  (-)
+1     CoupledPinned    0.00    0.00  -5.00   0.00   0.00   0.00   0.00        0.00        0.000e+00  0.00     0.00  0.00
+---------------------- RODS ----------------------------------------
+ID   RodType  Attachment     Xa    Ya    Za    Xb    Yb     Zb    NumSegs  RodOutputs
+(#)  (name)    (#/key)      (m)   (m)   (m)   (m)   (m)    (m)   (-)       (-)
+1      Can     Body1         0     0     -5     0     0     -15     3         p
+----------------------- POINTS ----------------------------------------------
+Node      Type      X        Y         Z        M        V         CdA   CA
+(-)       (-)      (m)      (m)       (m)      (kg)     (m^3)     (m^2)  (-)
+1         Fixed    30    0      -5       0        0          0     0
+2         Fixed    30    0      -10       0        0          0     0
+-------------------------- LINES -------------------------------------------------
+Line     LineType NodeA     NodeB  UnstrLen  NumSegs     Flags/Outputs
+(-)      (-)       (-)       (-)   (m)         (-)          (-)
+1        nylon      1         2    5        1           p
+-------------------------- SOLVER OPTIONS---------------------------------------------------
+2        writeLog     - Write a log file
+0.0     WtrDnsty     - Water Density
+0.001   dtM          - time step to use in mooring integration
+3.0e6    kb           - bottom stiffness
+3.0e5    cb           - bottom damping
+30       WtrDpth      - water depth
+0.015    threshIC     - threshold for IC convergence
+0.0    TmaxIC       - threshold for IC convergence
+0.01     dtIC         - Time lapse between convergence tests (s)
+------------------------------ OUTPUTS -----------------------------------------------------
+BODY1RX
+BODY1RY
+BODY1PX
+BODY1PY
+BODY1AY
+--------------------------- need this line -------------------------------------------------
\ No newline at end of file
diff --git a/tests/Mooring/polyester/motion.csv b/tests/Mooring/polyester/motion.csv
new file mode 100644
index 00000000..52858549
--- /dev/null
+++ b/tests/Mooring/polyester/motion.csv
@@ -0,0 +1,1601 @@
+"Time","X"
+0,1360.0
+1,1360.0064572539304
+2,1360.0475173246305
+3,1360.1470241698737
+4,1360.3183536684114
+5,1360.5658093638422
+6,1360.8860182084804
+7,1361.2693263072276
+8,1361.7011946614411
+9,1362.1635949128045
+10,1362.6364050871957
+11,1363.0988053385588
+12,1363.5306736927723
+13,1363.9139817915197
+14,1364.234190636158
+15,1364.4816463315885
+16,1364.6529758301263
+17,1364.7524826753695
+18,1364.7935427460695
+19,1364.8
+20,1364.8
+21,1364.8
+22,1364.8
+23,1364.8
+24,1364.8
+25,1364.8
+26,1364.8
+27,1364.8
+28,1364.8
+29,1364.8
+30,1364.8
+31,1364.8
+32,1364.8
+33,1364.8
+34,1364.8
+35,1364.8
+36,1364.8
+37,1364.8
+38,1364.8
+39,1364.8
+40,1364.8
+41,1364.8
+42,1364.8
+43,1364.8
+44,1364.8
+45,1364.8
+46,1364.8
+47,1364.8
+48,1364.8
+49,1364.8
+50,1364.8
+51,1364.8
+52,1364.8
+53,1364.8
+54,1364.8
+55,1364.8
+56,1364.8
+57,1364.8
+58,1364.8
+59,1364.8
+60,1364.8
+61,1364.8
+62,1364.8
+63,1364.8
+64,1364.8
+65,1364.8
+66,1364.8
+67,1364.8
+68,1364.8
+69,1364.8
+70,1364.8
+71,1364.8
+72,1364.8
+73,1364.8
+74,1364.8
+75,1364.8
+76,1364.8
+77,1364.8
+78,1364.8
+79,1364.8
+80,1364.8
+81,1364.8
+82,1364.8
+83,1364.8
+84,1364.8
+85,1364.8
+86,1364.8
+87,1364.8
+88,1364.8
+89,1364.8
+90,1364.8
+91,1364.8
+92,1364.8
+93,1364.8
+94,1364.8
+95,1364.8
+96,1364.8
+97,1364.8
+98,1364.8
+99,1364.8
+100,1364.8
+101,1364.8
+102,1364.8
+103,1364.8
+104,1364.8
+105,1364.8
+106,1364.8
+107,1364.8
+108,1364.8
+109,1364.8
+110,1364.8
+111,1364.8
+112,1364.8
+113,1364.8
+114,1364.8
+115,1364.8
+116,1364.8
+117,1364.8
+118,1364.8
+119,1364.8
+120,1364.8
+121,1364.8
+122,1364.8
+123,1364.8
+124,1364.8
+125,1364.8
+126,1364.8
+127,1364.8
+128,1364.8
+129,1364.8
+130,1364.8
+131,1364.8
+132,1364.8
+133,1364.8
+134,1364.8
+135,1364.8
+136,1364.8
+137,1364.8
+138,1364.8
+139,1364.8
+140,1364.8
+141,1364.8
+142,1364.8
+143,1364.8
+144,1364.8
+145,1364.8
+146,1364.8
+147,1364.8
+148,1364.8
+149,1364.8
+150,1364.8
+151,1364.8
+152,1364.8
+153,1364.8
+154,1364.8
+155,1364.8
+156,1364.8
+157,1364.8
+158,1364.8
+159,1364.8
+160,1364.8
+161,1364.8
+162,1364.8
+163,1364.8
+164,1364.8
+165,1364.8
+166,1364.8
+167,1364.8
+168,1364.8
+169,1364.8
+170,1364.8
+171,1364.8
+172,1364.8
+173,1364.8
+174,1364.8
+175,1364.8
+176,1364.8
+177,1364.8
+178,1364.8
+179,1364.8
+180,1364.8
+181,1364.8
+182,1364.8
+183,1364.8
+184,1364.8
+185,1364.8
+186,1364.8
+187,1364.8
+188,1364.8
+189,1364.8
+190,1364.8
+191,1364.8
+192,1364.8
+193,1364.8
+194,1364.8
+195,1364.8
+196,1364.8
+197,1364.8
+198,1364.8
+199,1364.8
+200,1364.8
+201,1364.8
+202,1364.8
+203,1364.8
+204,1364.8
+205,1364.8
+206,1364.8
+207,1364.8
+208,1364.8
+209,1364.8
+210,1364.8
+211,1364.8
+212,1364.8
+213,1364.8
+214,1364.8
+215,1364.8
+216,1364.8
+217,1364.8
+218,1364.8
+219,1364.8
+220,1366.269463130731
+221,1367.1776412907377
+222,1367.177641290738
+223,1366.269463130731
+224,1364.8
+225,1363.3305368692688
+226,1362.422358709262
+227,1362.422358709262
+228,1363.3305368692688
+229,1364.8
+230,1366.269463130731
+231,1367.177641290738
+232,1367.177641290738
+233,1366.269463130731
+234,1364.8
+235,1363.3305368692688
+236,1362.4223587092622
+237,1362.422358709262
+238,1363.3305368692688
+239,1364.8
+240,1366.269463130731
+241,1367.177641290738
+242,1367.177641290738
+243,1366.269463130731
+244,1364.8
+245,1363.3305368692688
+246,1362.422358709262
+247,1362.422358709262
+248,1363.3305368692688
+249,1364.8
+250,1366.269463130731
+251,1367.177641290738
+252,1367.177641290738
+253,1366.269463130731
+254,1364.8
+255,1363.3305368692688
+256,1362.422358709262
+257,1362.422358709262
+258,1363.3305368692688
+259,1364.8
+260,1366.269463130731
+261,1367.1776412907377
+262,1367.177641290738
+263,1366.269463130731
+264,1364.8
+265,1363.3305368692688
+266,1362.422358709262
+267,1362.422358709262
+268,1363.3305368692688
+269,1364.8
+270,1366.269463130731
+271,1367.177641290738
+272,1367.177641290738
+273,1366.269463130731
+274,1364.8
+275,1363.3305368692688
+276,1362.422358709262
+277,1362.422358709262
+278,1363.3305368692688
+279,1364.8
+280,1366.269463130731
+281,1367.177641290738
+282,1367.177641290738
+283,1366.269463130731
+284,1364.8
+285,1363.3305368692688
+286,1362.4223587092622
+287,1362.422358709262
+288,1363.3305368692688
+289,1364.8
+290,1366.269463130731
+291,1367.177641290738
+292,1367.177641290738
+293,1366.269463130731
+294,1364.8
+295,1363.3305368692688
+296,1362.422358709262
+297,1362.422358709262
+298,1363.3305368692688
+299,1364.8
+300,1366.269463130731
+301,1367.1776412907377
+302,1367.177641290738
+303,1366.269463130731
+304,1364.8
+305,1363.3305368692688
+306,1362.422358709262
+307,1362.422358709262
+308,1363.3305368692688
+309,1364.8
+310,1366.269463130731
+311,1367.177641290738
+312,1367.177641290738
+313,1366.269463130731
+314,1364.8
+315,1363.3305368692688
+316,1362.4223587092622
+317,1362.422358709262
+318,1363.3305368692688
+319,1364.8
+320,1364.8
+321,1364.8148651366523
+322,1364.9093888410762
+323,1365.1384618910633
+324,1365.5328766741557
+325,1366.1025403063447
+326,1366.8396877507723
+327,1367.7220949364303
+328,1368.7162918768595
+329,1369.7807757888518
+330,1370.8692242111483
+331,1371.9337081231404
+332,1372.9279050635696
+333,1373.8103122492275
+334,1374.5474596936551
+335,1375.1171233258444
+336,1375.5115381089365
+337,1375.7406111589237
+338,1375.8351348633475
+339,1375.85
+340,1375.85
+341,1375.85
+342,1375.85
+343,1375.85
+344,1375.85
+345,1375.85
+346,1375.85
+347,1375.85
+348,1375.85
+349,1375.85
+350,1375.85
+351,1375.85
+352,1375.85
+353,1375.85
+354,1375.85
+355,1375.85
+356,1375.85
+357,1375.85
+358,1375.85
+359,1375.85
+360,1375.85
+361,1375.85
+362,1375.85
+363,1375.85
+364,1375.85
+365,1375.85
+366,1375.85
+367,1375.85
+368,1375.85
+369,1375.85
+370,1375.85
+371,1375.85
+372,1375.85
+373,1375.85
+374,1375.85
+375,1375.85
+376,1375.85
+377,1375.85
+378,1375.85
+379,1375.85
+380,1375.85
+381,1375.85
+382,1375.85
+383,1375.85
+384,1375.85
+385,1375.85
+386,1375.85
+387,1375.85
+388,1375.85
+389,1375.85
+390,1375.85
+391,1375.85
+392,1375.85
+393,1375.85
+394,1375.85
+395,1375.85
+396,1375.85
+397,1375.85
+398,1375.85
+399,1375.85
+400,1375.85
+401,1375.85
+402,1375.85
+403,1375.85
+404,1375.85
+405,1375.85
+406,1375.85
+407,1375.85
+408,1375.85
+409,1375.85
+410,1375.85
+411,1375.85
+412,1375.85
+413,1375.85
+414,1375.85
+415,1375.85
+416,1375.85
+417,1375.85
+418,1375.85
+419,1375.85
+420,1375.85
+421,1375.85
+422,1375.85
+423,1375.85
+424,1375.85
+425,1375.85
+426,1375.85
+427,1375.85
+428,1375.85
+429,1375.85
+430,1375.85
+431,1375.85
+432,1375.85
+433,1375.85
+434,1375.85
+435,1375.85
+436,1375.85
+437,1375.85
+438,1375.85
+439,1375.85
+440,1375.85
+441,1375.85
+442,1375.85
+443,1375.85
+444,1375.85
+445,1375.85
+446,1375.85
+447,1375.85
+448,1375.85
+449,1375.85
+450,1375.85
+451,1375.85
+452,1375.85
+453,1375.85
+454,1375.85
+455,1375.85
+456,1375.85
+457,1375.85
+458,1375.85
+459,1375.85
+460,1375.85
+461,1375.85
+462,1375.85
+463,1375.85
+464,1375.85
+465,1375.85
+466,1375.85
+467,1375.85
+468,1375.85
+469,1375.85
+470,1375.85
+471,1375.85
+472,1375.85
+473,1375.85
+474,1375.85
+475,1375.85
+476,1375.85
+477,1375.85
+478,1375.85
+479,1375.85
+480,1375.85
+481,1375.85
+482,1375.85
+483,1375.85
+484,1375.85
+485,1375.85
+486,1375.85
+487,1375.85
+488,1375.85
+489,1375.85
+490,1375.85
+491,1375.85
+492,1375.85
+493,1375.85
+494,1375.85
+495,1375.85
+496,1375.85
+497,1375.85
+498,1375.85
+499,1375.85
+500,1375.85
+501,1375.85
+502,1375.85
+503,1375.85
+504,1375.85
+505,1375.85
+506,1375.85
+507,1375.85
+508,1375.85
+509,1375.85
+510,1375.85
+511,1375.85
+512,1375.85
+513,1375.85
+514,1375.85
+515,1375.85
+516,1375.85
+517,1375.85
+518,1375.85
+519,1375.85
+520,1375.85
+521,1375.85
+522,1375.85
+523,1375.85
+524,1375.85
+525,1375.85
+526,1375.85
+527,1375.85
+528,1375.85
+529,1375.85
+530,1375.85
+531,1375.85
+532,1375.85
+533,1375.85
+534,1375.85
+535,1375.85
+536,1375.85
+537,1375.85
+538,1375.85
+539,1375.85
+540,1377.319463130731
+541,1378.227641290738
+542,1378.227641290738
+543,1377.319463130731
+544,1375.85
+545,1374.3805368692688
+546,1373.472358709262
+547,1373.472358709262
+548,1374.3805368692688
+549,1375.85
+550,1377.319463130731
+551,1378.227641290738
+552,1378.227641290738
+553,1377.319463130731
+554,1375.85
+555,1374.3805368692688
+556,1373.4723587092622
+557,1373.472358709262
+558,1374.3805368692688
+559,1375.85
+560,1377.319463130731
+561,1378.2276412907377
+562,1378.227641290738
+563,1377.319463130731
+564,1375.85
+565,1374.3805368692688
+566,1373.472358709262
+567,1373.472358709262
+568,1374.3805368692686
+569,1375.85
+570,1377.319463130731
+571,1378.227641290738
+572,1378.227641290738
+573,1377.319463130731
+574,1375.85
+575,1374.3805368692688
+576,1373.472358709262
+577,1373.472358709262
+578,1374.3805368692688
+579,1375.85
+580,1377.319463130731
+581,1378.2276412907377
+582,1378.227641290738
+583,1377.319463130731
+584,1375.85
+585,1374.3805368692688
+586,1373.4723587092622
+587,1373.472358709262
+588,1374.3805368692688
+589,1375.85
+590,1377.319463130731
+591,1378.227641290738
+592,1378.227641290738
+593,1377.3194631307313
+594,1375.85
+595,1374.3805368692688
+596,1373.472358709262
+597,1373.472358709262
+598,1374.3805368692688
+599,1375.85
+600,1377.319463130731
+601,1378.227641290738
+602,1378.227641290738
+603,1377.319463130731
+604,1375.85
+605,1374.3805368692688
+606,1373.4723587092622
+607,1373.472358709262
+608,1374.3805368692688
+609,1375.85
+610,1377.319463130731
+611,1378.2276412907377
+612,1378.227641290738
+613,1377.319463130731
+614,1375.85
+615,1374.3805368692688
+616,1373.472358709262
+617,1373.472358709262
+618,1374.3805368692686
+619,1375.85
+620,1377.319463130731
+621,1378.227641290738
+622,1378.227641290738
+623,1377.319463130731
+624,1375.85
+625,1374.3805368692688
+626,1373.472358709262
+627,1373.472358709262
+628,1374.3805368692688
+629,1375.85
+630,1377.319463130731
+631,1378.227641290738
+632,1378.227641290738
+633,1377.319463130731
+634,1375.85
+635,1374.3805368692688
+636,1373.4723587092622
+637,1373.472358709262
+638,1374.3805368692688
+639,1375.85
+640,1375.85
+641,1375.8645691791805
+642,1375.9572109636972
+643,1376.1817232832773
+644,1376.5682854643533
+645,1377.1266073771685
+646,1377.8490785828837
+647,1378.7139174806823
+648,1379.6883204548767
+649,1380.7316110220147
+650,1381.798388977985
+651,1382.841679545123
+652,1383.8160825193177
+653,1384.680921417116
+654,1385.4033926228312
+655,1385.9617145356465
+656,1386.3482767167225
+657,1386.5727890363025
+658,1386.6654308208192
+659,1386.6799999999998
+660,1386.6799999999998
+661,1386.6799999999998
+662,1386.6799999999998
+663,1386.6799999999998
+664,1386.6799999999998
+665,1386.6799999999998
+666,1386.6799999999998
+667,1386.6799999999998
+668,1386.6799999999998
+669,1386.6799999999998
+670,1386.6799999999998
+671,1386.6799999999998
+672,1386.6799999999998
+673,1386.6799999999998
+674,1386.6799999999998
+675,1386.6799999999998
+676,1386.6799999999998
+677,1386.6799999999998
+678,1386.6799999999998
+679,1386.6799999999998
+680,1386.6799999999998
+681,1386.6799999999998
+682,1386.6799999999998
+683,1386.6799999999998
+684,1386.6799999999998
+685,1386.6799999999998
+686,1386.6799999999998
+687,1386.6799999999998
+688,1386.6799999999998
+689,1386.6799999999998
+690,1386.6799999999998
+691,1386.6799999999998
+692,1386.6799999999998
+693,1386.6799999999998
+694,1386.6799999999998
+695,1386.6799999999998
+696,1386.6799999999998
+697,1386.6799999999998
+698,1386.6799999999998
+699,1386.6799999999998
+700,1386.6799999999998
+701,1386.6799999999998
+702,1386.6799999999998
+703,1386.6799999999998
+704,1386.6799999999998
+705,1386.6799999999998
+706,1386.6799999999998
+707,1386.6799999999998
+708,1386.6799999999998
+709,1386.6799999999998
+710,1386.6799999999998
+711,1386.6799999999998
+712,1386.6799999999998
+713,1386.6799999999998
+714,1386.6799999999998
+715,1386.6799999999998
+716,1386.6799999999998
+717,1386.6799999999998
+718,1386.6799999999998
+719,1386.6799999999998
+720,1386.6799999999998
+721,1386.6799999999998
+722,1386.6799999999998
+723,1386.6799999999998
+724,1386.6799999999998
+725,1386.6799999999998
+726,1386.6799999999998
+727,1386.6799999999998
+728,1386.6799999999998
+729,1386.6799999999998
+730,1386.6799999999998
+731,1386.6799999999998
+732,1386.6799999999998
+733,1386.6799999999998
+734,1386.6799999999998
+735,1386.6799999999998
+736,1386.6799999999998
+737,1386.6799999999998
+738,1386.6799999999998
+739,1386.6799999999998
+740,1386.6799999999998
+741,1386.6799999999998
+742,1386.6799999999998
+743,1386.6799999999998
+744,1386.6799999999998
+745,1386.6799999999998
+746,1386.6799999999998
+747,1386.6799999999998
+748,1386.6799999999998
+749,1386.6799999999998
+750,1386.6799999999998
+751,1386.6799999999998
+752,1386.6799999999998
+753,1386.6799999999998
+754,1386.6799999999998
+755,1386.6799999999998
+756,1386.6799999999998
+757,1386.6799999999998
+758,1386.6799999999998
+759,1386.6799999999998
+760,1386.6799999999998
+761,1386.6799999999998
+762,1386.6799999999998
+763,1386.6799999999998
+764,1386.6799999999998
+765,1386.6799999999998
+766,1386.6799999999998
+767,1386.6799999999998
+768,1386.6799999999998
+769,1386.6799999999998
+770,1386.6799999999998
+771,1386.6799999999998
+772,1386.6799999999998
+773,1386.6799999999998
+774,1386.6799999999998
+775,1386.6799999999998
+776,1386.6799999999998
+777,1386.6799999999998
+778,1386.6799999999998
+779,1386.6799999999998
+780,1386.6799999999998
+781,1386.6799999999998
+782,1386.6799999999998
+783,1386.6799999999998
+784,1386.6799999999998
+785,1386.6799999999998
+786,1386.6799999999998
+787,1386.6799999999998
+788,1386.6799999999998
+789,1386.6799999999998
+790,1386.6799999999998
+791,1386.6799999999998
+792,1386.6799999999998
+793,1386.6799999999998
+794,1386.6799999999998
+795,1386.6799999999998
+796,1386.6799999999998
+797,1386.6799999999998
+798,1386.6799999999998
+799,1386.6799999999998
+800,1386.6799999999998
+801,1386.6799999999998
+802,1386.6799999999998
+803,1386.6799999999998
+804,1386.6799999999998
+805,1386.6799999999998
+806,1386.6799999999998
+807,1386.6799999999998
+808,1386.6799999999998
+809,1386.6799999999998
+810,1386.6799999999998
+811,1386.6799999999998
+812,1386.6799999999998
+813,1386.6799999999998
+814,1386.6799999999998
+815,1386.6799999999998
+816,1386.6799999999998
+817,1386.6799999999998
+818,1386.6799999999998
+819,1386.6799999999998
+820,1386.6799999999998
+821,1386.6799999999998
+822,1386.6799999999998
+823,1386.6799999999998
+824,1386.6799999999998
+825,1386.6799999999998
+826,1386.6799999999998
+827,1386.6799999999998
+828,1386.6799999999998
+829,1386.6799999999998
+830,1386.6799999999998
+831,1386.6799999999998
+832,1386.6799999999998
+833,1386.6799999999998
+834,1386.6799999999998
+835,1386.6799999999998
+836,1386.6799999999998
+837,1386.6799999999998
+838,1386.6799999999998
+839,1386.6799999999998
+840,1386.6799999999998
+841,1386.6799999999998
+842,1386.6799999999998
+843,1386.6799999999998
+844,1386.6799999999998
+845,1386.6799999999998
+846,1386.6799999999998
+847,1386.6799999999998
+848,1386.6799999999998
+849,1386.6799999999998
+850,1386.6799999999998
+851,1386.6799999999998
+852,1386.6799999999998
+853,1386.6799999999998
+854,1386.6799999999998
+855,1386.6799999999998
+856,1386.6799999999998
+857,1386.6799999999998
+858,1386.6799999999998
+859,1386.6799999999998
+860,1388.149463130731
+861,1389.0576412907376
+862,1389.0576412907376
+863,1388.149463130731
+864,1386.6799999999998
+865,1385.2105368692687
+866,1384.302358709262
+867,1384.302358709262
+868,1385.2105368692687
+869,1386.6799999999998
+870,1388.149463130731
+871,1389.0576412907378
+872,1389.0576412907378
+873,1388.149463130731
+874,1386.6799999999998
+875,1385.2105368692687
+876,1384.3023587092619
+877,1384.3023587092619
+878,1385.2105368692687
+879,1386.6799999999998
+880,1388.149463130731
+881,1389.0576412907378
+882,1389.0576412907378
+883,1388.149463130731
+884,1386.68
+885,1385.2105368692685
+886,1384.3023587092619
+887,1384.3023587092619
+888,1385.2105368692685
+889,1386.6799999999996
+890,1388.149463130731
+891,1389.0576412907378
+892,1389.0576412907378
+893,1388.1494631307312
+894,1386.6799999999998
+895,1385.2105368692687
+896,1384.3023587092619
+897,1384.3023587092619
+898,1385.2105368692685
+899,1386.6799999999998
+900,1388.149463130731
+901,1389.0576412907376
+902,1389.0576412907378
+903,1388.149463130731
+904,1386.6799999999998
+905,1385.2105368692687
+906,1384.302358709262
+907,1384.3023587092619
+908,1385.2105368692687
+909,1386.6799999999998
+910,1388.149463130731
+911,1389.0576412907376
+912,1389.0576412907376
+913,1388.149463130731
+914,1386.6799999999998
+915,1385.2105368692687
+916,1384.302358709262
+917,1384.302358709262
+918,1385.2105368692687
+919,1386.6799999999998
+920,1388.149463130731
+921,1389.0576412907376
+922,1389.0576412907376
+923,1388.149463130731
+924,1386.6799999999998
+925,1385.2105368692687
+926,1384.3023587092619
+927,1384.3023587092619
+928,1385.2105368692687
+929,1386.6799999999998
+930,1388.149463130731
+931,1389.0576412907378
+932,1389.0576412907378
+933,1388.149463130731
+934,1386.68
+935,1385.2105368692685
+936,1384.3023587092619
+937,1384.3023587092619
+938,1385.2105368692685
+939,1386.6799999999996
+940,1388.1494631307312
+941,1389.0576412907378
+942,1389.0576412907378
+943,1388.1494631307312
+944,1386.68
+945,1385.2105368692687
+946,1384.3023587092619
+947,1384.3023587092619
+948,1385.2105368692685
+949,1386.6799999999998
+950,1388.149463130731
+951,1389.0576412907378
+952,1389.0576412907378
+953,1388.1494631307312
+954,1386.6799999999998
+955,1385.2105368692687
+956,1384.302358709262
+957,1384.3023587092619
+958,1385.2105368692687
+959,1386.6799999999998
+960,1386.6799999999998
+961,1386.6945288213435
+962,1386.7869139804181
+963,1387.0108043822156
+964,1387.3962957539256
+965,1387.9530710686445
+966,1388.6735409690807
+967,1389.535984191262
+968,1390.5076879882427
+969,1391.5480885538097
+970,1392.6119114461899
+971,1393.652312011757
+972,1394.6240158087378
+973,1395.4864590309191
+974,1396.2069289313552
+975,1396.763704246074
+976,1397.149195617784
+977,1397.3730860195815
+978,1397.4654711786563
+979,1397.4799999999998
+980,1397.4799999999998
+981,1397.4799999999998
+982,1397.4799999999998
+983,1397.4799999999998
+984,1397.4799999999998
+985,1397.4799999999998
+986,1397.4799999999998
+987,1397.4799999999998
+988,1397.4799999999998
+989,1397.4799999999998
+990,1397.4799999999998
+991,1397.4799999999998
+992,1397.4799999999998
+993,1397.4799999999998
+994,1397.4799999999998
+995,1397.4799999999998
+996,1397.4799999999998
+997,1397.4799999999998
+998,1397.4799999999998
+999,1397.4799999999998
+1000,1397.4799999999998
+1001,1397.4799999999998
+1002,1397.4799999999998
+1003,1397.4799999999998
+1004,1397.4799999999998
+1005,1397.4799999999998
+1006,1397.4799999999998
+1007,1397.4799999999998
+1008,1397.4799999999998
+1009,1397.4799999999998
+1010,1397.4799999999998
+1011,1397.4799999999998
+1012,1397.4799999999998
+1013,1397.4799999999998
+1014,1397.4799999999998
+1015,1397.4799999999998
+1016,1397.4799999999998
+1017,1397.4799999999998
+1018,1397.4799999999998
+1019,1397.4799999999998
+1020,1397.4799999999998
+1021,1397.4799999999998
+1022,1397.4799999999998
+1023,1397.4799999999998
+1024,1397.4799999999998
+1025,1397.4799999999998
+1026,1397.4799999999998
+1027,1397.4799999999998
+1028,1397.4799999999998
+1029,1397.4799999999998
+1030,1397.4799999999998
+1031,1397.4799999999998
+1032,1397.4799999999998
+1033,1397.4799999999998
+1034,1397.4799999999998
+1035,1397.4799999999998
+1036,1397.4799999999998
+1037,1397.4799999999998
+1038,1397.4799999999998
+1039,1397.4799999999998
+1040,1397.4799999999998
+1041,1397.4799999999998
+1042,1397.4799999999998
+1043,1397.4799999999998
+1044,1397.4799999999998
+1045,1397.4799999999998
+1046,1397.4799999999998
+1047,1397.4799999999998
+1048,1397.4799999999998
+1049,1397.4799999999998
+1050,1397.4799999999998
+1051,1397.4799999999998
+1052,1397.4799999999998
+1053,1397.4799999999998
+1054,1397.4799999999998
+1055,1397.4799999999998
+1056,1397.4799999999998
+1057,1397.4799999999998
+1058,1397.4799999999998
+1059,1397.4799999999998
+1060,1397.4799999999998
+1061,1397.4799999999998
+1062,1397.4799999999998
+1063,1397.4799999999998
+1064,1397.4799999999998
+1065,1397.4799999999998
+1066,1397.4799999999998
+1067,1397.4799999999998
+1068,1397.4799999999998
+1069,1397.4799999999998
+1070,1397.4799999999998
+1071,1397.4799999999998
+1072,1397.4799999999998
+1073,1397.4799999999998
+1074,1397.4799999999998
+1075,1397.4799999999998
+1076,1397.4799999999998
+1077,1397.4799999999998
+1078,1397.4799999999998
+1079,1397.4799999999998
+1080,1397.4799999999998
+1081,1397.4799999999998
+1082,1397.4799999999998
+1083,1397.4799999999998
+1084,1397.4799999999998
+1085,1397.4799999999998
+1086,1397.4799999999998
+1087,1397.4799999999998
+1088,1397.4799999999998
+1089,1397.4799999999998
+1090,1397.4799999999998
+1091,1397.4799999999998
+1092,1397.4799999999998
+1093,1397.4799999999998
+1094,1397.4799999999998
+1095,1397.4799999999998
+1096,1397.4799999999998
+1097,1397.4799999999998
+1098,1397.4799999999998
+1099,1397.4799999999998
+1100,1397.4799999999998
+1101,1397.4799999999998
+1102,1397.4799999999998
+1103,1397.4799999999998
+1104,1397.4799999999998
+1105,1397.4799999999998
+1106,1397.4799999999998
+1107,1397.4799999999998
+1108,1397.4799999999998
+1109,1397.4799999999998
+1110,1397.4799999999998
+1111,1397.4799999999998
+1112,1397.4799999999998
+1113,1397.4799999999998
+1114,1397.4799999999998
+1115,1397.4799999999998
+1116,1397.4799999999998
+1117,1397.4799999999998
+1118,1397.4799999999998
+1119,1397.4799999999998
+1120,1397.4799999999998
+1121,1397.4799999999998
+1122,1397.4799999999998
+1123,1397.4799999999998
+1124,1397.4799999999998
+1125,1397.4799999999998
+1126,1397.4799999999998
+1127,1397.4799999999998
+1128,1397.4799999999998
+1129,1397.4799999999998
+1130,1397.4799999999998
+1131,1397.4799999999998
+1132,1397.4799999999998
+1133,1397.4799999999998
+1134,1397.4799999999998
+1135,1397.4799999999998
+1136,1397.4799999999998
+1137,1397.4799999999998
+1138,1397.4799999999998
+1139,1397.4799999999998
+1140,1397.4799999999998
+1141,1397.4799999999998
+1142,1397.4799999999998
+1143,1397.4799999999998
+1144,1397.4799999999998
+1145,1397.4799999999998
+1146,1397.4799999999998
+1147,1397.4799999999998
+1148,1397.4799999999998
+1149,1397.4799999999998
+1150,1397.4799999999998
+1151,1397.4799999999998
+1152,1397.4799999999998
+1153,1397.4799999999998
+1154,1397.4799999999998
+1155,1397.4799999999998
+1156,1397.4799999999998
+1157,1397.4799999999998
+1158,1397.4799999999998
+1159,1397.4799999999998
+1160,1397.4799999999998
+1161,1397.4799999999998
+1162,1397.4799999999998
+1163,1397.4799999999998
+1164,1397.4799999999998
+1165,1397.4799999999998
+1166,1397.4799999999998
+1167,1397.4799999999998
+1168,1397.4799999999998
+1169,1397.4799999999998
+1170,1397.4799999999998
+1171,1397.4799999999998
+1172,1397.4799999999998
+1173,1397.4799999999998
+1174,1397.4799999999998
+1175,1397.4799999999998
+1176,1397.4799999999998
+1177,1397.4799999999998
+1178,1397.4799999999998
+1179,1397.4799999999998
+1180,1398.949463130731
+1181,1399.8576412907375
+1182,1399.8576412907375
+1183,1398.949463130731
+1184,1397.4799999999998
+1185,1396.0105368692687
+1186,1395.102358709262
+1187,1395.102358709262
+1188,1396.0105368692687
+1189,1397.4799999999998
+1190,1398.949463130731
+1191,1399.8576412907378
+1192,1399.8576412907378
+1193,1398.949463130731
+1194,1397.4799999999998
+1195,1396.0105368692687
+1196,1395.1023587092618
+1197,1395.1023587092618
+1198,1396.0105368692687
+1199,1397.4799999999998
+1200,1398.949463130731
+1201,1399.8576412907378
+1202,1399.8576412907378
+1203,1398.949463130731
+1204,1397.48
+1205,1396.0105368692684
+1206,1395.1023587092618
+1207,1395.1023587092618
+1208,1396.0105368692684
+1209,1397.4799999999996
+1210,1398.949463130731
+1211,1399.8576412907378
+1212,1399.8576412907378
+1213,1398.9494631307311
+1214,1397.4799999999998
+1215,1396.0105368692687
+1216,1395.1023587092618
+1217,1395.1023587092618
+1218,1396.0105368692684
+1219,1397.4799999999998
+1220,1398.949463130731
+1221,1399.8576412907375
+1222,1399.8576412907378
+1223,1398.949463130731
+1224,1397.4799999999998
+1225,1396.0105368692687
+1226,1395.102358709262
+1227,1395.1023587092618
+1228,1396.0105368692687
+1229,1397.4799999999998
+1230,1398.949463130731
+1231,1399.8576412907375
+1232,1399.8576412907375
+1233,1398.949463130731
+1234,1397.4799999999998
+1235,1396.0105368692687
+1236,1395.102358709262
+1237,1395.102358709262
+1238,1396.0105368692687
+1239,1397.4799999999998
+1240,1398.949463130731
+1241,1399.8576412907375
+1242,1399.8576412907375
+1243,1398.949463130731
+1244,1397.4799999999998
+1245,1396.0105368692687
+1246,1395.1023587092618
+1247,1395.1023587092618
+1248,1396.0105368692687
+1249,1397.4799999999998
+1250,1398.949463130731
+1251,1399.8576412907378
+1252,1399.8576412907378
+1253,1398.949463130731
+1254,1397.48
+1255,1396.0105368692684
+1256,1395.1023587092618
+1257,1395.1023587092618
+1258,1396.0105368692684
+1259,1397.4799999999996
+1260,1398.9494631307311
+1261,1399.8576412907378
+1262,1399.8576412907378
+1263,1398.9494631307311
+1264,1397.48
+1265,1396.0105368692687
+1266,1395.1023587092618
+1267,1395.1023587092618
+1268,1396.0105368692684
+1269,1397.4799999999998
+1270,1398.949463130731
+1271,1399.8576412907378
+1272,1399.8576412907378
+1273,1398.9494631307311
+1274,1397.4799999999998
+1275,1396.0105368692687
+1276,1395.102358709262
+1277,1395.1023587092618
+1278,1396.0105368692687
+1279,1397.4799999999998
+1280,1397.4799999999998
+1281,1397.4945019161187
+1282,1397.5867159915656
+1283,1397.8101917815077
+1284,1398.1949692803073
+1285,1398.7507135296285
+1286,1399.4698492265452
+1287,1400.3306953316485
+1288,1401.3005996771533
+1289,1402.3390735750063
+1290,1403.4009264249933
+1291,1404.4394003228463
+1292,1405.409304668351
+1293,1406.2701507734544
+1294,1406.989286470371
+1295,1407.5450307196922
+1296,1407.9298082184919
+1297,1408.153284008434
+1298,1408.2454980838809
+1299,1408.2599999999998
+1300,1408.2599999999998
+1301,1408.2599999999998
+1302,1408.2599999999998
+1303,1408.2599999999998
+1304,1408.2599999999998
+1305,1408.2599999999998
+1306,1408.2599999999998
+1307,1408.2599999999998
+1308,1408.2599999999998
+1309,1408.2599999999998
+1310,1408.2599999999998
+1311,1408.2599999999998
+1312,1408.2599999999998
+1313,1408.2599999999998
+1314,1408.2599999999998
+1315,1408.2599999999998
+1316,1408.2599999999998
+1317,1408.2599999999998
+1318,1408.2599999999998
+1319,1408.2599999999998
+1320,1408.2599999999998
+1321,1408.2599999999998
+1322,1408.2599999999998
+1323,1408.2599999999998
+1324,1408.2599999999998
+1325,1408.2599999999998
+1326,1408.2599999999998
+1327,1408.2599999999998
+1328,1408.2599999999998
+1329,1408.2599999999998
+1330,1408.2599999999998
+1331,1408.2599999999998
+1332,1408.2599999999998
+1333,1408.2599999999998
+1334,1408.2599999999998
+1335,1408.2599999999998
+1336,1408.2599999999998
+1337,1408.2599999999998
+1338,1408.2599999999998
+1339,1408.2599999999998
+1340,1408.2599999999998
+1341,1408.2599999999998
+1342,1408.2599999999998
+1343,1408.2599999999998
+1344,1408.2599999999998
+1345,1408.2599999999998
+1346,1408.2599999999998
+1347,1408.2599999999998
+1348,1408.2599999999998
+1349,1408.2599999999998
+1350,1408.2599999999998
+1351,1408.2599999999998
+1352,1408.2599999999998
+1353,1408.2599999999998
+1354,1408.2599999999998
+1355,1408.2599999999998
+1356,1408.2599999999998
+1357,1408.2599999999998
+1358,1408.2599999999998
+1359,1408.2599999999998
+1360,1408.2599999999998
+1361,1408.2599999999998
+1362,1408.2599999999998
+1363,1408.2599999999998
+1364,1408.2599999999998
+1365,1408.2599999999998
+1366,1408.2599999999998
+1367,1408.2599999999998
+1368,1408.2599999999998
+1369,1408.2599999999998
+1370,1408.2599999999998
+1371,1408.2599999999998
+1372,1408.2599999999998
+1373,1408.2599999999998
+1374,1408.2599999999998
+1375,1408.2599999999998
+1376,1408.2599999999998
+1377,1408.2599999999998
+1378,1408.2599999999998
+1379,1408.2599999999998
+1380,1408.2599999999998
+1381,1408.2599999999998
+1382,1408.2599999999998
+1383,1408.2599999999998
+1384,1408.2599999999998
+1385,1408.2599999999998
+1386,1408.2599999999998
+1387,1408.2599999999998
+1388,1408.2599999999998
+1389,1408.2599999999998
+1390,1408.2599999999998
+1391,1408.2599999999998
+1392,1408.2599999999998
+1393,1408.2599999999998
+1394,1408.2599999999998
+1395,1408.2599999999998
+1396,1408.2599999999998
+1397,1408.2599999999998
+1398,1408.2599999999998
+1399,1408.2599999999998
+1400,1408.2599999999998
+1401,1408.2599999999998
+1402,1408.2599999999998
+1403,1408.2599999999998
+1404,1408.2599999999998
+1405,1408.2599999999998
+1406,1408.2599999999998
+1407,1408.2599999999998
+1408,1408.2599999999998
+1409,1408.2599999999998
+1410,1408.2599999999998
+1411,1408.2599999999998
+1412,1408.2599999999998
+1413,1408.2599999999998
+1414,1408.2599999999998
+1415,1408.2599999999998
+1416,1408.2599999999998
+1417,1408.2599999999998
+1418,1408.2599999999998
+1419,1408.2599999999998
+1420,1408.2599999999998
+1421,1408.2599999999998
+1422,1408.2599999999998
+1423,1408.2599999999998
+1424,1408.2599999999998
+1425,1408.2599999999998
+1426,1408.2599999999998
+1427,1408.2599999999998
+1428,1408.2599999999998
+1429,1408.2599999999998
+1430,1408.2599999999998
+1431,1408.2599999999998
+1432,1408.2599999999998
+1433,1408.2599999999998
+1434,1408.2599999999998
+1435,1408.2599999999998
+1436,1408.2599999999998
+1437,1408.2599999999998
+1438,1408.2599999999998
+1439,1408.2599999999998
+1440,1408.2599999999998
+1441,1408.2599999999998
+1442,1408.2599999999998
+1443,1408.2599999999998
+1444,1408.2599999999998
+1445,1408.2599999999998
+1446,1408.2599999999998
+1447,1408.2599999999998
+1448,1408.2599999999998
+1449,1408.2599999999998
+1450,1408.2599999999998
+1451,1408.2599999999998
+1452,1408.2599999999998
+1453,1408.2599999999998
+1454,1408.2599999999998
+1455,1408.2599999999998
+1456,1408.2599999999998
+1457,1408.2599999999998
+1458,1408.2599999999998
+1459,1408.2599999999998
+1460,1408.2599999999998
+1461,1408.2599999999998
+1462,1408.2599999999998
+1463,1408.2599999999998
+1464,1408.2599999999998
+1465,1408.2599999999998
+1466,1408.2599999999998
+1467,1408.2599999999998
+1468,1408.2599999999998
+1469,1408.2599999999998
+1470,1408.2599999999998
+1471,1408.2599999999998
+1472,1408.2599999999998
+1473,1408.2599999999998
+1474,1408.2599999999998
+1475,1408.2599999999998
+1476,1408.2599999999998
+1477,1408.2599999999998
+1478,1408.2599999999998
+1479,1408.2599999999998
+1480,1408.2599999999998
+1481,1408.2599999999998
+1482,1408.2599999999998
+1483,1408.2599999999998
+1484,1408.2599999999998
+1485,1408.2599999999998
+1486,1408.2599999999998
+1487,1408.2599999999998
+1488,1408.2599999999998
+1489,1408.2599999999998
+1490,1408.2599999999998
+1491,1408.2599999999998
+1492,1408.2599999999998
+1493,1408.2599999999998
+1494,1408.2599999999998
+1495,1408.2599999999998
+1496,1408.2599999999998
+1497,1408.2599999999998
+1498,1408.2599999999998
+1499,1408.2599999999998
+1500,1409.729463130731
+1501,1410.6376412907375
+1502,1410.6376412907375
+1503,1409.729463130731
+1504,1408.2599999999998
+1505,1406.7905368692686
+1506,1405.882358709262
+1507,1405.882358709262
+1508,1406.7905368692686
+1509,1408.2599999999998
+1510,1409.729463130731
+1511,1410.6376412907377
+1512,1410.6376412907377
+1513,1409.729463130731
+1514,1408.2599999999998
+1515,1406.7905368692686
+1516,1405.8823587092618
+1517,1405.8823587092618
+1518,1406.7905368692686
+1519,1408.2599999999998
+1520,1409.729463130731
+1521,1410.6376412907377
+1522,1410.6376412907377
+1523,1409.729463130731
+1524,1408.26
+1525,1406.7905368692684
+1526,1405.8823587092618
+1527,1405.8823587092618
+1528,1406.7905368692684
+1529,1408.2599999999995
+1530,1409.729463130731
+1531,1410.6376412907377
+1532,1410.6376412907377
+1533,1409.7294631307311
+1534,1408.2599999999998
+1535,1406.7905368692686
+1536,1405.8823587092618
+1537,1405.8823587092618
+1538,1406.7905368692684
+1539,1408.2599999999998
+1540,1409.729463130731
+1541,1410.6376412907375
+1542,1410.6376412907377
+1543,1409.729463130731
+1544,1408.2599999999998
+1545,1406.7905368692686
+1546,1405.882358709262
+1547,1405.8823587092618
+1548,1406.7905368692686
+1549,1408.2599999999998
+1550,1409.729463130731
+1551,1410.6376412907375
+1552,1410.6376412907375
+1553,1409.729463130731
+1554,1408.2599999999998
+1555,1406.7905368692686
+1556,1405.882358709262
+1557,1405.882358709262
+1558,1406.7905368692686
+1559,1408.2599999999998
+1560,1409.729463130731
+1561,1410.6376412907375
+1562,1410.6376412907375
+1563,1409.729463130731
+1564,1408.2599999999998
+1565,1406.7905368692686
+1566,1405.8823587092618
+1567,1405.8823587092618
+1568,1406.7905368692686
+1569,1408.2599999999998
+1570,1409.729463130731
+1571,1410.6376412907377
+1572,1410.6376412907377
+1573,1409.729463130731
+1574,1408.26
+1575,1406.7905368692684
+1576,1405.8823587092618
+1577,1405.8823587092618
+1578,1406.7905368692684
+1579,1408.2599999999995
+1580,1409.7294631307311
+1581,1410.6376412907377
+1582,1410.6376412907377
+1583,1409.7294631307311
+1584,1408.26
+1585,1406.7905368692686
+1586,1405.8823587092618
+1587,1405.8823587092618
+1588,1406.7905368692684
+1589,1408.2599999999998
+1590,1409.729463130731
+1591,1410.6376412907377
+1592,1410.6376412907377
+1593,1409.7294631307311
+1594,1408.2599999999998
+1595,1406.7905368692686
+1596,1405.882358709262
+1597,1405.8823587092618
+1598,1406.7905368692686
+1599,1408.2599999999998
diff --git a/tests/Mooring/polyester/simple.txt b/tests/Mooring/polyester/simple.txt
new file mode 100644
index 00000000..7bd617c8
--- /dev/null
+++ b/tests/Mooring/polyester/simple.txt
@@ -0,0 +1,30 @@
+--------------------- MoorDyn Input File ------------------------------------
+MoorDyn input file for Polyester stiffness and length variation as a function
+of the tension
+----------------------- LINE TYPES ------------------------------------------
+TypeName         Diam    Mass/m     EA         BA/-zeta    EI         Cd     Ca     CdAx    CaAx
+(name)           (m)     (kg/m)     (N)        (N-s/-)     (N-m^2)    (-)    (-)    (-)     (-)
+Polyester_290mm  0.217   52.0       3.339E+08  -1.0        0.0        1.6    1.79   0.008   0.0    
+---------------------- POINT PROPERTIES --------------------------------
+ID    Type      X       Y       Z         Mass   Volume  CdA    Ca
+(#)   (-)       (m)     (m)     (m)       (kg)   (mˆ3)   (m^2)  (-)
+1    Fixed      0.0     0.0     -372.192  0.0    0.0     0.0    0.0
+2    Coupled    1360.0  0.0     -18.0     0.0    0.0     0.0    0.0
+---------------------- LINES ----------------------------------------
+ID   LineType         AttachA  AttachB   UnstrLen  NumSegs  LineOutputs
+(#)  (name)           (#)      (#)       (m)       (-)      (-)
+1    Polyester_290mm  1        2         1400.0    70       -
+---------------------- OPTIONS -----------------------------------------
+2             writeLog             Write a log file
+0.005         dtM                  time step to use in mooring integration (s)
+1.0e5         kBot                 bottom stiffness (Pa/m)
+1.0e4         cBot                 bottom damping (Pa-s/m)
+9.80665       g                    gravitational constant (m/s^2)
+1025.0        WtrDnsty             water density (kg/m^3)
+372.3         WtrDpth              water depth (m)
+4.0           CdScaleIC            factor by which to scale drag coefficients during dynamic relaxation (-)
+0.5           FrictionCoefficient  general bottom friction coefficient, as a start (-)
+0.1           dtIC                 time interval for analyzing convergence during IC gen (s)
+100           TmaxIC               max time for ic gen (s)
+0.01          threshIC             threshold for IC convergence (-)
+------------------------- need this line -------------------------------------- 
diff --git a/tests/bodies.cpp b/tests/bodies.cpp
index 39267ba7..83928458 100644
--- a/tests/bodies.cpp
+++ b/tests/bodies.cpp
@@ -42,6 +42,8 @@
 #include <algorithm>
 #include <cmath>
 #include <limits>
+#include <vector>
+#include <numeric>
 
 #include "util.h"
 
@@ -384,7 +386,9 @@ rotatingBody(SeriesWriter* series_writer)
 	double angle = moordyn::rad2deg * 2 * acos(q.w());
 	double denom = (sqrt(1 - q.w() * q.w()));
 	moordyn::vec3 axis{ q.x() / denom, q.y() / denom, q.z() / denom };
-	if (!(axis.x() > 0.85 && axis.y() < 0.2 && axis.z() < 0.2)) {
+	if (!(abs(axis.x()) > 0.85 && abs(axis.y()) < 0.2 &&
+	      abs(axis.z()) < 0.2)) { // if we are just checking axis direction then
+		                          // +/- does not matter
 		cerr << "The final rotation of the body in angle axis form should "
 		        "have an axis in the x direction, but axis is "
 		     << axis.transpose() << endl;
@@ -397,7 +401,7 @@ rotatingBody(SeriesWriter* series_writer)
 	while (angle < -180) {
 		angle += 360;
 	}
-	if (!(abs(angle - (-90)) < 10)) {
+	if (!(abs(angle - 90) < 10)) {
 		cerr << "The final rotation of the body in angle-axis form should "
 		        "have a angle near 90 degrees but angle is "
 		     << angle << endl;
@@ -415,6 +419,186 @@ rotatingBody(SeriesWriter* series_writer)
 
 	return true;
 }
+
+/**
+ * @brief Compares inertial deflection of a pinned body to analytical solution
+ *
+ *
+ * The coupled pinned body that is massless and volumeless with a rod fixed to
+ * it is moved with constant acceleration in a vaccum (0 water density). The
+ * resulting avg inertial deflection should match an analytical solution of
+ * theta = arctan(-accel/gravity)
+ *
+ * This solution was derived both with Newtonian and LaGrangian mechanics.
+ * It is the same as the pendulum on an accelerating cart problem.
+ *
+ * This only tests the inertial properties of pinned bodies, other tests deal
+ * with hydrodynamics and general body properties
+ *
+ *
+ * @param series_writer
+ * @return true
+ * @return false
+ */
+bool
+pinnedBody(SeriesWriter* series_writer)
+{
+	int err;
+	cout << endl << " => " << __PRETTY_FUNC_NAME__ << "..." << endl;
+
+	MoorDyn system = MoorDyn_Create("Mooring/body_tests/pinnedBody.txt");
+	if (!system) {
+		cerr << "Failure Creating the Mooring system" << endl;
+		return false;
+	}
+
+	unsigned int n_dof;
+	err = MoorDyn_NCoupledDOF(system, &n_dof);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure getting NCoupledDOF: " << err << endl;
+		return false;
+	}
+	if (n_dof !=
+	    6) { // rotational DOF are ignored by MDC, same as a coupled pinned rods
+		cerr << "Expected 6 DOFs but got " << n_dof << endl;
+		return false;
+	}
+
+	moordyn::vec6 x{ 0, 0, -5, 0, 0, 0 };
+	moordyn::vec6 xd{ 0, 0, 0, 0, 0, 0 };
+	double f[6];
+
+	err = MoorDyn_Init(system, x.data(), xd.data());
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure during the mooring initialization: " << err << endl;
+		return false;
+	}
+
+	if (!write_system_vtk(system, 0, series_writer)) {
+		return false;
+	}
+
+	auto body = MoorDyn_GetBody(system, 1);
+	moordyn::vec6 r, rd;
+	vector<double> roll;
+	int i = 0, j = 0;
+	double t = 0.0, dt = 0.01, accel = 0.5;
+	bool local_min_max;
+
+	if (!write_system_vtk(system, t, series_writer)) {
+		return false;
+	}
+
+	while (t < 50.0) {
+
+		x[1] = 0.5 * accel * pow(t, 2);
+		xd[1] = accel * t;
+		err = MoorDyn_Step(system, x.data(), xd.data(), f, &t, &dt);
+		if (err != MOORDYN_SUCCESS) {
+			cerr << "Failure during the mooring initialization: " << err
+			     << endl;
+			return false;
+		}
+		if (!write_system_vtk(system, t, series_writer)) {
+			return false;
+		}
+
+		MoorDyn_GetBodyState(body, r.data(), rd.data());
+		roll.push_back(r[3]);
+
+		if (i >= 30) { // after the simulation has run for a few time steps
+			// When local min or max of oscillation, indicates half of an
+			// oscialltion has occured
+			local_min_max = (((roll[i] - roll[i - 1]) / dt) *
+			                 ((roll[i - 1] - roll[i - 2]) / dt)) < 0;
+			if (local_min_max)
+				j++;
+		}
+		if (j > 3)
+			break; // after 2 full oscillations
+
+		t = t + dt;
+		i++;
+	}
+	double theta = atan(-accel / 9.80665);
+	double average = reduce(roll.begin(), roll.end()) / roll.size();
+	if (abs(average - theta) > 0.001) {
+		cerr << "Pinned body inertial deflection should be " << theta
+		     << " but it is " << average << endl;
+		return false;
+	}
+
+	err = MoorDyn_Close(system);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure closing Moordyn: " << err << endl;
+		return false;
+	}
+
+	cout << setprecision(4) << "Average roll is " << average << endl;
+	cout << setprecision(4) << "Theoretical roll is " << theta << endl;
+
+	return true;
+}
+
+bool
+bodyDrag(SeriesWriter* series_writer)
+{
+	int err;
+	cout << endl << " => " << __PRETTY_FUNC_NAME__ << "..." << endl;
+
+	MoorDyn system = MoorDyn_Create("Mooring/body_tests/bodyDrag.txt");
+	if (!system) {
+		cerr << "Failure Creating the Mooring system" << endl;
+		return false;
+	}
+
+	unsigned int n_dof;
+	err = MoorDyn_NCoupledDOF(system, &n_dof);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure getting NCoupledDOF: " << err << endl;
+		return false;
+	}
+	if (n_dof != 0) {
+		cerr << "Expected 0 DOFs but got " << n_dof << endl;
+		return false;
+	}
+
+	double f[3];
+
+	err = MoorDyn_Init(system, nullptr, nullptr);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure during the mooring initialization: " << err << endl;
+		return false;
+	}
+
+	if (!write_system_vtk(system, 0, series_writer)) {
+		return false;
+	}
+
+	double t = 0, dt = 0.1;
+	double max_t = 5;
+	while (t < max_t) {
+		// do one outer time step just to make sure everything is settled
+		err = MoorDyn_Step(system, nullptr, nullptr, f, &t, &dt);
+		if (err != MOORDYN_SUCCESS) {
+			cerr << "Failure during the mooring dynamics: " << err << endl;
+			return false;
+		}
+
+		if (!write_system_vtk(system, t, series_writer)) {
+			return false;
+		}
+	}
+
+	err = MoorDyn_Close(system);
+	if (err != MOORDYN_SUCCESS) {
+		cerr << "Failure closing Moordyn: " << err << endl;
+		return false;
+	}
+
+	return true;
+}
+
 /** @brief Runs all the test
  * @return 0 if the tests have ran just fine. The index of the failing test
  * otherwise
@@ -435,6 +619,23 @@ main(int, char**)
 		return 3;
 	}
 
+	try {
+		// SeriesWriter series_writer;
+		if (!pinnedBody(NULL)) {
+			// series_writer.writeJson("../../vtk_out/");
+			return 3;
+		}
+		// series_writer.writeJson("../../vtk_out/");
+
+	} catch (std::exception& e) {
+		cerr << "pinnedBody failed with exception " << e.what() << endl;
+		return 3;
+	}
+
+	if (!bodyDrag(NULL)) {
+		return 2;
+	}
+
 	cout << "bodies.cpp passed successfully" << endl;
 	return 0;
 }
diff --git a/tests/c_api.c b/tests/c_api.c
new file mode 100644
index 00000000..c55e4597
--- /dev/null
+++ b/tests/c_api.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2022 Jose Luis Cercos-Pita <jlc@core-marine.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @file c_api.c
+ * Check that the C API can be called
+ */
+
+#include "MoorDyn.h"
+#include "MoorDyn2.h"
+#include <stdio.h>
+
+int
+main(int narg, char** arg)
+{
+	int ret_code;
+	int n;
+	unsigned int un;
+	double t=0, dt=0;
+	double d;
+
+	// MoorDyn.h
+	ret_code = MoorDynInit(NULL, NULL, "nofile");
+	if (ret_code != MOORDYN_UNHANDLED_ERROR) {
+		printf("MoorDynInit() test failed...");
+		return 255;
+	}
+	ret_code = MoorDynStep(NULL, NULL, NULL, &t, &dt);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDynStep() test failed...");
+		return 255;
+	}
+	ret_code = MoorDynClose();
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDynClose() test failed...");
+		return 255;
+	}
+	ret_code = externalWaveKinInit();
+	if (ret_code != MOORDYN_SUCCESS) {
+		printf("externalWaveKinInit() test failed...");
+		return 255;
+	}
+	getWaveKinCoordinates(NULL);
+	setWaveKin(NULL, NULL, t);
+	ret_code = GetFairTen(0);
+	if (ret_code != MOORDYN_INVALID_INPUT_FILE) {
+		printf("GetFairTen() test failed...");
+		return 255;
+	}
+	ret_code = GetFASTtens(&n, NULL, NULL, NULL, NULL);
+	if (ret_code != MOORDYN_MEM_ERROR) {
+		printf("GetFASTtens() test failed...");
+		return 255;
+	}
+	ret_code = GetPointForce(0, NULL);
+	if (ret_code != MOORDYN_MEM_ERROR) {
+		printf("GetPointForce() test failed...");
+		return 255;
+	}
+	ret_code = GetNodePos(0, 0, NULL);
+	if (ret_code != MOORDYN_MEM_ERROR) {
+		printf("GetNodePos() test failed...");
+		return 255;
+	}
+	ret_code = DrawWithGL();
+	if (ret_code != MOORDYN_MEM_ERROR) {
+		printf("DrawWithGL() test failed...");
+		return 255;
+	}
+
+	// MoorDyn2.h
+	MoorDyn system = MoorDyn_Create("nofile");
+	if (system) {
+		printf("MoorDyn_Create() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_NCoupledDOF(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_NCoupledDOF() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetVerbosity(NULL, n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetVerbosity() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetLogFile(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetLogFile() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetLogLevel(NULL, n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetLogLevel() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Log(NULL, n, "nomsg");
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Log() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Init(NULL, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Init() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Init_NoIC(NULL, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Init_NoIC() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Step(NULL, NULL, NULL, NULL, &t, &dt);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Step() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Close(NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Close() test failed...");
+		return 255;
+	}
+	MoorDynWaves waves = MoorDyn_GetWaves(NULL);
+	if (waves) {
+		printf("MoorDyn_GetWaves() test failed...");
+		return 255;
+	}
+	MoorDynSeafloor seafloor = MoorDyn_GetSeafloor(NULL);
+	if (seafloor) {
+		printf("MoorDyn_GetSeafloor() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_ExternalWaveKinInit(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_ExternalWaveKinInit() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_ExternalWaveKinGetN(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_ExternalWaveKinGetN() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_ExternalWaveKinGetCoordinates(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_ExternalWaveKinGetCoordinates() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetWaveKinCoordinates(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetWaveKinCoordinates() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_ExternalWaveKinSet(NULL, NULL, NULL, t);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_ExternalWaveKinSet() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetWaveKin(NULL, NULL, NULL, t);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetWaveKin() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetNumberBodies(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetNumberBodies() test failed...");
+		return 255;
+	}
+	MoorDynBody body = MoorDyn_GetBody(NULL, un);
+	if (body) {
+		printf("MoorDyn_GetBody() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetNumberRods(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetNumberRods() test failed...");
+		return 255;
+	}
+	MoorDynRod rod = MoorDyn_GetRod(NULL, un);
+	if (rod) {
+		printf("MoorDyn_GetRod() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetNumberPoints(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetNumberPoints() test failed...");
+		return 255;
+	}
+	MoorDynPoint point = MoorDyn_GetPoint(NULL, un);
+	if (point) {
+		printf("MoorDyn_GetPoint() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetNumberLines(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetNumberLines() test failed...");
+		return 255;
+	}
+	MoorDynLine line = MoorDyn_GetLine(NULL, un);
+	if (line) {
+		printf("MoorDyn_GetLine() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetFASTtens(NULL, &n, NULL, NULL, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetFASTtens() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Serialize(NULL, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Serialize() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Deserialize(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Deserialize() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Save(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Save() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_Load(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_Load() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_DrawWithGL(NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_DrawWithGL() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SaveVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_SaveVTK() test failed...");
+		return 255;
+	}
+
+	// Body.h
+	ret_code = MoorDyn_GetBodyID(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyID() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyType(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyType() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyState(NULL, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyState() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyPos(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyPos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyAngle(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyAngle() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyVel(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyVel() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyAngVel(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyAngVel() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyForce(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyForce() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyM(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyM() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyAngVel(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyPos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetBodyAngVel(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetBodyPos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SaveBodyVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_SaveBodyVTK() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_UseBodyVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_UseBodyVTK() test failed...");
+		return 255;
+	}
+
+	// Line.h
+	ret_code = MoorDyn_GetLineID(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineID() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineN(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineN() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNumberNodes(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNumberNodes() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineUnstretchedLength(NULL, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineUnstretchedLength() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetLineUnstretchedLength(NULL, d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetLineUnstretchedLength() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SetLineUnstretchedLengthVel(NULL, d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_SetLineUnstretchedLengthVel() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodePos(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodePos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeVel(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodePos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeForce(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeTen(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeBendStiff(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeWeight(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeDrag(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeFroudeKrilov(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeSeabedForce(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeCurv(NULL, un, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeCurv() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineNodeM(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineNodeTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineFairTen(NULL, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineFairTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetLineMaxTen(NULL, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetLineMaxTen() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SaveLineVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_SaveLineVTK() test failed...");
+		return 255;
+	}
+
+	// Point.h
+	ret_code = MoorDyn_GetPointID(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointID() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointType(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointType() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointPos(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointPos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointVel(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointVel() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointForce(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointForce() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointM(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointM() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointNAttached(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointNAttached() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetPointAttached(NULL, un, NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetPointAttached() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SavePointVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_SavePointVTK() test failed...");
+		return 255;
+	}
+
+	// Rod.h
+	ret_code = MoorDyn_GetRodID(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodID() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodType(NULL, &n);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodType() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodForce(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodForce() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodM(NULL, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodM() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodN(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodN() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodNumberNodes(NULL, &un);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodNumberNodes() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetRodNodePos(NULL, un, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetRodNodePos() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_SaveRodVTK(NULL, "nofile");
+	if (ret_code != MOORDYN_INVALID_VALUE && ret_code != MOORDYN_NON_IMPLEMENTED) {
+		printf("MoorDyn_SaveRodVTK() test failed...");
+		return 255;
+	}
+
+	// Seafloor.h
+	ret_code = MoorDyn_GetDepthAt(NULL, 0.0, 0.0, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetDepthAt() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetAverageDepth(NULL, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetAverageDepth() test failed...");
+		return 255;
+	}
+	ret_code = MoorDyn_GetMinDepth(NULL, &d);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetMinDepth() test failed...");
+		return 255;
+	}
+
+	// Waves.h
+	ret_code = MoorDyn_GetWavesKin(NULL, 0.0, 0.0, 0.0, NULL, NULL, &d, &d, NULL);
+	if (ret_code != MOORDYN_INVALID_VALUE) {
+		printf("MoorDyn_GetWavesKin() test failed...");
+		return 255;
+	}
+	double k = WaveNumber(0.0, 0.0, 0.0);
+
+	return 0;
+}
diff --git a/tests/csv_parser.h b/tests/csv_parser.h
new file mode 100644
index 00000000..0cea81ef
--- /dev/null
+++ b/tests/csv_parser.h
@@ -0,0 +1,368 @@
+/* MIT License
+ *
+ * Copyright (c) 2017 Aria Fallah
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef ARIA_CSV_H
+#define ARIA_CSV_H
+
+#include <fstream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace aria {
+  namespace csv {
+    enum class Term { CRLF = -2 };
+    enum class FieldType { DATA, ROW_END, CSV_END };
+    using CSV = std::vector<std::vector<std::string>>;
+
+    // Checking for '\n', '\r', and '\r\n' by default
+    inline bool operator==(const char c, const Term t) {
+      switch (t) {
+        case Term::CRLF:
+          return c == '\r' || c == '\n';
+        default:
+          return static_cast<char>(t) == c;
+      }
+    }
+
+    inline bool operator!=(const char c, const Term t) {
+      return !(c == t);
+    }
+
+    // Wraps returned fields so we can also indicate
+    // that we hit row endings or the end of the csv itself
+    struct Field {
+      explicit Field(FieldType t): type(t), data(nullptr) {}
+      explicit Field(const std::string& str): type(FieldType::DATA), data(&str) {}
+
+      FieldType type;
+      const std::string *data;
+    };
+
+    // Reads and parses lines from a csv file
+    class CsvParser {
+    private:
+      // CSV state for state machine
+      enum class State {
+        START_OF_FIELD,
+        IN_FIELD,
+        IN_QUOTED_FIELD,
+        IN_ESCAPED_QUOTE,
+        END_OF_ROW,
+        EMPTY
+      };
+      State m_state = State::START_OF_FIELD;
+
+      // Configurable attributes
+      char m_quote = '"';
+      char m_delimiter = ',';
+      Term m_terminator = Term::CRLF;
+      std::istream& m_input;
+
+      // Buffer capacities
+      static constexpr int FIELDBUF_CAP = 1024;
+      static constexpr int INPUTBUF_CAP = 1024 * 128;
+
+      // Buffers
+      std::string m_fieldbuf{};
+      std::unique_ptr<char[]> m_inputbuf = std::unique_ptr<char[]>(new char[INPUTBUF_CAP]{});
+
+      // Misc
+      bool m_eof = false;
+      size_t m_cursor = INPUTBUF_CAP;
+      size_t m_inputbuf_size = INPUTBUF_CAP;
+      std::streamoff m_scanposition = -INPUTBUF_CAP;
+    public:
+      // Creates the CSV parser which by default, splits on commas,
+      // uses quotes to escape, and handles CSV files that end in either
+      // '\r', '\n', or '\r\n'.
+      explicit CsvParser(std::istream& input): m_input(input) {
+        // Reserve space upfront to improve performance
+        m_fieldbuf.reserve(FIELDBUF_CAP);
+        if (!m_input.good()) {
+          throw std::runtime_error("Something is wrong with input stream");
+        }
+      }
+
+      // Change the quote character
+      CsvParser&& quote(char c) noexcept {
+        m_quote = c;
+        return std::move(*this);
+      }
+
+      // Change the delimiter character
+      CsvParser&& delimiter(char c) noexcept {
+        m_delimiter = c;
+        return std::move(*this);
+      }
+
+      // Change the terminator character
+      CsvParser&& terminator(char c) noexcept {
+        m_terminator = static_cast<Term>(c);
+        return std::move(*this);
+      }
+
+      // The parser is in the empty state when there are
+      // no more tokens left to read from the input buffer
+      bool empty() {
+        return m_state == State::EMPTY;
+      }
+
+      // Not the actual position in the stream (its buffered) just the
+      // position up to last availiable token
+      std::streamoff position() const
+      {
+          return m_scanposition + static_cast<std::streamoff>(m_cursor);
+      }
+
+      // Reads a single field from the CSV
+      Field next_field() {
+        if (empty()) {
+          return Field(FieldType::CSV_END);
+        }
+        m_fieldbuf.clear();
+
+        // This loop runs until either the parser has
+        // read a full field or until there's no tokens left to read
+        for (;;) {
+          char *maybe_token = top_token();
+
+          // If we're out of tokens to read return whatever's left in the
+          // field and row buffers. If there's nothing left, return null.
+          if (!maybe_token) {
+            m_state = State::EMPTY;
+            return !m_fieldbuf.empty() ? Field(m_fieldbuf) : Field(FieldType::CSV_END);
+          }
+
+          // Parsing the CSV is done using a finite state machine
+          char c = *maybe_token;
+          switch (m_state) {
+            case State::START_OF_FIELD:
+              m_cursor++;
+              if (c == m_terminator) {
+                handle_crlf(c);
+                m_state = State::END_OF_ROW;
+                return Field(m_fieldbuf);
+              }
+
+              if (c == m_quote) {
+                m_state = State::IN_QUOTED_FIELD;
+              } else if (c == m_delimiter) {
+                return Field(m_fieldbuf);
+              } else {
+                m_state = State::IN_FIELD;
+                m_fieldbuf += c;
+              }
+
+              break;
+
+            case State::IN_FIELD:
+              m_cursor++;
+              if (c == m_terminator) {
+                handle_crlf(c);
+                m_state = State::END_OF_ROW;
+                return Field(m_fieldbuf);
+              }
+
+              if (c == m_delimiter) {
+                m_state = State::START_OF_FIELD;
+                return Field(m_fieldbuf);
+              } else {
+                m_fieldbuf += c;
+              }
+
+              break;
+
+            case State::IN_QUOTED_FIELD:
+              m_cursor++;
+              if (c == m_quote) {
+                m_state = State::IN_ESCAPED_QUOTE;
+              } else {
+                m_fieldbuf += c;
+              }
+
+              break;
+
+            case State::IN_ESCAPED_QUOTE:
+              m_cursor++;
+              if (c == m_terminator) {
+                handle_crlf(c);
+                m_state = State::END_OF_ROW;
+                return Field(m_fieldbuf);
+              }
+
+              if (c == m_quote) {
+                m_state = State::IN_QUOTED_FIELD;
+                m_fieldbuf += c;
+              } else if (c == m_delimiter) {
+                m_state = State::START_OF_FIELD;
+                return Field(m_fieldbuf);
+              } else {
+                m_state = State::IN_FIELD;
+                m_fieldbuf += c;
+              }
+
+              break;
+
+            case State::END_OF_ROW:
+              m_state = State::START_OF_FIELD;
+              return Field(FieldType::ROW_END);
+
+            case State::EMPTY:
+              throw std::logic_error("You goofed");
+          }
+        }
+      }
+    private:
+      // When the parser hits the end of a line it needs
+      // to check the special case of '\r\n' as a terminator.
+      // If it finds that the previous token was a '\r', and
+      // the next token will be a '\n', it skips the '\n'.
+      void handle_crlf(const char c) {
+        if (m_terminator != Term::CRLF || c != '\r') {
+          return;
+        }
+
+        char *token = top_token();
+        if (token && *token == '\n') {
+          m_cursor++;
+        }
+      }
+
+      // Pulls the next token from the input buffer, but does not move
+      // the cursor forward. If the stream is empty and the input buffer
+      // is also empty return a nullptr.
+      char* top_token() {
+        // Return null if there's nothing left to read
+        if (m_eof && m_cursor == m_inputbuf_size) {
+          return nullptr;
+        }
+
+        // Refill the input buffer if it's been fully read
+        if (m_cursor == m_inputbuf_size) {
+          m_scanposition += static_cast<std::streamoff>(m_cursor);
+          m_cursor = 0;
+          m_input.read(m_inputbuf.get(), INPUTBUF_CAP);
+
+          // Indicate we hit end of file, and resize
+          // input buffer to show that it's not at full capacity
+          if (m_input.eof()) {
+            m_eof = true;
+            m_inputbuf_size = static_cast<size_t>(m_input.gcount());
+
+            // Return null if there's nothing left to read
+            if (m_inputbuf_size == 0) {
+              return nullptr;
+            }
+          }
+        }
+
+        return &m_inputbuf[m_cursor];
+      }
+    public:
+      // Iterator implementation for the CSV parser, which reads
+      // from the CSV row by row in the form of a vector of strings
+      class iterator {
+      public:
+        using difference_type = std::ptrdiff_t;
+        using value_type = std::vector<std::string>;
+        using pointer = const std::vector<std::string>*;
+        using reference = const std::vector<std::string>&;
+        using iterator_category = std::input_iterator_tag;
+
+        explicit iterator(CsvParser *p, bool end = false): m_parser(p) {
+          if (!end) {
+            m_row.reserve(50);
+            m_current_row = 0;
+            next();
+          }
+        }
+
+        iterator& operator++() {
+          next();
+          return *this;
+        }
+
+        iterator operator++(int) {
+          iterator i = (*this);
+          ++(*this);
+          return i;
+        }
+
+        bool operator==(const iterator& other) const {
+          return m_current_row == other.m_current_row
+            && m_row.size() == other.m_row.size();
+        }
+
+        bool operator!=(const iterator& other) const {
+          return !(*this == other);
+        }
+
+        reference operator*() const {
+          return m_row;
+        }
+
+        pointer operator->() const {
+          return &m_row;
+        }
+      private:
+        value_type m_row{};
+        CsvParser *m_parser;
+        int m_current_row = -1;
+
+        void next() {
+          value_type::size_type num_fields = 0;
+          for (;;) {
+            auto field = m_parser->next_field();
+            switch (field.type) {
+              case FieldType::CSV_END:
+                if (num_fields < m_row.size()) {
+                  m_row.resize(num_fields);
+                }
+                m_current_row = -1;
+                return;
+              case FieldType::ROW_END:
+                if (num_fields < m_row.size()) {
+                  m_row.resize(num_fields);
+                }
+                m_current_row++;
+                return;
+              case FieldType::DATA:
+                if (num_fields < m_row.size()) {
+                  m_row[num_fields] = std::move(*field.data);
+                } else {
+                  m_row.push_back(std::move(*field.data));
+                }
+                num_fields++;
+            }
+          }
+        }
+      };
+
+      iterator begin() { return iterator(this); };
+      iterator end() { return iterator(this, true); };
+    };
+  }
+}
+#endif 
diff --git a/tests/pendulum.cpp b/tests/pendulum.cpp
index e0ae1b6f..8593ef3b 100644
--- a/tests/pendulum.cpp
+++ b/tests/pendulum.cpp
@@ -147,7 +147,7 @@ pendulum()
 	}
 
 	for (unsigned int i = 0; i < t_peaks.size(); i++) {
-		CHECK_VALUE("T", t_peaks[i], i * 0.5 * T, 0.02 * T, t_peaks[i]);
+		CHECK_VALUE("T", t_peaks[i], i * 0.5 * T, 0.025 * T, t_peaks[i]);
 		CHECK_VALUE("X", fabs(x_peaks[i]), x0, 0.01 * x0, t_peaks[i]);
 	}
 
diff --git a/tests/polyester.cpp b/tests/polyester.cpp
new file mode 100644
index 00000000..6ac5863c
--- /dev/null
+++ b/tests/polyester.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2024 Jose Luis Cercos-Pita <jlc@core-marine.com> and Matt Hall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @file polyester.cpp
+ * Minimal tests that only checks the library is correctly initialized,
+ * running and closing
+ */
+
+#include <cmath>
+#include <iostream>
+#include <fstream>
+#include <deque>
+#include "MoorDyn2.h"
+#include "csv_parser.h"
+#include <catch2/catch_test_macros.hpp>
+
+
+#define MBL 25.3e6
+#define KRS 13.4
+#define KRD1 16.0
+#define KRD2 0.35
+#define TC 200.0
+// The tensions expected, on inverse order
+#define TTIMES { 320.0, 640.0, 960.0, 1280.0, 1599.0 }
+#define TMEANS { 0.1, 0.2, 0.3, 0.4, 0.5 }
+
+
+double vec_norm(const double v[3])
+{
+	return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+double get_average_tension(MoorDynLine line,
+                           MoorDynPoint anchor,
+                           MoorDynPoint fairlead)
+{
+	unsigned int n;
+	REQUIRE(MoorDyn_GetLineN(line, &n) == MOORDYN_SUCCESS);
+	// To average the tensions along the line, we are considering the
+	// connections instead of the line end up nodes, as long as the latter are
+	// not that accurate
+	double tension = 0.0;
+	double force[3];
+	REQUIRE(MoorDyn_GetPointForce(anchor, force) == MOORDYN_SUCCESS);
+	tension += vec_norm(force);
+	REQUIRE(MoorDyn_GetPointForce(fairlead, force) == MOORDYN_SUCCESS);
+	tension += vec_norm(force);
+	for (unsigned int i = 0; i < n; i++) {
+		REQUIRE(MoorDyn_GetLineNodeTen(line, i, force) == MOORDYN_SUCCESS);
+		tension += vec_norm(force);
+	}
+	return tension / (n + 1);
+}
+
+TEST_CASE("Ramp up, stabilization and cycles")
+{
+	MoorDyn system = MoorDyn_Create("Mooring/polyester/simple.txt");
+	REQUIRE(system);
+	unsigned int n_dof;
+	REQUIRE(MoorDyn_NCoupledDOF(system, &n_dof) == MOORDYN_SUCCESS);
+	REQUIRE(n_dof == 3);
+
+	double r[3], dr[3];
+	auto anchor = MoorDyn_GetPoint(system, 1);
+	REQUIRE(anchor);
+	auto fairlead = MoorDyn_GetPoint(system, 2);
+	REQUIRE(fairlead);
+	auto line = MoorDyn_GetLine(system, 1);
+	REQUIRE(line);
+
+	double l0;
+	REQUIRE(MoorDyn_GetLineUnstretchedLength(line, &l0) == MOORDYN_SUCCESS);
+
+	REQUIRE(MoorDyn_GetPointPos(fairlead, r) == MOORDYN_SUCCESS);
+	std::fill(dr, dr + 3, 0.0);
+	REQUIRE(MoorDyn_Init(system, r, dr) == MOORDYN_SUCCESS);
+
+	// Read the csv with the motions
+	std::ifstream csv("Mooring/polyester/motion.csv");
+	aria::csv::CsvParser csv_parser(csv);
+
+	std::vector<double> tdata, xdata;
+	unsigned int skip = 2;
+	for (auto& row : csv_parser) {
+		if (skip > 0) {
+			skip--;
+			continue;
+		}
+		if (row.size() < 2)
+			continue;
+		tdata.push_back(std::stod(row.at(0)));
+		xdata.push_back(std::stod(row.at(1)));
+	}
+
+	// Time to move
+	double t = 0.0, dt;
+	// Data about the lenght and stiffness recomputation as a function of time
+	std::deque<double> times;  
+	std::deque<double> tensions;  
+	std::deque<double> ttimes(TTIMES);
+	std::deque<double> tmeans(TMEANS);
+	for (unsigned int i = 0; i < tdata.size(); i++) {
+		REQUIRE(MoorDyn_GetPointPos(fairlead, r) == MOORDYN_SUCCESS);
+		double t_dst = tdata.at(i);
+		double dt = t_dst - t;
+		double x_dst = xdata.at(i);
+		dr[0] = (x_dst - r[0]) / dt;
+		double f[3];
+		REQUIRE(MoorDyn_Step(system, r, dr, f, &t, &dt) == MOORDYN_SUCCESS);
+		times.push_back(t);
+		tensions.push_back(get_average_tension(line, anchor, fairlead));
+
+		// Let's check and tweak the line if there is info enough
+		if (times.back() - times.front() < TC)
+			continue;
+
+		double tension = 0.0;
+		for (auto f : tensions)
+			tension += f;
+		tension /= tensions.size();
+		times.pop_front();
+		tensions.pop_front();
+
+		double ks = KRS * MBL;
+		double kd = (KRD1 + KRD2 * tension / MBL * 100) * MBL;
+		double l = l0 * (1.0 + tension / ks) / (1.0 + tension / kd);
+		REQUIRE(
+			MoorDyn_SetLineConstantEA(line, kd) == MOORDYN_SUCCESS);
+		REQUIRE(
+			MoorDyn_SetLineUnstretchedLength(line, l) == MOORDYN_SUCCESS);
+
+		if (t >= ttimes.front()) {
+			const double tmean = tmeans.front();
+			ttimes.pop_front();
+			tmeans.pop_front();
+			REQUIRE(
+				fabs(tension / MBL - tmean) < 0.025);
+		}
+	}
+
+	REQUIRE(MoorDyn_Close(system) == MOORDYN_SUCCESS);
+}
diff --git a/tests/test_generator.py b/tests/test_generator.py
index a13b46d3..2589449d 100644
--- a/tests/test_generator.py
+++ b/tests/test_generator.py
@@ -1,3 +1,6 @@
+import sys
+print('HERE')
+print(sys.path)
 from unittest import TestCase, main as unittest_main
 import moordyn
 
diff --git a/tests/test_minimal.py b/tests/test_minimal.py
index a475fe49..7d668109 100644
--- a/tests/test_minimal.py
+++ b/tests/test_minimal.py
@@ -1,3 +1,5 @@
+import sys
+print(sys.path)
 from unittest import TestCase, main as unittest_main
 import os
 import tempfile