Table of Contents

Plot a turbine in 3D using Python

The samples presented in this article are a series of functions and scripts that will plot a turbine assembly and it's components in 3D. The component scripts will work with any of the following components:

  • Tower: plot_tower.py
  • Hub: plot_hub.py
  • Drivetrain and Nacelle: plot_drivetrain_and_nacelle.py
  • Blade: plot_blade.py

The final sample connects (plot_assembly.py) all the components and plots in a full 3D turbine assembly.

The scripts in their supplied form plot the IEA 15MW turbine (NG_IEA_15_Onshore_SteadyOP.json). They can be used with other turbines, but they need to have the same components and the same structure. For different structures and extra components the user will have to change the files themselves.

Prerequisites

  1. Latest version of Bladed Next Gen and Samples installed including the Bladed Python packages.
  2. Licence for Bladed Next Gen (request a licence).
  3. Python 3.9 to 3.12 installed.

What you will learn

  • Identifying the JSON input file geometry properties and what they mean in a 3D space.

How to use this tutorial

To use this tutorial it is advised to download the samples. The package contains all required content to complete this tutorial.

Make sure to check the Get started article on how to setup your machine for the tutorials.

Switch to the samples folder and run each of the Python scripts described in this article.

Plot a tower

"""
plot_tower.py

This example script demonstrates how to plot the Tower component of the provided NG_IEA_15_Onshore_SteadyOP turbine model.
It showcases how to extract input data and visualize it using 3D plots.

Please note that this script is specific to the NG_IEA_15_Onshore_SteadyOP model and may not be compatible with other turbine models.

Requirements:
- tower_plot.py
- dnv_bladed_models (imported as models)
- numpy
- matplotlib

Usage:
1. Ensure that the required components (tower_plot.py, dnv_bladed_models) are available.
2. Make sure the required files and the NG_IEA_15_Onshore_SteadyOP.json are in the same folder.
3. Run the script to visualize the Tower component in a 3D plot.
"""

import os
import dnv_bladed_models as models
import matplotlib.pyplot as plt
import numpy as np

def plot_tower(figure_axis, tower):
    # Plots a tower structure using 3D surface plots for each can section.

    # Extract information from the Tower object
    can_list = tower.Cans
    first_can = True
    tower_height = 0

    # Iterate through each can in the list
    for i_can, can in enumerate(can_list):
        can_height = can.CanHeight
        # Calculate starting point and previous height
        if first_can:
            first_can = False
            can_starting_point = 0
            previous_height = can_height
        else:
            can_starting_point = previous_height
            previous_height = can_starting_point + can_height

        # Calculate base and top radii
        base_outer_radius = can.BaseSection.OutsideDiameter / 2
        base_inner_radius = can.BaseSection.OutsideDiameter / 2 - can_list[0].BaseSection.WallThickness

        if hasattr(can_list[0], 'TopSection'):
            if not can_list[0].TopSection is None:
                top_outer_radius = can.TopSection.OutsideDiameter / 2
                top_inner_radius = can.TopSection.OutsideDiameter / 2 - can_list[0].TopSection.WallThickness
            else:
                top_outer_radius = base_outer_radius
                top_inner_radius = base_inner_radius
        else:
            top_outer_radius = base_outer_radius
            top_inner_radius = base_inner_radius

        tower_height = tower_height + can.CanHeight

        # Plot surfaces for outer and inner radii
        x_grid, y_grid, z_grid = can_surface(base_outer_radius, top_outer_radius, can_starting_point, can_height)

        figure_axis.plot_surface(x_grid, y_grid, z_grid, alpha=0.5, color='grey')
        figure_axis.text(x_grid[0][0], y_grid[0][0], z_grid[0][0], 'Can '+str(i_can))

        x_grid, y_grid, z_grid = can_surface( base_inner_radius, top_inner_radius, can_starting_point, can_height)

        figure_axis.plot_surface(x_grid, y_grid, z_grid, alpha=0.5, color='k')

    # Set plot limits
    x_max = max(x_grid.max(), y_grid.max(), z_grid.max())
    plt.xlim([-x_max / 2, x_max / 2])
    plt.ylim([-x_max / 2, x_max / 2])
    figure_axis.set_zlim(0, x_max)

    # Plot assembly nodes
    figure_axis.scatter3D(0, 0, 0, marker='v', color="green")
    assembly_node = np.array([0, 0, tower_height])
    figure_axis.scatter3D(assembly_node[0], assembly_node[1], assembly_node[2], marker='^', color="green")

    return assembly_node


def can_surface(base_radius, top_radius, can_starting_point, can_height):
    # Generates a 3D surface representation of a truncated cone (can) using parametric equations

    # Create an array of z values along the height of the can
    z = np.linspace(can_starting_point, can_starting_point+can_height, 5)

    # Create an array of theta values (angle around the z-axis)
    theta = np.linspace(0, 2*np.pi, 50)

    # Create a grid of theta and z values
    theta_grid, z_grid = np.meshgrid(theta, z)

    # Calculate the varying radius along the height
    radius = base_radius + (top_radius - base_radius) * z_grid / can_height

    # Calculate x and y coordinates using polar coordinates
    x_grid = radius*np.cos(theta_grid)
    y_grid = radius*np.sin(theta_grid)

    return x_grid, y_grid, z_grid


if __name__ == "__main__":

    # Specify required folders, relative to this script
    examples_models_path = os.path.dirname(__file__)

    # read in a full analysis from file
    analysis_model_file_path = os.path.join(examples_models_path, 'NG_IEA_15_Onshore_SteadyOP.json')

    # Extract the components of the project file
    Components = models.BladedAnalysis.from_file(analysis_model_file_path).ComponentDefinitions

    # Figure settings
    fig, figure_axis = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(15, 10))
    figure_axis.set(xlabel='X-axis', ylabel='Y-axis', zlabel='Z-axis')
    figure_axis.set_title("Tower Component")

    # Plots the tower
    plot_tower(figure_axis, Components['IEA15Tower'])

    plt.show()

3D_tower.png

Figure 1: IEA 15MW tower plot.

Plot a nacelle and drivetrain

"""
plot_drivetrain_and_nacelle.py

This example script demonstrates how to plot the DrivetrainAndNacelle component of the provided NG_IEA_15_Onshore_SteadyOP turbine model.
It showcases how to extract input data and visualize it using 3D plots.

Please note that this script is specific to the NG_IEA_15_Onshore_SteadyOP model and may not be compatible with other turbine models.

Requirements:
- drivetrain_and_nacelle_plot.py
- dnv_bladed_models (imported as models)
- numpy
- scipy
- matplotlib

Usage:
1. Ensure that the required components (drivetrain_and_nacelle_plot.py, dnv_bladed_models) are available.
2. Make sure the required files and the NG_IEA_15_Onshore_SteadyOP.json are in the same folder.
3. Run the script to visualize the DrivetrainAndNacelle component in a 3D plot.
"""

import os
import dnv_bladed_models as models
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial.transform import Rotation as R
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

def create_box_points_and_faces(centre, length, width, height, initial_coordinates):
    # Creates points and faces for a 3D box (cuboid) given its center, dimensions, and initial coordinates.

    half_length = length / 2
    half_width = width / 2
    half_height = height / 2

    # Define the 8 corner points of the box
    points = np.array([
        [centre[0] - half_length, centre[1] - half_width, centre[2] - half_height],
        [centre[0] + half_length, centre[1] - half_width, centre[2] - half_height],
        [centre[0] + half_length, centre[1] + half_width, centre[2] - half_height],
        [centre[0] - half_length, centre[1] + half_width, centre[2] - half_height],
        [centre[0] - half_length, centre[1] - half_width, centre[2] + half_height],
        [centre[0] + half_length, centre[1] - half_width, centre[2] + half_height],
        [centre[0] + half_length, centre[1] + half_width, centre[2] + half_height],
        [centre[0] - half_length, centre[1] + half_width, centre[2] + half_height]
    ]) + initial_coordinates

    # Define the faces of the box (each face as a list of points)
    faces = [[points[0], points[1], points[2], points[3]],
             [points[4], points[5], points[6], points[7]],
             [points[0], points[1], points[5], points[4]],
             [points[2], points[3], points[7], points[6]],
             [points[1], points[2], points[6], points[5]],
             [points[4], points[7], points[3], points[0]]]

    return points, faces


def plot_nacelle(figure_axis, drivetrain_and_nacelle, initial_coordinates=np.array([0.0, 0.0, 0.0])):
    # PLots the nacelle assembly in 3D space

    # Specify dimensions (width, length, height)
    width = drivetrain_and_nacelle.NacelleCover.Width
    length = drivetrain_and_nacelle.NacelleCover.Length
    height = drivetrain_and_nacelle.NacelleCover.Height

    # Specify the centre point (x, y, z). Assumes the geometric centre coincides with the centre of pressure.
    centre = (drivetrain_and_nacelle.NacelleCover.CentreOfPressure.X,
              drivetrain_and_nacelle.NacelleCover.CentreOfPressure.Y,
              drivetrain_and_nacelle.NacelleCover.CentreOfPressure.Z)  # centre position (x, y, z)

    # Specify hub assembly point and direction
    hub_assembly_point = np.zeros(3)
    hub_assembly_point[0] = - drivetrain_and_nacelle.PositionOfHubCentre.Overhang
    hub_assembly_point[1] = drivetrain_and_nacelle.PositionOfHubCentre.SideOffset
    hub_assembly_point[2] = drivetrain_and_nacelle.PositionOfHubCentre.HeightOffset

    # Tilt rotation matrix
    hub_assembly_tilt = R.from_euler('y', drivetrain_and_nacelle.PositionOfHubCentre.RotorTilt).as_matrix()

    # Compute the eight corner points (vertices) and faces
    points, faces = create_box_points_and_faces(centre, length, width, height, initial_coordinates)

    # Plot the parallelepiped
    figure_axis.scatter3D(*points.T, color='red', alpha=0)  # Plot vertices

    # Plot the faces
    figure_axis.add_collection3d(Poly3DCollection(faces, facecolors='grey', linewidths=1, edgecolors='black', alpha=0.25))

    # Plot previous assembly point
    figure_axis.scatter3D(*initial_coordinates, color='g', marker='v')

    # Rotate hub pitch:
    shaft_unit_vector = hub_assembly_tilt.dot(np.array([1.0, 0.0, 0.0]))

    # Start location of the low speed shaft
    shaft_start = np.array([0.0, 0.0, 0.0])
    shaft_start[0] = hub_assembly_point[0] + (length/2-hub_assembly_point[0]) * shaft_unit_vector[0]
    shaft_start[1] = hub_assembly_point[1]
    shaft_start[2] = hub_assembly_point[2] + (length/2-hub_assembly_point[0]) * shaft_unit_vector[2]

    # Rotate as to horizontal of nacelle:
    shaft_start = shaft_start + initial_coordinates

    # Rotate assembly
    hub_assembly_point = hub_assembly_point + initial_coordinates

    # Plot next assembly point
    figure_axis.scatter3D(hub_assembly_point[0], hub_assembly_point[1], hub_assembly_point[2], color='g', marker='^')

    figure_axis.text(hub_assembly_point[0], hub_assembly_point[1], hub_assembly_point[2], 'Position Of Hub Centre')

    # Nacelle mid point
    midpoint = np.average(points, axis=0)

    figure_axis.text(midpoint[0], midpoint[1], midpoint[2], 'Nacelle')

    # Plot shaft
    figure_axis.plot([hub_assembly_point[0], shaft_start[0]], [hub_assembly_point[1], shaft_start[1]], zs=[hub_assembly_point[2], shaft_start[2]])

    plt.xlim([min(hub_assembly_point[0], initial_coordinates[1]-width/2, initial_coordinates[2]-height),
             max(initial_coordinates[0]+length/2, initial_coordinates[1]+width/2, initial_coordinates[2]+height)])
    plt.ylim([min(hub_assembly_point[0], initial_coordinates[1]-width/2, initial_coordinates[2]-height),
             max(initial_coordinates[0]+length/2, initial_coordinates[1]+width/2, initial_coordinates[2]+height)])
    figure_axis.set_zlim(min(hub_assembly_point[0], initial_coordinates[1]-width/2, initial_coordinates[2]-height), max(
        initial_coordinates[0]+length/2, initial_coordinates[1]+width/2, initial_coordinates[2]+height))

    return hub_assembly_point, hub_assembly_tilt


if __name__ == "__main__":

    # Specify required folders, relative to this script
    examples_models_path = os.path.dirname(__file__)

    # Read in a full analysis from file
    analysis_model_file_path = os.path.join(examples_models_path, 'NG_IEA_15_Onshore_SteadyOP.json')

    # Extract the components of the project file
    Components = models.BladedAnalysis.from_file(analysis_model_file_path).ComponentDefinitions

    # Figure settings
    fig, figure_axis = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(15, 10))
    figure_axis.set(xlabel='X-axis', ylabel='Y-axis', zlabel='Z-axis')
    figure_axis.set_title("DrivetrainAndNacelle Component")

    # Plots the nacelle and drivetrain low speed shaft
    plot_nacelle(figure_axis, Components['IEA15DrivetrainAndNacelle'])

    plt.show()

3D_nacelle_and_drivetrain.png

Figure 2: IEA 15MW nacelle and drivetrain.

Plot a blade hub

"""
plot_hub.py

This example script demonstrates how to plot the Hub component of the provided NG_IEA_15_Onshore_SteadyOP turbine model.
It showcases how to extract input data and visualize it using 3D plots.

Please note that this script is specific to the NG_IEA_15_Onshore_SteadyOP model and may not be compatible with other turbine models.

Requirements:
- hub_plot.py
- dnv_bladed_models (imported as models)
- numpy
- scipy
- matplotlib

Usage:
1. Ensure that the required components (hub_plot.py, dnv_bladed_models) are available.
2. Make sure the required files and the NG_IEA_15_Onshore_SteadyOP.json are in the same folder.
3. Run the script to visualize the Hub component in a 3D plot.
"""

import os
import dnv_bladed_models as models
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial.transform import Rotation as R

def plot_hub(figure_axis, hub, rotor_tilt=np.eye(3), initial_coordinates=np.array([0.0, 0.0, 0.0])):
    # Plots the hub and calculates the blade mounting points

    # Radius of the hub
    hub_radius = hub.Spinner.Diameter/2

    # Plot the hub as a sphere
    plot_half_sphere(figure_axis, hub_radius, initial_coordinates)

    blade_rotations = []
    blade_starting_points = []
    azimuth_step = 2*np.pi/hub.NumberOfBlades

    for i in range(0, hub.NumberOfBlades):
        # Calculates the blade mounting rotations, direction and the starting points
        
        # Calculate the rotation matrix to rotate due to different azimuth positions and coning rotation
        rot_azimuth = R.from_euler('x', azimuth_step*i).as_matrix()
        rot_cone = R.from_euler('y', - hub.MountingPoints.ConingAngle).as_matrix()
        
        # Final rotation matrix due to tilt, azimuth and coning angle
        blade_rotations.append(rotor_tilt.dot(rot_azimuth.dot(rot_cone)))

        blade_directions = blade_rotations[i].dot(np.array([0.0, 0.0, 1.0]))

        blade_starting_points.append(initial_coordinates + blade_directions * hub.MountingPoints.RadiusOfBladeConnection)

        # Plot blade connections
        figure_axis.plot([initial_coordinates[0], blade_starting_points[i][0]], [
                        initial_coordinates[1], blade_starting_points[i][1]], zs=[initial_coordinates[2], blade_starting_points[i][2]])

        # Plot blade starting points
        figure_axis.scatter3D(blade_starting_points[i][0], blade_starting_points[i][1], blade_starting_points[i][2], color='g', marker='^')

    # Plot hub center
    figure_axis.scatter3D(initial_coordinates[0], initial_coordinates[1], initial_coordinates[2], color='g', marker='v')

    return blade_starting_points, blade_rotations


def plot_half_sphere(figure_axis, radius, initial_coordinates):

    # Define the number of steps
    step = np.pi/10

    # Define the co-ordinates of points in the sphere
    phi = np.arange(0 , np.pi + step, step) # Only half of a shpere
    theta = np.arange(np.pi / 2, np.pi * 1.5 + step, step)
    phi, theta = np.meshgrid(phi, theta)

    # Convert spherical coordinates to cartesian coordinates
    x = radius * np.sin(phi) * np.cos(theta) + initial_coordinates[0]
    y = radius * np.sin(phi) * np.sin(theta) + initial_coordinates[1]
    z = radius * np.cos(phi) + initial_coordinates[2]

    # Create a 3D plot
    figure_axis.plot_surface(x, y, z, color='grey', alpha=0.5)

if __name__ == "__main__":

    # Specify required folders, relative to this script
    examples_models_path = os.path.dirname(__file__)

    # Read in a full analysis from file
    analysis_model_file_path = os.path.join(examples_models_path, 'NG_IEA_15_Onshore_SteadyOP.json')

    # Extract the components of the project file
    Components = models.BladedAnalysis.from_file(analysis_model_file_path).ComponentDefinitions

    # Figure settings
    fig, figure_axis = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(15, 10))
    figure_axis.set(xlabel='X-axis', ylabel='Y-axis', zlabel='Z-axis')
    figure_axis.set_title("IndependentPitchHub Component")

    plot_hub(figure_axis, Components['IEA15Hub'])

    plt.show()

3D_hub.png

Figure 3: IEA 15MW blade hub plot.

Plot a blade

"""
plot_blade.py

This example script demonstrates how to plot the Blade component of the provided NG_IEA_15_Onshore_SteadyOP turbine model.
It showcases how to extract input data and visualize it using 3D plots.

Please note that this script is specific to the NG_IEA_15_Onshore_SteadyOP model and may not be compatible with other turbine models.

Requirements:
- blade_plot.py
- dnv_bladed_models (imported as models)
- numpy
- scipy

Usage:
1. Ensure that the required components (blade_plot.py, dnv_bladed_models) are available.
2. Make sure the required files and the NG_IEA_15_Onshore_SteadyOP.json are in the same folder.
3. Run the script to visualize the Blade component in a 3D plot.
"""

import os
import numpy as np
import matplotlib.pyplot as plt
import dnv_bladed_models as models
from scipy.spatial.transform import Rotation as R

def plot_blade(figure_axis, blade, initial_coordinates=np.array([0, 0, 0]), blade_rotations=np.eye(3)):
    # Plots the blade given the blade data, initial coordinates and blade mounting angles
    # Transformations are based on the theory presented in the Bladed Next Gen Online Documentation: "Blade Coordinate Systems Theory"

    reference_systems = np.empty((0, 3))
    leading_edges = np.empty((0, 3))
    trailing_edges = np.empty((0, 3))
    quarter_chords = np.empty((0, 3))
    
    for blade_section in blade.SectionDefinitions:
        # Extract relevant data from blade section
        coordinate_system = blade_section.AxesAndCoordinateSystems
        blade_reference_axis = coordinate_system.ReferenceCoordinateSystem
        blade_aerodynamic_properties = blade_section.AerodynamicProperties
        blade_quarter_chord_axis = coordinate_system.QuarterChordCoordinateSystem

        # Extracts the unit vectors of the reference coordinate system
        unit_z_axis = np.array([blade_reference_axis.ZAxis.X, blade_reference_axis.ZAxis.Y, blade_reference_axis.ZAxis.Z])
        unit_y_axis = np.array([blade_reference_axis.YAxis.X, blade_reference_axis.YAxis.Y, blade_reference_axis.YAxis.Z])
        unit_x_axis = (np.cross(unit_y_axis, unit_z_axis)) / np.linalg.norm(np.cross(unit_y_axis, unit_z_axis))

        # Extract origin and rotation matrix from the blade root cordinate system to the reference coordinate system
        o_root_ref = np.array([blade_reference_axis.Origin.X, blade_reference_axis.Origin.Y, blade_reference_axis.Origin.Z])
        rotation_root_ref = np.column_stack((unit_x_axis, unit_y_axis, unit_z_axis))

        # Extract origin and rotation matrix from the blade reference cordinate system to the quarter chord coordinate system
        rotation_ref_quarter = R.from_euler('z', blade_quarter_chord_axis.RotationAboutReferenceZ).as_matrix()
        o_ref_quarter = np.array([blade_quarter_chord_axis.OffsetInReferenceX, blade_quarter_chord_axis.OffsetInReferenceY, 0.0])

        # Combines them into a rotation and translation from the root coordinate system to the quarter chord coordinate system
        rotation_root_quarter = rotation_root_ref.dot(rotation_ref_quarter)
        o_root_quarter = o_root_ref + rotation_root_ref.dot(o_ref_quarter)

        # Creates the transformation matrix for the QuarterChordCoordinateSystem
        f_qc = np.column_stack((rotation_root_quarter, o_root_quarter))
        f_qc = np.vstack((f_qc, np.array([0, 0, 0, 1])))

        # Uses the transformation matrix to calculate the location of the leading and trailing edge
        leading_edge_location = f_qc.dot(np.array([0, -0.25*blade_aerodynamic_properties.Chord, 0, 1]))[0:3]
        trailing_edge_location = f_qc.dot(np.array([0, 0.75*blade_aerodynamic_properties.Chord, 0, 1]))[0:3]
        quarter_chord_location = f_qc[0:3, 3]

        # Rotates the locations to account for the different azimuth positions and adds the initial positions to get them into global coordinates
        quarter_chord_location = blade_rotations.dot(quarter_chord_location) + initial_coordinates
        leading_edge_location = blade_rotations.dot(leading_edge_location) + initial_coordinates
        trailing_edge_location = blade_rotations.dot(trailing_edge_location) + initial_coordinates
        reference_system_location = blade_rotations.dot(o_root_ref) + initial_coordinates

        quarter_chords = np.vstack((quarter_chords, quarter_chord_location))
        leading_edges = np.vstack((leading_edges, leading_edge_location))
        trailing_edges = np.vstack((trailing_edges, trailing_edge_location))
        reference_systems = np.vstack((reference_systems, reference_system_location))

    # Plots the quarter chord location, trailing edge and the leading edge
    figure_axis.scatter3D(quarter_chords[:, 0], quarter_chords[:, 1], quarter_chords[:, 2], color='b', label='Quarter Chord')
    figure_axis.scatter3D(leading_edges[:, 0], leading_edges[:, 1], leading_edges[:, 2], color='r', label='Leading Edge')
    figure_axis.scatter3D(trailing_edges[:, 0], trailing_edges[:, 1], trailing_edges[:, 2], color='g', label='Trailing Edge')
    figure_axis.scatter3D(reference_systems[:, 0], reference_systems[:, 1], reference_systems[:, 2], color='c', label='Reference Coordinate System')
    
    # Update maximum coordinates for plot limits
    maximum = np.max(np.concatenate((quarter_chords, leading_edges, trailing_edges)))
    plt.xlim([-maximum/2, maximum/2])
    plt.ylim([-maximum/2, maximum/2])
    figure_axis.set_zlim(0, maximum)


if __name__ == "__main__":

    # Specify required folders, relative to this script
    examples_models_path = os.path.dirname(__file__)

    # Read in a full analysis from file
    analysis_model_file_path = os.path.join(examples_models_path, 'NG_IEA_15_Onshore_SteadyOP.json')

    # Extract the components of the project file
    Components = models.BladedAnalysis.from_file(analysis_model_file_path).ComponentDefinitions

    # Figure settings
    fig, figure_axis = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(15, 10))
    figure_axis.set(xlabel='X-axis', ylabel='Y-axis', zlabel='Z-axis')
    figure_axis.set_title("Blade Component")

    # Plots the blade
    plot_blade(figure_axis, Components['IEA15Blade'])
    figure_axis.legend(loc = 'upper left')
    
    plt.show()

3D_blade.png

Figure 4: IEA 15MW single blade plot.

Plot a turbine assembly

"""
plot_assembly.py

This example script demonstrates how to plot various components of the provided NG_IEA_15_Onshore_SteadyOP turbine model.
It showcases how to extract input data and visualize it using 3D plots.

Please note that this script is specific to the NG_IEA_15_Onshore_SteadyOP model and may not be compatible with other turbine models.

Requirements:
- blade_plot.py
- dnv_bladed_models (imported as models)
- drivetrain_and_nacelle_plot.py
- hub_plot.py
- tower_plot.py

Usage:
1. Ensure that the required components (blade_plot.py, dnv_bladed_models, drivetrain_and_nacelle_plot.py, hub_plot.py, tower_plot.py) are available.
2. Make sure the required files and the NG_IEA_15_Onshore_SteadyOP.json are in the same folder.
3. Run the script to visualize the turbine components in a 3D plot.
"""

import os
import matplotlib.pyplot as plt
import dnv_bladed_models as models
from plot_blade import plot_blade
from plot_drivetrain_and_nacelle import plot_nacelle
from plot_hub import plot_hub
from plot_tower import plot_tower

if __name__ == "__main__":

    # Specify required folders, relative to this script
    examples_models_path = os.path.dirname(__file__)

    # Read in a full analysis from file
    analysis_model_file_path = os.path.join(examples_models_path, 'NG_IEA_15_Onshore_SteadyOP.json')

    # Extract the components of the project file
    Components = models.BladedAnalysis.from_file(analysis_model_file_path).ComponentDefinitions

    # Figure settings
    fig, figure_axis = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(15, 10))
    figure_axis.set(xlabel='X-axis', ylabel='Y-axis', zlabel='Z-axis')
    figure_axis.set_title("Turbine assembly")
    figure_axis.view_init(elev=30, azim=225)  # Change the viewing angle

    # Utilising the tree-like structure of the Bladed Next Gen turbine assembly to plot the Tower and output the assembly node position for the DrivetrainAndNacelle
    assembly_node = plot_tower(figure_axis, Components['IEA15Tower'])

    # Plot the Nacelle and outputs the assembly node position for the Hub
    assembly_node, rotor_tilt = plot_nacelle(figure_axis, Components['IEA15DrivetrainAndNacelle'], assembly_node)

    # Plot the Hub and outputs the blade mounting points and mounting angles
    blade_starting_points, blade_rotations = plot_hub(figure_axis, Components['IEA15Hub'], rotor_tilt, assembly_node)

    # Plot the Blades
    for i, blade_start_point in enumerate(blade_starting_points):
        plot_blade(figure_axis, Components['IEA15Blade'], blade_start_point, blade_rotations=blade_rotations[i])

    # Only show the legends for the blade AxesAndCoordinateSystems
    handles, labels = plt.gca().get_legend_handles_labels()
    plt.legend(handles[:4], labels[:4], loc = 'upper left')

    plt.show()

3D_turbine_assembly.png

Figure 5: IEA 15MW turbine assembly plot.