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.

Curtailments can also be added to WTGs (both NewWTGs and ExistingWTGs). A list of Curtailments can be given for each turbine. It is possible to control if the turbine is shut down or reduced with the property OperationMode. ApiCOMShutdown for shutting down the turbine and ApiCOMPowerCurve for using a power curve specified in ActionUID. For WTGs with power matrix the UniqueID in the WTG file (as accessible from the WTG explorer Service) is referring the the whole power matrix. For those turbines the ActionUID needs to be the combined string of the UniqueID and ModeName. Curtailment conditions are specified in (CurtailmentCondition) with a starting (ConditionFrom) and end (ConditionTo) date. Conditions associated with dates and time need to be given as floats.

Date related values in curtailment.

Date-time

ApiCCDateTime

Days since 30.12.1899

Time

ApiCCTime

Hour of day / 24h

Sunrise and Sunset

ApiCCSunriseSunset

See ObjWtgService.GetSunriseAndSunsetValues

Date

ApiCCDate

Days since 30.12.1899

Weekday

ApiCCWeekday

0 (Monday) to 6 (Sunday)

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

"""
Copyright 2024 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.1'), 'New Salem', '4.1')
project_path = os.path.join(working_dir, 'New Salem.w41p')

_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)

Find below an example showing the use of curtailments from Scripting.

"""
Copyright 2024 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 datetime import datetime
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.1'), 'New Salem', '4.1')
project_path = os.path.join(working_dir, 'New Salem.w41p')

_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)


## Curtailment helper functions
# Function for changing the date time into format for windPRO (same as excel)
def excel_date(date1):
    temp = datetime(1899, 12, 30)    # Note, not 31st Dec but 30th!
    delta = date1 - temp
    return float(delta.days) + (float(delta.seconds) / 86400)

# For chaning curtailments by type
def change_curtailment(curtailment, curtailment_type, condition_from, condition_to):
    for c in curtailment.CurtailmentConditions.TApiWtgCurtailmentCondition:
        if c.CurtailmentCondition == curtailment_type:
            c.ConditionFrom = condition_from
            c.ConditionTo = condition_to
            return
    raise KeyError(f'Could not find {curtailment_type}.')

def delete_curtailment(curtailment, curtailment_type):
    for i, c in enumerate(curtailment.CurtailmentConditions.TApiWtgCurtailmentCondition):
        if c.CurtailmentCondition == curtailment_type:
            curtailment.CurtailmentConditions.TApiWtgCurtailmentCondition.pop(i)
            return
    raise KeyError(f'Could not find {curtailment_type}.')

## Adding predefined curtailment
# Curtailment template for birds
newCurtailment = obj_wtg_service.GetCurtailmentTemplate('ApiCTBirds')
# Adjusting curtailment
newCurtailment.Name = 'Bird from default'
delete_curtailment(newCurtailment, 'ApiCCTemperature')
delete_curtailment(newCurtailment, 'ApiCCWindDirection')
delete_curtailment(newCurtailment, 'ApiCCWindSpeed')
# Time of day is set by fraction of 24 h values
change_curtailment(newCurtailment, 'ApiCCTime', 5/24, 7/24)
# Dates need to be set as days since 30th December 1899 like in excel. The year does not matter but needs to be given.
change_curtailment(newCurtailment, 'ApiCCDate', excel_date(datetime(2020, 3, 1)), excel_date(datetime(2020, 5, 1)))
# Times before sunset and sunrise are set by integer values.
# A list is available from the wind scripting api
print(obj_wtg_service.GetSunriseAndSunsetValues())
# using '0:45h before sunset: 19955', '0:45h after sunset: 20045'
change_curtailment(newCurtailment, 'ApiCCSunriseSunset', 19955, 20045)

wtg_obj = obj_wtg_service.GetWtgObject(objs[0].Handle)
# Adding curtailment list if None is present
if wtg_obj.Curtailment is None:
    TApiWtgCurtailments = factory.TApiWtgCurtailments()
    wtg_obj.Curtailment = TApiWtgCurtailments

wtg_obj.Curtailment.TApiWtgCurtailment.append(newCurtailment)
nan_to_skipvalue(wtg_obj)
obj_wtg_service.SetWtgObject(wtg_obj)

## Adding Curtailment with reduction in the mode
# Finding a power curve
print(wtg_explorer_service.GetWtgFromFile(filename=wtg_obj.Filename, details=True))

# Using similar settings as above
change_curtailment(newCurtailment, 'ApiCCDate', excel_date(datetime(2020, 8, 1)), excel_date(datetime(2020, 10, 1)))
newCurtailment.OperationMode = 'ApiCOMPowerCurve'
newCurtailment.ActionUID = '{6B20F289-1E7F-430D-AD71-C3A2D617ED38}'
newCurtailment.Name = 'Scripting reduced power curve'

wtg_obj = obj_wtg_service.GetWtgObject(objs[0].Handle)
# Adding curtailment list if None is present
if wtg_obj.Curtailment is None:
    TApiWtgCurtailments = factory.TApiWtgCurtailments()
    wtg_obj.Curtailment = TApiWtgCurtailments

wtg_obj.Curtailment.TApiWtgCurtailment.append(newCurtailment)
nan_to_skipvalue(wtg_obj)
obj_wtg_service.SetWtgObject(wtg_obj)

## Adding freely defined curtailment
newCurtailment = obj_wtg_service.GetCurtailmentTemplate('ApiCTOther')
newCurtailment.Name = f'Freely defined from Scripting'

# Making conditions
condition = factory.TApiWtgCurtailmentCondition()
condition.CurtailmentCondition = 'ApiCCDateTime'
condition.ConditionFrom = excel_date(datetime(2024, 8, 1))
condition.ConditionTo = excel_date(datetime(2020, 8, 31))

TApiWtgCurtailmentConditions = factory.TApiWtgCurtailmentConditions()
TApiWtgCurtailmentConditions.TApiWtgCurtailmentCondition.append(condition)

newCurtailment.CurtailmentConditions = TApiWtgCurtailmentConditions

wtg_obj = obj_wtg_service.GetWtgObject(objs[0].Handle)
# Adding curtailment list if None is present
if wtg_obj.Curtailment is None:
    TApiWtgCurtailments = factory.TApiWtgCurtailments()
    wtg_obj.Curtailment = TApiWtgCurtailments

wtg_obj.Curtailment.TApiWtgCurtailment.append(newCurtailment)
nan_to_skipvalue(wtg_obj)
obj_wtg_service.SetWtgObject(wtg_obj)

Find below and example for using curtailments using information from a shadow calculation and adding all the the curtailments from that calculation:

"""
Copyright 2024 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
import numpy as np
from windproapi import WindProApi, nan_to_skipvalue
from windproapi import compare_objects
from windproapi.utils import get_windpro_sample_path
from datetime import datetime

def get_obj_from_list(objs, variable, value):
    l_obj = []
    for o in objs:
        if o[variable] == value:
            l_obj.append(o)
    if len(l_obj) == 0:
        raise ValueError(f'Could not find {value} in any {variable}.')
    elif len(l_obj) == 1:
        return l_obj[0]
    else:
        raise ValueError(f'More than one variable {variable} has value {value}.')


_windproapi = WindProApi()
_windproapi.start_windpro_random_port()

# This folder needs to exist on your machine
working_dir = os.path.join(get_windpro_sample_path('4.1'), 'ParkCurtailment')
os.makedirs(working_dir, exist_ok=True)
project_path = os.path.join(working_dir, 'ParkCurtailment.w41p')

# Get to all services
online_data_service = _windproapi.get_service('OnlineDataService')
project_service = _windproapi.get_service('ProjectService')
objects_service = _windproapi.get_service('ObjectsService')
calculation_service = _windproapi.get_service('CalculationService')
obj_wtg_service = _windproapi.get_service('ObjWtgService')
windpro_service = _windproapi.get_service('WindproService')
calc_ZVI_service = _windproapi.get_service('CalcZVIService')
wtg_explorer_service = _windproapi.get_service('WtgExplorerService')
obj_elevation_grid_service = _windproapi.get_service('ObjElevationGridService')
calc_decibel_service = _windproapi.get_service('CalcDecibelService')
obj_NSA_service = _windproapi.get_service('ObjNSAService')
calc_shadow_service = _windproapi.get_service('CalcShadowService')
calc_park_service = _windproapi.get_service('CalcParkService')
obj_site_data_service = _windproapi.get_service('ObjSiteDataService')
scaler_service = _windproapi.get_service('ScalerService')
factory = _windproapi.get_factory('WindproService')

# Position of the project
lng = 13
lat = 57

# Wind Farm locations in m from the site center
wtg_easting = [2500 + (i % 5) * 500 for i in range(20)]
wtg_northing = [-4000 + np.floor(i / 5) * 500 + (i % 5) * 100 for i in range(20)]

# Make a new project
project_service.NewProject(lng=lng, lat=lat, filename=project_path)

# Setting the project coordinate system to UTM
utm_epsg = project_service.GetUtmWgs84EpsgForProject()
project_service.SetProjectCoorEPSG(epsg=utm_epsg)

# Map data #
# Adding a layer for terrain data
layer_terrain = objects_service.AddLayer(layerName='Terrain data', parentFolderHandle=0)
objects_service.SetEditLayer(handle=layer_terrain)

# Getting elevation data sets
terrain_data_type = 'ODSTerrainGrid'
online_data_service.PrepareService(dataType=terrain_data_type, lat=lat, lon=lng)
terrain_services = online_data_service.GetServices(dataType=terrain_data_type)
print(terrain_services)
# Choosing a specific dataset and downloading the data
terrain_service_id = 'SWE50_Grid'
terrain_handle = online_data_service.DownloadHeightData(dataType=terrain_data_type,
                                                        implId=terrain_service_id,
                                                        userDesc='Terrain data grid',
                                                        lat=lat,
                                                        lon=lng,
                                                        width=20000,
                                                        height=20000,
                                                        useAsTin=True)

# Adding forest layers
forest_layer = objects_service.AddLayer(layerName='Forest data', parentFolderHandle=0)
terrain_data_type = 'ODSObjHeightsGrid'
online_data_service.PrepareService(dataType=terrain_data_type, lat=lat, lon=lng)
available_data = online_data_service.GetServices(dataType=terrain_data_type)
handle_forest = online_data_service.DownloadHeightData(dataType=terrain_data_type,
                                                       implId='SLU_FOREST_TREEHEIGHTS_ServiceID',
                                                       userDesc='Forest Data',
                                                       lat=lat,
                                                       lon=lng,
                                                       width=10000,
                                                       height=10000,
                                                       useAsTin=False)

# Adding roughness layer
riughness_layer = objects_service.AddLayer(layerName='Forest data', parentFolderHandle=0)
roughness_data_type = 'ODSRoughnessGridToLine'
online_data_service.PrepareService(dataType=roughness_data_type, lat=lat, lon=lng)
roughness_service = online_data_service.GetServices(dataType=roughness_data_type)
roughness_handle = online_data_service.DownloadRoughnessData(dataType=roughness_data_type,
                                                             implId='DataService_Rou_grid_GlobCover',
                                                             userDesc='Roughness data',
                                                             lat=lat,
                                                             lon=lng,
                                                             width=30000,
                                                             height=30000)

meteo_layer = objects_service.AddLayer(layerName='Meteo', parentFolderHandle=0)
# Getting data for an observed wind climate. This is form the list of above
dataType = 'ODSClimate'
online_data_service.PrepareService(dataType=dataType, lat=lat, lon=lng)
# The GetService returns a list with dertails about all available data.
meteo_services = online_data_service.GetServices(dataType=dataType)
# Use the service ID to download the data
print(meteo_services)
meteo_handles = online_data_service.DownloadMeteoData(implId='sicerra',
                                                      lat=lat,
                                                      lon=lng,
                                                      maxDist=30000,
                                                      numPoints=1,
                                                      fromYear=2020,
                                                      toYear=2020)

# Adding a site data object
siteData = objects_service.AddObject(apiObjType='SiteData',
                                     lat=lat,
                                     lng=lng,
                                     userDesc='STATGEN SiteData from scripting')
obj_site_data_service.SetPurpose(handle=siteData.Handle, purpose='SdPurpStatgen')
obj_site_data_service.TerrainLinkElevationAndRoughnessLine(handle=siteData.Handle,
                                                           elevHandle=terrain_handle,
                                                           rouLineHandle=roughness_handle)




# Adding wind turbines
windfarm_layer = objects_service.AddLayer(layerName='Wind Farm', parentFolderHandle=0)
# Seaching for Vestas turbines. 0 indicate to take all not an actual filtering
wtgs = wtg_explorer_service.GetWtgsWithFilter(manufactor='VESTAS',
                                              minRatedPower=0,
                                              maxRatedPower=0,
                                              minHubHeight=0,
                                              maxHubHeight=0,
                                              minRotorDiameter=0,
                                              maxRotorDiameter=0)
wtg = [w for w in wtgs if 'VESTAS V90 2000 90.0 !O!' == w.DataName][0]



# Converting coordinate systems
utm_zone = project_service.GetUtmWgs84EpsgForProject()
# Converting from latitude/longitude using this for coordinate transformations
utm_center = windpro_service.ConvertCoorEPSG(x=lng,
                                             y=lat,
                                             inEpsg=4326,
                                             toEpsg=utm_zone)
# Converted coordinates are equivalent to result from ConvertCoorEPSG
print('Easting: {:.1f}m, Northing: {:.1f}m'.format(utm_center.X, utm_center.Y))

for i, i_east, i_northing in zip(range(len(wtg_easting)), wtg_easting, wtg_northing):
    position = windpro_service.ConvertCoorEPSG(x=utm_center.X + i_east,
                                               y=utm_center.Y + i_northing,
                                               inEpsg=utm_zone,
                                               toEpsg=4326)
    print('Longitude: {:.3f}, Latitude: {:.3f}'.format(position.X, position.Y))
    new_obj = objects_service.AddObject(apiObjType='NewWTG',
                                        lat=position.Y,
                                        lng=position.X,
                                        userDesc='New WTG obj')
    wtgObj = obj_wtg_service.GetWtgObject(new_obj.Handle)
    wtgObj.Filename = wtg.FileName
    wtgObj.Hubheight = wtg.DefHubHeight
    wtgObj.UserDescription = 'Turbine' + str(i + 1).zfill(2)
    wtgObj.UserLabel = 'Turbine ' + str(i + 1).zfill(2)
    nan_to_skipvalue(wtgObj)
    res = obj_wtg_service.SetWtgObject(wtgObj)

wtgids = factory.TApiWtgIds()
for w in objects_service.GetObjects(apiObjType='NewWTG'):
    dummy = factory.TApiWtgId()
    dummy.Handle = w.Handle
    dummy.Rowindex = 0
    wtgids['TApiWtgId'].append(dummy)


# Adding two shadow receptors representable for the two near by towns
# Keeping them to default settings fro simplicity
objects_service.AddObject(apiObjType='Shadow', lat=56.971983, lng=13.072742, userDesc='House 1')
objects_service.AddObject(apiObjType='Shadow', lat=56.965227, lng=13.063942, userDesc='House 2')
objects_service.AddObject(apiObjType='Shadow', lat=56.973484, lng=13.061792, userDesc='House 3')

# Creading shadow calcution
shadow_handle = calculation_service.CreateEmpty(calcType='CalcShadow')
calculation_service.OpenCalcForEdit(handle=shadow_handle)
calc_shadow = calc_shadow_service.GetShadowCalc()

# Add settings to a map as well
calc_shadow.Name = 'Script Generated'
calc_shadow.MapCalc = True
calc_shadow.WidthWest = 1000
calc_shadow.WidthEast = 7000
calc_shadow.HeightSouth = 7000
calc_shadow.HeightNorth = 1000

# Adding all shadow receptors to the calculation
receptors = factory.TApiWtgIds()
for obj in objects_service.GetObjects(apiObjType='Shadow'):
    receptor = factory.TApiWtgId()
    receptor.Handle = obj.Handle
    receptor.Rowindex = -1
    receptors.TApiWtgId.append(receptor)
calc_shadow.Receptors = receptors

# Adding all wtg objects
calc_shadow.NewWtgs = wtgids

nan_to_skipvalue(calc_shadow)
calc_shadow_service.SetShadowCalc(calc_shadow)
calculation_service.CloseCalc(save=True)
calculation_service.Calculate(handle=shadow_handle)

# Saving the shadow calculations
file_shadow_periods = os.path.join(working_dir, 'shadow_calendar', 'shadow_period.txt')
os.makedirs(os.path.dirname(file_shadow_periods), exist_ok=True)
calculation_service.ResultToFile(shadow_handle, 'Periods per WTG', file_shadow_periods)

# Turbine in the shadow caluculation dictionary
wtg_dict = {objects_service.GetObjectFromHandle(d['Handle'])['UserDescription']:d['Handle'] for d in calc_shadow.NewWtgs.TApiWtgId}

# Reading the shadow calculations
# You might need to change the format here to match what your file is
import pandas as pd
df = pd.read_csv(file_shadow_periods, sep='\t')
df['From'] = pd.to_datetime(df['From'], format='mixed') #, format='%Y-%m-%d %H:%M:%S'
df['To'] = pd.to_datetime(df['To'], format='mixed') #, format='Y-%m-%d %H:%M:%S')

# Function for changing the date time into format for windPRO (same as excel)
def excel_date(date1):
    temp = datetime(1899, 12, 30)    # Note, not 31st Dec but 30th!
    delta = date1 - temp
    return float(delta.days) + (float(delta.seconds) / 86400)

# Looping through the
for unique_wtg_str in df['WTG'].unique():
    handle_temp = None
    for key in wtg_dict:
        if key in unique_wtg_str:
            if handle_temp is None:
                handle_temp = wtg_dict[key]
            else:
                ValueError(f'Turbine {unique_wtg_str} in shadow result to file not unique.')

    df_temp = df[df['WTG']==unique_wtg_str]
    wtgReducedMode = obj_wtg_service.GetWtgObject(handle_temp)

    # Looping through all curtailment conditions
    TApiWtgCurtailments = factory.TApiWtgCurtailments()
    for i, row in df_temp.iterrows():
        newCurtailment = obj_wtg_service.GetCurtailmentTemplate('ApiCTOther')
        newCurtailment.Name = f'Shadow from {row["From"]} to {row["To"]}'

        # Making conditions
        condition = factory.TApiWtgCurtailmentCondition()
        condition.CurtailmentCondition = 'ApiCCDateTime'
        # Using the excel date time definition
        condition.ConditionFrom = excel_date(row['From'])
        condition.ConditionTo = excel_date(row['To'])

        TApiWtgCurtailmentConditions = factory.TApiWtgCurtailmentConditions()
        TApiWtgCurtailmentConditions.TApiWtgCurtailmentCondition.append(condition)

        newCurtailment.CurtailmentConditions = TApiWtgCurtailmentConditions
        TApiWtgCurtailments.TApiWtgCurtailment.append(newCurtailment)
    wtgReducedMode.Curtailment = TApiWtgCurtailments

    nan_to_skipvalue(wtgReducedMode)
    obj_wtg_service.SetWtgObject(wtgReducedMode)

# New PARK calculation
handle_park = calculation_service.CreateEmpty(calcType='CalcPark')
calculation_service.OpenCalcForEdit(handle=handle_park)
park_calc = calc_park_service.GetParkCalc()

# ParkTypeSiteData is a WAsP resource calculation together with a PARK calculation.
park_calc.ParkCalcType = 'ParkTypeTimeVaryingMeasure'
park_calc.WakeModelType = 'ParkWakeModelNOJensenPark2'
park_calc.Name = 'With Curtailment{}'.format(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))

# Turn on Curtailment
park_calc.UseCurtailment = True

# Meteo objects setups
TApiScalerMetItems = factory.TApiScalerMetItems()
TApiScalerMetItem = factory.TApiScalerMetItem()
MeteoSignalUIDs = factory.TApiObjectHandles()
MeteoShearSignalUIDs = factory.TApiObjectHandles()
MeteoSignalUIDs.int.append(1)
MeteoSignalUIDs.int.append(2)
MeteoShearSignalUIDs.int.append(1)
MeteoShearSignalUIDs.int.append(2)
TApiScalerMetItem.MeteoHandle = meteo_handles[0]
TApiScalerMetItem.MeteoSignalUIDs = MeteoSignalUIDs
TApiScalerMetItem.MeteoShearSignalUIDs = MeteoShearSignalUIDs
TApiScalerMetItems.TApiScalerMetItem.append(TApiScalerMetItem)
park_calc.TimevarData.ScalingData.ScalerMetItems = TApiScalerMetItems

# Scaling setup
default_scalars = scaler_service.GetScalers()
mast_scaler = get_obj_from_list(default_scalars, 'Name', 'EMD Default Measurement Mast Scaler')
park_calc.TimevarData.ScalingData.ScalerHandle = mast_scaler.Handle
park_calc.TimevarData.ScalingData.MaxWeight = 1.0
park_calc.TimevarData.ScalingData.DistancePower = 2.0

# Making an array of WTGs that can be added to the park
wtgids = factory.TApiWtgIds()
for w in objects_service.GetObjects(apiObjType='NewWTG'):
    dummy = calc_park_service.GetTApiWtgId()
    dummy.Handle = w.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=handle_park)