WTG Object Service

The ObjWtgService is giving access to wind turbine generator objects. To make a new wtg object, you can use the ObjectService. Then getting the properties of the wtg with the ObjWtgService.GetWtgObject. These properties can be manipulated and then send back to windPRO with ObjWtgService.SetWtgObject. Adding a path with a wtg file is enough to do a basic calculation. More advanced manipulation is not possible at the moment. Properties of the object can be found here TApiObjWtg.

It is possible to change the power and noise curves that are used in a turbine. Each curve is identified by a unique ID. To get access to those you need to open the wtg file with WTG explorer Service. You can do that with WtgExplorerService.GetWtgFromFile(filename=file_name, details=True) displaying all information that is stored inside the wtg (though not the actual power curve values). You need to assign the right UID to identify the choice of power curve, noise data etc.

Which value to modify to choose power curve, noise data etc.

UniqueID WtgExplorerService

Where to assign in TApiObjWtg

WTGDTPowerCurve

Powercurve

WTGDTNoise

Noisedata

WTGDTVisual

Noisedata

WTGDTeGrid

Noisedata

WTGDTAny

WTGDTPCNoiseList

PCNoise

WTGDTPowerMatrix

PowerMatrix

WTGDTVisual3D

Additionally, you need to set the boolean flags correctly to correctly set the power or noise curves.

  • For any choice that is not using default power curves use: UseDefault = False.

  • For using classical powre curves use: LegacyMode = True and PowerMatrixMode = False.

  • For power noice pairs format use: LegacyMode = False and PowerMatrixMode = False.

  • For Powermatrix format use: LegacyMode = False and PowerMatrixMode = True.

Find below a working example that can be run on the test project New Salem.

"""
Copyright 2023 EMD International
License for this script: MIT https://opensource.org/license/mit/
License for windPRO commercial software: https://www.emd-international.com/contact-us/general-terms-conditions-sale/
"""

import os
from windproapi.utils import get_windpro_sample_path
from windproapi import WindProApi
from windproapi import nan_to_skipvalue

# Opening windPRO
_windproapi = WindProApi()
working_dir = os.path.join(get_windpro_sample_path('4.0'), 'New Salem\\4.0')
project_path = os.path.join(working_dir, 'New Salem.w40p')

_windproapi.start_windpro_random_port()

# Services
obj_wtg_service = _windproapi.get_service('ObjWtgService')
project_service = _windproapi.get_service('ProjectService')
objects_service = _windproapi.get_service('ObjectsService')
wtg_explorer_service = _windproapi.get_service('WtgExplorerService')
calculation_service = _windproapi.get_service('CalculationService')
calc_park_service = _windproapi.get_service('CalcParkService')
factory = _windproapi.get_factory('WindproService')

# Loading New Salem project
project_service.LoadFromFile(filename=project_path)

# Getting wind turbine generators already present
objs = objects_service.GetObjects(apiObjType='NewWTG')
wtg_obj = obj_wtg_service.GetWtgObject(objs[0].Handle)
print(wtg_obj)

# Adding a new wtg object
# Making a new object
new_obj = objects_service.AddObject(apiObjType='NewWTG',
                                    lat=wtg_obj.Lat + 0.01,
                                    lng=wtg_obj.Lng + 0.01,
                                    userDesc='New WTG obj')
wtg_obj = obj_wtg_service.GetWtgObject(new_obj.Handle)
wtg_obj.UserLabel = 'New WTG obj'

path_to_WTG = os.path.join(os.path.dirname(__file__), '../data/SIEMENS SWT-2.3_test.wtg')
wtg_obj.Filename = path_to_WTG
# Hub height needs to be given explicitely
wtg_obj.Hubheight = 92.6
# Needed to handle None values when communicating with zeep
nan_to_skipvalue(wtg_obj)
# Set everything back into the wtg object
obj_wtg_service.SetWtgObject(wtg_obj)

## Adding new turbines
# Getting all Vestas turbines from turbine catalogue between 2000kw and 2500kW and rotor diameter between 90m and 110m
list_possible_wtgs = wtg_explorer_service.GetWtgsWithFilter(manufactor='VESTAS',
                                                            minRatedPower=3000,
                                                            maxRatedPower=3500,
                                                            minHubHeight=0,
                                                            maxHubHeight=0,
                                                            minRotorDiameter=120,
                                                            maxRotorDiameter=150)
wtg_alternative = list_possible_wtgs[0]

wtg_details = wtg_explorer_service.GetWtgFromFile(filename=wtg_alternative.FileName,
                                                  details=True)

# Finding all objects in the WTG's layer
vestas_wtg_layer = [o for o in objects_service.GetLayers() if o.Name=="WTG's"][0]
objects_service.AddLayer(layerName=wtg_details.DataName, parentFolderHandle=0)

# Looping over all turbines the WTG's layer and making new wtgs with this wtg type
vestas_wtg_handles = []
for handle in vestas_wtg_layer.ObjectHandles.int:
    obj = objects_service.GetObjectFromHandle(handle)
    if obj.ApiObjType == 'NewWTG':
        new_obj = objects_service.AddObject(apiObjType='NewWTG',
                                            lat=obj.Lat,
                                            lng=obj.Lng,
                                            userDesc='New WTG obj')
        vestas_wtg_handles.append(new_obj.Handle)
        # Getting object and modifying data
        wtg_obj = obj_wtg_service.GetWtgObject(handle=new_obj.Handle)
        wtg_obj.UserLabel = "Vestas " + obj.UserDescription
        wtg_obj.UserDescription = "Vestas " + obj.UserDescription
        wtg_obj.Hubheight = wtg_details.DefHubHeight
        wtg_obj.Filename = wtg_details.FileName
        nan_to_skipvalue(wtg_obj)
        obj_wtg_service.SetWtgObject(wtg_obj)

## Cloning a park calculation and making a new calculation with all the Vestas turbines
# Getting all PARK calculations in
all_park_calcs = calculation_service.GetCalcs(calcType='CalcPark')
vestas_park_handle = calculation_service.Clone(handle=all_park_calcs[1].Handle)

calculation_service.OpenCalcForEdit(handle=vestas_park_handle)
park_calc = calc_park_service.GetParkCalc()

park_calc.Name = "Vestas turbines"

wtgids = factory.TApiWtgIds()
for handle in vestas_wtg_handles:
    dummy = factory.TApiWtgId()
    dummy.Handle = handle
    dummy.Rowindex = 0
    wtgids['TApiWtgId'].append(dummy)

# Adding turbines
park_calc.NewWtgs = wtgids
# No existing wtgs
park_calc.ExistWtgs = factory.TApiWtgIds()

nan_to_skipvalue(park_calc)
calc_park_service.SetParkCalc(park_calc)
calculation_service.CloseCalc(save=True)
calculation_service.Calculate(handle=vestas_park_handle)

## Choosing one of the turbine and calculating with all power curves (noice reduced modes)
# Turbine handle to choose:
noise_reduction_turbine_handle = vestas_wtg_handles[0]
noise_reduction_turbine_user_descr = objects_service.GetObjectFromHandle(noise_reduction_turbine_handle).UserDescription
print(f"Reducing noise mode for {noise_reduction_turbine_user_descr}")

# Looking through all curves and getting power curves
list_power_curve_details = []
for entry in wtg_details.DetailDatas.TApiWtgDetailData:
    if entry.DetailType == "WTGDTPowerCurve" and not entry.Invalid:
        list_power_curve_details.append(entry)

# Looping over all power curves found
for entry in list_power_curve_details:
    print(f"Power curve: {entry.Name}")

    # Changing power curve for wtg object
    wtg_obj = obj_wtg_service.GetWtgObject(noise_reduction_turbine_handle)
    wtg_obj.Powercurve = entry.UniqueID
    wtg_obj.UseDefault = False
    nan_to_skipvalue(wtg_obj)
    obj_wtg_service.SetWtgObject(wtg_obj)

    # Making a new PARK calculation for each power curve
    cloned_park_handle = calculation_service.Clone(handle=vestas_park_handle)
    calculation_service.OpenCalcForEdit(handle=cloned_park_handle)
    park_calc = calc_park_service.GetParkCalc()

    park_calc.Name = "Vestas turbines PC: " + entry.Name

    nan_to_skipvalue(park_calc)
    calc_park_service.SetParkCalc(park_calc)
    calculation_service.CloseCalc(save=True)
    calculation_service.Calculate(handle=cloned_park_handle)

    try:
        import pandas as pd

        # Exporting results from the wind farm
        path_result = os.path.join(working_dir, f'park_result_vestas_{entry.Name}.csv')
        calculation_service.ResultToFile(handle=cloned_park_handle, id='Park result', filename=path_result)

        # CAUTION: this can fail depending on the setup of the computer to use , or . as decimal seperators.
        # Or depending on your language setup the names of the columns might be different.
        df = pd.read_csv(path_result, sep=';', thousands='.', decimal=',', header=1, skiprows=[2],
                         usecols=['Row data/Description', 'Free mean wind speed', 'Result'],
                         dtype={'Row data/Description': str, 'Free mean wind speed': float, 'Result': float}, encoding = "ISO-8859-1")

        print("Farm AEP: {:.2f} mWh/y".format(df["Result"].sum()))
    except:
        print('Could not read results file.')

# Changing turbine back to default power curves
# Changing power curve for wtg object
wtg_obj = obj_wtg_service.GetWtgObject(noise_reduction_turbine_handle)
wtg_obj.UseDefault = True
nan_to_skipvalue(wtg_obj)
obj_wtg_service.SetWtgObject(wtg_obj)